200 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// Implements the "extract" refactoring that can pull code into11/// 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 24namespace clang {25namespace tooling {26 27namespace {28 29/// Returns true if \c E is a simple literal or a reference expression that30/// should not be extracted.31bool isSimpleExpression(const Expr *E) {32 if (!E)33 return false;34 switch (E->IgnoreParenCasts()->getStmtClass()) {35 case Stmt::DeclRefExprClass:36 case Stmt::PredefinedExprClass:37 case Stmt::IntegerLiteralClass:38 case Stmt::FloatingLiteralClass:39 case Stmt::ImaginaryLiteralClass:40 case Stmt::CharacterLiteralClass:41 case Stmt::StringLiteralClass:42 return true;43 default:44 return false;45 }46}47 48SourceLocation computeFunctionExtractionLocation(const Decl *D) {49 if (isa<CXXMethodDecl>(D)) {50 // Code from method that is defined in class body should be extracted to a51 // function defined just before the class.52 while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext()))53 D = RD;54 }55 return D->getBeginLoc();56}57 58} // end anonymous namespace59 60const RefactoringDescriptor &ExtractFunction::describe() {61 static const RefactoringDescriptor Descriptor = {62 "extract-function",63 "Extract Function",64 "(WIP action; use with caution!) Extracts code into a new function",65 };66 return Descriptor;67}68 69Expected<ExtractFunction>70ExtractFunction::initiate(RefactoringRuleContext &Context,71 CodeRangeASTSelection Code,72 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 / field75 // initializers and other top-level expressions.76 if (!Code.isInFunctionLikeBodyOfCode())77 return Context.createDiagnosticError(78 diag::err_refactor_code_outside_of_function);79 80 if (Code.size() == 1) {81 // Avoid extraction of simple literals and references.82 if (isSimpleExpression(dyn_cast<Expr>(Code[0])))83 return Context.createDiagnosticError(84 diag::err_refactor_extract_simple_expression);85 86 // Property setters can't be extracted.87 if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) {88 if (!PRE->isMessagingGetter())89 return Context.createDiagnosticError(90 diag::err_refactor_extract_prohibited_expression);91 }92 }93 94 return ExtractFunction(std::move(Code), DeclName);95}96 97// FIXME: Support C++ method extraction.98// FIXME: Support Objective-C method extraction.99Expected<AtomicChanges>100ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) {101 const Decl *ParentDecl = Code.getFunctionLikeNearestParent();102 assert(ParentDecl && "missing parent");103 104 // Compute the source range of the code that should be extracted.105 SourceRange ExtractedRange(Code[0]->getBeginLoc(),106 Code[Code.size() - 1]->getEndLoc());107 // FIXME (Alex L): Add code that accounts for macro locations.108 109 ASTContext &AST = Context.getASTContext();110 SourceManager &SM = AST.getSourceManager();111 const LangOptions &LangOpts = AST.getLangOpts();112 Rewriter ExtractedCodeRewriter(SM, LangOpts);113 114 // FIXME: Capture used variables.115 116 // Compute the return type.117 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 bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]);121 if (IsExpr) {122 // FIXME (Alex L): Get a more user-friendly type if needed.123 ReturnType = cast<Expr>(Code[0])->getType();124 }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 SourceLocation ExtractedDeclLocation =138 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 PrintingPolicy PP = AST.getPrintingPolicy();144 // FIXME: PP.UseStdFunctionForLambda = true;145 PP.SuppressStrongLifetime = true;146 PP.SuppressLifetimeQualifiers = true;147 PP.SuppressUnwrittenScope = true;148 149 ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute(150 Code[Code.size() - 1], ExtractedRange, SM, LangOpts);151 AtomicChange Change(SM, ExtractedDeclLocation);152 // Create the replacement for the extracted declaration.153 {154 std::string ExtractedCode;155 llvm::raw_string_ostream OS(ExtractedCode);156 // FIXME: Use 'inline' in header.157 OS << "static ";158 ReturnType.print(OS, PP, DeclName);159 OS << '(';160 // FIXME: Arguments.161 OS << ')';162 163 // Function body.164 OS << " {\n";165 if (IsExpr && !ReturnType->isVoidType())166 OS << "return ";167 OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange);168 if (Semicolons.isNeededInExtractedFunction())169 OS << ';';170 OS << "\n}\n\n";171 auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str());172 if (Err)173 return std::move(Err);174 }175 176 // Create the replacement for the call to the extracted declaration.177 {178 std::string ReplacedCode;179 llvm::raw_string_ostream OS(ReplacedCode);180 181 OS << DeclName << '(';182 // FIXME: Forward arguments.183 OS << ')';184 if (Semicolons.isNeededInOriginalFunction())185 OS << ';';186 187 auto Err = Change.replace(188 SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str());189 if (Err)190 return std::move(Err);191 }192 193 // FIXME: Add support for assocciated symbol location to AtomicChange to mark194 // the ranges of the name of the extracted declaration.195 return AtomicChanges{std::move(Change)};196}197 198} // end namespace tooling199} // end namespace clang200