mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Refactoring] Extract Renamer to its own file
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "ContextFinder.h"
|
||||
#include "ExtractExprBase.h"
|
||||
#include "RefactoringActions.h"
|
||||
#include "Renamer.h"
|
||||
#include "Utils.h"
|
||||
#include "swift/AST/ASTContext.h"
|
||||
#include "swift/AST/ASTPrinter.h"
|
||||
@@ -47,400 +48,6 @@ using namespace swift::ide;
|
||||
using namespace swift::index;
|
||||
using namespace swift::refactoring;
|
||||
|
||||
class Renamer {
|
||||
protected:
|
||||
const SourceManager &SM;
|
||||
|
||||
protected:
|
||||
Renamer(const SourceManager &SM, StringRef OldName) : SM(SM), Old(OldName) {}
|
||||
|
||||
// Implementor's interface.
|
||||
virtual void doRenameLabel(CharSourceRange Label,
|
||||
RefactoringRangeKind RangeKind,
|
||||
unsigned NameIndex) = 0;
|
||||
virtual void doRenameBase(CharSourceRange Range,
|
||||
RefactoringRangeKind RangeKind) = 0;
|
||||
|
||||
public:
|
||||
const DeclNameViewer Old;
|
||||
|
||||
public:
|
||||
virtual ~Renamer() {}
|
||||
|
||||
/// Adds a replacement to rename the given base name range
|
||||
/// \return true if the given range does not match the old name
|
||||
bool renameBase(CharSourceRange Range, RefactoringRangeKind RangeKind) {
|
||||
assert(Range.isValid());
|
||||
|
||||
if (stripBackticks(Range).str() != Old.base())
|
||||
return true;
|
||||
doRenameBase(Range, RangeKind);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Adds replacements to rename the given label ranges
|
||||
/// \return true if the label ranges do not match the old name
|
||||
bool renameLabels(ArrayRef<CharSourceRange> LabelRanges,
|
||||
llvm::Optional<unsigned> FirstTrailingLabel,
|
||||
LabelRangeType RangeType, bool isCallSite) {
|
||||
if (isCallSite)
|
||||
return renameLabelsLenient(LabelRanges, FirstTrailingLabel, RangeType);
|
||||
|
||||
assert(!FirstTrailingLabel);
|
||||
ArrayRef<StringRef> OldLabels = Old.args();
|
||||
|
||||
if (OldLabels.size() != LabelRanges.size())
|
||||
return true;
|
||||
|
||||
size_t Index = 0;
|
||||
for (const auto &LabelRange : LabelRanges) {
|
||||
assert(LabelRange.isValid());
|
||||
if (!labelRangeMatches(LabelRange, RangeType, OldLabels[Index]))
|
||||
return true;
|
||||
splitAndRenameLabel(LabelRange, RangeType, Index++);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isOperator() const { return Lexer::isOperator(Old.base()); }
|
||||
|
||||
private:
|
||||
|
||||
/// Returns the range of the (possibly escaped) identifier at the start of
|
||||
/// \p Range and updates \p IsEscaped to indicate whether it's escaped or not.
|
||||
CharSourceRange getLeadingIdentifierRange(CharSourceRange Range, bool &IsEscaped) {
|
||||
assert(Range.isValid() && Range.getByteLength());
|
||||
IsEscaped = Range.str().front() == '`';
|
||||
SourceLoc Start = Range.getStart();
|
||||
if (IsEscaped)
|
||||
Start = Start.getAdvancedLoc(1);
|
||||
return Lexer::getCharSourceRangeFromSourceRange(SM, Start);
|
||||
}
|
||||
|
||||
CharSourceRange stripBackticks(CharSourceRange Range) {
|
||||
StringRef Content = Range.str();
|
||||
if (Content.size() < 3 || Content.front() != '`' || Content.back() != '`') {
|
||||
return Range;
|
||||
}
|
||||
return CharSourceRange(Range.getStart().getAdvancedLoc(1),
|
||||
Range.getByteLength() - 2);
|
||||
}
|
||||
|
||||
void splitAndRenameLabel(CharSourceRange Range, LabelRangeType RangeType,
|
||||
size_t NameIndex) {
|
||||
switch (RangeType) {
|
||||
case LabelRangeType::CallArg:
|
||||
return splitAndRenameCallArg(Range, NameIndex);
|
||||
case LabelRangeType::Param:
|
||||
return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/true);
|
||||
case LabelRangeType::NoncollapsibleParam:
|
||||
return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/false);
|
||||
case LabelRangeType::Selector:
|
||||
return doRenameLabel(
|
||||
Range, RefactoringRangeKind::SelectorArgumentLabel, NameIndex);
|
||||
case LabelRangeType::None:
|
||||
llvm_unreachable("expected a label range");
|
||||
}
|
||||
}
|
||||
|
||||
void splitAndRenameParamLabel(CharSourceRange Range, size_t NameIndex, bool IsCollapsible) {
|
||||
// Split parameter range foo([a b]: Int) into decl argument label [a] and
|
||||
// parameter name [b] or noncollapsible parameter name [b] if IsCollapsible
|
||||
// is false (as for subscript decls). If we have only foo([a]: Int), then we
|
||||
// add an empty range for the local name, or for the decl argument label if
|
||||
// IsCollapsible is false.
|
||||
StringRef Content = Range.str();
|
||||
size_t ExternalNameEnd = Content.find_first_of(" \t\n\v\f\r/");
|
||||
|
||||
if (ExternalNameEnd == StringRef::npos) { // foo([a]: Int)
|
||||
if (IsCollapsible) {
|
||||
doRenameLabel(Range, RefactoringRangeKind::DeclArgumentLabel, NameIndex);
|
||||
doRenameLabel(CharSourceRange{Range.getEnd(), 0},
|
||||
RefactoringRangeKind::ParameterName, NameIndex);
|
||||
} else {
|
||||
doRenameLabel(CharSourceRange{Range.getStart(), 0},
|
||||
RefactoringRangeKind::DeclArgumentLabel, NameIndex);
|
||||
doRenameLabel(Range, RefactoringRangeKind::NoncollapsibleParameterName,
|
||||
NameIndex);
|
||||
}
|
||||
} else { // foo([a b]: Int)
|
||||
CharSourceRange Ext{Range.getStart(), unsigned(ExternalNameEnd)};
|
||||
|
||||
// Note: we consider the leading whitespace part of the parameter name
|
||||
// if the parameter is collapsible, since if the parameter is collapsed
|
||||
// into a matching argument label, we want to remove the whitespace too.
|
||||
// FIXME: handle comments foo(a /*...*/b: Int).
|
||||
size_t LocalNameStart = Content.find_last_of(" \t\n\v\f\r/");
|
||||
assert(LocalNameStart != StringRef::npos);
|
||||
if (!IsCollapsible)
|
||||
++LocalNameStart;
|
||||
auto LocalLoc = Range.getStart().getAdvancedLocOrInvalid(LocalNameStart);
|
||||
CharSourceRange Local{LocalLoc, unsigned(Content.size() - LocalNameStart)};
|
||||
|
||||
doRenameLabel(Ext, RefactoringRangeKind::DeclArgumentLabel, NameIndex);
|
||||
if (IsCollapsible) {
|
||||
doRenameLabel(Local, RefactoringRangeKind::ParameterName, NameIndex);
|
||||
} else {
|
||||
doRenameLabel(Local, RefactoringRangeKind::NoncollapsibleParameterName, NameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void splitAndRenameCallArg(CharSourceRange Range, size_t NameIndex) {
|
||||
// Split call argument foo([a: ]1) into argument name [a] and the remainder
|
||||
// [: ].
|
||||
StringRef Content = Range.str();
|
||||
size_t Colon = Content.find(':'); // FIXME: leading whitespace?
|
||||
if (Colon == StringRef::npos) {
|
||||
assert(Content.empty());
|
||||
doRenameLabel(Range, RefactoringRangeKind::CallArgumentCombined,
|
||||
NameIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Include any whitespace before the ':'.
|
||||
assert(Colon == Content.substr(0, Colon).size());
|
||||
Colon = Content.substr(0, Colon).rtrim().size();
|
||||
|
||||
CharSourceRange Arg{Range.getStart(), unsigned(Colon)};
|
||||
doRenameLabel(Arg, RefactoringRangeKind::CallArgumentLabel, NameIndex);
|
||||
|
||||
auto ColonLoc = Range.getStart().getAdvancedLocOrInvalid(Colon);
|
||||
assert(ColonLoc.isValid());
|
||||
CharSourceRange Rest{ColonLoc, unsigned(Content.size() - Colon)};
|
||||
doRenameLabel(Rest, RefactoringRangeKind::CallArgumentColon, NameIndex);
|
||||
}
|
||||
|
||||
bool labelRangeMatches(CharSourceRange Range, LabelRangeType RangeType, StringRef Expected) {
|
||||
if (Range.getByteLength()) {
|
||||
bool IsEscaped = false;
|
||||
CharSourceRange ExistingLabelRange = getLeadingIdentifierRange(Range, IsEscaped);
|
||||
StringRef ExistingLabel = ExistingLabelRange.str();
|
||||
bool IsSingleName = Range == ExistingLabelRange ||
|
||||
(IsEscaped && Range.getByteLength() == ExistingLabel.size() + 2);
|
||||
|
||||
switch (RangeType) {
|
||||
case LabelRangeType::NoncollapsibleParam:
|
||||
if (IsSingleName && Expected.empty()) // subscript([x]: Int)
|
||||
return true;
|
||||
LLVM_FALLTHROUGH;
|
||||
case LabelRangeType::CallArg:
|
||||
case LabelRangeType::Param:
|
||||
case LabelRangeType::Selector:
|
||||
return ExistingLabel == (Expected.empty() ? "_" : Expected);
|
||||
case LabelRangeType::None:
|
||||
llvm_unreachable("Unhandled label range type");
|
||||
}
|
||||
}
|
||||
return Expected.empty();
|
||||
}
|
||||
|
||||
bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges,
|
||||
llvm::Optional<unsigned> FirstTrailingLabel,
|
||||
LabelRangeType RangeType) {
|
||||
|
||||
ArrayRef<StringRef> OldNames = Old.args();
|
||||
|
||||
// First, match trailing closure arguments in reverse
|
||||
if (FirstTrailingLabel) {
|
||||
auto TrailingLabels = LabelRanges.drop_front(*FirstTrailingLabel);
|
||||
LabelRanges = LabelRanges.take_front(*FirstTrailingLabel);
|
||||
|
||||
for (auto LabelIndex: llvm::reverse(indices(TrailingLabels))) {
|
||||
CharSourceRange Label = TrailingLabels[LabelIndex];
|
||||
|
||||
if (Label.getByteLength()) {
|
||||
if (OldNames.empty())
|
||||
return true;
|
||||
|
||||
while (!labelRangeMatches(Label, LabelRangeType::Selector,
|
||||
OldNames.back())) {
|
||||
if ((OldNames = OldNames.drop_back()).empty())
|
||||
return true;
|
||||
}
|
||||
splitAndRenameLabel(Label, LabelRangeType::Selector,
|
||||
OldNames.size() - 1);
|
||||
OldNames = OldNames.drop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
// empty labelled trailing closure label
|
||||
if (LabelIndex) {
|
||||
if (OldNames.empty())
|
||||
return true;
|
||||
|
||||
while (!OldNames.back().empty()) {
|
||||
if ((OldNames = OldNames.drop_back()).empty())
|
||||
return true;
|
||||
}
|
||||
splitAndRenameLabel(Label, LabelRangeType::Selector,
|
||||
OldNames.size() - 1);
|
||||
OldNames = OldNames.drop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
// unlabelled trailing closure label
|
||||
OldNames = OldNames.drop_back();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Next, match the non-trailing arguments.
|
||||
size_t NameIndex = 0;
|
||||
|
||||
for (CharSourceRange Label : LabelRanges) {
|
||||
// empty label
|
||||
if (!Label.getByteLength()) {
|
||||
|
||||
// first name pos
|
||||
if (!NameIndex) {
|
||||
while (!OldNames[NameIndex].empty()) {
|
||||
if (++NameIndex >= OldNames.size())
|
||||
return true;
|
||||
}
|
||||
splitAndRenameLabel(Label, RangeType, NameIndex++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// other name pos
|
||||
if (NameIndex >= OldNames.size() || !OldNames[NameIndex].empty()) {
|
||||
// FIXME: only allow one variadic param
|
||||
continue; // allow for variadic
|
||||
}
|
||||
splitAndRenameLabel(Label, RangeType, NameIndex++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// non-empty label
|
||||
if (NameIndex >= OldNames.size())
|
||||
return true;
|
||||
|
||||
while (!labelRangeMatches(Label, RangeType, OldNames[NameIndex])) {
|
||||
if (++NameIndex >= OldNames.size())
|
||||
return true;
|
||||
};
|
||||
splitAndRenameLabel(Label, RangeType, NameIndex++);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static RegionType getSyntacticRenameRegionType(const ResolvedLoc &Resolved) {
|
||||
if (Resolved.Node.isNull())
|
||||
return RegionType::Comment;
|
||||
|
||||
if (Expr *E = Resolved.Node.getAsExpr()) {
|
||||
if (isa<StringLiteralExpr>(E))
|
||||
return RegionType::String;
|
||||
}
|
||||
if (Resolved.IsInSelector)
|
||||
return RegionType::Selector;
|
||||
if (Resolved.IsActive)
|
||||
return RegionType::ActiveCode;
|
||||
return RegionType::InactiveCode;
|
||||
}
|
||||
|
||||
public:
|
||||
RegionType addSyntacticRenameRanges(const ResolvedLoc &Resolved,
|
||||
const RenameLoc &Config) {
|
||||
|
||||
if (!Resolved.Range.isValid())
|
||||
return RegionType::Unmatched;
|
||||
|
||||
auto RegionKind = getSyntacticRenameRegionType(Resolved);
|
||||
// Don't include unknown references coming from active code; if we don't
|
||||
// have a semantic NameUsage for them, then they're likely unrelated symbols
|
||||
// that happen to have the same name.
|
||||
if (RegionKind == RegionType::ActiveCode &&
|
||||
Config.Usage == NameUsage::Unknown)
|
||||
return RegionType::Unmatched;
|
||||
|
||||
assert(Config.Usage != NameUsage::Call || Config.IsFunctionLike);
|
||||
|
||||
// FIXME: handle escaped keyword names `init`
|
||||
bool IsSubscript = Old.base() == "subscript" && Config.IsFunctionLike;
|
||||
bool IsInit = Old.base() == "init" && Config.IsFunctionLike;
|
||||
|
||||
// FIXME: this should only be treated specially for instance methods.
|
||||
bool IsCallAsFunction = Old.base() == "callAsFunction" &&
|
||||
Config.IsFunctionLike;
|
||||
|
||||
bool IsSpecialBase = IsInit || IsSubscript || IsCallAsFunction;
|
||||
|
||||
// Filter out non-semantic special basename locations with no labels.
|
||||
// We've already filtered out those in active code, so these are
|
||||
// any appearance of just 'init', 'subscript', or 'callAsFunction' in
|
||||
// strings, comments, and inactive code.
|
||||
if (IsSpecialBase && (Config.Usage == NameUsage::Unknown &&
|
||||
Resolved.LabelType == LabelRangeType::None))
|
||||
return RegionType::Unmatched;
|
||||
|
||||
if (!Config.IsFunctionLike || !IsSpecialBase) {
|
||||
if (renameBase(Resolved.Range, RefactoringRangeKind::BaseName))
|
||||
return RegionType::Mismatch;
|
||||
|
||||
} else if (IsInit || IsCallAsFunction) {
|
||||
if (renameBase(Resolved.Range, RefactoringRangeKind::KeywordBaseName)) {
|
||||
// The base name doesn't need to match (but may) for calls, but
|
||||
// it should for definitions and references.
|
||||
if (Config.Usage == NameUsage::Definition ||
|
||||
Config.Usage == NameUsage::Reference) {
|
||||
return RegionType::Mismatch;
|
||||
}
|
||||
}
|
||||
} else if (IsSubscript && Config.Usage == NameUsage::Definition) {
|
||||
if (renameBase(Resolved.Range, RefactoringRangeKind::KeywordBaseName))
|
||||
return RegionType::Mismatch;
|
||||
}
|
||||
|
||||
bool HandleLabels = false;
|
||||
if (Config.IsFunctionLike) {
|
||||
switch (Config.Usage) {
|
||||
case NameUsage::Call:
|
||||
HandleLabels = !isOperator();
|
||||
break;
|
||||
case NameUsage::Definition:
|
||||
HandleLabels = true;
|
||||
break;
|
||||
case NameUsage::Reference:
|
||||
HandleLabels = Resolved.LabelType == LabelRangeType::Selector || IsSubscript;
|
||||
break;
|
||||
case NameUsage::Unknown:
|
||||
HandleLabels = Resolved.LabelType != LabelRangeType::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (HandleLabels) {
|
||||
bool isCallSite = Config.Usage != NameUsage::Definition &&
|
||||
(Config.Usage != NameUsage::Reference || IsSubscript) &&
|
||||
Resolved.LabelType == LabelRangeType::CallArg;
|
||||
|
||||
if (renameLabels(Resolved.LabelRanges, Resolved.FirstTrailingLabel,
|
||||
Resolved.LabelType, isCallSite))
|
||||
return Config.Usage == NameUsage::Unknown ?
|
||||
RegionType::Unmatched : RegionType::Mismatch;
|
||||
}
|
||||
|
||||
return RegionKind;
|
||||
}
|
||||
};
|
||||
|
||||
class RenameRangeDetailCollector : public Renamer {
|
||||
void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind,
|
||||
unsigned NameIndex) override {
|
||||
Ranges.push_back({Label, RangeKind, NameIndex});
|
||||
}
|
||||
void doRenameBase(CharSourceRange Range,
|
||||
RefactoringRangeKind RangeKind) override {
|
||||
Ranges.push_back({Range, RangeKind, llvm::None});
|
||||
}
|
||||
|
||||
public:
|
||||
RenameRangeDetailCollector(const SourceManager &SM, StringRef OldName)
|
||||
: Renamer(SM, OldName) {}
|
||||
std::vector<RenameRangeDetail> Ranges;
|
||||
};
|
||||
|
||||
class TextReplacementsRenamer : public Renamer {
|
||||
llvm::StringSet<> &ReplaceTextContext;
|
||||
SmallVector<Replacement> Replacements;
|
||||
|
||||
Reference in New Issue
Block a user