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 StringRef 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 StringRef();
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(OptionMap.begin(), OptionMap.end()) {}
denormalizellvm::yaml::NOptionMap72   ClangTidyOptions::OptionMap denormalize(IO &) {
73     ClangTidyOptions::OptionMap Map;
74     for (const auto &KeyValue : Options)
75       Map[KeyValue.first] = KeyValue.second;
76     return Map;
77   }
78   std::vector<ClangTidyOptions::StringPair> Options;
79 };
80 
81 template <> struct MappingTraits<ClangTidyOptions> {
mappingllvm::yaml::MappingTraits82   static void mapping(IO &IO, ClangTidyOptions &Options) {
83     MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(
84         IO, Options.CheckOptions);
85     bool Ignored = false;
86     IO.mapOptional("Checks", Options.Checks);
87     IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);
88     IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
89     IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility
90     IO.mapOptional("FormatStyle", Options.FormatStyle);
91     IO.mapOptional("User", Options.User);
92     IO.mapOptional("CheckOptions", NOpts->Options);
93     IO.mapOptional("ExtraArgs", Options.ExtraArgs);
94     IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);
95   }
96 };
97 
98 } // namespace yaml
99 } // namespace llvm
100 
101 namespace clang {
102 namespace tidy {
103 
getDefaults()104 ClangTidyOptions ClangTidyOptions::getDefaults() {
105   ClangTidyOptions Options;
106   Options.Checks = "";
107   Options.WarningsAsErrors = "";
108   Options.HeaderFilterRegex = "";
109   Options.SystemHeaders = false;
110   Options.FormatStyle = "none";
111   Options.User = llvm::None;
112   for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(),
113                                          E = ClangTidyModuleRegistry::end();
114        I != E; ++I)
115     Options = Options.mergeWith(I->instantiate()->getModuleOptions());
116   return Options;
117 }
118 
119 template <typename T>
mergeVectors(Optional<T> & Dest,const Optional<T> & Src)120 static void mergeVectors(Optional<T> &Dest, const Optional<T> &Src) {
121   if (Src) {
122     if (Dest)
123       Dest->insert(Dest->end(), Src->begin(), Src->end());
124     else
125       Dest = Src;
126   }
127 }
128 
mergeCommaSeparatedLists(Optional<std::string> & Dest,const Optional<std::string> & Src)129 static void mergeCommaSeparatedLists(Optional<std::string> &Dest,
130                                      const Optional<std::string> &Src) {
131   if (Src)
132     Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
133 }
134 
135 template <typename T>
overrideValue(Optional<T> & Dest,const Optional<T> & Src)136 static void overrideValue(Optional<T> &Dest, const Optional<T> &Src) {
137   if (Src)
138     Dest = Src;
139 }
140 
141 ClangTidyOptions
mergeWith(const ClangTidyOptions & Other) const142 ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const {
143   ClangTidyOptions Result = *this;
144 
145   mergeCommaSeparatedLists(Result.Checks, Other.Checks);
146   mergeCommaSeparatedLists(Result.WarningsAsErrors, Other.WarningsAsErrors);
147   overrideValue(Result.HeaderFilterRegex, Other.HeaderFilterRegex);
148   overrideValue(Result.SystemHeaders, Other.SystemHeaders);
149   overrideValue(Result.FormatStyle, Other.FormatStyle);
150   overrideValue(Result.User, Other.User);
151   mergeVectors(Result.ExtraArgs, Other.ExtraArgs);
152   mergeVectors(Result.ExtraArgsBefore, Other.ExtraArgsBefore);
153 
154   for (const auto &KeyValue : Other.CheckOptions)
155     Result.CheckOptions[KeyValue.first] = KeyValue.second;
156 
157   return Result;
158 }
159 
160 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
161     "clang-tidy binary";
162 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
163     "command-line option '-checks'";
164 const char
165     ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
166         "command-line option '-config'";
167 
168 ClangTidyOptions
getOptions(llvm::StringRef FileName)169 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {
170   ClangTidyOptions Result;
171   for (const auto &Source : getRawOptions(FileName))
172     Result = Result.mergeWith(Source.first);
173   return Result;
174 }
175 
176 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)177 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
178   std::vector<OptionsSource> Result;
179   Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
180   return Result;
181 }
182 
ConfigOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & ConfigOptions,const ClangTidyOptions & OverrideOptions)183 ConfigOptionsProvider::ConfigOptionsProvider(
184     const ClangTidyGlobalOptions &GlobalOptions,
185     const ClangTidyOptions &DefaultOptions,
186     const ClangTidyOptions &ConfigOptions,
187     const ClangTidyOptions &OverrideOptions)
188     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
189       ConfigOptions(ConfigOptions), OverrideOptions(OverrideOptions) {}
190 
191 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)192 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
193   std::vector<OptionsSource> RawOptions =
194       DefaultOptionsProvider::getRawOptions(FileName);
195   RawOptions.emplace_back(ConfigOptions,
196                           OptionsSourceTypeConfigCommandLineOption);
197   RawOptions.emplace_back(OverrideOptions,
198                           OptionsSourceTypeCheckCommandLineOption);
199   return RawOptions;
200 }
201 
FileOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)202 FileOptionsProvider::FileOptionsProvider(
203     const ClangTidyGlobalOptions &GlobalOptions,
204     const ClangTidyOptions &DefaultOptions,
205     const ClangTidyOptions &OverrideOptions,
206     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
207     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
208       OverrideOptions(OverrideOptions), FS(std::move(VFS)) {
209   if (!FS)
210     FS = llvm::vfs::getRealFileSystem();
211   ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
212 }
213 
FileOptionsProvider(const ClangTidyGlobalOptions & GlobalOptions,const ClangTidyOptions & DefaultOptions,const ClangTidyOptions & OverrideOptions,const FileOptionsProvider::ConfigFileHandlers & ConfigHandlers)214 FileOptionsProvider::FileOptionsProvider(
215     const ClangTidyGlobalOptions &GlobalOptions,
216     const ClangTidyOptions &DefaultOptions,
217     const ClangTidyOptions &OverrideOptions,
218     const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers)
219     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
220       OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {}
221 
222 // FIXME: This method has some common logic with clang::format::getStyle().
223 // Consider pulling out common bits to a findParentFileWithName function or
224 // similar.
225 std::vector<OptionsSource>
getRawOptions(StringRef FileName)226 FileOptionsProvider::getRawOptions(StringRef FileName) {
227   LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
228                           << "...\n");
229   assert(FS && "FS must be set.");
230 
231   llvm::SmallString<128> AbsoluteFilePath(FileName);
232 
233   if (FS->makeAbsolute(AbsoluteFilePath))
234     return {};
235 
236   std::vector<OptionsSource> RawOptions =
237       DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
238   OptionsSource CommandLineOptions(OverrideOptions,
239                                    OptionsSourceTypeCheckCommandLineOption);
240   // Look for a suitable configuration file in all parent directories of the
241   // file. Start with the immediate parent directory and move up.
242   StringRef Path = llvm::sys::path::parent_path(AbsoluteFilePath.str());
243   for (StringRef CurrentPath = Path; !CurrentPath.empty();
244        CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
245     llvm::Optional<OptionsSource> Result;
246 
247     auto Iter = CachedOptions.find(CurrentPath);
248     if (Iter != CachedOptions.end())
249       Result = Iter->second;
250 
251     if (!Result)
252       Result = tryReadConfigFile(CurrentPath);
253 
254     if (Result) {
255       // Store cached value for all intermediate directories.
256       while (Path != CurrentPath) {
257         LLVM_DEBUG(llvm::dbgs()
258                    << "Caching configuration for path " << Path << ".\n");
259         CachedOptions[Path] = *Result;
260         Path = llvm::sys::path::parent_path(Path);
261       }
262       CachedOptions[Path] = *Result;
263 
264       RawOptions.push_back(*Result);
265       break;
266     }
267   }
268   RawOptions.push_back(CommandLineOptions);
269   return RawOptions;
270 }
271 
272 llvm::Optional<OptionsSource>
tryReadConfigFile(StringRef Directory)273 FileOptionsProvider::tryReadConfigFile(StringRef Directory) {
274   assert(!Directory.empty());
275 
276   if (!llvm::sys::fs::is_directory(Directory)) {
277     llvm::errs() << "Error reading configuration from " << Directory
278                  << ": directory doesn't exist.\n";
279     return llvm::None;
280   }
281 
282   for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
283     SmallString<128> ConfigFile(Directory);
284     llvm::sys::path::append(ConfigFile, ConfigHandler.first);
285     LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
286 
287     bool IsFile = false;
288     // Ignore errors from is_regular_file: we only need to know if we can read
289     // the file or not.
290     llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
291     if (!IsFile)
292       continue;
293 
294     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
295         llvm::MemoryBuffer::getFile(ConfigFile.c_str());
296     if (std::error_code EC = Text.getError()) {
297       llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
298                    << "\n";
299       continue;
300     }
301 
302     // Skip empty files, e.g. files opened for writing via shell output
303     // redirection.
304     if ((*Text)->getBuffer().empty())
305       continue;
306     llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
307         ConfigHandler.second((*Text)->getBuffer());
308     if (!ParsedOptions) {
309       if (ParsedOptions.getError())
310         llvm::errs() << "Error parsing " << ConfigFile << ": "
311                      << ParsedOptions.getError().message() << "\n";
312       continue;
313     }
314     return OptionsSource(*ParsedOptions, ConfigFile.c_str());
315   }
316   return llvm::None;
317 }
318 
319 /// \brief Parses -line-filter option and stores it to the \c Options.
parseLineFilter(StringRef LineFilter,clang::tidy::ClangTidyGlobalOptions & Options)320 std::error_code parseLineFilter(StringRef LineFilter,
321                                 clang::tidy::ClangTidyGlobalOptions &Options) {
322   llvm::yaml::Input Input(LineFilter);
323   Input >> Options.LineFilter;
324   return Input.error();
325 }
326 
parseConfiguration(StringRef Config)327 llvm::ErrorOr<ClangTidyOptions> parseConfiguration(StringRef Config) {
328   llvm::yaml::Input Input(Config);
329   ClangTidyOptions Options;
330   Input >> Options;
331   if (Input.error())
332     return Input.error();
333   return Options;
334 }
335 
configurationAsText(const ClangTidyOptions & Options)336 std::string configurationAsText(const ClangTidyOptions &Options) {
337   std::string Text;
338   llvm::raw_string_ostream Stream(Text);
339   llvm::yaml::Output Output(Stream);
340   // We use the same mapping method for input and output, so we need a non-const
341   // reference here.
342   ClangTidyOptions NonConstValue = Options;
343   Output << NonConstValue;
344   return Stream.str();
345 }
346 
347 } // namespace tidy
348 } // namespace clang
349