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