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 | |
31 | using namespace clang::tooling; |
32 | using namespace llvm; |
33 | |
34 | static cl::desc desc(StringRef description) { return {description.ltrim()}; } |
35 | |
36 | static cl::OptionCategory ClangTidyCategory("clang-tidy options" ); |
37 | |
38 | static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); |
39 | static cl::extrahelp ClangTidyHelp(R"( |
40 | Configuration 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 | |
96 | const char DefaultChecks[] = // Enable these checks by default: |
97 | "clang-diagnostic-*," // * compiler diagnostics |
98 | "clang-analyzer-*" ; // * Static Analyzer checks |
99 | |
100 | static cl::opt<std::string> Checks("checks" , desc(description: R"( |
101 | Comma-separated list of globs with optional '-' |
102 | prefix. Globs are processed in order of |
103 | appearance in the list. Globs without '-' |
104 | prefix add checks with matching names to the |
105 | set, globs with the '-' prefix remove checks |
106 | with matching names from the set of enabled |
107 | checks. This option's value is appended to the |
108 | value of the 'Checks' option in .clang-tidy |
109 | file, if any. |
110 | )" ), |
111 | cl::init(Val: "" ), cl::cat(ClangTidyCategory)); |
112 | |
113 | static cl::opt<std::string> WarningsAsErrors("warnings-as-errors" , desc(description: R"( |
114 | Upgrades warnings to errors. Same format as |
115 | '-checks'. |
116 | This option's value is appended to the value of |
117 | the 'WarningsAsErrors' option in .clang-tidy |
118 | file, if any. |
119 | )" ), |
120 | cl::init(Val: "" ), |
121 | cl::cat(ClangTidyCategory)); |
122 | |
123 | static cl::opt<std::string> ("header-filter" , desc(description: R"( |
124 | Regular expression matching the names of the |
125 | headers to output diagnostics from. Diagnostics |
126 | from the main file of each translation unit are |
127 | always displayed. |
128 | Can be used together with -line-filter. |
129 | This option overrides the 'HeaderFilterRegex' |
130 | option in .clang-tidy file, if any. |
131 | )" ), |
132 | cl::init(Val: "" ), |
133 | cl::cat(ClangTidyCategory)); |
134 | |
135 | static cl::opt<bool> ("system-headers" , desc(description: R"( |
136 | Display the errors from system headers. |
137 | This option overrides the 'SystemHeaders' option |
138 | in .clang-tidy file, if any. |
139 | )" ), |
140 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
141 | |
142 | static cl::opt<std::string> LineFilter("line-filter" , desc(description: R"( |
143 | List of files with line ranges to filter the |
144 | warnings. Can be used together with |
145 | -header-filter. The format of the list is a |
146 | JSON 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 | |
155 | static cl::opt<bool> Fix("fix" , desc(description: R"( |
156 | Apply suggested fixes. Without -fix-errors |
157 | clang-tidy will bail out if any compilation |
158 | errors were found. |
159 | )" ), |
160 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
161 | |
162 | static cl::opt<bool> FixErrors("fix-errors" , desc(description: R"( |
163 | Apply suggested fixes even if compilation |
164 | errors were found. If compiler errors have |
165 | attached fix-its, clang-tidy will apply them as |
166 | well. |
167 | )" ), |
168 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
169 | |
170 | static cl::opt<bool> FixNotes("fix-notes" , desc(description: R"( |
171 | If a warning has no fix, but a single fix can |
172 | be found through an associated diagnostic note, |
173 | apply the fix. |
174 | Specifying this flag will implicitly enable the |
175 | '--fix' flag. |
176 | )" ), |
177 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
178 | |
179 | static cl::opt<std::string> FormatStyle("format-style" , desc(description: R"( |
180 | Style 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' |
188 | See clang-format documentation for the up-to-date |
189 | information about formatting styles and options. |
190 | This option overrides the 'FormatStyle` option in |
191 | .clang-tidy file, if any. |
192 | )" ), |
193 | cl::init(Val: "none" ), |
194 | cl::cat(ClangTidyCategory)); |
195 | |
196 | static cl::opt<bool> ListChecks("list-checks" , desc(description: R"( |
197 | List all enabled checks and exit. Use with |
198 | -checks=* to list all available checks. |
199 | )" ), |
200 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
201 | |
202 | static cl::opt<bool> ExplainConfig("explain-config" , desc(description: R"( |
203 | For each enabled check explains, where it is |
204 | enabled, i.e. in clang-tidy binary, command |
205 | line or a specific configuration file. |
206 | )" ), |
207 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
208 | |
209 | static cl::opt<std::string> Config("config" , desc(description: R"( |
210 | Specifies a configuration in YAML/JSON format: |
211 | -config="{Checks: '*', |
212 | CheckOptions: {x: y}}" |
213 | When the value is empty, clang-tidy will |
214 | attempt to find a file named .clang-tidy for |
215 | each source file in its parent directories. |
216 | )" ), |
217 | cl::init(Val: "" ), cl::cat(ClangTidyCategory)); |
218 | |
219 | static cl::opt<std::string> ConfigFile("config-file" , desc(description: R"( |
220 | Specify the path of .clang-tidy or custom config file: |
221 | e.g. --config-file=/some/path/myTidyConfigFile |
222 | This option internally works exactly the same way as |
223 | --config option after reading specified config file. |
224 | Use either --config-file or --config, not both. |
225 | )" ), |
226 | cl::init(Val: "" ), |
227 | cl::cat(ClangTidyCategory)); |
228 | |
229 | static cl::opt<bool> DumpConfig("dump-config" , desc(description: R"( |
230 | Dumps configuration in the YAML format to |
231 | stdout. This option can be used along with a |
232 | file name (and '--' if the file is outside of a |
233 | project with configured compilation database). |
234 | The configuration used for this file will be |
235 | printed. |
236 | Use along with -checks=* to include |
237 | configuration of all checks. |
238 | )" ), |
239 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
240 | |
241 | static cl::opt<bool> EnableCheckProfile("enable-check-profile" , desc(description: R"( |
242 | Enable per-check timing profiles, and print a |
243 | report to stderr. |
244 | )" ), |
245 | cl::init(Val: false), |
246 | cl::cat(ClangTidyCategory)); |
247 | |
248 | static cl::opt<std::string> StoreCheckProfile("store-check-profile" , desc(description: R"( |
249 | By default reports are printed in tabulated |
250 | format to stderr. When this option is passed, |
251 | these 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. |
259 | static cl::opt<bool> |
260 | AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers" , |
261 | cl::init(Val: false), cl::Hidden, |
262 | cl::cat(ClangTidyCategory)); |
263 | |
264 | static cl::opt<bool> ("enable-module-headers-parsing" , |
265 | desc(description: R"( |
266 | Enables preprocessor-level module header parsing |
267 | for C++20 and above, empowering specific checks |
268 | to detect macro definitions within modules. This |
269 | feature may cause performance and parsing issues |
270 | and is therefore considered experimental. |
271 | )" ), |
272 | cl::init(Val: false), |
273 | cl::cat(ClangTidyCategory)); |
274 | |
275 | static cl::opt<std::string> ExportFixes("export-fixes" , desc(description: R"( |
276 | YAML file to store suggested fixes in. The |
277 | stored fixes can be applied to the input source |
278 | code with clang-apply-replacements. |
279 | )" ), |
280 | cl::value_desc("filename" ), |
281 | cl::cat(ClangTidyCategory)); |
282 | |
283 | static cl::opt<bool> Quiet("quiet" , desc(description: R"( |
284 | Run clang-tidy in quiet mode. This suppresses |
285 | printing statistics about ignored warnings and |
286 | warnings treated as errors if the respective |
287 | options are specified. |
288 | )" ), |
289 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
290 | |
291 | static cl::opt<std::string> VfsOverlay("vfsoverlay" , desc(description: R"( |
292 | Overlay the virtual filesystem described by file |
293 | over the real file system. |
294 | )" ), |
295 | cl::value_desc("filename" ), |
296 | cl::cat(ClangTidyCategory)); |
297 | |
298 | static cl::opt<bool> UseColor("use-color" , desc(description: R"( |
299 | Use colors in diagnostics. If not set, colors |
300 | will be used if the terminal connected to |
301 | standard output supports colors. |
302 | This option overrides the 'UseColor' option in |
303 | .clang-tidy file, if any. |
304 | )" ), |
305 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
306 | |
307 | static cl::opt<bool> VerifyConfig("verify-config" , desc(description: R"( |
308 | Check the config files to ensure each check and |
309 | option is recognized. |
310 | )" ), |
311 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
312 | |
313 | namespace clang::tidy { |
314 | |
315 | static 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 | |
343 | static 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 | |
418 | llvm::IntrusiveRefCntPtr<vfs::FileSystem> |
419 | getVfsFromFile(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 | |
440 | static 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 | |
453 | static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n" ; |
454 | |
455 | static 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 | |
483 | static bool verifyFileExtensions( |
484 | const std::vector<std::string> &, |
485 | const std::vector<std::string> &ImplementationFileExtensions, |
486 | StringRef Source) { |
487 | bool AnyInvalid = false; |
488 | for (const auto & : 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 | |
503 | static 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 | |
514 | static 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 | |
528 | int 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 | |