Imported Upstream version 6.10.0.49

Former-commit-id: 1d6753294b2993e1fbf92de9366bb9544db4189b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2020-01-16 16:38:04 +00:00
parent d94e79959b
commit 468663ddbb
48518 changed files with 2789335 additions and 61176 deletions

View File

@@ -0,0 +1,453 @@
//===--- ASTSelection.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.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/ASTSelection.h"
#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h"
#include "clang/Lex/Lexer.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace tooling;
using ast_type_traits::DynTypedNode;
namespace {
CharSourceRange getLexicalDeclRange(Decl *D, const SourceManager &SM,
const LangOptions &LangOpts) {
if (!isa<ObjCImplDecl>(D))
return CharSourceRange::getTokenRange(D->getSourceRange());
// Objective-C implementation declarations end at the '@' instead of the 'end'
// keyword. Use the lexer to find the location right after 'end'.
SourceRange R = D->getSourceRange();
SourceLocation LocAfterEnd = Lexer::findLocationAfterToken(
R.getEnd(), tok::raw_identifier, SM, LangOpts,
/*SkipTrailingWhitespaceAndNewLine=*/false);
return LocAfterEnd.isValid()
? CharSourceRange::getCharRange(R.getBegin(), LocAfterEnd)
: CharSourceRange::getTokenRange(R);
}
/// Constructs the tree of selected AST nodes that either contain the location
/// of the cursor or overlap with the selection range.
class ASTSelectionFinder
: public LexicallyOrderedRecursiveASTVisitor<ASTSelectionFinder> {
public:
ASTSelectionFinder(SourceRange Selection, FileID TargetFile,
const ASTContext &Context)
: LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()),
SelectionBegin(Selection.getBegin()),
SelectionEnd(Selection.getBegin() == Selection.getEnd()
? SourceLocation()
: Selection.getEnd()),
TargetFile(TargetFile), Context(Context) {
// The TU decl is the root of the selected node tree.
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()),
SourceSelectionKind::None));
}
Optional<SelectedASTNode> getSelectedASTNode() {
assert(SelectionStack.size() == 1 && "stack was not popped");
SelectedASTNode Result = std::move(SelectionStack.back());
SelectionStack.pop_back();
if (Result.Children.empty())
return None;
return std::move(Result);
}
bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
// Avoid traversing the semantic expressions. They should be handled by
// looking through the appropriate opaque expressions in order to build
// a meaningful selection tree.
llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, true);
return TraverseStmt(E->getSyntacticForm());
}
bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) {
if (!LookThroughOpaqueValueExprs)
return true;
llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, false);
return TraverseStmt(E->getSourceExpr());
}
bool TraverseDecl(Decl *D) {
if (isa<TranslationUnitDecl>(D))
return LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);
if (D->isImplicit())
return true;
// Check if this declaration is written in the file of interest.
const SourceRange DeclRange = D->getSourceRange();
const SourceManager &SM = Context.getSourceManager();
SourceLocation FileLoc;
if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID())
FileLoc = DeclRange.getEnd();
else
FileLoc = SM.getSpellingLoc(DeclRange.getBegin());
if (SM.getFileID(FileLoc) != TargetFile)
return true;
SourceSelectionKind SelectionKind =
selectionKindFor(getLexicalDeclRange(D, SM, Context.getLangOpts()));
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*D), SelectionKind));
LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);
popAndAddToSelectionIfSelected(SelectionKind);
if (DeclRange.getEnd().isValid() &&
SM.isBeforeInTranslationUnit(SelectionEnd.isValid() ? SelectionEnd
: SelectionBegin,
DeclRange.getEnd())) {
// Stop early when we've reached a declaration after the selection.
return false;
}
return true;
}
bool TraverseStmt(Stmt *S) {
if (!S)
return true;
if (auto *Opaque = dyn_cast<OpaqueValueExpr>(S))
return TraverseOpaqueValueExpr(Opaque);
// Avoid selecting implicit 'this' expressions.
if (auto *TE = dyn_cast<CXXThisExpr>(S)) {
if (TE->isImplicit())
return true;
}
// FIXME (Alex Lorenz): Improve handling for macro locations.
SourceSelectionKind SelectionKind =
selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange()));
SelectionStack.push_back(
SelectedASTNode(DynTypedNode::create(*S), SelectionKind));
LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S);
popAndAddToSelectionIfSelected(SelectionKind);
return true;
}
private:
void popAndAddToSelectionIfSelected(SourceSelectionKind SelectionKind) {
SelectedASTNode Node = std::move(SelectionStack.back());
SelectionStack.pop_back();
if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty())
SelectionStack.back().Children.push_back(std::move(Node));
}
SourceSelectionKind selectionKindFor(CharSourceRange Range) {
SourceLocation End = Range.getEnd();
const SourceManager &SM = Context.getSourceManager();
if (Range.isTokenRange())
End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts());
if (!SourceLocation::isPairOfFileLocations(Range.getBegin(), End))
return SourceSelectionKind::None;
if (!SelectionEnd.isValid()) {
// Do a quick check when the selection is of length 0.
if (SM.isPointWithin(SelectionBegin, Range.getBegin(), End))
return SourceSelectionKind::ContainsSelection;
return SourceSelectionKind::None;
}
bool HasStart = SM.isPointWithin(SelectionBegin, Range.getBegin(), End);
bool HasEnd = SM.isPointWithin(SelectionEnd, Range.getBegin(), End);
if (HasStart && HasEnd)
return SourceSelectionKind::ContainsSelection;
if (SM.isPointWithin(Range.getBegin(), SelectionBegin, SelectionEnd) &&
SM.isPointWithin(End, SelectionBegin, SelectionEnd))
return SourceSelectionKind::InsideSelection;
// Ensure there's at least some overlap with the 'start'/'end' selection
// types.
if (HasStart && SelectionBegin != End)
return SourceSelectionKind::ContainsSelectionStart;
if (HasEnd && SelectionEnd != Range.getBegin())
return SourceSelectionKind::ContainsSelectionEnd;
return SourceSelectionKind::None;
}
const SourceLocation SelectionBegin, SelectionEnd;
FileID TargetFile;
const ASTContext &Context;
std::vector<SelectedASTNode> SelectionStack;
/// Controls whether we can traverse through the OpaqueValueExpr. This is
/// typically enabled during the traversal of syntactic form for
/// PseudoObjectExprs.
bool LookThroughOpaqueValueExprs = false;
};
} // end anonymous namespace
Optional<SelectedASTNode>
clang::tooling::findSelectedASTNodes(const ASTContext &Context,
SourceRange SelectionRange) {
assert(SelectionRange.isValid() &&
SourceLocation::isPairOfFileLocations(SelectionRange.getBegin(),
SelectionRange.getEnd()) &&
"Expected a file range");
FileID TargetFile =
Context.getSourceManager().getFileID(SelectionRange.getBegin());
assert(Context.getSourceManager().getFileID(SelectionRange.getEnd()) ==
TargetFile &&
"selection range must span one file");
ASTSelectionFinder Visitor(SelectionRange, TargetFile, Context);
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
return Visitor.getSelectedASTNode();
}
static const char *selectionKindToString(SourceSelectionKind Kind) {
switch (Kind) {
case SourceSelectionKind::None:
return "none";
case SourceSelectionKind::ContainsSelection:
return "contains-selection";
case SourceSelectionKind::ContainsSelectionStart:
return "contains-selection-start";
case SourceSelectionKind::ContainsSelectionEnd:
return "contains-selection-end";
case SourceSelectionKind::InsideSelection:
return "inside";
}
llvm_unreachable("invalid selection kind");
}
static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS,
unsigned Indent = 0) {
OS.indent(Indent * 2);
if (const Decl *D = Node.Node.get<Decl>()) {
OS << D->getDeclKindName() << "Decl";
if (const auto *ND = dyn_cast<NamedDecl>(D))
OS << " \"" << ND->getNameAsString() << '"';
} else if (const Stmt *S = Node.Node.get<Stmt>()) {
OS << S->getStmtClassName();
}
OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n";
for (const auto &Child : Node.Children)
dump(Child, OS, Indent + 1);
}
void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); }
/// Returns true if the given node has any direct children with the following
/// selection kind.
///
/// Note: The direct children also include children of direct children with the
/// "None" selection kind.
static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node,
SourceSelectionKind Kind) {
assert(Kind != SourceSelectionKind::None && "invalid predicate!");
for (const auto &Child : Node.Children) {
if (Child.SelectionKind == Kind)
return true;
if (Child.SelectionKind == SourceSelectionKind::None)
return hasAnyDirectChildrenWithKind(Child, Kind);
}
return false;
}
namespace {
struct SelectedNodeWithParents {
SelectedNodeWithParents(SelectedNodeWithParents &&) = default;
SelectedNodeWithParents &operator=(SelectedNodeWithParents &&) = default;
SelectedASTNode::ReferenceType Node;
llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents;
/// Canonicalizes the given selection by selecting different related AST nodes
/// when it makes sense to do so.
void canonicalize();
};
enum SelectionCanonicalizationAction { KeepSelection, SelectParent };
/// Returns the canonicalization action which should be applied to the
/// selected statement.
SelectionCanonicalizationAction
getSelectionCanonizalizationAction(const Stmt *S, const Stmt *Parent) {
// Select the parent expression when:
// - The string literal in ObjC string literal is selected, e.g.:
// @"test" becomes @"test"
// ~~~~~~ ~~~~~~~
if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent))
return SelectParent;
// The entire call should be selected when just the member expression
// that refers to the method or the decl ref that refers to the function
// is selected.
// f.call(args) becomes f.call(args)
// ~~~~ ~~~~~~~~~~~~
// func(args) becomes func(args)
// ~~~~ ~~~~~~~~~~
else if (const auto *CE = dyn_cast<CallExpr>(Parent)) {
if ((isa<MemberExpr>(S) || isa<DeclRefExpr>(S)) &&
CE->getCallee()->IgnoreImpCasts() == S)
return SelectParent;
}
// FIXME: Syntactic form -> Entire pseudo-object expr.
return KeepSelection;
}
} // end anonymous namespace
void SelectedNodeWithParents::canonicalize() {
const Stmt *S = Node.get().Node.get<Stmt>();
assert(S && "non statement selection!");
const Stmt *Parent = Parents[Parents.size() - 1].get().Node.get<Stmt>();
if (!Parent)
return;
// Look through the implicit casts in the parents.
unsigned ParentIndex = 1;
for (; (ParentIndex + 1) <= Parents.size() && isa<ImplicitCastExpr>(Parent);
++ParentIndex) {
const Stmt *NewParent =
Parents[Parents.size() - ParentIndex - 1].get().Node.get<Stmt>();
if (!NewParent)
break;
Parent = NewParent;
}
switch (getSelectionCanonizalizationAction(S, Parent)) {
case SelectParent:
Node = Parents[Parents.size() - ParentIndex];
for (; ParentIndex != 0; --ParentIndex)
Parents.pop_back();
break;
case KeepSelection:
break;
}
}
/// Finds the set of bottom-most selected AST nodes that are in the selection
/// tree with the specified selection kind.
///
/// For example, given the following selection tree:
///
/// FunctionDecl "f" contains-selection
/// CompoundStmt contains-selection [#1]
/// CallExpr inside
/// ImplicitCastExpr inside
/// DeclRefExpr inside
/// IntegerLiteral inside
/// IntegerLiteral inside
/// FunctionDecl "f2" contains-selection
/// CompoundStmt contains-selection [#2]
/// CallExpr inside
/// ImplicitCastExpr inside
/// DeclRefExpr inside
/// IntegerLiteral inside
/// IntegerLiteral inside
///
/// This function will find references to nodes #1 and #2 when searching for the
/// \c ContainsSelection kind.
static void findDeepestWithKind(
const SelectedASTNode &ASTSelection,
llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
SourceSelectionKind Kind,
llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) {
if (ASTSelection.Node.get<DeclStmt>()) {
// Select the entire decl stmt when any of its child declarations is the
// bottom-most.
for (const auto &Child : ASTSelection.Children) {
if (!hasAnyDirectChildrenWithKind(Child, Kind)) {
MatchingNodes.push_back(SelectedNodeWithParents{
std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});
return;
}
}
} else {
if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) {
// This node is the bottom-most.
MatchingNodes.push_back(SelectedNodeWithParents{
std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});
return;
}
}
// Search in the children.
ParentStack.push_back(std::cref(ASTSelection));
for (const auto &Child : ASTSelection.Children)
findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack);
ParentStack.pop_back();
}
static void findDeepestWithKind(
const SelectedASTNode &ASTSelection,
llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,
SourceSelectionKind Kind) {
llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack;
findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack);
}
Optional<CodeRangeASTSelection>
CodeRangeASTSelection::create(SourceRange SelectionRange,
const SelectedASTNode &ASTSelection) {
// Code range is selected when the selection range is not empty.
if (SelectionRange.getBegin() == SelectionRange.getEnd())
return None;
llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection;
findDeepestWithKind(ASTSelection, ContainSelection,
SourceSelectionKind::ContainsSelection);
// We are looking for a selection in one body of code, so let's focus on
// one matching result.
if (ContainSelection.size() != 1)
return None;
SelectedNodeWithParents &Selected = ContainSelection[0];
if (!Selected.Node.get().Node.get<Stmt>())
return None;
const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>();
if (!isa<CompoundStmt>(CodeRangeStmt)) {
Selected.canonicalize();
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/false);
}
// FIXME (Alex L): First selected SwitchCase means that first case statement.
// is selected actually
// (See https://github.com/apple/swift-clang & CompoundStmtRange).
// FIXME (Alex L): Tweak selection rules for compound statements, see:
// https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/
// Refactor/ASTSlice.cpp#L513
// The user selected multiple statements in a compound statement.
Selected.Parents.push_back(Selected.Node);
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/true);
}
static bool isFunctionLikeDeclaration(const Decl *D) {
// FIXME (Alex L): Test for BlockDecl.
return isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D);
}
bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const {
bool IsPrevCompound = false;
// Scan through the parents (bottom-to-top) and check if the selection is
// contained in a compound statement that's a body of a function/method
// declaration.
for (const auto &Parent : llvm::reverse(Parents)) {
const DynTypedNode &Node = Parent.get().Node;
if (const auto *D = Node.get<Decl>()) {
if (isFunctionLikeDeclaration(D))
return IsPrevCompound;
// Stop the search at any type declaration to avoid returning true for
// expressions in type declarations in functions, like:
// function foo() { struct X {
// int m = /*selection:*/ 1 + 2 /*selection end*/; }; };
if (isa<TypeDecl>(D))
return false;
}
IsPrevCompound = Node.get<CompoundStmt>() != nullptr;
}
return false;
}
const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const {
for (const auto &Parent : llvm::reverse(Parents)) {
const DynTypedNode &Node = Parent.get().Node;
if (const auto *D = Node.get<Decl>()) {
if (isFunctionLikeDeclaration(D))
return D;
}
}
return nullptr;
}

