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"
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/ErrorOr.h"
15#include "llvm/Support/MemoryBufferRef.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Support/YAMLTraits.h"
18#include <algorithm>
19#include <optional>
20#include <utility>
21
22#define DEBUG_TYPE "clang-tidy-options"
23
24using clang::tidy::ClangTidyOptions;
25using clang::tidy::FileFilter;
26using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;
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(Key: "name", Val&: File.Name);
48 IO.mapOptional(Key: "lines", Val&: 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: "key", Val&: KeyValue.first);
64 IO.mapRequired(Key: "value", Val&: KeyValue.second);
65 }
66};
67
68struct NOptionMap {
69 NOptionMap(IO &) {}
70 NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) {
71 Options.reserve(n: OptionMap.size());
72 for (const auto &KeyValue : OptionMap)
73 Options.emplace_back(args: std::string(KeyValue.getKey()),
74 args: KeyValue.getValue().Value);
75 }
76 ClangTidyOptions::OptionMap denormalize(IO &) {
77 ClangTidyOptions::OptionMap Map;
78 for (const auto &KeyValue : Options)
79 Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second);
80 return Map;
81 }
82 std::vector<ClangTidyOptions::StringPair> Options;
83};
84
85template <>
86void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
87 EmptyContext &Ctx) {
88 if (IO.outputting()) {
89 // Ensure check options are sorted
90 std::vector<std::pair<StringRef, StringRef>> SortedOptions;
91 SortedOptions.reserve(n: Val.size());
92 for (auto &Key : Val) {
93 SortedOptions.emplace_back(args: Key.getKey(), args&: Key.getValue().Value);
94 }
95 std::sort(first: SortedOptions.begin(), last: SortedOptions.end());
96
97 IO.beginMapping();
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(S: Option.second));
104 IO.postflightKey(SaveInfo);
105 }
106 IO.endMapping();
107 } else {
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>(Val: I.getCurrentNode())) {
112 MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(IO,
113 Val);
114 EmptyContext Ctx;
115 yamlize(io&: IO, Seq&: NOpts->Options, true, Ctx);
116 } else if (isa<MappingNode>(Val: I.getCurrentNode())) {
117 IO.beginMapping();
118 for (StringRef Key : IO.keys()) {
119 IO.mapRequired(Key: Key.data(), Val&: Val[Key].Value);
120 }
121 IO.endMapping();
122 } else {
123 IO.setError("expected a sequence or map");
124 }
125 }
126}
127
128struct ChecksVariant {
129 std::optional<std::string> AsString;
130 std::optional<std::vector<std::string>> AsVector;
131};
132
133template <> 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>(Val: I.getCurrentNode())) {
139 Val.AsString = std::string();
140 yamlize(io&: IO, Val&: *Val.AsString, true, Ctx);
141 } else if (isa<SequenceNode>(Val: I.getCurrentNode())) {
142 Val.AsVector = std::vector<std::string>();
143 yamlize(io&: IO, Seq&: *Val.AsVector, true, Ctx);
144 } else {
145 IO.setError("expected string or sequence");
146 }
147 }
148}
149
150static void mapChecks(IO &IO, std::optional<std::string> &Checks) {
151 if (IO.outputting()) {
152 // Output always a string
153 IO.mapOptional(Key: "Checks", Val&: Checks);
154 } else {
155 // Input as either a string or a list
156 ChecksVariant ChecksAsVariant;
157 IO.mapOptional(Key: "Checks", Val&: ChecksAsVariant);
158 if (ChecksAsVariant.AsString)
159 Checks = ChecksAsVariant.AsString;
160 else if (ChecksAsVariant.AsVector)
161 Checks = llvm::join(R&: *ChecksAsVariant.AsVector, Separator: ",");
162 }
163}
164
165template <> struct MappingTraits<ClangTidyOptions> {
166 static void mapping(IO &IO, ClangTidyOptions &Options) {
167 mapChecks(IO, Checks&: Options.Checks);
168 IO.mapOptional(Key: "WarningsAsErrors", Val&: Options.WarningsAsErrors);
169 IO.mapOptional(Key: "HeaderFileExtensions", Val&: Options.HeaderFileExtensions);
170 IO.mapOptional(Key: "ImplementationFileExtensions",
171 Val&: Options.ImplementationFileExtensions);
172 IO.mapOptional(Key: "HeaderFilterRegex", Val&: Options.HeaderFilterRegex);
173 IO.mapOptional(Key: "ExcludeHeaderFilterRegex",
174 Val&: Options.ExcludeHeaderFilterRegex);
175 IO.mapOptional(Key: "FormatStyle", Val&: Options.FormatStyle);
176 IO.mapOptional(Key: "User", Val&: Options.User);
177 IO.mapOptional(Key: "CheckOptions", Val&: Options.CheckOptions);
178 IO.mapOptional(Key: "ExtraArgs", Val&: Options.ExtraArgs);
179 IO.mapOptional(Key: "ExtraArgsBefore", Val&: Options.ExtraArgsBefore);
180 IO.mapOptional(Key: "InheritParentConfig", Val&: Options.InheritParentConfig);
181 IO.mapOptional(Key: "UseColor", Val&: Options.UseColor);
182 IO.mapOptional(Key: "SystemHeaders", Val&: Options.SystemHeaders);
183 }
184};
185
186} // namespace llvm::yaml
187
188namespace clang::tidy {
189
190ClangTidyOptions ClangTidyOptions::getDefaults() {
191 ClangTidyOptions Options;
192 Options.Checks = "";
193 Options.WarningsAsErrors = "";
194 Options.HeaderFileExtensions = {"", "h", "hh", "hpp", "hxx"};
195 Options.ImplementationFileExtensions = {"c", "cc", "cpp", "cxx"};
196 Options.HeaderFilterRegex = "";
197 Options.ExcludeHeaderFilterRegex = "";
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(Other: Module.instantiate()->getModuleOptions(), Order: 0);
204 return Options;
205}
206
207template <typename T>
208static void mergeVectors(std::optional<T> &Dest, const std::optional<T> &Src) {
209 if (Src) {
210 if (Dest)
211 Dest->insert(Dest->end(), Src->begin(), Src->end());
212 else
213 Dest = Src;
214 }
215}
216
217static void mergeCommaSeparatedLists(std::optional<std::string> &Dest,
218 const std::optional<std::string> &Src) {
219 if (Src)
220 Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
221}
222
223template <typename T>
224static void overrideValue(std::optional<T> &Dest, const std::optional<T> &Src) {
225 if (Src)
226 Dest = Src;
227}
228
229ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
230 unsigned Order) {
231 mergeCommaSeparatedLists(Dest&: Checks, Src: Other.Checks);
232 mergeCommaSeparatedLists(Dest&: WarningsAsErrors, Src: Other.WarningsAsErrors);
233 overrideValue(Dest&: HeaderFileExtensions, Src: Other.HeaderFileExtensions);
234 overrideValue(Dest&: ImplementationFileExtensions,
235 Src: Other.ImplementationFileExtensions);
236 overrideValue(Dest&: HeaderFilterRegex, Src: Other.HeaderFilterRegex);
237 overrideValue(Dest&: ExcludeHeaderFilterRegex, Src: Other.ExcludeHeaderFilterRegex);
238 overrideValue(Dest&: SystemHeaders, Src: Other.SystemHeaders);
239 overrideValue(Dest&: FormatStyle, Src: Other.FormatStyle);
240 overrideValue(Dest&: User, Src: Other.User);
241 overrideValue(Dest&: UseColor, Src: Other.UseColor);
242 mergeVectors(Dest&: ExtraArgs, Src: Other.ExtraArgs);
243 mergeVectors(Dest&: ExtraArgsBefore, Src: Other.ExtraArgsBefore);
244
245 for (const auto &KeyValue : Other.CheckOptions) {
246 CheckOptions.insert_or_assign(
247 Key: KeyValue.getKey(),
248 Val: ClangTidyValue(KeyValue.getValue().Value,
249 KeyValue.getValue().Priority + Order));
250 }
251 return *this;
252}
253
254ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,
255 unsigned Order) const {
256 ClangTidyOptions Result = *this;
257 Result.mergeWith(Other, Order);
258 return Result;
259}
260
261const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
262 "clang-tidy binary";
263const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
264 "command-line option '-checks'";
265const char
266 ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
267 "command-line option '-config'";
268
269ClangTidyOptions
270ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {
271 ClangTidyOptions Result;
272 unsigned Priority = 0;
273 for (auto &Source : getRawOptions(FileName))
274 Result.mergeWith(Other: Source.first, Order: ++Priority);
275 return Result;
276}
277
278std::vector<OptionsSource>
279DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
280 std::vector<OptionsSource> Result;
281 Result.emplace_back(args&: DefaultOptions, args: OptionsSourceTypeDefaultBinary);
282 return Result;
283}
284
285ConfigOptionsProvider::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)) {}
293
294std::vector<OptionsSource>
295ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
296 std::vector<OptionsSource> RawOptions =
297 DefaultOptionsProvider::getRawOptions(FileName);
298 if (ConfigOptions.InheritParentConfig.value_or(u: false)) {
299 LLVM_DEBUG(llvm::dbgs()
300 << "Getting options for file " << FileName << "...\n");
301
302 llvm::ErrorOr<llvm::SmallString<128>> AbsoluteFilePath =
303 getNormalizedAbsolutePath(AbsolutePath: FileName);
304 if (AbsoluteFilePath) {
305 addRawFileOptions(AbsolutePath: AbsoluteFilePath->str(), CurOptions&: RawOptions);
306 }
307 }
308 RawOptions.emplace_back(args&: ConfigOptions,
309 args: OptionsSourceTypeConfigCommandLineOption);
310 RawOptions.emplace_back(args&: OverrideOptions,
311 args: OptionsSourceTypeCheckCommandLineOption);
312 return RawOptions;
313}
314
315FileOptionsBaseProvider::FileOptionsBaseProvider(
316 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
317 ClangTidyOptions OverrideOptions,
318 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
319 : DefaultOptionsProvider(std::move(GlobalOptions),
320 std::move(DefaultOptions)),
321 OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {
322 if (!FS)
323 FS = llvm::vfs::getRealFileSystem();
324 ConfigHandlers.emplace_back(args: ".clang-tidy", args&: parseConfiguration);
325}
326
327FileOptionsBaseProvider::FileOptionsBaseProvider(
328 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
329 ClangTidyOptions OverrideOptions,
330 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
331 : DefaultOptionsProvider(std::move(GlobalOptions),
332 std::move(DefaultOptions)),
333 OverrideOptions(std::move(OverrideOptions)),
334 ConfigHandlers(std::move(ConfigHandlers)) {}
335
336llvm::ErrorOr<llvm::SmallString<128>>
337FileOptionsBaseProvider::getNormalizedAbsolutePath(llvm::StringRef Path) {
338 assert(FS && "FS must be set.");
339 llvm::SmallString<128> NormalizedAbsolutePath = {Path};
340 std::error_code Err = FS->makeAbsolute(Path&: NormalizedAbsolutePath);
341 if (Err)
342 return Err;
343 llvm::sys::path::remove_dots(path&: NormalizedAbsolutePath, /*remove_dot_dot=*/true);
344 return NormalizedAbsolutePath;
345}
346
347void FileOptionsBaseProvider::addRawFileOptions(
348 llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
349 auto CurSize = CurOptions.size();
350 // Look for a suitable configuration file in all parent directories of the
351 // file. Start with the immediate parent directory and move up.
352 StringRef RootPath = llvm::sys::path::parent_path(path: AbsolutePath);
353 auto MemorizedConfigFile =
354 [this, &RootPath](StringRef CurrentPath) -> std::optional<OptionsSource> {
355 const auto Iter = CachedOptions.Memorized.find(Key: CurrentPath);
356 if (Iter != CachedOptions.Memorized.end())
357 return CachedOptions.Storage[Iter->second];
358 std::optional<OptionsSource> OptionsSource = tryReadConfigFile(Directory: CurrentPath);
359 if (OptionsSource) {
360 const size_t Index = CachedOptions.Storage.size();
361 CachedOptions.Storage.emplace_back(Args&: OptionsSource.value());
362 while (RootPath != CurrentPath) {
363 LLVM_DEBUG(llvm::dbgs()
364 << "Caching configuration for path " << RootPath << ".\n");
365 CachedOptions.Memorized[RootPath] = Index;
366 RootPath = llvm::sys::path::parent_path(path: RootPath);
367 }
368 CachedOptions.Memorized[CurrentPath] = Index;
369 RootPath = llvm::sys::path::parent_path(path: CurrentPath);
370 }
371 return OptionsSource;
372 };
373 for (StringRef CurrentPath = RootPath; !CurrentPath.empty();
374 CurrentPath = llvm::sys::path::parent_path(path: CurrentPath)) {
375 if (std::optional<OptionsSource> Result =
376 MemorizedConfigFile(CurrentPath)) {
377 CurOptions.emplace_back(args&: Result.value());
378 if (!Result->first.InheritParentConfig.value_or(u: false))
379 break;
380 }
381 }
382 // Reverse order of file configs because closer configs should have higher
383 // priority.
384 std::reverse(first: CurOptions.begin() + CurSize, last: CurOptions.end());
385}
386
387FileOptionsProvider::FileOptionsProvider(
388 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
389 ClangTidyOptions OverrideOptions,
390 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
391 : FileOptionsBaseProvider(std::move(GlobalOptions),
392 std::move(DefaultOptions),
393 std::move(OverrideOptions), std::move(VFS)) {}
394
395FileOptionsProvider::FileOptionsProvider(
396 ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
397 ClangTidyOptions OverrideOptions,
398 FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
399 : FileOptionsBaseProvider(
400 std::move(GlobalOptions), std::move(DefaultOptions),
401 std::move(OverrideOptions), std::move(ConfigHandlers)) {}
402
403// FIXME: This method has some common logic with clang::format::getStyle().
404// Consider pulling out common bits to a findParentFileWithName function or
405// similar.
406std::vector<OptionsSource>
407FileOptionsProvider::getRawOptions(StringRef FileName) {
408 LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
409 << "...\n");
410
411 llvm::ErrorOr<llvm::SmallString<128>> AbsoluteFilePath =
412 getNormalizedAbsolutePath(Path: FileName);
413 if (!AbsoluteFilePath)
414 return {};
415
416 std::vector<OptionsSource> RawOptions =
417 DefaultOptionsProvider::getRawOptions(FileName: AbsoluteFilePath->str());
418 addRawFileOptions(AbsolutePath: AbsoluteFilePath->str(), CurOptions&: RawOptions);
419 OptionsSource CommandLineOptions(OverrideOptions,
420 OptionsSourceTypeCheckCommandLineOption);
421
422 RawOptions.push_back(x: CommandLineOptions);
423 return RawOptions;
424}
425
426std::optional<OptionsSource>
427FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
428 assert(!Directory.empty());
429
430 llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Path: Directory);
431
432 if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
433 llvm::errs() << "Error reading configuration from " << Directory
434 << ": directory doesn't exist.\n";
435 return std::nullopt;
436 }
437
438 for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
439 SmallString<128> ConfigFile(Directory);
440 llvm::sys::path::append(path&: ConfigFile, a: ConfigHandler.first);
441 LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
442
443 llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(Path: ConfigFile);
444
445 if (!FileStatus || !FileStatus->isRegularFile())
446 continue;
447
448 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
449 FS->getBufferForFile(Name: ConfigFile);
450 if (std::error_code EC = Text.getError()) {
451 llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
452 << "\n";
453 continue;
454 }
455
456 // Skip empty files, e.g. files opened for writing via shell output
457 // redirection.
458 if ((*Text)->getBuffer().empty())
459 continue;
460 llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
461 ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});
462 if (!ParsedOptions) {
463 if (ParsedOptions.getError())
464 llvm::errs() << "Error parsing " << ConfigFile << ": "
465 << ParsedOptions.getError().message() << "\n";
466 continue;
467 }
468 return OptionsSource(*ParsedOptions, std::string(ConfigFile));
469 }
470 return std::nullopt;
471}
472
473/// Parses -line-filter option and stores it to the \c Options.
474std::error_code parseLineFilter(StringRef LineFilter,
475 clang::tidy::ClangTidyGlobalOptions &Options) {
476 llvm::yaml::Input Input(LineFilter);
477 Input >> Options.LineFilter;
478 return Input.error();
479}
480
481llvm::ErrorOr<ClangTidyOptions>
482parseConfiguration(llvm::MemoryBufferRef Config) {
483 llvm::yaml::Input Input(Config);
484 ClangTidyOptions Options;
485 Input >> Options;
486 if (Input.error())
487 return Input.error();
488 return Options;
489}
490
491static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
492 (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
493}
494
495llvm::ErrorOr<ClangTidyOptions>
496parseConfigurationWithDiags(llvm::MemoryBufferRef Config,
497 DiagCallback Handler) {
498 llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
499 &Handler);
500 ClangTidyOptions Options;
501 Input >> Options;
502 if (Input.error())
503 return Input.error();
504 return Options;
505}
506
507std::string configurationAsText(const ClangTidyOptions &Options) {
508 std::string Text;
509 llvm::raw_string_ostream Stream(Text);
510 llvm::yaml::Output Output(Stream);
511 // We use the same mapping method for input and output, so we need a non-const
512 // reference here.
513 ClangTidyOptions NonConstValue = Options;
514 Output << NonConstValue;
515 return Stream.str();
516}
517
518} // namespace clang::tidy
519

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/ClangTidyOptions.cpp