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