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