1 //===--- Extract.cpp - Clang refactoring library --------------------------===//
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 /// \file
10 /// Implements the "extract" refactoring that can pull code into
11 /// new functions, methods or declare new variables.
12 ///
13 //===----------------------------------------------------------------------===//
14
15 #include "clang/Tooling/Refactoring/Extract/Extract.h"
16 #include "clang/AST/ASTContext.h"
17 #include "clang/AST/DeclCXX.h"
18 #include "clang/AST/Expr.h"
19 #include "clang/AST/ExprObjC.h"
20 #include "clang/Rewrite/Core/Rewriter.h"
21 #include "clang/Tooling/Refactoring/Extract/SourceExtraction.h"
22
23 namespace clang {
24 namespace tooling {
25
26 namespace {
27
28 /// Returns true if \c E is a simple literal or a reference expression that
29 /// should not be extracted.
isSimpleExpression(const Expr * E)30 bool isSimpleExpression(const Expr *E) {
31 if (!E)
32 return false;
33 switch (E->IgnoreParenCasts()->getStmtClass()) {
34 case Stmt::DeclRefExprClass:
35 case Stmt::PredefinedExprClass:
36 case Stmt::IntegerLiteralClass:
37 case Stmt::FloatingLiteralClass:
38 case Stmt::ImaginaryLiteralClass:
39 case Stmt::CharacterLiteralClass:
40 case Stmt::StringLiteralClass:
41 return true;
42 default:
43 return false;
44 }
45 }
46
computeFunctionExtractionLocation(const Decl * D)47 SourceLocation computeFunctionExtractionLocation(const Decl *D) {
48 if (isa<CXXMethodDecl>(D)) {
49 // Code from method that is defined in class body should be extracted to a
50 // function defined just before the class.
51 while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext()))
52 D = RD;
53 }
54 return D->getBeginLoc();
55 }
56
57 } // end anonymous namespace
58
describe()59 const RefactoringDescriptor &ExtractFunction::describe() {
60 static const RefactoringDescriptor Descriptor = {
61 "extract-function",
62 "Extract Function",
63 "(WIP action; use with caution!) Extracts code into a new function",
64 };
65 return Descriptor;
66 }
67
68 Expected<ExtractFunction>
initiate(RefactoringRuleContext & Context,CodeRangeASTSelection Code,Optional<std::string> DeclName)69 ExtractFunction::initiate(RefactoringRuleContext &Context,
70 CodeRangeASTSelection Code,
71 Optional<std::string> DeclName) {
72 // We would like to extract code out of functions/methods/blocks.
73 // Prohibit extraction from things like global variable / field
74 // initializers and other top-level expressions.
75 if (!Code.isInFunctionLikeBodyOfCode())
76 return Context.createDiagnosticError(
77 diag::err_refactor_code_outside_of_function);
78
79 if (Code.size() == 1) {
80 // Avoid extraction of simple literals and references.
81 if (isSimpleExpression(dyn_cast<Expr>(Code[0])))
82 return Context.createDiagnosticError(
83 diag::err_refactor_extract_simple_expression);
84
85 // Property setters can't be extracted.
86 if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) {
87 if (!PRE->isMessagingGetter())
88 return Context.createDiagnosticError(
89 diag::err_refactor_extract_prohibited_expression);
90 }
91 }
92
93 return ExtractFunction(std::move(Code), DeclName);
94 }
95
96 // FIXME: Support C++ method extraction.
97 // FIXME: Support Objective-C method extraction.
98 Expected<AtomicChanges>
createSourceReplacements(RefactoringRuleContext & Context)99 ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) {
100 const Decl *ParentDecl = Code.getFunctionLikeNearestParent();
101 assert(ParentDecl && "missing parent");
102
103 // Compute the source range of the code that should be extracted.
104 SourceRange ExtractedRange(Code[0]->getBeginLoc(),
105 Code[Code.size() - 1]->getEndLoc());
106 // FIXME (Alex L): Add code that accounts for macro locations.
107
108 ASTContext &AST = Context.getASTContext();
109 SourceManager &SM = AST.getSourceManager();
110 const LangOptions &LangOpts = AST.getLangOpts();
111 Rewriter ExtractedCodeRewriter(SM, LangOpts);
112
113 // FIXME: Capture used variables.
114
115 // Compute the return type.
116 QualType ReturnType = AST.VoidTy;
117 // FIXME (Alex L): Account for the return statement in extracted code.
118 // FIXME (Alex L): Check for lexical expression instead.
119 bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]);
120 if (IsExpr) {
121 // FIXME (Alex L): Get a more user-friendly type if needed.
122 ReturnType = cast<Expr>(Code[0])->getType();
123 }
124
125 // FIXME: Rewrite the extracted code performing any required adjustments.
126
127 // FIXME: Capture any field if necessary (method -> function extraction).
128
129 // FIXME: Sort captured variables by name.
130
131 // FIXME: Capture 'this' / 'self' if necessary.
132
133 // FIXME: Compute the actual parameter types.
134
135 // Compute the location of the extracted declaration.
136 SourceLocation ExtractedDeclLocation =
137 computeFunctionExtractionLocation(ParentDecl);
138 // FIXME: Adjust the location to account for any preceding comments.
139
140 // FIXME: Adjust with PP awareness like in Sema to get correct 'bool'
141 // treatment.
142 PrintingPolicy PP = AST.getPrintingPolicy();
143 // FIXME: PP.UseStdFunctionForLambda = true;
144 PP.SuppressStrongLifetime = true;
145 PP.SuppressLifetimeQualifiers = true;
146 PP.SuppressUnwrittenScope = true;
147
148 ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute(
149 Code[Code.size() - 1], ExtractedRange, SM, LangOpts);
150 AtomicChange Change(SM, ExtractedDeclLocation);
151 // Create the replacement for the extracted declaration.
152 {
153 std::string ExtractedCode;
154 llvm::raw_string_ostream OS(ExtractedCode);
155 // FIXME: Use 'inline' in header.
156 OS << "static ";
157 ReturnType.print(OS, PP, DeclName);
158 OS << '(';
159 // FIXME: Arguments.
160 OS << ')';
161
162 // Function body.
163 OS << " {\n";
164 if (IsExpr && !ReturnType->isVoidType())
165 OS << "return ";
166 OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange);
167 if (Semicolons.isNeededInExtractedFunction())
168 OS << ';';
169 OS << "\n}\n\n";
170 auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str());
171 if (Err)
172 return std::move(Err);
173 }
174
175 // Create the replacement for the call to the extracted declaration.
176 {
177 std::string ReplacedCode;
178 llvm::raw_string_ostream OS(ReplacedCode);
179
180 OS << DeclName << '(';
181 // FIXME: Forward arguments.
182 OS << ')';
183 if (Semicolons.isNeededInOriginalFunction())
184 OS << ';';
185
186 auto Err = Change.replace(
187 SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str());
188 if (Err)
189 return std::move(Err);
190 }
191
192 // FIXME: Add support for assocciated symbol location to AtomicChange to mark
193 // the ranges of the name of the extracted declaration.
194 return AtomicChanges{std::move(Change)};
195 }
196
197 } // end namespace tooling
198 } // end namespace clang
199