View File

@@ -0,0 +1,48 @@
//===--- ASTSelectionRequirements.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.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h"
using namespace clang;
using namespace tooling;
Expected<SelectedASTNode>
ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const {
// FIXME: Memoize so that selection is evaluated only once.
Expected<SourceRange> Range =
SourceRangeSelectionRequirement::evaluate(Context);
if (!Range)
return Range.takeError();
Optional<SelectedASTNode> Selection =
findSelectedASTNodes(Context.getASTContext(), *Range);
if (!Selection)
return Context.createDiagnosticError(
Range->getBegin(), diag::err_refactor_selection_invalid_ast);
return std::move(*Selection);
}
Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate(
RefactoringRuleContext &Context) const {
// FIXME: Memoize so that selection is evaluated only once.
Expected<SelectedASTNode> ASTSelection =
ASTSelectionRequirement::evaluate(Context);
if (!ASTSelection)
return ASTSelection.takeError();
std::unique_ptr<SelectedASTNode> StoredSelection =
llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection));
Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create(
Context.getSelectionRange(), *StoredSelection);
if (!CodeRange)
return Context.createDiagnosticError(
Context.getSelectionRange().getBegin(),
diag::err_refactor_selection_invalid_ast);
Context.setASTSelection(std::move(StoredSelection));
return std::move(*CodeRange);
}

