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, StringRef Message,
26 DiagnosticIDs::Level Level) {
27 return Context->diag(CheckName, Loc, Message, Level);
28}
29
30DiagnosticBuilder ClangTidyCheck::diag(StringRef Message,
31 DiagnosticIDs::Level Level) {
32 return Context->diag(CheckName, Message, Level);
33}
34
35DiagnosticBuilder
36ClangTidyCheck::configurationDiag(StringRef Description,
37 DiagnosticIDs::Level Level) const {
38 return Context->configurationDiag(Description, Level);
39}
40
41void 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
48ClangTidyCheck::OptionsView::OptionsView(
49 StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions,
50 ClangTidyContext *Context)
51 : NamePrefix((CheckName + ".").str()), CheckOptions(CheckOptions),
52 Context(Context) {}
53
54std::optional<StringRef>
55ClangTidyCheck::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
64static ClangTidyOptions::OptionMap::const_iterator
65findPriorityOption(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
83std::optional<StringRef>
84ClangTidyCheck::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
92static 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
105template <>
106std::optional<bool>
107ClangTidyCheck::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
116template <>
117std::optional<bool>
118ClangTidyCheck::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
129void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
130 StringRef LocalName,
131 StringRef Value) const {
132 Options[(NamePrefix + LocalName).str()] = Value;
133}
134
135void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options,
136 StringRef LocalName,
137 int64_t Value) const {
138 store(Options, LocalName, llvm::itostr(Value));
139}
140
141template <>
142void 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
148std::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
188static 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
192void 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
199void 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
206void 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
217StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName,
218 StringRef Default) const {
219 return get(LocalName).value_or(Default);
220}
221
222StringRef
223ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
224 StringRef Default) const {
225 return getLocalOrGlobal(LocalName).value_or(Default);
226}
227} // namespace clang::tidy
228