Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticRefactoringKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def err_refactor_extract_simple_expression : Error<"the selected expression "
def err_refactor_extract_prohibited_expression : Error<"the selected "
"expression cannot be extracted">;

def err_refactor_no_location : Error<"refactoring action can't be initiated "
"without a location">;
def err_refactor_no_location_match : Error<"refactoring action can't be initiated "
"without a matching location">;
def err_refactor_invalid_edit_generator : Error<"refactoring action can't be initiated "
"without a correct edit generator">;
}

} // end of Refactoring diagnostics
49 changes: 49 additions & 0 deletions clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===--- EditMatchRule.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_EDIT_EDITMATCHRULE_H
#define LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H

#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
#include "clang/Tooling/Transformer/RewriteRule.h"

namespace clang {
namespace tooling {

/// A "Edit Match" refactoring rule edits code around matches according to
/// EditGenerator.
class EditMatchRule final : public SourceChangeRefactoringRule {
public:
/// Initiates the delete match refactoring operation.
///
/// \param R MatchResult Match result to edit.
/// \param EG EditGenerator Edit to perform.
static Expected<EditMatchRule>
initiate(RefactoringRuleContext &Context,
ast_matchers::MatchFinder::MatchResult R,
transformer::EditGenerator EG);

static const RefactoringDescriptor &describe();

private:
EditMatchRule(ast_matchers::MatchFinder::MatchResult R,
transformer::EditGenerator EG)
: Result(std::move(R)), EditGenerator(std::move(EG)) {}

Expected<AtomicChanges>
createSourceReplacements(RefactoringRuleContext &Context) override;

ast_matchers::MatchFinder::MatchResult Result;
transformer::EditGenerator EditGenerator;
};

} // end namespace tooling
} // end namespace clang

#endif // LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
#ifndef LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
#define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H

#include "clang/AST/ASTTypeTraits.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.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"
#include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "llvm/Support/Error.h"
#include <type_traits>

namespace clang {
namespace tooling {
Expand Down Expand Up @@ -77,6 +82,101 @@ 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<SourceLocation> evaluate(RefactoringRuleContext &Context) const {
if (Context.getLocation().isValid())
return Context.getLocation();
return Context.createDiagnosticError(diag::err_refactor_no_location);
}
};

AST_POLYMORPHIC_MATCHER_P(hasPointWithin,
AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl),
FullSourceLoc, L) {
if (!L.hasManager()) {
return false;
}
const SourceRange &SR = Node.getSourceRange();
return L.getManager().isPointWithin(L, SR.getBegin(), SR.getEnd());
}

/// An AST location match is satisfied when there is match around given
/// location. In case of several matches inner one is taken.
///
/// The requirement will be evaluated only once during the initiation and
/// search of matching refactoring action rules.
template <typename MatcherType>
class ASTLocMatchRequirement : public SourceLocationRequirement {
public:
static_assert(
std::is_same<ast_matchers::StatementMatcher, MatcherType>::value ||
std::is_same<ast_matchers::DeclarationMatcher, MatcherType>::value,
"Expected a Statement or Declaration matcher");

class LocMatchCallback : public ast_matchers::MatchFinder::MatchCallback {
public:
void run(const clang::ast_matchers::MatchFinder::MatchResult &R) override {
Result = std::make_unique<ast_matchers::MatchFinder::MatchResult>(R);
}
std::unique_ptr<ast_matchers::MatchFinder::MatchResult> Result;
};

Expected<ast_matchers::MatchFinder::MatchResult>
evaluate(RefactoringRuleContext &Context) const {
Expected<SourceLocation> Location =
SourceLocationRequirement::evaluate(Context);
if (!Location)
return Location.takeError();
MatcherType M = createWrapperMatcher(
FullSourceLoc(*Location, Context.getASTContext().getSourceManager()),
Matcher);

ast_matchers::MatchFinder MF;
LocMatchCallback Callback;
MF.addMatcher(M, &Callback);
MF.matchAST(Context.getASTContext());
if (!Callback.Result)
return Context.createDiagnosticError(
diag::err_refactor_no_location_match);
return *Callback.Result;
}

ASTLocMatchRequirement(MatcherType M) : Matcher(M) {}

private:
ast_matchers::StatementMatcher
createWrapperMatcher(FullSourceLoc L,
ast_matchers::StatementMatcher M) const {
return ast_matchers::stmt(M, hasPointWithin(L)).bind(transformer::RootID);
}

ast_matchers::DeclarationMatcher
createWrapperMatcher(FullSourceLoc L,
ast_matchers::DeclarationMatcher M) const {
return ast_matchers::decl(M, hasPointWithin(L)).bind(transformer::RootID);
}

MatcherType Matcher;
};

