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