1 //===---------- TransformerClangTidyCheck.cpp - clang-tidy ----------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "TransformerClangTidyCheck.h"
10 #include "clang/Basic/DiagnosticIDs.h"
11 #include "clang/Lex/Preprocessor.h"
12 #include "llvm/ADT/STLExtras.h"
15 namespace clang::tidy::utils
{
16 using transformer::RewriteRuleWith
;
19 static bool hasGenerator(const transformer::Generator
<std::string
> &G
) {
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");
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.
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
== ToEscape
.npos
)
41 Result
.reserve(ToEscape
.size());
42 // Convert position to a count.
44 Result
.append(ToEscape
, 0, Pos
);
47 for (auto N
= ToEscape
.size(); Pos
< N
; ++Pos
) {
48 const char C
= ToEscape
.at(Pos
);
57 TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name
,
58 ClangTidyContext
*Context
)
59 : ClangTidyCheck(Name
, Context
),
60 Inserter(Options
.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM
),
61 areDiagsSelfContained()) {}
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.
68 TransformerClangTidyCheck::TransformerClangTidyCheck(
69 std::function
<std::optional
<RewriteRuleWith
<std::string
>>(
70 const LangOptions
&, const OptionsView
&)>
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
));
79 TransformerClangTidyCheck::TransformerClangTidyCheck(
80 RewriteRuleWith
<std::string
> R
, StringRef Name
, ClangTidyContext
*Context
)
81 : TransformerClangTidyCheck(Name
, Context
) {
82 setRule(std::move(R
));
85 void TransformerClangTidyCheck::setRule(
86 transformer::RewriteRuleWith
<std::string
> R
) {
91 void TransformerClangTidyCheck::registerPPCallbacks(
92 const SourceManager
&SM
, Preprocessor
*PP
, Preprocessor
*ModuleExpanderPP
) {
93 Inserter
.registerPreprocessor(PP
);
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);
103 void TransformerClangTidyCheck::check(
104 const ast_matchers::MatchFinder::MatchResult
&Result
) {
105 if (Result
.Context
->getDiagnostics().hasErrorOccurred())
108 size_t I
= transformer::detail::findSelectedCase(Result
, Rule
);
109 Expected
<SmallVector
<transformer::Edit
, 1>> Edits
=
110 Rule
.Cases
[I
].Edits(Result
);
112 llvm::errs() << "Rewrite failed: " << llvm::toString(Edits
.takeError())
117 // No rewrite applied, but no error encountered either.
121 Expected
<std::string
> Explanation
= Rule
.Metadata
[I
]->eval(Result
);
123 llvm::errs() << "Error in explanation: "
124 << llvm::toString(Explanation
.takeError()) << "\n";
128 // Associate the diagnostic with the location of the first change.
130 DiagnosticBuilder Diag
=
131 diag((*Edits
)[0].Range
.getBegin(), escapeForDiagnostic(*Explanation
));
132 for (const auto &T
: *Edits
) {
134 case transformer::EditKind::Range
:
135 Diag
<< FixItHint::CreateReplacement(T
.Range
, T
.Replacement
);
137 case transformer::EditKind::AddInclude
:
138 Diag
<< Inserter
.createIncludeInsertion(
139 Result
.SourceManager
->getFileID(T
.Range
.getBegin()), T
.Replacement
);
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
);
153 void TransformerClangTidyCheck::storeOptions(
154 ClangTidyOptions::OptionMap
&Opts
) {
155 Options
.store(Opts
, "IncludeStyle", Inserter
.getStyle());
158 } // namespace clang::tidy::utils