clang-tools 17.0.0git
ClangTidyOptions.cpp
Go to the documentation of this file.
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"
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 <optional>
20#include <utility>
21
22#define DEBUG_TYPE "clang-tidy-options"
23
27
28LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
29LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
30
31namespace llvm::yaml {
32
33// Map std::pair<int, int> to a JSON array of size 2.
34template <> 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;
37 }
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;
42 }
43};
44
45template <> struct MappingTraits<FileFilter> {
46 static void mapping(IO &IO, FileFilter &File) {
47 IO.mapRequired("name", File.Name);
48 IO.mapOptional("lines", File.LineRanges);
49 }
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";
56 }
57 return "";
58 }
59};
60
61template <> 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);
65 }
66};
67
68struct 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);
74 }
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;
80 }
81 std::vector<ClangTidyOptions::StringPair> Options;
82};
83
84template <>
85void 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);
97 }
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);
112 }
113 IO.endMapping();
114 } else {
115 IO.setError("expected a sequence or map");
116 }
117 }
118}
119
120template <> 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("HeaderFileExtensions", Options.HeaderFileExtensions);
126 IO.mapOptional("ImplementationFileExtensions",
128 IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
129 IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // deprecated
130 IO.mapOptional("FormatStyle", Options.FormatStyle);
131 IO.mapOptional("User", Options.User);
132 IO.mapOptional("CheckOptions", Options.CheckOptions);
133 IO.mapOptional("ExtraArgs", Options.ExtraArgs);
134 IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);
135 IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
136 IO.mapOptional("UseColor", Options.UseColor);
137 }
138};
139
140} // namespace llvm::yaml
141
142namespace clang::tidy {
143
145 ClangTidyOptions Options;
146 Options.Checks = "";
147 Options.WarningsAsErrors = "";
148 Options.HeaderFileExtensions = {"", "h", "hh", "hpp", "hxx"};
149 Options.ImplementationFileExtensions = {"c", "cc", "cpp", "cxx"};
150 Options.HeaderFilterRegex = "";
151 Options.SystemHeaders = false;
152 Options.FormatStyle = "none";
153 Options.User = std::nullopt;
154 for (const ClangTidyModuleRegistry::entry &Module :
155 ClangTidyModuleRegistry::entries())
156 Options.mergeWith(Module.instantiate()->getModuleOptions(), 0);
157 return Options;
158}
159
160template <typename T>
161static void mergeVectors(std::optional<T> &Dest, const std::optional<T> &Src) {
162 if (Src) {
163 if (Dest)
164 Dest->insert(Dest->end(), Src->begin(), Src->end());
165 else
166 Dest = Src;
167 }
168}
169
170static void mergeCommaSeparatedLists(std::optional<std::string> &Dest,
171 const std::optional<std::string> &Src) {
172 if (Src)
173 Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
174}
175
176template <typename T>
177static void overrideValue(std::optional<T> &Dest, const std::optional<T> &Src) {
178 if (Src)
179 Dest = Src;
180}
181
183 unsigned Order) {
192 overrideValue(User, Other.User);
196
197 for (const auto &KeyValue : Other.CheckOptions) {
198 CheckOptions.insert_or_assign(
199 KeyValue.getKey(),
200 ClangTidyValue(KeyValue.getValue().Value,
201 KeyValue.getValue().Priority + Order));
202 }
203 return *this;
204}
205
207 unsigned Order) const {
208 ClangTidyOptions Result = *this;
209 Result.mergeWith(Other, Order);
210 return Result;
211}
212
214 "clang-tidy binary";
216 "command-line option '-checks'";
217const char
219 "command-line option '-config'";
220
223 ClangTidyOptions Result;
224 unsigned Priority = 0;
225 for (auto &Source : getRawOptions(FileName))
226 Result.mergeWith(Source.first, ++Priority);
227 return Result;
228}
229
230std::vector<OptionsSource>
232 std::vector<OptionsSource> Result;
233 Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
234 return Result;
235}
236
238 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
239 ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions,
240 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
241 : FileOptionsBaseProvider(std::move(GlobalOptions),
242 std::move(DefaultOptions),
243 std::move(OverrideOptions), std::move(FS)),
244 ConfigOptions(std::move(ConfigOptions)) {}
245
246std::vector<OptionsSource>
248 std::vector<OptionsSource> RawOptions =
250 if (ConfigOptions.InheritParentConfig.value_or(false)) {
251 LLVM_DEBUG(llvm::dbgs()
252 << "Getting options for file " << FileName << "...\n");
253 assert(FS && "FS must be set.");
254
255 llvm::SmallString<128> AbsoluteFilePath(FileName);
256
257 if (!FS->makeAbsolute(AbsoluteFilePath)) {
258 addRawFileOptions(AbsoluteFilePath, RawOptions);
259 }
260 }
261 RawOptions.emplace_back(ConfigOptions,
263 RawOptions.emplace_back(OverrideOptions,
265 return RawOptions;
266}
267
269 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
270 ClangTidyOptions OverrideOptions,
271 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
272 : DefaultOptionsProvider(std::move(GlobalOptions),
273 std::move(DefaultOptions)),
274 OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {
275 if (!FS)
276 FS = llvm::vfs::getRealFileSystem();
277 ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
278}
279
281 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
282 ClangTidyOptions OverrideOptions,
284 : DefaultOptionsProvider(std::move(GlobalOptions),
285 std::move(DefaultOptions)),
286 OverrideOptions(std::move(OverrideOptions)),
287 ConfigHandlers(std::move(ConfigHandlers)) {}
288
290 llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
291 auto CurSize = CurOptions.size();
292
293 // Look for a suitable configuration file in all parent directories of the
294 // file. Start with the immediate parent directory and move up.
295 StringRef Path = llvm::sys::path::parent_path(AbsolutePath);
296 for (StringRef CurrentPath = Path; !CurrentPath.empty();
297 CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
298 std::optional<OptionsSource> Result;
299
300 auto Iter = CachedOptions.find(CurrentPath);
301 if (Iter != CachedOptions.end())
302 Result = Iter->second;
303
304 if (!Result)
305 Result = tryReadConfigFile(CurrentPath);
306
307 if (Result) {
308 // Store cached value for all intermediate directories.
309 while (Path != CurrentPath) {
310 LLVM_DEBUG(llvm::dbgs()
311 << "Caching configuration for path " << Path << ".\n");
312 if (!CachedOptions.count(Path))
313 CachedOptions[Path] = *Result;
314 Path = llvm::sys::path::parent_path(Path);
315 }
316 CachedOptions[Path] = *Result;
317
318 CurOptions.push_back(*Result);
319 if (!Result->first.InheritParentConfig.value_or(false))
320 break;
321 }
322 }
323 // Reverse order of file configs because closer configs should have higher
324 // priority.
325 std::reverse(CurOptions.begin() + CurSize, CurOptions.end());
326}
327
329 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
330 ClangTidyOptions OverrideOptions,
331 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
332 : FileOptionsBaseProvider(std::move(GlobalOptions),
333 std::move(DefaultOptions),
334 std::move(OverrideOptions), std::move(VFS)) {}
335
337 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
338 ClangTidyOptions OverrideOptions,
341 std::move(GlobalOptions), std::move(DefaultOptions),
342 std::move(OverrideOptions), std::move(ConfigHandlers)) {}
343
344// FIXME: This method has some common logic with clang::format::getStyle().
345// Consider pulling out common bits to a findParentFileWithName function or
346// similar.
347std::vector<OptionsSource>
349 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
350 << "...\n");
351 assert(FS && "FS must be set.");
352
353 llvm::SmallString<128> AbsoluteFilePath(FileName);
354
355 if (FS->makeAbsolute(AbsoluteFilePath))
356 return {};
357
358 std::vector<OptionsSource> RawOptions =
359 DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
360 addRawFileOptions(AbsoluteFilePath, RawOptions);
361 OptionsSource CommandLineOptions(OverrideOptions,
363
364 RawOptions.push_back(CommandLineOptions);
365 return RawOptions;
366}
367
368std::optional<OptionsSource>
370 assert(!Directory.empty());
371
372 llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);
373
374 if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
375 llvm::errs() << "Error reading configuration from " << Directory
376 << ": directory doesn't exist.\n";
377 return std::nullopt;
378 }
379
380 for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
381 SmallString<128> ConfigFile(Directory);
382 llvm::sys::path::append(ConfigFile, ConfigHandler.first);
383 LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
384
385 llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
386
387 if (!FileStatus || !FileStatus->isRegularFile())
388 continue;
389
390 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
391 FS->getBufferForFile(ConfigFile);
392 if (std::error_code EC = Text.getError()) {
393 llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
394 << "\n";
395 continue;
396 }
397
398 // Skip empty files, e.g. files opened for writing via shell output
399 // redirection.
400 if ((*Text)->getBuffer().empty())
401 continue;
402 llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
403 ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});
404 if (!ParsedOptions) {
405 if (ParsedOptions.getError())
406 llvm::errs() << "Error parsing " << ConfigFile << ": "
407 << ParsedOptions.getError().message() << "\n";
408 continue;
409 }
410 return OptionsSource(*ParsedOptions, std::string(ConfigFile));
411 }
412 return std::nullopt;
413}
414
415/// Parses -line-filter option and stores it to the \c Options.
416std::error_code parseLineFilter(StringRef LineFilter,
418 llvm::yaml::Input Input(LineFilter);
419 Input >> Options.LineFilter;
420 return Input.error();
421}
422
423llvm::ErrorOr<ClangTidyOptions>
424parseConfiguration(llvm::MemoryBufferRef Config) {
425 llvm::yaml::Input Input(Config);
426 ClangTidyOptions Options;
427 Input >> Options;
428 if (Input.error())
429 return Input.error();
430 return Options;
431}
432
433static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
434 (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
435}
436
437llvm::ErrorOr<ClangTidyOptions>
439 DiagCallback Handler) {
440 llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
441 &Handler);
442 ClangTidyOptions Options;
443 Input >> Options;
444 if (Input.error())
445 return Input.error();
446 return Options;
447}
448
449std::string configurationAsText(const ClangTidyOptions &Options) {
450 std::string Text;
451 llvm::raw_string_ostream Stream(Text);
452 llvm::yaml::Output Output(Stream);
453 // We use the same mapping method for input and output, so we need a non-const
454 // reference here.
455 ClangTidyOptions NonConstValue = Options;
456 Output << NonConstValue;
457 return Stream.str();
458}
459
460} // namespace clang::tidy
static cl::opt< std::string > ConfigFile("config-file", desc(R"( Specify the path of .clang-tidy or custom config file: e.g. --config-file=/some/path/myTidyConfigFile This option internally works exactly the same way as --config option after reading specified config file. Use either --config-file or --config, not both. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Config("config", desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks:' *', CheckOptions:{x:y}}" When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > LineFilter("line-filter", desc(R"( List of files with line ranges to filter the warnings. Can be used together with -header-filter. The format of the list is a JSON array of objects: [ {"name":"file1.cpp","lines":[[1,3],[5,7]]}, {"name":"file2.h"} ] )"), cl::init(""), cl::cat(ClangTidyCategory))
clang::tidy::ClangTidyOptionsProvider::OptionsSource OptionsSource
std::string Text
CharSourceRange Range
SourceRange for the file name.
StringRef FileName
std::vector< HeaderHandle > Path
llvm::StringRef Directory
llvm::StringRef Src
std::string Output
Definition: TraceTests.cpp:159
std::pair< ClangTidyOptions, std::string > OptionsSource
ClangTidyOptions and its source.
ClangTidyOptions getOptions(llvm::StringRef FileName)
Returns options applying to a specific translation unit with the specified FileName.
static const char OptionsSourceTypeCheckCommandLineOption[]
virtual std::vector< OptionsSource > getRawOptions(llvm::StringRef FileName)=0
Returns an ordered vector of OptionsSources, in order of increasing priority.
static const char OptionsSourceTypeConfigCommandLineOption[]
static const char OptionsSourceTypeDefaultBinary[]
ConfigOptionsProvider(ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS=nullptr)
std::vector< OptionsSource > getRawOptions(llvm::StringRef FileName) override
Returns an ordered vector of OptionsSources, in order of increasing priority.
Implementation of the ClangTidyOptionsProvider interface, which returns the same options for all file...
std::vector< OptionsSource > getRawOptions(llvm::StringRef FileName) override
Returns an ordered vector of OptionsSources, in order of increasing priority.
std::vector< ConfigFileHandler > ConfigFileHandlers
Configuration file handlers listed in the order of priority.
std::optional< OptionsSource > tryReadConfigFile(llvm::StringRef Directory)
Try to read configuration files from Directory using registered ConfigHandlers.
llvm::StringMap< OptionsSource > CachedOptions
std::pair< std::string, std::function< llvm::ErrorOr< ClangTidyOptions >(llvm::MemoryBufferRef)> > ConfigFileHandler
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS
FileOptionsBaseProvider(ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, ClangTidyOptions OverrideOptions, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS)
void addRawFileOptions(llvm::StringRef AbsolutePath, std::vector< OptionsSource > &CurOptions)
std::vector< OptionsSource > getRawOptions(llvm::StringRef FileName) override
Returns an ordered vector of OptionsSources, in order of increasing priority.
FileOptionsProvider(ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions, ClangTidyOptions OverrideOptions, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS=nullptr)
Initializes the FileOptionsProvider instance.
std::error_code parseLineFilter(StringRef LineFilter, clang::tidy::ClangTidyGlobalOptions &Options)
Parses -line-filter option and stores it to the Options.
static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx)
static void mergeVectors(std::optional< T > &Dest, const std::optional< T > &Src)
llvm::function_ref< void(const llvm::SMDiagnostic &)> DiagCallback
llvm::ErrorOr< ClangTidyOptions > parseConfigurationWithDiags(llvm::MemoryBufferRef Config, DiagCallback Handler)
static void mergeCommaSeparatedLists(std::optional< std::string > &Dest, const std::optional< std::string > &Src)
std::string configurationAsText(const ClangTidyOptions &Options)
Serializes configuration to a YAML-encoded string.
llvm::ErrorOr< ClangTidyOptions > parseConfiguration(llvm::MemoryBufferRef Config)
Parses configuration from JSON and returns ClangTidyOptions or an error.
static void overrideValue(std::optional< T > &Dest, const std::optional< T > &Src)
void yamlize(IO &IO, ClangTidyOptions::OptionMap &Options, bool, EmptyContext &Ctx)
std::vector< FileFilter > LineFilter
Output warnings from certain line ranges of certain files only.
Helper structure for storing option value with priority of the value.
Contains options for clang-tidy.
OptionMap CheckOptions
Key-value mapping used to store check-specific options.
ClangTidyOptions merge(const ClangTidyOptions &Other, unsigned Order) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
std::optional< bool > InheritParentConfig
Only used in the FileOptionsProvider and ConfigOptionsProvider.
std::optional< std::string > HeaderFilterRegex
Output warnings from headers matching this filter.
std::optional< std::string > Checks
Checks filter.
std::optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
std::optional< std::vector< std::string > > ImplementationFileExtensions
File extensions to consider to determine if a given diagnostic is located is located in an implementa...
ClangTidyOptions & mergeWith(const ClangTidyOptions &Other, unsigned Order)
Overwrites all fields in here by the fields of Other that have a value.
std::optional< std::string > User
Specifies the name or e-mail of the user running clang-tidy.
std::optional< std::vector< std::string > > HeaderFileExtensions
File extensions to consider to determine if a given diagnostic is located in a header file.
std::optional< bool > UseColor
Use colors in diagnostics. If missing, it will be auto detected.
std::optional< bool > SystemHeaders
Output warnings from system headers matching HeaderFilterRegex.
static ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.
std::optional< ArgList > ExtraArgsBefore
Add extra compilation arguments to the start of the list.
std::optional< std::string > FormatStyle
Format code around applied fixes with clang-format using this style.
std::optional< ArgList > ExtraArgs
Add extra compilation arguments to the end of the list.
Contains a list of line ranges in a single file.
std::pair< unsigned, unsigned > LineRange
LineRange is a pair<start, end> (inclusive).
static void mapping(IO &IO, ClangTidyOptions &Options)
static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue)
static std::string validate(IO &Io, FileFilter &File)
static void mapping(IO &IO, FileFilter &File)
ClangTidyOptions::OptionMap denormalize(IO &)
NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap)
std::vector< ClangTidyOptions::StringPair > Options
static unsigned & element(IO &IO, FileFilter::LineRange &Range, size_t Index)
static size_t size(IO &IO, FileFilter::LineRange &Range)