View File

@@ -0,0 +1,365 @@
//===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/AtomicChange.h"
#include "clang/Tooling/ReplacementsYaml.h"
#include "llvm/Support/YAMLTraits.h"
#include <string>
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
namespace {
/// \brief Helper to (de)serialize an AtomicChange since we don't have direct
/// access to its data members.
/// Data members of a normalized AtomicChange can be directly mapped from/to
/// YAML string.
struct NormalizedAtomicChange {
NormalizedAtomicChange() = default;
NormalizedAtomicChange(const llvm::yaml::IO &) {}
// This converts AtomicChange's internal implementation of the replacements
// set to a vector of replacements.
NormalizedAtomicChange(const llvm::yaml::IO &,
const clang::tooling::AtomicChange &E)
: Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
InsertedHeaders(E.getInsertedHeaders()),
RemovedHeaders(E.getRemovedHeaders()),
Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
// This is not expected to be called but needed for template instantiation.
clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
"Use AtomicChange::convertFromYAML instead.");
}
std::string Key;
std::string FilePath;
std::string Error;
std::vector<std::string> InsertedHeaders;
std::vector<std::string> RemovedHeaders;
std::vector<clang::tooling::Replacement> Replaces;
};
} // anonymous namespace
namespace llvm {
namespace yaml {
/// \brief Specialized MappingTraits to describe how an AtomicChange is
/// (de)serialized.
template <> struct MappingTraits<NormalizedAtomicChange> {
static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
Io.mapRequired("Key", Doc.Key);
Io.mapRequired("FilePath", Doc.FilePath);
Io.mapRequired("Error", Doc.Error);
Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
Io.mapRequired("Replacements", Doc.Replaces);
}
};
/// \brief Specialized MappingTraits to describe how an AtomicChange is
/// (de)serialized.
template <> struct MappingTraits<clang::tooling::AtomicChange> {
static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
Keys(Io, Doc);
Io.mapRequired("Key", Keys->Key);
Io.mapRequired("FilePath", Keys->FilePath);
Io.mapRequired("Error", Keys->Error);
Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
Io.mapRequired("Replacements", Keys->Replaces);
}
};
} // end namespace yaml
} // end namespace llvm
namespace clang {
namespace tooling {
namespace {
// Returns true if there is any line that violates \p ColumnLimit in range
// [Start, End].
bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit,
unsigned Start, unsigned End) {
auto StartPos = Code.rfind('\n', Start);
StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1;
auto EndPos = Code.find("\n", End);
if (EndPos == llvm::StringRef::npos)
EndPos = Code.size();
llvm::SmallVector<llvm::StringRef, 8> Lines;
Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n');
for (llvm::StringRef Line : Lines)
if (Line.size() > ColumnLimit)
return true;
return false;
}
std::vector<Range>
getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit,
ApplyChangesSpec::FormatOption Format,
const clang::tooling::Replacements &Replaces) {
// kNone suppresses formatting entirely.
if (Format == ApplyChangesSpec::kNone)
return {};
std::vector<clang::tooling::Range> Ranges;
// This works assuming that replacements are ordered by offset.
// FIXME: use `getAffectedRanges()` to calculate when it does not include '\n'
// at the end of an insertion in affected ranges.
int Offset = 0;
for (const clang::tooling::Replacement &R : Replaces) {
int Start = R.getOffset() + Offset;
int End = Start + R.getReplacementText().size();
if (!R.getReplacementText().empty() &&
R.getReplacementText().back() == '\n' && R.getLength() == 0 &&
R.getOffset() > 0 && R.getOffset() <= Code.size() &&
Code[R.getOffset() - 1] == '\n')
// If we are inserting at the start of a line and the replacement ends in
// a newline, we don't need to format the subsequent line.
--End;
Offset += R.getReplacementText().size() - R.getLength();
if (Format == ApplyChangesSpec::kAll ||
violatesColumnLimit(Code, ColumnLimit, Start, End))
Ranges.emplace_back(Start, End - Start);
}
return Ranges;
}
inline llvm::Error make_string_error(const llvm::Twine &Message) {
return llvm::make_error<llvm::StringError>(Message,
llvm::inconvertibleErrorCode());
}
// Creates replacements for inserting/deleting #include headers.
llvm::Expected<Replacements>
createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code,
llvm::ArrayRef<AtomicChange> Changes,
const format::FormatStyle &Style) {
// Create header insertion/deletion replacements to be cleaned up
// (i.e. converted to real insertion/deletion replacements).
Replacements HeaderReplacements;
for (const auto &Change : Changes) {
for (llvm::StringRef Header : Change.getInsertedHeaders()) {
std::string EscapedHeader =
Header.startswith("<") || Header.startswith("\"")
? Header.str()
: ("\"" + Header + "\"").str();
std::string ReplacementText = "#include " + EscapedHeader;
// Offset UINT_MAX and length 0 indicate that the replacement is a header
// insertion.
llvm::Error Err = HeaderReplacements.add(
tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText));
if (Err)
return std::move(Err);
}
for (const std::string &Header : Change.getRemovedHeaders()) {
// Offset UINT_MAX and length 1 indicate that the replacement is a header
// deletion.
llvm::Error Err =
HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header));
if (Err)
return std::move(Err);
}
}
// cleanupAroundReplacements() converts header insertions/deletions into
// actual replacements that add/remove headers at the right location.
return clang::format::cleanupAroundReplacements(Code, HeaderReplacements,
Style);
}
// Combine replacements in all Changes as a `Replacements`. This ignores the
// file path in all replacements and replaces them with \p FilePath.
llvm::Expected<Replacements>
combineReplacementsInChanges(llvm::StringRef FilePath,
llvm::ArrayRef<AtomicChange> Changes) {
Replacements Replaces;
for (const auto &Change : Changes)
for (const auto &R : Change.getReplacements())
if (auto Err = Replaces.add(Replacement(
FilePath, R.getOffset(), R.getLength(), R.getReplacementText())))
return std::move(Err);
return Replaces;
}
} // end namespace
AtomicChange::AtomicChange(const SourceManager &SM,
SourceLocation KeyPosition) {
const FullSourceLoc FullKeyPosition(KeyPosition, SM);
std::pair<FileID, unsigned> FileIDAndOffset =
FullKeyPosition.getSpellingLoc().getDecomposedLoc();
const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
assert(FE && "Cannot create AtomicChange with invalid location.");
FilePath = FE->getName();
Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
}
AtomicChange::AtomicChange(std::string Key, std::string FilePath,
std::string Error,
std::vector<std::string> InsertedHeaders,
std::vector<std::string> RemovedHeaders,
clang::tooling::Replacements Replaces)
: Key(std::move(Key)), FilePath(std::move(FilePath)),
Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
}
bool AtomicChange::operator==(const AtomicChange &Other) const {
if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
return false;
if (!(Replaces == Other.Replaces))
return false;
// FXIME: Compare header insertions/removals.
return true;
}
std::string AtomicChange::toYAMLString() {
std::string YamlContent;
llvm::raw_string_ostream YamlContentStream(YamlContent);
llvm::yaml::Output YAML(YamlContentStream);
YAML << *this;
YamlContentStream.flush();
return YamlContent;
}
AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
NormalizedAtomicChange NE;
llvm::yaml::Input YAML(YAMLContent);
YAML >> NE;
AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
NE.RemovedHeaders, tooling::Replacements());
for (const auto &R : NE.Replaces) {
llvm::Error Err = E.Replaces.add(R);
if (Err)
llvm_unreachable(
"Failed to add replacement when Converting YAML to AtomicChange.");
llvm::consumeError(std::move(Err));
}
return E;
}
llvm::Error AtomicChange::replace(const SourceManager &SM,
const CharSourceRange &Range,
llvm::StringRef ReplacementText) {
return Replaces.add(Replacement(SM, Range, ReplacementText));
}
llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc,
unsigned Length, llvm::StringRef Text) {
return Replaces.add(Replacement(SM, Loc, Length, Text));
}
llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
llvm::StringRef Text, bool InsertAfter) {
if (Text.empty())
return llvm::Error::success();
Replacement R(SM, Loc, 0, Text);
llvm::Error Err = Replaces.add(R);
if (Err) {
return llvm::handleErrors(
std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
if (RE.get() != replacement_error::insert_conflict)
return llvm::make_error<ReplacementError>(RE);
unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
if (!InsertAfter)
NewOffset -=
RE.getExistingReplacement()->getReplacementText().size();
Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
Replaces = Replaces.merge(Replacements(NewR));
return llvm::Error::success();
});
}
return llvm::Error::success();
}
void AtomicChange::addHeader(llvm::StringRef Header) {
InsertedHeaders.push_back(Header);
}
void AtomicChange::removeHeader(llvm::StringRef Header) {
RemovedHeaders.push_back(Header);
}
llvm::Expected<std::string>
applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
llvm::ArrayRef<AtomicChange> Changes,
const ApplyChangesSpec &Spec) {
llvm::Expected<Replacements> HeaderReplacements =
createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style);
if (!HeaderReplacements)
return make_string_error(
"Failed to create replacements for header changes: " +
llvm::toString(HeaderReplacements.takeError()));
llvm::Expected<Replacements> Replaces =
combineReplacementsInChanges(FilePath, Changes);
if (!Replaces)
return make_string_error("Failed to combine replacements in all changes: " +
llvm::toString(Replaces.takeError()));
Replacements AllReplaces = std::move(*Replaces);
for (const auto &R : *HeaderReplacements) {
llvm::Error Err = AllReplaces.add(R);
if (Err)
return make_string_error(
"Failed to combine existing replacements with header replacements: " +
llvm::toString(std::move(Err)));
}
if (Spec.Cleanup) {
llvm::Expected<Replacements> CleanReplaces =
format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style);
if (!CleanReplaces)
return make_string_error("Failed to cleanup around replacements: " +
llvm::toString(CleanReplaces.takeError()));
AllReplaces = std::move(*CleanReplaces);
}
// Apply all replacements.
llvm::Expected<std::string> ChangedCode =
applyAllReplacements(Code, AllReplaces);
if (!ChangedCode)
return make_string_error("Failed to apply all replacements: " +
llvm::toString(ChangedCode.takeError()));
// Sort inserted headers. This is done even if other formatting is turned off
// as incorrectly sorted headers are always just wrong, it's not a matter of
// taste.
Replacements HeaderSortingReplacements = format::sortIncludes(
Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath);
ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements);
if (!ChangedCode)
return make_string_error(
"Failed to apply replacements for sorting includes: " +
llvm::toString(ChangedCode.takeError()));
AllReplaces = AllReplaces.merge(HeaderSortingReplacements);
std::vector<Range> FormatRanges = getRangesForFormating(
*ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces);
if (!FormatRanges.empty()) {
Replacements FormatReplacements =
format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath);
ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements);
if (!ChangedCode)
return make_string_error(
"Failed to apply replacements for formatting changed code: " +
llvm::toString(ChangedCode.takeError()));
}
return ChangedCode;
}
} // end namespace tooling
} // end namespace clang

