Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/tools/clang-format/ClangFormat.cpp
35260 views
1
//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
2
//
3
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4
// See https://llvm.org/LICENSE.txt for license information.
5
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
//
7
//===----------------------------------------------------------------------===//
8
///
9
/// \file
10
/// This file implements a clang-format tool that automatically formats
11
/// (fragments of) C++ code.
12
///
13
//===----------------------------------------------------------------------===//
14
15
#include "../../lib/Format/MatchFilePath.h"
16
#include "clang/Basic/Diagnostic.h"
17
#include "clang/Basic/DiagnosticOptions.h"
18
#include "clang/Basic/FileManager.h"
19
#include "clang/Basic/SourceManager.h"
20
#include "clang/Basic/Version.h"
21
#include "clang/Format/Format.h"
22
#include "clang/Rewrite/Core/Rewriter.h"
23
#include "llvm/ADT/StringSwitch.h"
24
#include "llvm/Support/CommandLine.h"
25
#include "llvm/Support/FileSystem.h"
26
#include "llvm/Support/InitLLVM.h"
27
#include "llvm/Support/Process.h"
28
#include <fstream>
29
30
using namespace llvm;
31
using clang::tooling::Replacements;
32
33
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
34
35
// Mark all our options with this category, everything else (except for -version
36
// and -help) will be hidden.
37
static cl::OptionCategory ClangFormatCategory("Clang-format options");
38
39
static cl::list<unsigned>
40
Offsets("offset",
41
cl::desc("Format a range starting at this byte offset.\n"
42
"Multiple ranges can be formatted by specifying\n"
43
"several -offset and -length pairs.\n"
44
"Can only be used with one input file."),
45
cl::cat(ClangFormatCategory));
46
static cl::list<unsigned>
47
Lengths("length",
48
cl::desc("Format a range of this length (in bytes).\n"
49
"Multiple ranges can be formatted by specifying\n"
50
"several -offset and -length pairs.\n"
51
"When only a single -offset is specified without\n"
52
"-length, clang-format will format up to the end\n"
53
"of the file.\n"
54
"Can only be used with one input file."),
55
cl::cat(ClangFormatCategory));
56
static cl::list<std::string>
57
LineRanges("lines",
58
cl::desc("<start line>:<end line> - format a range of\n"
59
"lines (both 1-based).\n"
60
"Multiple ranges can be formatted by specifying\n"
61
"several -lines arguments.\n"
62
"Can't be used with -offset and -length.\n"
63
"Can only be used with one input file."),
64
cl::cat(ClangFormatCategory));
65
static cl::opt<std::string>
66
Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
67
cl::init(clang::format::DefaultFormatStyle),
68
cl::cat(ClangFormatCategory));
69
static cl::opt<std::string>
70
FallbackStyle("fallback-style",
71
cl::desc("The name of the predefined style used as a\n"
72
"fallback in case clang-format is invoked with\n"
73
"-style=file, but can not find the .clang-format\n"
74
"file to use. Defaults to 'LLVM'.\n"
75
"Use -fallback-style=none to skip formatting."),
76
cl::init(clang::format::DefaultFallbackStyle),
77
cl::cat(ClangFormatCategory));
78
79
static cl::opt<std::string> AssumeFileName(
80
"assume-filename",
81
cl::desc("Set filename used to determine the language and to find\n"
82
".clang-format file.\n"
83
"Only used when reading from stdin.\n"
84
"If this is not passed, the .clang-format file is searched\n"
85
"relative to the current working directory when reading stdin.\n"
86
"Unrecognized filenames are treated as C++.\n"
87
"supported:\n"
88
" CSharp: .cs\n"
89
" Java: .java\n"
90
" JavaScript: .mjs .js .ts\n"
91
" Json: .json\n"
92
" Objective-C: .m .mm\n"
93
" Proto: .proto .protodevel\n"
94
" TableGen: .td\n"
95
" TextProto: .txtpb .textpb .pb.txt .textproto .asciipb\n"
96
" Verilog: .sv .svh .v .vh"),
97
cl::init("<stdin>"), cl::cat(ClangFormatCategory));
98
99
static cl::opt<bool> Inplace("i",
100
cl::desc("Inplace edit <file>s, if specified."),
101
cl::cat(ClangFormatCategory));
102
103
static cl::opt<bool> OutputXML("output-replacements-xml",
104
cl::desc("Output replacements as XML."),
105
cl::cat(ClangFormatCategory));
106
static cl::opt<bool>
107
DumpConfig("dump-config",
108
cl::desc("Dump configuration options to stdout and exit.\n"
109
"Can be used with -style option."),
110
cl::cat(ClangFormatCategory));
111
static cl::opt<unsigned>
112
Cursor("cursor",
113
cl::desc("The position of the cursor when invoking\n"
114
"clang-format from an editor integration"),
115
cl::init(0), cl::cat(ClangFormatCategory));
116
117
static cl::opt<bool>
118
SortIncludes("sort-includes",
119
cl::desc("If set, overrides the include sorting behavior\n"
120
"determined by the SortIncludes style flag"),
121
cl::cat(ClangFormatCategory));
122
123
static cl::opt<std::string> QualifierAlignment(
124
"qualifier-alignment",
125
cl::desc("If set, overrides the qualifier alignment style\n"
126
"determined by the QualifierAlignment style flag"),
127
cl::init(""), cl::cat(ClangFormatCategory));
128
129
static cl::opt<std::string> Files(
130
"files",
131
cl::desc("A file containing a list of files to process, one per line."),
132
cl::value_desc("filename"), cl::init(""), cl::cat(ClangFormatCategory));
133
134
static cl::opt<bool>
135
Verbose("verbose", cl::desc("If set, shows the list of processed files"),
136
cl::cat(ClangFormatCategory));
137
138
// Use --dry-run to match other LLVM tools when you mean do it but don't
139
// actually do it
140
static cl::opt<bool>
141
DryRun("dry-run",
142
cl::desc("If set, do not actually make the formatting changes"),
143
cl::cat(ClangFormatCategory));
144
145
// Use -n as a common command as an alias for --dry-run. (git and make use -n)
146
static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
147
cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
148
cl::NotHidden);
149
150
// Emulate being able to turn on/off the warning.
151
static cl::opt<bool>
152
WarnFormat("Wclang-format-violations",
153
cl::desc("Warnings about individual formatting changes needed. "
154
"Used only with --dry-run or -n"),
155
cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
156
157
static cl::opt<bool>
158
NoWarnFormat("Wno-clang-format-violations",
159
cl::desc("Do not warn about individual formatting changes "
160
"needed. Used only with --dry-run or -n"),
161
cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
162
163
static cl::opt<unsigned> ErrorLimit(
164
"ferror-limit",
165
cl::desc("Set the maximum number of clang-format errors to emit\n"
166
"before stopping (0 = no limit).\n"
167
"Used only with --dry-run or -n"),
168
cl::init(0), cl::cat(ClangFormatCategory));
169
170
static cl::opt<bool>
171
WarningsAsErrors("Werror",
172
cl::desc("If set, changes formatting warnings to errors"),
173
cl::cat(ClangFormatCategory));
174
175
namespace {
176
enum class WNoError { Unknown };
177
}
178
179
static cl::bits<WNoError> WNoErrorList(
180
"Wno-error",
181
cl::desc("If set don't error out on the specified warning type."),
182
cl::values(
183
clEnumValN(WNoError::Unknown, "unknown",
184
"If set, unknown format options are only warned about.\n"
185
"This can be used to enable formatting, even if the\n"
186
"configuration contains unknown (newer) options.\n"
187
"Use with caution, as this might lead to dramatically\n"
188
"differing format depending on an option being\n"
189
"supported or not.")),
190
cl::cat(ClangFormatCategory));
191
192
static cl::opt<bool>
193
ShowColors("fcolor-diagnostics",
194
cl::desc("If set, and on a color-capable terminal controls "
195
"whether or not to print diagnostics in color"),
196
cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
197
198
static cl::opt<bool>
199
NoShowColors("fno-color-diagnostics",
200
cl::desc("If set, and on a color-capable terminal controls "
201
"whether or not to print diagnostics in color"),
202
cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
203
204
static cl::list<std::string> FileNames(cl::Positional,
205
cl::desc("[@<file>] [<file> ...]"),
206
cl::cat(ClangFormatCategory));
207
208
static cl::opt<bool> FailOnIncompleteFormat(
209
"fail-on-incomplete-format",
210
cl::desc("If set, fail with exit code 1 on incomplete format."),
211
cl::init(false), cl::cat(ClangFormatCategory));
212
213
static cl::opt<bool> ListIgnored("list-ignored",
214
cl::desc("List ignored files."),
215
cl::cat(ClangFormatCategory), cl::Hidden);
216
217
namespace clang {
218
namespace format {
219
220
static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
221
SourceManager &Sources, FileManager &Files,
222
llvm::vfs::InMemoryFileSystem *MemFS) {
223
MemFS->addFileNoOwn(FileName, 0, Source);
224
auto File = Files.getOptionalFileRef(FileName);
225
assert(File && "File not added to MemFS?");
226
return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
227
}
228
229
// Parses <start line>:<end line> input to a pair of line numbers.
230
// Returns true on error.
231
static bool parseLineRange(StringRef Input, unsigned &FromLine,
232
unsigned &ToLine) {
233
std::pair<StringRef, StringRef> LineRange = Input.split(':');
234
return LineRange.first.getAsInteger(0, FromLine) ||
235
LineRange.second.getAsInteger(0, ToLine);
236
}
237
238
static bool fillRanges(MemoryBuffer *Code,
239
std::vector<tooling::Range> &Ranges) {
240
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
241
new llvm::vfs::InMemoryFileSystem);
242
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
243
DiagnosticsEngine Diagnostics(
244
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
245
new DiagnosticOptions);
246
SourceManager Sources(Diagnostics, Files);
247
FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
248
InMemoryFileSystem.get());
249
if (!LineRanges.empty()) {
250
if (!Offsets.empty() || !Lengths.empty()) {
251
errs() << "error: cannot use -lines with -offset/-length\n";
252
return true;
253
}
254
255
for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
256
unsigned FromLine, ToLine;
257
if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
258
errs() << "error: invalid <start line>:<end line> pair\n";
259
return true;
260
}
261
if (FromLine < 1) {
262
errs() << "error: start line should be at least 1\n";
263
return true;
264
}
265
if (FromLine > ToLine) {
266
errs() << "error: start line should not exceed end line\n";
267
return true;
268
}
269
SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
270
SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
271
if (Start.isInvalid() || End.isInvalid())
272
return true;
273
unsigned Offset = Sources.getFileOffset(Start);
274
unsigned Length = Sources.getFileOffset(End) - Offset;
275
Ranges.push_back(tooling::Range(Offset, Length));
276
}
277
return false;
278
}
279
280
if (Offsets.empty())
281
Offsets.push_back(0);
282
if (Offsets.size() != Lengths.size() &&
283
!(Offsets.size() == 1 && Lengths.empty())) {
284
errs() << "error: number of -offset and -length arguments must match.\n";
285
return true;
286
}
287
for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
288
if (Offsets[i] >= Code->getBufferSize()) {
289
errs() << "error: offset " << Offsets[i] << " is outside the file\n";
290
return true;
291
}
292
SourceLocation Start =
293
Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
294
SourceLocation End;
295
if (i < Lengths.size()) {
296
if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
297
errs() << "error: invalid length " << Lengths[i]
298
<< ", offset + length (" << Offsets[i] + Lengths[i]
299
<< ") is outside the file.\n";
300
return true;
301
}
302
End = Start.getLocWithOffset(Lengths[i]);
303
} else {
304
End = Sources.getLocForEndOfFile(ID);
305
}
306
unsigned Offset = Sources.getFileOffset(Start);
307
unsigned Length = Sources.getFileOffset(End) - Offset;
308
Ranges.push_back(tooling::Range(Offset, Length));
309
}
310
return false;
311
}
312
313
static void outputReplacementXML(StringRef Text) {
314
// FIXME: When we sort includes, we need to make sure the stream is correct
315
// utf-8.
316
size_t From = 0;
317
size_t Index;
318
while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
319
outs() << Text.substr(From, Index - From);
320
switch (Text[Index]) {
321
case '\n':
322
outs() << "&#10;";
323
break;
324
case '\r':
325
outs() << "&#13;";
326
break;
327
case '<':
328
outs() << "&lt;";
329
break;
330
case '&':
331
outs() << "&amp;";
332
break;
333
default:
334
llvm_unreachable("Unexpected character encountered!");
335
}
336
From = Index + 1;
337
}
338
outs() << Text.substr(From);
339
}
340
341
static void outputReplacementsXML(const Replacements &Replaces) {
342
for (const auto &R : Replaces) {
343
outs() << "<replacement "
344
<< "offset='" << R.getOffset() << "' "
345
<< "length='" << R.getLength() << "'>";
346
outputReplacementXML(R.getReplacementText());
347
outs() << "</replacement>\n";
348
}
349
}
350
351
static bool
352
emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
353
const std::unique_ptr<llvm::MemoryBuffer> &Code) {
354
if (Replaces.empty())
355
return false;
356
357
unsigned Errors = 0;
358
if (WarnFormat && !NoWarnFormat) {
359
SourceMgr Mgr;
360
const char *StartBuf = Code->getBufferStart();
361
362
Mgr.AddNewSourceBuffer(
363
MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
364
for (const auto &R : Replaces) {
365
SMDiagnostic Diag = Mgr.GetMessage(
366
SMLoc::getFromPointer(StartBuf + R.getOffset()),
367
WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
368
: SourceMgr::DiagKind::DK_Warning,
369
"code should be clang-formatted [-Wclang-format-violations]");
370
371
Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors));
372
if (ErrorLimit && ++Errors >= ErrorLimit)
373
break;
374
}
375
}
376
return WarningsAsErrors;
377
}
378
379
static void outputXML(const Replacements &Replaces,
380
const Replacements &FormatChanges,
381
const FormattingAttemptStatus &Status,
382
const cl::opt<unsigned> &Cursor,
383
unsigned CursorPosition) {
384
outs() << "<?xml version='1.0'?>\n<replacements "
385
"xml:space='preserve' incomplete_format='"
386
<< (Status.FormatComplete ? "false" : "true") << "'";
387
if (!Status.FormatComplete)
388
outs() << " line='" << Status.Line << "'";
389
outs() << ">\n";
390
if (Cursor.getNumOccurrences() != 0) {
391
outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
392
<< "</cursor>\n";
393
}
394
395
outputReplacementsXML(Replaces);
396
outs() << "</replacements>\n";
397
}
398
399
class ClangFormatDiagConsumer : public DiagnosticConsumer {
400
virtual void anchor() {}
401
402
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
403
const Diagnostic &Info) override {
404
405
SmallVector<char, 16> vec;
406
Info.FormatDiagnostic(vec);
407
errs() << "clang-format error:" << vec << "\n";
408
}
409
};
410
411
// Returns true on error.
412
static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
413
const bool IsSTDIN = FileName == "-";
414
if (!OutputXML && Inplace && IsSTDIN) {
415
errs() << "error: cannot use -i when reading from stdin.\n";
416
return false;
417
}
418
// On Windows, overwriting a file with an open file mapping doesn't work,
419
// so read the whole file into memory when formatting in-place.
420
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
421
!OutputXML && Inplace
422
? MemoryBuffer::getFileAsStream(FileName)
423
: MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true);
424
if (std::error_code EC = CodeOrErr.getError()) {
425
errs() << EC.message() << "\n";
426
return true;
427
}
428
std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
429
if (Code->getBufferSize() == 0)
430
return false; // Empty files are formatted correctly.
431
432
StringRef BufStr = Code->getBuffer();
433
434
const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
435
436
if (InvalidBOM) {
437
errs() << "error: encoding with unsupported byte order mark \""
438
<< InvalidBOM << "\" detected";
439
if (!IsSTDIN)
440
errs() << " in file '" << FileName << "'";
441
errs() << ".\n";
442
return true;
443
}
444
445
std::vector<tooling::Range> Ranges;
446
if (fillRanges(Code.get(), Ranges))
447
return true;
448
StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
449
if (AssumedFileName.empty()) {
450
llvm::errs() << "error: empty filenames are not allowed\n";
451
return true;
452
}
453
454
Expected<FormatStyle> FormatStyle =
455
getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
456
nullptr, WNoErrorList.isSet(WNoError::Unknown));
457
if (!FormatStyle) {
458
llvm::errs() << toString(FormatStyle.takeError()) << "\n";
459
return true;
460
}
461
462
StringRef QualifierAlignmentOrder = QualifierAlignment;
463
464
FormatStyle->QualifierAlignment =
465
StringSwitch<FormatStyle::QualifierAlignmentStyle>(
466
QualifierAlignmentOrder.lower())
467
.Case("right", FormatStyle::QAS_Right)
468
.Case("left", FormatStyle::QAS_Left)
469
.Default(FormatStyle->QualifierAlignment);
470
471
if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
472
FormatStyle->QualifierOrder = {"const", "volatile", "type"};
473
} else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
474
FormatStyle->QualifierOrder = {"type", "const", "volatile"};
475
} else if (QualifierAlignmentOrder.contains("type")) {
476
FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
477
SmallVector<StringRef> Qualifiers;
478
QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
479
/*KeepEmpty=*/false);
480
FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
481
}
482
483
if (SortIncludes.getNumOccurrences() != 0) {
484
if (SortIncludes)
485
FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive;
486
else
487
FormatStyle->SortIncludes = FormatStyle::SI_Never;
488
}
489
unsigned CursorPosition = Cursor;
490
Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
491
AssumedFileName, &CursorPosition);
492
493
// To format JSON insert a variable to trick the code into thinking its
494
// JavaScript.
495
if (FormatStyle->isJson() && !FormatStyle->DisableFormat) {
496
auto Err = Replaces.add(tooling::Replacement(
497
tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
498
if (Err)
499
llvm::errs() << "Bad Json variable insertion\n";
500
}
501
502
auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
503
if (!ChangedCode) {
504
llvm::errs() << toString(ChangedCode.takeError()) << "\n";
505
return true;
506
}
507
// Get new affected ranges after sorting `#includes`.
508
Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
509
FormattingAttemptStatus Status;
510
Replacements FormatChanges =
511
reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
512
Replaces = Replaces.merge(FormatChanges);
513
if (OutputXML || DryRun) {
514
if (DryRun)
515
return emitReplacementWarnings(Replaces, AssumedFileName, Code);
516
outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
517
} else {
518
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
519
new llvm::vfs::InMemoryFileSystem);
520
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
521
522
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
523
ClangFormatDiagConsumer IgnoreDiagnostics;
524
DiagnosticsEngine Diagnostics(
525
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
526
&IgnoreDiagnostics, false);
527
SourceManager Sources(Diagnostics, Files);
528
FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
529
InMemoryFileSystem.get());
530
Rewriter Rewrite(Sources, LangOptions());
531
tooling::applyAllReplacements(Replaces, Rewrite);
532
if (Inplace) {
533
if (Rewrite.overwriteChangedFiles())
534
return true;
535
} else {
536
if (Cursor.getNumOccurrences() != 0) {
537
outs() << "{ \"Cursor\": "
538
<< FormatChanges.getShiftedCodePosition(CursorPosition)
539
<< ", \"IncompleteFormat\": "
540
<< (Status.FormatComplete ? "false" : "true");
541
if (!Status.FormatComplete)
542
outs() << ", \"Line\": " << Status.Line;
543
outs() << " }\n";
544
}
545
Rewrite.getEditBuffer(ID).write(outs());
546
}
547
}
548
return ErrorOnIncompleteFormat && !Status.FormatComplete;
549
}
550
551
} // namespace format
552
} // namespace clang
553
554
static void PrintVersion(raw_ostream &OS) {
555
OS << clang::getClangToolFullVersion("clang-format") << '\n';
556
}
557
558
// Dump the configuration.
559
static int dumpConfig() {
560
std::unique_ptr<llvm::MemoryBuffer> Code;
561
// We can't read the code to detect the language if there's no file name.
562
if (!FileNames.empty()) {
563
// Read in the code in case the filename alone isn't enough to detect the
564
// language.
565
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
566
MemoryBuffer::getFileOrSTDIN(FileNames[0], /*IsText=*/true);
567
if (std::error_code EC = CodeOrErr.getError()) {
568
llvm::errs() << EC.message() << "\n";
569
return 1;
570
}
571
Code = std::move(CodeOrErr.get());
572
}
573
Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle(
574
Style,
575
FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0],
576
FallbackStyle, Code ? Code->getBuffer() : "");
577
if (!FormatStyle) {
578
llvm::errs() << toString(FormatStyle.takeError()) << "\n";
579
return 1;
580
}
581
std::string Config = clang::format::configurationAsText(*FormatStyle);
582
outs() << Config << "\n";
583
return 0;
584
}
585
586
using String = SmallString<128>;
587
static String IgnoreDir; // Directory of .clang-format-ignore file.
588
static String PrevDir; // Directory of previous `FilePath`.
589
static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
590
591
// Check whether `FilePath` is ignored according to the nearest
592
// .clang-format-ignore file based on the rules below:
593
// - A blank line is skipped.
594
// - Leading and trailing spaces of a line are trimmed.
595
// - A line starting with a hash (`#`) is a comment.
596
// - A non-comment line is a single pattern.
597
// - The slash (`/`) is used as the directory separator.
598
// - A pattern is relative to the directory of the .clang-format-ignore file (or
599
// the root directory if the pattern starts with a slash).
600
// - A pattern is negated if it starts with a bang (`!`).
601
static bool isIgnored(StringRef FilePath) {
602
using namespace llvm::sys::fs;
603
if (!is_regular_file(FilePath))
604
return false;
605
606
String Path;
607
String AbsPath{FilePath};
608
609
using namespace llvm::sys::path;
610
make_absolute(AbsPath);
611
remove_dots(AbsPath, /*remove_dot_dot=*/true);
612
613
if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
614
PrevDir = Dir;
615
616
for (;;) {
617
Path = Dir;
618
append(Path, ".clang-format-ignore");
619
if (is_regular_file(Path))
620
break;
621
Dir = parent_path(Dir);
622
if (Dir.empty())
623
return false;
624
}
625
626
IgnoreDir = convert_to_slash(Dir);
627
628
std::ifstream IgnoreFile{Path.c_str()};
629
if (!IgnoreFile.good())
630
return false;
631
632
Patterns.clear();
633
634
for (std::string Line; std::getline(IgnoreFile, Line);) {
635
if (const auto Pattern{StringRef{Line}.trim()};
636
// Skip empty and comment lines.
637
!Pattern.empty() && Pattern[0] != '#') {
638
Patterns.push_back(Pattern);
639
}
640
}
641
}
642
643
if (IgnoreDir.empty())
644
return false;
645
646
const auto Pathname{convert_to_slash(AbsPath)};
647
for (const auto &Pat : Patterns) {
648
const bool IsNegated = Pat[0] == '!';
649
StringRef Pattern{Pat};
650
if (IsNegated)
651
Pattern = Pattern.drop_front();
652
653
if (Pattern.empty())
654
continue;
655
656
Pattern = Pattern.ltrim();
657
658
// `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
659
// This doesn't support patterns containing drive names (e.g. `C:`).
660
if (Pattern[0] != '/') {
661
Path = IgnoreDir;
662
append(Path, Style::posix, Pattern);
663
remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
664
Pattern = Path;
665
}
666
667
if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
668
return true;
669
}
670
671
return false;
672
}
673
674
int main(int argc, const char **argv) {
675
InitLLVM X(argc, argv);
676
677
cl::HideUnrelatedOptions(ClangFormatCategory);
678
679
cl::SetVersionPrinter(PrintVersion);
680
cl::ParseCommandLineOptions(
681
argc, argv,
682
"A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
683
"code.\n\n"
684
"If no arguments are specified, it formats the code from standard input\n"
685
"and writes the result to the standard output.\n"
686
"If <file>s are given, it reformats the files. If -i is specified\n"
687
"together with <file>s, the files are edited in-place. Otherwise, the\n"
688
"result is written to the standard output.\n");
689
690
if (Help) {
691
cl::PrintHelpMessage();
692
return 0;
693
}
694
695
if (DumpConfig)
696
return dumpConfig();
697
698
if (!Files.empty()) {
699
std::ifstream ExternalFileOfFiles{std::string(Files)};
700
std::string Line;
701
unsigned LineNo = 1;
702
while (std::getline(ExternalFileOfFiles, Line)) {
703
FileNames.push_back(Line);
704
LineNo++;
705
}
706
errs() << "Clang-formating " << LineNo << " files\n";
707
}
708
709
if (FileNames.empty())
710
return clang::format::format("-", FailOnIncompleteFormat);
711
712
if (FileNames.size() > 1 &&
713
(!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
714
errs() << "error: -offset, -length and -lines can only be used for "
715
"single file.\n";
716
return 1;
717
}
718
719
unsigned FileNo = 1;
720
bool Error = false;
721
for (const auto &FileName : FileNames) {
722
const bool Ignored = isIgnored(FileName);
723
if (ListIgnored) {
724
if (Ignored)
725
outs() << FileName << '\n';
726
continue;
727
}
728
if (Ignored)
729
continue;
730
if (Verbose) {
731
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
732
<< FileName << "\n";
733
}
734
Error |= clang::format::format(FileName, FailOnIncompleteFormat);
735
}
736
return Error ? 1 : 0;
737
}
738
739