ClangTidyOptions.cpp 16 KB

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