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