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