230 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "ClangTidyCheck.h"10#include "llvm/ADT/StringRef.h"11#include "llvm/ADT/StringSet.h"12#include "llvm/Support/YAMLParser.h"13#include <optional>14#include <string>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 36DiagnosticBuilder37ClangTidyCheck::configurationDiag(StringRef Description,38 DiagnosticIDs::Level Level) const {39 return Context->configurationDiag(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 interface45 // 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((NamePrefix + LocalName).str());59 const auto &Iter = CheckOptions.find((NamePrefix + LocalName).str());60 if (Iter != CheckOptions.end())61 return StringRef(Iter->getValue().Value);62 return std::nullopt;63}64 65static ClangTidyOptions::OptionMap::const_iterator66findPriorityOption(const ClangTidyOptions::OptionMap &Options,67 StringRef NamePrefix, StringRef LocalName,68 ClangTidyContext *Context) {69 llvm::StringSet<> *Collector = Context->getOptionsCollector();70 if (Collector) {71 Collector->insert((NamePrefix + LocalName).str());72 Collector->insert(LocalName);73 }74 auto IterLocal = Options.find((NamePrefix + LocalName).str());75 auto IterGlobal = Options.find(LocalName);76 if (IterLocal == Options.end())77 return IterGlobal;78 if (IterGlobal == Options.end())79 return IterLocal;80 if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority)81 return IterLocal;82 return IterGlobal;83}84 85std::optional<StringRef>86ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {87 auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, Context);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 if (std::optional<bool> Parsed = llvm::yaml::parseBool(Value))95 return Parsed;96 // To maintain backwards compatibility, we support parsing numbers as97 // booleans, even though its not supported in YAML.98 long long Number = 0;99 if (!Value.getAsInteger(10, Number))100 return Number != 0;101 return std::nullopt;102}103 104template <>105std::optional<bool>106ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {107 if (std::optional<StringRef> ValueOr = get(LocalName)) {108 if (auto Result = getAsBool(*ValueOr))109 return Result;110 diagnoseBadBooleanOption(NamePrefix + LocalName, *ValueOr);111 }112 return std::nullopt;113}114 115template <>116std::optional<bool>117ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {118 auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, Context);119 if (Iter != CheckOptions.end()) {120 if (auto Result = getAsBool(Iter->getValue().Value))121 return Result;122 diagnoseBadBooleanOption(Iter->getKey(), Iter->getValue().Value);123 }124 return std::nullopt;125}126 127void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,128 StringRef LocalName,129 StringRef Value) const {130 Options[(NamePrefix + LocalName).str()] = Value;131}132 133void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options,134 StringRef LocalName,135 int64_t Value) const {136 store(Options, LocalName, llvm::itostr(Value));137}138 139void ClangTidyCheck::OptionsView::storeUnsigned(140 ClangTidyOptions::OptionMap &Options, StringRef LocalName,141 uint64_t Value) const {142 store(Options, LocalName, llvm::utostr(Value));143}144 145template <>146void ClangTidyCheck::OptionsView::store<bool>(147 ClangTidyOptions::OptionMap &Options, StringRef LocalName,148 bool Value) const {149 store(Options, LocalName, Value ? StringRef("true") : StringRef("false"));150}151 152std::optional<int64_t>153ClangTidyCheck::OptionsView::getEnumInt(StringRef LocalName,154 ArrayRef<NameAndValue> Mapping,155 bool CheckGlobal) const {156 if (!CheckGlobal && Context->getOptionsCollector())157 Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());158 auto Iter = CheckGlobal ? findPriorityOption(CheckOptions, NamePrefix,159 LocalName, Context)160 : CheckOptions.find((NamePrefix + LocalName).str());161 if (Iter == CheckOptions.end())162 return std::nullopt;163 164 const StringRef Value = Iter->getValue().Value;165 StringRef Closest;166 unsigned EditDistance = 3;167 for (const auto &NameAndEnum : Mapping) {168 if (Value == NameAndEnum.second) {169 return NameAndEnum.first;170 }171 if (Value.equals_insensitive(NameAndEnum.second)) {172 Closest = NameAndEnum.second;173 EditDistance = 0;174 continue;175 }176 const unsigned Distance =177 Value.edit_distance(NameAndEnum.second, true, EditDistance);178 if (Distance < EditDistance) {179 EditDistance = Distance;180 Closest = NameAndEnum.second;181 }182 }183 if (EditDistance < 3)184 diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value, Closest);185 else186 diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value);187 return std::nullopt;188}189 190static constexpr llvm::StringLiteral ConfigWarning(191 "invalid configuration value '%0' for option '%1'%select{|; expected a "192 "bool|; expected an integer|; did you mean '%3'?}2");193 194void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption(195 const Twine &Lookup, StringRef Unparsed) const {196 SmallString<64> Buffer;197 Context->configurationDiag(ConfigWarning)198 << Unparsed << Lookup.toStringRef(Buffer) << 1;199}200 201void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption(202 const Twine &Lookup, StringRef Unparsed) const {203 SmallString<64> Buffer;204 Context->configurationDiag(ConfigWarning)205 << Unparsed << Lookup.toStringRef(Buffer) << 2;206}207 208void ClangTidyCheck::OptionsView::diagnoseBadEnumOption(209 const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const {210 SmallString<64> Buffer;211 auto Diag = Context->configurationDiag(ConfigWarning)212 << Unparsed << Lookup.toStringRef(Buffer);213 if (Suggestion.empty())214 Diag << 0;215 else216 Diag << 3 << Suggestion;217}218 219StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName,220 StringRef Default) const {221 return get(LocalName).value_or(Default);222}223 224StringRef225ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,226 StringRef Default) const {227 return getLocalOrGlobal(LocalName).value_or(Default);228}229} // namespace clang::tidy230