xref: /llvm-project/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp (revision 8ba103ca681670750e0929f74ab72870a871202f)
1 //===---------- TransformerClangTidyCheck.cpp - clang-tidy ----------------===//
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 "TransformerClangTidyCheck.h"
10 #include "clang/Basic/DiagnosticIDs.h"
11 #include "clang/Lex/Preprocessor.h"
12 #include "llvm/ADT/STLExtras.h"
13 #include <optional>
14 
15 namespace clang::tidy::utils {
16 using transformer::RewriteRuleWith;
17 
18 #ifndef NDEBUG
hasGenerator(const transformer::Generator<std::string> & G)19 static bool hasGenerator(const transformer::Generator<std::string> &G) {
20   return G != nullptr;
21 }
22 #endif
23 
verifyRule(const RewriteRuleWith<std::string> & Rule)24 static 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, Clang
31 // will claim the string is ill-formed and assert-fail. This function escapes
32 // such strings so they can be safely used in diagnostics.
escapeForDiagnostic(std::string ToEscape)33 std::string escapeForDiagnostic(std::string ToEscape) {
34   // Optimize for the common case that the string does not contain `%` at the
35   // 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 
TransformerClangTidyCheck(StringRef Name,ClangTidyContext * Context)57 TransformerClangTidyCheck::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, in
64 // order to get meaningful results from `getLangOpts` and `Options`, we need the
65 // `ClangTidyCheck()` constructor to have been called. If we were to dispatch,
66 // we would be accessing `getLangOpts` and `Options` before the underlying
67 // `ClangTidyCheck` instance was properly initialized.
TransformerClangTidyCheck(std::function<std::optional<RewriteRuleWith<std::string>> (const LangOptions &,const OptionsView &)> MakeRule,StringRef Name,ClangTidyContext * Context)68 TransformerClangTidyCheck::TransformerClangTidyCheck(
69     std::function<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 
TransformerClangTidyCheck(RewriteRuleWith<std::string> R,StringRef Name,ClangTidyContext * Context)79 TransformerClangTidyCheck::TransformerClangTidyCheck(
80     RewriteRuleWith<std::string> R, StringRef Name, ClangTidyContext *Context)
81     : TransformerClangTidyCheck(Name, Context) {
82   setRule(std::move(R));
83 }
84 
setRule(transformer::RewriteRuleWith<std::string> R)85 void TransformerClangTidyCheck::setRule(
86     transformer::RewriteRuleWith<std::string> R) {
87   verifyRule(R);
88   Rule = std::move(R);
89 }
90 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)91 void TransformerClangTidyCheck::registerPPCallbacks(
92     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
93   Inserter.registerPreprocessor(PP);
94 }
95 
registerMatchers(ast_matchers::MatchFinder * Finder)96 void 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 
check(const ast_matchers::MatchFinder::MatchResult & Result)103 void TransformerClangTidyCheck::check(
104     const ast_matchers::MatchFinder::MatchResult &Result) {
105   if (Result.Context->getDiagnostics().hasErrorOccurred())
106     return;
107 
108   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     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 
storeOptions(ClangTidyOptions::OptionMap & Opts)153 void TransformerClangTidyCheck::storeOptions(
154     ClangTidyOptions::OptionMap &Opts) {
155   Options.store(Opts, "IncludeStyle", Inserter.getStyle());
156 }
157 
158 } // namespace clang::tidy::utils
159