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