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