1 //===--- ClangTidyCheck.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 "ClangTidyCheck.h"
10 #include "llvm/ADT/SmallString.h"
11 #include "llvm/ADT/StringRef.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/WithColor.h"
14 #include "llvm/Support/raw_ostream.h"
15
16 namespace clang {
17 namespace tidy {
18
19 char MissingOptionError::ID;
20 char UnparseableEnumOptionError::ID;
21 char UnparseableIntegerOptionError::ID;
22
message() const23 std::string MissingOptionError::message() const {
24 llvm::SmallString<128> Buffer({"option not found '", OptionName, "'"});
25 return std::string(Buffer);
26 }
27
message() const28 std::string UnparseableEnumOptionError::message() const {
29 llvm::SmallString<256> Buffer({"invalid configuration value '", LookupValue,
30 "' for option '", LookupName, "'"});
31 if (SuggestedValue)
32 Buffer.append({"; did you mean '", *SuggestedValue, "'?"});
33 return std::string(Buffer);
34 }
35
message() const36 std::string UnparseableIntegerOptionError::message() const {
37 llvm::SmallString<256> Buffer({"invalid configuration value '", LookupValue,
38 "' for option '", LookupName, "'; expected ",
39 (IsBoolean ? "a bool" : "an integer value")});
40 return std::string(Buffer);
41 }
42
ClangTidyCheck(StringRef CheckName,ClangTidyContext * Context)43 ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context)
44 : CheckName(CheckName), Context(Context),
45 Options(CheckName, Context->getOptions().CheckOptions, Context) {
46 assert(Context != nullptr);
47 assert(!CheckName.empty());
48 }
49
diag(SourceLocation Loc,StringRef Message,DiagnosticIDs::Level Level)50 DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message,
51 DiagnosticIDs::Level Level) {
52 return Context->diag(CheckName, Loc, Message, Level);
53 }
54
diag(StringRef Message,DiagnosticIDs::Level Level)55 DiagnosticBuilder ClangTidyCheck::diag(StringRef Message,
56 DiagnosticIDs::Level Level) {
57 return Context->diag(CheckName, Message, Level);
58 }
59
60 DiagnosticBuilder
configurationDiag(StringRef Description,DiagnosticIDs::Level Level)61 ClangTidyCheck::configurationDiag(StringRef Description,
62 DiagnosticIDs::Level Level) {
63 return Context->configurationDiag(Description, Level);
64 }
65
run(const ast_matchers::MatchFinder::MatchResult & Result)66 void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) {
67 // For historical reasons, checks don't implement the MatchFinder run()
68 // callback directly. We keep the run()/check() distinction to avoid interface
69 // churn, and to allow us to add cross-cutting logic in the future.
70 check(Result);
71 }
72
OptionsView(StringRef CheckName,const ClangTidyOptions::OptionMap & CheckOptions,ClangTidyContext * Context)73 ClangTidyCheck::OptionsView::OptionsView(
74 StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions,
75 ClangTidyContext *Context)
76 : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions),
77 Context(Context) {}
78
79 llvm::Expected<std::string>
get(StringRef LocalName) const80 ClangTidyCheck::OptionsView::get(StringRef LocalName) const {
81 const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str());
82 if (Iter != CheckOptions.end())
83 return Iter->getValue().Value;
84 return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
85 }
86
87 static ClangTidyOptions::OptionMap::const_iterator
findPriorityOption(const ClangTidyOptions::OptionMap & Options,StringRef NamePrefix,StringRef LocalName)88 findPriorityOption(const ClangTidyOptions::OptionMap &Options, StringRef NamePrefix,
89 StringRef LocalName) {
90 auto IterLocal = Options.find((NamePrefix + LocalName).str());
91 auto IterGlobal = Options.find(LocalName.str());
92 if (IterLocal == Options.end())
93 return IterGlobal;
94 if (IterGlobal == Options.end())
95 return IterLocal;
96 if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority)
97 return IterLocal;
98 return IterGlobal;
99 }
100
101 llvm::Expected<std::string>
getLocalOrGlobal(StringRef LocalName) const102 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {
103 auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName);
104 if (Iter != CheckOptions.end())
105 return Iter->getValue().Value;
106 return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
107 }
108
getAsBool(StringRef Value,const llvm::Twine & LookupName)109 static llvm::Expected<bool> getAsBool(StringRef Value,
110 const llvm::Twine &LookupName) {
111 if (Value == "true")
112 return true;
113 if (Value == "false")
114 return false;
115 bool Result;
116 if (!Value.getAsInteger(10, Result))
117 return Result;
118 return llvm::make_error<UnparseableIntegerOptionError>(LookupName.str(),
119 Value.str(), true);
120 }
121
122 template <>
123 llvm::Expected<bool>
get(StringRef LocalName) const124 ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {
125 llvm::Expected<std::string> ValueOr = get(LocalName);
126 if (ValueOr)
127 return getAsBool(*ValueOr, NamePrefix + LocalName);
128 return ValueOr.takeError();
129 }
130
131 template <>
get(StringRef LocalName,bool Default) const132 bool ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName,
133 bool Default) const {
134 llvm::Expected<bool> ValueOr = get<bool>(LocalName);
135 if (ValueOr)
136 return *ValueOr;
137 reportOptionParsingError(ValueOr.takeError());
138 return Default;
139 }
140
141 template <>
142 llvm::Expected<bool>
getLocalOrGlobal(StringRef LocalName) const143 ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {
144 auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName);
145 if (Iter != CheckOptions.end())
146 return getAsBool(Iter->getValue().Value, Iter->getKey());
147 return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
148 }
149
150 template <>
getLocalOrGlobal(StringRef LocalName,bool Default) const151 bool ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName,
152 bool Default) const {
153 llvm::Expected<bool> ValueOr = getLocalOrGlobal<bool>(LocalName);
154 if (ValueOr)
155 return *ValueOr;
156 reportOptionParsingError(ValueOr.takeError());
157 return Default;
158 }
159
store(ClangTidyOptions::OptionMap & Options,StringRef LocalName,StringRef Value) const160 void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
161 StringRef LocalName,
162 StringRef Value) const {
163 Options[NamePrefix + LocalName.str()] = Value;
164 }
165
storeInt(ClangTidyOptions::OptionMap & Options,StringRef LocalName,int64_t Value) const166 void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options,
167 StringRef LocalName,
168 int64_t Value) const {
169 store(Options, LocalName, llvm::itostr(Value));
170 }
171
172 template <>
store(ClangTidyOptions::OptionMap & Options,StringRef LocalName,bool Value) const173 void ClangTidyCheck::OptionsView::store<bool>(
174 ClangTidyOptions::OptionMap &Options, StringRef LocalName,
175 bool Value) const {
176 store(Options, LocalName, Value ? StringRef("true") : StringRef("false"));
177 }
178
getEnumInt(StringRef LocalName,ArrayRef<NameAndValue> Mapping,bool CheckGlobal,bool IgnoreCase) const179 llvm::Expected<int64_t> ClangTidyCheck::OptionsView::getEnumInt(
180 StringRef LocalName, ArrayRef<NameAndValue> Mapping, bool CheckGlobal,
181 bool IgnoreCase) const {
182 auto Iter = CheckGlobal
183 ? findPriorityOption(CheckOptions, NamePrefix, LocalName)
184 : CheckOptions.find((NamePrefix + LocalName).str());
185 if (Iter == CheckOptions.end())
186 return llvm::make_error<MissingOptionError>((NamePrefix + LocalName).str());
187
188 StringRef Value = Iter->getValue().Value;
189 StringRef Closest;
190 unsigned EditDistance = -1;
191 for (const auto &NameAndEnum : Mapping) {
192 if (IgnoreCase) {
193 if (Value.equals_lower(NameAndEnum.second))
194 return NameAndEnum.first;
195 } else if (Value.equals(NameAndEnum.second)) {
196 return NameAndEnum.first;
197 } else if (Value.equals_lower(NameAndEnum.second)) {
198 Closest = NameAndEnum.second;
199 EditDistance = 0;
200 continue;
201 }
202 unsigned Distance = Value.edit_distance(NameAndEnum.second);
203 if (Distance < EditDistance) {
204 EditDistance = Distance;
205 Closest = NameAndEnum.second;
206 }
207 }
208 if (EditDistance < 3)
209 return llvm::make_error<UnparseableEnumOptionError>(
210 Iter->getKey().str(), Iter->getValue().Value, Closest.str());
211 return llvm::make_error<UnparseableEnumOptionError>(Iter->getKey().str(),
212 Iter->getValue().Value);
213 }
214
reportOptionParsingError(llvm::Error && Err) const215 void ClangTidyCheck::OptionsView::reportOptionParsingError(
216 llvm::Error &&Err) const {
217 if (auto RemainingErrors =
218 llvm::handleErrors(std::move(Err), [](const MissingOptionError &) {}))
219 Context->configurationDiag(llvm::toString(std::move(RemainingErrors)));
220 }
221
222 template <>
getOptional(StringRef LocalName) const223 Optional<std::string> ClangTidyCheck::OptionsView::getOptional<std::string>(
224 StringRef LocalName) const {
225 if (auto ValueOr = get(LocalName))
226 return *ValueOr;
227 else
228 consumeError(ValueOr.takeError());
229 return llvm::None;
230 }
231
232 template <>
233 Optional<std::string>
getOptionalLocalOrGlobal(StringRef LocalName) const234 ClangTidyCheck::OptionsView::getOptionalLocalOrGlobal<std::string>(
235 StringRef LocalName) const {
236 if (auto ValueOr = getLocalOrGlobal(LocalName))
237 return *ValueOr;
238 else
239 consumeError(ValueOr.takeError());
240 return llvm::None;
241 }
242
243 } // namespace tidy
244 } // namespace clang
245