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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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