blob: 33ce34ac3203fd9c87db7b32f30cbc0fc854126b [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"
13#include "llvm/Support/Debug.h"
14#include "llvm/Support/FileSystem.h"
15#include "llvm/Support/Path.h"
16#include "llvm/Support/raw_ostream.h"
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000017#include "llvm/Support/YAMLTraits.h"
Alexander Kornienkod53d2682014-09-04 14:23:36 +000018#include <utility>
19
20#define DEBUG_TYPE "clang-tidy-options"
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000021
Alexander Kornienkoa4695222014-06-05 13:31:45 +000022using clang::tidy::ClangTidyOptions;
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000023using clang::tidy::FileFilter;
24
25LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
26LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
27
28namespace llvm {
29namespace yaml {
30
31// Map std::pair<int, int> to a JSON array of size 2.
32template <> struct SequenceTraits<FileFilter::LineRange> {
33 static size_t size(IO &IO, FileFilter::LineRange &Range) {
34 return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2;
35 }
36 static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) {
37 if (Index > 1)
38 IO.setError("Too many elements in line range.");
39 return Index == 0 ? Range.first : Range.second;
40 }
41};
42
43template <> struct MappingTraits<FileFilter> {
44 static void mapping(IO &IO, FileFilter &File) {
45 IO.mapRequired("name", File.Name);
46 IO.mapOptional("lines", File.LineRanges);
47 }
48 static StringRef validate(IO &io, FileFilter &File) {
49 if (File.Name.empty())
50 return "No file name specified";
51 for (const FileFilter::LineRange &Range : File.LineRanges) {
52 if (Range.first <= 0 || Range.second <= 0)
53 return "Invalid line range";
54 }
55 return StringRef();
56 }
57};
58
Alexander Kornienkoa4695222014-06-05 13:31:45 +000059template <> struct MappingTraits<ClangTidyOptions> {
60 static void mapping(IO &IO, ClangTidyOptions &Options) {
61 IO.mapOptional("Checks", Options.Checks);
62 IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
63 IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors);
64 }
65};
66
Alexander Kornienkodad4acb2014-05-22 16:07:11 +000067} // namespace yaml
68} // namespace llvm
69
70namespace clang {
71namespace tidy {
72
Alexander Kornienkod53d2682014-09-04 14:23:36 +000073ClangTidyOptions
74ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const {
75 ClangTidyOptions Result = *this;
76
77 // Merge comma-separated glob lists by appending the new value after a comma.
78 if (Other.Checks)
79 Result.Checks =
80 (Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") +
81 *Other.Checks;
82
83 if (Other.HeaderFilterRegex)
84 Result.HeaderFilterRegex = Other.HeaderFilterRegex;
85 if (Other.AnalyzeTemporaryDtors)
86 Result.AnalyzeTemporaryDtors = Other.AnalyzeTemporaryDtors;
87 return Result;
88}
89
90FileOptionsProvider::FileOptionsProvider(
91 const ClangTidyGlobalOptions &GlobalOptions,
92 const ClangTidyOptions &FallbackOptions,
93 const ClangTidyOptions &OverrideOptions)
94 : DefaultOptionsProvider(GlobalOptions, FallbackOptions),
95 OverrideOptions(OverrideOptions) {
96 CachedOptions[""] = FallbackOptions.mergeWith(OverrideOptions);
97}
98
99static const char ConfigFileName[] = ".clang-tidy";
100
101// FIXME: This method has some common logic with clang::format::getStyle().
102// Consider pulling out common bits to a findParentFileWithName function or
103// similar.
104const ClangTidyOptions &FileOptionsProvider::getOptions(StringRef FileName) {
105 DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n");
106 SmallString<256> FilePath(FileName);
107
108 if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) {
109 llvm::errs() << "Can't make absolute path from " << FileName << ": "
110 << EC.message() << "\n";
111 // FIXME: Figure out what to do.
112 } else {
113 FileName = FilePath;
114 }
115
116 // Look for a suitable configuration file in all parent directories of the
117 // file. Start with the immediate parent directory and move up.
118 StringRef Path = llvm::sys::path::parent_path(FileName);
119 for (StringRef CurrentPath = Path;;
120 CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
121 llvm::ErrorOr<ClangTidyOptions> Result = std::error_code();
122
123 auto Iter = CachedOptions.find(CurrentPath);
124 if (Iter != CachedOptions.end())
125 Result = Iter->second;
126
127 if (!Result)
128 Result = TryReadConfigFile(CurrentPath);
129
130 if (Result) {
131 // Store cached value for all intermediate directories.
132 while (Path != CurrentPath) {
133 DEBUG(llvm::dbgs() << "Caching configuration for path " << Path
134 << ".\n");
135 CachedOptions.GetOrCreateValue(Path, *Result);
136 Path = llvm::sys::path::parent_path(Path);
137 }
138 return CachedOptions.GetOrCreateValue(Path, *Result).getValue();
139 }
140 if (Result.getError() != std::errc::no_such_file_or_directory) {
141 llvm::errs() << "Error reading " << ConfigFileName << " from " << Path
142 << ": " << Result.getError().message() << "\n";
143 }
144 }
145}
146
147llvm::ErrorOr<ClangTidyOptions>
148FileOptionsProvider::TryReadConfigFile(StringRef Directory) {
149 assert(!Directory.empty());
150
151 ClangTidyOptions Options = DefaultOptionsProvider::getOptions(Directory);
152 if (!llvm::sys::fs::is_directory(Directory))
153 return std::errc::not_a_directory;
154
155 SmallString<128> ConfigFile(Directory);
156 llvm::sys::path::append(ConfigFile, ".clang-tidy");
157 DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
158
159 bool IsFile = false;
160 // Ignore errors from is_regular_file: we only need to know if we can read
161 // the file or not.
162 llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
163
164 if (!IsFile)
165 return std::errc::no_such_file_or_directory;
166
167 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
168 llvm::MemoryBuffer::getFile(ConfigFile.c_str());
169 if (std::error_code EC = Text.getError())
170 return EC;
171 if (std::error_code EC = parseConfiguration((*Text)->getBuffer(), Options))
172 return EC;
173 return Options.mergeWith(OverrideOptions);
174}
175
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000176/// \brief Parses -line-filter option and stores it to the \c Options.
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000177std::error_code parseLineFilter(StringRef LineFilter,
Rafael Espindola15c57842014-06-12 13:32:11 +0000178 clang::tidy::ClangTidyGlobalOptions &Options) {
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000179 llvm::yaml::Input Input(LineFilter);
180 Input >> Options.LineFilter;
181 return Input.error();
182}
183
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000184std::error_code parseConfiguration(StringRef Config,
Rafael Espindola15c57842014-06-12 13:32:11 +0000185 clang::tidy::ClangTidyOptions &Options) {
Alexander Kornienkoa4695222014-06-05 13:31:45 +0000186 llvm::yaml::Input Input(Config);
187 Input >> Options;
188 return Input.error();
189}
190
Alexander Kornienkod53d2682014-09-04 14:23:36 +0000191std::string configurationAsText(const ClangTidyOptions &Options) {
192 std::string Text;
193 llvm::raw_string_ostream Stream(Text);
194 llvm::yaml::Output Output(Stream);
195 // We use the same mapping method for input and output, so we need a non-const
196 // reference here.
197 ClangTidyOptions NonConstValue = Options;
198 Output << NonConstValue;
199 return Stream.str();
200}
201
Alexander Kornienkodad4acb2014-05-22 16:07:11 +0000202} // namespace tidy
203} // namespace clang