159 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 "TransformerClangTidyCheck.h"10#include "clang/Basic/DiagnosticIDs.h"11#include "clang/Lex/Preprocessor.h"12#include "llvm/ADT/STLExtras.h"13#include <optional>14 15namespace clang::tidy::utils {16using transformer::RewriteRuleWith;17 18#ifndef NDEBUG19static bool hasGenerator(const transformer::Generator<std::string> &G) {20 return G != nullptr;21}22#endif23 24static void verifyRule(const RewriteRuleWith<std::string> &Rule) {25 assert(llvm::all_of(Rule.Metadata, hasGenerator) &&26 "clang-tidy checks must have an explanation by default;"27 " explicitly provide an empty explanation if none is desired");28}29 30// If a string unintentionally containing '%' is passed as a diagnostic, Clang31// will claim the string is ill-formed and assert-fail. This function escapes32// such strings so they can be safely used in diagnostics.33static std::string escapeForDiagnostic(std::string ToEscape) {34 // Optimize for the common case that the string does not contain `%` at the35 // cost of an extra scan over the string in the slow case.36 auto Pos = ToEscape.find('%');37 if (Pos == std::string::npos)38 return ToEscape;39 40 std::string Result;41 Result.reserve(ToEscape.size());42 // Convert position to a count.43 ++Pos;44 Result.append(ToEscape, 0, Pos);45 Result += '%';46 47 for (auto N = ToEscape.size(); Pos < N; ++Pos) {48 const char C = ToEscape.at(Pos);49 Result += C;50 if (C == '%')51 Result += '%';52 }53 54 return Result;55}56 57TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name,58 ClangTidyContext *Context)59 : ClangTidyCheck(Name, Context),60 Inserter(Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM),61 areDiagsSelfContained()) {}62 63// This constructor cannot dispatch to the simpler one (below), because, in64// order to get meaningful results from `getLangOpts` and `Options`, we need the65// `ClangTidyCheck()` constructor to have been called. If we were to dispatch,66// we would be accessing `getLangOpts` and `Options` before the underlying67// `ClangTidyCheck` instance was properly initialized.68TransformerClangTidyCheck::TransformerClangTidyCheck(69 llvm::function_ref<std::optional<RewriteRuleWith<std::string>>(70 const LangOptions &, const OptionsView &)>71 MakeRule,72 StringRef Name, ClangTidyContext *Context)73 : TransformerClangTidyCheck(Name, Context) {74 if (std::optional<RewriteRuleWith<std::string>> R =75 MakeRule(getLangOpts(), Options))76 setRule(std::move(*R));77}78 79TransformerClangTidyCheck::TransformerClangTidyCheck(80 RewriteRuleWith<std::string> R, StringRef Name, ClangTidyContext *Context)81 : TransformerClangTidyCheck(Name, Context) {82 setRule(std::move(R));83}84 85void TransformerClangTidyCheck::setRule(86 transformer::RewriteRuleWith<std::string> R) {87 verifyRule(R);88 Rule = std::move(R);89}90 91void TransformerClangTidyCheck::registerPPCallbacks(92 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {93 Inserter.registerPreprocessor(PP);94}95 96void TransformerClangTidyCheck::registerMatchers(97 ast_matchers::MatchFinder *Finder) {98 if (!Rule.Cases.empty())99 for (auto &Matcher : transformer::detail::buildMatchers(Rule))100 Finder->addDynamicMatcher(Matcher, this);101}102 103void TransformerClangTidyCheck::check(104 const ast_matchers::MatchFinder::MatchResult &Result) {105 if (Result.Context->getDiagnostics().hasErrorOccurred())106 return;107 108 const size_t I = transformer::detail::findSelectedCase(Result, Rule);109 Expected<SmallVector<transformer::Edit, 1>> Edits =110 Rule.Cases[I].Edits(Result);111 if (!Edits) {112 llvm::errs() << "Rewrite failed: " << llvm::toString(Edits.takeError())113 << "\n";114 return;115 }116 117 // No rewrite applied, but no error encountered either.118 if (Edits->empty())119 return;120 121 Expected<std::string> Explanation = Rule.Metadata[I]->eval(Result);122 if (!Explanation) {123 llvm::errs() << "Error in explanation: "124 << llvm::toString(Explanation.takeError()) << "\n";125 return;126 }127 128 // Associate the diagnostic with the location of the first change.129 {130 const DiagnosticBuilder Diag =131 diag((*Edits)[0].Range.getBegin(), escapeForDiagnostic(*Explanation));132 for (const auto &T : *Edits) {133 switch (T.Kind) {134 case transformer::EditKind::Range:135 Diag << FixItHint::CreateReplacement(T.Range, T.Replacement);136 break;137 case transformer::EditKind::AddInclude:138 Diag << Inserter.createIncludeInsertion(139 Result.SourceManager->getFileID(T.Range.getBegin()), T.Replacement);140 break;141 }142 }143 }144 // Emit potential notes.145 for (const auto &T : *Edits) {146 if (!T.Note.empty()) {147 diag(T.Range.getBegin(), escapeForDiagnostic(T.Note),148 DiagnosticIDs::Note);149 }150 }151}152 153void TransformerClangTidyCheck::storeOptions(154 ClangTidyOptions::OptionMap &Opts) {155 Options.store(Opts, "IncludeStyle", Inserter.getStyle());156}157 158} // namespace clang::tidy::utils159