• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/Path.h"
17 #include "llvm/Support/YAMLTraits.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <utility>
20 
21 #define DEBUG_TYPE "clang-tidy-options"
22 
23 using clang::tidy::ClangTidyOptions;
24 using clang::tidy::FileFilter;
25 using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;
26 
27 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
28 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
29 
30 namespace llvm {
31 namespace yaml {
32 
33 // Map std::pair<int, int> to a JSON array of size 2.
34 template <> struct SequenceTraits<FileFilter::LineRange> {
sizellvm::yaml::SequenceTraits35   static size_t size(IO &IO, FileFilter::LineRange &Range) {
36     return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2;
37   }
elementllvm::yaml::SequenceTraits38   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> {
mappingllvm::yaml::MappingTraits46   static void mapping(IO &IO, FileFilter &File) {
47     IO.mapRequired("name", File.Name);
48     IO.mapOptional("lines", File.LineRanges);
49   }
validatellvm::yaml::MappingTraits50   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> {
mappingllvm::yaml::MappingTraits62   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 {
NOptionMapllvm::yaml::NOptionMap69   NOptionMap(IO &) {}
NOptionMapllvm::yaml::NOptionMap70   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   }
denormalizellvm::yaml::NOptionMap75   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 <> struct MappingTraits<ClangTidyOptions> {
mappingllvm::yaml::MappingTraits85   static void mapping(IO &IO, ClangTidyOptions &Options) {
86     MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(
87         IO, Options.CheckOptions);
88     bool Ignored = false;
89     IO.mapOptional("Checks", Options.Checks);
90     IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);
91     IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
92     IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility
93     IO.mapOptional("FormatStyle", Options.FormatStyle);
94     IO.mapOptional("User", Options.User);
95     IO.mapOptional("CheckOptions", NOpts->Options);
96     IO.mapOptional("ExtraArgs", Options.ExtraArgs);
97     IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);
98     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
99     IO.mapOptional("UseColor", Options.UseColor);
100   }
101 };
102 
103 } // namespace yaml
104 } // namespace llvm
105 
106 namespace clang {
107 namespace tidy {
108 
getDefaults()109 ClangTidyOptions ClangTidyOptions::getDefaults() {
110   ClangTidyOptions Options;
111   Options.Checks = "";
112   Options.WarningsAsErrors = "";
113   Options.HeaderFilterRegex = "";
114   Options.SystemHeaders = false;
115   Options.FormatStyle = "none";
116   Options.User = llvm::None;
117   for (const ClangTidyModuleRegistry::entry &Module :
118        ClangTidyModuleRegistry::entries())
119     Options.mergeWith(Module.instantiate()->getModuleOptions(), 0);
120   return Options;
121 }
122 
123 template <typename T>
mergeVectors(Optional<T> & Dest,const Optional<T> & Src)124 static void mergeVectors(Optional<T> &Dest, const Optional<T> &Src) {
125   if (Src) {
126     if (Dest)
127       Dest->insert(Dest->end(), Src->begin(), Src->end());
128     else
129       Dest = Src;
130   }
131 }
132 
mergeCommaSeparatedLists(Optional<std::string> & Dest,const Optional<std::string> & Src)133 static void mergeCommaSeparatedLists(Optional<std::string> &Dest,
134                                      const Optional<std::string> &Src) {
135   if (Src)
136     Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
137 }
138 
139 template <typename T>
overrideValue(Optional<T> & Dest,const Optional<T> & Src)140 static void overrideValue(Optional<T> &Dest, const Optional<T> &Src) {
141   if (Src)
142     Dest = Src;
143 }
144 
mergeWith(const ClangTidyOptions & Other,unsigned Order)145 ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
146                                               unsigned Order) {
147   mergeCommaSeparatedLists(Checks, Other.Checks);
148   mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors);
149   overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
150   overrideValue(SystemHeaders, Other.SystemHeaders);
151   overrideValue(FormatStyle, Other.FormatStyle);
152   overrideValue(User, Other.User);
153   overrideValue(UseColor, Other.UseColor);
154   mergeVectors(ExtraArgs, Other.ExtraArgs);
155   mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
156 
157   for (const auto &KeyValue : Other.CheckOptions) {
158     CheckOptions.insert_or_assign(
159         KeyValue.getKey(),
160         ClangTidyValue(KeyValue.getValue().Value,
161                        KeyValue.getValue().Priority + Order));
162   }
163   return *this;
164 }
165 
merge(const ClangTidyOptions & Other,unsigned Order) const166 ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,
167                                          unsigned Order) const {
168   ClangTidyOptions Result = *this;
169   Result.mergeWith(Other, Order);
170   return Result;
171 }
172 
173 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
174     "clang-tidy binary";
175 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
176     "command-line option '-checks'";
177 const char
178     ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
179         "command-line option '-config'";
180 
181 ClangTidyOptions
getOptions(llvm::StringRef FileName)182 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {
183   ClangTidyOptions Result;
184   unsigned Priority = 0;
185   for (auto &Source : getRawOptions(FileName))
186     Result.mergeWith(Source.first, ++Priority);
187   return Result;
188 }
189 
190 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)191 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
192   std::vector<OptionsSource> Result;
193   Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
194   return Result;
195 }
196 
ConfigOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & ConfigOptions,const ClangTidyOptions & OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)197 ConfigOptionsProvider::ConfigOptionsProvider(
198     const ClangTidyGlobalOptions &GlobalOptions,
199     const ClangTidyOptions &DefaultOptions,
200     const ClangTidyOptions &ConfigOptions,
201     const ClangTidyOptions &OverrideOptions,
202     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
203     : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions,
204                               FS),
205       ConfigOptions(ConfigOptions) {}
206 
207 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)208 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
209   std::vector<OptionsSource> RawOptions =
210       DefaultOptionsProvider::getRawOptions(FileName);
211   if (ConfigOptions.InheritParentConfig.getValueOr(false)) {
212     LLVM_DEBUG(llvm::dbgs()
213                << "Getting options for file " << FileName << "...\n");
214     assert(FS && "FS must be set.");
215 
216     llvm::SmallString<128> AbsoluteFilePath(FileName);
217 
218     if (!FS->makeAbsolute(AbsoluteFilePath)) {
219       addRawFileOptions(AbsoluteFilePath, RawOptions);
220     }
221   }
222   RawOptions.emplace_back(ConfigOptions,
223                           OptionsSourceTypeConfigCommandLineOption);
224   RawOptions.emplace_back(OverrideOptions,
225                           OptionsSourceTypeCheckCommandLineOption);
226   return RawOptions;
227 }
228 
FileOptionsBaseProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)229 FileOptionsBaseProvider::FileOptionsBaseProvider(
230     const ClangTidyGlobalOptions &GlobalOptions,
231     const ClangTidyOptions &DefaultOptions,
232     const ClangTidyOptions &OverrideOptions,
233     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
234     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
235       OverrideOptions(OverrideOptions), FS(std::move(VFS)) {
236   if (!FS)
237     FS = llvm::vfs::getRealFileSystem();
238   ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
239 }
240 
FileOptionsBaseProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,const FileOptionsBaseProvider::ConfigFileHandlers & ConfigHandlers)241 FileOptionsBaseProvider::FileOptionsBaseProvider(
242     const ClangTidyGlobalOptions &GlobalOptions,
243     const ClangTidyOptions &DefaultOptions,
244     const ClangTidyOptions &OverrideOptions,
245     const FileOptionsBaseProvider::ConfigFileHandlers &ConfigHandlers)
246     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
247       OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {}
248 
addRawFileOptions(llvm::StringRef AbsolutePath,std::vector<OptionsSource> & CurOptions)249 void FileOptionsBaseProvider::addRawFileOptions(
250     llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
251   auto CurSize = CurOptions.size();
252 
253   // Look for a suitable configuration file in all parent directories of the
254   // file. Start with the immediate parent directory and move up.
255   StringRef Path = llvm::sys::path::parent_path(AbsolutePath);
256   for (StringRef CurrentPath = Path; !CurrentPath.empty();
257        CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
258     llvm::Optional<OptionsSource> Result;
259 
260     auto Iter = CachedOptions.find(CurrentPath);
261     if (Iter != CachedOptions.end())
262       Result = Iter->second;
263 
264     if (!Result)
265       Result = tryReadConfigFile(CurrentPath);
266 
267     if (Result) {
268       // Store cached value for all intermediate directories.
269       while (Path != CurrentPath) {
270         LLVM_DEBUG(llvm::dbgs()
271                    << "Caching configuration for path " << Path << ".\n");
272         if (!CachedOptions.count(Path))
273           CachedOptions[Path] = *Result;
274         Path = llvm::sys::path::parent_path(Path);
275       }
276       CachedOptions[Path] = *Result;
277 
278       CurOptions.push_back(*Result);
279       if (!Result->first.InheritParentConfig.getValueOr(false))
280         break;
281     }
282   }
283   // Reverse order of file configs because closer configs should have higher
284   // priority.
285   std::reverse(CurOptions.begin() + CurSize, CurOptions.end());
286 }
287 
FileOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)288 FileOptionsProvider::FileOptionsProvider(
289     const ClangTidyGlobalOptions &GlobalOptions,
290     const ClangTidyOptions &DefaultOptions,
291     const ClangTidyOptions &OverrideOptions,
292     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
293     : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions,
294                               VFS){}
295 
FileOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,const FileOptionsBaseProvider::ConfigFileHandlers & ConfigHandlers)296 FileOptionsProvider::FileOptionsProvider(
297     const ClangTidyGlobalOptions &GlobalOptions,
298     const ClangTidyOptions &DefaultOptions,
299     const ClangTidyOptions &OverrideOptions,
300     const FileOptionsBaseProvider::ConfigFileHandlers &ConfigHandlers)
301     : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions,
302                               ConfigHandlers) {}
303 
304 // FIXME: This method has some common logic with clang::format::getStyle().
305 // Consider pulling out common bits to a findParentFileWithName function or
306 // similar.
307 std::vector<OptionsSource>
getRawOptions(StringRef FileName)308 FileOptionsProvider::getRawOptions(StringRef FileName) {
309   LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
310                           << "...\n");
311   assert(FS && "FS must be set.");
312 
313   llvm::SmallString<128> AbsoluteFilePath(FileName);
314 
315   if (FS->makeAbsolute(AbsoluteFilePath))
316     return {};
317 
318   std::vector<OptionsSource> RawOptions =
319       DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
320   addRawFileOptions(AbsoluteFilePath, RawOptions);
321   OptionsSource CommandLineOptions(OverrideOptions,
322                                    OptionsSourceTypeCheckCommandLineOption);
323 
324   RawOptions.push_back(CommandLineOptions);
325   return RawOptions;
326 }
327 
328 llvm::Optional<OptionsSource>
tryReadConfigFile(StringRef Directory)329 FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
330   assert(!Directory.empty());
331 
332   llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);
333 
334   if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
335     llvm::errs() << "Error reading configuration from " << Directory
336                  << ": directory doesn't exist.\n";
337     return llvm::None;
338   }
339 
340   for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
341     SmallString<128> ConfigFile(Directory);
342     llvm::sys::path::append(ConfigFile, ConfigHandler.first);
343     LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
344 
345     llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
346 
347     if (!FileStatus || !FileStatus->isRegularFile())
348       continue;
349 
350     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
351         FS->getBufferForFile(ConfigFile);
352     if (std::error_code EC = Text.getError()) {
353       llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
354                    << "\n";
355       continue;
356     }
357 
358     // Skip empty files, e.g. files opened for writing via shell output
359     // redirection.
360     if ((*Text)->getBuffer().empty())
361       continue;
362     llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
363         ConfigHandler.second((*Text)->getBuffer());
364     if (!ParsedOptions) {
365       if (ParsedOptions.getError())
366         llvm::errs() << "Error parsing " << ConfigFile << ": "
367                      << ParsedOptions.getError().message() << "\n";
368       continue;
369     }
370     return OptionsSource(*ParsedOptions, std::string(ConfigFile));
371   }
372   return llvm::None;
373 }
374 
375 /// Parses -line-filter option and stores it to the \c Options.
parseLineFilter(StringRef LineFilter,clang::tidy::ClangTidyGlobalOptions & Options)376 std::error_code parseLineFilter(StringRef LineFilter,
377                                 clang::tidy::ClangTidyGlobalOptions &Options) {
378   llvm::yaml::Input Input(LineFilter);
379   Input >> Options.LineFilter;
380   return Input.error();
381 }
382 
parseConfiguration(StringRef Config)383 llvm::ErrorOr<ClangTidyOptions> parseConfiguration(StringRef Config) {
384   llvm::yaml::Input Input(Config);
385   ClangTidyOptions Options;
386   Input >> Options;
387   if (Input.error())
388     return Input.error();
389   return Options;
390 }
391 
configurationAsText(const ClangTidyOptions & Options)392 std::string configurationAsText(const ClangTidyOptions &Options) {
393   std::string Text;
394   llvm::raw_string_ostream Stream(Text);
395   llvm::yaml::Output Output(Stream);
396   // We use the same mapping method for input and output, so we need a non-const
397   // reference here.
398   ClangTidyOptions NonConstValue = Options;
399   Output << NonConstValue;
400   return Stream.str();
401 }
402 
403 } // namespace tidy
404 } // namespace clang
405