llvm-project

Форк
0
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

25
using clang::tidy::ClangTidyOptions;
26
using clang::tidy::FileFilter;
27
using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;
28

29
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
30
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
31

32
namespace llvm::yaml {
33

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;
38
  }
39
  static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) {
40
    if (Index > 1)
41
      IO.setError("Too many elements in line range.");
42
    return Index == 0 ? Range.first : Range.second;
43
  }
44
};
45

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);
50
  }
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";
57
    }
58
    return "";
59
  }
60
};
61

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);
66
  }
67
};
68

69
struct NOptionMap {
70
  NOptionMap(IO &) {}
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);
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

85
template <>
86
void 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(Val.size());
92
    for (auto &Key : Val) {
93
      SortedOptions.emplace_back(Key.getKey(), Key.getValue().Value);
94
    }
95
    std::sort(SortedOptions.begin(), 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(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>(I.getCurrentNode())) {
112
      MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(IO,
113
                                                                          Val);
114
      EmptyContext Ctx;
115
      yamlize(IO, NOpts->Options, true, Ctx);
116
    } else if (isa<MappingNode>(I.getCurrentNode())) {
117
      IO.beginMapping();
118
      for (StringRef Key : IO.keys()) {
119
        IO.mapRequired(Key.data(), Val[Key].Value);
120
      }
121
      IO.endMapping();
122
    } else {
123
      IO.setError("expected a sequence or map");
124
    }
125
  }
126
}
127

128
struct ChecksVariant {
129
  std::optional<std::string> AsString;
130
  std::optional<std::vector<std::string>> AsVector;
131
};
132

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);
144
    } else {
145
      IO.setError("expected string or sequence");
146
    }
147
  }
148
}
149

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);
154
  } else {
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, ",");
162
  }
163
}
164

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);
183
  }
184
};
185

186
} // namespace llvm::yaml
187

188
namespace clang::tidy {
189

190
ClangTidyOptions 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 = 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);
204
  return Options;
205
}
206

207
template <typename T>
208
static 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

217
static 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

223
template <typename T>
224
static void overrideValue(std::optional<T> &Dest, const std::optional<T> &Src) {
225
  if (Src)
226
    Dest = Src;
227
}
228

229
ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
230
                                              unsigned Order) {
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);
244

245
  for (const auto &KeyValue : Other.CheckOptions) {
246
    CheckOptions.insert_or_assign(
247
        KeyValue.getKey(),
248
        ClangTidyValue(KeyValue.getValue().Value,
249
                       KeyValue.getValue().Priority + Order));
250
  }
251
  return *this;
252
}
253

254
ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,
255
                                         unsigned Order) const {
256
  ClangTidyOptions Result = *this;
257
  Result.mergeWith(Other, Order);
258
  return Result;
259
}
260

261
const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
262
    "clang-tidy binary";
263
const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
264
    "command-line option '-checks'";
265
const char
266
    ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
267
        "command-line option '-config'";
268

269
ClangTidyOptions
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);
275
  return Result;
276
}
277

278
std::vector<OptionsSource>
279
DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
280
  std::vector<OptionsSource> Result;
281
  Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
282
  return Result;
283
}
284

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)) {}
293

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.");
302

303
    llvm::SmallString<128> AbsoluteFilePath(FileName);
304

305
    if (!FS->makeAbsolute(AbsoluteFilePath)) {
306
      addRawFileOptions(AbsoluteFilePath, RawOptions);
307
    }
308
  }
309
  RawOptions.emplace_back(ConfigOptions,
310
                          OptionsSourceTypeConfigCommandLineOption);
311
  RawOptions.emplace_back(OverrideOptions,
312
                          OptionsSourceTypeCheckCommandLineOption);
313
  return RawOptions;
314
}
315

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)) {
323
  if (!FS)
324
    FS = llvm::vfs::getRealFileSystem();
325
  ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
326
}
327

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)) {}
336

337
void FileOptionsBaseProvider::addRawFileOptions(
338
    llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
339
  auto 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.
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;
347

348
    auto Iter = CachedOptions.find(CurrentPath);
349
    if (Iter != CachedOptions.end())
350
      Result = Iter->second;
351

352
    if (!Result)
353
      Result = tryReadConfigFile(CurrentPath);
354

355
    if (Result) {
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);
363
      }
364
      CachedOptions[Path] = *Result;
365

366
      CurOptions.push_back(*Result);
367
      if (!Result->first.InheritParentConfig.value_or(false))
368
        break;
369
    }
370
  }
371
  // Reverse order of file configs because closer configs should have higher
372
  // priority.
373
  std::reverse(CurOptions.begin() + CurSize, CurOptions.end());
374
}
375

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)) {}
383

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)) {}
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.
395
std::vector<OptionsSource>
396
FileOptionsProvider::getRawOptions(StringRef FileName) {
397
  LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
398
                          << "...\n");
399
  assert(FS && "FS must be set.");
400

401
  llvm::SmallString<128> AbsoluteFilePath(FileName);
402

403
  if (FS->makeAbsolute(AbsoluteFilePath))
404
    return {};
405

406
  std::vector<OptionsSource> RawOptions =
407
      DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
408
  addRawFileOptions(AbsoluteFilePath, RawOptions);
409
  OptionsSource CommandLineOptions(OverrideOptions,
410
                                   OptionsSourceTypeCheckCommandLineOption);
411

412
  RawOptions.push_back(CommandLineOptions);
413
  return RawOptions;
414
}
415

416
std::optional<OptionsSource>
417
FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
418
  assert(!Directory.empty());
419

420
  llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);
421

422
  if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
423
    llvm::errs() << "Error reading configuration from " << Directory
424
                 << ": directory doesn't exist.\n";
425
    return std::nullopt;
426
  }
427

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");
432

433
    llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
434

435
    if (!FileStatus || !FileStatus->isRegularFile())
436
      continue;
437

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()
442
                   << "\n";
443
      continue;
444
    }
445

446
    // Skip empty files, e.g. files opened for writing via shell output
447
    // redirection.
448
    if ((*Text)->getBuffer().empty())
449
      continue;
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";
456
      continue;
457
    }
458
    return OptionsSource(*ParsedOptions, std::string(ConfigFile));
459
  }
460
  return std::nullopt;
461
}
462

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();
469
}
470

471
llvm::ErrorOr<ClangTidyOptions>
472
parseConfiguration(llvm::MemoryBufferRef Config) {
473
  llvm::yaml::Input Input(Config);
474
  ClangTidyOptions Options;
475
  Input >> Options;
476
  if (Input.error())
477
    return Input.error();
478
  return Options;
479
}
480

481
static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
482
  (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
483
}
484

485
llvm::ErrorOr<ClangTidyOptions>
486
parseConfigurationWithDiags(llvm::MemoryBufferRef Config,
487
                            DiagCallback Handler) {
488
  llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
489
                          &Handler);
490
  ClangTidyOptions Options;
491
  Input >> Options;
492
  if (Input.error())
493
    return Input.error();
494
  return Options;
495
}
496

497
std::string configurationAsText(const ClangTidyOptions &Options) {
498
  std::string Text;
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
502
  // reference here.
503
  ClangTidyOptions NonConstValue = Options;
504
  Output << NonConstValue;
505
  return Stream.str();
506
}
507

508
} // namespace clang::tidy
509

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.