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

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