View File

@@ -0,0 +1,25 @@
set(LLVM_LINK_COMPONENTS Support)
add_clang_library(clangToolingRefactor
ASTSelection.cpp
ASTSelectionRequirements.cpp
AtomicChange.cpp
Extract/Extract.cpp
Extract/SourceExtraction.cpp
RefactoringActions.cpp
Rename/RenamingAction.cpp
Rename/SymbolOccurrences.cpp
Rename/USRFinder.cpp
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
LINK_LIBS
clangAST
clangASTMatchers
clangBasic
clangFormat
clangIndex
clangLex
clangRewrite
clangToolingCore
)

View File

@@ -0,0 +1,199 @@
//===--- 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

View File

@@ -0,0 +1,112 @@
//===--- SourceExtraction.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.
//
//===----------------------------------------------------------------------===//
#include "SourceExtraction.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtObjC.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
using namespace clang;
namespace {
/// Returns true if the token at the given location is a semicolon.
bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM,
const LangOptions &LangOpts) {
return Lexer::getSourceText(
CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM,
LangOpts) == ";";
}
/// Returns true if there should be a semicolon after the given statement.
bool isSemicolonRequiredAfter(const Stmt *S) {
if (isa<CompoundStmt>(S))
return false;
if (const auto *If = dyn_cast<IfStmt>(S))
return isSemicolonRequiredAfter(If->getElse() ? If->getElse()
: If->getThen());
if (const auto *While = dyn_cast<WhileStmt>(S))
return isSemicolonRequiredAfter(While->getBody());
if (const auto *For = dyn_cast<ForStmt>(S))
return isSemicolonRequiredAfter(For->getBody());
if (const auto *CXXFor = dyn_cast<CXXForRangeStmt>(S))
return isSemicolonRequiredAfter(CXXFor->getBody());
if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(S))
return isSemicolonRequiredAfter(ObjCFor->getBody());
switch (S->getStmtClass()) {
case Stmt::SwitchStmtClass:
case Stmt::CXXTryStmtClass:
case Stmt::ObjCAtSynchronizedStmtClass:
case Stmt::ObjCAutoreleasePoolStmtClass:
case Stmt::ObjCAtTryStmtClass:
return false;
default:
return true;
}
}
/// Returns true if the two source locations are on the same line.
bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2,
const SourceManager &SM) {
return !Loc1.isMacroID() && !Loc2.isMacroID() &&
SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2);
}
} // end anonymous namespace
namespace clang {
namespace tooling {
ExtractionSemicolonPolicy
ExtractionSemicolonPolicy::compute(const Stmt *S, SourceRange &ExtractedRange,
const SourceManager &SM,
const LangOptions &LangOpts) {
auto neededInExtractedFunction = []() {
return ExtractionSemicolonPolicy(true, false);
};
auto neededInOriginalFunction = []() {
return ExtractionSemicolonPolicy(false, true);
};
/// The extracted expression should be terminated with a ';'. The call to
/// the extracted function will replace this expression, so it won't need
/// a terminating ';'.
if (isa<Expr>(S))
return neededInExtractedFunction();
/// Some statements don't need to be terminated with ';'. The call to the
/// extracted function will be a standalone statement, so it should be
/// terminated with a ';'.
bool NeedsSemi = isSemicolonRequiredAfter(S);
if (!NeedsSemi)
return neededInOriginalFunction();
/// Some statements might end at ';'. The extraction will move that ';', so
/// the call to the extracted function should be terminated with a ';'.
SourceLocation End = ExtractedRange.getEnd();
if (isSemicolonAtLocation(End, SM, LangOpts))
return neededInOriginalFunction();
/// Other statements should generally have a trailing ';'. We can try to find
/// it and move it together it with the extracted code.
Optional<Token> NextToken = Lexer::findNextToken(End, SM, LangOpts);
if (NextToken && NextToken->is(tok::semi) &&
areOnSameLine(NextToken->getLocation(), End, SM)) {
ExtractedRange.setEnd(NextToken->getLocation());
return neededInOriginalFunction();
}
/// Otherwise insert semicolons in both places.
return ExtractionSemicolonPolicy(true, true);
}
} // end namespace tooling
} // end namespace clang

