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