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 | template <> |
143 | void ClangTidyCheck::OptionsView::store<bool>( |
144 | ClangTidyOptions::OptionMap &Options, StringRef LocalName, |
145 | bool Value) const { |
146 | store(Options, LocalName, Value: Value ? StringRef("true" ) : StringRef("false" )); |
147 | } |
148 | |
149 | std::optional<int64_t> ClangTidyCheck::OptionsView::getEnumInt( |
150 | StringRef LocalName, ArrayRef<NameAndValue> Mapping, bool CheckGlobal, |
151 | bool IgnoreCase) const { |
152 | if (!CheckGlobal && Context->getOptionsCollector()) |
153 | Context->getOptionsCollector()->insert(key: (NamePrefix + LocalName).str()); |
154 | auto Iter = CheckGlobal |
155 | ? findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, |
156 | Collector: Context->getOptionsCollector()) |
157 | : CheckOptions.find(Key: (NamePrefix + LocalName).str()); |
158 | if (Iter == CheckOptions.end()) |
159 | return std::nullopt; |
160 | |
161 | StringRef Value = Iter->getValue().Value; |
162 | StringRef Closest; |
163 | unsigned EditDistance = 3; |
164 | for (const auto &NameAndEnum : Mapping) { |
165 | if (IgnoreCase) { |
166 | if (Value.equals_insensitive(RHS: NameAndEnum.second)) |
167 | return NameAndEnum.first; |
168 | } else if (Value.equals(RHS: NameAndEnum.second)) { |
169 | return NameAndEnum.first; |
170 | } else if (Value.equals_insensitive(RHS: NameAndEnum.second)) { |
171 | Closest = NameAndEnum.second; |
172 | EditDistance = 0; |
173 | continue; |
174 | } |
175 | unsigned Distance = |
176 | Value.edit_distance(Other: NameAndEnum.second, AllowReplacements: true, MaxEditDistance: EditDistance); |
177 | if (Distance < EditDistance) { |
178 | EditDistance = Distance; |
179 | Closest = NameAndEnum.second; |
180 | } |
181 | } |
182 | if (EditDistance < 3) |
183 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value, Suggestion: Closest); |
184 | else |
185 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value); |
186 | return std::nullopt; |
187 | } |
188 | |
189 | static constexpr llvm::StringLiteral ConfigWarning( |
190 | "invalid configuration value '%0' for option '%1'%select{|; expected a " |
191 | "bool|; expected an integer|; did you mean '%3'?}2" ); |
192 | |
193 | void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption( |
194 | const Twine &Lookup, StringRef Unparsed) const { |
195 | SmallString<64> Buffer; |
196 | Context->configurationDiag(Message: ConfigWarning) |
197 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 1; |
198 | } |
199 | |
200 | void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption( |
201 | const Twine &Lookup, StringRef Unparsed) const { |
202 | SmallString<64> Buffer; |
203 | Context->configurationDiag(Message: ConfigWarning) |
204 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 2; |
205 | } |
206 | |
207 | void ClangTidyCheck::OptionsView::diagnoseBadEnumOption( |
208 | const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const { |
209 | SmallString<64> Buffer; |
210 | auto Diag = Context->configurationDiag(Message: ConfigWarning) |
211 | << Unparsed << Lookup.toStringRef(Out&: Buffer); |
212 | if (Suggestion.empty()) |
213 | Diag << 0; |
214 | else |
215 | Diag << 3 << Suggestion; |
216 | } |
217 | |
218 | StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName, |
219 | StringRef Default) const { |
220 | return get(LocalName).value_or(u&: Default); |
221 | } |
222 | |
223 | StringRef |
224 | ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, |
225 | StringRef Default) const { |
226 | return getLocalOrGlobal(LocalName).value_or(u&: Default); |
227 | } |
228 | } // namespace clang::tidy |
229 | |