View File

@@ -0,0 +1,52 @@
//===--- SourceExtraction.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.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H
#define LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H
#include "clang/Basic/LLVM.h"
namespace clang {
class LangOptions;
class SourceManager;
class SourceRange;
class Stmt;
namespace tooling {
/// Determines which semicolons should be inserted during extraction.
class ExtractionSemicolonPolicy {
public:
bool isNeededInExtractedFunction() const {
return IsNeededInExtractedFunction;
}
bool isNeededInOriginalFunction() const { return IsNeededInOriginalFunction; }
/// Returns the semicolon insertion policy that is needed for extraction of
/// the given statement from the given source range.
static ExtractionSemicolonPolicy compute(const Stmt *S,
SourceRange &ExtractedRange,
const SourceManager &SM,
const LangOptions &LangOpts);
private:
ExtractionSemicolonPolicy(bool IsNeededInExtractedFunction,
bool IsNeededInOriginalFunction)
: IsNeededInExtractedFunction(IsNeededInExtractedFunction),
IsNeededInOriginalFunction(IsNeededInOriginalFunction) {}
bool IsNeededInExtractedFunction;
bool IsNeededInOriginalFunction;
};
} // end namespace tooling
} // end namespace clang
#endif // LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H

View File

@@ -0,0 +1,114 @@
//===--- RefactoringActions.cpp - Constructs refactoring actions ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Extract/Extract.h"
#include "clang/Tooling/Refactoring/RefactoringAction.h"
#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
namespace clang {
namespace tooling {
namespace {
class DeclNameOption final : public OptionalRefactoringOption<std::string> {
public:
StringRef getName() const { return "name"; }
StringRef getDescription() const {
return "Name of the extracted declaration";
}
};
// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with
// rules.
class ExtractRefactoring final : public RefactoringAction {
public:
StringRef getCommand() const override { return "extract"; }
StringRef getDescription() const override {
return "(WIP action; use with caution!) Extracts code into a new function";
}
/// Returns a set of refactoring actions rules that are defined by this
/// action.
RefactoringActionRules createActionRules() const override {
RefactoringActionRules Rules;
Rules.push_back(createRefactoringActionRule<ExtractFunction>(
CodeRangeASTSelectionRequirement(),
OptionRequirement<DeclNameOption>()));
return Rules;
}
};
class OldQualifiedNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "old-qualified-name"; }
StringRef getDescription() const override {
return "The old qualified name to be renamed";
}
};
class NewQualifiedNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "new-qualified-name"; }
StringRef getDescription() const override {
return "The new qualified name to change the symbol to";
}
};
class NewNameOption : public RequiredRefactoringOption<std::string> {
public:
StringRef getName() const override { return "new-name"; }
StringRef getDescription() const override {
return "The new name to change the symbol to";
}
};
// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with
// rules.
class LocalRename final : public RefactoringAction {
public:
StringRef getCommand() const override { return "local-rename"; }
StringRef getDescription() const override {
return "Finds and renames symbols in code with no indexer support";
}
/// Returns a set of refactoring actions rules that are defined by this
/// action.
RefactoringActionRules createActionRules() const override {
RefactoringActionRules Rules;
Rules.push_back(createRefactoringActionRule<RenameOccurrences>(
SourceRangeSelectionRequirement(), OptionRequirement<NewNameOption>()));
// FIXME: Use NewNameOption.
Rules.push_back(createRefactoringActionRule<QualifiedRenameRule>(
OptionRequirement<OldQualifiedNameOption>(),
OptionRequirement<NewQualifiedNameOption>()));
return Rules;
}
};
} // end anonymous namespace
std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() {
std::vector<std::unique_ptr<RefactoringAction>> Actions;
Actions.push_back(llvm::make_unique<LocalRename>());
Actions.push_back(llvm::make_unique<ExtractRefactoring>());
return Actions;
}
RefactoringActionRules RefactoringAction::createActiveActionRules() {
// FIXME: Filter out rules that are not supported by a particular client.
return createActionRules();
}
} // end namespace tooling
} // end namespace clang

