1 //===--- tools/extra/clang-tidy/ClangTidyMain.cpp - Clang tidy 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 This file implements a clang-tidy tool.
10 ///
11 /// This tool uses the Clang Tooling infrastructure, see
12 /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
13 /// for details on setting it up with LLVM source tree.
14 ///
15 //===----------------------------------------------------------------------===//
16
17 #include "ClangTidyMain.h"
18 #include "../ClangTidy.h"
19 #include "../ClangTidyForceLinker.h"
20 #include "../GlobList.h"
21 #include "clang/Tooling/CommonOptionsParser.h"
22 #include "llvm/ADT/StringSet.h"
23 #include "llvm/Support/InitLLVM.h"
24 #include "llvm/Support/PluginLoader.h"
25 #include "llvm/Support/Process.h"
26 #include "llvm/Support/Signals.h"
27 #include "llvm/Support/TargetSelect.h"
28 #include "llvm/Support/WithColor.h"
29
30 using namespace clang::tooling;
31 using namespace llvm;
32
33 static cl::OptionCategory ClangTidyCategory("clang-tidy options");
34
35 static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
36 static cl::extrahelp ClangTidyHelp(R"(
37 Configuration files:
38 clang-tidy attempts to read configuration for each source file from a
39 .clang-tidy file located in the closest parent directory of the source
40 file. If InheritParentConfig is true in a config file, the configuration file
41 in the parent directory (if any exists) will be taken and current config file
42 will be applied on top of the parent one. If any configuration options have
43 a corresponding command-line option, command-line option takes precedence.
44 The effective configuration can be inspected using -dump-config:
45
46 $ clang-tidy -dump-config
47 ---
48 Checks: '-*,some-check'
49 WarningsAsErrors: ''
50 HeaderFilterRegex: ''
51 FormatStyle: none
52 InheritParentConfig: true
53 User: user
54 CheckOptions:
55 some-check.SomeOption: 'some value'
56 ...
57
58 )");
59
60 const char DefaultChecks[] = // Enable these checks by default:
61 "clang-diagnostic-*," // * compiler diagnostics
62 "clang-analyzer-*"; // * Static Analyzer checks
63
64 static cl::opt<std::string> Checks("checks", cl::desc(R"(
65 Comma-separated list of globs with optional '-'
66 prefix. Globs are processed in order of
67 appearance in the list. Globs without '-'
68 prefix add checks with matching names to the
69 set, globs with the '-' prefix remove checks
70 with matching names from the set of enabled
71 checks. This option's value is appended to the
72 value of the 'Checks' option in .clang-tidy
73 file, if any.
74 )"),
75 cl::init(""), cl::cat(ClangTidyCategory));
76
77 static cl::opt<std::string> WarningsAsErrors("warnings-as-errors", cl::desc(R"(
78 Upgrades warnings to errors. Same format as
79 '-checks'.
80 This option's value is appended to the value of
81 the 'WarningsAsErrors' option in .clang-tidy
82 file, if any.
83 )"),
84 cl::init(""),
85 cl::cat(ClangTidyCategory));
86
87 static cl::opt<std::string> HeaderFilter("header-filter", cl::desc(R"(
88 Regular expression matching the names of the
89 headers to output diagnostics from. Diagnostics
90 from the main file of each translation unit are
91 always displayed.
92 Can be used together with -line-filter.
93 This option overrides the 'HeaderFilterRegex'
94 option in .clang-tidy file, if any.
95 )"),
96 cl::init(""),
97 cl::cat(ClangTidyCategory));
98
99 static cl::opt<bool>
100 SystemHeaders("system-headers",
101 cl::desc("Display the errors from system headers."),
102 cl::init(false), cl::cat(ClangTidyCategory));
103 static cl::opt<std::string> LineFilter("line-filter", cl::desc(R"(
104 List of files with line ranges to filter the
105 warnings. Can be used together with
106 -header-filter. The format of the list is a
107 JSON array of objects:
108 [
109 {"name":"file1.cpp","lines":[[1,3],[5,7]]},
110 {"name":"file2.h"}
111 ]
112 )"),
113 cl::init(""),
114 cl::cat(ClangTidyCategory));
115
116 static cl::opt<bool> Fix("fix", cl::desc(R"(
117 Apply suggested fixes. Without -fix-errors
118 clang-tidy will bail out if any compilation
119 errors were found.
120 )"),
121 cl::init(false), cl::cat(ClangTidyCategory));
122
123 static cl::opt<bool> FixErrors("fix-errors", cl::desc(R"(
124 Apply suggested fixes even if compilation
125 errors were found. If compiler errors have
126 attached fix-its, clang-tidy will apply them as
127 well.
128 )"),
129 cl::init(false), cl::cat(ClangTidyCategory));
130
131 static cl::opt<bool> FixNotes("fix-notes", cl::desc(R"(
132 If a warning has no fix, but a single fix can
133 be found through an associated diagnostic note,
134 apply the fix.
135 Specifying this flag will implicitly enable the
136 '--fix' flag.
137 )"),
138 cl::init(false), cl::cat(ClangTidyCategory));
139
140 static cl::opt<std::string> FormatStyle("format-style", cl::desc(R"(
141 Style for formatting code around applied fixes:
142 - 'none' (default) turns off formatting
143 - 'file' (literally 'file', not a placeholder)
144 uses .clang-format file in the closest parent
145 directory
146 - '{ <json> }' specifies options inline, e.g.
147 -format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
148 - 'llvm', 'google', 'webkit', 'mozilla'
149 See clang-format documentation for the up-to-date
150 information about formatting styles and options.
151 This option overrides the 'FormatStyle` option in
152 .clang-tidy file, if any.
153 )"),
154 cl::init("none"),
155 cl::cat(ClangTidyCategory));
156
157 static cl::opt<bool> ListChecks("list-checks", cl::desc(R"(
158 List all enabled checks and exit. Use with
159 -checks=* to list all available checks.
160 )"),
161 cl::init(false), cl::cat(ClangTidyCategory));
162
163 static cl::opt<bool> ExplainConfig("explain-config", cl::desc(R"(
164 For each enabled check explains, where it is
165 enabled, i.e. in clang-tidy binary, command
166 line or a specific configuration file.
167 )"),
168 cl::init(false), cl::cat(ClangTidyCategory));
169
170 static cl::opt<std::string> Config("config", cl::desc(R"(
171 Specifies a configuration in YAML/JSON format:
172 -config="{Checks: '*',
173 CheckOptions: {x: y}}"
174 When the value is empty, clang-tidy will
175 attempt to find a file named .clang-tidy for
176 each source file in its parent directories.
177 )"),
178 cl::init(""), cl::cat(ClangTidyCategory));
179
180 static cl::opt<std::string> ConfigFile("config-file", cl::desc(R"(
181 Specify the path of .clang-tidy or custom config file:
182 e.g. --config-file=/some/path/myTidyConfigFile
183 This option internally works exactly the same way as
184 --config option after reading specified config file.
185 Use either --config-file or --config, not both.
186 )"),
187 cl::init(""),
188 cl::cat(ClangTidyCategory));
189
190 static cl::opt<bool> DumpConfig("dump-config", cl::desc(R"(
191 Dumps configuration in the YAML format to
192 stdout. This option can be used along with a
193 file name (and '--' if the file is outside of a
194 project with configured compilation database).
195 The configuration used for this file will be
196 printed.
197 Use along with -checks=* to include
198 configuration of all checks.
199 )"),
200 cl::init(false), cl::cat(ClangTidyCategory));
201
202 static cl::opt<bool> EnableCheckProfile("enable-check-profile", cl::desc(R"(
203 Enable per-check timing profiles, and print a
204 report to stderr.
205 )"),
206 cl::init(false),
207 cl::cat(ClangTidyCategory));
208
209 static cl::opt<std::string> StoreCheckProfile("store-check-profile",
210 cl::desc(R"(
211 By default reports are printed in tabulated
212 format to stderr. When this option is passed,
213 these per-TU profiles are instead stored as JSON.
214 )"),
215 cl::value_desc("prefix"),
216 cl::cat(ClangTidyCategory));
217
218 /// This option allows enabling the experimental alpha checkers from the static
219 /// analyzer. This option is set to false and not visible in help, because it is
220 /// highly not recommended for users.
221 static cl::opt<bool>
222 AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers",
223 cl::init(false), cl::Hidden,
224 cl::cat(ClangTidyCategory));
225
226 static cl::opt<std::string> ExportFixes("export-fixes", cl::desc(R"(
227 YAML file to store suggested fixes in. The
228 stored fixes can be applied to the input source
229 code with clang-apply-replacements.
230 )"),
231 cl::value_desc("filename"),
232 cl::cat(ClangTidyCategory));
233
234 static cl::opt<bool> Quiet("quiet", cl::desc(R"(
235 Run clang-tidy in quiet mode. This suppresses
236 printing statistics about ignored warnings and
237 warnings treated as errors if the respective
238 options are specified.
239 )"),
240 cl::init(false),
241 cl::cat(ClangTidyCategory));
242
243 static cl::opt<std::string> VfsOverlay("vfsoverlay", cl::desc(R"(
244 Overlay the virtual filesystem described by file
245 over the real file system.
246 )"),
247 cl::value_desc("filename"),
248 cl::cat(ClangTidyCategory));
249
250 static cl::opt<bool> UseColor("use-color", cl::desc(R"(
251 Use colors in diagnostics. If not set, colors
252 will be used if the terminal connected to
253 standard output supports colors.
254 This option overrides the 'UseColor' option in
255 .clang-tidy file, if any.
256 )"),
257 cl::init(false), cl::cat(ClangTidyCategory));
258
259 static cl::opt<bool> VerifyConfig("verify-config", cl::desc(R"(
260 Check the config files to ensure each check and
261 option is recognized.
262 )"),
263 cl::init(false), cl::cat(ClangTidyCategory));
264
265 namespace clang {
266 namespace tidy {
267
printStats(const ClangTidyStats & Stats)268 static void printStats(const ClangTidyStats &Stats) {
269 if (Stats.errorsIgnored()) {
270 llvm::errs() << "Suppressed " << Stats.errorsIgnored() << " warnings (";
271 StringRef Separator = "";
272 if (Stats.ErrorsIgnoredNonUserCode) {
273 llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code";
274 Separator = ", ";
275 }
276 if (Stats.ErrorsIgnoredLineFilter) {
277 llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter
278 << " due to line filter";
279 Separator = ", ";
280 }
281 if (Stats.ErrorsIgnoredNOLINT) {
282 llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT";
283 Separator = ", ";
284 }
285 if (Stats.ErrorsIgnoredCheckFilter)
286 llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter
287 << " with check filters";
288 llvm::errs() << ").\n";
289 if (Stats.ErrorsIgnoredNonUserCode)
290 llvm::errs() << "Use -header-filter=.* to display errors from all "
291 "non-system headers. Use -system-headers to display "
292 "errors from system headers as well.\n";
293 }
294 }
295
createOptionsProvider(llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS)296 static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
297 llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) {
298 ClangTidyGlobalOptions GlobalOptions;
299 if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) {
300 llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n";
301 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
302 return nullptr;
303 }
304
305 ClangTidyOptions DefaultOptions;
306 DefaultOptions.Checks = DefaultChecks;
307 DefaultOptions.WarningsAsErrors = "";
308 DefaultOptions.HeaderFilterRegex = HeaderFilter;
309 DefaultOptions.SystemHeaders = SystemHeaders;
310 DefaultOptions.FormatStyle = FormatStyle;
311 DefaultOptions.User = llvm::sys::Process::GetEnv("USER");
312 // USERNAME is used on Windows.
313 if (!DefaultOptions.User)
314 DefaultOptions.User = llvm::sys::Process::GetEnv("USERNAME");
315
316 ClangTidyOptions OverrideOptions;
317 if (Checks.getNumOccurrences() > 0)
318 OverrideOptions.Checks = Checks;
319 if (WarningsAsErrors.getNumOccurrences() > 0)
320 OverrideOptions.WarningsAsErrors = WarningsAsErrors;
321 if (HeaderFilter.getNumOccurrences() > 0)
322 OverrideOptions.HeaderFilterRegex = HeaderFilter;
323 if (SystemHeaders.getNumOccurrences() > 0)
324 OverrideOptions.SystemHeaders = SystemHeaders;
325 if (FormatStyle.getNumOccurrences() > 0)
326 OverrideOptions.FormatStyle = FormatStyle;
327 if (UseColor.getNumOccurrences() > 0)
328 OverrideOptions.UseColor = UseColor;
329
330 auto LoadConfig =
331 [&](StringRef Configuration,
332 StringRef Source) -> std::unique_ptr<ClangTidyOptionsProvider> {
333 llvm::ErrorOr<ClangTidyOptions> ParsedConfig =
334 parseConfiguration(MemoryBufferRef(Configuration, Source));
335 if (ParsedConfig)
336 return std::make_unique<ConfigOptionsProvider>(
337 std::move(GlobalOptions),
338 ClangTidyOptions::getDefaults().merge(DefaultOptions, 0),
339 std::move(*ParsedConfig), std::move(OverrideOptions), std::move(FS));
340 llvm::errs() << "Error: invalid configuration specified.\n"
341 << ParsedConfig.getError().message() << "\n";
342 return nullptr;
343 };
344
345 if (ConfigFile.getNumOccurrences() > 0) {
346 if (Config.getNumOccurrences() > 0) {
347 llvm::errs() << "Error: --config-file and --config are "
348 "mutually exclusive. Specify only one.\n";
349 return nullptr;
350 }
351
352 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
353 llvm::MemoryBuffer::getFile(ConfigFile);
354 if (std::error_code EC = Text.getError()) {
355 llvm::errs() << "Error: can't read config-file '" << ConfigFile
356 << "': " << EC.message() << "\n";
357 return nullptr;
358 }
359
360 return LoadConfig((*Text)->getBuffer(), ConfigFile);
361 }
362
363 if (Config.getNumOccurrences() > 0)
364 return LoadConfig(Config, "<command-line-config>");
365
366 return std::make_unique<FileOptionsProvider>(
367 std::move(GlobalOptions), std::move(DefaultOptions),
368 std::move(OverrideOptions), std::move(FS));
369 }
370
371 llvm::IntrusiveRefCntPtr<vfs::FileSystem>
getVfsFromFile(const std::string & OverlayFile,llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS)372 getVfsFromFile(const std::string &OverlayFile,
373 llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS) {
374 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer =
375 BaseFS->getBufferForFile(OverlayFile);
376 if (!Buffer) {
377 llvm::errs() << "Can't load virtual filesystem overlay file '"
378 << OverlayFile << "': " << Buffer.getError().message()
379 << ".\n";
380 return nullptr;
381 }
382
383 IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML(
384 std::move(Buffer.get()), /*DiagHandler*/ nullptr, OverlayFile);
385 if (!FS) {
386 llvm::errs() << "Error: invalid virtual filesystem overlay file '"
387 << OverlayFile << "'.\n";
388 return nullptr;
389 }
390 return FS;
391 }
392
closest(StringRef Value,const StringSet<> & Allowed)393 static StringRef closest(StringRef Value, const StringSet<> &Allowed) {
394 unsigned MaxEdit = 5U;
395 StringRef Closest;
396 for (auto Item : Allowed.keys()) {
397 unsigned Cur = Value.edit_distance_insensitive(Item, true, MaxEdit);
398 if (Cur < MaxEdit) {
399 Closest = Item;
400 MaxEdit = Cur;
401 }
402 }
403 return Closest;
404 }
405
406 static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n";
407
verifyChecks(const StringSet<> & AllChecks,StringRef CheckGlob,StringRef Source)408 static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob,
409 StringRef Source) {
410 llvm::StringRef Cur, Rest;
411 bool AnyInvalid = false;
412 for (std::tie(Cur, Rest) = CheckGlob.split(',');
413 !(Cur.empty() && Rest.empty()); std::tie(Cur, Rest) = Rest.split(',')) {
414 Cur = Cur.trim();
415 if (Cur.empty())
416 continue;
417 Cur.consume_front("-");
418 if (Cur.startswith("clang-diagnostic"))
419 continue;
420 if (Cur.contains('*')) {
421 SmallString<128> RegexText("^");
422 StringRef MetaChars("()^$|*+?.[]\\{}");
423 for (char C : Cur) {
424 if (C == '*')
425 RegexText.push_back('.');
426 else if (MetaChars.contains(C))
427 RegexText.push_back('\\');
428 RegexText.push_back(C);
429 }
430 RegexText.push_back('$');
431 llvm::Regex Glob(RegexText);
432 std::string Error;
433 if (!Glob.isValid(Error)) {
434 AnyInvalid = true;
435 llvm::WithColor::error(llvm::errs(), Source)
436 << "building check glob '" << Cur << "' " << Error << "'\n";
437 continue;
438 }
439 if (llvm::none_of(AllChecks.keys(),
440 [&Glob](StringRef S) { return Glob.match(S); })) {
441 AnyInvalid = true;
442 llvm::WithColor::warning(llvm::errs(), Source)
443 << "check glob '" << Cur << "' doesn't match any known check"
444 << VerifyConfigWarningEnd;
445 }
446 } else {
447 if (AllChecks.contains(Cur))
448 continue;
449 AnyInvalid = true;
450 llvm::raw_ostream &Output = llvm::WithColor::warning(llvm::errs(), Source)
451 << "unknown check '" << Cur << '\'';
452 llvm::StringRef Closest = closest(Cur, AllChecks);
453 if (!Closest.empty())
454 Output << "; did you mean '" << Closest << '\'';
455 Output << VerifyConfigWarningEnd;
456 }
457 }
458 return AnyInvalid;
459 }
460
clangTidyMain(int argc,const char ** argv)461 int clangTidyMain(int argc, const char **argv) {
462 llvm::InitLLVM X(argc, argv);
463
464 // Enable help for -load option, if plugins are enabled.
465 if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup("load"))
466 LoadOpt->addCategory(ClangTidyCategory);
467
468 llvm::Expected<CommonOptionsParser> OptionsParser =
469 CommonOptionsParser::create(argc, argv, ClangTidyCategory,
470 cl::ZeroOrMore);
471 if (!OptionsParser) {
472 llvm::WithColor::error() << llvm::toString(OptionsParser.takeError());
473 return 1;
474 }
475
476 llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS(
477 new vfs::OverlayFileSystem(vfs::getRealFileSystem()));
478
479 if (!VfsOverlay.empty()) {
480 IntrusiveRefCntPtr<vfs::FileSystem> VfsFromFile =
481 getVfsFromFile(VfsOverlay, BaseFS);
482 if (!VfsFromFile)
483 return 1;
484 BaseFS->pushOverlay(std::move(VfsFromFile));
485 }
486
487 auto OwningOptionsProvider = createOptionsProvider(BaseFS);
488 auto *OptionsProvider = OwningOptionsProvider.get();
489 if (!OptionsProvider)
490 return 1;
491
492 auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> {
493 if (Input.empty())
494 return {};
495 SmallString<256> AbsolutePath(Input);
496 if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) {
497 llvm::errs() << "Can't make absolute path from " << Input << ": "
498 << EC.message() << "\n";
499 }
500 return AbsolutePath;
501 };
502
503 SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile);
504
505 StringRef FileName("dummy");
506 auto PathList = OptionsParser->getSourcePathList();
507 if (!PathList.empty()) {
508 FileName = PathList.front();
509 }
510
511 SmallString<256> FilePath = MakeAbsolute(std::string(FileName));
512
513 ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
514 std::vector<std::string> EnabledChecks =
515 getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
516
517 if (ExplainConfig) {
518 // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
519 std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource>
520 RawOptions = OptionsProvider->getRawOptions(FilePath);
521 for (const std::string &Check : EnabledChecks) {
522 for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) {
523 if (It->first.Checks && GlobList(*It->first.Checks).contains(Check)) {
524 llvm::outs() << "'" << Check << "' is enabled in the " << It->second
525 << ".\n";
526 break;
527 }
528 }
529 }
530 return 0;
531 }
532
533 if (ListChecks) {
534 if (EnabledChecks.empty()) {
535 llvm::errs() << "No checks enabled.\n";
536 return 1;
537 }
538 llvm::outs() << "Enabled checks:";
539 for (const auto &CheckName : EnabledChecks)
540 llvm::outs() << "\n " << CheckName;
541 llvm::outs() << "\n\n";
542 return 0;
543 }
544
545 if (DumpConfig) {
546 EffectiveOptions.CheckOptions =
547 getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
548 llvm::outs() << configurationAsText(ClangTidyOptions::getDefaults().merge(
549 EffectiveOptions, 0))
550 << "\n";
551 return 0;
552 }
553
554 if (VerifyConfig) {
555 std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
556 OptionsProvider->getRawOptions(FileName);
557 NamesAndOptions Valid =
558 getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers);
559 bool AnyInvalid = false;
560 for (const std::pair<ClangTidyOptions, std::string> &OptionWithSource :
561 RawOptions) {
562 const ClangTidyOptions &Opts = OptionWithSource.first;
563 if (Opts.Checks)
564 AnyInvalid |=
565 verifyChecks(Valid.Names, *Opts.Checks, OptionWithSource.second);
566
567 for (auto Key : Opts.CheckOptions.keys()) {
568 if (Valid.Options.contains(Key))
569 continue;
570 AnyInvalid = true;
571 auto &Output =
572 llvm::WithColor::warning(llvm::errs(), OptionWithSource.second)
573 << "unknown check option '" << Key << '\'';
574 llvm::StringRef Closest = closest(Key, Valid.Options);
575 if (!Closest.empty())
576 Output << "; did you mean '" << Closest << '\'';
577 Output << VerifyConfigWarningEnd;
578 }
579 }
580 if (AnyInvalid)
581 return 1;
582 llvm::outs() << "No config errors detected.\n";
583 return 0;
584 }
585
586 if (EnabledChecks.empty()) {
587 llvm::errs() << "Error: no checks enabled.\n";
588 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
589 return 1;
590 }
591
592 if (PathList.empty()) {
593 llvm::errs() << "Error: no input files specified.\n";
594 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
595 return 1;
596 }
597
598 llvm::InitializeAllTargetInfos();
599 llvm::InitializeAllTargetMCs();
600 llvm::InitializeAllAsmParsers();
601
602 ClangTidyContext Context(std::move(OwningOptionsProvider),
603 AllowEnablingAnalyzerAlphaCheckers);
604 std::vector<ClangTidyError> Errors =
605 runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
606 FixNotes, EnableCheckProfile, ProfilePrefix);
607 bool FoundErrors = llvm::any_of(Errors, [](const ClangTidyError &E) {
608 return E.DiagLevel == ClangTidyError::Error;
609 });
610
611 // --fix-errors and --fix-notes imply --fix.
612 FixBehaviour Behaviour = FixNotes ? FB_FixNotes
613 : (Fix || FixErrors) ? FB_Fix
614 : FB_NoFix;
615
616 const bool DisableFixes = FoundErrors && !FixErrors;
617
618 unsigned WErrorCount = 0;
619
620 handleErrors(Errors, Context, DisableFixes ? FB_NoFix : Behaviour,
621 WErrorCount, BaseFS);
622
623 if (!ExportFixes.empty() && !Errors.empty()) {
624 std::error_code EC;
625 llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None);
626 if (EC) {
627 llvm::errs() << "Error opening output file: " << EC.message() << '\n';
628 return 1;
629 }
630 exportReplacements(FilePath.str(), Errors, OS);
631 }
632
633 if (!Quiet) {
634 printStats(Context.getStats());
635 if (DisableFixes && Behaviour != FB_NoFix)
636 llvm::errs()
637 << "Found compiler errors, but -fix-errors was not specified.\n"
638 "Fixes have NOT been applied.\n\n";
639 }
640
641 if (WErrorCount) {
642 if (!Quiet) {
643 StringRef Plural = WErrorCount == 1 ? "" : "s";
644 llvm::errs() << WErrorCount << " warning" << Plural << " treated as error"
645 << Plural << "\n";
646 }
647 return 1;
648 }
649
650 if (FoundErrors) {
651 // TODO: Figure out when zero exit code should be used with -fix-errors:
652 // a. when a fix has been applied for an error
653 // b. when a fix has been applied for all errors
654 // c. some other condition.
655 // For now always returning zero when -fix-errors is used.
656 if (FixErrors)
657 return 0;
658 if (!Quiet)
659 llvm::errs() << "Found compiler error(s).\n";
660 return 1;
661 }
662
663 return 0;
664 }
665
666 } // namespace tidy
667 } // namespace clang
668