[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clang-tidy / ClangTidyOptions.cpp
blobf07a8f9e893d8416eb8dae7c61aceeb080ad7e67
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 //===----------------------------------------------------------------------===//
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 <utility>
21 #define DEBUG_TYPE "clang-tidy-options"
23 using clang::tidy::ClangTidyOptions;
24 using clang::tidy::FileFilter;
25 using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;
27 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
28 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
30 namespace llvm {
31 namespace yaml {
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;
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;
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);
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";
57 return "";
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);
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);
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;
81 std::vector<ClangTidyOptions::StringPair> Options;
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);
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);
113 IO.endMapping();
114 } else {
115 IO.setError("expected a sequence or map");
120 template <> struct MappingTraits<ClangTidyOptions> {
121 static void mapping(IO &IO, ClangTidyOptions &Options) {
122 bool Ignored = false;
123 IO.mapOptional("Checks", Options.Checks);
124 IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);
125 IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
126 IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility
127 IO.mapOptional("FormatStyle", Options.FormatStyle);
128 IO.mapOptional("User", Options.User);
129 IO.mapOptional("CheckOptions", Options.CheckOptions);
130 IO.mapOptional("ExtraArgs", Options.ExtraArgs);
131 IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);
132 IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
133 IO.mapOptional("UseColor", Options.UseColor);
137 } // namespace yaml
138 } // namespace llvm
140 namespace clang {
141 namespace tidy {
143 ClangTidyOptions ClangTidyOptions::getDefaults() {
144 ClangTidyOptions Options;
145 Options.Checks = "";
146 Options.WarningsAsErrors = "";
147 Options.HeaderFilterRegex = "";
148 Options.SystemHeaders = false;
149 Options.FormatStyle = "none";
150 Options.User = llvm::None;
151 for (const ClangTidyModuleRegistry::entry &Module :
152 ClangTidyModuleRegistry::entries())
153 Options.mergeWith(Module.instantiate()->getModuleOptions(), 0);
154 return Options;
157 template <typename T>
158 static void mergeVectors(Optional<T> &Dest, const Optional<T> &Src) {
159 if (Src) {
160 if (Dest)
161 Dest->insert(Dest->end(), Src->begin(), Src->end());
162 else
163 Dest = Src;
167 static void mergeCommaSeparatedLists(Optional<std::string> &Dest,
168 const Optional<std::string> &Src) {
169 if (Src)
170 Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
173 template <typename T>
174 static void overrideValue(Optional<T> &Dest, const Optional<T> &Src) {
175 if (Src)
176 Dest = Src;
179 ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
180 unsigned Order) {
181 mergeCommaSeparatedLists(Checks, Other.Checks);
182 mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors);
183 overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
184 overrideValue(SystemHeaders, Other.SystemHeaders);
185 overrideValue(FormatStyle, Other.FormatStyle);
186 overrideValue(User, Other.User);
187 overrideValue(UseColor, Other.UseColor);
188 mergeVectors(ExtraArgs, Other.ExtraArgs);
189 mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
191 for (const auto &KeyValue : Other.CheckOptions) {
192 CheckOptions.insert_or_assign(
193 KeyValue.getKey(),
194 ClangTidyValue(KeyValue.getValue().Value,
195 KeyValue.getValue().Priority + Order));
197 return *this;
200 ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,
201 unsigned Order) const {
202 ClangTidyOptions Result = *this;
203 Result.mergeWith(Other, Order);
204 return Result;
207 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
208 "clang-tidy binary";
209 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
210 "command-line option '-checks'";
211 const char
212 ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
213 "command-line option '-config'";
215 ClangTidyOptions
216 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {
217 ClangTidyOptions Result;
218 unsigned Priority = 0;
219 for (auto &Source : getRawOptions(FileName))
220 Result.mergeWith(Source.first, ++Priority);
221 return Result;
224 std::vector<OptionsSource>
225 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
226 std::vector<OptionsSource> Result;
227 Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
228 return Result;
231 ConfigOptionsProvider::ConfigOptionsProvider(
232 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
233 ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions,
234 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
235 : FileOptionsBaseProvider(std::move(GlobalOptions),
236 std::move(DefaultOptions),
237 std::move(OverrideOptions), std::move(FS)),
238 ConfigOptions(std::move(ConfigOptions)) {}
240 std::vector<OptionsSource>
241 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
242 std::vector<OptionsSource> RawOptions =
243 DefaultOptionsProvider::getRawOptions(FileName);
244 if (ConfigOptions.InheritParentConfig.value_or(false)) {
245 LLVM_DEBUG(llvm::dbgs()
246 << "Getting options for file " << FileName << "...\n");
247 assert(FS && "FS must be set.");
249 llvm::SmallString<128> AbsoluteFilePath(FileName);
251 if (!FS->makeAbsolute(AbsoluteFilePath)) {
252 addRawFileOptions(AbsoluteFilePath, RawOptions);
255 RawOptions.emplace_back(ConfigOptions,
256 OptionsSourceTypeConfigCommandLineOption);
257 RawOptions.emplace_back(OverrideOptions,
258 OptionsSourceTypeCheckCommandLineOption);
259 return RawOptions;
262 FileOptionsBaseProvider::FileOptionsBaseProvider(
263 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
264 ClangTidyOptions OverrideOptions,
265 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
266 : DefaultOptionsProvider(std::move(GlobalOptions),
267 std::move(DefaultOptions)),
268 OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {
269 if (!FS)
270 FS = llvm::vfs::getRealFileSystem();
271 ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
274 FileOptionsBaseProvider::FileOptionsBaseProvider(
275 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
276 ClangTidyOptions OverrideOptions,
277 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
278 : DefaultOptionsProvider(std::move(GlobalOptions),
279 std::move(DefaultOptions)),
280 OverrideOptions(std::move(OverrideOptions)),
281 ConfigHandlers(std::move(ConfigHandlers)) {}
283 void FileOptionsBaseProvider::addRawFileOptions(
284 llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
285 auto CurSize = CurOptions.size();
287 // Look for a suitable configuration file in all parent directories of the
288 // file. Start with the immediate parent directory and move up.
289 StringRef Path = llvm::sys::path::parent_path(AbsolutePath);
290 for (StringRef CurrentPath = Path; !CurrentPath.empty();
291 CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
292 llvm::Optional<OptionsSource> Result;
294 auto Iter = CachedOptions.find(CurrentPath);
295 if (Iter != CachedOptions.end())
296 Result = Iter->second;
298 if (!Result)
299 Result = tryReadConfigFile(CurrentPath);
301 if (Result) {
302 // Store cached value for all intermediate directories.
303 while (Path != CurrentPath) {
304 LLVM_DEBUG(llvm::dbgs()
305 << "Caching configuration for path " << Path << ".\n");
306 if (!CachedOptions.count(Path))
307 CachedOptions[Path] = *Result;
308 Path = llvm::sys::path::parent_path(Path);
310 CachedOptions[Path] = *Result;
312 CurOptions.push_back(*Result);
313 if (!Result->first.InheritParentConfig.value_or(false))
314 break;
317 // Reverse order of file configs because closer configs should have higher
318 // priority.
319 std::reverse(CurOptions.begin() + CurSize, CurOptions.end());
322 FileOptionsProvider::FileOptionsProvider(
323 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
324 ClangTidyOptions OverrideOptions,
325 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
326 : FileOptionsBaseProvider(std::move(GlobalOptions),
327 std::move(DefaultOptions),
328 std::move(OverrideOptions), std::move(VFS)) {}
330 FileOptionsProvider::FileOptionsProvider(
331 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
332 ClangTidyOptions OverrideOptions,
333 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
334 : FileOptionsBaseProvider(
335 std::move(GlobalOptions), std::move(DefaultOptions),
336 std::move(OverrideOptions), std::move(ConfigHandlers)) {}
338 // FIXME: This method has some common logic with clang::format::getStyle().
339 // Consider pulling out common bits to a findParentFileWithName function or
340 // similar.
341 std::vector<OptionsSource>
342 FileOptionsProvider::getRawOptions(StringRef FileName) {
343 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
344 << "...\n");
345 assert(FS && "FS must be set.");
347 llvm::SmallString<128> AbsoluteFilePath(FileName);
349 if (FS->makeAbsolute(AbsoluteFilePath))
350 return {};
352 std::vector<OptionsSource> RawOptions =
353 DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
354 addRawFileOptions(AbsoluteFilePath, RawOptions);
355 OptionsSource CommandLineOptions(OverrideOptions,
356 OptionsSourceTypeCheckCommandLineOption);
358 RawOptions.push_back(CommandLineOptions);
359 return RawOptions;
362 llvm::Optional<OptionsSource>
363 FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
364 assert(!Directory.empty());
366 llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);
368 if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
369 llvm::errs() << "Error reading configuration from " << Directory
370 << ": directory doesn't exist.\n";
371 return llvm::None;
374 for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
375 SmallString<128> ConfigFile(Directory);
376 llvm::sys::path::append(ConfigFile, ConfigHandler.first);
377 LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
379 llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
381 if (!FileStatus || !FileStatus->isRegularFile())
382 continue;
384 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
385 FS->getBufferForFile(ConfigFile);
386 if (std::error_code EC = Text.getError()) {
387 llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
388 << "\n";
389 continue;
392 // Skip empty files, e.g. files opened for writing via shell output
393 // redirection.
394 if ((*Text)->getBuffer().empty())
395 continue;
396 llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
397 ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});
398 if (!ParsedOptions) {
399 if (ParsedOptions.getError())
400 llvm::errs() << "Error parsing " << ConfigFile << ": "
401 << ParsedOptions.getError().message() << "\n";
402 continue;
404 return OptionsSource(*ParsedOptions, std::string(ConfigFile));
406 return llvm::None;
409 /// Parses -line-filter option and stores it to the \c Options.
410 std::error_code parseLineFilter(StringRef LineFilter,
411 clang::tidy::ClangTidyGlobalOptions &Options) {
412 llvm::yaml::Input Input(LineFilter);
413 Input >> Options.LineFilter;
414 return Input.error();
417 llvm::ErrorOr<ClangTidyOptions>
418 parseConfiguration(llvm::MemoryBufferRef Config) {
419 llvm::yaml::Input Input(Config);
420 ClangTidyOptions Options;
421 Input >> Options;
422 if (Input.error())
423 return Input.error();
424 return Options;
427 static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
428 (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
431 llvm::ErrorOr<ClangTidyOptions>
432 parseConfigurationWithDiags(llvm::MemoryBufferRef Config,
433 DiagCallback Handler) {
434 llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
435 &Handler);
436 ClangTidyOptions Options;
437 Input >> Options;
438 if (Input.error())
439 return Input.error();
440 return Options;
443 std::string configurationAsText(const ClangTidyOptions &Options) {
444 std::string Text;
445 llvm::raw_string_ostream Stream(Text);
446 llvm::yaml::Output Output(Stream);
447 // We use the same mapping method for input and output, so we need a non-const
448 // reference here.
449 ClangTidyOptions NonConstValue = Options;
450 Output << NonConstValue;
451 return Stream.str();
454 } // namespace tidy
455 } // namespace clang