[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang / tools / clang-format / ClangFormat.cpp
blobcc735e48725921c43f92bb92126eb5a2d1e8f040
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 //===----------------------------------------------------------------------===//
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>
30 using namespace llvm;
31 using clang::tooling::Replacements;
33 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
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");
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));
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));
99 static cl::opt<bool> Inplace("i",
100 cl::desc("Inplace edit <file>s, if specified."),
101 cl::cat(ClangFormatCategory));
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));
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));
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));
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));
134 static cl::opt<bool>
135 Verbose("verbose", cl::desc("If set, shows the list of processed files"),
136 cl::cat(ClangFormatCategory));
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));
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);
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);
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);
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));
170 static cl::opt<bool>
171 WarningsAsErrors("Werror",
172 cl::desc("If set, changes formatting warnings to errors"),
173 cl::cat(ClangFormatCategory));
175 namespace {
176 enum class WNoError { Unknown };
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));
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);
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);
204 static cl::list<std::string> FileNames(cl::Positional,
205 cl::desc("[@<file>] [<file> ...]"),
206 cl::cat(ClangFormatCategory));
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));
213 static cl::opt<bool> ListIgnored("list-ignored",
214 cl::desc("List ignored files."),
215 cl::cat(ClangFormatCategory), cl::Hidden);
217 namespace clang {
218 namespace format {
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);
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);
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;
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;
261 if (FromLine < 1) {
262 errs() << "error: start line should be at least 1\n";
263 return true;
265 if (FromLine > ToLine) {
266 errs() << "error: start line should not exceed end line\n";
267 return true;
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));
277 return false;
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;
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;
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;
302 End = Start.getLocWithOffset(Lengths[i]);
303 } else {
304 End = Sources.getLocForEndOfFile(ID);
306 unsigned Offset = Sources.getFileOffset(Start);
307 unsigned Length = Sources.getFileOffset(End) - Offset;
308 Ranges.push_back(tooling::Range(Offset, Length));
310 return false;
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!");
336 From = Index + 1;
338 outs() << Text.substr(From);
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";
351 static bool
352 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
353 const std::unique_ptr<llvm::MemoryBuffer> &Code) {
354 unsigned Errors = 0;
355 if (WarnFormat && !NoWarnFormat) {
356 SourceMgr Mgr;
357 const char *StartBuf = Code->getBufferStart();
359 Mgr.AddNewSourceBuffer(
360 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
361 for (const auto &R : Replaces) {
362 SMDiagnostic Diag = Mgr.GetMessage(
363 SMLoc::getFromPointer(StartBuf + R.getOffset()),
364 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
365 : SourceMgr::DiagKind::DK_Warning,
366 "code should be clang-formatted [-Wclang-format-violations]");
368 Diag.print(nullptr, llvm::errs(), ShowColors && !NoShowColors);
369 if (ErrorLimit && ++Errors >= ErrorLimit)
370 break;
373 return WarningsAsErrors;
376 static void outputXML(const Replacements &Replaces,
377 const Replacements &FormatChanges,
378 const FormattingAttemptStatus &Status,
379 const cl::opt<unsigned> &Cursor,
380 unsigned CursorPosition) {
381 outs() << "<?xml version='1.0'?>\n<replacements "
382 "xml:space='preserve' incomplete_format='"
383 << (Status.FormatComplete ? "false" : "true") << "'";
384 if (!Status.FormatComplete)
385 outs() << " line='" << Status.Line << "'";
386 outs() << ">\n";
387 if (Cursor.getNumOccurrences() != 0) {
388 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
389 << "</cursor>\n";
392 outputReplacementsXML(Replaces);
393 outs() << "</replacements>\n";
396 class ClangFormatDiagConsumer : public DiagnosticConsumer {
397 virtual void anchor() {}
399 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
400 const Diagnostic &Info) override {
402 SmallVector<char, 16> vec;
403 Info.FormatDiagnostic(vec);
404 errs() << "clang-format error:" << vec << "\n";
408 // Returns true on error.
409 static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
410 const bool IsSTDIN = FileName == "-";
411 if (!OutputXML && Inplace && IsSTDIN) {
412 errs() << "error: cannot use -i when reading from stdin.\n";
413 return true;
415 // On Windows, overwriting a file with an open file mapping doesn't work,
416 // so read the whole file into memory when formatting in-place.
417 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
418 !OutputXML && Inplace
419 ? MemoryBuffer::getFileAsStream(FileName)
420 : MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true);
421 if (std::error_code EC = CodeOrErr.getError()) {
422 errs() << FileName << ": " << EC.message() << "\n";
423 return true;
425 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
426 if (Code->getBufferSize() == 0)
427 return false; // Empty files are formatted correctly.
429 StringRef BufStr = Code->getBuffer();
431 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
433 if (InvalidBOM) {
434 errs() << "error: encoding with unsupported byte order mark \""
435 << InvalidBOM << "\" detected";
436 if (!IsSTDIN)
437 errs() << " in file '" << FileName << "'";
438 errs() << ".\n";
439 return true;
442 std::vector<tooling::Range> Ranges;
443 if (fillRanges(Code.get(), Ranges))
444 return true;
445 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
446 if (AssumedFileName.empty()) {
447 llvm::errs() << "error: empty filenames are not allowed\n";
448 return true;
451 Expected<FormatStyle> FormatStyle =
452 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
453 nullptr, WNoErrorList.isSet(WNoError::Unknown));
454 if (!FormatStyle) {
455 llvm::errs() << toString(FormatStyle.takeError()) << "\n";
456 return true;
459 StringRef QualifierAlignmentOrder = QualifierAlignment;
461 FormatStyle->QualifierAlignment =
462 StringSwitch<FormatStyle::QualifierAlignmentStyle>(
463 QualifierAlignmentOrder.lower())
464 .Case("right", FormatStyle::QAS_Right)
465 .Case("left", FormatStyle::QAS_Left)
466 .Default(FormatStyle->QualifierAlignment);
468 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
469 FormatStyle->QualifierOrder = {"const", "volatile", "type"};
470 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
471 FormatStyle->QualifierOrder = {"type", "const", "volatile"};
472 } else if (QualifierAlignmentOrder.contains("type")) {
473 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
474 SmallVector<StringRef> Qualifiers;
475 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
476 /*KeepEmpty=*/false);
477 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
480 if (SortIncludes.getNumOccurrences() != 0) {
481 if (SortIncludes)
482 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive;
483 else
484 FormatStyle->SortIncludes = FormatStyle::SI_Never;
486 unsigned CursorPosition = Cursor;
487 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
488 AssumedFileName, &CursorPosition);
490 const bool IsJson = FormatStyle->isJson();
492 // To format JSON insert a variable to trick the code into thinking its
493 // JavaScript.
494 if (IsJson && !FormatStyle->DisableFormat) {
495 auto Err = Replaces.add(tooling::Replacement(
496 tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
497 if (Err)
498 llvm::errs() << "Bad Json variable insertion\n";
501 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
502 if (!ChangedCode) {
503 llvm::errs() << toString(ChangedCode.takeError()) << "\n";
504 return true;
506 // Get new affected ranges after sorting `#includes`.
507 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
508 FormattingAttemptStatus Status;
509 Replacements FormatChanges =
510 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
511 Replaces = Replaces.merge(FormatChanges);
512 if (DryRun) {
513 return Replaces.size() > (IsJson ? 1u : 0u) &&
514 emitReplacementWarnings(Replaces, AssumedFileName, Code);
516 if (OutputXML) {
517 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
518 } else {
519 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
520 new llvm::vfs::InMemoryFileSystem);
521 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
523 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
524 ClangFormatDiagConsumer IgnoreDiagnostics;
525 DiagnosticsEngine Diagnostics(
526 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
527 &IgnoreDiagnostics, false);
528 SourceManager Sources(Diagnostics, Files);
529 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
530 InMemoryFileSystem.get());
531 Rewriter Rewrite(Sources, LangOptions());
532 tooling::applyAllReplacements(Replaces, Rewrite);
533 if (Inplace) {
534 if (Rewrite.overwriteChangedFiles())
535 return true;
536 } else {
537 if (Cursor.getNumOccurrences() != 0) {
538 outs() << "{ \"Cursor\": "
539 << FormatChanges.getShiftedCodePosition(CursorPosition)
540 << ", \"IncompleteFormat\": "
541 << (Status.FormatComplete ? "false" : "true");
542 if (!Status.FormatComplete)
543 outs() << ", \"Line\": " << Status.Line;
544 outs() << " }\n";
546 Rewrite.getEditBuffer(ID).write(outs());
549 return ErrorOnIncompleteFormat && !Status.FormatComplete;
552 } // namespace format
553 } // namespace clang
555 static void PrintVersion(raw_ostream &OS) {
556 OS << clang::getClangToolFullVersion("clang-format") << '\n';
559 // Dump the configuration.
560 static int dumpConfig() {
561 std::unique_ptr<llvm::MemoryBuffer> Code;
562 // We can't read the code to detect the language if there's no file name.
563 if (!FileNames.empty()) {
564 // Read in the code in case the filename alone isn't enough to detect the
565 // language.
566 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
567 MemoryBuffer::getFileOrSTDIN(FileNames[0], /*IsText=*/true);
568 if (std::error_code EC = CodeOrErr.getError()) {
569 llvm::errs() << EC.message() << "\n";
570 return 1;
572 Code = std::move(CodeOrErr.get());
574 Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle(
575 Style,
576 FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0],
577 FallbackStyle, Code ? Code->getBuffer() : "");
578 if (!FormatStyle) {
579 llvm::errs() << toString(FormatStyle.takeError()) << "\n";
580 return 1;
582 std::string Config = clang::format::configurationAsText(*FormatStyle);
583 outs() << Config << "\n";
584 return 0;
587 using String = SmallString<128>;
588 static String IgnoreDir; // Directory of .clang-format-ignore file.
589 static String PrevDir; // Directory of previous `FilePath`.
590 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
592 // Check whether `FilePath` is ignored according to the nearest
593 // .clang-format-ignore file based on the rules below:
594 // - A blank line is skipped.
595 // - Leading and trailing spaces of a line are trimmed.
596 // - A line starting with a hash (`#`) is a comment.
597 // - A non-comment line is a single pattern.
598 // - The slash (`/`) is used as the directory separator.
599 // - A pattern is relative to the directory of the .clang-format-ignore file (or
600 // the root directory if the pattern starts with a slash).
601 // - A pattern is negated if it starts with a bang (`!`).
602 static bool isIgnored(StringRef FilePath) {
603 using namespace llvm::sys::fs;
604 if (!is_regular_file(FilePath))
605 return false;
607 String Path;
608 String AbsPath{FilePath};
610 using namespace llvm::sys::path;
611 make_absolute(AbsPath);
612 remove_dots(AbsPath, /*remove_dot_dot=*/true);
614 if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
615 PrevDir = Dir;
617 for (;;) {
618 Path = Dir;
619 append(Path, ".clang-format-ignore");
620 if (is_regular_file(Path))
621 break;
622 Dir = parent_path(Dir);
623 if (Dir.empty())
624 return false;
627 IgnoreDir = convert_to_slash(Dir);
629 std::ifstream IgnoreFile{Path.c_str()};
630 if (!IgnoreFile.good())
631 return false;
633 Patterns.clear();
635 for (std::string Line; std::getline(IgnoreFile, Line);) {
636 if (const auto Pattern{StringRef{Line}.trim()};
637 // Skip empty and comment lines.
638 !Pattern.empty() && Pattern[0] != '#') {
639 Patterns.push_back(Pattern);
644 if (IgnoreDir.empty())
645 return false;
647 const auto Pathname{convert_to_slash(AbsPath)};
648 for (const auto &Pat : Patterns) {
649 const bool IsNegated = Pat[0] == '!';
650 StringRef Pattern{Pat};
651 if (IsNegated)
652 Pattern = Pattern.drop_front();
654 if (Pattern.empty())
655 continue;
657 Pattern = Pattern.ltrim();
659 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
660 // This doesn't support patterns containing drive names (e.g. `C:`).
661 if (Pattern[0] != '/') {
662 Path = IgnoreDir;
663 append(Path, Style::posix, Pattern);
664 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
665 Pattern = Path;
668 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
669 return true;
672 return false;
675 int main(int argc, const char **argv) {
676 InitLLVM X(argc, argv);
678 cl::HideUnrelatedOptions(ClangFormatCategory);
680 cl::SetVersionPrinter(PrintVersion);
681 cl::ParseCommandLineOptions(
682 argc, argv,
683 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
684 "code.\n\n"
685 "If no arguments are specified, it formats the code from standard input\n"
686 "and writes the result to the standard output.\n"
687 "If <file>s are given, it reformats the files. If -i is specified\n"
688 "together with <file>s, the files are edited in-place. Otherwise, the\n"
689 "result is written to the standard output.\n");
691 if (Help) {
692 cl::PrintHelpMessage();
693 return 0;
696 if (DumpConfig)
697 return dumpConfig();
699 if (!Files.empty()) {
700 std::ifstream ExternalFileOfFiles{std::string(Files)};
701 std::string Line;
702 unsigned LineNo = 1;
703 while (std::getline(ExternalFileOfFiles, Line)) {
704 FileNames.push_back(Line);
705 LineNo++;
707 errs() << "Clang-formatting " << LineNo << " files\n";
710 if (FileNames.empty()) {
711 if (isIgnored(AssumeFileName))
712 return 0;
713 return clang::format::format("-", FailOnIncompleteFormat);
716 if (FileNames.size() > 1 &&
717 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
718 errs() << "error: -offset, -length and -lines can only be used for "
719 "single file.\n";
720 return 1;
723 unsigned FileNo = 1;
724 bool Error = false;
725 for (const auto &FileName : FileNames) {
726 const bool Ignored = isIgnored(FileName);
727 if (ListIgnored) {
728 if (Ignored)
729 outs() << FileName << '\n';
730 continue;
732 if (Ignored)
733 continue;
734 if (Verbose) {
735 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
736 << FileName << "\n";
738 Error |= clang::format::format(FileName, FailOnIncompleteFormat);
740 return Error ? 1 : 0;