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"
23 #define DEBUG_TYPE "clang-tidy-options"
25 using clang::tidy::ClangTidyOptions
;
26 using clang::tidy::FileFilter
;
27 using OptionsSource
= clang::tidy::ClangTidyOptionsProvider::OptionsSource
;
29 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter
)
30 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange
)
32 namespace llvm::yaml
{
34 // Map std::pair<int, int> to a JSON array of size 2.
35 template <> struct SequenceTraits
<FileFilter::LineRange
> {
36 static size_t size(IO
&IO
, FileFilter::LineRange
&Range
) {
37 return Range
.first
== 0 ? 0 : Range
.second
== 0 ? 1 : 2;
39 static unsigned &element(IO
&IO
, FileFilter::LineRange
&Range
, size_t Index
) {
41 IO
.setError("Too many elements in line range.");
42 return Index
== 0 ? Range
.first
: Range
.second
;
46 template <> struct MappingTraits
<FileFilter
> {
47 static void mapping(IO
&IO
, FileFilter
&File
) {
48 IO
.mapRequired("name", File
.Name
);
49 IO
.mapOptional("lines", File
.LineRanges
);
51 static std::string
validate(IO
&Io
, FileFilter
&File
) {
52 if (File
.Name
.empty())
53 return "No file name specified";
54 for (const FileFilter::LineRange
&Range
: File
.LineRanges
) {
55 if (Range
.first
<= 0 || Range
.second
<= 0)
56 return "Invalid line range";
62 template <> struct MappingTraits
<ClangTidyOptions::StringPair
> {
63 static void mapping(IO
&IO
, ClangTidyOptions::StringPair
&KeyValue
) {
64 IO
.mapRequired("key", KeyValue
.first
);
65 IO
.mapRequired("value", KeyValue
.second
);
71 NOptionMap(IO
&, const ClangTidyOptions::OptionMap
&OptionMap
) {
72 Options
.reserve(OptionMap
.size());
73 for (const auto &KeyValue
: OptionMap
)
74 Options
.emplace_back(std::string(KeyValue
.getKey()), KeyValue
.getValue().Value
);
76 ClangTidyOptions::OptionMap
denormalize(IO
&) {
77 ClangTidyOptions::OptionMap Map
;
78 for (const auto &KeyValue
: Options
)
79 Map
[KeyValue
.first
] = ClangTidyOptions::ClangTidyValue(KeyValue
.second
);
82 std::vector
<ClangTidyOptions::StringPair
> Options
;
86 void yamlize(IO
&IO
, ClangTidyOptions::OptionMap
&Val
, bool,
88 if (IO
.outputting()) {
89 // Ensure check options are sorted
90 std::vector
<std::pair
<StringRef
, StringRef
>> SortedOptions
;
91 SortedOptions
.reserve(Val
.size());
92 for (auto &Key
: Val
) {
93 SortedOptions
.emplace_back(Key
.getKey(), Key
.getValue().Value
);
95 std::sort(SortedOptions
.begin(), SortedOptions
.end());
98 // Only output as a map
99 for (auto &Option
: SortedOptions
) {
100 bool UseDefault
= false;
101 void *SaveInfo
= nullptr;
102 IO
.preflightKey(Option
.first
.data(), true, false, UseDefault
, SaveInfo
);
103 IO
.scalarString(Option
.second
, needsQuotes(Option
.second
));
104 IO
.postflightKey(SaveInfo
);
108 // We need custom logic here to support the old method of specifying check
109 // options using a list of maps containing key and value keys.
110 auto &I
= reinterpret_cast<Input
&>(IO
);
111 if (isa
<SequenceNode
>(I
.getCurrentNode())) {
112 MappingNormalization
<NOptionMap
, ClangTidyOptions::OptionMap
> NOpts(IO
,
115 yamlize(IO
, NOpts
->Options
, true, Ctx
);
116 } else if (isa
<MappingNode
>(I
.getCurrentNode())) {
118 for (StringRef Key
: IO
.keys()) {
119 IO
.mapRequired(Key
.data(), Val
[Key
].Value
);
123 IO
.setError("expected a sequence or map");
128 struct ChecksVariant
{
129 std::optional
<std::string
> AsString
;
130 std::optional
<std::vector
<std::string
>> AsVector
;
133 template <> void yamlize(IO
&IO
, ChecksVariant
&Val
, bool, EmptyContext
&Ctx
) {
134 if (!IO
.outputting()) {
135 // Special case for reading from YAML
136 // Must support reading from both a string or a list
137 auto &I
= reinterpret_cast<Input
&>(IO
);
138 if (isa
<ScalarNode
, BlockScalarNode
>(I
.getCurrentNode())) {
139 Val
.AsString
= std::string();
140 yamlize(IO
, *Val
.AsString
, true, Ctx
);
141 } else if (isa
<SequenceNode
>(I
.getCurrentNode())) {
142 Val
.AsVector
= std::vector
<std::string
>();
143 yamlize(IO
, *Val
.AsVector
, true, Ctx
);
145 IO
.setError("expected string or sequence");
150 static void mapChecks(IO
&IO
, std::optional
<std::string
> &Checks
) {
151 if (IO
.outputting()) {
152 // Output always a string
153 IO
.mapOptional("Checks", Checks
);
155 // Input as either a string or a list
156 ChecksVariant ChecksAsVariant
;
157 IO
.mapOptional("Checks", ChecksAsVariant
);
158 if (ChecksAsVariant
.AsString
)
159 Checks
= ChecksAsVariant
.AsString
;
160 else if (ChecksAsVariant
.AsVector
)
161 Checks
= llvm::join(*ChecksAsVariant
.AsVector
, ",");
165 template <> struct MappingTraits
<ClangTidyOptions
> {
166 static void mapping(IO
&IO
, ClangTidyOptions
&Options
) {
167 mapChecks(IO
, Options
.Checks
);
168 IO
.mapOptional("WarningsAsErrors", Options
.WarningsAsErrors
);
169 IO
.mapOptional("HeaderFileExtensions", Options
.HeaderFileExtensions
);
170 IO
.mapOptional("ImplementationFileExtensions",
171 Options
.ImplementationFileExtensions
);
172 IO
.mapOptional("HeaderFilterRegex", Options
.HeaderFilterRegex
);
173 IO
.mapOptional("FormatStyle", Options
.FormatStyle
);
174 IO
.mapOptional("User", Options
.User
);
175 IO
.mapOptional("CheckOptions", Options
.CheckOptions
);
176 IO
.mapOptional("ExtraArgs", Options
.ExtraArgs
);
177 IO
.mapOptional("ExtraArgsBefore", Options
.ExtraArgsBefore
);
178 IO
.mapOptional("InheritParentConfig", Options
.InheritParentConfig
);
179 IO
.mapOptional("UseColor", Options
.UseColor
);
180 IO
.mapOptional("SystemHeaders", Options
.SystemHeaders
);
184 } // namespace llvm::yaml
186 namespace clang::tidy
{
188 ClangTidyOptions
ClangTidyOptions::getDefaults() {
189 ClangTidyOptions Options
;
191 Options
.WarningsAsErrors
= "";
192 Options
.HeaderFileExtensions
= {"", "h", "hh", "hpp", "hxx"};
193 Options
.ImplementationFileExtensions
= {"c", "cc", "cpp", "cxx"};
194 Options
.HeaderFilterRegex
= "";
195 Options
.SystemHeaders
= false;
196 Options
.FormatStyle
= "none";
197 Options
.User
= std::nullopt
;
198 for (const ClangTidyModuleRegistry::entry
&Module
:
199 ClangTidyModuleRegistry::entries())
200 Options
.mergeWith(Module
.instantiate()->getModuleOptions(), 0);
204 template <typename T
>
205 static void mergeVectors(std::optional
<T
> &Dest
, const std::optional
<T
> &Src
) {
208 Dest
->insert(Dest
->end(), Src
->begin(), Src
->end());
214 static void mergeCommaSeparatedLists(std::optional
<std::string
> &Dest
,
215 const std::optional
<std::string
> &Src
) {
217 Dest
= (Dest
&& !Dest
->empty() ? *Dest
+ "," : "") + *Src
;
220 template <typename T
>
221 static void overrideValue(std::optional
<T
> &Dest
, const std::optional
<T
> &Src
) {
226 ClangTidyOptions
&ClangTidyOptions::mergeWith(const ClangTidyOptions
&Other
,
228 mergeCommaSeparatedLists(Checks
, Other
.Checks
);
229 mergeCommaSeparatedLists(WarningsAsErrors
, Other
.WarningsAsErrors
);
230 overrideValue(HeaderFileExtensions
, Other
.HeaderFileExtensions
);
231 overrideValue(ImplementationFileExtensions
,
232 Other
.ImplementationFileExtensions
);
233 overrideValue(HeaderFilterRegex
, Other
.HeaderFilterRegex
);
234 overrideValue(SystemHeaders
, Other
.SystemHeaders
);
235 overrideValue(FormatStyle
, Other
.FormatStyle
);
236 overrideValue(User
, Other
.User
);
237 overrideValue(UseColor
, Other
.UseColor
);
238 mergeVectors(ExtraArgs
, Other
.ExtraArgs
);
239 mergeVectors(ExtraArgsBefore
, Other
.ExtraArgsBefore
);
241 for (const auto &KeyValue
: Other
.CheckOptions
) {
242 CheckOptions
.insert_or_assign(
244 ClangTidyValue(KeyValue
.getValue().Value
,
245 KeyValue
.getValue().Priority
+ Order
));
250 ClangTidyOptions
ClangTidyOptions::merge(const ClangTidyOptions
&Other
,
251 unsigned Order
) const {
252 ClangTidyOptions Result
= *this;
253 Result
.mergeWith(Other
, Order
);
257 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary
[] =
259 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption
[] =
260 "command-line option '-checks'";
262 ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption
[] =
263 "command-line option '-config'";
266 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName
) {
267 ClangTidyOptions Result
;
268 unsigned Priority
= 0;
269 for (auto &Source
: getRawOptions(FileName
))
270 Result
.mergeWith(Source
.first
, ++Priority
);
274 std::vector
<OptionsSource
>
275 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName
) {
276 std::vector
<OptionsSource
> Result
;
277 Result
.emplace_back(DefaultOptions
, OptionsSourceTypeDefaultBinary
);
281 ConfigOptionsProvider::ConfigOptionsProvider(
282 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
283 ClangTidyOptions ConfigOptions
, ClangTidyOptions OverrideOptions
,
284 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> FS
)
285 : FileOptionsBaseProvider(std::move(GlobalOptions
),
286 std::move(DefaultOptions
),
287 std::move(OverrideOptions
), std::move(FS
)),
288 ConfigOptions(std::move(ConfigOptions
)) {}
290 std::vector
<OptionsSource
>
291 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName
) {
292 std::vector
<OptionsSource
> RawOptions
=
293 DefaultOptionsProvider::getRawOptions(FileName
);
294 if (ConfigOptions
.InheritParentConfig
.value_or(false)) {
295 LLVM_DEBUG(llvm::dbgs()
296 << "Getting options for file " << FileName
<< "...\n");
297 assert(FS
&& "FS must be set.");
299 llvm::SmallString
<128> AbsoluteFilePath(FileName
);
301 if (!FS
->makeAbsolute(AbsoluteFilePath
)) {
302 addRawFileOptions(AbsoluteFilePath
, RawOptions
);
305 RawOptions
.emplace_back(ConfigOptions
,
306 OptionsSourceTypeConfigCommandLineOption
);
307 RawOptions
.emplace_back(OverrideOptions
,
308 OptionsSourceTypeCheckCommandLineOption
);
312 FileOptionsBaseProvider::FileOptionsBaseProvider(
313 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
314 ClangTidyOptions OverrideOptions
,
315 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> VFS
)
316 : DefaultOptionsProvider(std::move(GlobalOptions
),
317 std::move(DefaultOptions
)),
318 OverrideOptions(std::move(OverrideOptions
)), FS(std::move(VFS
)) {
320 FS
= llvm::vfs::getRealFileSystem();
321 ConfigHandlers
.emplace_back(".clang-tidy", parseConfiguration
);
324 FileOptionsBaseProvider::FileOptionsBaseProvider(
325 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
326 ClangTidyOptions OverrideOptions
,
327 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers
)
328 : DefaultOptionsProvider(std::move(GlobalOptions
),
329 std::move(DefaultOptions
)),
330 OverrideOptions(std::move(OverrideOptions
)),
331 ConfigHandlers(std::move(ConfigHandlers
)) {}
333 void FileOptionsBaseProvider::addRawFileOptions(
334 llvm::StringRef AbsolutePath
, std::vector
<OptionsSource
> &CurOptions
) {
335 auto CurSize
= CurOptions
.size();
337 // Look for a suitable configuration file in all parent directories of the
338 // file. Start with the immediate parent directory and move up.
339 StringRef Path
= llvm::sys::path::parent_path(AbsolutePath
);
340 for (StringRef CurrentPath
= Path
; !CurrentPath
.empty();
341 CurrentPath
= llvm::sys::path::parent_path(CurrentPath
)) {
342 std::optional
<OptionsSource
> Result
;
344 auto Iter
= CachedOptions
.find(CurrentPath
);
345 if (Iter
!= CachedOptions
.end())
346 Result
= Iter
->second
;
349 Result
= tryReadConfigFile(CurrentPath
);
352 // Store cached value for all intermediate directories.
353 while (Path
!= CurrentPath
) {
354 LLVM_DEBUG(llvm::dbgs()
355 << "Caching configuration for path " << Path
<< ".\n");
356 if (!CachedOptions
.count(Path
))
357 CachedOptions
[Path
] = *Result
;
358 Path
= llvm::sys::path::parent_path(Path
);
360 CachedOptions
[Path
] = *Result
;
362 CurOptions
.push_back(*Result
);
363 if (!Result
->first
.InheritParentConfig
.value_or(false))
367 // Reverse order of file configs because closer configs should have higher
369 std::reverse(CurOptions
.begin() + CurSize
, CurOptions
.end());
372 FileOptionsProvider::FileOptionsProvider(
373 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
374 ClangTidyOptions OverrideOptions
,
375 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> VFS
)
376 : FileOptionsBaseProvider(std::move(GlobalOptions
),
377 std::move(DefaultOptions
),
378 std::move(OverrideOptions
), std::move(VFS
)) {}
380 FileOptionsProvider::FileOptionsProvider(
381 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
382 ClangTidyOptions OverrideOptions
,
383 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers
)
384 : FileOptionsBaseProvider(
385 std::move(GlobalOptions
), std::move(DefaultOptions
),
386 std::move(OverrideOptions
), std::move(ConfigHandlers
)) {}
388 // FIXME: This method has some common logic with clang::format::getStyle().
389 // Consider pulling out common bits to a findParentFileWithName function or
391 std::vector
<OptionsSource
>
392 FileOptionsProvider::getRawOptions(StringRef FileName
) {
393 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
395 assert(FS
&& "FS must be set.");
397 llvm::SmallString
<128> AbsoluteFilePath(FileName
);
399 if (FS
->makeAbsolute(AbsoluteFilePath
))
402 std::vector
<OptionsSource
> RawOptions
=
403 DefaultOptionsProvider::getRawOptions(AbsoluteFilePath
.str());
404 addRawFileOptions(AbsoluteFilePath
, RawOptions
);
405 OptionsSource
CommandLineOptions(OverrideOptions
,
406 OptionsSourceTypeCheckCommandLineOption
);
408 RawOptions
.push_back(CommandLineOptions
);
412 std::optional
<OptionsSource
>
413 FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory
) {
414 assert(!Directory
.empty());
416 llvm::ErrorOr
<llvm::vfs::Status
> DirectoryStatus
= FS
->status(Directory
);
418 if (!DirectoryStatus
|| !DirectoryStatus
->isDirectory()) {
419 llvm::errs() << "Error reading configuration from " << Directory
420 << ": directory doesn't exist.\n";
424 for (const ConfigFileHandler
&ConfigHandler
: ConfigHandlers
) {
425 SmallString
<128> ConfigFile(Directory
);
426 llvm::sys::path::append(ConfigFile
, ConfigHandler
.first
);
427 LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile
<< "...\n");
429 llvm::ErrorOr
<llvm::vfs::Status
> FileStatus
= FS
->status(ConfigFile
);
431 if (!FileStatus
|| !FileStatus
->isRegularFile())
434 llvm::ErrorOr
<std::unique_ptr
<llvm::MemoryBuffer
>> Text
=
435 FS
->getBufferForFile(ConfigFile
);
436 if (std::error_code EC
= Text
.getError()) {
437 llvm::errs() << "Can't read " << ConfigFile
<< ": " << EC
.message()
442 // Skip empty files, e.g. files opened for writing via shell output
444 if ((*Text
)->getBuffer().empty())
446 llvm::ErrorOr
<ClangTidyOptions
> ParsedOptions
=
447 ConfigHandler
.second({(*Text
)->getBuffer(), ConfigFile
});
448 if (!ParsedOptions
) {
449 if (ParsedOptions
.getError())
450 llvm::errs() << "Error parsing " << ConfigFile
<< ": "
451 << ParsedOptions
.getError().message() << "\n";
454 return OptionsSource(*ParsedOptions
, std::string(ConfigFile
));
459 /// Parses -line-filter option and stores it to the \c Options.
460 std::error_code
parseLineFilter(StringRef LineFilter
,
461 clang::tidy::ClangTidyGlobalOptions
&Options
) {
462 llvm::yaml::Input
Input(LineFilter
);
463 Input
>> Options
.LineFilter
;
464 return Input
.error();
467 llvm::ErrorOr
<ClangTidyOptions
>
468 parseConfiguration(llvm::MemoryBufferRef Config
) {
469 llvm::yaml::Input
Input(Config
);
470 ClangTidyOptions Options
;
473 return Input
.error();
477 static void diagHandlerImpl(const llvm::SMDiagnostic
&Diag
, void *Ctx
) {
478 (*reinterpret_cast<DiagCallback
*>(Ctx
))(Diag
);
481 llvm::ErrorOr
<ClangTidyOptions
>
482 parseConfigurationWithDiags(llvm::MemoryBufferRef Config
,
483 DiagCallback Handler
) {
484 llvm::yaml::Input
Input(Config
, nullptr, Handler
? diagHandlerImpl
: nullptr,
486 ClangTidyOptions Options
;
489 return Input
.error();
493 std::string
configurationAsText(const ClangTidyOptions
&Options
) {
495 llvm::raw_string_ostream
Stream(Text
);
496 llvm::yaml::Output
Output(Stream
);
497 // We use the same mapping method for input and output, so we need a non-const
499 ClangTidyOptions NonConstValue
= Options
;
500 Output
<< NonConstValue
;
504 } // namespace clang::tidy