diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td index 5446b32efbdd4..6f3e2f1724d06 100644 --- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td +++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td @@ -28,6 +28,10 @@ def err_refactor_extract_simple_expression : Error<"the selected expression " def err_refactor_extract_prohibited_expression : Error<"the selected " "expression can't be extracted">; +def err_refactor_no_location : Error<"refactoring action can't be initiated " + "without a location">; +def err_refactor_location_no_statement : Error<"the provided location is not " + "surrouded by an AST statement">; } } // end of Refactoring diagnostics diff --git a/clang/include/clang/Tooling/Refactoring/ASTStatement.h b/clang/include/clang/Tooling/Refactoring/ASTStatement.h new file mode 100644 index 0000000000000..168c68ecd2d36 --- /dev/null +++ b/clang/include/clang/Tooling/Refactoring/ASTStatement.h @@ -0,0 +1,30 @@ +//===--- ASTStatement.h - Clang refactoring library -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTORING_ASTSTATEMENT_H +#define LLVM_CLANG_TOOLING_REFACTORING_ASTSTATEMENT_H + +#include "clang/AST/Stmt.h" +#include "clang/Basic/SourceLocation.h" + +namespace clang { + +class ASTContext; + +namespace tooling { + +/// Traverses the given ASTContext and finds closest outer statement. +/// +/// \returns nullptr if location is not surrounded by any statement, or a AST +/// statement otherwise. +Stmt *findOuterStmt(const ASTContext &Context, + SourceLocation Location); +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTORING_ASTSTATEMENT_H diff --git a/clang/include/clang/Tooling/Refactoring/Delete/DeleteStatementRule.h b/clang/include/clang/Tooling/Refactoring/Delete/DeleteStatementRule.h new file mode 100644 index 0000000000000..51babee0716c2 --- /dev/null +++ b/clang/include/clang/Tooling/Refactoring/Delete/DeleteStatementRule.h @@ -0,0 +1,41 @@ +//===--- DeleteStatementRule.h - Clang refactoring library ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTORING_DELETE_DELETESTATEMENTRULE_H +#define LLVM_CLANG_TOOLING_REFACTORING_DELETE_DELETESTATEMENTRULE_H + +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" + +namespace clang { +namespace tooling { + +/// A "Delete Statement" refactoring rule deletes code around given statement +class DeleteStatementRule final : public SourceChangeRefactoringRule { +public: + /// Initiates the delete statement refactoring operation. + /// + /// \param Statement Statement to delete. + static Expected + initiate(RefactoringRuleContext &Context, Stmt *Stmt); + + static const RefactoringDescriptor &describe(); + +private: + DeleteStatementRule(Stmt *Stmt) + : Statement(std::move(Stmt)) {} + + Expected + createSourceReplacements(RefactoringRuleContext &Context) override; + + Stmt *Statement; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTORING_DELETE_DELETESTATEMENTRULE_H diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h index c6a6c4f6093a3..374f19d6d8233 100644 --- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h +++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h @@ -56,6 +56,10 @@ class RefactoringActionRule : public RefactoringActionRuleBase { /// to be fulfilled before refactoring can be performed. virtual bool hasSelectionRequirement() = 0; + /// Returns true when the rule has a source location requirement that has + /// to be fulfilled before refactoring can be performed. + virtual bool hasLocationRequirement() = 0; + /// Traverses each refactoring option used by the rule and invokes the /// \c visit callback in the consumer for each option. /// diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h index 1a318da3acca1..2550dd07ba2c9 100644 --- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h +++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Refactoring/ASTSelection.h" #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h" #include "clang/Tooling/Refactoring/RefactoringOption.h" @@ -77,6 +78,25 @@ class CodeRangeASTSelectionRequirement : public ASTSelectionRequirement { evaluate(RefactoringRuleContext &Context) const; }; +/// A base class for any requirement that expects source code position (or the refactoring tool with the -location option). +class SourceLocationRequirement : public RefactoringActionRuleRequirement { +public: + Expected evaluate(RefactoringRuleContext &Context) const { + if (Context.getLocation().isValid()) + return Context.getLocation(); + return Context.createDiagnosticError(diag::err_refactor_no_location); + } +}; + +/// An AST statement requirement is satisfied when location is surrounded by statement. +/// +/// The requirement will be evaluated only once during the initiation and +/// search of matching refactoring action rules. +class ASTStatementRequirement : public SourceLocationRequirement { +public: + Expected evaluate(RefactoringRuleContext &Context) const; +}; + /// A base class for any requirement that requires some refactoring options. class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement { public: diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h index 33194c401ea14..52afb012f4874 100644 --- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h +++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h @@ -139,6 +139,11 @@ createRefactoringActionRule(const RequirementTypes &... Requirements) { RequirementTypes...>::value; } + bool hasLocationRequirement() override { + return internal::HasBaseOf::value; + } + void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) override { internal::visitRefactoringOptions( Visitor, Requirements, diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h index 7d97f811f024e..85bba662afcd2 100644 --- a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h +++ b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h @@ -30,6 +30,9 @@ namespace tooling { /// /// - SelectionRange: an optional source selection ranges that can be used /// to represent a selection in an editor. +/// +/// - Location: an optional source location that can be used +/// to represent a cursor in an editor. class RefactoringRuleContext { public: RefactoringRuleContext(const SourceManager &SM) : SM(SM) {} @@ -40,8 +43,14 @@ class RefactoringRuleContext { /// refactoring engine. Can be invalid. SourceRange getSelectionRange() const { return SelectionRange; } + /// Returns the current source location as set by the + /// refactoring engine. Can be invalid. + SourceLocation getLocation() const { return Location; } + void setSelectionRange(SourceRange R) { SelectionRange = R; } + void setLocation(SourceLocation L) { Location = L; } + bool hasASTContext() const { return AST; } ASTContext &getASTContext() const { @@ -73,6 +82,9 @@ class RefactoringRuleContext { /// An optional source selection range that's commonly used to represent /// a selection in an editor. SourceRange SelectionRange; + /// An optional source location that's commonly used to represent + /// a cursor in an editor. + SourceLocation Location; /// An optional AST for the translation unit on which a refactoring action /// might operate on. ASTContext *AST = nullptr; diff --git a/clang/lib/Tooling/Refactoring/ASTStatement.cpp b/clang/lib/Tooling/Refactoring/ASTStatement.cpp new file mode 100644 index 0000000000000..62c635fe75681 --- /dev/null +++ b/clang/lib/Tooling/Refactoring/ASTStatement.cpp @@ -0,0 +1,58 @@ +//===--- ASTStatement.cpp - Clang refactoring library ---------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/ASTStatement.h" +#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h" + +using namespace clang; +using namespace tooling; + +namespace { + +class ASTStatementFinder + : public LexicallyOrderedRecursiveASTVisitor { +public: + ASTStatementFinder(SourceLocation Location, FileID TargetFile, + const ASTContext &Context) + : LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()), + Location(std::move(Location)), + TargetFile(TargetFile), Context(Context) {} + + bool TraverseStmt(Stmt *Statement) { + if (!Statement) + return true; + const SourceManager &SM = Context.getSourceManager(); + if (SM.isPointWithin(Location, Statement->getBeginLoc(), Statement->getEndLoc())) { + this->Statement = Statement; + } + LexicallyOrderedRecursiveASTVisitor::TraverseStmt(Statement); + return true; + } + + Stmt *getOuterStatement() { + return Statement; + } +private: + const SourceLocation Location; + FileID TargetFile; + const ASTContext &Context; + Stmt *Statement = nullptr; +}; + +} // end anonymous namespace + +Stmt * +clang::tooling::findOuterStmt(const ASTContext &Context, SourceLocation Location) { + assert(Location.isValid() && Location.isFileID() && "Expected a file location"); + + FileID TargetFile = Context.getSourceManager().getFileID(Location); + + ASTStatementFinder Visitor(Location, TargetFile, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + return Visitor.getOuterStatement(); +} diff --git a/clang/lib/Tooling/Refactoring/ASTStatementRequirements.cpp b/clang/lib/Tooling/Refactoring/ASTStatementRequirements.cpp new file mode 100644 index 0000000000000..5e1ba7c240a11 --- /dev/null +++ b/clang/lib/Tooling/Refactoring/ASTStatementRequirements.cpp @@ -0,0 +1,28 @@ +//===--- ASTStatementRequirements.cpp - Clang refactoring library ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" +#include "clang/Tooling/Refactoring/ASTStatement.h" + +using namespace clang; +using namespace tooling; + +Expected +ASTStatementRequirement::evaluate(RefactoringRuleContext &Context) const { + Expected Location = + SourceLocationRequirement::evaluate(Context); + if (!Location) + return Location.takeError(); + + Stmt *Statement = + findOuterStmt(Context.getASTContext(), *Location); + if (Statement == nullptr) + return Context.createDiagnosticError( + *Location, diag::err_refactor_location_no_statement); + return std::move(Statement); +} diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt index d3077be8810aa..2a6bd1a7d83c3 100644 --- a/clang/lib/Tooling/Refactoring/CMakeLists.txt +++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt @@ -3,6 +3,8 @@ set(LLVM_LINK_COMPONENTS Support) add_clang_library(clangToolingRefactoring ASTSelection.cpp ASTSelectionRequirements.cpp + ASTStatement.cpp + ASTStatementRequirements.cpp AtomicChange.cpp Extract/Extract.cpp Extract/SourceExtraction.cpp @@ -13,6 +15,7 @@ add_clang_library(clangToolingRefactoring Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp + Delete/DeleteStatementRule.cpp LINK_LIBS clangAST diff --git a/clang/lib/Tooling/Refactoring/Delete/DeleteStatementRule.cpp b/clang/lib/Tooling/Refactoring/Delete/DeleteStatementRule.cpp new file mode 100644 index 0000000000000..bacade7e9b8a7 --- /dev/null +++ b/clang/lib/Tooling/Refactoring/Delete/DeleteStatementRule.cpp @@ -0,0 +1,62 @@ +//===--- DeleteStatementRule.cpp - Clang refactoring library --------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Implements the "delete-statement" refactoring rule that can delete stmts +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/ASTStatement.h" +#include "clang/Tooling/Refactoring/Delete/DeleteStatementRule.h" +#include "clang/AST/ASTContext.h" +#include "clang/Rewrite/Core/Rewriter.h" + +using namespace clang; +using namespace tooling; + +Expected +DeleteStatementRule::initiate(RefactoringRuleContext &Context, Stmt *Stmt) { + return DeleteStatementRule(std::move(Stmt)); +} + +const RefactoringDescriptor &DeleteStatementRule::describe() { + static const RefactoringDescriptor Descriptor = { + "delete-statement", + "Delete Statement", + "Deletes stmts from code", + }; + return Descriptor; +} + +Expected +DeleteStatementRule::createSourceReplacements(RefactoringRuleContext &Context) { + // Compute the source range of the code that should be deleted. + SourceRange DeleteRange(Statement->getBeginLoc(), + Statement->getEndLoc()); + + ASTContext &AST = Context.getASTContext(); + SourceManager &SM = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + Rewriter DeleteCodeRewriter(SM, LangOpts); + + PrintingPolicy PP = AST.getPrintingPolicy(); + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + AtomicChange Change(SM, Statement->getBeginLoc()); + // Create the replacement for deleting statement + { + auto Err = Change.replace( + SM, CharSourceRange::getTokenRange(DeleteRange), ""); + if (Err) + return std::move(Err); + } + + return AtomicChanges{std::move(Change)}; +} diff --git a/clang/tools/clang-refactor/ClangRefactor.cpp b/clang/tools/clang-refactor/ClangRefactor.cpp index 175a2b8234e9a..d936e56b77b19 100644 --- a/clang/tools/clang-refactor/ClangRefactor.cpp +++ b/clang/tools/clang-refactor/ClangRefactor.cpp @@ -164,6 +164,83 @@ SourceSelectionArgument::fromString(StringRef Value) { return nullptr; } +/// Stores the parsed `-location` argument. +class SourceLocationArgument { +public: + virtual ~SourceLocationArgument() {} + + /// Parse the `-location` argument. + /// + /// \returns A valid argument when the parse succedeed, null otherwise. + static std::unique_ptr fromString(StringRef Value); + + /// Prints any additional state associated with the location argument to + /// the given output stream. + virtual void print(raw_ostream &OS) {} + + /// Returns a replacement refactoring result consumer (if any) that should + /// consume the results of a refactoring operation. + /// + /// The replacement refactoring result consumer is used by \c + /// TestSourceLocationArgument to inject a test-specific result handling + /// logic into the refactoring operation. The test-specific consumer + /// ensures that the individual results in a particular test group are + /// identical. + virtual std::unique_ptr + createCustomConsumer() { + return nullptr; + } + + /// Runs the given refactoring function for each specified location. + /// + /// \returns true if an error occurred, false otherwise. + virtual bool + forAllLocations(const SourceManager &SM, + llvm::function_ref Callback) = 0; +}; + +/// Stores the parsed -location=filename:line:column option. +class SourceLocLocationArgument final : public SourceLocationArgument { +public: + SourceLocLocationArgument(ParsedSourceLocation Location) + : Location(std::move(Location)) {} + + bool forAllLocations(const SourceManager &SM, + llvm::function_ref Callback) override { + auto FE = SM.getFileManager().getFile(Location.FileName); + FileID FID = FE ? SM.translateFile(*FE) : FileID(); + if (!FE || FID.isInvalid()) { + llvm::errs() << "error: -location=" << Location.FileName + << ":... : given file is not in the target TU\n"; + return true; + } + + SourceLocation Loc = SM.getMacroArgExpandedLocation( + SM.translateLineCol(FID, Location.Line, Location.Column)); + if (Loc.isInvalid()) { + llvm::errs() << "error: -location=" << Location.FileName << ':' + << Location.Line << ':' << Location.Column + << " : invalid source location\n"; + return true; + } + Callback(Loc); + return false; + } + +private: + ParsedSourceLocation Location; +}; + +std::unique_ptr +SourceLocationArgument::fromString(StringRef Value) { + std::optional Location = ParsedSourceLocation::FromString(Value); + if (Location) + return std::make_unique(std::move(*Location)); + llvm::errs() << "error: '-location' option must be specified using " + "::\n"; + return nullptr; +} + /// A container that stores the command-line options used by a single /// refactoring option. class RefactoringActionCommandLineOptions { @@ -272,6 +349,18 @@ class RefactoringActionSubcommand : public cl::SubCommand { break; } } + // Check if the location option is supported. + for (const auto &Rule : this->ActionRules) { + if (Rule->hasLocationRequirement()) { + Location = std::make_unique>( + "location", + cl::desc( + "Location where refactoring should " + "be initiated( ::)"), + cl::cat(Category), cl::sub(*this)); + break; + } + } // Create the refactoring options. for (const auto &Rule : this->ActionRules) { CommandLineRefactoringOptionCreator OptionCreator(Category, *this, @@ -296,11 +385,28 @@ class RefactoringActionSubcommand : public cl::SubCommand { return false; } + /// Parses the "-location" command-line argument. + /// + /// \returns true on error, false otherwise. + bool parseLocationArgument() { + if (Location) { + ParsedLocation = SourceLocationArgument::fromString(*Location); + if (!ParsedLocation) + return true; + } + return false; + } + SourceSelectionArgument *getSelection() const { assert(Selection && "selection not supported!"); return ParsedSelection.get(); } + SourceLocationArgument *getLocation() const { + assert(Location && "location not supported!"); + return ParsedLocation.get(); + } + const RefactoringActionCommandLineOptions &getOptions() const { return Options; } @@ -309,7 +415,9 @@ class RefactoringActionSubcommand : public cl::SubCommand { std::unique_ptr Action; RefactoringActionRules ActionRules; std::unique_ptr> Selection; + std::unique_ptr> Location; std::unique_ptr ParsedSelection; + std::unique_ptr ParsedLocation; RefactoringActionCommandLineOptions Options; }; @@ -399,6 +507,7 @@ class ClangRefactorTool { // consumer. std::unique_ptr TestConsumer; bool HasSelection = MatchingRule->hasSelectionRequirement(); + bool HasLocation = MatchingRule->hasLocationRequirement(); if (HasSelection) TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer(); ClangRefactorToolConsumerInterface *ActiveConsumer = @@ -424,6 +533,20 @@ class ClangRefactorTool { ActiveConsumer->endTU(); return; } + if (HasLocation) { + assert(SelectedSubcommand->getLocation() && + "Missing location argument?"); + if (opts::Verbose) + SelectedSubcommand->getLocation()->print(llvm::outs()); + if (SelectedSubcommand->getLocation()->forAllLocations( + Context.getSources(), [&](SourceLocation L) { + Context.setLocation(L); + InvokeRule(*ActiveConsumer); + })) + HasFailed = true; + ActiveConsumer->endTU(); + return; + } InvokeRule(*ActiveConsumer); ActiveConsumer->endTU(); } @@ -528,6 +651,12 @@ class ClangRefactorTool { R.getEnd().print(llvm::outs(), Context.getSources()); llvm::outs() << "\n"; } + if (Context.getLocation().isValid()) { + SourceLocation L = Context.getLocation(); + llvm::outs() << " -location="; + L.print(llvm::outs(), Context.getSources()); + llvm::outs() << "\n"; + } } llvm::Expected @@ -539,16 +668,24 @@ class ClangRefactorTool { CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions()); Rule->visitRefactoringOptions(Visitor); if (Visitor.getMissingRequiredOptions().empty()) { - if (!Rule->hasSelectionRequirement()) { - MatchingRules.push_back(Rule.get()); - } else { + bool HasMissingOptions = false; + if (Rule->hasSelectionRequirement()) { Subcommand.parseSelectionArgument(); - if (Subcommand.getSelection()) { - MatchingRules.push_back(Rule.get()); - } else { + if (!Subcommand.getSelection()) { MissingOptions.insert("selection"); + HasMissingOptions = true; } } + if (Rule->hasLocationRequirement()) { + Subcommand.parseLocationArgument(); + if (!Subcommand.getLocation()) { + MissingOptions.insert("location"); + HasMissingOptions = true; + } + } + if (!HasMissingOptions) { + MatchingRules.push_back(Rule.get()); + } } for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions()) MissingOptions.insert(Opt->getName());