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/YAMLParser.h" |
14 | #include <optional> |
15 | |
16 | namespace clang::tidy { |
17 | |
18 | ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context) |
19 | : CheckName(CheckName), Context(Context), |
20 | Options(CheckName, Context->getOptions().CheckOptions, Context) { |
21 | assert(Context != nullptr); |
22 | assert(!CheckName.empty()); |
23 | } |
24 | |
25 | DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, |
26 | StringRef Description, |
27 | DiagnosticIDs::Level Level) { |
28 | return Context->diag(CheckName, Loc, Description, Level); |
29 | } |
30 | |
31 | DiagnosticBuilder ClangTidyCheck::diag(StringRef Description, |
32 | DiagnosticIDs::Level Level) { |
33 | return Context->diag(CheckName, Description, Level); |
34 | } |
35 | |
36 | DiagnosticBuilder |
37 | ClangTidyCheck::configurationDiag(StringRef Description, |
38 | DiagnosticIDs::Level Level) const { |
39 | return Context->configurationDiag(Message: Description, Level); |
40 | } |
41 | |
42 | void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) { |
43 | // For historical reasons, checks don't implement the MatchFinder run() |
44 | // callback directly. We keep the run()/check() distinction to avoid interface |
45 | // churn, and to allow us to add cross-cutting logic in the future. |
46 | check(Result); |
47 | } |
48 | |
49 | ClangTidyCheck::OptionsView::OptionsView( |
50 | StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions, |
51 | ClangTidyContext *Context) |
52 | : NamePrefix((CheckName + "." ).str()), CheckOptions(CheckOptions), |
53 | Context(Context) {} |
54 | |
55 | std::optional<StringRef> |
56 | ClangTidyCheck::OptionsView::get(StringRef LocalName) const { |
57 | if (Context->getOptionsCollector()) |
58 | Context->getOptionsCollector()->insert(key: (NamePrefix + LocalName).str()); |
59 | const auto &Iter = CheckOptions.find(Key: (NamePrefix + LocalName).str()); |
60 | if (Iter != CheckOptions.end()) |
61 | return StringRef(Iter->getValue().Value); |
62 | return std::nullopt; |
63 | } |
64 | |
65 | static ClangTidyOptions::OptionMap::const_iterator |
66 | findPriorityOption(const ClangTidyOptions::OptionMap &Options, |
67 | StringRef NamePrefix, StringRef LocalName, |
68 | llvm::StringSet<> *Collector) { |
69 | if (Collector) { |
70 | Collector->insert(key: (NamePrefix + LocalName).str()); |
71 | Collector->insert(key: LocalName); |
72 | } |
73 | auto IterLocal = Options.find(Key: (NamePrefix + LocalName).str()); |
74 | auto IterGlobal = Options.find(Key: LocalName); |
75 | if (IterLocal == Options.end()) |
76 | return IterGlobal; |
77 | if (IterGlobal == Options.end()) |
78 | return IterLocal; |
79 | if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority) |
80 | return IterLocal; |
81 | return IterGlobal; |
82 | } |
83 | |
84 | std::optional<StringRef> |
85 | ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { |
86 | auto Iter = findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, |
87 | Collector: Context->getOptionsCollector()); |
88 | if (Iter != CheckOptions.end()) |
89 | return StringRef(Iter->getValue().Value); |
90 | return std::nullopt; |
91 | } |
92 | |
93 | static std::optional<bool> getAsBool(StringRef Value, |
94 | const llvm::Twine &LookupName) { |
95 | |
96 | if (std::optional<bool> Parsed = llvm::yaml::parseBool(S: Value)) |
97 | return Parsed; |
98 | // To maintain backwards compatability, we support parsing numbers as |
99 | // booleans, even though its not supported in YAML. |
100 | long long Number = 0; |
101 | if (!Value.getAsInteger(Radix: 10, Result&: Number)) |
102 | return Number != 0; |
103 | return std::nullopt; |
104 | } |
105 | |
106 | template <> |
107 | std::optional<bool> |
108 | ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const { |
109 | if (std::optional<StringRef> ValueOr = get(LocalName)) { |
110 | if (auto Result = getAsBool(Value: *ValueOr, LookupName: NamePrefix + LocalName)) |
111 | return Result; |
112 | diagnoseBadBooleanOption(Lookup: NamePrefix + LocalName, Unparsed: *ValueOr); |
113 | } |
114 | return std::nullopt; |
115 | } |
116 | |
117 | template <> |
118 | std::optional<bool> |
119 | ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const { |
120 | auto Iter = findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, |
121 | Collector: Context->getOptionsCollector()); |
122 | if (Iter != CheckOptions.end()) { |
123 | if (auto Result = getAsBool(Value: Iter->getValue().Value, LookupName: Iter->getKey())) |
124 | return Result; |
125 | diagnoseBadBooleanOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value); |
126 | } |
127 | return std::nullopt; |
128 | } |
129 | |
130 | void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options, |
131 | StringRef LocalName, |
132 | StringRef Value) const { |
133 | Options[(NamePrefix + LocalName).str()] = Value; |
134 | } |
135 | |
136 | void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options, |
137 | StringRef LocalName, |
138 | int64_t Value) const { |
139 | store(Options, LocalName, Value: llvm::itostr(X: Value)); |
140 | } |
141 | |
142 | void ClangTidyCheck::OptionsView::storeUnsigned( |
143 | ClangTidyOptions::OptionMap &Options, StringRef LocalName, |
144 | uint64_t Value) const { |
145 | store(Options, LocalName, Value: llvm::utostr(X: Value)); |
146 | } |
147 | |
148 | template <> |
149 | void ClangTidyCheck::OptionsView::store<bool>( |
150 | ClangTidyOptions::OptionMap &Options, StringRef LocalName, |
151 | bool Value) const { |
152 | store(Options, LocalName, Value: Value ? StringRef("true" ) : StringRef("false" )); |
153 | } |
154 | |
155 | std::optional<int64_t> ClangTidyCheck::OptionsView::getEnumInt( |
156 | StringRef LocalName, ArrayRef<NameAndValue> Mapping, bool CheckGlobal, |
157 | bool IgnoreCase) const { |
158 | if (!CheckGlobal && Context->getOptionsCollector()) |
159 | Context->getOptionsCollector()->insert(key: (NamePrefix + LocalName).str()); |
160 | auto Iter = CheckGlobal |
161 | ? findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, |
162 | Collector: Context->getOptionsCollector()) |
163 | : CheckOptions.find(Key: (NamePrefix + LocalName).str()); |
164 | if (Iter == CheckOptions.end()) |
165 | return std::nullopt; |
166 | |
167 | StringRef Value = Iter->getValue().Value; |
168 | StringRef Closest; |
169 | unsigned EditDistance = 3; |
170 | for (const auto &NameAndEnum : Mapping) { |
171 | if (IgnoreCase) { |
172 | if (Value.equals_insensitive(RHS: NameAndEnum.second)) |
173 | return NameAndEnum.first; |
174 | } else if (Value.equals(RHS: NameAndEnum.second)) { |
175 | return NameAndEnum.first; |
176 | } else if (Value.equals_insensitive(RHS: NameAndEnum.second)) { |
177 | Closest = NameAndEnum.second; |
178 | EditDistance = 0; |
179 | continue; |
180 | } |
181 | unsigned Distance = |
182 | Value.edit_distance(Other: NameAndEnum.second, AllowReplacements: true, MaxEditDistance: EditDistance); |
183 | if (Distance < EditDistance) { |
184 | EditDistance = Distance; |
185 | Closest = NameAndEnum.second; |
186 | } |
187 | } |
188 | if (EditDistance < 3) |
189 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value, Suggestion: Closest); |
190 | else |
191 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value); |
192 | return std::nullopt; |
193 | } |
194 | |
195 | static constexpr llvm::StringLiteral ConfigWarning( |
196 | "invalid configuration value '%0' for option '%1'%select{|; expected a " |
197 | "bool|; expected an integer|; did you mean '%3'?}2" ); |
198 | |
199 | void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption( |
200 | const Twine &Lookup, StringRef Unparsed) const { |
201 | SmallString<64> Buffer; |
202 | Context->configurationDiag(Message: ConfigWarning) |
203 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 1; |
204 | } |
205 | |
206 | void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption( |
207 | const Twine &Lookup, StringRef Unparsed) const { |
208 | SmallString<64> Buffer; |
209 | Context->configurationDiag(Message: ConfigWarning) |
210 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 2; |
211 | } |
212 | |
213 | void ClangTidyCheck::OptionsView::diagnoseBadEnumOption( |
214 | const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const { |
215 | SmallString<64> Buffer; |
216 | auto Diag = Context->configurationDiag(Message: ConfigWarning) |
217 | << Unparsed << Lookup.toStringRef(Out&: Buffer); |
218 | if (Suggestion.empty()) |
219 | Diag << 0; |
220 | else |
221 | Diag << 3 << Suggestion; |
222 | } |
223 | |
224 | StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName, |
225 | StringRef Default) const { |
226 | return get(LocalName).value_or(u&: Default); |
227 | } |
228 | |
229 | StringRef |
230 | ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, |
231 | StringRef Default) const { |
232 | return getLocalOrGlobal(LocalName).value_or(u&: Default); |
233 | } |
234 | } // namespace clang::tidy |
235 | |