clang-tools 19.0.0git
TransformerClangTidyCheck.cpp
Go to the documentation of this file.
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
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 NDEBUG
19static bool hasGenerator(const transformer::Generator<std::string> &G) {
20 return G != nullptr;
21}
22#endif
23
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, 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.
33std::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
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.
69 std::function<std::optional<RewriteRuleWith<std::string>>(
70 const LangOptions &, const OptionsView &)>
71 MakeRule,
72 StringRef Name, ClangTidyContext *Context)
74 if (std::optional<RewriteRuleWith<std::string>> R =
75 MakeRule(getLangOpts(), Options))
76 setRule(std::move(*R));
77}
78
80 RewriteRuleWith<std::string> R, StringRef Name, ClangTidyContext *Context)
81 : TransformerClangTidyCheck(Name, Context) {
82 setRule(std::move(R));
83}
84
86 transformer::RewriteRuleWith<std::string> R) {
87 verifyRule(R);
88 Rule = std::move(R);
89}
90
92 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
93 Inserter.registerPreprocessor(PP);
94}
95
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
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
155 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
156}
157
158} // namespace clang::tidy::utils
llvm::SmallString< 256U > Name
const Criteria C
CharSourceRange Range
SourceRange for the file name.
size_t Pos
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPreprocessor(Preprocessor *PP)
Registers this with the Preprocessor PP, must be called before this class is used.
std::optional< FixItHint > createIncludeInsertion(FileID FileID, llvm::StringRef Header)
Creates a Header inclusion directive fixit in the File FileID.
IncludeSorter::IncludeStyle getStyle() const
Class used by IncludeInserterCallback to record the names of the inclusions in a given source file be...
Definition: IncludeSorter.h:23
A base class for defining a ClangTidy check based on a RewriteRule.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
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
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) final
Override this to register AST matchers with Finder.
TransformerClangTidyCheck(StringRef Name, ClangTidyContext *Context)
std::string escapeForDiagnostic(std::string ToEscape)
static bool hasGenerator(const transformer::Generator< std::string > &G)
static void verifyRule(const RewriteRuleWith< std::string > &Rule)
llvm::StringMap< ClangTidyValue > OptionMap