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
16namespace clang::tidy {
17
18ClangTidyCheck::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
25DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc,
26 StringRef Description,
27 DiagnosticIDs::Level Level) {
28 return Context->diag(CheckName, Loc, Description, Level);
29}
30
31DiagnosticBuilder ClangTidyCheck::diag(StringRef Description,
32 DiagnosticIDs::Level Level) {
33 return Context->diag(CheckName, Description, Level);
34}
35
36DiagnosticBuilder
37ClangTidyCheck::configurationDiag(StringRef Description,
38 DiagnosticIDs::Level Level) const {
39 return Context->configurationDiag(Message: Description, Level);
40}
41
42void 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
49ClangTidyCheck::OptionsView::OptionsView(
50 StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions,
51 ClangTidyContext *Context)
52 : NamePrefix((CheckName + ".").str()), CheckOptions(CheckOptions),
53 Context(Context) {}
54
55std::optional<StringRef>
56ClangTidyCheck::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
65static ClangTidyOptions::OptionMap::const_iterator
66findPriorityOption(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
84std::optional<StringRef>
85ClangTidyCheck::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
93static 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
106template <>
107std::optional<bool>
108ClangTidyCheck::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
117template <>
118std::optional<bool>
119ClangTidyCheck::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
130void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
131 StringRef LocalName,
132 StringRef Value) const {
133 Options[(NamePrefix + LocalName).str()] = Value;
134}
135
136void 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
142void 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
148template <>
149void 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
155std::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
195static 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
199void 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
206void 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
213void 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
224StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName,
225 StringRef Default) const {
226 return get(LocalName).value_or(u&: Default);
227}
228
229StringRef
230ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
231 StringRef Default) const {
232 return getLocalOrGlobal(LocalName).value_or(u&: Default);
233}
234} // namespace clang::tidy
235

source code of clang-tools-extra/clang-tidy/ClangTidyCheck.cpp