| //===--- Extract.cpp - Clang refactoring library --------------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// \brief Implements the "extract" refactoring that can pull code into |
| /// new functions, methods or declare new variables. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Tooling/Refactoring/Extract/Extract.h" |
| #include "SourceExtraction.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/AST/ExprObjC.h" |
| #include "clang/Rewrite/Core/Rewriter.h" |
| |
| namespace clang { |
| namespace tooling { |
| |
| namespace { |
| |
| /// Returns true if \c E is a simple literal or a reference expression that |
| /// should not be extracted. |
| bool isSimpleExpression(const Expr *E) { |
| if (!E) |
| return false; |
| switch (E->IgnoreParenCasts()->getStmtClass()) { |
| case Stmt::DeclRefExprClass: |
| case Stmt::PredefinedExprClass: |
| case Stmt::IntegerLiteralClass: |
| case Stmt::FloatingLiteralClass: |
| case Stmt::ImaginaryLiteralClass: |
| case Stmt::CharacterLiteralClass: |
| case Stmt::StringLiteralClass: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| SourceLocation computeFunctionExtractionLocation(const Decl *D) { |
| if (isa<CXXMethodDecl>(D)) { |
| // Code from method that is defined in class body should be extracted to a |
| // function defined just before the class. |
| while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext())) |
| D = RD; |
| } |
| return D->getLocStart(); |
| } |
| |
| } // end anonymous namespace |
| |
| const RefactoringDescriptor &ExtractFunction::describe() { |
| static const RefactoringDescriptor Descriptor = { |
| "extract-function", |
| "Extract Function", |
| "(WIP action; use with caution!) Extracts code into a new function", |
| }; |
| return Descriptor; |
| } |
| |
| Expected<ExtractFunction> |
| ExtractFunction::initiate(RefactoringRuleContext &Context, |
| CodeRangeASTSelection Code, |
| Optional<std::string> DeclName) { |
| // We would like to extract code out of functions/methods/blocks. |
| // Prohibit extraction from things like global variable / field |
| // initializers and other top-level expressions. |
| if (!Code.isInFunctionLikeBodyOfCode()) |
| return Context.createDiagnosticError( |
| diag::err_refactor_code_outside_of_function); |
| |
| if (Code.size() == 1) { |
| // Avoid extraction of simple literals and references. |
| if (isSimpleExpression(dyn_cast<Expr>(Code[0]))) |
| return Context.createDiagnosticError( |
| diag::err_refactor_extract_simple_expression); |
| |
| // Property setters can't be extracted. |
| if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) { |
| if (!PRE->isMessagingGetter()) |
| return Context.createDiagnosticError( |
| diag::err_refactor_extract_prohibited_expression); |
| } |
| } |
| |
| return ExtractFunction(std::move(Code), DeclName); |
| } |
| |
| // FIXME: Support C++ method extraction. |
| // FIXME: Support Objective-C method extraction. |
| Expected<AtomicChanges> |
| ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) { |
| const Decl *ParentDecl = Code.getFunctionLikeNearestParent(); |
| assert(ParentDecl && "missing parent"); |
| |
| // Compute the source range of the code that should be extracted. |
| SourceRange ExtractedRange(Code[0]->getLocStart(), |
| Code[Code.size() - 1]->getLocEnd()); |
| // FIXME (Alex L): Add code that accounts for macro locations. |
| |
| ASTContext &AST = Context.getASTContext(); |
| SourceManager &SM = AST.getSourceManager(); |
| const LangOptions &LangOpts = AST.getLangOpts(); |
| Rewriter ExtractedCodeRewriter(SM, LangOpts); |
| |
| // FIXME: Capture used variables. |
| |
| // Compute the return type. |
| QualType ReturnType = AST.VoidTy; |
| // FIXME (Alex L): Account for the return statement in extracted code. |
| // FIXME (Alex L): Check for lexical expression instead. |
| bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]); |
| if (IsExpr) { |
| // FIXME (Alex L): Get a more user-friendly type if needed. |
| ReturnType = cast<Expr>(Code[0])->getType(); |
| } |
| |
| // FIXME: Rewrite the extracted code performing any required adjustments. |
| |
| // FIXME: Capture any field if necessary (method -> function extraction). |
| |
| // FIXME: Sort captured variables by name. |
| |
| // FIXME: Capture 'this' / 'self' if necessary. |
| |
| // FIXME: Compute the actual parameter types. |
| |
| // Compute the location of the extracted declaration. |
| SourceLocation ExtractedDeclLocation = |
| computeFunctionExtractionLocation(ParentDecl); |
| // FIXME: Adjust the location to account for any preceding comments. |
| |
| // FIXME: Adjust with PP awareness like in Sema to get correct 'bool' |
| // treatment. |
| PrintingPolicy PP = AST.getPrintingPolicy(); |
| // FIXME: PP.UseStdFunctionForLambda = true; |
| PP.SuppressStrongLifetime = true; |
| PP.SuppressLifetimeQualifiers = true; |
| PP.SuppressUnwrittenScope = true; |
| |
| ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute( |
| Code[Code.size() - 1], ExtractedRange, SM, LangOpts); |
| AtomicChange Change(SM, ExtractedDeclLocation); |
| // Create the replacement for the extracted declaration. |
| { |
| std::string ExtractedCode; |
| llvm::raw_string_ostream OS(ExtractedCode); |
| // FIXME: Use 'inline' in header. |
| OS << "static "; |
| ReturnType.print(OS, PP, DeclName); |
| OS << '('; |
| // FIXME: Arguments. |
| OS << ')'; |
| |
| // Function body. |
| OS << " {\n"; |
| if (IsExpr && !ReturnType->isVoidType()) |
| OS << "return "; |
| OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange); |
| if (Semicolons.isNeededInExtractedFunction()) |
| OS << ';'; |
| OS << "\n}\n\n"; |
| auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str()); |
| if (Err) |
| return std::move(Err); |
| } |
| |
| // Create the replacement for the call to the extracted declaration. |
| { |
| std::string ReplacedCode; |
| llvm::raw_string_ostream OS(ReplacedCode); |
| |
| OS << DeclName << '('; |
| // FIXME: Forward arguments. |
| OS << ')'; |
| if (Semicolons.isNeededInOriginalFunction()) |
| OS << ';'; |
| |
| auto Err = Change.replace( |
| SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str()); |
| if (Err) |
| return std::move(Err); |
| } |
| |
| // FIXME: Add support for assocciated symbol location to AtomicChange to mark |
| // the ranges of the name of the extracted declaration. |
| return AtomicChanges{std::move(Change)}; |
| } |
| |
| } // end namespace tooling |
| } // end namespace clang |