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(Description: 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(Val: ""), cl::cat(ClangTidyCategory)); |
120 | |
121 | static cl::opt<std::string> WarningsAsErrors("warnings-as-errors", desc(Description: 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(Val: ""), |
129 | cl::cat(ClangTidyCategory)); |
130 | |
131 | static cl::opt<std::string> HeaderFilter("header-filter", desc(Description: 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(Val: ""), |
141 | cl::cat(ClangTidyCategory)); |
142 | |
143 | static cl::opt<std::string> ExcludeHeaderFilter("exclude-header-filter", |
144 | desc(Description: 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(Val: ""), |
155 | cl::cat(ClangTidyCategory)); |
156 | |
157 | static cl::opt<bool> SystemHeaders("system-headers", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
163 | |
164 | static cl::opt<std::string> LineFilter("line-filter", desc(Description: 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(Val: ""), |
175 | cl::cat(ClangTidyCategory)); |
176 | |
177 | static cl::opt<bool> Fix("fix", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
183 | |
184 | static cl::opt<bool> FixErrors("fix-errors", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
191 | |
192 | static cl::opt<bool> FixNotes("fix-notes", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
200 | |
201 | static cl::opt<std::string> FormatStyle("format-style", desc(Description: 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(Val: "none"), |
216 | cl::cat(ClangTidyCategory)); |
217 | |
218 | static cl::opt<bool> ListChecks("list-checks", desc(Description: R"( |
219 | List all enabled checks and exit. Use with |
220 | -checks=* to list all available checks. |
221 | )"), |
222 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
223 | |
224 | static cl::opt<bool> ExplainConfig("explain-config", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
230 | |
231 | static cl::opt<std::string> Config("config", desc(Description: 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(Val: ""), cl::cat(ClangTidyCategory)); |
240 | |
241 | static cl::opt<std::string> ConfigFile("config-file", desc(Description: 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(Val: ""), |
249 | cl::cat(ClangTidyCategory)); |
250 | |
251 | static cl::opt<bool> DumpConfig("dump-config", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
262 | |
263 | static cl::opt<bool> EnableCheckProfile("enable-check-profile", desc(Description: R"( |
264 | Enable per-check timing profiles, and print a |
265 | report to stderr. |
266 | )"), |
267 | cl::init(Val: false), |
268 | cl::cat(ClangTidyCategory)); |
269 | |
270 | static cl::opt<std::string> StoreCheckProfile("store-check-profile", desc(Description: 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(Val: false), cl::Hidden, |
284 | cl::cat(ClangTidyCategory)); |
285 | |
286 | static cl::opt<bool> EnableModuleHeadersParsing("enable-module-headers-parsing", |
287 | desc(Description: 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(Val: false), |
295 | cl::cat(ClangTidyCategory)); |
296 | |
297 | static cl::opt<std::string> ExportFixes("export-fixes", desc(Description: 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(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
312 | |
313 | static cl::opt<std::string> VfsOverlay("vfsoverlay", desc(Description: 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(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
328 | |
329 | static cl::opt<bool> VerifyConfig("verify-config", desc(Description: R"( |
330 | Check the config files to ensure each check and |
331 | option is recognized. |
332 | )"), |
333 | cl::init(Val: false), cl::cat(ClangTidyCategory)); |
334 | |
335 | static cl::opt<bool> AllowNoChecks("allow-no-checks", desc(Description: 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(Val: false), cl::cat(ClangTidyCategory)); |
341 | |
342 | namespace clang::tidy { |
343 | |
344 | static void printStats(const ClangTidyStats &Stats) { |
345 | if (Stats.errorsIgnored()) { |
346 | llvm::errs() << "Suppressed "<< Stats.errorsIgnored() << " warnings ("; |
347 | StringRef Separator = ""; |
348 | if (Stats.ErrorsIgnoredNonUserCode) { |
349 | llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code"; |
350 | Separator = ", "; |
351 | } |
352 | if (Stats.ErrorsIgnoredLineFilter) { |
353 | llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter |
354 | << " due to line filter"; |
355 | Separator = ", "; |
356 | } |
357 | if (Stats.ErrorsIgnoredNOLINT) { |
358 | llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT"; |
359 | Separator = ", "; |
360 | } |
361 | if (Stats.ErrorsIgnoredCheckFilter) |
362 | llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter |
363 | << " with check filters"; |
364 | llvm::errs() << ").\n"; |
365 | if (Stats.ErrorsIgnoredNonUserCode) |
366 | llvm::errs() << "Use -header-filter=.* to display errors from all " |
367 | "non-system headers. Use -system-headers to display " |
368 | "errors from system headers as well.\n"; |
369 | } |
370 | } |
371 | |
372 | static std::unique_ptr<ClangTidyOptionsProvider> |
373 | createOptionsProvider(llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) { |
374 | ClangTidyGlobalOptions GlobalOptions; |
375 | if (std::error_code Err = parseLineFilter(LineFilter, Options&: GlobalOptions)) { |
376 | llvm::errs() << "Invalid LineFilter: "<< Err.message() << "\n\nUsage:\n"; |
377 | llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); |
378 | return nullptr; |
379 | } |
380 | |
381 | ClangTidyOptions DefaultOptions; |
382 | DefaultOptions.Checks = DefaultChecks; |
383 | DefaultOptions.WarningsAsErrors = ""; |
384 | DefaultOptions.HeaderFilterRegex = HeaderFilter; |
385 | DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; |
386 | DefaultOptions.SystemHeaders = SystemHeaders; |
387 | DefaultOptions.FormatStyle = FormatStyle; |
388 | DefaultOptions.User = llvm::sys::Process::GetEnv(name: "USER"); |
389 | // USERNAME is used on Windows. |
390 | if (!DefaultOptions.User) |
391 | DefaultOptions.User = llvm::sys::Process::GetEnv(name: "USERNAME"); |
392 | |
393 | ClangTidyOptions OverrideOptions; |
394 | if (Checks.getNumOccurrences() > 0) |
395 | OverrideOptions.Checks = Checks; |
396 | if (WarningsAsErrors.getNumOccurrences() > 0) |
397 | OverrideOptions.WarningsAsErrors = WarningsAsErrors; |
398 | if (HeaderFilter.getNumOccurrences() > 0) |
399 | OverrideOptions.HeaderFilterRegex = HeaderFilter; |
400 | if (ExcludeHeaderFilter.getNumOccurrences() > 0) |
401 | OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter; |
402 | if (SystemHeaders.getNumOccurrences() > 0) |
403 | OverrideOptions.SystemHeaders = SystemHeaders; |
404 | if (FormatStyle.getNumOccurrences() > 0) |
405 | OverrideOptions.FormatStyle = FormatStyle; |
406 | if (UseColor.getNumOccurrences() > 0) |
407 | OverrideOptions.UseColor = UseColor; |
408 | |
409 | auto LoadConfig = |
410 | [&](StringRef Configuration, |
411 | StringRef Source) -> std::unique_ptr<ClangTidyOptionsProvider> { |
412 | llvm::ErrorOr<ClangTidyOptions> ParsedConfig = |
413 | parseConfiguration(Config: MemoryBufferRef(Configuration, Source)); |
414 | if (ParsedConfig) |
415 | return std::make_unique<ConfigOptionsProvider>( |
416 | args: std::move(GlobalOptions), |
417 | args: ClangTidyOptions::getDefaults().merge(Other: DefaultOptions, Order: 0), |
418 | args: std::move(*ParsedConfig), args: std::move(OverrideOptions), args: std::move(FS)); |
419 | llvm::errs() << "Error: invalid configuration specified.\n" |
420 | << ParsedConfig.getError().message() << "\n"; |
421 | return nullptr; |
422 | }; |
423 | |
424 | if (ConfigFile.getNumOccurrences() > 0) { |
425 | if (Config.getNumOccurrences() > 0) { |
426 | llvm::errs() << "Error: --config-file and --config are " |
427 | "mutually exclusive. Specify only one.\n"; |
428 | return nullptr; |
429 | } |
430 | |
431 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text = |
432 | llvm::MemoryBuffer::getFile(Filename: ConfigFile); |
433 | if (std::error_code EC = Text.getError()) { |
434 | llvm::errs() << "Error: can't read config-file '"<< ConfigFile |
435 | << "': "<< EC.message() << "\n"; |
436 | return nullptr; |
437 | } |
438 | |
439 | return LoadConfig((*Text)->getBuffer(), ConfigFile); |
440 | } |
441 | |
442 | if (Config.getNumOccurrences() > 0) |
443 | return LoadConfig(Config, "<command-line-config>"); |
444 | |
445 | return std::make_unique<FileOptionsProvider>( |
446 | args: std::move(GlobalOptions), args: std::move(DefaultOptions), |
447 | args: std::move(OverrideOptions), args: std::move(FS)); |
448 | } |
449 | |
450 | static llvm::IntrusiveRefCntPtr<vfs::FileSystem> |
451 | getVfsFromFile(const std::string &OverlayFile, |
452 | llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS) { |
453 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer = |
454 | BaseFS->getBufferForFile(Name: OverlayFile); |
455 | if (!Buffer) { |
456 | llvm::errs() << "Can't load virtual filesystem overlay file '" |
457 | << OverlayFile << "': "<< Buffer.getError().message() |
458 | << ".\n"; |
459 | return nullptr; |
460 | } |
461 | |
462 | IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML( |
463 | Buffer: std::move(Buffer.get()), /*DiagHandler*/ nullptr, YAMLFilePath: OverlayFile); |
464 | if (!FS) { |
465 | llvm::errs() << "Error: invalid virtual filesystem overlay file '" |
466 | << OverlayFile << "'.\n"; |
467 | return nullptr; |
468 | } |
469 | return FS; |
470 | } |
471 | |
472 | static StringRef closest(StringRef Value, const StringSet<> &Allowed) { |
473 | unsigned MaxEdit = 5U; |
474 | StringRef Closest; |
475 | for (auto Item : Allowed.keys()) { |
476 | unsigned Cur = Value.edit_distance_insensitive(Other: Item, AllowReplacements: true, MaxEditDistance: MaxEdit); |
477 | if (Cur < MaxEdit) { |
478 | Closest = Item; |
479 | MaxEdit = Cur; |
480 | } |
481 | } |
482 | return Closest; |
483 | } |
484 | |
485 | static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n"; |
486 | |
487 | static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob, |
488 | StringRef Source) { |
489 | GlobList Globs(CheckGlob); |
490 | bool AnyInvalid = false; |
491 | for (const auto &Item : Globs.getItems()) { |
492 | if (Item.Text.starts_with(Prefix: "clang-diagnostic")) |
493 | continue; |
494 | if (llvm::none_of(Range: AllChecks.keys(), |
495 | P: [&Item](StringRef S) { return Item.Regex.match(String: S); })) { |
496 | AnyInvalid = true; |
497 | if (Item.Text.contains(C: '*')) |
498 | llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source) |
499 | << "check glob '"<< Item.Text << "' doesn't match any known check" |
500 | << VerifyConfigWarningEnd; |
501 | else { |
502 | llvm::raw_ostream &Output = |
503 | llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source) |
504 | << "unknown check '"<< Item.Text << '\''; |
505 | llvm::StringRef Closest = closest(Value: Item.Text, Allowed: AllChecks); |
506 | if (!Closest.empty()) |
507 | Output << "; did you mean '"<< Closest << '\''; |
508 | Output << VerifyConfigWarningEnd; |
509 | } |
510 | } |
511 | } |
512 | return AnyInvalid; |
513 | } |
514 | |
515 | static bool verifyFileExtensions( |
516 | const std::vector<std::string> &HeaderFileExtensions, |
517 | const std::vector<std::string> &ImplementationFileExtensions, |
518 | StringRef Source) { |
519 | bool AnyInvalid = false; |
520 | for (const auto &HeaderExtension : HeaderFileExtensions) { |
521 | for (const auto &ImplementationExtension : ImplementationFileExtensions) { |
522 | if (HeaderExtension == ImplementationExtension) { |
523 | AnyInvalid = true; |
524 | auto &Output = llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source) |
525 | << "HeaderFileExtension '"<< HeaderExtension << '\'' |
526 | << " is the same as ImplementationFileExtension '" |
527 | << ImplementationExtension << '\''; |
528 | Output << VerifyConfigWarningEnd; |
529 | } |
530 | } |
531 | } |
532 | return AnyInvalid; |
533 | } |
534 | |
535 | static bool verifyOptions(const llvm::StringSet<> &ValidOptions, |
536 | const ClangTidyOptions::OptionMap &OptionMap, |
537 | StringRef Source) { |
538 | bool AnyInvalid = false; |
539 | for (auto Key : OptionMap.keys()) { |
540 | if (ValidOptions.contains(key: Key)) |
541 | continue; |
542 | AnyInvalid = true; |
543 | auto &Output = llvm::WithColor::warning(OS&: llvm::errs(), Prefix: Source) |
544 | << "unknown check option '"<< Key << '\''; |
545 | llvm::StringRef Closest = closest(Value: Key, Allowed: ValidOptions); |
546 | if (!Closest.empty()) |
547 | Output << "; did you mean '"<< Closest << '\''; |
548 | Output << VerifyConfigWarningEnd; |
549 | } |
550 | return AnyInvalid; |
551 | } |
552 | |
553 | static SmallString<256> makeAbsolute(llvm::StringRef Input) { |
554 | if (Input.empty()) |
555 | return {}; |
556 | SmallString<256> AbsolutePath(Input); |
557 | if (std::error_code EC = llvm::sys::fs::make_absolute(path&: AbsolutePath)) { |
558 | llvm::errs() << "Can't make absolute path from "<< Input << ": " |
559 | << EC.message() << "\n"; |
560 | } |
561 | return AbsolutePath; |
562 | } |
563 | |
564 | static llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> createBaseFS() { |
565 | llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS( |
566 | new vfs::OverlayFileSystem(vfs::getRealFileSystem())); |
567 | |
568 | if (!VfsOverlay.empty()) { |
569 | IntrusiveRefCntPtr<vfs::FileSystem> VfsFromFile = |
570 | getVfsFromFile(OverlayFile: VfsOverlay, BaseFS); |
571 | if (!VfsFromFile) |
572 | return nullptr; |
573 | BaseFS->pushOverlay(FS: std::move(VfsFromFile)); |
574 | } |
575 | return BaseFS; |
576 | } |
577 | |
578 | int clangTidyMain(int argc, const char **argv) { |
579 | llvm::InitLLVM X(argc, argv); |
580 | SmallVector<const char *> Args{argv, argv + argc}; |
581 | |
582 | // expand parameters file to argc and argv. |
583 | llvm::BumpPtrAllocator Alloc; |
584 | llvm::cl::TokenizerCallback Tokenizer = |
585 | llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows() |
586 | ? llvm::cl::TokenizeWindowsCommandLine |
587 | : llvm::cl::TokenizeGNUCommandLine; |
588 | llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer); |
589 | if (llvm::Error Err = ECtx.expandResponseFiles(Argv&: Args)) { |
590 | llvm::WithColor::error() << llvm::toString(E: std::move(Err)) << "\n"; |
591 | return 1; |
592 | } |
593 | argc = static_cast<int>(Args.size()); |
594 | argv = Args.data(); |
595 | |
596 | // Enable help for -load option, if plugins are enabled. |
597 | if (cl::Option *LoadOpt = cl::getRegisteredOptions().lookup(Key: "load")) |
598 | LoadOpt->addCategory(C&: ClangTidyCategory); |
599 | |
600 | llvm::Expected<CommonOptionsParser> OptionsParser = |
601 | CommonOptionsParser::create(argc, argv, Category&: ClangTidyCategory, |
602 | OccurrencesFlag: cl::ZeroOrMore); |
603 | if (!OptionsParser) { |
604 | llvm::WithColor::error() << llvm::toString(E: OptionsParser.takeError()); |
605 | return 1; |
606 | } |
607 | |
608 | llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS = createBaseFS(); |
609 | if (!BaseFS) |
610 | return 1; |
611 | |
612 | auto OwningOptionsProvider = createOptionsProvider(FS: BaseFS); |
613 | auto *OptionsProvider = OwningOptionsProvider.get(); |
614 | if (!OptionsProvider) |
615 | return 1; |
616 | |
617 | SmallString<256> ProfilePrefix = makeAbsolute(Input: StoreCheckProfile); |
618 | |
619 | StringRef FileName("dummy"); |
620 | auto PathList = OptionsParser->getSourcePathList(); |
621 | if (!PathList.empty()) { |
622 | FileName = PathList.front(); |
623 | } |
624 | |
625 | SmallString<256> FilePath = makeAbsolute(Input: FileName); |
626 | ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FileName: FilePath); |
627 | |
628 | std::vector<std::string> EnabledChecks = |
629 | getCheckNames(Options: EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); |
630 | |
631 | if (ExplainConfig) { |
632 | // FIXME: Show other ClangTidyOptions' fields, like ExtraArg. |
633 | std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource> |
634 | RawOptions = OptionsProvider->getRawOptions(FileName: FilePath); |
635 | for (const std::string &Check : EnabledChecks) { |
636 | for (const auto &[Opts, Source] : llvm::reverse(C&: RawOptions)) { |
637 | if (Opts.Checks && GlobList(*Opts.Checks).contains(S: Check)) { |
638 | llvm::outs() << "'"<< Check << "' is enabled in the "<< Source |
639 | << ".\n"; |
640 | break; |
641 | } |
642 | } |
643 | } |
644 | return 0; |
645 | } |
646 | |
647 | if (ListChecks) { |
648 | if (EnabledChecks.empty() && !AllowNoChecks) { |
649 | llvm::errs() << "No checks enabled.\n"; |
650 | return 1; |
651 | } |
652 | llvm::outs() << "Enabled checks:"; |
653 | for (const auto &CheckName : EnabledChecks) |
654 | llvm::outs() << "\n "<< CheckName; |
655 | llvm::outs() << "\n\n"; |
656 | return 0; |
657 | } |
658 | |
659 | if (DumpConfig) { |
660 | EffectiveOptions.CheckOptions = |
661 | getCheckOptions(Options: EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); |
662 | llvm::outs() << configurationAsText(Options: ClangTidyOptions::getDefaults().merge( |
663 | Other: EffectiveOptions, Order: 0)) |
664 | << "\n"; |
665 | return 0; |
666 | } |
667 | |
668 | if (VerifyConfig) { |
669 | std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions = |
670 | OptionsProvider->getRawOptions(FileName); |
671 | ChecksAndOptions Valid = |
672 | getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers); |
673 | bool AnyInvalid = false; |
674 | for (const auto &[Opts, Source] : RawOptions) { |
675 | if (Opts.Checks) |
676 | AnyInvalid |= verifyChecks(AllChecks: Valid.Checks, CheckGlob: *Opts.Checks, Source); |
677 | if (Opts.HeaderFileExtensions && Opts.ImplementationFileExtensions) |
678 | AnyInvalid |= |
679 | verifyFileExtensions(HeaderFileExtensions: *Opts.HeaderFileExtensions, |
680 | ImplementationFileExtensions: *Opts.ImplementationFileExtensions, Source); |
681 | AnyInvalid |= verifyOptions(ValidOptions: Valid.Options, OptionMap: Opts.CheckOptions, Source); |
682 | } |
683 | if (AnyInvalid) |
684 | return 1; |
685 | llvm::outs() << "No config errors detected.\n"; |
686 | return 0; |
687 | } |
688 | |
689 | if (EnabledChecks.empty()) { |
690 | if (AllowNoChecks) { |
691 | llvm::outs() << "No checks enabled.\n"; |
692 | return 0; |
693 | } |
694 | llvm::errs() << "Error: no checks enabled.\n"; |
695 | llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); |
696 | return 1; |
697 | } |
698 | |
699 | if (PathList.empty()) { |
700 | llvm::errs() << "Error: no input files specified.\n"; |
701 | llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); |
702 | return 1; |
703 | } |
704 | |
705 | llvm::InitializeAllTargetInfos(); |
706 | llvm::InitializeAllTargetMCs(); |
707 | llvm::InitializeAllAsmParsers(); |
708 | |
709 | ClangTidyContext Context(std::move(OwningOptionsProvider), |
710 | AllowEnablingAnalyzerAlphaCheckers, |
711 | EnableModuleHeadersParsing); |
712 | std::vector<ClangTidyError> Errors = |
713 | runClangTidy(Context, Compilations: OptionsParser->getCompilations(), InputFiles: PathList, BaseFS, |
714 | ApplyAnyFix: FixNotes, EnableCheckProfile, StoreCheckProfile: ProfilePrefix); |
715 | bool FoundErrors = llvm::any_of(Range&: Errors, P: [](const ClangTidyError &E) { |
716 | return E.DiagLevel == ClangTidyError::Error; |
717 | }); |
718 | |
719 | // --fix-errors and --fix-notes imply --fix. |
720 | FixBehaviour Behaviour = FixNotes ? FB_FixNotes |
721 | : (Fix || FixErrors) ? FB_Fix |
722 | : FB_NoFix; |
723 | |
724 | const bool DisableFixes = FoundErrors && !FixErrors; |
725 | |
726 | unsigned WErrorCount = 0; |
727 | |
728 | handleErrors(Errors, Context, Fix: DisableFixes ? FB_NoFix : Behaviour, |
729 | WarningsAsErrorsCount&: WErrorCount, BaseFS); |
730 | |
731 | if (!ExportFixes.empty() && !Errors.empty()) { |
732 | std::error_code EC; |
733 | llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None); |
734 | if (EC) { |
735 | llvm::errs() << "Error opening output file: "<< EC.message() << '\n'; |
736 | return 1; |
737 | } |
738 | exportReplacements(MainFilePath: FilePath.str(), Errors, OS); |
739 | } |
740 | |
741 | if (!Quiet) { |
742 | printStats(Stats: Context.getStats()); |
743 | if (DisableFixes && Behaviour != FB_NoFix) |
744 | llvm::errs() |
745 | << "Found compiler errors, but -fix-errors was not specified.\n" |
746 | "Fixes have NOT been applied.\n\n"; |
747 | } |
748 | |
749 | if (WErrorCount) { |
750 | if (!Quiet) { |
751 | StringRef Plural = WErrorCount == 1 ? "": "s"; |
752 | llvm::errs() << WErrorCount << " warning"<< Plural << " treated as error" |
753 | << Plural << "\n"; |
754 | } |
755 | return 1; |
756 | } |
757 | |
758 | if (FoundErrors) { |
759 | // TODO: Figure out when zero exit code should be used with -fix-errors: |
760 | // a. when a fix has been applied for an error |
761 | // b. when a fix has been applied for all errors |
762 | // c. some other condition. |
763 | // For now always returning zero when -fix-errors is used. |
764 | if (FixErrors) |
765 | return 0; |
766 | if (!Quiet) |
767 | llvm::errs() << "Found compiler error(s).\n"; |
768 | return 1; |
769 | } |
770 | |
771 | return 0; |
772 | } |
773 | |
774 | } // namespace clang::tidy |
775 |
Definitions
- desc
- ClangTidyCategory
- CommonHelp
- ClangTidyParameterFileHelp
- ClangTidyHelp
- DefaultChecks
- Checks
- WarningsAsErrors
- HeaderFilter
- ExcludeHeaderFilter
- SystemHeaders
- LineFilter
- Fix
- FixErrors
- FixNotes
- FormatStyle
- ListChecks
- ExplainConfig
- Config
- ConfigFile
- DumpConfig
- EnableCheckProfile
- StoreCheckProfile
- AllowEnablingAnalyzerAlphaCheckers
- EnableModuleHeadersParsing
- ExportFixes
- Quiet
- VfsOverlay
- UseColor
- VerifyConfig
- AllowNoChecks
- printStats
- createOptionsProvider
- getVfsFromFile
- closest
- VerifyConfigWarningEnd
- verifyChecks
- verifyFileExtensions
- verifyOptions
- makeAbsolute
- createBaseFS
Learn to use CMake with our Intro Training
Find out more