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: .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 namespace clang {
209 namespace format {
210
createInMemoryFile(StringRef FileName,MemoryBufferRef Source,SourceManager & Sources,FileManager & Files,llvm::vfs::InMemoryFileSystem * MemFS)211 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
212 SourceManager &Sources, FileManager &Files,
213 llvm::vfs::InMemoryFileSystem *MemFS) {
214 MemFS->addFileNoOwn(FileName, 0, Source);
215 auto File = Files.getOptionalFileRef(FileName);
216 assert(File && "File not added to MemFS?");
217 return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
218 }
219
220 // Parses <start line>:<end line> input to a pair of line numbers.
221 // Returns true on error.
parseLineRange(StringRef Input,unsigned & FromLine,unsigned & ToLine)222 static bool parseLineRange(StringRef Input, unsigned &FromLine,
223 unsigned &ToLine) {
224 std::pair<StringRef, StringRef> LineRange = Input.split(':');
225 return LineRange.first.getAsInteger(0, FromLine) ||
226 LineRange.second.getAsInteger(0, ToLine);
227 }
228
fillRanges(MemoryBuffer * Code,std::vector<tooling::Range> & Ranges)229 static bool fillRanges(MemoryBuffer *Code,
230 std::vector<tooling::Range> &Ranges) {
231 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
232 new llvm::vfs::InMemoryFileSystem);
233 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
234 DiagnosticsEngine Diagnostics(
235 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
236 new DiagnosticOptions);
237 SourceManager Sources(Diagnostics, Files);
238 FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
239 InMemoryFileSystem.get());
240 if (!LineRanges.empty()) {
241 if (!Offsets.empty() || !Lengths.empty()) {
242 errs() << "error: cannot use -lines with -offset/-length\n";
243 return true;
244 }
245
246 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
247 unsigned FromLine, ToLine;
248 if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
249 errs() << "error: invalid <start line>:<end line> pair\n";
250 return true;
251 }
252 if (FromLine < 1) {
253 errs() << "error: start line should be at least 1\n";
254 return true;
255 }
256 if (FromLine > ToLine) {
257 errs() << "error: start line should not exceed end line\n";
258 return true;
259 }
260 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
261 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
262 if (Start.isInvalid() || End.isInvalid())
263 return true;
264 unsigned Offset = Sources.getFileOffset(Start);
265 unsigned Length = Sources.getFileOffset(End) - Offset;
266 Ranges.push_back(tooling::Range(Offset, Length));
267 }
268 return false;
269 }
270
271 if (Offsets.empty())
272 Offsets.push_back(0);
273 if (Offsets.size() != Lengths.size() &&
274 !(Offsets.size() == 1 && Lengths.empty())) {
275 errs() << "error: number of -offset and -length arguments must match.\n";
276 return true;
277 }
278 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
279 if (Offsets[i] >= Code->getBufferSize()) {
280 errs() << "error: offset " << Offsets[i] << " is outside the file\n";
281 return true;
282 }
283 SourceLocation Start =
284 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
285 SourceLocation End;
286 if (i < Lengths.size()) {
287 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
288 errs() << "error: invalid length " << Lengths[i]
289 << ", offset + length (" << Offsets[i] + Lengths[i]
290 << ") is outside the file.\n";
291 return true;
292 }
293 End = Start.getLocWithOffset(Lengths[i]);
294 } else {
295 End = Sources.getLocForEndOfFile(ID);
296 }
297 unsigned Offset = Sources.getFileOffset(Start);
298 unsigned Length = Sources.getFileOffset(End) - Offset;
299 Ranges.push_back(tooling::Range(Offset, Length));
300 }
301 return false;
302 }
303
outputReplacementXML(StringRef Text)304 static void outputReplacementXML(StringRef Text) {
305 // FIXME: When we sort includes, we need to make sure the stream is correct
306 // utf-8.
307 size_t From = 0;
308 size_t Index;
309 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
310 outs() << Text.substr(From, Index - From);
311 switch (Text[Index]) {
312 case '\n':
313 outs() << " ";
314 break;
315 case '\r':
316 outs() << " ";
317 break;
318 case '<':
319 outs() << "<";
320 break;
321 case '&':
322 outs() << "&";
323 break;
324 default:
325 llvm_unreachable("Unexpected character encountered!");
326 }
327 From = Index + 1;
328 }
329 outs() << Text.substr(From);
330 }
331
outputReplacementsXML(const Replacements & Replaces)332 static void outputReplacementsXML(const Replacements &Replaces) {
333 for (const auto &R : Replaces) {
334 outs() << "<replacement " << "offset='" << R.getOffset() << "' "
335 << "length='" << R.getLength() << "'>";
336 outputReplacementXML(R.getReplacementText());
337 outs() << "</replacement>\n";
338 }
339 }
340
341 static bool
emitReplacementWarnings(const Replacements & Replaces,StringRef AssumedFileName,const std::unique_ptr<llvm::MemoryBuffer> & Code)342 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
343 const std::unique_ptr<llvm::MemoryBuffer> &Code) {
344 if (Replaces.empty())
345 return false;
346
347 unsigned Errors = 0;
348 if (WarnFormat && !NoWarnFormat) {
349 llvm::SourceMgr Mgr;
350 const char *StartBuf = Code->getBufferStart();
351
352 Mgr.AddNewSourceBuffer(
353 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
354 for (const auto &R : Replaces) {
355 SMDiagnostic Diag = Mgr.GetMessage(
356 SMLoc::getFromPointer(StartBuf + R.getOffset()),
357 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
358 : SourceMgr::DiagKind::DK_Warning,
359 "code should be clang-formatted [-Wclang-format-violations]");
360
361 Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors));
362 if (ErrorLimit && ++Errors >= ErrorLimit)
363 break;
364 }
365 }
366 return WarningsAsErrors;
367 }
368
outputXML(const Replacements & Replaces,const Replacements & FormatChanges,const FormattingAttemptStatus & Status,const cl::opt<unsigned> & Cursor,unsigned CursorPosition)369 static void outputXML(const Replacements &Replaces,
370 const Replacements &FormatChanges,
371 const FormattingAttemptStatus &Status,
372 const cl::opt<unsigned> &Cursor,
373 unsigned CursorPosition) {
374 outs() << "<?xml version='1.0'?>\n<replacements "
375 "xml:space='preserve' incomplete_format='"
376 << (Status.FormatComplete ? "false" : "true") << "'";
377 if (!Status.FormatComplete)
378 outs() << " line='" << Status.Line << "'";
379 outs() << ">\n";
380 if (Cursor.getNumOccurrences() != 0) {
381 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
382 << "</cursor>\n";
383 }
384
385 outputReplacementsXML(Replaces);
386 outs() << "</replacements>\n";
387 }
388
389 class ClangFormatDiagConsumer : public DiagnosticConsumer {
anchor()390 virtual void anchor() {}
391
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)392 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
393 const Diagnostic &Info) override {
394
395 SmallVector<char, 16> vec;
396 Info.FormatDiagnostic(vec);
397 errs() << "clang-format error:" << vec << "\n";
398 }
399 };
400
401 // Returns true on error.
format(StringRef FileName)402 static bool format(StringRef FileName) {
403 const bool IsSTDIN = FileName == "-";
404 if (!OutputXML && Inplace && IsSTDIN) {
405 errs() << "error: cannot use -i when reading from stdin.\n";
406 return false;
407 }
408 // On Windows, overwriting a file with an open file mapping doesn't work,
409 // so read the whole file into memory when formatting in-place.
410 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
411 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName)
412 : MemoryBuffer::getFileOrSTDIN(FileName);
413 if (std::error_code EC = CodeOrErr.getError()) {
414 errs() << EC.message() << "\n";
415 return true;
416 }
417 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
418 if (Code->getBufferSize() == 0)
419 return false; // Empty files are formatted correctly.
420
421 StringRef BufStr = Code->getBuffer();
422
423 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
424
425 if (InvalidBOM) {
426 errs() << "error: encoding with unsupported byte order mark \""
427 << InvalidBOM << "\" detected";
428 if (!IsSTDIN)
429 errs() << " in file '" << FileName << "'";
430 errs() << ".\n";
431 return true;
432 }
433
434 std::vector<tooling::Range> Ranges;
435 if (fillRanges(Code.get(), Ranges))
436 return true;
437 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
438 if (AssumedFileName.empty()) {
439 llvm::errs() << "error: empty filenames are not allowed\n";
440 return true;
441 }
442
443 llvm::Expected<FormatStyle> FormatStyle =
444 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
445 nullptr, WNoErrorList.isSet(WNoError::Unknown));
446 if (!FormatStyle) {
447 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
448 return true;
449 }
450
451 StringRef QualifierAlignmentOrder = QualifierAlignment;
452
453 FormatStyle->QualifierAlignment =
454 StringSwitch<FormatStyle::QualifierAlignmentStyle>(
455 QualifierAlignmentOrder.lower())
456 .Case("right", FormatStyle::QAS_Right)
457 .Case("left", FormatStyle::QAS_Left)
458 .Default(FormatStyle->QualifierAlignment);
459
460 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
461 FormatStyle->QualifierOrder = {"const", "volatile", "type"};
462 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
463 FormatStyle->QualifierOrder = {"type", "const", "volatile"};
464 } else if (QualifierAlignmentOrder.contains("type")) {
465 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
466 SmallVector<StringRef> Qualifiers;
467 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
468 /*KeepEmpty=*/false);
469 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
470 }
471
472 if (SortIncludes.getNumOccurrences() != 0) {
473 if (SortIncludes)
474 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive;
475 else
476 FormatStyle->SortIncludes = FormatStyle::SI_Never;
477 }
478 unsigned CursorPosition = Cursor;
479 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
480 AssumedFileName, &CursorPosition);
481
482 // To format JSON insert a variable to trick the code into thinking its
483 // JavaScript.
484 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) {
485 auto Err = Replaces.add(tooling::Replacement(
486 tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
487 if (Err)
488 llvm::errs() << "Bad Json variable insertion\n";
489 }
490
491 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
492 if (!ChangedCode) {
493 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
494 return true;
495 }
496 // Get new affected ranges after sorting `#includes`.
497 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
498 FormattingAttemptStatus Status;
499 Replacements FormatChanges =
500 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
501 Replaces = Replaces.merge(FormatChanges);
502 if (OutputXML || DryRun) {
503 if (DryRun)
504 return emitReplacementWarnings(Replaces, AssumedFileName, Code);
505 else
506 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
507 } else {
508 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
509 new llvm::vfs::InMemoryFileSystem);
510 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
511
512 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
513 ClangFormatDiagConsumer IgnoreDiagnostics;
514 DiagnosticsEngine Diagnostics(
515 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
516 &IgnoreDiagnostics, false);
517 SourceManager Sources(Diagnostics, Files);
518 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
519 InMemoryFileSystem.get());
520 Rewriter Rewrite(Sources, LangOptions());
521 tooling::applyAllReplacements(Replaces, Rewrite);
522 if (Inplace) {
523 if (Rewrite.overwriteChangedFiles())
524 return true;
525 } else {
526 if (Cursor.getNumOccurrences() != 0) {
527 outs() << "{ \"Cursor\": "
528 << FormatChanges.getShiftedCodePosition(CursorPosition)
529 << ", \"IncompleteFormat\": "
530 << (Status.FormatComplete ? "false" : "true");
531 if (!Status.FormatComplete)
532 outs() << ", \"Line\": " << Status.Line;
533 outs() << " }\n";
534 }
535 Rewrite.getEditBuffer(ID).write(outs());
536 }
537 }
538 return false;
539 }
540
541 } // namespace format
542 } // namespace clang
543
PrintVersion(raw_ostream & OS)544 static void PrintVersion(raw_ostream &OS) {
545 OS << clang::getClangToolFullVersion("clang-format") << '\n';
546 }
547
548 // Dump the configuration.
dumpConfig()549 static int dumpConfig() {
550 std::unique_ptr<llvm::MemoryBuffer> Code;
551 // We can't read the code to detect the language if there's no file name.
552 if (!FileNames.empty()) {
553 // Read in the code in case the filename alone isn't enough to detect the
554 // language.
555 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
556 MemoryBuffer::getFileOrSTDIN(FileNames[0]);
557 if (std::error_code EC = CodeOrErr.getError()) {
558 llvm::errs() << EC.message() << "\n";
559 return 1;
560 }
561 Code = std::move(CodeOrErr.get());
562 }
563 llvm::Expected<clang::format::FormatStyle> FormatStyle =
564 clang::format::getStyle(Style,
565 FileNames.empty() || FileNames[0] == "-"
566 ? AssumeFileName
567 : FileNames[0],
568 FallbackStyle, Code ? Code->getBuffer() : "");
569 if (!FormatStyle) {
570 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
571 return 1;
572 }
573 std::string Config = clang::format::configurationAsText(*FormatStyle);
574 outs() << Config << "\n";
575 return 0;
576 }
577
578 using String = SmallString<128>;
579 static String IgnoreDir; // Directory of .clang-format-ignore file.
580 static String PrevDir; // Directory of previous `FilePath`.
581 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
582
583 // Check whether `FilePath` is ignored according to the nearest
584 // .clang-format-ignore file based on the rules below:
585 // - A blank line is skipped.
586 // - Leading and trailing spaces of a line are trimmed.
587 // - A line starting with a hash (`#`) is a comment.
588 // - A non-comment line is a single pattern.
589 // - The slash (`/`) is used as the directory separator.
590 // - A pattern is relative to the directory of the .clang-format-ignore file (or
591 // the root directory if the pattern starts with a slash).
592 // - A pattern is negated if it starts with a bang (`!`).
isIgnored(StringRef FilePath)593 static bool isIgnored(StringRef FilePath) {
594 using namespace llvm::sys::fs;
595 if (!is_regular_file(FilePath))
596 return false;
597
598 String Path;
599 String AbsPath{FilePath};
600
601 using namespace llvm::sys::path;
602 make_absolute(AbsPath);
603 remove_dots(AbsPath, /*remove_dot_dot=*/true);
604
605 if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
606 PrevDir = Dir;
607
608 for (;;) {
609 Path = Dir;
610 append(Path, ".clang-format-ignore");
611 if (is_regular_file(Path))
612 break;
613 Dir = parent_path(Dir);
614 if (Dir.empty())
615 return false;
616 }
617
618 IgnoreDir = convert_to_slash(Dir);
619
620 std::ifstream IgnoreFile{Path.c_str()};
621 if (!IgnoreFile.good())
622 return false;
623
624 Patterns.clear();
625
626 for (std::string Line; std::getline(IgnoreFile, Line);) {
627 if (const auto Pattern{StringRef{Line}.trim()};
628 // Skip empty and comment lines.
629 !Pattern.empty() && Pattern[0] != '#') {
630 Patterns.push_back(Pattern);
631 }
632 }
633 }
634
635 if (IgnoreDir.empty())
636 return false;
637
638 const auto Pathname{convert_to_slash(AbsPath)};
639 for (const auto &Pat : Patterns) {
640 const bool IsNegated = Pat[0] == '!';
641 StringRef Pattern{Pat};
642 if (IsNegated)
643 Pattern = Pattern.drop_front();
644
645 if (Pattern.empty())
646 continue;
647
648 Pattern = Pattern.ltrim();
649
650 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
651 // This doesn't support patterns containing drive names (e.g. `C:`).
652 if (Pattern[0] != '/') {
653 Path = IgnoreDir;
654 append(Path, Style::posix, Pattern);
655 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
656 Pattern = Path;
657 }
658
659 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
660 return true;
661 }
662
663 return false;
664 }
665
main(int argc,const char ** argv)666 int main(int argc, const char **argv) {
667 llvm::InitLLVM X(argc, argv);
668
669 cl::HideUnrelatedOptions(ClangFormatCategory);
670
671 cl::SetVersionPrinter(PrintVersion);
672 cl::ParseCommandLineOptions(
673 argc, argv,
674 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
675 "code.\n\n"
676 "If no arguments are specified, it formats the code from standard input\n"
677 "and writes the result to the standard output.\n"
678 "If <file>s are given, it reformats the files. If -i is specified\n"
679 "together with <file>s, the files are edited in-place. Otherwise, the\n"
680 "result is written to the standard output.\n");
681
682 if (Help) {
683 cl::PrintHelpMessage();
684 return 0;
685 }
686
687 if (DumpConfig)
688 return dumpConfig();
689
690 if (!Files.empty()) {
691 std::ifstream ExternalFileOfFiles{std::string(Files)};
692 std::string Line;
693 unsigned LineNo = 1;
694 while (std::getline(ExternalFileOfFiles, Line)) {
695 FileNames.push_back(Line);
696 LineNo++;
697 }
698 errs() << "Clang-formating " << LineNo << " files\n";
699 }
700
701 if (FileNames.empty())
702 return clang::format::format("-");
703
704 if (FileNames.size() > 1 &&
705 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
706 errs() << "error: -offset, -length and -lines can only be used for "
707 "single file.\n";
708 return 1;
709 }
710
711 unsigned FileNo = 1;
712 bool Error = false;
713 for (const auto &FileName : FileNames) {
714 if (isIgnored(FileName))
715 continue;
716 if (Verbose) {
717 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
718 << FileName << "\n";
719 }
720 Error |= clang::format::format(FileName);
721 }
722 return Error ? 1 : 0;
723 }
724