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 "clang/Basic/Diagnostic.h" 16 #include "clang/Basic/DiagnosticOptions.h" 17 #include "clang/Basic/FileManager.h" 18 #include "clang/Basic/SourceManager.h" 19 #include "clang/Basic/Version.h" 20 #include "clang/Format/Format.h" 21 #include "clang/Frontend/TextDiagnosticPrinter.h" 22 #include "clang/Rewrite/Core/Rewriter.h" 23 #include "llvm/Support/CommandLine.h" 24 #include "llvm/Support/FileSystem.h" 25 #include "llvm/Support/InitLLVM.h" 26 #include "llvm/Support/Process.h" 27 28 using namespace llvm; 29 using clang::tooling::Replacements; 30 31 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); 32 33 // Mark all our options with this category, everything else (except for -version 34 // and -help) will be hidden. 35 static cl::OptionCategory ClangFormatCategory("Clang-format options"); 36 37 static cl::list<unsigned> 38 Offsets("offset", 39 cl::desc("Format a range starting at this byte offset.\n" 40 "Multiple ranges can be formatted by specifying\n" 41 "several -offset and -length pairs.\n" 42 "Can only be used with one input file."), 43 cl::cat(ClangFormatCategory)); 44 static cl::list<unsigned> 45 Lengths("length", 46 cl::desc("Format a range of this length (in bytes).\n" 47 "Multiple ranges can be formatted by specifying\n" 48 "several -offset and -length pairs.\n" 49 "When only a single -offset is specified without\n" 50 "-length, clang-format will format up to the end\n" 51 "of the file.\n" 52 "Can only be used with one input file."), 53 cl::cat(ClangFormatCategory)); 54 static cl::list<std::string> 55 LineRanges("lines", 56 cl::desc("<start line>:<end line> - format a range of\n" 57 "lines (both 1-based).\n" 58 "Multiple ranges can be formatted by specifying\n" 59 "several -lines arguments.\n" 60 "Can't be used with -offset and -length.\n" 61 "Can only be used with one input file."), 62 cl::cat(ClangFormatCategory)); 63 static cl::opt<std::string> 64 Style("style", cl::desc(clang::format::StyleOptionHelpDescription), 65 cl::init(clang::format::DefaultFormatStyle), 66 cl::cat(ClangFormatCategory)); 67 static cl::opt<std::string> 68 FallbackStyle("fallback-style", 69 cl::desc("The name of the predefined style used as a\n" 70 "fallback in case clang-format is invoked with\n" 71 "-style=file, but can not find the .clang-format\n" 72 "file to use.\n" 73 "Use -fallback-style=none to skip formatting."), 74 cl::init(clang::format::DefaultFallbackStyle), 75 cl::cat(ClangFormatCategory)); 76 77 static cl::opt<std::string> AssumeFileName( 78 "assume-filename", 79 cl::desc("Override filename used to determine the language.\n" 80 "When reading from stdin, clang-format assumes this\n" 81 "filename to determine the language."), 82 cl::init("<stdin>"), cl::cat(ClangFormatCategory)); 83 84 static cl::opt<bool> Inplace("i", 85 cl::desc("Inplace edit <file>s, if specified."), 86 cl::cat(ClangFormatCategory)); 87 88 static cl::opt<bool> OutputXML("output-replacements-xml", 89 cl::desc("Output replacements as XML."), 90 cl::cat(ClangFormatCategory)); 91 static cl::opt<bool> 92 DumpConfig("dump-config", 93 cl::desc("Dump configuration options to stdout and exit.\n" 94 "Can be used with -style option."), 95 cl::cat(ClangFormatCategory)); 96 static cl::opt<unsigned> 97 Cursor("cursor", 98 cl::desc("The position of the cursor when invoking\n" 99 "clang-format from an editor integration"), 100 cl::init(0), cl::cat(ClangFormatCategory)); 101 102 static cl::opt<bool> SortIncludes( 103 "sort-includes", 104 cl::desc("If set, overrides the include sorting behavior determined by the " 105 "SortIncludes style flag"), 106 cl::cat(ClangFormatCategory)); 107 108 // using the full param name as Wno-error probably won't be a common use case in 109 // clang-format 110 static cl::opt<bool> AllowUnknownOptions( 111 "Wno-error=unknown", 112 cl::desc("If set, unknown format options are only warned about.\n" 113 "This can be used to enable formatting, even if the\n" 114 "configuration contains unknown (newer) options.\n" 115 "Use with caution, as this might lead to dramatically\n" 116 "differing format depending on an option being\n" 117 "supported or not."), 118 cl::init(false), cl::cat(ClangFormatCategory)); 119 120 static cl::opt<bool> 121 Verbose("verbose", cl::desc("If set, shows the list of processed files"), 122 cl::cat(ClangFormatCategory)); 123 124 // Use --dry-run to match other LLVM tools when you mean do it but don't 125 // actually do it 126 static cl::opt<bool> 127 DryRun("dry-run", 128 cl::desc("If set, do not actually make the formatting changes"), 129 cl::cat(ClangFormatCategory)); 130 131 // Use -n as a common command as an alias for --dry-run. (git and make use -n) 132 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"), 133 cl::cat(ClangFormatCategory), cl::aliasopt(DryRun), 134 cl::NotHidden); 135 136 // Emulate being able to turn on/off the warning. 137 static cl::opt<bool> 138 WarnFormat("Wclang-format-violations", 139 cl::desc("Warnings about individual formatting changes needed. " 140 "Used only with --dry-run or -n"), 141 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 142 143 static cl::opt<bool> 144 NoWarnFormat("Wno-clang-format-violations", 145 cl::desc("Do not warn about individual formatting changes " 146 "needed. Used only with --dry-run or -n"), 147 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 148 149 static cl::opt<unsigned> ErrorLimit( 150 "ferror-limit", 151 cl::desc("Set the maximum number of clang-format errors to emit before " 152 "stopping (0 = no limit). Used only with --dry-run or -n"), 153 cl::init(0), cl::cat(ClangFormatCategory)); 154 155 static cl::opt<bool> 156 WarningsAsErrors("Werror", 157 cl::desc("If set, changes formatting warnings to errors"), 158 cl::cat(ClangFormatCategory)); 159 160 static cl::opt<bool> 161 ShowColors("fcolor-diagnostics", 162 cl::desc("If set, and on a color-capable terminal controls " 163 "whether or not to print diagnostics in color"), 164 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 165 166 static cl::opt<bool> 167 NoShowColors("fno-color-diagnostics", 168 cl::desc("If set, and on a color-capable terminal controls " 169 "whether or not to print diagnostics in color"), 170 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 171 172 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"), 173 cl::cat(ClangFormatCategory)); 174 175 namespace clang { 176 namespace format { 177 178 static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source, 179 SourceManager &Sources, FileManager &Files, 180 llvm::vfs::InMemoryFileSystem *MemFS) { 181 MemFS->addFileNoOwn(FileName, 0, Source); 182 auto File = Files.getFile(FileName); 183 assert(File && "File not added to MemFS?"); 184 return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User); 185 } 186 187 // Parses <start line>:<end line> input to a pair of line numbers. 188 // Returns true on error. 189 static bool parseLineRange(StringRef Input, unsigned &FromLine, 190 unsigned &ToLine) { 191 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 192 return LineRange.first.getAsInteger(0, FromLine) || 193 LineRange.second.getAsInteger(0, ToLine); 194 } 195 196 static bool fillRanges(MemoryBuffer *Code, 197 std::vector<tooling::Range> &Ranges) { 198 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 199 new llvm::vfs::InMemoryFileSystem); 200 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 201 DiagnosticsEngine Diagnostics( 202 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 203 new DiagnosticOptions); 204 SourceManager Sources(Diagnostics, Files); 205 FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files, 206 InMemoryFileSystem.get()); 207 if (!LineRanges.empty()) { 208 if (!Offsets.empty() || !Lengths.empty()) { 209 errs() << "error: cannot use -lines with -offset/-length\n"; 210 return true; 211 } 212 213 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 214 unsigned FromLine, ToLine; 215 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 216 errs() << "error: invalid <start line>:<end line> pair\n"; 217 return true; 218 } 219 if (FromLine > ToLine) { 220 errs() << "error: start line should be less than end line\n"; 221 return true; 222 } 223 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 224 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 225 if (Start.isInvalid() || End.isInvalid()) 226 return true; 227 unsigned Offset = Sources.getFileOffset(Start); 228 unsigned Length = Sources.getFileOffset(End) - Offset; 229 Ranges.push_back(tooling::Range(Offset, Length)); 230 } 231 return false; 232 } 233 234 if (Offsets.empty()) 235 Offsets.push_back(0); 236 if (Offsets.size() != Lengths.size() && 237 !(Offsets.size() == 1 && Lengths.empty())) { 238 errs() << "error: number of -offset and -length arguments must match.\n"; 239 return true; 240 } 241 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 242 if (Offsets[i] >= Code->getBufferSize()) { 243 errs() << "error: offset " << Offsets[i] << " is outside the file\n"; 244 return true; 245 } 246 SourceLocation Start = 247 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 248 SourceLocation End; 249 if (i < Lengths.size()) { 250 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 251 errs() << "error: invalid length " << Lengths[i] 252 << ", offset + length (" << Offsets[i] + Lengths[i] 253 << ") is outside the file.\n"; 254 return true; 255 } 256 End = Start.getLocWithOffset(Lengths[i]); 257 } else { 258 End = Sources.getLocForEndOfFile(ID); 259 } 260 unsigned Offset = Sources.getFileOffset(Start); 261 unsigned Length = Sources.getFileOffset(End) - Offset; 262 Ranges.push_back(tooling::Range(Offset, Length)); 263 } 264 return false; 265 } 266 267 static void outputReplacementXML(StringRef Text) { 268 // FIXME: When we sort includes, we need to make sure the stream is correct 269 // utf-8. 270 size_t From = 0; 271 size_t Index; 272 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { 273 outs() << Text.substr(From, Index - From); 274 switch (Text[Index]) { 275 case '\n': 276 outs() << " "; 277 break; 278 case '\r': 279 outs() << " "; 280 break; 281 case '<': 282 outs() << "<"; 283 break; 284 case '&': 285 outs() << "&"; 286 break; 287 default: 288 llvm_unreachable("Unexpected character encountered!"); 289 } 290 From = Index + 1; 291 } 292 outs() << Text.substr(From); 293 } 294 295 static void outputReplacementsXML(const Replacements &Replaces) { 296 for (const auto &R : Replaces) { 297 outs() << "<replacement " 298 << "offset='" << R.getOffset() << "' " 299 << "length='" << R.getLength() << "'>"; 300 outputReplacementXML(R.getReplacementText()); 301 outs() << "</replacement>\n"; 302 } 303 } 304 305 static bool 306 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 307 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 308 if (Replaces.empty()) 309 return false; 310 311 unsigned Errors = 0; 312 if (WarnFormat && !NoWarnFormat) { 313 llvm::SourceMgr Mgr; 314 const char *StartBuf = Code->getBufferStart(); 315 316 Mgr.AddNewSourceBuffer( 317 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); 318 for (const auto &R : Replaces) { 319 SMDiagnostic Diag = Mgr.GetMessage( 320 SMLoc::getFromPointer(StartBuf + R.getOffset()), 321 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 322 : SourceMgr::DiagKind::DK_Warning, 323 "code should be clang-formatted [-Wclang-format-violations]"); 324 325 Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); 326 if (ErrorLimit && ++Errors >= ErrorLimit) 327 break; 328 } 329 } 330 return WarningsAsErrors; 331 } 332 333 static void outputXML(const Replacements &Replaces, 334 const Replacements &FormatChanges, 335 const FormattingAttemptStatus &Status, 336 const cl::opt<unsigned> &Cursor, 337 unsigned CursorPosition) { 338 outs() << "<?xml version='1.0'?>\n<replacements " 339 "xml:space='preserve' incomplete_format='" 340 << (Status.FormatComplete ? "false" : "true") << "'"; 341 if (!Status.FormatComplete) 342 outs() << " line='" << Status.Line << "'"; 343 outs() << ">\n"; 344 if (Cursor.getNumOccurrences() != 0) 345 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition) 346 << "</cursor>\n"; 347 348 outputReplacementsXML(Replaces); 349 outs() << "</replacements>\n"; 350 } 351 352 // Returns true on error. 353 static bool format(StringRef FileName) { 354 if (!OutputXML && Inplace && FileName == "-") { 355 errs() << "error: cannot use -i when reading from stdin.\n"; 356 return false; 357 } 358 // On Windows, overwriting a file with an open file mapping doesn't work, 359 // so read the whole file into memory when formatting in-place. 360 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 361 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) 362 : MemoryBuffer::getFileOrSTDIN(FileName); 363 if (std::error_code EC = CodeOrErr.getError()) { 364 errs() << EC.message() << "\n"; 365 return true; 366 } 367 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 368 if (Code->getBufferSize() == 0) 369 return false; // Empty files are formatted correctly. 370 371 StringRef BufStr = Code->getBuffer(); 372 373 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 374 375 if (InvalidBOM) { 376 errs() << "error: encoding with unsupported byte order mark \"" 377 << InvalidBOM << "\" detected"; 378 if (FileName != "-") 379 errs() << " in file '" << FileName << "'"; 380 errs() << ".\n"; 381 return true; 382 } 383 384 std::vector<tooling::Range> Ranges; 385 if (fillRanges(Code.get(), Ranges)) 386 return true; 387 StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; 388 if (AssumedFileName.empty()) { 389 llvm::errs() << "error: empty filenames are not allowed\n"; 390 return true; 391 } 392 393 llvm::Expected<FormatStyle> FormatStyle = 394 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 395 nullptr, AllowUnknownOptions.getValue()); 396 if (!FormatStyle) { 397 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 398 return true; 399 } 400 401 if (SortIncludes.getNumOccurrences() != 0) 402 FormatStyle->SortIncludes = SortIncludes; 403 unsigned CursorPosition = Cursor; 404 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 405 AssumedFileName, &CursorPosition); 406 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 407 if (!ChangedCode) { 408 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 409 return true; 410 } 411 // Get new affected ranges after sorting `#includes`. 412 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 413 FormattingAttemptStatus Status; 414 Replacements FormatChanges = 415 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 416 Replaces = Replaces.merge(FormatChanges); 417 if (OutputXML || DryRun) { 418 if (DryRun) { 419 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 420 } else { 421 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 422 } 423 } else { 424 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 425 new llvm::vfs::InMemoryFileSystem); 426 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 427 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 428 TextDiagnosticPrinter DiagnosticsConsumer(errs(), &*DiagOpts); 429 DiagnosticsEngine Diagnostics( 430 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, 431 &DiagnosticsConsumer, false); 432 SourceManager Sources(Diagnostics, Files); 433 FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files, 434 InMemoryFileSystem.get()); 435 Rewriter Rewrite(Sources, LangOptions()); 436 tooling::applyAllReplacements(Replaces, Rewrite); 437 if (Inplace) { 438 if (Rewrite.overwriteChangedFiles()) 439 return true; 440 } else { 441 if (Cursor.getNumOccurrences() != 0) { 442 outs() << "{ \"Cursor\": " 443 << FormatChanges.getShiftedCodePosition(CursorPosition) 444 << ", \"IncompleteFormat\": " 445 << (Status.FormatComplete ? "false" : "true"); 446 if (!Status.FormatComplete) 447 outs() << ", \"Line\": " << Status.Line; 448 outs() << " }\n"; 449 } 450 Rewrite.getEditBuffer(ID).write(outs()); 451 } 452 } 453 return false; 454 } 455 456 } // namespace format 457 } // namespace clang 458 459 static void PrintVersion(raw_ostream &OS) { 460 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 461 } 462 463 // Dump the configuration. 464 static int dumpConfig() { 465 StringRef FileName; 466 std::unique_ptr<llvm::MemoryBuffer> Code; 467 if (FileNames.empty()) { 468 // We can't read the code to detect the language if there's no 469 // file name, so leave Code empty here. 470 FileName = AssumeFileName; 471 } else { 472 // Read in the code in case the filename alone isn't enough to 473 // detect the language. 474 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 475 MemoryBuffer::getFileOrSTDIN(FileNames[0]); 476 if (std::error_code EC = CodeOrErr.getError()) { 477 llvm::errs() << EC.message() << "\n"; 478 return 1; 479 } 480 FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0]; 481 Code = std::move(CodeOrErr.get()); 482 } 483 llvm::Expected<clang::format::FormatStyle> FormatStyle = 484 clang::format::getStyle(Style, FileName, FallbackStyle, 485 Code ? Code->getBuffer() : ""); 486 if (!FormatStyle) { 487 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 488 return 1; 489 } 490 std::string Config = clang::format::configurationAsText(*FormatStyle); 491 outs() << Config << "\n"; 492 return 0; 493 } 494 495 int main(int argc, const char **argv) { 496 llvm::InitLLVM X(argc, argv); 497 498 cl::HideUnrelatedOptions(ClangFormatCategory); 499 500 cl::SetVersionPrinter(PrintVersion); 501 cl::ParseCommandLineOptions( 502 argc, argv, 503 "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n" 504 "If no arguments are specified, it formats the code from standard input\n" 505 "and writes the result to the standard output.\n" 506 "If <file>s are given, it reformats the files. If -i is specified\n" 507 "together with <file>s, the files are edited in-place. Otherwise, the\n" 508 "result is written to the standard output.\n"); 509 510 if (Help) { 511 cl::PrintHelpMessage(); 512 return 0; 513 } 514 515 if (DumpConfig) { 516 return dumpConfig(); 517 } 518 519 bool Error = false; 520 if (FileNames.empty()) { 521 Error = clang::format::format("-"); 522 return Error ? 1 : 0; 523 } 524 if (FileNames.size() != 1 && 525 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 526 errs() << "error: -offset, -length and -lines can only be used for " 527 "single file.\n"; 528 return 1; 529 } 530 for (const auto &FileName : FileNames) { 531 if (Verbose) 532 errs() << "Formatting " << FileName << "\n"; 533 Error |= clang::format::format(FileName); 534 } 535 return Error ? 1 : 0; 536 } 537