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