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

source code of clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp