/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Tooling/RefactoringCallbacks.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- RefactoringCallbacks.cpp - Structural query framework ------------===// |
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 | | // |
10 | | //===----------------------------------------------------------------------===// |
11 | | #include "clang/Tooling/RefactoringCallbacks.h" |
12 | | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | | #include "clang/Basic/SourceLocation.h" |
14 | | #include "clang/Lex/Lexer.h" |
15 | | |
16 | | using llvm::StringError; |
17 | | using llvm::make_error; |
18 | | |
19 | | namespace clang { |
20 | | namespace tooling { |
21 | | |
22 | 10 | RefactoringCallback::RefactoringCallback() {} |
23 | 20 | tooling::Replacements &RefactoringCallback::getReplacements() { |
24 | 20 | return Replace; |
25 | 20 | } |
26 | | |
27 | | ASTMatchRefactorer::ASTMatchRefactorer( |
28 | | std::map<std::string, Replacements> &FileToReplaces) |
29 | 10 | : FileToReplaces(FileToReplaces) {} |
30 | | |
31 | | void ASTMatchRefactorer::addDynamicMatcher( |
32 | | const ast_matchers::internal::DynTypedMatcher &Matcher, |
33 | 0 | RefactoringCallback *Callback) { |
34 | 0 | MatchFinder.addDynamicMatcher(Matcher, Callback); |
35 | 0 | Callbacks.push_back(Callback); |
36 | 0 | } |
37 | | |
38 | | class RefactoringASTConsumer : public ASTConsumer { |
39 | | public: |
40 | | explicit RefactoringASTConsumer(ASTMatchRefactorer &Refactoring) |
41 | 10 | : Refactoring(Refactoring) {} |
42 | | |
43 | 10 | void HandleTranslationUnit(ASTContext &Context) override { |
44 | | // The ASTMatchRefactorer is re-used between translation units. |
45 | | // Clear the matchers so that each Replacement is only emitted once. |
46 | 10 | for (const auto &Callback : Refactoring.Callbacks) { |
47 | 10 | Callback->getReplacements().clear(); |
48 | 10 | } |
49 | 10 | Refactoring.MatchFinder.matchAST(Context); |
50 | 10 | for (const auto &Callback : Refactoring.Callbacks) { |
51 | 10 | for (const auto &Replacement : Callback->getReplacements()) { |
52 | 9 | llvm::Error Err = |
53 | 9 | Refactoring.FileToReplaces[std::string(Replacement.getFilePath())] |
54 | 9 | .add(Replacement); |
55 | 9 | if (Err) { |
56 | 0 | llvm::errs() << "Skipping replacement " << Replacement.toString() |
57 | 0 | << " due to this error:\n" |
58 | 0 | << toString(std::move(Err)) << "\n"; |
59 | 0 | } |
60 | 9 | } |
61 | 10 | } |
62 | 10 | } |
63 | | |
64 | | private: |
65 | | ASTMatchRefactorer &Refactoring; |
66 | | }; |
67 | | |
68 | 10 | std::unique_ptr<ASTConsumer> ASTMatchRefactorer::newASTConsumer() { |
69 | 10 | return std::make_unique<RefactoringASTConsumer>(*this); |
70 | 10 | } |
71 | | |
72 | | static Replacement replaceStmtWithText(SourceManager &Sources, const Stmt &From, |
73 | 3 | StringRef Text) { |
74 | 3 | return tooling::Replacement( |
75 | 3 | Sources, CharSourceRange::getTokenRange(From.getSourceRange()), Text); |
76 | 3 | } |
77 | | static Replacement replaceStmtWithStmt(SourceManager &Sources, const Stmt &From, |
78 | 2 | const Stmt &To) { |
79 | 2 | return replaceStmtWithText( |
80 | 2 | Sources, From, |
81 | 2 | Lexer::getSourceText(CharSourceRange::getTokenRange(To.getSourceRange()), |
82 | 2 | Sources, LangOptions())); |
83 | 2 | } |
84 | | |
85 | | ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText) |
86 | 4 | : FromId(std::string(FromId)), ToText(std::string(ToText)) {} |
87 | | |
88 | | void ReplaceStmtWithText::run( |
89 | 3 | const ast_matchers::MatchFinder::MatchResult &Result) { |
90 | 3 | if (const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId)) { |
91 | 3 | auto Err = Replace.add(tooling::Replacement( |
92 | 3 | *Result.SourceManager, |
93 | 3 | CharSourceRange::getTokenRange(FromMatch->getSourceRange()), ToText)); |
94 | | // FIXME: better error handling. For now, just print error message in the |
95 | | // release version. |
96 | 3 | if (Err) { |
97 | 0 | llvm::errs() << llvm::toString(std::move(Err)) << "\n"; |
98 | 0 | assert(false); |
99 | 0 | } |
100 | 3 | } |
101 | 3 | } |
102 | | |
103 | | ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId) |
104 | 1 | : FromId(std::string(FromId)), ToId(std::string(ToId)) {} |
105 | | |
106 | | void ReplaceStmtWithStmt::run( |
107 | 1 | const ast_matchers::MatchFinder::MatchResult &Result) { |
108 | 1 | const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId); |
109 | 1 | const Stmt *ToMatch = Result.Nodes.getNodeAs<Stmt>(ToId); |
110 | 1 | if (FromMatch && ToMatch) { |
111 | 1 | auto Err = Replace.add( |
112 | 1 | replaceStmtWithStmt(*Result.SourceManager, *FromMatch, *ToMatch)); |
113 | | // FIXME: better error handling. For now, just print error message in the |
114 | | // release version. |
115 | 1 | if (Err) { |
116 | 0 | llvm::errs() << llvm::toString(std::move(Err)) << "\n"; |
117 | 0 | assert(false); |
118 | 0 | } |
119 | 1 | } |
120 | 1 | } |
121 | | |
122 | | ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id, |
123 | | bool PickTrueBranch) |
124 | 2 | : Id(std::string(Id)), PickTrueBranch(PickTrueBranch) {} |
125 | | |
126 | | void ReplaceIfStmtWithItsBody::run( |
127 | 2 | const ast_matchers::MatchFinder::MatchResult &Result) { |
128 | 2 | if (const IfStmt *Node = Result.Nodes.getNodeAs<IfStmt>(Id)) { |
129 | 2 | const Stmt *Body = PickTrueBranch ? Node->getThen()1 : Node->getElse()1 ; |
130 | 2 | if (Body) { |
131 | 1 | auto Err = |
132 | 1 | Replace.add(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body)); |
133 | | // FIXME: better error handling. For now, just print error message in the |
134 | | // release version. |
135 | 1 | if (Err) { |
136 | 0 | llvm::errs() << llvm::toString(std::move(Err)) << "\n"; |
137 | 0 | assert(false); |
138 | 0 | } |
139 | 1 | } else if (!PickTrueBranch) { |
140 | | // If we want to use the 'else'-branch, but it doesn't exist, delete |
141 | | // the whole 'if'. |
142 | 1 | auto Err = |
143 | 1 | Replace.add(replaceStmtWithText(*Result.SourceManager, *Node, "")); |
144 | | // FIXME: better error handling. For now, just print error message in the |
145 | | // release version. |
146 | 1 | if (Err) { |
147 | 0 | llvm::errs() << llvm::toString(std::move(Err)) << "\n"; |
148 | 0 | assert(false); |
149 | 0 | } |
150 | 1 | } |
151 | 2 | } |
152 | 2 | } |
153 | | |
154 | | ReplaceNodeWithTemplate::ReplaceNodeWithTemplate( |
155 | | llvm::StringRef FromId, std::vector<TemplateElement> Template) |
156 | 3 | : FromId(std::string(FromId)), Template(std::move(Template)) {} |
157 | | |
158 | | llvm::Expected<std::unique_ptr<ReplaceNodeWithTemplate>> |
159 | 5 | ReplaceNodeWithTemplate::create(StringRef FromId, StringRef ToTemplate) { |
160 | 5 | std::vector<TemplateElement> ParsedTemplate; |
161 | 17 | for (size_t Index = 0; Index < ToTemplate.size();) { |
162 | 14 | if (ToTemplate[Index] == '$') { |
163 | 6 | if (ToTemplate.substr(Index, 2) == "$$") { |
164 | 2 | Index += 2; |
165 | 2 | ParsedTemplate.push_back( |
166 | 2 | TemplateElement{TemplateElement::Literal, "$"}); |
167 | 4 | } else if (ToTemplate.substr(Index, 2) == "${") { |
168 | 3 | size_t EndOfIdentifier = ToTemplate.find("}", Index); |
169 | 3 | if (EndOfIdentifier == std::string::npos) { |
170 | 1 | return make_error<StringError>( |
171 | 1 | "Unterminated ${...} in replacement template near " + |
172 | 1 | ToTemplate.substr(Index), |
173 | 1 | llvm::inconvertibleErrorCode()); |
174 | 1 | } |
175 | 2 | std::string SourceNodeName = std::string( |
176 | 2 | ToTemplate.substr(Index + 2, EndOfIdentifier - Index - 2)); |
177 | 2 | ParsedTemplate.push_back( |
178 | 2 | TemplateElement{TemplateElement::Identifier, SourceNodeName}); |
179 | 2 | Index = EndOfIdentifier + 1; |
180 | 2 | } else { |
181 | 1 | return make_error<StringError>( |
182 | 1 | "Invalid $ in replacement template near " + |
183 | 1 | ToTemplate.substr(Index), |
184 | 1 | llvm::inconvertibleErrorCode()); |
185 | 1 | } |
186 | 8 | } else { |
187 | 8 | size_t NextIndex = ToTemplate.find('$', Index + 1); |
188 | 8 | ParsedTemplate.push_back(TemplateElement{ |
189 | 8 | TemplateElement::Literal, |
190 | 8 | std::string(ToTemplate.substr(Index, NextIndex - Index))}); |
191 | 8 | Index = NextIndex; |
192 | 8 | } |
193 | 14 | } |
194 | 3 | return std::unique_ptr<ReplaceNodeWithTemplate>( |
195 | 3 | new ReplaceNodeWithTemplate(FromId, std::move(ParsedTemplate))); |
196 | 5 | } |
197 | | |
198 | | void ReplaceNodeWithTemplate::run( |
199 | 3 | const ast_matchers::MatchFinder::MatchResult &Result) { |
200 | 3 | const auto &NodeMap = Result.Nodes.getMap(); |
201 | | |
202 | 3 | std::string ToText; |
203 | 8 | for (const auto &Element : Template) { |
204 | 8 | switch (Element.Type) { |
205 | 6 | case TemplateElement::Literal: |
206 | 6 | ToText += Element.Value; |
207 | 6 | break; |
208 | 2 | case TemplateElement::Identifier: { |
209 | 2 | auto NodeIter = NodeMap.find(Element.Value); |
210 | 2 | if (NodeIter == NodeMap.end()) { |
211 | 0 | llvm::errs() << "Node " << Element.Value |
212 | 0 | << " used in replacement template not bound in Matcher \n"; |
213 | 0 | llvm::report_fatal_error("Unbound node in replacement template."); |
214 | 0 | } |
215 | 2 | CharSourceRange Source = |
216 | 2 | CharSourceRange::getTokenRange(NodeIter->second.getSourceRange()); |
217 | 2 | ToText += Lexer::getSourceText(Source, *Result.SourceManager, |
218 | 2 | Result.Context->getLangOpts()); |
219 | 2 | break; |
220 | 0 | } |
221 | 8 | } |
222 | 8 | } |
223 | 3 | if (NodeMap.count(FromId) == 0) { |
224 | 0 | llvm::errs() << "Node to be replaced " << FromId |
225 | 0 | << " not bound in query.\n"; |
226 | 0 | llvm::report_fatal_error("FromId node not bound in MatchResult"); |
227 | 0 | } |
228 | 3 | auto Replacement = |
229 | 3 | tooling::Replacement(*Result.SourceManager, &NodeMap.at(FromId), ToText, |
230 | 3 | Result.Context->getLangOpts()); |
231 | 3 | llvm::Error Err = Replace.add(Replacement); |
232 | 3 | if (Err) { |
233 | 0 | llvm::errs() << "Query and replace failed in " << Replacement.getFilePath() |
234 | 0 | << "! " << llvm::toString(std::move(Err)) << "\n"; |
235 | 0 | llvm::report_fatal_error("Replacement failed"); |
236 | 0 | } |
237 | 3 | } |
238 | | |
239 | | } // end namespace tooling |
240 | | } // end namespace clang |