1 | //===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===// |
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 | #include "ClangTidyOptions.h" |
10 | #include "ClangTidyModuleRegistry.h" |
11 | #include "clang/Basic/LLVM.h" |
12 | #include "llvm/ADT/SmallString.h" |
13 | #include "llvm/Support/Debug.h" |
14 | #include "llvm/Support/Errc.h" |
15 | #include "llvm/Support/FileSystem.h" |
16 | #include "llvm/Support/MemoryBufferRef.h" |
17 | #include "llvm/Support/Path.h" |
18 | #include "llvm/Support/YAMLTraits.h" |
19 | #include <optional> |
20 | #include <utility> |
21 | |
22 | #define DEBUG_TYPE "clang-tidy-options" |
23 | |
24 | using clang::tidy::ClangTidyOptions; |
25 | using clang::tidy::FileFilter; |
26 | using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource; |
27 | |
28 | LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter) |
29 | LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange) |
30 | |
31 | namespace llvm::yaml { |
32 | |
33 | // Map std::pair<int, int> to a JSON array of size 2. |
34 | template <> struct SequenceTraits<FileFilter::LineRange> { |
35 | static size_t size(IO &IO, FileFilter::LineRange &Range) { |
36 | return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2; |
37 | } |
38 | static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) { |
39 | if (Index > 1) |
40 | IO.setError("Too many elements in line range." ); |
41 | return Index == 0 ? Range.first : Range.second; |
42 | } |
43 | }; |
44 | |
45 | template <> struct MappingTraits<FileFilter> { |
46 | static void mapping(IO &IO, FileFilter &File) { |
47 | IO.mapRequired("name" , File.Name); |
48 | IO.mapOptional("lines" , File.LineRanges); |
49 | } |
50 | static std::string validate(IO &Io, FileFilter &File) { |
51 | if (File.Name.empty()) |
52 | return "No file name specified" ; |
53 | for (const FileFilter::LineRange &Range : File.LineRanges) { |
54 | if (Range.first <= 0 || Range.second <= 0) |
55 | return "Invalid line range" ; |
56 | } |
57 | return "" ; |
58 | } |
59 | }; |
60 | |
61 | template <> struct MappingTraits<ClangTidyOptions::StringPair> { |
62 | static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) { |
63 | IO.mapRequired("key" , KeyValue.first); |
64 | IO.mapRequired("value" , KeyValue.second); |
65 | } |
66 | }; |
67 | |
68 | struct NOptionMap { |
69 | NOptionMap(IO &) {} |
70 | NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) { |
71 | Options.reserve(OptionMap.size()); |
72 | for (const auto &KeyValue : OptionMap) |
73 | Options.emplace_back(std::string(KeyValue.getKey()), KeyValue.getValue().Value); |
74 | } |
75 | ClangTidyOptions::OptionMap denormalize(IO &) { |
76 | ClangTidyOptions::OptionMap Map; |
77 | for (const auto &KeyValue : Options) |
78 | Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second); |
79 | return Map; |
80 | } |
81 | std::vector<ClangTidyOptions::StringPair> Options; |
82 | }; |
83 | |
84 | template <> |
85 | void yamlize(IO &IO, ClangTidyOptions::OptionMap &Options, bool, |
86 | EmptyContext &Ctx) { |
87 | if (IO.outputting()) { |
88 | IO.beginMapping(); |
89 | // Only output as a map |
90 | for (auto &Key : Options) { |
91 | bool UseDefault; |
92 | void *SaveInfo; |
93 | IO.preflightKey(Key.getKey().data(), true, false, UseDefault, SaveInfo); |
94 | StringRef S = Key.getValue().Value; |
95 | IO.scalarString(S, needsQuotes(S)); |
96 | IO.postflightKey(SaveInfo); |
97 | } |
98 | IO.endMapping(); |
99 | } else { |
100 | // We need custom logic here to support the old method of specifying check |
101 | // options using a list of maps containing key and value keys. |
102 | Input &I = reinterpret_cast<Input &>(IO); |
103 | if (isa<SequenceNode>(I.getCurrentNode())) { |
104 | MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts( |
105 | IO, Options); |
106 | EmptyContext Ctx; |
107 | yamlize(IO, NOpts->Options, true, Ctx); |
108 | } else if (isa<MappingNode>(I.getCurrentNode())) { |
109 | IO.beginMapping(); |
110 | for (StringRef Key : IO.keys()) { |
111 | IO.mapRequired(Key.data(), Options[Key].Value); |
112 | } |
113 | IO.endMapping(); |
114 | } else { |
115 | IO.setError("expected a sequence or map" ); |
116 | } |
117 | } |
118 | } |
119 | |
120 | struct ChecksVariant { |
121 | std::optional<std::string> AsString; |
122 | std::optional<std::vector<std::string>> AsVector; |
123 | }; |
124 | |
125 | template <> |
126 | void yamlize(IO &IO, ChecksVariant &Checks, bool, EmptyContext &Ctx) { |
127 | if (!IO.outputting()) { |
128 | // Special case for reading from YAML |
129 | // Must support reading from both a string or a list |
130 | Input &I = reinterpret_cast<Input &>(IO); |
131 | if (isa<ScalarNode, BlockScalarNode>(I.getCurrentNode())) { |
132 | Checks.AsString = std::string(); |
133 | yamlize(IO, *Checks.AsString, true, Ctx); |
134 | } else if (isa<SequenceNode>(I.getCurrentNode())) { |
135 | Checks.AsVector = std::vector<std::string>(); |
136 | yamlize(IO, *Checks.AsVector, true, Ctx); |
137 | } else { |
138 | IO.setError("expected string or sequence" ); |
139 | } |
140 | } |
141 | } |
142 | |
143 | static void mapChecks(IO &IO, std::optional<std::string> &Checks) { |
144 | if (IO.outputting()) { |
145 | // Output always a string |
146 | IO.mapOptional("Checks" , Checks); |
147 | } else { |
148 | // Input as either a string or a list |
149 | ChecksVariant ChecksAsVariant; |
150 | IO.mapOptional("Checks" , ChecksAsVariant); |
151 | if (ChecksAsVariant.AsString) |
152 | Checks = ChecksAsVariant.AsString; |
153 | else if (ChecksAsVariant.AsVector) |
154 | Checks = llvm::join(*ChecksAsVariant.AsVector, "," ); |
155 | } |
156 | } |
157 | |
158 | template <> struct MappingTraits<ClangTidyOptions> { |
159 | static void mapping(IO &IO, ClangTidyOptions &Options) { |
160 | bool Ignored = false; |
161 | mapChecks(IO, Options.Checks); |
162 | IO.mapOptional("WarningsAsErrors" , Options.WarningsAsErrors); |
163 | IO.mapOptional("HeaderFileExtensions" , Options.HeaderFileExtensions); |
164 | IO.mapOptional("ImplementationFileExtensions" , |
165 | Options.ImplementationFileExtensions); |
166 | IO.mapOptional("HeaderFilterRegex" , Options.HeaderFilterRegex); |
167 | IO.mapOptional("AnalyzeTemporaryDtors" , Ignored); // deprecated |
168 | IO.mapOptional("FormatStyle" , Options.FormatStyle); |
169 | IO.mapOptional("User" , Options.User); |
170 | IO.mapOptional("CheckOptions" , Options.CheckOptions); |
171 | IO.mapOptional("ExtraArgs" , Options.ExtraArgs); |
172 | IO.mapOptional("ExtraArgsBefore" , Options.ExtraArgsBefore); |
173 | IO.mapOptional("InheritParentConfig" , Options.InheritParentConfig); |
174 | IO.mapOptional("UseColor" , Options.UseColor); |
175 | IO.mapOptional("SystemHeaders" , Options.SystemHeaders); |
176 | } |
177 | }; |
178 | |
179 | } // namespace llvm::yaml |
180 | |
181 | namespace clang::tidy { |
182 | |
183 | ClangTidyOptions ClangTidyOptions::getDefaults() { |
184 | ClangTidyOptions Options; |
185 | Options.Checks = "" ; |
186 | Options.WarningsAsErrors = "" ; |
187 | Options.HeaderFileExtensions = {"" , "h" , "hh" , "hpp" , "hxx" }; |
188 | Options.ImplementationFileExtensions = {"c" , "cc" , "cpp" , "cxx" }; |
189 | Options.HeaderFilterRegex = "" ; |
190 | Options.SystemHeaders = false; |
191 | Options.FormatStyle = "none" ; |
192 | Options.User = std::nullopt; |
193 | for (const ClangTidyModuleRegistry::entry &Module : |
194 | ClangTidyModuleRegistry::entries()) |
195 | Options.mergeWith(Module.instantiate()->getModuleOptions(), 0); |
196 | return Options; |
197 | } |
198 | |
199 | template <typename T> |
200 | static void mergeVectors(std::optional<T> &Dest, const std::optional<T> &Src) { |
201 | if (Src) { |
202 | if (Dest) |
203 | Dest->insert(Dest->end(), Src->begin(), Src->end()); |
204 | else |
205 | Dest = Src; |
206 | } |
207 | } |
208 | |
209 | static void mergeCommaSeparatedLists(std::optional<std::string> &Dest, |
210 | const std::optional<std::string> &Src) { |
211 | if (Src) |
212 | Dest = (Dest && !Dest->empty() ? *Dest + "," : "" ) + *Src; |
213 | } |
214 | |
215 | template <typename T> |
216 | static void overrideValue(std::optional<T> &Dest, const std::optional<T> &Src) { |
217 | if (Src) |
218 | Dest = Src; |
219 | } |
220 | |
221 | ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other, |
222 | unsigned Order) { |
223 | mergeCommaSeparatedLists(Checks, Other.Checks); |
224 | mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors); |
225 | overrideValue(HeaderFileExtensions, Other.HeaderFileExtensions); |
226 | overrideValue(ImplementationFileExtensions, |
227 | Other.ImplementationFileExtensions); |
228 | overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex); |
229 | overrideValue(SystemHeaders, Other.SystemHeaders); |
230 | overrideValue(FormatStyle, Other.FormatStyle); |
231 | overrideValue(User, Other.User); |
232 | overrideValue(UseColor, Other.UseColor); |
233 | mergeVectors(ExtraArgs, Other.ExtraArgs); |
234 | mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore); |
235 | |
236 | for (const auto &KeyValue : Other.CheckOptions) { |
237 | CheckOptions.insert_or_assign( |
238 | KeyValue.getKey(), |
239 | ClangTidyValue(KeyValue.getValue().Value, |
240 | KeyValue.getValue().Priority + Order)); |
241 | } |
242 | return *this; |
243 | } |
244 | |
245 | ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other, |
246 | unsigned Order) const { |
247 | ClangTidyOptions Result = *this; |
248 | Result.mergeWith(Other, Order); |
249 | return Result; |
250 | } |
251 | |
252 | const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] = |
253 | "clang-tidy binary" ; |
254 | const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] = |
255 | "command-line option '-checks'" ; |
256 | const char |
257 | ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] = |
258 | "command-line option '-config'" ; |
259 | |
260 | ClangTidyOptions |
261 | ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) { |
262 | ClangTidyOptions Result; |
263 | unsigned Priority = 0; |
264 | for (auto &Source : getRawOptions(FileName)) |
265 | Result.mergeWith(Source.first, ++Priority); |
266 | return Result; |
267 | } |
268 | |
269 | std::vector<OptionsSource> |
270 | DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { |
271 | std::vector<OptionsSource> Result; |
272 | Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary); |
273 | return Result; |
274 | } |
275 | |
276 | ConfigOptionsProvider::ConfigOptionsProvider( |
277 | ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, |
278 | ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions, |
279 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) |
280 | : FileOptionsBaseProvider(std::move(GlobalOptions), |
281 | std::move(DefaultOptions), |
282 | std::move(OverrideOptions), std::move(FS)), |
283 | ConfigOptions(std::move(ConfigOptions)) {} |
284 | |
285 | std::vector<OptionsSource> |
286 | ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) { |
287 | std::vector<OptionsSource> RawOptions = |
288 | DefaultOptionsProvider::getRawOptions(FileName); |
289 | if (ConfigOptions.InheritParentConfig.value_or(false)) { |
290 | LLVM_DEBUG(llvm::dbgs() |
291 | << "Getting options for file " << FileName << "...\n" ); |
292 | assert(FS && "FS must be set." ); |
293 | |
294 | llvm::SmallString<128> AbsoluteFilePath(FileName); |
295 | |
296 | if (!FS->makeAbsolute(AbsoluteFilePath)) { |
297 | addRawFileOptions(AbsoluteFilePath, RawOptions); |
298 | } |
299 | } |
300 | RawOptions.emplace_back(ConfigOptions, |
301 | OptionsSourceTypeConfigCommandLineOption); |
302 | RawOptions.emplace_back(OverrideOptions, |
303 | OptionsSourceTypeCheckCommandLineOption); |
304 | return RawOptions; |
305 | } |
306 | |
307 | FileOptionsBaseProvider::FileOptionsBaseProvider( |
308 | ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, |
309 | ClangTidyOptions OverrideOptions, |
310 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) |
311 | : DefaultOptionsProvider(std::move(GlobalOptions), |
312 | std::move(DefaultOptions)), |
313 | OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) { |
314 | if (!FS) |
315 | FS = llvm::vfs::getRealFileSystem(); |
316 | ConfigHandlers.emplace_back(".clang-tidy" , parseConfiguration); |
317 | } |
318 | |
319 | FileOptionsBaseProvider::FileOptionsBaseProvider( |
320 | ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, |
321 | ClangTidyOptions OverrideOptions, |
322 | FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers) |
323 | : DefaultOptionsProvider(std::move(GlobalOptions), |
324 | std::move(DefaultOptions)), |
325 | OverrideOptions(std::move(OverrideOptions)), |
326 | ConfigHandlers(std::move(ConfigHandlers)) {} |
327 | |
328 | void FileOptionsBaseProvider::addRawFileOptions( |
329 | llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) { |
330 | auto CurSize = CurOptions.size(); |
331 | |
332 | // Look for a suitable configuration file in all parent directories of the |
333 | // file. Start with the immediate parent directory and move up. |
334 | StringRef Path = llvm::sys::path::parent_path(AbsolutePath); |
335 | for (StringRef CurrentPath = Path; !CurrentPath.empty(); |
336 | CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { |
337 | std::optional<OptionsSource> Result; |
338 | |
339 | auto Iter = CachedOptions.find(CurrentPath); |
340 | if (Iter != CachedOptions.end()) |
341 | Result = Iter->second; |
342 | |
343 | if (!Result) |
344 | Result = tryReadConfigFile(CurrentPath); |
345 | |
346 | if (Result) { |
347 | // Store cached value for all intermediate directories. |
348 | while (Path != CurrentPath) { |
349 | LLVM_DEBUG(llvm::dbgs() |
350 | << "Caching configuration for path " << Path << ".\n" ); |
351 | if (!CachedOptions.count(Path)) |
352 | CachedOptions[Path] = *Result; |
353 | Path = llvm::sys::path::parent_path(Path); |
354 | } |
355 | CachedOptions[Path] = *Result; |
356 | |
357 | CurOptions.push_back(*Result); |
358 | if (!Result->first.InheritParentConfig.value_or(false)) |
359 | break; |
360 | } |
361 | } |
362 | // Reverse order of file configs because closer configs should have higher |
363 | // priority. |
364 | std::reverse(CurOptions.begin() + CurSize, CurOptions.end()); |
365 | } |
366 | |
367 | FileOptionsProvider::FileOptionsProvider( |
368 | ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, |
369 | ClangTidyOptions OverrideOptions, |
370 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) |
371 | : FileOptionsBaseProvider(std::move(GlobalOptions), |
372 | std::move(DefaultOptions), |
373 | std::move(OverrideOptions), std::move(VFS)) {} |
374 | |
375 | FileOptionsProvider::FileOptionsProvider( |
376 | ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, |
377 | ClangTidyOptions OverrideOptions, |
378 | FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers) |
379 | : FileOptionsBaseProvider( |
380 | std::move(GlobalOptions), std::move(DefaultOptions), |
381 | std::move(OverrideOptions), std::move(ConfigHandlers)) {} |
382 | |
383 | // FIXME: This method has some common logic with clang::format::getStyle(). |
384 | // Consider pulling out common bits to a findParentFileWithName function or |
385 | // similar. |
386 | std::vector<OptionsSource> |
387 | FileOptionsProvider::getRawOptions(StringRef FileName) { |
388 | LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName |
389 | << "...\n" ); |
390 | assert(FS && "FS must be set." ); |
391 | |
392 | llvm::SmallString<128> AbsoluteFilePath(FileName); |
393 | |
394 | if (FS->makeAbsolute(AbsoluteFilePath)) |
395 | return {}; |
396 | |
397 | std::vector<OptionsSource> RawOptions = |
398 | DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str()); |
399 | addRawFileOptions(AbsoluteFilePath, RawOptions); |
400 | OptionsSource CommandLineOptions(OverrideOptions, |
401 | OptionsSourceTypeCheckCommandLineOption); |
402 | |
403 | RawOptions.push_back(CommandLineOptions); |
404 | return RawOptions; |
405 | } |
406 | |
407 | std::optional<OptionsSource> |
408 | FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) { |
409 | assert(!Directory.empty()); |
410 | |
411 | llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory); |
412 | |
413 | if (!DirectoryStatus || !DirectoryStatus->isDirectory()) { |
414 | llvm::errs() << "Error reading configuration from " << Directory |
415 | << ": directory doesn't exist.\n" ; |
416 | return std::nullopt; |
417 | } |
418 | |
419 | for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) { |
420 | SmallString<128> ConfigFile(Directory); |
421 | llvm::sys::path::append(ConfigFile, ConfigHandler.first); |
422 | LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n" ); |
423 | |
424 | llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile); |
425 | |
426 | if (!FileStatus || !FileStatus->isRegularFile()) |
427 | continue; |
428 | |
429 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text = |
430 | FS->getBufferForFile(ConfigFile); |
431 | if (std::error_code EC = Text.getError()) { |
432 | llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message() |
433 | << "\n" ; |
434 | continue; |
435 | } |
436 | |
437 | // Skip empty files, e.g. files opened for writing via shell output |
438 | // redirection. |
439 | if ((*Text)->getBuffer().empty()) |
440 | continue; |
441 | llvm::ErrorOr<ClangTidyOptions> ParsedOptions = |
442 | ConfigHandler.second({(*Text)->getBuffer(), ConfigFile}); |
443 | if (!ParsedOptions) { |
444 | if (ParsedOptions.getError()) |
445 | llvm::errs() << "Error parsing " << ConfigFile << ": " |
446 | << ParsedOptions.getError().message() << "\n" ; |
447 | continue; |
448 | } |
449 | return OptionsSource(*ParsedOptions, std::string(ConfigFile)); |
450 | } |
451 | return std::nullopt; |
452 | } |
453 | |
454 | /// Parses -line-filter option and stores it to the \c Options. |
455 | std::error_code parseLineFilter(StringRef LineFilter, |
456 | clang::tidy::ClangTidyGlobalOptions &Options) { |
457 | llvm::yaml::Input Input(LineFilter); |
458 | Input >> Options.LineFilter; |
459 | return Input.error(); |
460 | } |
461 | |
462 | llvm::ErrorOr<ClangTidyOptions> |
463 | parseConfiguration(llvm::MemoryBufferRef Config) { |
464 | llvm::yaml::Input Input(Config); |
465 | ClangTidyOptions Options; |
466 | Input >> Options; |
467 | if (Input.error()) |
468 | return Input.error(); |
469 | return Options; |
470 | } |
471 | |
472 | static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) { |
473 | (*reinterpret_cast<DiagCallback *>(Ctx))(Diag); |
474 | } |
475 | |
476 | llvm::ErrorOr<ClangTidyOptions> |
477 | parseConfigurationWithDiags(llvm::MemoryBufferRef Config, |
478 | DiagCallback Handler) { |
479 | llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr, |
480 | &Handler); |
481 | ClangTidyOptions Options; |
482 | Input >> Options; |
483 | if (Input.error()) |
484 | return Input.error(); |
485 | return Options; |
486 | } |
487 | |
488 | std::string configurationAsText(const ClangTidyOptions &Options) { |
489 | std::string Text; |
490 | llvm::raw_string_ostream Stream(Text); |
491 | llvm::yaml::Output Output(Stream); |
492 | // We use the same mapping method for input and output, so we need a non-const |
493 | // reference here. |
494 | ClangTidyOptions NonConstValue = Options; |
495 | Output << NonConstValue; |
496 | return Stream.str(); |
497 | } |
498 | |
499 | } // namespace clang::tidy |
500 | |