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