blob: a4bfe0488c1cc2d182f7d0b7dea52fd6e5ada3c5 [file] [log] [blame]
Alexander Kornienkodad4acb2014-05-22 16:07:11 +00001//===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "ClangTidyOptions.h"
Alexander Kornienkod53d2682014-09-04 14:23:36 +000011#include "clang/Basic/LLVM.h"
12#include "llvm/ADT/SmallString.h"
Hans Wennborgfece4c62014-09-04 22:41:03 +000013#include "llvm/Support/Errc.h"
Alexander Kornienkod53d2682014-09-04 14:23:36 +000014#include "llvm/Support/Debug.h"
15#include "llvm/Support/FileSystem.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Support/raw_ostream.h"
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000018#include "llvm/Support/YAMLTraits.h"
Alexander Kornienkod53d2682014-09-04 14:23:36 +000019#include <utility>
20
21#define DEBUG_TYPE "clang-tidy-options"
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000022
Alexander Kornienkoa4695222014-06-05 13:31:45 +000023using clang::tidy::ClangTidyOptions;
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000024using clang::tidy::FileFilter;
25
26LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
27LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
Benjamin Kramer39e40cc2014-09-23 14:46:55 +000028LLVM_YAML_IS_SEQUENCE_VECTOR(ClangTidyOptions::StringPair)
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000029
30namespace llvm {
31namespace 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("name", File.Name);
48 IO.mapOptional("lines", File.LineRanges);
49 }
50 static StringRef 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 StringRef();
58 }
59};
60
Alexander Kornienko6e0cbc82014-09-12 08:53:36 +000061template <> struct MappingTraits<ClangTidyOptions::StringPair> {
62 static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) {
63 IO.mapRequired("key", KeyValue.first);
64 IO.mapRequired("value", KeyValue.second);
65 }
66};
67
68struct NOptionMap {
69 NOptionMap(IO &) {}
70 NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap)
71 : Options(OptionMap.begin(), OptionMap.end()) {}
72 ClangTidyOptions::OptionMap denormalize(IO &) {
73 ClangTidyOptions::OptionMap Map;
74 for (const auto &KeyValue : Options)
75 Map[KeyValue.first] = KeyValue.second;
76 return Map;
77 }
78 std::vector<ClangTidyOptions::StringPair> Options;
79};
80
Alexander Kornienkoa4695222014-06-05 13:31:45 +000081template <> struct MappingTraits<ClangTidyOptions> {
82 static void mapping(IO &IO, ClangTidyOptions &Options) {
Alexander Kornienko6e0cbc82014-09-12 08:53:36 +000083 MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(
84 IO, Options.CheckOptions);
Alexander Kornienkoa4695222014-06-05 13:31:45 +000085 IO.mapOptional("Checks", Options.Checks);
86 IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
87 IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors);
Alexander Kornienkoe9951542014-09-24 18:36:03 +000088 IO.mapOptional("User", Options.User);
Alexander Kornienko6e0cbc82014-09-12 08:53:36 +000089 IO.mapOptional("CheckOptions", NOpts->Options);
Alexander Kornienkoa4695222014-06-05 13:31:45 +000090 }
91};
92
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000093} // namespace yaml
94} // namespace llvm
95
96namespace clang {
97namespace tidy {
98
Alexander Kornienkod53d2682014-09-04 14:23:36 +000099ClangTidyOptions
100ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const {
101 ClangTidyOptions Result = *this;
102
103 // Merge comma-separated glob lists by appending the new value after a comma.
104 if (Other.Checks)
105 Result.Checks =
106 (Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") +
107 *Other.Checks;
108
109 if (Other.HeaderFilterRegex)
110 Result.HeaderFilterRegex = Other.HeaderFilterRegex;
111 if (Other.AnalyzeTemporaryDtors)
112 Result.AnalyzeTemporaryDtors = Other.AnalyzeTemporaryDtors;
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000113 if (Other.User)
114 Result.User = Other.User;
Alexander Kornienko6e0cbc82014-09-12 08:53:36 +0000115
116 for (const auto &KeyValue : Other.CheckOptions)
117 Result.CheckOptions[KeyValue.first] = KeyValue.second;
118
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000119 return Result;
120}
121
122FileOptionsProvider::FileOptionsProvider(
123 const ClangTidyGlobalOptions &GlobalOptions,
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000124 const ClangTidyOptions &DefaultOptions,
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000125 const ClangTidyOptions &OverrideOptions)
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000126 : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000127 OverrideOptions(OverrideOptions) {
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000128 CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions);
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000129}
130
131static const char ConfigFileName[] = ".clang-tidy";
132
133// FIXME: This method has some common logic with clang::format::getStyle().
134// Consider pulling out common bits to a findParentFileWithName function or
135// similar.
136const ClangTidyOptions &FileOptionsProvider::getOptions(StringRef FileName) {
137 DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n");
138 SmallString<256> FilePath(FileName);
139
140 if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) {
141 llvm::errs() << "Can't make absolute path from " << FileName << ": "
142 << EC.message() << "\n";
143 // FIXME: Figure out what to do.
144 } else {
145 FileName = FilePath;
146 }
147
148 // Look for a suitable configuration file in all parent directories of the
149 // file. Start with the immediate parent directory and move up.
150 StringRef Path = llvm::sys::path::parent_path(FileName);
151 for (StringRef CurrentPath = Path;;
152 CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
153 llvm::ErrorOr<ClangTidyOptions> Result = std::error_code();
154
155 auto Iter = CachedOptions.find(CurrentPath);
156 if (Iter != CachedOptions.end())
157 Result = Iter->second;
158
159 if (!Result)
160 Result = TryReadConfigFile(CurrentPath);
161
162 if (Result) {
163 // Store cached value for all intermediate directories.
164 while (Path != CurrentPath) {
165 DEBUG(llvm::dbgs() << "Caching configuration for path " << Path
166 << ".\n");
167 CachedOptions.GetOrCreateValue(Path, *Result);
168 Path = llvm::sys::path::parent_path(Path);
169 }
170 return CachedOptions.GetOrCreateValue(Path, *Result).getValue();
171 }
Hans Wennborgfece4c62014-09-04 22:41:03 +0000172 if (Result.getError() != llvm::errc::no_such_file_or_directory) {
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000173 llvm::errs() << "Error reading " << ConfigFileName << " from " << Path
174 << ": " << Result.getError().message() << "\n";
175 }
176 }
177}
178
179llvm::ErrorOr<ClangTidyOptions>
180FileOptionsProvider::TryReadConfigFile(StringRef Directory) {
181 assert(!Directory.empty());
182
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000183 if (!llvm::sys::fs::is_directory(Directory))
Hans Wennborgfece4c62014-09-04 22:41:03 +0000184 return make_error_code(llvm::errc::not_a_directory);
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000185
186 SmallString<128> ConfigFile(Directory);
187 llvm::sys::path::append(ConfigFile, ".clang-tidy");
188 DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
189
190 bool IsFile = false;
191 // Ignore errors from is_regular_file: we only need to know if we can read
192 // the file or not.
193 llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
194
195 if (!IsFile)
Hans Wennborgfece4c62014-09-04 22:41:03 +0000196 return make_error_code(llvm::errc::no_such_file_or_directory);
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000197
198 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
199 llvm::MemoryBuffer::getFile(ConfigFile.c_str());
200 if (std::error_code EC = Text.getError())
201 return EC;
Alexander Kornienko6e0cbc82014-09-12 08:53:36 +0000202 // Skip empty files, e.g. files opened for writing via shell output
203 // redirection.
204 if ((*Text)->getBuffer().empty())
205 return make_error_code(llvm::errc::no_such_file_or_directory);
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000206 llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
207 parseConfiguration((*Text)->getBuffer());
208 if (ParsedOptions) {
209 ClangTidyOptions Defaults = DefaultOptionsProvider::getOptions(Directory);
210 // Only use checks from the config file.
211 Defaults.Checks = None;
212 return Defaults.mergeWith(*ParsedOptions).mergeWith(OverrideOptions);
213 }
214 return ParsedOptions.getError();
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000215}
216
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000217/// \brief Parses -line-filter option and stores it to the \c Options.
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000218std::error_code parseLineFilter(StringRef LineFilter,
Rafael Espindola15c57842014-06-12 13:32:11 +0000219 clang::tidy::ClangTidyGlobalOptions &Options) {
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000220 llvm::yaml::Input Input(LineFilter);
221 Input >> Options.LineFilter;
222 return Input.error();
223}
224
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000225llvm::ErrorOr<ClangTidyOptions> parseConfiguration(StringRef Config) {
Alexander Kornienkoa4695222014-06-05 13:31:45 +0000226 llvm::yaml::Input Input(Config);
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000227 ClangTidyOptions Options;
Alexander Kornienkoa4695222014-06-05 13:31:45 +0000228 Input >> Options;
Alexander Kornienkoe9951542014-09-24 18:36:03 +0000229 if (Input.error())
230 return Input.error();
231 return Options;
Alexander Kornienkoa4695222014-06-05 13:31:45 +0000232}
233
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000234std::string configurationAsText(const ClangTidyOptions &Options) {
235 std::string Text;
236 llvm::raw_string_ostream Stream(Text);
237 llvm::yaml::Output Output(Stream);
238 // We use the same mapping method for input and output, so we need a non-const
239 // reference here.
240 ClangTidyOptions NonConstValue = Options;
241 Output << NonConstValue;
242 return Stream.str();
243}
244
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000245} // namespace tidy
246} // namespace clang