1 //===--- ClangTidyCheck.cpp - clang-tidy ------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
16 namespace clang::tidy
{
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());
25 DiagnosticBuilder
ClangTidyCheck::diag(SourceLocation Loc
,
26 StringRef Description
,
27 DiagnosticIDs::Level Level
) {
28 return Context
->diag(CheckName
, Loc
, Description
, Level
);
31 DiagnosticBuilder
ClangTidyCheck::diag(StringRef Description
,
32 DiagnosticIDs::Level Level
) {
33 return Context
->diag(CheckName
, Description
, Level
);
37 ClangTidyCheck::configurationDiag(StringRef Description
,
38 DiagnosticIDs::Level Level
) const {
39 return Context
->configurationDiag(Description
, Level
);
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.
49 ClangTidyCheck::OptionsView::OptionsView(
50 StringRef CheckName
, const ClangTidyOptions::OptionMap
&CheckOptions
,
51 ClangTidyContext
*Context
)
52 : NamePrefix((CheckName
+ ".").str()), CheckOptions(CheckOptions
),
55 std::optional
<StringRef
>
56 ClangTidyCheck::OptionsView::get(StringRef LocalName
) const {
57 if (Context
->getOptionsCollector())
58 Context
->getOptionsCollector()->insert((NamePrefix
+ LocalName
).str());
59 const auto &Iter
= CheckOptions
.find((NamePrefix
+ LocalName
).str());
60 if (Iter
!= CheckOptions
.end())
61 return StringRef(Iter
->getValue().Value
);
65 static ClangTidyOptions::OptionMap::const_iterator
66 findPriorityOption(const ClangTidyOptions::OptionMap
&Options
,
67 StringRef NamePrefix
, StringRef LocalName
,
68 llvm::StringSet
<> *Collector
) {
70 Collector
->insert((NamePrefix
+ LocalName
).str());
71 Collector
->insert(LocalName
);
73 auto IterLocal
= Options
.find((NamePrefix
+ LocalName
).str());
74 auto IterGlobal
= Options
.find(LocalName
);
75 if (IterLocal
== Options
.end())
77 if (IterGlobal
== Options
.end())
79 if (IterLocal
->getValue().Priority
>= IterGlobal
->getValue().Priority
)
84 std::optional
<StringRef
>
85 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName
) const {
86 auto Iter
= findPriorityOption(CheckOptions
, NamePrefix
, LocalName
,
87 Context
->getOptionsCollector());
88 if (Iter
!= CheckOptions
.end())
89 return StringRef(Iter
->getValue().Value
);
93 static std::optional
<bool> getAsBool(StringRef Value
,
94 const llvm::Twine
&LookupName
) {
96 if (std::optional
<bool> Parsed
= llvm::yaml::parseBool(Value
))
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(10, Number
))
108 ClangTidyCheck::OptionsView::get
<bool>(StringRef LocalName
) const {
109 if (std::optional
<StringRef
> ValueOr
= get(LocalName
)) {
110 if (auto Result
= getAsBool(*ValueOr
, NamePrefix
+ LocalName
))
112 diagnoseBadBooleanOption(NamePrefix
+ LocalName
, *ValueOr
);
119 ClangTidyCheck::OptionsView::getLocalOrGlobal
<bool>(StringRef LocalName
) const {
120 auto Iter
= findPriorityOption(CheckOptions
, NamePrefix
, LocalName
,
121 Context
->getOptionsCollector());
122 if (Iter
!= CheckOptions
.end()) {
123 if (auto Result
= getAsBool(Iter
->getValue().Value
, Iter
->getKey()))
125 diagnoseBadBooleanOption(Iter
->getKey(), Iter
->getValue().Value
);
130 void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap
&Options
,
132 StringRef Value
) const {
133 Options
[(NamePrefix
+ LocalName
).str()] = Value
;
136 void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap
&Options
,
138 int64_t Value
) const {
139 store(Options
, LocalName
, llvm::itostr(Value
));
142 void ClangTidyCheck::OptionsView::storeUnsigned(
143 ClangTidyOptions::OptionMap
&Options
, StringRef LocalName
,
144 uint64_t Value
) const {
145 store(Options
, LocalName
, llvm::utostr(Value
));
149 void ClangTidyCheck::OptionsView::store
<bool>(
150 ClangTidyOptions::OptionMap
&Options
, StringRef LocalName
,
152 store(Options
, LocalName
, Value
? StringRef("true") : StringRef("false"));
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((NamePrefix
+ LocalName
).str());
160 auto Iter
= CheckGlobal
161 ? findPriorityOption(CheckOptions
, NamePrefix
, LocalName
,
162 Context
->getOptionsCollector())
163 : CheckOptions
.find((NamePrefix
+ LocalName
).str());
164 if (Iter
== CheckOptions
.end())
167 StringRef Value
= Iter
->getValue().Value
;
169 unsigned EditDistance
= 3;
170 for (const auto &NameAndEnum
: Mapping
) {
172 if (Value
.equals_insensitive(NameAndEnum
.second
))
173 return NameAndEnum
.first
;
174 } else if (Value
== NameAndEnum
.second
) {
175 return NameAndEnum
.first
;
176 } else if (Value
.equals_insensitive(NameAndEnum
.second
)) {
177 Closest
= NameAndEnum
.second
;
182 Value
.edit_distance(NameAndEnum
.second
, true, EditDistance
);
183 if (Distance
< EditDistance
) {
184 EditDistance
= Distance
;
185 Closest
= NameAndEnum
.second
;
188 if (EditDistance
< 3)
189 diagnoseBadEnumOption(Iter
->getKey(), Iter
->getValue().Value
, Closest
);
191 diagnoseBadEnumOption(Iter
->getKey(), Iter
->getValue().Value
);
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");
199 void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption(
200 const Twine
&Lookup
, StringRef Unparsed
) const {
201 SmallString
<64> Buffer
;
202 Context
->configurationDiag(ConfigWarning
)
203 << Unparsed
<< Lookup
.toStringRef(Buffer
) << 1;
206 void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption(
207 const Twine
&Lookup
, StringRef Unparsed
) const {
208 SmallString
<64> Buffer
;
209 Context
->configurationDiag(ConfigWarning
)
210 << Unparsed
<< Lookup
.toStringRef(Buffer
) << 2;
213 void ClangTidyCheck::OptionsView::diagnoseBadEnumOption(
214 const Twine
&Lookup
, StringRef Unparsed
, StringRef Suggestion
) const {
215 SmallString
<64> Buffer
;
216 auto Diag
= Context
->configurationDiag(ConfigWarning
)
217 << Unparsed
<< Lookup
.toStringRef(Buffer
);
218 if (Suggestion
.empty())
221 Diag
<< 3 << Suggestion
;
224 StringRef
ClangTidyCheck::OptionsView::get(StringRef LocalName
,
225 StringRef Default
) const {
226 return get(LocalName
).value_or(Default
);
230 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName
,
231 StringRef Default
) const {
232 return getLocalOrGlobal(LocalName
).value_or(Default
);
234 } // namespace clang::tidy