1//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// This file implements a clang-format tool that automatically formats
11/// (fragments of) C++ code.
12///
13//===----------------------------------------------------------------------===//
14
15#include "../../lib/Format/MatchFilePath.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/DiagnosticOptions.h"
18#include "clang/Basic/FileManager.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Basic/Version.h"
21#include "clang/Format/Format.h"
22#include "clang/Rewrite/Core/Rewriter.h"
23#include "llvm/ADT/StringSwitch.h"
24#include "llvm/Support/CommandLine.h"
25#include "llvm/Support/FileSystem.h"
26#include "llvm/Support/InitLLVM.h"
27#include "llvm/Support/Process.h"
28#include <fstream>
29
30using namespace llvm;
31using clang::tooling::Replacements;
32
33static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
34
35// Mark all our options with this category, everything else (except for -version
36// and -help) will be hidden.
37static cl::OptionCategory ClangFormatCategory("Clang-format options");
38
39static cl::list<unsigned>
40 Offsets("offset",
41 cl::desc("Format a range starting at this byte offset.\n"
42 "Multiple ranges can be formatted by specifying\n"
43 "several -offset and -length pairs.\n"
44 "Can only be used with one input file."),
45 cl::cat(ClangFormatCategory));
46static cl::list<unsigned>
47 Lengths("length",
48 cl::desc("Format a range of this length (in bytes).\n"
49 "Multiple ranges can be formatted by specifying\n"
50 "several -offset and -length pairs.\n"
51 "When only a single -offset is specified without\n"
52 "-length, clang-format will format up to the end\n"
53 "of the file.\n"
54 "Can only be used with one input file."),
55 cl::cat(ClangFormatCategory));
56static cl::list<std::string>
57 LineRanges("lines",
58 cl::desc("<start line>:<end line> - format a range of\n"
59 "lines (both 1-based).\n"
60 "Multiple ranges can be formatted by specifying\n"
61 "several -lines arguments.\n"
62 "Can't be used with -offset and -length.\n"
63 "Can only be used with one input file."),
64 cl::cat(ClangFormatCategory));
65static cl::opt<std::string>
66 Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
67 cl::init(Val: clang::format::DefaultFormatStyle),
68 cl::cat(ClangFormatCategory));
69static cl::opt<std::string>
70 FallbackStyle("fallback-style",
71 cl::desc("The name of the predefined style used as a\n"
72 "fallback in case clang-format is invoked with\n"
73 "-style=file, but can not find the .clang-format\n"
74 "file to use. Defaults to 'LLVM'.\n"
75 "Use -fallback-style=none to skip formatting."),
76 cl::init(Val: clang::format::DefaultFallbackStyle),
77 cl::cat(ClangFormatCategory));
78
79static cl::opt<std::string> AssumeFileName(
80 "assume-filename",
81 cl::desc("Set filename used to determine the language and to find\n"
82 ".clang-format file.\n"
83 "Only used when reading from stdin.\n"
84 "If this is not passed, the .clang-format file is searched\n"
85 "relative to the current working directory when reading stdin.\n"
86 "Unrecognized filenames are treated as C++.\n"
87 "supported:\n"
88 " CSharp: .cs\n"
89 " Java: .java\n"
90 " JavaScript: .js .mjs .cjs .ts\n"
91 " JSON: .json .ipynb\n"
92 " Objective-C: .m .mm\n"
93 " Proto: .proto .protodevel\n"
94 " TableGen: .td\n"
95 " TextProto: .txtpb .textpb .pb.txt .textproto .asciipb\n"
96 " Verilog: .sv .svh .v .vh"),
97 cl::init(Val: "<stdin>"), cl::cat(ClangFormatCategory));
98
99static cl::opt<bool> Inplace("i",
100 cl::desc("Inplace edit <file>s, if specified."),
101 cl::cat(ClangFormatCategory));
102
103static cl::opt<bool> OutputXML("output-replacements-xml",
104 cl::desc("Output replacements as XML."),
105 cl::cat(ClangFormatCategory));
106static cl::opt<bool>
107 DumpConfig("dump-config",
108 cl::desc("Dump configuration options to stdout and exit.\n"
109 "Can be used with -style option."),
110 cl::cat(ClangFormatCategory));
111static cl::opt<unsigned>
112 Cursor("cursor",
113 cl::desc("The position of the cursor when invoking\n"
114 "clang-format from an editor integration"),
115 cl::init(Val: 0), cl::cat(ClangFormatCategory));
116
117static cl::opt<bool>
118 SortIncludes("sort-includes",
119 cl::desc("If set, overrides the include sorting behavior\n"
120 "determined by the SortIncludes style flag"),
121 cl::cat(ClangFormatCategory));
122
123static cl::opt<std::string> QualifierAlignment(
124 "qualifier-alignment",
125 cl::desc("If set, overrides the qualifier alignment style\n"
126 "determined by the QualifierAlignment style flag"),
127 cl::init(Val: ""), cl::cat(ClangFormatCategory));
128
129static cl::opt<std::string> Files(
130 "files",
131 cl::desc("A file containing a list of files to process, one per line."),
132 cl::value_desc("filename"), cl::init(Val: ""), cl::cat(ClangFormatCategory));
133
134static cl::opt<bool>
135 Verbose("verbose", cl::desc("If set, shows the list of processed files"),
136 cl::cat(ClangFormatCategory));
137
138// Use --dry-run to match other LLVM tools when you mean do it but don't
139// actually do it
140static cl::opt<bool>
141 DryRun("dry-run",
142 cl::desc("If set, do not actually make the formatting changes"),
143 cl::cat(ClangFormatCategory));
144
145// Use -n as a common command as an alias for --dry-run. (git and make use -n)
146static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
147 cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
148 cl::NotHidden);
149
150// Emulate being able to turn on/off the warning.
151static cl::opt<bool>
152 WarnFormat("Wclang-format-violations",
153 cl::desc("Warnings about individual formatting changes needed. "
154 "Used only with --dry-run or -n"),
155 cl::init(Val: true), cl::cat(ClangFormatCategory), cl::Hidden);
156
157static cl::opt<bool>
158 NoWarnFormat("Wno-clang-format-violations",
159 cl::desc("Do not warn about individual formatting changes "
160 "needed. Used only with --dry-run or -n"),
161 cl::init(Val: false), cl::cat(ClangFormatCategory), cl::Hidden);
162
163static cl::opt<unsigned> ErrorLimit(
164 "ferror-limit",
165 cl::desc("Set the maximum number of clang-format errors to emit\n"
166 "before stopping (0 = no limit).\n"
167 "Used only with --dry-run or -n"),
168 cl::init(Val: 0), cl::cat(ClangFormatCategory));
169
170static cl::opt<bool>
171 WarningsAsErrors("Werror",
172 cl::desc("If set, changes formatting warnings to errors"),
173 cl::cat(ClangFormatCategory));
174
175namespace {
176enum class WNoError { Unknown };
177}
178
179static cl::bits<WNoError> WNoErrorList(
180 "Wno-error",
181 cl::desc("If set, don't error out on the specified warning type."),
182 cl::values(
183 clEnumValN(WNoError::Unknown, "unknown",
184 "If set, unknown format options are only warned about.\n"
185 "This can be used to enable formatting, even if the\n"
186 "configuration contains unknown (newer) options.\n"
187 "Use with caution, as this might lead to dramatically\n"
188 "differing format depending on an option being\n"
189 "supported or not.")),
190 cl::cat(ClangFormatCategory));
191
192static cl::opt<bool>
193 ShowColors("fcolor-diagnostics",
194 cl::desc("If set, and on a color-capable terminal controls "
195 "whether or not to print diagnostics in color"),
196 cl::init(Val: true), cl::cat(ClangFormatCategory), cl::Hidden);
197
198static cl::opt<bool>
199 NoShowColors("fno-color-diagnostics",
200 cl::desc("If set, and on a color-capable terminal controls "
201 "whether or not to print diagnostics in color"),
202 cl::init(Val: false), cl::cat(ClangFormatCategory), cl::Hidden);
203
204static cl::list<std::string> FileNames(cl::Positional,
205 cl::desc("[@<file>] [<file> ...]"),
206 cl::cat(ClangFormatCategory));
207
208static cl::opt<bool> FailOnIncompleteFormat(
209 "fail-on-incomplete-format",
210 cl::desc("If set, fail with exit code 1 on incomplete format."),
211 cl::init(Val: false), cl::cat(ClangFormatCategory));
212
213static cl::opt<bool> ListIgnored("list-ignored",
214 cl::desc("List ignored files."),
215 cl::cat(ClangFormatCategory), cl::Hidden);
216
217namespace clang {
218namespace format {
219
220static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
221 SourceManager &Sources, FileManager &Files,
222 llvm::vfs::InMemoryFileSystem *MemFS) {
223 MemFS->addFileNoOwn(Path: FileName, ModificationTime: 0, Buffer: Source);
224 auto File = Files.getOptionalFileRef(Filename: FileName);
225 assert(File && "File not added to MemFS?");
226 return Sources.createFileID(SourceFile: *File, IncludePos: SourceLocation(), FileCharacter: SrcMgr::C_User);
227}
228
229// Parses <start line>:<end line> input to a pair of line numbers.
230// Returns true on error.
231static bool parseLineRange(StringRef Input, unsigned &FromLine,
232 unsigned &ToLine) {
233 std::pair<StringRef, StringRef> LineRange = Input.split(Separator: ':');
234 return LineRange.first.getAsInteger(Radix: 0, Result&: FromLine) ||
235 LineRange.second.getAsInteger(Radix: 0, Result&: ToLine);
236}
237
238static bool fillRanges(MemoryBuffer *Code,
239 std::vector<tooling::Range> &Ranges) {
240 auto InMemoryFileSystem =
241 makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
242 FileManager Files(FileSystemOptions(), InMemoryFileSystem);
243 DiagnosticOptions DiagOpts;
244 DiagnosticsEngine Diagnostics(DiagnosticIDs::create(), DiagOpts);
245 SourceManager Sources(Diagnostics, Files);
246 const auto ID = createInMemoryFile(FileName: "<irrelevant>", Source: *Code, Sources, Files,
247 MemFS: InMemoryFileSystem.get());
248 if (!LineRanges.empty()) {
249 if (!Offsets.empty() || !Lengths.empty()) {
250 errs() << "error: cannot use -lines with -offset/-length\n";
251 return true;
252 }
253
254 for (const auto &LineRange : LineRanges) {
255 unsigned FromLine, ToLine;
256 if (parseLineRange(Input: LineRange, FromLine, ToLine)) {
257 errs() << "error: invalid <start line>:<end line> pair\n";
258 return true;
259 }
260 if (FromLine < 1) {
261 errs() << "error: start line should be at least 1\n";
262 return true;
263 }
264 if (FromLine > ToLine) {
265 errs() << "error: start line should not exceed end line\n";
266 return true;
267 }
268 const auto Start = Sources.translateLineCol(FID: ID, Line: FromLine, Col: 1);
269 const auto End = Sources.translateLineCol(FID: ID, Line: ToLine, UINT_MAX);
270 if (Start.isInvalid() || End.isInvalid())
271 return true;
272 const auto Offset = Sources.getFileOffset(SpellingLoc: Start);
273 const auto Length = Sources.getFileOffset(SpellingLoc: End) - Offset;
274 Ranges.push_back(x: tooling::Range(Offset, Length));
275 }
276 return false;
277 }
278
279 if (Offsets.empty())
280 Offsets.push_back(value: 0);
281 const bool EmptyLengths = Lengths.empty();
282 unsigned Length = 0;
283 if (Offsets.size() == 1 && EmptyLengths) {
284 Length = Sources.getFileOffset(SpellingLoc: Sources.getLocForEndOfFile(FID: ID)) - Offsets[0];
285 } else if (Offsets.size() != Lengths.size()) {
286 errs() << "error: number of -offset and -length arguments must match.\n";
287 return true;
288 }
289 for (unsigned I = 0, E = Offsets.size(), CodeSize = Code->getBufferSize();
290 I < E; ++I) {
291 const auto Offset = Offsets[I];
292 if (Offset >= CodeSize) {
293 errs() << "error: offset " << Offset << " is outside the file\n";
294 return true;
295 }
296 if (!EmptyLengths)
297 Length = Lengths[I];
298 if (Offset + Length > CodeSize) {
299 errs() << "error: invalid length " << Length << ", offset + length ("
300 << Offset + Length << ") is outside the file.\n";
301 return true;
302 }
303 Ranges.push_back(x: tooling::Range(Offset, Length));
304 }
305 return false;
306}
307
308static void outputReplacementXML(StringRef Text) {
309 // FIXME: When we sort includes, we need to make sure the stream is correct
310 // utf-8.
311 size_t From = 0;
312 size_t Index;
313 while ((Index = Text.find_first_of(Chars: "\n\r<&", From)) != StringRef::npos) {
314 outs() << Text.substr(Start: From, N: Index - From);
315 switch (Text[Index]) {
316 case '\n':
317 outs() << "&#10;";
318 break;
319 case '\r':
320 outs() << "&#13;";
321 break;
322 case '<':
323 outs() << "&lt;";
324 break;
325 case '&':
326 outs() << "&amp;";
327 break;
328 default:
329 llvm_unreachable("Unexpected character encountered!");
330 }
331 From = Index + 1;
332 }
333 outs() << Text.substr(Start: From);
334}
335
336static void outputReplacementsXML(const Replacements &Replaces) {
337 for (const auto &R : Replaces) {
338 outs() << "<replacement "
339 << "offset='" << R.getOffset() << "' "
340 << "length='" << R.getLength() << "'>";
341 outputReplacementXML(Text: R.getReplacementText());
342 outs() << "</replacement>\n";
343 }
344}
345
346static bool
347emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
348 const std::unique_ptr<llvm::MemoryBuffer> &Code) {
349 unsigned Errors = 0;
350 if (WarnFormat && !NoWarnFormat) {
351 SourceMgr Mgr;
352 const char *StartBuf = Code->getBufferStart();
353
354 Mgr.AddNewSourceBuffer(
355 F: MemoryBuffer::getMemBuffer(InputData: StartBuf, BufferName: AssumedFileName), 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);
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 const auto Pathname{convert_to_slash(path: AbsPath)};
641 for (const auto &Pat : Patterns) {
642 const bool IsNegated = Pat[0] == '!';
643 StringRef Pattern{Pat};
644 if (IsNegated)
645 Pattern = Pattern.drop_front();
646
647 if (Pattern.empty())
648 continue;
649
650 Pattern = Pattern.ltrim();
651
652 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
653 // This doesn't support patterns containing drive names (e.g. `C:`).
654 if (Pattern[0] != '/') {
655 Path = IgnoreDir;
656 append(path&: Path, style: Style::posix, a: Pattern);
657 remove_dots(path&: Path, /*remove_dot_dot=*/true, style: Style::posix);
658 Pattern = Path;
659 }
660
661 if (clang::format::matchFilePath(Pattern, FilePath: Pathname) == !IsNegated)
662 return true;
663 }
664
665 return false;
666}
667
668int main(int argc, const char **argv) {
669 InitLLVM X(argc, argv);
670
671 cl::HideUnrelatedOptions(Category&: ClangFormatCategory);
672
673 cl::SetVersionPrinter(PrintVersion);
674 cl::ParseCommandLineOptions(
675 argc, argv,
676 Overview: "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
677 "code.\n\n"
678 "If no arguments are specified, it formats the code from standard input\n"
679 "and writes the result to the standard output.\n"
680 "If <file>s are given, it reformats the files. If -i is specified\n"
681 "together with <file>s, the files are edited in-place. Otherwise, the\n"
682 "result is written to the standard output.\n");
683
684 if (Help) {
685 cl::PrintHelpMessage();
686 return 0;
687 }
688
689 if (DumpConfig)
690 return dumpConfig();
691
692 if (!Files.empty()) {
693 std::ifstream ExternalFileOfFiles{std::string(Files)};
694 std::string Line;
695 unsigned LineNo = 1;
696 while (std::getline(is&: ExternalFileOfFiles, str&: Line)) {
697 FileNames.push_back(value: Line);
698 LineNo++;
699 }
700 errs() << "Clang-formatting " << LineNo << " files\n";
701 }
702
703 if (FileNames.empty()) {
704 if (isIgnored(FilePath: AssumeFileName))
705 return 0;
706 return clang::format::format(FileName: "-", ErrorOnIncompleteFormat: FailOnIncompleteFormat);
707 }
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 const bool Ignored = isIgnored(FilePath: FileName);
720 if (ListIgnored) {
721 if (Ignored)
722 outs() << FileName << '\n';
723 continue;
724 }
725 if (Ignored)
726 continue;
727 if (Verbose) {
728 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
729 << FileName << "\n";
730 }
731 Error |= clang::format::format(FileName, ErrorOnIncompleteFormat: FailOnIncompleteFormat);
732 }
733 return Error ? 1 : 0;
734}
735