1 //===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===//
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
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"
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
)
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
) {
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";
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
);
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
);
81 std::vector
<ClangTidyOptions::StringPair
> Options
;
85 void yamlize(IO
&IO
, ClangTidyOptions::OptionMap
&Options
, bool,
87 if (IO
.outputting()) {
89 // Only output as a map
90 for (auto &Key
: Options
) {
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
);
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(
107 yamlize(IO
, NOpts
->Options
, true, Ctx
);
108 } else if (isa
<MappingNode
>(I
.getCurrentNode())) {
110 for (StringRef Key
: IO
.keys()) {
111 IO
.mapRequired(Key
.data(), Options
[Key
].Value
);
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
);
143 ClangTidyOptions
ClangTidyOptions::getDefaults() {
144 ClangTidyOptions Options
;
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);
157 template <typename T
>
158 static void mergeVectors(Optional
<T
> &Dest
, const Optional
<T
> &Src
) {
161 Dest
->insert(Dest
->end(), Src
->begin(), Src
->end());
167 static void mergeCommaSeparatedLists(Optional
<std::string
> &Dest
,
168 const Optional
<std::string
> &Src
) {
170 Dest
= (Dest
&& !Dest
->empty() ? *Dest
+ "," : "") + *Src
;
173 template <typename T
>
174 static void overrideValue(Optional
<T
> &Dest
, const Optional
<T
> &Src
) {
179 ClangTidyOptions
&ClangTidyOptions::mergeWith(const ClangTidyOptions
&Other
,
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(
194 ClangTidyValue(KeyValue
.getValue().Value
,
195 KeyValue
.getValue().Priority
+ Order
));
200 ClangTidyOptions
ClangTidyOptions::merge(const ClangTidyOptions
&Other
,
201 unsigned Order
) const {
202 ClangTidyOptions Result
= *this;
203 Result
.mergeWith(Other
, Order
);
207 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary
[] =
209 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption
[] =
210 "command-line option '-checks'";
212 ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption
[] =
213 "command-line option '-config'";
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
);
224 std::vector
<OptionsSource
>
225 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName
) {
226 std::vector
<OptionsSource
> Result
;
227 Result
.emplace_back(DefaultOptions
, OptionsSourceTypeDefaultBinary
);
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
);
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
)) {
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
;
299 Result
= tryReadConfigFile(CurrentPath
);
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))
317 // Reverse order of file configs because closer configs should have higher
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
341 std::vector
<OptionsSource
>
342 FileOptionsProvider::getRawOptions(StringRef FileName
) {
343 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
345 assert(FS
&& "FS must be set.");
347 llvm::SmallString
<128> AbsoluteFilePath(FileName
);
349 if (FS
->makeAbsolute(AbsoluteFilePath
))
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
);
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";
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())
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()
392 // Skip empty files, e.g. files opened for writing via shell output
394 if ((*Text
)->getBuffer().empty())
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";
404 return OptionsSource(*ParsedOptions
, std::string(ConfigFile
));
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
;
423 return Input
.error();
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,
436 ClangTidyOptions Options
;
439 return Input
.error();
443 std::string
configurationAsText(const ClangTidyOptions
&Options
) {
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
449 ClangTidyOptions NonConstValue
= Options
;
450 Output
<< NonConstValue
;