| //===--- QueryCheck.cpp - clang-tidy --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "QueryCheck.h" |
| #include "../../clang-query/Query.h" |
| #include "../../clang-query/QueryParser.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/ASTMatchers/Dynamic/VariantValue.h" |
| #include "clang/Basic/DiagnosticIDs.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include <string> |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang::tidy::custom { |
| |
| static void emitConfigurationDiag(ClangTidyContext *Context, StringRef Message, |
| StringRef CheckName) { |
| Context->configurationDiag("%0 in '%1'", DiagnosticIDs::Warning) |
| << Message << CheckName; |
| } |
| |
| static SmallVector<ast_matchers::dynamic::DynTypedMatcher> |
| parseQuery(const ClangTidyOptions::CustomCheckValue &V, |
| ClangTidyContext *Context) { |
| SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{}; |
| clang::query::QuerySession QS({}); |
| llvm::StringRef QueryStringRef{V.Query}; |
| while (!QueryStringRef.empty()) { |
| query::QueryRef Q = query::QueryParser::parse(QueryStringRef, QS); |
| switch (Q->Kind) { |
| case query::QK_Match: { |
| const auto &MatchQuery = llvm::cast<query::MatchQuery>(*Q); |
| Matchers.push_back(MatchQuery.Matcher); |
| break; |
| } |
| case query::QK_Let: { |
| const auto &LetQuery = llvm::cast<query::LetQuery>(*Q); |
| LetQuery.run(llvm::errs(), QS); |
| break; |
| } |
| case query::QK_NoOp: { |
| const auto &NoOpQuery = llvm::cast<query::NoOpQuery>(*Q); |
| NoOpQuery.run(llvm::errs(), QS); |
| break; |
| } |
| case query::QK_Invalid: { |
| const auto &InvalidQuery = llvm::cast<query::InvalidQuery>(*Q); |
| emitConfigurationDiag(Context, InvalidQuery.ErrStr, V.Name); |
| return {}; |
| } |
| // FIXME: TODO |
| case query::QK_File: { |
| emitConfigurationDiag(Context, "unsupported query kind 'File'", V.Name); |
| return {}; |
| } |
| case query::QK_DisableOutputKind: { |
| emitConfigurationDiag( |
| Context, "unsupported query kind 'DisableOutputKind'", V.Name); |
| return {}; |
| } |
| case query::QK_EnableOutputKind: { |
| emitConfigurationDiag( |
| Context, "unsupported query kind 'EnableOutputKind'", V.Name); |
| return {}; |
| } |
| case query::QK_SetOutputKind: { |
| emitConfigurationDiag(Context, "unsupported query kind 'SetOutputKind'", |
| V.Name); |
| return {}; |
| } |
| case query::QK_SetTraversalKind: { |
| emitConfigurationDiag( |
| Context, "unsupported query kind 'SetTraversalKind'", V.Name); |
| return {}; |
| } |
| case query::QK_SetBool: { |
| emitConfigurationDiag(Context, "unsupported query kind 'SetBool'", |
| V.Name); |
| return {}; |
| } |
| case query::QK_Help: { |
| emitConfigurationDiag(Context, "unsupported query kind 'Help'", V.Name); |
| return {}; |
| } |
| case query::QK_Quit: { |
| emitConfigurationDiag(Context, "unsupported query kind 'Quit'", V.Name); |
| return {}; |
| } |
| } |
| QueryStringRef = Q->RemainingContent; |
| } |
| return Matchers; |
| } |
| |
| QueryCheck::QueryCheck(llvm::StringRef Name, |
| const ClangTidyOptions::CustomCheckValue &V, |
| ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context) { |
| for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) { |
| auto DiagnosticIdIt = |
| Diags |
| .try_emplace(D.Level.value_or(DiagnosticIDs::Warning), |
| llvm::StringMap<llvm::SmallVector<std::string>>{}) |
| .first; |
| auto DiagMessageIt = |
| DiagnosticIdIt->getSecond() |
| .try_emplace(D.BindName, llvm::SmallVector<std::string>{}) |
| .first; |
| DiagMessageIt->second.emplace_back(D.Message); |
| } |
| Matchers = parseQuery(V, Context); |
| } |
| |
| void QueryCheck::registerMatchers(MatchFinder *Finder) { |
| for (const ast_matchers::dynamic::DynTypedMatcher &M : Matchers) |
| Finder->addDynamicMatcher(M, this); |
| } |
| |
| void QueryCheck::check(const MatchFinder::MatchResult &Result) { |
| auto Emit = [this](const DiagMaps &DiagMaps, const std::string &BindName, |
| const DynTypedNode &Node, DiagnosticIDs::Level Level) { |
| DiagMaps::const_iterator DiagMapIt = DiagMaps.find(Level); |
| if (DiagMapIt == DiagMaps.end()) |
| return; |
| const BindNameMapToDiagMessage &BindNameMap = DiagMapIt->second; |
| BindNameMapToDiagMessage::const_iterator BindNameMapIt = |
| BindNameMap.find(BindName); |
| if (BindNameMapIt == BindNameMap.end()) |
| return; |
| for (const std::string &Message : BindNameMapIt->second) |
| diag(Node.getSourceRange().getBegin(), Message, Level); |
| }; |
| for (const auto &[Name, Node] : Result.Nodes.getMap()) |
| Emit(Diags, Name, Node, DiagnosticIDs::Warning); |
| // place Note last, otherwise it will not be emitted |
| for (const auto &[Name, Node] : Result.Nodes.getMap()) |
| Emit(Diags, Name, Node, DiagnosticIDs::Note); |
| } |
| } // namespace clang::tidy::custom |