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