/// Requirement that evaluates to the EditGenerator value given at its creation.
class EditGeneratorRequirement : public RefactoringActionRuleRequirement {
public:
Expected<transformer::EditGenerator>
evaluate(RefactoringRuleContext &Context) const {
return EditGenerator;
}

EditGeneratorRequirement(transformer::EditGenerator EG)
: EditGenerator(std::move(EG)) {}

private:
transformer::EditGenerator EditGenerator;
};

/// A base class for any requirement that requires some refactoring options.
class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement {
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ createRefactoringActionRule(const RequirementTypes &... Requirements) {
RequirementTypes...>::value;
}

bool hasLocationRequirement() override {
return internal::HasBaseOf<SourceLocationRequirement,
RequirementTypes...>::value;
}

void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) override {
internal::visitRefactoringOptions(
Visitor, Requirements,
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Tooling/Refactoring/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_clang_library(clangToolingRefactoring
Rename/USRFinder.cpp
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
Edit/EditMatchRule.cpp

LINK_LIBS
clangAST
Expand All @@ -23,6 +24,7 @@ add_clang_library(clangToolingRefactoring
clangLex
clangRewrite
clangToolingCore
clangTransformer

DEPENDS
omp_gen
Expand Down
74 changes: 74 additions & 0 deletions clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//===--- EditMatchRule.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 "edit-match" refactoring rule that can edit matcher results
///
//===----------------------------------------------------------------------===//

#include "clang/Tooling/Refactoring/Edit/EditMatchRule.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/DiagnosticRefactoring.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/Refactoring/AtomicChange.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/SourceCode.h"

using namespace clang;
using namespace tooling;
using namespace transformer;

Expected<EditMatchRule>
EditMatchRule::initiate(RefactoringRuleContext &Context,
ast_matchers::MatchFinder::MatchResult R,
transformer::EditGenerator EG) {
return EditMatchRule(std::move(R), std::move(EG));
}

const RefactoringDescriptor &EditMatchRule::describe() {
static const RefactoringDescriptor Descriptor = {
"edit-match",
"Edit Match",
"Edits match result source code",
};
return Descriptor;
}

Expected<AtomicChanges>
EditMatchRule::createSourceReplacements(RefactoringRuleContext &Context) {
ASTContext &AST = Context.getASTContext();
SourceManager &SM = AST.getSourceManager();
Expected<SmallVector<transformer::Edit, 1>> Edits = EditGenerator(Result);

if (!Edits) {
return std::move(Edits.takeError());
}
if (Edits->empty())
return Context.createDiagnosticError(
diag::err_refactor_invalid_edit_generator);

AtomicChange Change(SM, Edits->front().Range.getBegin());
{
for (const auto &Edit : *Edits) {
switch (Edit.Kind) {
case EditKind::Range:
if (auto Err = Change.replace(SM, std::move(Edit.Range),
std::move(Edit.Replacement))) {
return std::move(Err);
}
break;
case EditKind::AddInclude:
Change.addHeader(std::move(Edit.Replacement));
break;
}
}
}

return AtomicChanges{std::move(Change)};
}
Loading
Loading