View File

@@ -0,0 +1,277 @@
//===--- RenamingAction.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 Provides an action to rename every symbol at a point.
///
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/FileManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/RefactoringAction.h"
#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include <string>
#include <vector>
using namespace llvm;
namespace clang {
namespace tooling {
namespace {
Expected<SymbolOccurrences>
findSymbolOccurrences(const NamedDecl *ND, RefactoringRuleContext &Context) {
std::vector<std::string> USRs =
getUSRsForDeclaration(ND, Context.getASTContext());
std::string PrevName = ND->getNameAsString();
return getOccurrencesOfUSRs(USRs, PrevName,
Context.getASTContext().getTranslationUnitDecl());
}
} // end anonymous namespace
const RefactoringDescriptor &RenameOccurrences::describe() {
static const RefactoringDescriptor Descriptor = {
"local-rename",
"Rename",
"Finds and renames symbols in code with no indexer support",
};
return Descriptor;
}
Expected<RenameOccurrences>
RenameOccurrences::initiate(RefactoringRuleContext &Context,
SourceRange SelectionRange, std::string NewName) {
const NamedDecl *ND =
getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin());
if (!ND)
return Context.createDiagnosticError(
SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol);
return RenameOccurrences(getCanonicalSymbolDeclaration(ND),
std::move(NewName));
}
Expected<AtomicChanges>
RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) {
Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context);
if (!Occurrences)
return Occurrences.takeError();
// FIXME: Verify that the new name is valid.
SymbolName Name(NewName);
return createRenameReplacements(
*Occurrences, Context.getASTContext().getSourceManager(), Name);
}
Expected<QualifiedRenameRule>
QualifiedRenameRule::initiate(RefactoringRuleContext &Context,
std::string OldQualifiedName,
std::string NewQualifiedName) {
const NamedDecl *ND =
getNamedDeclFor(Context.getASTContext(), OldQualifiedName);
if (!ND)
return llvm::make_error<llvm::StringError>("Could not find symbol " +
OldQualifiedName,
llvm::errc::invalid_argument);
return QualifiedRenameRule(ND, std::move(NewQualifiedName));
}
const RefactoringDescriptor &QualifiedRenameRule::describe() {
static const RefactoringDescriptor Descriptor = {
/*Name=*/"local-qualified-rename",
/*Title=*/"Qualified Rename",
/*Description=*/
R"(Finds and renames qualified symbols in code within a translation unit.
It is used to move/rename a symbol to a new namespace/name:
* Supported symbols: classes, class members, functions, enums, and type alias.
* Renames all symbol occurrences from the old qualified name to the new
qualified name. All symbol references will be correctly qualified; For
symbol definitions, only name will be changed.
For example, rename "A::Foo" to "B::Bar":
Old code:
namespace foo {
class A {};
}
namespace bar {
void f(foo::A a) {}
}
New code after rename:
namespace foo {
class B {};
}
namespace bar {
void f(B b) {}
})"
};
return Descriptor;
}
Expected<AtomicChanges>
QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) {
auto USRs = getUSRsForDeclaration(ND, Context.getASTContext());
assert(!USRs.empty());
return tooling::createRenameAtomicChanges(
USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl());
}
Expected<std::vector<AtomicChange>>
createRenameReplacements(const SymbolOccurrences &Occurrences,
const SourceManager &SM, const SymbolName &NewName) {
// FIXME: A true local rename can use just one AtomicChange.
std::vector<AtomicChange> Changes;
for (const auto &Occurrence : Occurrences) {
ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges();
assert(NewName.getNamePieces().size() == Ranges.size() &&
"Mismatching number of ranges and name pieces");
AtomicChange Change(SM, Ranges[0].getBegin());
for (const auto &Range : llvm::enumerate(Ranges)) {
auto Error =
Change.replace(SM, CharSourceRange::getCharRange(Range.value()),
NewName.getNamePieces()[Range.index()]);
if (Error)
return std::move(Error);
}
Changes.push_back(std::move(Change));
}
return std::move(Changes);
}
/// Takes each atomic change and inserts its replacements into the set of
/// replacements that belong to the appropriate file.
static void convertChangesToFileReplacements(
ArrayRef<AtomicChange> AtomicChanges,
std::map<std::string, tooling::Replacements> *FileToReplaces) {
for (const auto &AtomicChange : AtomicChanges) {
for (const auto &Replace : AtomicChange.getReplacements()) {
llvm::Error Err = (*FileToReplaces)[Replace.getFilePath()].add(Replace);
if (Err) {
llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! "
<< llvm::toString(std::move(Err)) << "\n";
}
}
}
}
class RenamingASTConsumer : public ASTConsumer {
public:
RenamingASTConsumer(
const std::vector<std::string> &NewNames,
const std::vector<std::string> &PrevNames,
const std::vector<std::vector<std::string>> &USRList,
std::map<std::string, tooling::Replacements> &FileToReplaces,
bool PrintLocations)
: NewNames(NewNames), PrevNames(PrevNames), USRList(USRList),
FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {}
void HandleTranslationUnit(ASTContext &Context) override {
for (unsigned I = 0; I < NewNames.size(); ++I) {
// If the previous name was not found, ignore this rename request.
if (PrevNames[I].empty())
continue;
HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]);
}
}
void HandleOneRename(ASTContext &Context, const std::string &NewName,
const std::string &PrevName,
const std::vector<std::string> &USRs) {
const SourceManager &SourceMgr = Context.getSourceManager();
SymbolOccurrences Occurrences = tooling::getOccurrencesOfUSRs(
USRs, PrevName, Context.getTranslationUnitDecl());
if (PrintLocations) {
for (const auto &Occurrence : Occurrences) {
FullSourceLoc FullLoc(Occurrence.getNameRanges()[0].getBegin(),
SourceMgr);
errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(FullLoc)
<< ":" << FullLoc.getSpellingLineNumber() << ":"
<< FullLoc.getSpellingColumnNumber() << "\n";
}
}
// FIXME: Support multi-piece names.
// FIXME: better error handling (propagate error out).
SymbolName NewNameRef(NewName);
Expected<std::vector<AtomicChange>> Change =
createRenameReplacements(Occurrences, SourceMgr, NewNameRef);
if (!Change) {
llvm::errs() << "Failed to create renaming replacements for '" << PrevName
<< "'! " << llvm::toString(Change.takeError()) << "\n";
return;
}
convertChangesToFileReplacements(*Change, &FileToReplaces);
}
private:
const std::vector<std::string> &NewNames, &PrevNames;
const std::vector<std::vector<std::string>> &USRList;
std::map<std::string, tooling::Replacements> &FileToReplaces;
bool PrintLocations;
};
// A renamer to rename symbols which are identified by a give USRList to
// new name.
//
// FIXME: Merge with the above RenamingASTConsumer.
class USRSymbolRenamer : public ASTConsumer {
public:
USRSymbolRenamer(const std::vector<std::string> &NewNames,
const std::vector<std::vector<std::string>> &USRList,
std::map<std::string, tooling::Replacements> &FileToReplaces)
: NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) {
assert(USRList.size() == NewNames.size());
}
void HandleTranslationUnit(ASTContext &Context) override {
for (unsigned I = 0; I < NewNames.size(); ++I) {
// FIXME: Apply AtomicChanges directly once the refactoring APIs are
// ready.
auto AtomicChanges = tooling::createRenameAtomicChanges(
USRList[I], NewNames[I], Context.getTranslationUnitDecl());
convertChangesToFileReplacements(AtomicChanges, &FileToReplaces);
}
}
private:
const std::vector<std::string> &NewNames;
const std::vector<std::vector<std::string>> &USRList;
std::map<std::string, tooling::Replacements> &FileToReplaces;
};
std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() {
return llvm::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList,
FileToReplaces, PrintLocations);
}
std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() {
return llvm::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces);
}
} // end namespace tooling
} // end namespace clang

