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