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 GlobList Globs(CheckGlob);
458 bool AnyInvalid = false;
459 for (const auto &Item : Globs.getItems()) {
460 if (Item.Text.starts_with(Prefix: "clang-diagnostic"))
461 continue;
462 if (llvm::none_of(Range: AllChecks.keys(),
463 P: [&Item](StringRef S) { return Item.Regex.match(String: S); })) {
464 AnyInvalid = true;
465 if (Item.Text.contains(C: '*'))
466 llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source)
467 << "check glob '" << Item.Text << "' doesn't match any known check"
468 << VerifyConfigWarningEnd;
469 else {
470 llvm::raw_ostream &Output =
471 llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source)
472 << "unknown check '" << Item.Text << '\'';
473 llvm::StringRef Closest = closest(Value: Item.Text, Allowed: AllChecks);
474 if (!Closest.empty())
475 Output << "; did you mean '" << Closest << '\'';
476 Output << VerifyConfigWarningEnd;
477 }
478 }
479 }
480 return AnyInvalid;
481}
482
483static bool verifyFileExtensions(
484 const std::vector<std::string> &HeaderFileExtensions,
485 const std::vector<std::string> &ImplementationFileExtensions,
486 StringRef Source) {
487 bool AnyInvalid = false;
488 for (const auto &HeaderExtension : HeaderFileExtensions) {
489 for (const auto &ImplementationExtension : ImplementationFileExtensions) {
490 if (HeaderExtension == ImplementationExtension) {
491 AnyInvalid = true;
492 auto &Output = llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source)
493 << "HeaderFileExtension '" << HeaderExtension << '\''
494 << " is the same as ImplementationFileExtension '"
495 << ImplementationExtension << '\'';
496 Output << VerifyConfigWarningEnd;
497 }
498 }
499 }
500 return AnyInvalid;
501}
502
503static SmallString<256> makeAbsolute(llvm::StringRef Input) {
504 if (Input.empty())
505 return {};
506 SmallString<256> AbsolutePath(Input);
507 if (std::error_code EC = llvm::sys::fs::make_absolute(path&: AbsolutePath)) {
508 llvm::errs() << "Can't make absolute path from " << Input << ": "
509 << EC.message() << "\n";
510 }
511 return AbsolutePath;
512}
513
514static llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> createBaseFS() {
515 llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS(
516 new vfs::OverlayFileSystem(vfs::getRealFileSystem()));
517
518 if (!VfsOverlay.empty()) {
519 IntrusiveRefCntPtr<vfs::FileSystem> VfsFromFile =
520 getVfsFromFile(OverlayFile: VfsOverlay, BaseFS);
521 if (!VfsFromFile)
522 return nullptr;
523 BaseFS->pushOverlay(FS: std::move(VfsFromFile));
524 }
525 return BaseFS;
526}
527
528int clangTidyMain(int argc, const char **argv) {
529 llvm::InitLLVM X(argc, argv);
530
531 // Enable help for -load option, if plugins are enabled.
532 if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup(Key: "load"))
533 LoadOpt->addCategory(C&: ClangTidyCategory);
534
535 llvm::Expected<CommonOptionsParser> OptionsParser =
536 CommonOptionsParser::create(argc, argv, Category&: ClangTidyCategory,
537 OccurrencesFlag: cl::ZeroOrMore);
538 if (!OptionsParser) {
539 llvm::WithColor::error() << llvm::toString(E: OptionsParser.takeError());
540 return 1;
541 }
542
543 llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS = createBaseFS();
544 if (!BaseFS)
545 return 1;
546
547 auto OwningOptionsProvider = createOptionsProvider(FS: BaseFS);
548 auto *OptionsProvider = OwningOptionsProvider.get();
549 if (!OptionsProvider)
550 return 1;
551
552 SmallString<256> ProfilePrefix = makeAbsolute(Input: StoreCheckProfile);
553
554 StringRef FileName("dummy");
555 auto PathList = OptionsParser->getSourcePathList();
556 if (!PathList.empty()) {
557 FileName = PathList.front();
558 }
559
560 SmallString<256> FilePath = makeAbsolute(Input: FileName);
561 ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FileName: FilePath);
562
563 std::vector<std::string> EnabledChecks =
564 getCheckNames(Options: EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
565
566 if (ExplainConfig) {
567 // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
568 std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource>
569 RawOptions = OptionsProvider->getRawOptions(FileName: FilePath);
570 for (const std::string &Check : EnabledChecks) {
571 for (const auto &[Opts, Source] : llvm::reverse(C&: RawOptions)) {
572 if (Opts.Checks && GlobList(*Opts.Checks).contains(S: Check)) {
573 llvm::outs() << "'" << Check << "' is enabled in the " << Source
574 << ".\n";
575 break;
576 }
577 }
578 }
579 return 0;
580 }
581
582 if (ListChecks) {
583 if (EnabledChecks.empty()) {
584 llvm::errs() << "No checks enabled.\n";
585 return 1;
586 }
587 llvm::outs() << "Enabled checks:";
588 for (const auto &CheckName : EnabledChecks)
589 llvm::outs() << "\n " << CheckName;
590 llvm::outs() << "\n\n";
591 return 0;
592 }
593
594 if (DumpConfig) {
595 EffectiveOptions.CheckOptions =
596 getCheckOptions(Options: EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
597 llvm::outs() << configurationAsText(Options: ClangTidyOptions::getDefaults().merge(
598 Other: EffectiveOptions, Order: 0))
599 << "\n";
600 return 0;
601 }
602
603 if (VerifyConfig) {
604 std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
605 OptionsProvider->getRawOptions(FileName);
606 NamesAndOptions Valid =
607 getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers);
608 bool AnyInvalid = false;
609 for (const auto &[Opts, Source] : RawOptions) {
610 if (Opts.Checks)
611 AnyInvalid |= verifyChecks(AllChecks: Valid.Names, CheckGlob: *Opts.Checks, Source);
612
613 if (Opts.HeaderFileExtensions && Opts.ImplementationFileExtensions)
614 AnyInvalid |=
615 verifyFileExtensions(HeaderFileExtensions: *Opts.HeaderFileExtensions,
616 ImplementationFileExtensions: *Opts.ImplementationFileExtensions, Source);
617
618 for (auto Key : Opts.CheckOptions.keys()) {
619 if (Valid.Options.contains(key: Key))
620 continue;
621 AnyInvalid = true;
622 auto &Output = llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source)
623 << "unknown check option '" << Key << '\'';
624 llvm::StringRef Closest = closest(Value: Key, Allowed: Valid.Options);
625 if (!Closest.empty())
626 Output << "; did you mean '" << Closest << '\'';
627 Output << VerifyConfigWarningEnd;
628 }
629 }
630 if (AnyInvalid)
631 return 1;
632 llvm::outs() << "No config errors detected.\n";
633 return 0;
634 }
635
636 if (EnabledChecks.empty()) {
637 llvm::errs() << "Error: no checks enabled.\n";
638 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
639 return 1;
640 }
641
642 if (PathList.empty()) {
643 llvm::errs() << "Error: no input files specified.\n";
644 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
645 return 1;
646 }
647
648 llvm::InitializeAllTargetInfos();
649 llvm::InitializeAllTargetMCs();
650 llvm::InitializeAllAsmParsers();
651
652 ClangTidyContext Context(std::move(OwningOptionsProvider),
653 AllowEnablingAnalyzerAlphaCheckers,
654 EnableModuleHeadersParsing);
655 std::vector<ClangTidyError> Errors =
656 runClangTidy(Context, Compilations: OptionsParser->getCompilations(), InputFiles: PathList, BaseFS,
657 ApplyAnyFix: FixNotes, EnableCheckProfile, StoreCheckProfile: ProfilePrefix);
658 bool FoundErrors = llvm::any_of(Range&: Errors, P: [](const ClangTidyError &E) {
659 return E.DiagLevel == ClangTidyError::Error;
660 });
661
662 // --fix-errors and --fix-notes imply --fix.
663 FixBehaviour Behaviour = FixNotes ? FB_FixNotes
664 : (Fix || FixErrors) ? FB_Fix
665 : FB_NoFix;
666
667 const bool DisableFixes = FoundErrors && !FixErrors;
668
669 unsigned WErrorCount = 0;
670
671 handleErrors(Errors, Context, Fix: DisableFixes ? FB_NoFix : Behaviour,
672 WarningsAsErrorsCount&: WErrorCount, BaseFS);
673
674 if (!ExportFixes.empty() && !Errors.empty()) {
675 std::error_code EC;
676 llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None);
677 if (EC) {
678 llvm::errs() << "Error opening output file: " << EC.message() << '\n';
679 return 1;
680 }
681 exportReplacements(MainFilePath: FilePath.str(), Errors, OS);
682 }
683
684 if (!Quiet) {
685 printStats(Stats: Context.getStats());
686 if (DisableFixes && Behaviour != FB_NoFix)
687 llvm::errs()
688 << "Found compiler errors, but -fix-errors was not specified.\n"
689 "Fixes have NOT been applied.\n\n";
690 }
691
692 if (WErrorCount) {
693 if (!Quiet) {
694 StringRef Plural = WErrorCount == 1 ? "" : "s";
695 llvm::errs() << WErrorCount << " warning" << Plural << " treated as error"
696 << Plural << "\n";
697 }
698 return 1;
699 }
700
701 if (FoundErrors) {
702 // TODO: Figure out when zero exit code should be used with -fix-errors:
703 // a. when a fix has been applied for an error
704 // b. when a fix has been applied for all errors
705 // c. some other condition.
706 // For now always returning zero when -fix-errors is used.
707 if (FixErrors)
708 return 0;
709 if (!Quiet)
710 llvm::errs() << "Found compiler error(s).\n";
711 return 1;
712 }
713
714 return 0;
715}
716
717} // namespace clang::tidy
718

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