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("ExcludeHeaderFilterRegex",
174 Options
.ExcludeHeaderFilterRegex
);
175 IO
.mapOptional("FormatStyle", Options
.FormatStyle
);
176 IO
.mapOptional("User", Options
.User
);
177 IO
.mapOptional("CheckOptions", Options
.CheckOptions
);
178 IO
.mapOptional("ExtraArgs", Options
.ExtraArgs
);
179 IO
.mapOptional("ExtraArgsBefore", Options
.ExtraArgsBefore
);
180 IO
.mapOptional("InheritParentConfig", Options
.InheritParentConfig
);
181 IO
.mapOptional("UseColor", Options
.UseColor
);
182 IO
.mapOptional("SystemHeaders", Options
.SystemHeaders
);
186 } // namespace llvm::yaml
188 namespace clang::tidy
{
190 ClangTidyOptions
ClangTidyOptions::getDefaults() {
191 ClangTidyOptions Options
;
193 Options
.WarningsAsErrors
= "";
194 Options
.HeaderFileExtensions
= {"", "h", "hh", "hpp", "hxx"};
195 Options
.ImplementationFileExtensions
= {"c", "cc", "cpp", "cxx"};
196 Options
.HeaderFilterRegex
= std::nullopt
;
197 Options
.ExcludeHeaderFilterRegex
= std::nullopt
;
198 Options
.SystemHeaders
= false;
199 Options
.FormatStyle
= "none";
200 Options
.User
= std::nullopt
;
201 for (const ClangTidyModuleRegistry::entry
&Module
:
202 ClangTidyModuleRegistry::entries())
203 Options
.mergeWith(Module
.instantiate()->getModuleOptions(), 0);
207 template <typename T
>
208 static void mergeVectors(std::optional
<T
> &Dest
, const std::optional
<T
> &Src
) {
211 Dest
->insert(Dest
->end(), Src
->begin(), Src
->end());
217 static void mergeCommaSeparatedLists(std::optional
<std::string
> &Dest
,
218 const std::optional
<std::string
> &Src
) {
220 Dest
= (Dest
&& !Dest
->empty() ? *Dest
+ "," : "") + *Src
;
223 template <typename T
>
224 static void overrideValue(std::optional
<T
> &Dest
, const std::optional
<T
> &Src
) {
229 ClangTidyOptions
&ClangTidyOptions::mergeWith(const ClangTidyOptions
&Other
,
231 mergeCommaSeparatedLists(Checks
, Other
.Checks
);
232 mergeCommaSeparatedLists(WarningsAsErrors
, Other
.WarningsAsErrors
);
233 overrideValue(HeaderFileExtensions
, Other
.HeaderFileExtensions
);
234 overrideValue(ImplementationFileExtensions
,
235 Other
.ImplementationFileExtensions
);
236 overrideValue(HeaderFilterRegex
, Other
.HeaderFilterRegex
);
237 overrideValue(ExcludeHeaderFilterRegex
, Other
.ExcludeHeaderFilterRegex
);
238 overrideValue(SystemHeaders
, Other
.SystemHeaders
);
239 overrideValue(FormatStyle
, Other
.FormatStyle
);
240 overrideValue(User
, Other
.User
);
241 overrideValue(UseColor
, Other
.UseColor
);
242 mergeVectors(ExtraArgs
, Other
.ExtraArgs
);
243 mergeVectors(ExtraArgsBefore
, Other
.ExtraArgsBefore
);
245 for (const auto &KeyValue
: Other
.CheckOptions
) {
246 CheckOptions
.insert_or_assign(
248 ClangTidyValue(KeyValue
.getValue().Value
,
249 KeyValue
.getValue().Priority
+ Order
));
254 ClangTidyOptions
ClangTidyOptions::merge(const ClangTidyOptions
&Other
,
255 unsigned Order
) const {
256 ClangTidyOptions Result
= *this;
257 Result
.mergeWith(Other
, Order
);
261 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary
[] =
263 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption
[] =
264 "command-line option '-checks'";
266 ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption
[] =
267 "command-line option '-config'";
270 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName
) {
271 ClangTidyOptions Result
;
272 unsigned Priority
= 0;
273 for (auto &Source
: getRawOptions(FileName
))
274 Result
.mergeWith(Source
.first
, ++Priority
);
278 std::vector
<OptionsSource
>
279 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName
) {
280 std::vector
<OptionsSource
> Result
;
281 Result
.emplace_back(DefaultOptions
, OptionsSourceTypeDefaultBinary
);
285 ConfigOptionsProvider::ConfigOptionsProvider(
286 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
287 ClangTidyOptions ConfigOptions
, ClangTidyOptions OverrideOptions
,
288 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> FS
)
289 : FileOptionsBaseProvider(std::move(GlobalOptions
),
290 std::move(DefaultOptions
),
291 std::move(OverrideOptions
), std::move(FS
)),
292 ConfigOptions(std::move(ConfigOptions
)) {}
294 std::vector
<OptionsSource
>
295 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName
) {
296 std::vector
<OptionsSource
> RawOptions
=
297 DefaultOptionsProvider::getRawOptions(FileName
);
298 if (ConfigOptions
.InheritParentConfig
.value_or(false)) {
299 LLVM_DEBUG(llvm::dbgs()
300 << "Getting options for file " << FileName
<< "...\n");
301 assert(FS
&& "FS must be set.");
303 llvm::SmallString
<128> AbsoluteFilePath(FileName
);
305 if (!FS
->makeAbsolute(AbsoluteFilePath
)) {
306 addRawFileOptions(AbsoluteFilePath
, RawOptions
);
309 RawOptions
.emplace_back(ConfigOptions
,
310 OptionsSourceTypeConfigCommandLineOption
);
311 RawOptions
.emplace_back(OverrideOptions
,
312 OptionsSourceTypeCheckCommandLineOption
);
316 FileOptionsBaseProvider::FileOptionsBaseProvider(
317 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
318 ClangTidyOptions OverrideOptions
,
319 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> VFS
)
320 : DefaultOptionsProvider(std::move(GlobalOptions
),
321 std::move(DefaultOptions
)),
322 OverrideOptions(std::move(OverrideOptions
)), FS(std::move(VFS
)) {
324 FS
= llvm::vfs::getRealFileSystem();
325 ConfigHandlers
.emplace_back(".clang-tidy", parseConfiguration
);
328 FileOptionsBaseProvider::FileOptionsBaseProvider(
329 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
330 ClangTidyOptions OverrideOptions
,
331 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers
)
332 : DefaultOptionsProvider(std::move(GlobalOptions
),
333 std::move(DefaultOptions
)),
334 OverrideOptions(std::move(OverrideOptions
)),
335 ConfigHandlers(std::move(ConfigHandlers
)) {}
337 void FileOptionsBaseProvider::addRawFileOptions(
338 llvm::StringRef AbsolutePath
, std::vector
<OptionsSource
> &CurOptions
) {
339 auto CurSize
= CurOptions
.size();
341 // Look for a suitable configuration file in all parent directories of the
342 // file. Start with the immediate parent directory and move up.
343 StringRef Path
= llvm::sys::path::parent_path(AbsolutePath
);
344 for (StringRef CurrentPath
= Path
; !CurrentPath
.empty();
345 CurrentPath
= llvm::sys::path::parent_path(CurrentPath
)) {
346 std::optional
<OptionsSource
> Result
;
348 auto Iter
= CachedOptions
.find(CurrentPath
);
349 if (Iter
!= CachedOptions
.end())
350 Result
= Iter
->second
;
353 Result
= tryReadConfigFile(CurrentPath
);
356 // Store cached value for all intermediate directories.
357 while (Path
!= CurrentPath
) {
358 LLVM_DEBUG(llvm::dbgs()
359 << "Caching configuration for path " << Path
<< ".\n");
360 if (!CachedOptions
.count(Path
))
361 CachedOptions
[Path
] = *Result
;
362 Path
= llvm::sys::path::parent_path(Path
);
364 CachedOptions
[Path
] = *Result
;
366 CurOptions
.push_back(*Result
);
367 if (!Result
->first
.InheritParentConfig
.value_or(false))
371 // Reverse order of file configs because closer configs should have higher
373 std::reverse(CurOptions
.begin() + CurSize
, CurOptions
.end());
376 FileOptionsProvider::FileOptionsProvider(
377 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
378 ClangTidyOptions OverrideOptions
,
379 llvm::IntrusiveRefCntPtr
<llvm::vfs::FileSystem
> VFS
)
380 : FileOptionsBaseProvider(std::move(GlobalOptions
),
381 std::move(DefaultOptions
),
382 std::move(OverrideOptions
), std::move(VFS
)) {}
384 FileOptionsProvider::FileOptionsProvider(
385 ClangTidyGlobalOptions GlobalOptions
, ClangTidyOptions DefaultOptions
,
386 ClangTidyOptions OverrideOptions
,
387 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers
)
388 : FileOptionsBaseProvider(
389 std::move(GlobalOptions
), std::move(DefaultOptions
),
390 std::move(OverrideOptions
), std::move(ConfigHandlers
)) {}
392 // FIXME: This method has some common logic with clang::format::getStyle().
393 // Consider pulling out common bits to a findParentFileWithName function or
395 std::vector
<OptionsSource
>
396 FileOptionsProvider::getRawOptions(StringRef FileName
) {
397 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
399 assert(FS
&& "FS must be set.");
401 llvm::SmallString
<128> AbsoluteFilePath(FileName
);
403 if (FS
->makeAbsolute(AbsoluteFilePath
))
406 std::vector
<OptionsSource
> RawOptions
=
407 DefaultOptionsProvider::getRawOptions(AbsoluteFilePath
.str());
408 addRawFileOptions(AbsoluteFilePath
, RawOptions
);
409 OptionsSource
CommandLineOptions(OverrideOptions
,
410 OptionsSourceTypeCheckCommandLineOption
);
412 RawOptions
.push_back(CommandLineOptions
);
416 std::optional
<OptionsSource
>
417 FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory
) {
418 assert(!Directory
.empty());
420 llvm::ErrorOr
<llvm::vfs::Status
> DirectoryStatus
= FS
->status(Directory
);
422 if (!DirectoryStatus
|| !DirectoryStatus
->isDirectory()) {
423 llvm::errs() << "Error reading configuration from " << Directory
424 << ": directory doesn't exist.\n";
428 for (const ConfigFileHandler
&ConfigHandler
: ConfigHandlers
) {
429 SmallString
<128> ConfigFile(Directory
);
430 llvm::sys::path::append(ConfigFile
, ConfigHandler
.first
);
431 LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile
<< "...\n");
433 llvm::ErrorOr
<llvm::vfs::Status
> FileStatus
= FS
->status(ConfigFile
);
435 if (!FileStatus
|| !FileStatus
->isRegularFile())
438 llvm::ErrorOr
<std::unique_ptr
<llvm::MemoryBuffer
>> Text
=
439 FS
->getBufferForFile(ConfigFile
);
440 if (std::error_code EC
= Text
.getError()) {
441 llvm::errs() << "Can't read " << ConfigFile
<< ": " << EC
.message()
446 // Skip empty files, e.g. files opened for writing via shell output
448 if ((*Text
)->getBuffer().empty())
450 llvm::ErrorOr
<ClangTidyOptions
> ParsedOptions
=
451 ConfigHandler
.second({(*Text
)->getBuffer(), ConfigFile
});
452 if (!ParsedOptions
) {
453 if (ParsedOptions
.getError())
454 llvm::errs() << "Error parsing " << ConfigFile
<< ": "
455 << ParsedOptions
.getError().message() << "\n";
458 return OptionsSource(*ParsedOptions
, std::string(ConfigFile
));
463 /// Parses -line-filter option and stores it to the \c Options.
464 std::error_code
parseLineFilter(StringRef LineFilter
,
465 clang::tidy::ClangTidyGlobalOptions
&Options
) {
466 llvm::yaml::Input
Input(LineFilter
);
467 Input
>> Options
.LineFilter
;
468 return Input
.error();
471 llvm::ErrorOr
<ClangTidyOptions
>
472 parseConfiguration(llvm::MemoryBufferRef Config
) {
473 llvm::yaml::Input
Input(Config
);
474 ClangTidyOptions Options
;
477 return Input
.error();
481 static void diagHandlerImpl(const llvm::SMDiagnostic
&Diag
, void *Ctx
) {
482 (*reinterpret_cast<DiagCallback
*>(Ctx
))(Diag
);
485 llvm::ErrorOr
<ClangTidyOptions
>
486 parseConfigurationWithDiags(llvm::MemoryBufferRef Config
,
487 DiagCallback Handler
) {
488 llvm::yaml::Input
Input(Config
, nullptr, Handler
? diagHandlerImpl
: nullptr,
490 ClangTidyOptions Options
;
493 return Input
.error();
497 std::string
configurationAsText(const ClangTidyOptions
&Options
) {
499 llvm::raw_string_ostream
Stream(Text
);
500 llvm::yaml::Output
Output(Stream
);
501 // We use the same mapping method for input and output, so we need a non-const
503 ClangTidyOptions NonConstValue
= Options
;
504 Output
<< NonConstValue
;
508 } // namespace clang::tidy