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 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
241 new llvm::vfs::InMemoryFileSystem);
242 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
243 DiagnosticOptions DiagOpts;
244 DiagnosticsEngine Diagnostics(
245 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 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
348emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
349 const 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(
356 F: MemoryBuffer::getMemBuffer(InputData: StartBuf, BufferName: AssumedFileName), IncludeLoc: SMLoc());
357 for (const auto &R : Replaces) {
358 SMDiagnostic Diag = Mgr.GetMessage(
359 Loc: SMLoc::getFromPointer(Ptr: StartBuf + R.getOffset()),
360 Kind: WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
361 : SourceMgr::DiagKind::DK_Warning,
362 Msg: "code should be clang-formatted [-Wclang-format-violations]");
363
364 Diag.print(ProgName: nullptr, S&: llvm::errs(), ShowColors: ShowColors && !NoShowColors);
365 if (ErrorLimit && ++Errors >= ErrorLimit)
366 break;
367 }
368 }
369 return WarningsAsErrors;
370}
371
372static void outputXML(const Replacements &Replaces,
373 const Replacements &FormatChanges,
374 const FormattingAttemptStatus &Status,
375 const cl::opt<unsigned> &Cursor,
376 unsigned CursorPosition) {
377 outs() << "<?xml version='1.0'?>\n<replacements "
378 "xml:space='preserve' incomplete_format='"
379 << (Status.FormatComplete ? "false" : "true") << "'";
380 if (!Status.FormatComplete)
381 outs() << " line='" << Status.Line << "'";
382 outs() << ">\n";
383 if (Cursor.getNumOccurrences() != 0) {
384 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(Position: CursorPosition)
385 << "</cursor>\n";
386 }
387
388 outputReplacementsXML(Replaces);
389 outs() << "</replacements>\n";
390}
391
392class ClangFormatDiagConsumer : public DiagnosticConsumer {
393 virtual void anchor() {}
394
395 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
396 const Diagnostic &Info) override {
397
398 SmallVector<char, 16> vec;
399 Info.FormatDiagnostic(OutStr&: vec);
400 errs() << "clang-format error:" << vec << "\n";
401 }
402};
403
404// Returns true on error.
405static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
406 const bool IsSTDIN = FileName == "-";
407 if (!OutputXML && Inplace && IsSTDIN) {
408 errs() << "error: cannot use -i when reading from stdin.\n";
409 return true;
410 }
411 // On Windows, overwriting a file with an open file mapping doesn't work,
412 // so read the whole file into memory when formatting in-place.
413 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
414 !OutputXML && Inplace
415 ? MemoryBuffer::getFileAsStream(Filename: FileName)
416 : MemoryBuffer::getFileOrSTDIN(Filename: FileName, /*IsText=*/true);
417 if (std::error_code EC = CodeOrErr.getError()) {
418 errs() << FileName << ": " << EC.message() << "\n";
419 return true;
420 }
421 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
422 if (Code->getBufferSize() == 0)
423 return false; // Empty files are formatted correctly.
424
425 StringRef BufStr = Code->getBuffer();
426
427 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
428
429 if (InvalidBOM) {
430 errs() << "error: encoding with unsupported byte order mark \""
431 << InvalidBOM << "\" detected";
432 if (!IsSTDIN)
433 errs() << " in file '" << FileName << "'";
434 errs() << ".\n";
435 return true;
436 }
437
438 std::vector<tooling::Range> Ranges;
439 if (fillRanges(Code: Code.get(), Ranges))
440 return true;
441 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
442 if (AssumedFileName.empty()) {
443 llvm::errs() << "error: empty filenames are not allowed\n";
444 return true;
445 }
446
447 Expected<FormatStyle> FormatStyle =
448 getStyle(StyleName: Style, FileName: AssumedFileName, FallbackStyle, Code: Code->getBuffer(),
449 FS: nullptr, AllowUnknownOptions: WNoErrorList.isSet(V: WNoError::Unknown));
450 if (!FormatStyle) {
451 llvm::errs() << toString(E: FormatStyle.takeError()) << "\n";
452 return true;
453 }
454
455 StringRef QualifierAlignmentOrder = QualifierAlignment;
456
457 FormatStyle->QualifierAlignment =
458 StringSwitch<FormatStyle::QualifierAlignmentStyle>(
459 QualifierAlignmentOrder.lower())
460 .Case(S: "right", Value: FormatStyle::QAS_Right)
461 .Case(S: "left", Value: FormatStyle::QAS_Left)
462 .Default(Value: FormatStyle->QualifierAlignment);
463
464 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
465 FormatStyle->QualifierOrder = {"const", "volatile", "type"};
466 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
467 FormatStyle->QualifierOrder = {"type", "const", "volatile"};
468 } else if (QualifierAlignmentOrder.contains(Other: "type")) {
469 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
470 SmallVector<StringRef> Qualifiers;
471 QualifierAlignmentOrder.split(A&: Qualifiers, Separator: " ", /*MaxSplit=*/-1,
472 /*KeepEmpty=*/false);
473 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
474 }
475
476 if (SortIncludes.getNumOccurrences() != 0) {
477 FormatStyle->SortIncludes = {};
478 if (SortIncludes)
479 FormatStyle->SortIncludes.Enabled = true;
480 }
481 unsigned CursorPosition = Cursor;
482 Replacements Replaces = sortIncludes(Style: *FormatStyle, Code: Code->getBuffer(), Ranges,
483 FileName: AssumedFileName, Cursor: &CursorPosition);
484
485 const bool IsJson = FormatStyle->isJson();
486
487 // To format JSON insert a variable to trick the code into thinking its
488 // JavaScript.
489 if (IsJson && !FormatStyle->DisableFormat) {
490 auto Err =
491 Replaces.add(R: tooling::Replacement(AssumedFileName, 0, 0, "x = "));
492 if (Err)
493 llvm::errs() << "Bad Json variable insertion\n";
494 }
495
496 auto ChangedCode = tooling::applyAllReplacements(Code: Code->getBuffer(), Replaces);
497 if (!ChangedCode) {
498 llvm::errs() << toString(E: ChangedCode.takeError()) << "\n";
499 return true;
500 }
501 // Get new affected ranges after sorting `#includes`.
502 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
503 FormattingAttemptStatus Status;
504 Replacements FormatChanges =
505 reformat(Style: *FormatStyle, Code: *ChangedCode, Ranges, FileName: AssumedFileName, Status: &Status);
506 Replaces = Replaces.merge(Replaces: FormatChanges);
507 if (DryRun) {
508 return Replaces.size() > (IsJson ? 1u : 0u) &&
509 emitReplacementWarnings(Replaces, AssumedFileName, Code);
510 }
511 if (OutputXML) {
512 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
513 } else {
514 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
515 new llvm::vfs::InMemoryFileSystem);
516 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
517
518 DiagnosticOptions DiagOpts;
519 ClangFormatDiagConsumer IgnoreDiagnostics;
520 DiagnosticsEngine Diagnostics(
521 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts,
522 &IgnoreDiagnostics, false);
523 SourceManager Sources(Diagnostics, Files);
524 FileID ID = createInMemoryFile(FileName: AssumedFileName, Source: *Code, Sources, Files,
525 MemFS: InMemoryFileSystem.get());
526 Rewriter Rewrite(Sources, LangOptions());
527 tooling::applyAllReplacements(Replaces, Rewrite);
528 if (Inplace) {
529 if (Rewrite.overwriteChangedFiles())
530 return true;
531 } else {
532 if (Cursor.getNumOccurrences() != 0) {
533 outs() << "{ \"Cursor\": "
534 << FormatChanges.getShiftedCodePosition(Position: CursorPosition)
535 << ", \"IncompleteFormat\": "
536 << (Status.FormatComplete ? "false" : "true");
537 if (!Status.FormatComplete)
538 outs() << ", \"Line\": " << Status.Line;
539 outs() << " }\n";
540 }
541 Rewrite.getEditBuffer(FID: ID).write(Stream&: outs());
542 }
543 }
544 return ErrorOnIncompleteFormat && !Status.FormatComplete;
545}
546
547} // namespace format
548} // namespace clang
549
550static void PrintVersion(raw_ostream &OS) {
551 OS << clang::getClangToolFullVersion(ToolName: "clang-format") << '\n';
552}
553
554// Dump the configuration.
555static int dumpConfig() {
556 std::unique_ptr<llvm::MemoryBuffer> Code;
557 // We can't read the code to detect the language if there's no file name.
558 if (!FileNames.empty()) {
559 // Read in the code in case the filename alone isn't enough to detect the
560 // language.
561 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
562 MemoryBuffer::getFileOrSTDIN(Filename: FileNames[0], /*IsText=*/true);
563 if (std::error_code EC = CodeOrErr.getError()) {
564 llvm::errs() << EC.message() << "\n";
565 return 1;
566 }
567 Code = std::move(CodeOrErr.get());
568 }
569 Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle(
570 StyleName: Style,
571 FileName: FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0],
572 FallbackStyle, Code: Code ? Code->getBuffer() : "");
573 if (!FormatStyle) {
574 llvm::errs() << toString(E: FormatStyle.takeError()) << "\n";
575 return 1;
576 }
577 std::string Config = clang::format::configurationAsText(Style: *FormatStyle);
578 outs() << Config << "\n";
579 return 0;
580}
581
582using String = SmallString<128>;
583static String IgnoreDir; // Directory of .clang-format-ignore file.
584static String PrevDir; // Directory of previous `FilePath`.
585static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
586
587// Check whether `FilePath` is ignored according to the nearest
588// .clang-format-ignore file based on the rules below:
589// - A blank line is skipped.
590// - Leading and trailing spaces of a line are trimmed.
591// - A line starting with a hash (`#`) is a comment.
592// - A non-comment line is a single pattern.
593// - The slash (`/`) is used as the directory separator.
594// - A pattern is relative to the directory of the .clang-format-ignore file (or
595// the root directory if the pattern starts with a slash).
596// - A pattern is negated if it starts with a bang (`!`).
597static bool isIgnored(StringRef FilePath) {
598 using namespace llvm::sys::fs;
599 if (!is_regular_file(Path: FilePath))
600 return false;
601
602 String Path;
603 String AbsPath{FilePath};
604
605 using namespace llvm::sys::path;
606 make_absolute(path&: AbsPath);
607 remove_dots(path&: AbsPath, /*remove_dot_dot=*/true);
608
609 if (StringRef Dir{parent_path(path: AbsPath)}; PrevDir != Dir) {
610 PrevDir = Dir;
611
612 for (;;) {
613 Path = Dir;
614 append(path&: Path, a: ".clang-format-ignore");
615 if (is_regular_file(Path))
616 break;
617 Dir = parent_path(path: Dir);
618 if (Dir.empty())
619 return false;
620 }
621
622 IgnoreDir = convert_to_slash(path: Dir);
623
624 std::ifstream IgnoreFile{Path.c_str()};
625 if (!IgnoreFile.good())
626 return false;
627
628 Patterns.clear();
629
630 for (std::string Line; std::getline(is&: IgnoreFile, str&: Line);) {
631 if (const auto Pattern{StringRef{Line}.trim()};
632 // Skip empty and comment lines.
633 !Pattern.empty() && Pattern[0] != '#') {
634 Patterns.push_back(Elt: Pattern);
635 }
636 }
637 }
638
639 if (IgnoreDir.empty())
640 return false;
641
642 const auto Pathname{convert_to_slash(path: AbsPath)};
643 for (const auto &Pat : Patterns) {
644 const bool IsNegated = Pat[0] == '!';
645 StringRef Pattern{Pat};
646 if (IsNegated)
647 Pattern = Pattern.drop_front();
648
649 if (Pattern.empty())
650 continue;
651
652 Pattern = Pattern.ltrim();
653
654 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
655 // This doesn't support patterns containing drive names (e.g. `C:`).
656 if (Pattern[0] != '/') {
657 Path = IgnoreDir;
658 append(path&: Path, style: Style::posix, a: Pattern);
659 remove_dots(path&: Path, /*remove_dot_dot=*/true, style: Style::posix);
660 Pattern = Path;
661 }
662
663 if (clang::format::matchFilePath(Pattern, FilePath: Pathname) == !IsNegated)
664 return true;
665 }
666
667 return false;
668}
669
670int main(int argc, const char **argv) {
671 InitLLVM X(argc, argv);
672
673 cl::HideUnrelatedOptions(Category&: ClangFormatCategory);
674
675 cl::SetVersionPrinter(PrintVersion);
676 cl::ParseCommandLineOptions(
677 argc, argv,
678 Overview: "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
679 "code.\n\n"
680 "If no arguments are specified, it formats the code from standard input\n"
681 "and writes the result to the standard output.\n"
682 "If <file>s are given, it reformats the files. If -i is specified\n"
683 "together with <file>s, the files are edited in-place. Otherwise, the\n"
684 "result is written to the standard output.\n");
685
686 if (Help) {
687 cl::PrintHelpMessage();
688 return 0;
689 }
690
691 if (DumpConfig)
692 return dumpConfig();
693
694 if (!Files.empty()) {
695 std::ifstream ExternalFileOfFiles{std::string(Files)};
696 std::string Line;
697 unsigned LineNo = 1;
698 while (std::getline(is&: ExternalFileOfFiles, str&: Line)) {
699 FileNames.push_back(value: Line);
700 LineNo++;
701 }
702 errs() << "Clang-formatting " << LineNo << " files\n";
703 }
704
705 if (FileNames.empty()) {
706 if (isIgnored(FilePath: AssumeFileName))
707 return 0;
708 return clang::format::format(FileName: "-", ErrorOnIncompleteFormat: FailOnIncompleteFormat);
709 }
710
711 if (FileNames.size() > 1 &&
712 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
713 errs() << "error: -offset, -length and -lines can only be used for "
714 "single file.\n";
715 return 1;
716 }
717
718 unsigned FileNo = 1;
719 bool Error = false;
720 for (const auto &FileName : FileNames) {
721 const bool Ignored = isIgnored(FilePath: FileName);
722 if (ListIgnored) {
723 if (Ignored)
724 outs() << FileName << '\n';
725 continue;
726 }
727 if (Ignored)
728 continue;
729 if (Verbose) {
730 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
731 << FileName << "\n";
732 }
733 Error |= clang::format::format(FileName, ErrorOnIncompleteFormat: FailOnIncompleteFormat);
734 }
735 return Error ? 1 : 0;
736}
737

source code of clang/tools/clang-format/ClangFormat.cpp