View File

@@ -0,0 +1,37 @@
//===--- SymbolOccurrences.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.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h"
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
#include "llvm/ADT/STLExtras.h"
using namespace clang;
using namespace tooling;
SymbolOccurrence::SymbolOccurrence(const SymbolName &Name, OccurrenceKind Kind,
ArrayRef<SourceLocation> Locations)
: Kind(Kind) {
ArrayRef<std::string> NamePieces = Name.getNamePieces();
assert(Locations.size() == NamePieces.size() &&
"mismatching number of locations and lengths");
assert(!Locations.empty() && "no locations");
if (Locations.size() == 1) {
RangeOrNumRanges = SourceRange(
Locations[0], Locations[0].getLocWithOffset(NamePieces[0].size()));
return;
}
MultipleRanges = llvm::make_unique<SourceRange[]>(Locations.size());
RangeOrNumRanges.setBegin(
SourceLocation::getFromRawEncoding(Locations.size()));
for (const auto &Loc : llvm::enumerate(Locations)) {
MultipleRanges[Loc.index()] = SourceRange(
Loc.value(),
Loc.value().getLocWithOffset(NamePieces[Loc.index()].size()));
}
}

View File

@@ -0,0 +1,146 @@
//===--- USRFinder.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 Implements a recursive AST visitor that finds the USR of a symbol at a
/// point.
///
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
#include "llvm/ADT/SmallVector.h"
using namespace llvm;
namespace clang {
namespace tooling {
namespace {
/// Recursively visits each AST node to find the symbol underneath the cursor.
class NamedDeclOccurrenceFindingVisitor
: public RecursiveSymbolVisitor<NamedDeclOccurrenceFindingVisitor> {
public:
// \brief Finds the NamedDecl at a point in the source.
// \param Point the location in the source to search for the NamedDecl.
explicit NamedDeclOccurrenceFindingVisitor(const SourceLocation Point,
const ASTContext &Context)
: RecursiveSymbolVisitor(Context.getSourceManager(),
Context.getLangOpts()),
Point(Point), Context(Context) {}
bool visitSymbolOccurrence(const NamedDecl *ND,
ArrayRef<SourceRange> NameRanges) {
if (!ND)
return true;
for (const auto &Range : NameRanges) {
SourceLocation Start = Range.getBegin();
SourceLocation End = Range.getEnd();
if (!Start.isValid() || !Start.isFileID() || !End.isValid() ||
!End.isFileID() || !isPointWithin(Start, End))
return true;
}
Result = ND;
return false;
}
const NamedDecl *getNamedDecl() const { return Result; }
private:
// \brief Determines if the Point is within Start and End.
bool isPointWithin(const SourceLocation Start, const SourceLocation End) {
// FIXME: Add tests for Point == End.
return Point == Start || Point == End ||
(Context.getSourceManager().isBeforeInTranslationUnit(Start,
Point) &&
Context.getSourceManager().isBeforeInTranslationUnit(Point, End));
}
const NamedDecl *Result = nullptr;
const SourceLocation Point; // The location to find the NamedDecl.
const ASTContext &Context;
};
} // end anonymous namespace
const NamedDecl *getNamedDeclAt(const ASTContext &Context,
const SourceLocation Point) {
const SourceManager &SM = Context.getSourceManager();
NamedDeclOccurrenceFindingVisitor Visitor(Point, Context);
// Try to be clever about pruning down the number of top-level declarations we
// see. If both start and end is either before or after the point we're
// looking for the point cannot be inside of this decl. Don't even look at it.
for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) {
SourceLocation StartLoc = CurrDecl->getLocStart();
SourceLocation EndLoc = CurrDecl->getLocEnd();
if (StartLoc.isValid() && EndLoc.isValid() &&
SM.isBeforeInTranslationUnit(StartLoc, Point) !=
SM.isBeforeInTranslationUnit(EndLoc, Point))
Visitor.TraverseDecl(CurrDecl);
}
return Visitor.getNamedDecl();
}
namespace {
/// Recursively visits each NamedDecl node to find the declaration with a
/// specific name.
class NamedDeclFindingVisitor
: public RecursiveASTVisitor<NamedDeclFindingVisitor> {
public:
explicit NamedDeclFindingVisitor(StringRef Name) : Name(Name) {}
// We don't have to traverse the uses to find some declaration with a
// specific name, so just visit the named declarations.
bool VisitNamedDecl(const NamedDecl *ND) {
if (!ND)
return true;
// Fully qualified name is used to find the declaration.
if (Name != ND->getQualifiedNameAsString() &&
Name != "::" + ND->getQualifiedNameAsString())
return true;
Result = ND;
return false;
}
const NamedDecl *getNamedDecl() const { return Result; }
private:
const NamedDecl *Result = nullptr;
StringRef Name;
};
} // end anonymous namespace
const NamedDecl *getNamedDeclFor(const ASTContext &Context,
const std::string &Name) {
NamedDeclFindingVisitor Visitor(Name);
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
return Visitor.getNamedDecl();
}
std::string getUSRForDecl(const Decl *Decl) {
llvm::SmallVector<char, 128> Buff;
// FIXME: Add test for the nullptr case.
if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff))
return "";
return std::string(Buff.data(), Buff.size());
}
} // end namespace tooling
} // end namespace clang

View File

@@ -0,0 +1,274 @@
//===--- USRFindingAction.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 Provides an action to find USR for the symbol at <offset>, as well as
/// all additional USRs.
///
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/FileManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Refactoring/Rename/USRFinder.h"
#include "clang/Tooling/Tooling.h"
#include <algorithm>
#include <set>
#include <string>
#include <vector>
using namespace llvm;
namespace clang {
namespace tooling {
const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl) {
// If FoundDecl is a constructor or destructor, we want to instead take
// the Decl of the corresponding class.
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl))
FoundDecl = CtorDecl->getParent();
else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl))
FoundDecl = DtorDecl->getParent();
// FIXME: (Alex L): Canonicalize implicit template instantions, just like
// the indexer does it.
// Note: please update the declaration's doc comment every time the
// canonicalization rules are changed.
return FoundDecl;
}
namespace {
// \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to
// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given
// Decl refers to class and adds USRs of all overridden methods if Decl refers
// to virtual method.
class AdditionalUSRFinder : public RecursiveASTVisitor<AdditionalUSRFinder> {
public:
AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context)
: FoundDecl(FoundDecl), Context(Context) {}
std::vector<std::string> Find() {
// Fill OverriddenMethods and PartialSpecs storages.
TraverseDecl(Context.getTranslationUnitDecl());
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FoundDecl)) {
addUSRsOfOverridenFunctions(MethodDecl);
for (const auto &OverriddenMethod : OverriddenMethods) {
if (checkIfOverriddenFunctionAscends(OverriddenMethod))
USRSet.insert(getUSRForDecl(OverriddenMethod));
}
addUSRsOfInstantiatedMethods(MethodDecl);
} else if (const auto *RecordDecl = dyn_cast<CXXRecordDecl>(FoundDecl)) {
handleCXXRecordDecl(RecordDecl);
} else if (const auto *TemplateDecl =
dyn_cast<ClassTemplateDecl>(FoundDecl)) {
handleClassTemplateDecl(TemplateDecl);
} else {
USRSet.insert(getUSRForDecl(FoundDecl));
}
return std::vector<std::string>(USRSet.begin(), USRSet.end());
}
bool shouldVisitTemplateInstantiations() const { return true; }
bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) {
if (MethodDecl->isVirtual())
OverriddenMethods.push_back(MethodDecl);
if (MethodDecl->getInstantiatedFromMemberFunction())
InstantiatedMethods.push_back(MethodDecl);
return true;
}
bool VisitClassTemplatePartialSpecializationDecl(
const ClassTemplatePartialSpecializationDecl *PartialSpec) {
PartialSpecs.push_back(PartialSpec);
return true;
}
private:
void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) {
RecordDecl = RecordDecl->getDefinition();
if (const auto *ClassTemplateSpecDecl =
dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl))
handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate());
addUSRsOfCtorDtors(RecordDecl);
}
void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) {
for (const auto *Specialization : TemplateDecl->specializations())
addUSRsOfCtorDtors(Specialization);
for (const auto *PartialSpec : PartialSpecs) {
if (PartialSpec->getSpecializedTemplate() == TemplateDecl)
addUSRsOfCtorDtors(PartialSpec);
}
addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl());
}
void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) {
RecordDecl = RecordDecl->getDefinition();
// Skip if the CXXRecordDecl doesn't have definition.
if (!RecordDecl)
return;
for (const auto *CtorDecl : RecordDecl->ctors())
USRSet.insert(getUSRForDecl(CtorDecl));
USRSet.insert(getUSRForDecl(RecordDecl->getDestructor()));
USRSet.insert(getUSRForDecl(RecordDecl));
}
void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) {
USRSet.insert(getUSRForDecl(MethodDecl));
// Recursively visit each OverridenMethod.
for (const auto &OverriddenMethod : MethodDecl->overridden_methods())
addUSRsOfOverridenFunctions(OverriddenMethod);
}
void addUSRsOfInstantiatedMethods(const CXXMethodDecl *MethodDecl) {
// For renaming a class template method, all references of the instantiated
// member methods should be renamed too, so add USRs of the instantiated
// methods to the USR set.
USRSet.insert(getUSRForDecl(MethodDecl));
if (const auto *FT = MethodDecl->getInstantiatedFromMemberFunction())
USRSet.insert(getUSRForDecl(FT));
for (const auto *Method : InstantiatedMethods) {
if (USRSet.find(getUSRForDecl(
Method->getInstantiatedFromMemberFunction())) != USRSet.end())
USRSet.insert(getUSRForDecl(Method));
}
}
bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) {
for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) {
if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end())
return true;
return checkIfOverriddenFunctionAscends(OverriddenMethod);
}
return false;
}
const Decl *FoundDecl;
ASTContext &Context;
std::set<std::string> USRSet;
std::vector<const CXXMethodDecl *> OverriddenMethods;
std::vector<const CXXMethodDecl *> InstantiatedMethods;
std::vector<const ClassTemplatePartialSpecializationDecl *> PartialSpecs;
};
} // namespace
std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND,
ASTContext &Context) {
AdditionalUSRFinder Finder(ND, Context);
return Finder.Find();
}
class NamedDeclFindingConsumer : public ASTConsumer {
public:
NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets,
ArrayRef<std::string> QualifiedNames,
std::vector<std::string> &SpellingNames,
std::vector<std::vector<std::string>> &USRList,
bool Force, bool &ErrorOccurred)
: SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames),
SpellingNames(SpellingNames), USRList(USRList), Force(Force),
ErrorOccurred(ErrorOccurred) {}
private:
bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr,
unsigned SymbolOffset, const std::string &QualifiedName) {
DiagnosticsEngine &Engine = Context.getDiagnostics();
const FileID MainFileID = SourceMgr.getMainFileID();
if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) {
ErrorOccurred = true;
unsigned InvalidOffset = Engine.getCustomDiagID(
DiagnosticsEngine::Error,
"SourceLocation in file %0 at offset %1 is invalid");
Engine.Report(SourceLocation(), InvalidOffset)
<< SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset;
return false;
}
const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID)
.getLocWithOffset(SymbolOffset);
const NamedDecl *FoundDecl = QualifiedName.empty()
? getNamedDeclAt(Context, Point)
: getNamedDeclFor(Context, QualifiedName);
if (FoundDecl == nullptr) {
if (QualifiedName.empty()) {
FullSourceLoc FullLoc(Point, SourceMgr);
unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID(
DiagnosticsEngine::Error,
"clang-rename could not find symbol (offset %0)");
Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset;
ErrorOccurred = true;
return false;
}
if (Force) {
SpellingNames.push_back(std::string());
USRList.push_back(std::vector<std::string>());
return true;
}
unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID(
DiagnosticsEngine::Error, "clang-rename could not find symbol %0");
Engine.Report(CouldNotFindSymbolNamed) << QualifiedName;
ErrorOccurred = true;
return false;
}
FoundDecl = getCanonicalSymbolDeclaration(FoundDecl);
SpellingNames.push_back(FoundDecl->getNameAsString());
AdditionalUSRFinder Finder(FoundDecl, Context);
USRList.push_back(Finder.Find());
return true;
}
void HandleTranslationUnit(ASTContext &Context) override {
const SourceManager &SourceMgr = Context.getSourceManager();
for (unsigned Offset : SymbolOffsets) {
if (!FindSymbol(Context, SourceMgr, Offset, ""))
return;
}
for (const std::string &QualifiedName : QualifiedNames) {
if (!FindSymbol(Context, SourceMgr, 0, QualifiedName))
return;
}
}
ArrayRef<unsigned> SymbolOffsets;
ArrayRef<std::string> QualifiedNames;
std::vector<std::string> &SpellingNames;
std::vector<std::vector<std::string>> &USRList;
bool Force;
bool &ErrorOccurred;
};
std::unique_ptr<ASTConsumer> USRFindingAction::newASTConsumer() {
return llvm::make_unique<NamedDeclFindingConsumer>(
SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force,
ErrorOccurred);
}
} // end namespace tooling
} // end namespace clang

File diff suppressed because it is too large Load Diff