| //===--- Refactoring.cpp ---------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/IDE/Refactoring.h" |
| #include "swift/AST/ASTContext.h" |
| #include "swift/AST/Decl.h" |
| #include "swift/AST/DiagnosticsRefactoring.h" |
| #include "swift/AST/Expr.h" |
| #include "swift/AST/NameLookup.h" |
| #include "swift/AST/Pattern.h" |
| #include "swift/AST/ProtocolConformance.h" |
| #include "swift/AST/Stmt.h" |
| #include "swift/AST/Types.h" |
| #include "swift/AST/USRGeneration.h" |
| #include "swift/Basic/Edit.h" |
| #include "swift/Basic/StringExtras.h" |
| #include "swift/Frontend/Frontend.h" |
| #include "swift/Index/Index.h" |
| #include "swift/Parse/Lexer.h" |
| #include "swift/Subsystems.h" |
| #include "clang/Rewrite/Core/RewriteBuffer.h" |
| #include "llvm/ADT/StringSet.h" |
| |
| using namespace swift; |
| using namespace swift::ide; |
| using namespace swift::index; |
| |
| namespace { |
| class ContextFinder : public SourceEntityWalker { |
| SourceFile &SF; |
| ASTContext &Ctx; |
| SourceManager &SM; |
| ASTNode Target; |
| llvm::function_ref<bool(ASTNode)> IsContext; |
| SmallVector<ASTNode, 4> AllContexts; |
| bool contains(ASTNode Enclosing) { |
| auto Result = SM.rangeContains(Enclosing.getSourceRange(), |
| Target.getSourceRange()); |
| if (Result && IsContext(Enclosing)) |
| AllContexts.push_back(Enclosing); |
| return Result; |
| } |
| public: |
| ContextFinder(SourceFile &SF, ASTNode Target, |
| llvm::function_ref<bool(ASTNode)> IsContext = |
| [](ASTNode N) { return true; }) : SF(SF), |
| Ctx(SF.getASTContext()), SM(Ctx.SourceMgr), Target(Target), |
| IsContext(IsContext) {} |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) override { return contains(D); } |
| bool walkToStmtPre(Stmt *S) override { return contains(S); } |
| bool walkToExprPre(Expr *E) override { return contains(E); } |
| void resolve() { walk(SF); } |
| llvm::ArrayRef<ASTNode> getContexts() const { |
| return llvm::makeArrayRef(AllContexts); |
| } |
| }; |
| |
| 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()); |
| |
| StringRef Existing = Range.str(); |
| if (Existing != 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, |
| LabelRangeType RangeType, bool isCallSite) { |
| if (isCallSite) |
| return renameLabelsLenient(LabelRanges, RangeType); |
| |
| 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, OldLabels[Index])) |
| return true; |
| splitAndRenameLabel(LabelRange, RangeType, Index++); |
| } |
| return false; |
| } |
| |
| bool isOperator() const { return Lexer::isOperator(Old.base()); } |
| |
| private: |
| 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); |
| 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) { |
| // Split parameter range foo([a b]: Int) into decl argument label [a] and |
| // parameter name [b]. If we have only foo([a]: Int), then we add an empty |
| // range for the local name. |
| StringRef Content = Range.str(); |
| size_t ExternalNameEnd = Content.find_first_of(" \t\n\v\f\r/"); |
| ExternalNameEnd = |
| ExternalNameEnd == StringRef::npos ? Content.size() : ExternalNameEnd; |
| |
| CharSourceRange Ext{Range.getStart(), unsigned(ExternalNameEnd)}; |
| doRenameLabel(Ext, RefactoringRangeKind::DeclArgumentLabel, NameIndex); |
| |
| size_t LocalNameStart = Content.find_last_of(" \t\n\v\f\r/"); |
| LocalNameStart = |
| LocalNameStart == StringRef::npos ? ExternalNameEnd : LocalNameStart; |
| // Note: we consider the leading whitespace part of the parameter name since |
| // when the parameter is removed we want to remove the whitespace too. |
| // FIXME: handle comments foo(a /*...*/b: Int). |
| auto LocalLoc = Range.getStart().getAdvancedLocOrInvalid(LocalNameStart); |
| assert(LocalLoc.isValid()); |
| CharSourceRange Local{LocalLoc, unsigned(Content.size() - LocalNameStart)}; |
| doRenameLabel(Local, RefactoringRangeKind::ParameterName, 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, StringRef Expected) { |
| if (Range.getByteLength()) { |
| StringRef ExistingLabel = Lexer::getCharSourceRangeFromSourceRange(SM, |
| Range.getStart()).str(); |
| if (!Expected.empty()) |
| return Expected == ExistingLabel; |
| else |
| return ExistingLabel == "_"; |
| } |
| return Expected.empty(); |
| } |
| |
| bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges, |
| LabelRangeType RangeType) { |
| |
| ArrayRef<StringRef> OldNames = Old.args(); |
| |
| 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, 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); |
| |
| bool isKeywordBase = Old.base() == "init" || Old.base() == "subscript"; |
| |
| if (!Config.IsFunctionLike || !isKeywordBase) { |
| if (renameBase(Resolved.Range, RefactoringRangeKind::BaseName)) |
| return RegionType::Mismatch; |
| |
| } else if (isKeywordBase && 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; |
| break; |
| case NameUsage::Unknown: |
| HandleLabels = Resolved.LabelType != LabelRangeType::None; |
| break; |
| } |
| } else if (Resolved.LabelType != LabelRangeType::None && |
| !Config.IsNonProtocolType && |
| // FIXME: Workaround for enum case labels until we support them |
| Config.Usage != NameUsage::Definition) { |
| return RegionType::Mismatch; |
| } |
| |
| if (HandleLabels) { |
| bool isCallSite = Config.Usage != NameUsage::Definition && |
| Config.Usage != NameUsage::Reference && |
| Resolved.LabelType == LabelRangeType::CallArg; |
| |
| if (renameLabels(Resolved.LabelRanges, Resolved.LabelType, isCallSite)) |
| return 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, None}); |
| } |
| |
| public: |
| RenameRangeDetailCollector(const SourceManager &SM, StringRef OldName) |
| : Renamer(SM, OldName) {} |
| std::vector<RenameRangeDetail> Ranges; |
| }; |
| |
| class TextReplacementsRenamer : public Renamer { |
| llvm::StringMap<char> &ReplaceTextContext; |
| std::vector<Replacement> Replacements; |
| |
| public: |
| const DeclNameViewer New; |
| |
| private: |
| StringRef registerText(StringRef Text) { |
| return ReplaceTextContext.insert({Text, char()}).first->getKey(); |
| } |
| |
| StringRef getCallArgLabelReplacement(StringRef OldLabelRange, |
| StringRef NewLabel) { |
| return NewLabel.empty() ? "" : NewLabel; |
| } |
| |
| StringRef getCallArgColonReplacement(StringRef OldLabelRange, |
| StringRef NewLabel) { |
| // Expected OldLabelRange: foo( []3, a[: ]2, b[ : ]3 ...) |
| // FIXME: Preserve comments: foo([a/*:*/ : /*:*/ ]2, ...) |
| if (NewLabel.empty()) |
| return ""; |
| if (OldLabelRange.empty()) |
| return ": "; |
| return registerText(OldLabelRange); |
| } |
| |
| StringRef getCallArgCombinedReplacement(StringRef OldArgLabel, |
| StringRef NewArgLabel) { |
| // This case only happens when going from foo([]1) to foo([a: ]1). |
| assert(OldArgLabel.empty()); |
| if (NewArgLabel.empty()) |
| return ""; |
| return registerText((llvm::Twine(NewArgLabel) + ": ").str()); |
| } |
| |
| StringRef getParamNameReplacement(StringRef OldParam, StringRef OldArgLabel, |
| StringRef NewArgLabel) { |
| // We don't want to get foo(a a: Int), so drop the parameter name if the |
| // argument label will match the original name. |
| // Note: the leading whitespace is part of the parameter range. |
| if (!NewArgLabel.empty() && OldParam.ltrim() == NewArgLabel) |
| return ""; |
| |
| // If we're renaming foo(x: Int) to foo(_:), then use the original argument |
| // label as the parameter name so as to not break references in the body. |
| if (NewArgLabel.empty() && !OldArgLabel.empty() && OldParam.empty()) |
| return registerText((llvm::Twine(" ") + OldArgLabel).str()); |
| |
| return registerText(OldParam); |
| } |
| |
| StringRef getReplacementText(StringRef LabelRange, |
| RefactoringRangeKind RangeKind, |
| StringRef OldLabel, StringRef NewLabel) { |
| switch (RangeKind) { |
| case RefactoringRangeKind::CallArgumentLabel: |
| return getCallArgLabelReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::CallArgumentColon: |
| return getCallArgColonReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::CallArgumentCombined: |
| return getCallArgCombinedReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::ParameterName: |
| return getParamNameReplacement(LabelRange, OldLabel, NewLabel); |
| case RefactoringRangeKind::DeclArgumentLabel: |
| case RefactoringRangeKind::SelectorArgumentLabel: |
| return registerText(NewLabel.empty() ? "_" : NewLabel); |
| default: |
| llvm_unreachable("label range type is none but there are labels"); |
| } |
| } |
| |
| void addReplacement(CharSourceRange LabelRange, |
| RefactoringRangeKind RangeKind, StringRef OldLabel, |
| StringRef NewLabel) { |
| StringRef ExistingLabel = LabelRange.str(); |
| StringRef Text = |
| getReplacementText(ExistingLabel, RangeKind, OldLabel, NewLabel); |
| if (Text != ExistingLabel) |
| Replacements.push_back({LabelRange, Text, {}}); |
| } |
| |
| void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind, |
| unsigned NameIndex) override { |
| addReplacement(Label, RangeKind, Old.args()[NameIndex], |
| New.args()[NameIndex]); |
| } |
| |
| void doRenameBase(CharSourceRange Range, RefactoringRangeKind) override { |
| if (Old.base() != New.base()) |
| Replacements.push_back({Range, registerText(New.base()), {}}); |
| } |
| |
| public: |
| TextReplacementsRenamer(const SourceManager &SM, StringRef OldName, |
| StringRef NewName, |
| llvm::StringMap<char> &ReplaceTextContext) |
| : Renamer(SM, OldName), ReplaceTextContext(ReplaceTextContext), |
| New(NewName) { |
| assert(Old.isValid() && New.isValid()); |
| assert(Old.partsCount() == New.partsCount()); |
| } |
| |
| std::vector<Replacement> getReplacements() const { |
| return std::move(Replacements); |
| } |
| }; |
| |
| static const ValueDecl *getRelatedSystemDecl(const ValueDecl *VD) { |
| if (VD->getModuleContext()->isSystemModule()) |
| return VD; |
| for (auto *Req : VD->getSatisfiedProtocolRequirements()) { |
| if (Req->getModuleContext()->isSystemModule()) |
| return Req; |
| } |
| for (auto Over = VD->getOverriddenDecl(); Over; |
| Over = Over->getOverriddenDecl()) { |
| if (Over->getModuleContext()->isSystemModule()) |
| return Over; |
| } |
| return nullptr; |
| } |
| |
| static Optional<RefactoringKind> getAvailableRenameForDecl(const ValueDecl *VD) { |
| std::vector<RenameAvailabiliyInfo> Scratch; |
| for (auto &Info : collectRenameAvailabilityInfo(VD, Scratch)) { |
| if (Info.AvailableKind == RenameAvailableKind::Available) |
| return Info.Kind; |
| } |
| return None; |
| } |
| |
| class RenameRangeCollector : public IndexDataConsumer { |
| public: |
| RenameRangeCollector(StringRef USR, StringRef newName) |
| : USR(USR.str()), newName(newName.str()) {} |
| |
| RenameRangeCollector(const ValueDecl *D, StringRef newName) |
| : newName(newName.str()) { |
| llvm::raw_string_ostream OS(USR); |
| printDeclUSR(D, OS); |
| } |
| |
| ArrayRef<RenameLoc> results() const { return locations; } |
| |
| private: |
| bool indexLocals() override { return true; } |
| void failed(StringRef error) override {} |
| bool recordHash(StringRef hash, bool isKnown) override { return true; } |
| bool startDependency(StringRef name, StringRef path, bool isClangModule, |
| bool isSystem, StringRef hash) override { |
| return true; |
| } |
| bool finishDependency(bool isClangModule) override { return true; } |
| |
| Action startSourceEntity(const IndexSymbol &symbol) override { |
| if (symbol.USR == USR) { |
| if (auto loc = indexSymbolToRenameLoc(symbol, newName)) { |
| locations.push_back(std::move(*loc)); |
| } |
| } |
| return IndexDataConsumer::Continue; |
| } |
| |
| bool finishSourceEntity(SymbolInfo symInfo, SymbolRoleSet roles) override { |
| return true; |
| } |
| |
| Optional<RenameLoc> indexSymbolToRenameLoc(const index::IndexSymbol &symbol, |
| StringRef NewName); |
| |
| private: |
| std::string USR; |
| std::string newName; |
| StringScratchSpace stringStorage; |
| std::vector<RenameLoc> locations; |
| }; |
| |
| Optional<RenameLoc> |
| RenameRangeCollector::indexSymbolToRenameLoc(const index::IndexSymbol &symbol, |
| StringRef newName) { |
| if (symbol.roles & (unsigned)index::SymbolRole::Implicit) { |
| return None; |
| } |
| |
| NameUsage usage = NameUsage::Unknown; |
| if (symbol.roles & (unsigned)index::SymbolRole::Call) { |
| usage = NameUsage::Call; |
| } else if (symbol.roles & (unsigned)index::SymbolRole::Definition) { |
| usage = NameUsage::Definition; |
| } else if (symbol.roles & (unsigned)index::SymbolRole::Reference) { |
| usage = NameUsage::Reference; |
| } else { |
| llvm_unreachable("unexpected role"); |
| } |
| |
| bool isFunctionLike = false; |
| bool isNonProtocolType = false; |
| |
| switch (symbol.symInfo.Kind) { |
| case index::SymbolKind::EnumConstant: |
| case index::SymbolKind::Function: |
| case index::SymbolKind::Constructor: |
| case index::SymbolKind::ConversionFunction: |
| case index::SymbolKind::InstanceMethod: |
| case index::SymbolKind::ClassMethod: |
| case index::SymbolKind::StaticMethod: |
| isFunctionLike = true; |
| break; |
| case index::SymbolKind::Class: |
| case index::SymbolKind::Enum: |
| case index::SymbolKind::Struct: |
| isNonProtocolType = true; |
| break; |
| default: |
| break; |
| } |
| StringRef oldName = stringStorage.copyString(symbol.name); |
| return RenameLoc{symbol.line, symbol.column, usage, oldName, newName, |
| isFunctionLike, isNonProtocolType}; |
| } |
| |
| ArrayRef<SourceFile*> |
| collectSourceFiles(ModuleDecl *MD, llvm::SmallVectorImpl<SourceFile*> &Scratch) { |
| for (auto Unit : MD->getFiles()) { |
| if (auto SF = dyn_cast<SourceFile>(Unit)) { |
| Scratch.push_back(SF); |
| } |
| } |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| /// Get the source file that contains the given range and belongs to the module. |
| SourceFile *getContainingFile(ModuleDecl *M, RangeConfig Range) { |
| llvm::SmallVector<SourceFile*, 4> Files; |
| for (auto File : collectSourceFiles(M, Files)) { |
| if (File->getBufferID()) { |
| if (File->getBufferID().getValue() == Range.BufferId) { |
| return File; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| class RefactoringAction { |
| protected: |
| ModuleDecl *MD; |
| SourceFile *TheFile; |
| SourceEditConsumer &EditConsumer; |
| ASTContext &Ctx; |
| SourceManager &SM; |
| DiagnosticEngine DiagEngine; |
| SourceLoc StartLoc; |
| StringRef PreferredName; |
| public: |
| RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer); |
| virtual ~RefactoringAction() = default; |
| virtual bool performChange() = 0; |
| }; |
| |
| RefactoringAction:: |
| RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer): MD(MD), |
| TheFile(getContainingFile(MD, Opts.Range)), |
| EditConsumer(EditConsumer), Ctx(MD->getASTContext()), |
| SM(MD->getASTContext().SourceMgr), DiagEngine(SM), |
| StartLoc(Lexer::getLocForStartOfToken(SM, Opts.Range.getStart(SM))), |
| PreferredName(Opts.PreferredName) { |
| DiagEngine.addConsumer(DiagConsumer); |
| } |
| |
| /// Different from RangeBasedRefactoringAction, TokenBasedRefactoringAction takes |
| /// the input of a given token, e.g., a name or an "if" key word. Contextual |
| /// refactoring kinds can suggest applicable refactorings on that token, e.g. |
| /// rename or reverse if statement. |
| class TokenBasedRefactoringAction : public RefactoringAction { |
| protected: |
| ResolvedCursorInfo CursorInfo; |
| bool CanProceed; |
| public: |
| TokenBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) : |
| RefactoringAction(MD, Opts, EditConsumer, DiagConsumer) { |
| // We can only proceed with valid location and source file. |
| CanProceed = StartLoc.isValid() && TheFile; |
| if (!CanProceed) |
| return; |
| |
| // Resolve the sema token and save it for later use. |
| CursorInfoResolver Resolver(*TheFile); |
| CursorInfo = Resolver.resolve(StartLoc); |
| CanProceed = CursorInfo.isValid(); |
| } |
| }; |
| |
| #define CURSOR_REFACTORING(KIND, NAME, ID) \ |
| class RefactoringAction##KIND: public TokenBasedRefactoringAction { \ |
| public: \ |
| RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \ |
| SourceEditConsumer &EditConsumer, \ |
| DiagnosticConsumer &DiagConsumer) : \ |
| TokenBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \ |
| static bool isApplicable(ResolvedCursorInfo Tok); \ |
| bool performChange() override; \ |
| }; |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| class RangeBasedRefactoringAction : public RefactoringAction { |
| RangeResolver Resolver; |
| protected: |
| ResolvedRangeInfo RangeInfo; |
| public: |
| RangeBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) : |
| RefactoringAction(MD, Opts, EditConsumer, DiagConsumer), |
| Resolver(*TheFile, Opts.Range.getStart(SM), Opts.Range.getEnd(SM)), |
| RangeInfo(Resolver.resolve()) {} |
| }; |
| |
| #define RANGE_REFACTORING(KIND, NAME, ID) \ |
| class RefactoringAction##KIND: public RangeBasedRefactoringAction { \ |
| public: \ |
| RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \ |
| SourceEditConsumer &EditConsumer, \ |
| DiagnosticConsumer &DiagConsumer) : \ |
| RangeBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \ |
| bool performChange() override; \ |
| static bool isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag); \ |
| }; |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| bool RefactoringActionLocalRename::isApplicable(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::ValueRef) |
| return false; |
| auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD); |
| return RenameOp.hasValue() && |
| RenameOp.getValue() == RefactoringKind::LocalRename; |
| } |
| |
| static void analyzeRenameScope(ValueDecl *VD, DiagnosticEngine &Diags, |
| llvm::SmallVectorImpl<DeclContext *> &Scopes) { |
| Scopes.clear(); |
| if (!getAvailableRenameForDecl(VD).hasValue()) { |
| Diags.diagnose(SourceLoc(), diag::value_decl_no_loc, VD->getFullName()); |
| return; |
| } |
| |
| auto *Scope = VD->getDeclContext(); |
| // If the context is a top-level code decl, there may be other sibling |
| // decls that the renamed symbol is visible from |
| if (isa<TopLevelCodeDecl>(Scope)) |
| Scope = Scope->getParent(); |
| |
| Scopes.push_back(Scope); |
| } |
| |
| bool RefactoringActionLocalRename::performChange() { |
| if (StartLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_location); |
| return true; |
| } |
| if (!DeclNameViewer(PreferredName).isValid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| if (!TheFile) { |
| DiagEngine.diagnose(StartLoc, diag::location_module_mismatch, |
| MD->getNameStr()); |
| return true; |
| } |
| CursorInfoResolver Resolver(*TheFile); |
| ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc); |
| if (CursorInfo.isValid() && CursorInfo.ValueD) { |
| ValueDecl *VD = CursorInfo.ValueD; |
| llvm::SmallVector<DeclContext *, 8> Scopes; |
| analyzeRenameScope(VD, DiagEngine, Scopes); |
| if (Scopes.empty()) |
| return true; |
| RenameRangeCollector rangeCollector(VD, PreferredName); |
| for (DeclContext *DC : Scopes) |
| indexDeclContext(DC, rangeCollector); |
| |
| auto consumers = DiagEngine.takeConsumers(); |
| assert(consumers.size() == 1); |
| return syntacticRename(TheFile, rangeCollector.results(), EditConsumer, |
| *consumers[0]); |
| } else { |
| DiagEngine.diagnose(StartLoc, diag::unresolved_location); |
| return true; |
| } |
| } |
| |
| StringRef getDefaultPreferredName(RefactoringKind Kind) { |
| switch(Kind) { |
| case RefactoringKind::None: |
| llvm_unreachable("Should be a valid refactoring kind"); |
| case RefactoringKind::GlobalRename: |
| case RefactoringKind::LocalRename: |
| return "newName"; |
| case RefactoringKind::ExtractExpr: |
| case RefactoringKind::ExtractRepeatedExpr: |
| return "extractedExpr"; |
| case RefactoringKind::ExtractFunction: |
| return "extractedFunc"; |
| default: |
| return ""; |
| } |
| } |
| |
| enum class CannotExtractReason { |
| Literal, |
| VoidType, |
| }; |
| |
| class ExtractCheckResult { |
| bool KnownFailure; |
| llvm::SmallVector<CannotExtractReason, 2> AllReasons; |
| |
| public: |
| ExtractCheckResult(): KnownFailure(true) {} |
| ExtractCheckResult(ArrayRef<CannotExtractReason> AllReasons): |
| KnownFailure(false), AllReasons(AllReasons.begin(), AllReasons.end()) {} |
| bool success() { return success({}); } |
| bool success(llvm::ArrayRef<CannotExtractReason> ExpectedReasons) { |
| if (KnownFailure) |
| return false; |
| bool Result = true; |
| |
| // Check if any reasons aren't covered by the list of expected reasons |
| // provided by the client. |
| for (auto R: AllReasons) { |
| Result &= llvm::is_contained(ExpectedReasons, R); |
| } |
| return Result; |
| } |
| }; |
| |
| /// Check whether a given range can be extracted. |
| /// Return true on successful condition checking,. |
| /// Return false on failed conditions. |
| ExtractCheckResult checkExtractConditions(ResolvedRangeInfo &RangeInfo, |
| DiagnosticEngine &DiagEngine) { |
| llvm::SmallVector<CannotExtractReason, 2> AllReasons; |
| // If any declared declaration is refered out of the given range, return false. |
| auto Declared = RangeInfo.DeclaredDecls; |
| auto It = std::find_if(Declared.begin(), Declared.end(), |
| [](DeclaredDecl DD) { return DD.ReferredAfterRange; }); |
| if (It != Declared.end()) { |
| DiagEngine.diagnose(It->VD->getLoc(), |
| diag::value_decl_referenced_out_of_range, |
| It->VD->getFullName()); |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract a range with multi entry points. |
| if (!RangeInfo.HasSingleEntry) { |
| DiagEngine.diagnose(SourceLoc(), diag::multi_entry_range); |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract code that is not sure to exit or not. |
| if (RangeInfo.exit() == ExitState::Unsure) { |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract expressions of l-value type. |
| if (auto Ty = RangeInfo.getType()) { |
| if (Ty->hasLValueType() || Ty->getKind() == TypeKind::InOut) |
| return ExtractCheckResult(); |
| |
| // Disallow extracting error type expressions/statements |
| // FIXME: diagnose what happened? |
| if (Ty->hasError()) |
| return ExtractCheckResult(); |
| |
| if (Ty->isVoid()) { |
| AllReasons.emplace_back(CannotExtractReason::VoidType); |
| } |
| } |
| |
| // We cannot extract a range with orphaned loop keyword. |
| switch (RangeInfo.Orphan) { |
| case swift::ide::OrphanKind::Continue: |
| DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "continue"); |
| return ExtractCheckResult(); |
| case swift::ide::OrphanKind::Break: |
| DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "break"); |
| return ExtractCheckResult(); |
| case swift::ide::OrphanKind::None: |
| break; |
| } |
| |
| // Guard statement can not be extracted. |
| if (llvm::any_of(RangeInfo.ContainedNodes, |
| [](ASTNode N) { return N.isStmt(StmtKind::Guard); })) { |
| return ExtractCheckResult(); |
| } |
| |
| // Disallow extracting literals. |
| if (RangeInfo.Kind == RangeKind::SingleExpression) { |
| Expr *E = RangeInfo.ContainedNodes[0].get<Expr*>(); |
| |
| // Until implementing the performChange() part of extracting trailing |
| // closures, we disable them for now. |
| if (isa<AbstractClosureExpr>(E)) |
| return ExtractCheckResult(); |
| |
| if (isa<LiteralExpr>(E)) |
| AllReasons.emplace_back(CannotExtractReason::Literal); |
| } |
| |
| switch (RangeInfo.RangeContext->getContextKind()) { |
| case swift::DeclContextKind::Initializer: |
| case swift::DeclContextKind::SubscriptDecl: |
| case swift::DeclContextKind::AbstractFunctionDecl: |
| case swift::DeclContextKind::AbstractClosureExpr: |
| case swift::DeclContextKind::TopLevelCodeDecl: |
| break; |
| |
| case swift::DeclContextKind::SerializedLocal: |
| case swift::DeclContextKind::Module: |
| case swift::DeclContextKind::FileUnit: |
| case swift::DeclContextKind::GenericTypeDecl: |
| case swift::DeclContextKind::ExtensionDecl: |
| return ExtractCheckResult(); |
| } |
| return ExtractCheckResult(AllReasons); |
| } |
| |
| bool RefactoringActionExtractFunction:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::Invalid: |
| return false; |
| case RangeKind::SingleExpression: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: { |
| return checkExtractConditions(Info, Diag). |
| success({CannotExtractReason::VoidType}); |
| } |
| } |
| } |
| |
| static StringRef correctNameInternal(ASTContext &Ctx, StringRef Name, |
| ArrayRef<ValueDecl*> AllVisibles) { |
| // If we find the collision. |
| bool FoundCollision = false; |
| |
| // The suffixes we cannot use by appending to the original given name. |
| llvm::StringSet<> UsedSuffixes; |
| for (auto VD : AllVisibles) { |
| StringRef S = VD->getBaseName().userFacingName(); |
| if (!S.startswith(Name)) |
| continue; |
| StringRef Suffix = S.substr(Name.size()); |
| if (Suffix.empty()) |
| FoundCollision = true; |
| else |
| UsedSuffixes.insert(Suffix); |
| } |
| if (!FoundCollision) |
| return Name; |
| |
| // Find the first suffix we can use. |
| std::string SuffixToUse; |
| for (unsigned I = 1; ; I ++) { |
| SuffixToUse = std::to_string(I); |
| if (UsedSuffixes.count(SuffixToUse) == 0) |
| break; |
| } |
| return Ctx.getIdentifier((llvm::Twine(Name) + SuffixToUse).str()).str(); |
| } |
| |
| static StringRef correctNewDeclName(DeclContext *DC, StringRef Name) { |
| |
| // Collect all visible decls in the decl context. |
| llvm::SmallVector<ValueDecl*, 16> AllVisibles; |
| VectorDeclConsumer Consumer(AllVisibles); |
| ASTContext &Ctx = DC->getASTContext(); |
| lookupVisibleDecls(Consumer, DC, Ctx.getLazyResolver(), true); |
| return correctNameInternal(Ctx, Name, AllVisibles); |
| } |
| |
| static Type sanitizeType(Type Ty) { |
| // Transform lvalue type to inout type so that we can print it properly. |
| return Ty.transform([](Type Ty) { |
| if (Ty->getKind() == TypeKind::LValue) { |
| return Type(InOutType::get(Ty->getRValueType()->getCanonicalType())); |
| } |
| return Ty; |
| }); |
| } |
| |
| static SourceLoc |
| getNewFuncInsertLoc(DeclContext *DC, DeclContext*& InsertToContext) { |
| if (auto D = DC->getInnermostDeclarationDeclContext()) { |
| |
| // If extracting from a getter/setter, we should skip both the immediate |
| // getter/setter function and the individual var decl. The pattern binding |
| // decl is the position before which we should insert the newly extracted |
| // function. |
| if (auto *FD = dyn_cast<FuncDecl>(D)) { |
| if (FD->isAccessor()) { |
| ValueDecl *SD = FD->getAccessorStorageDecl(); |
| switch(SD->getKind()) { |
| case DeclKind::Var: |
| if (auto *PBD = static_cast<VarDecl*>(SD)->getParentPatternBinding()) |
| D = PBD; |
| break; |
| case DeclKind::Subscript: |
| D = SD; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| auto Result = D->getStartLoc(); |
| assert(Result.isValid()); |
| |
| // The insert loc should be before every decl attributes. |
| for (auto Attr : D->getAttrs()) { |
| auto Loc = Attr->getRangeWithAt().Start; |
| if (Loc.isValid() && |
| Loc.getOpaquePointerValue() < Result.getOpaquePointerValue()) |
| Result = Loc; |
| } |
| |
| // The insert loc should be before the doc comments associated with this decl. |
| if (!D->getRawComment().Comments.empty()) { |
| auto Loc = D->getRawComment().Comments.front().Range.getStart(); |
| if (Loc.isValid() && |
| Loc.getOpaquePointerValue() < Result.getOpaquePointerValue()) { |
| Result = Loc; |
| } |
| } |
| InsertToContext = D->getDeclContext(); |
| return Result; |
| } |
| return SourceLoc(); |
| } |
| |
| static std::vector<NoteRegion> |
| getNotableRegions(StringRef SourceText, unsigned NameOffset, StringRef Name, |
| bool IsFunctionLike = false, bool IsNonProtocolType = false) { |
| auto InputBuffer = llvm::MemoryBuffer::getMemBufferCopy(SourceText,"<extract>"); |
| |
| CompilerInvocation Invocation{}; |
| Invocation.addInputBuffer(InputBuffer.get()); |
| Invocation.getFrontendOptions().PrimaryInput = {0, SelectedInput::InputKind::Buffer}; |
| Invocation.getFrontendOptions().ModuleName = "extract"; |
| |
| auto Instance = llvm::make_unique<swift::CompilerInstance>(); |
| if (Instance->setup(Invocation)) |
| llvm_unreachable("Failed setup"); |
| |
| Instance->performParseOnly(); |
| |
| unsigned BufferId = Instance->getPrimarySourceFile()->getBufferID().getValue(); |
| SourceManager &SM = Instance->getSourceMgr(); |
| SourceLoc NameLoc = SM.getLocForOffset(BufferId, NameOffset); |
| auto LineAndCol = SM.getLineAndColumn(NameLoc); |
| |
| UnresolvedLoc UnresoledName{NameLoc, true}; |
| |
| NameMatcher Matcher(*Instance->getPrimarySourceFile()); |
| auto Resolved = Matcher.resolve(llvm::makeArrayRef(UnresoledName), None); |
| assert(!Resolved.empty() && "Failed to resolve generated func name loc"); |
| |
| RenameLoc RenameConfig = { |
| LineAndCol.first, LineAndCol.second, |
| NameUsage::Definition, /*OldName=*/Name, /*NewName=*/"", |
| IsFunctionLike, IsNonProtocolType |
| }; |
| RenameRangeDetailCollector Renamer(SM, Name); |
| Renamer.addSyntacticRenameRanges(Resolved.back(), RenameConfig); |
| auto Ranges = Renamer.Ranges; |
| |
| std::vector<NoteRegion> NoteRegions(Renamer.Ranges.size()); |
| std::transform(Ranges.begin(), Ranges.end(), NoteRegions.begin(), |
| [&SM](RenameRangeDetail &Detail) -> NoteRegion { |
| auto Start = SM.getLineAndColumn(Detail.Range.getStart()); |
| auto End = SM.getLineAndColumn(Detail.Range.getEnd()); |
| return {Detail.RangeKind, Start.first, Start.second, End.first, End.second, Detail.Index}; |
| }); |
| |
| return NoteRegions; |
| } |
| |
| bool RefactoringActionExtractFunction::performChange() { |
| if (!isApplicable(RangeInfo, DiagEngine)) |
| return true; |
| // Check if the new name is ok. |
| if (!Lexer::isIdentifier(PreferredName)) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| DeclContext *DC = RangeInfo.RangeContext; |
| DeclContext *InsertToDC = nullptr; |
| SourceLoc InsertLoc = getNewFuncInsertLoc(DC, InsertToDC); |
| |
| // Complain about no inserting position. |
| if (InsertLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::no_insert_position); |
| return true; |
| } |
| |
| // Correct the given name if collision happens. |
| PreferredName = correctNewDeclName(InsertToDC, PreferredName); |
| |
| // Collect the paramters to pass down to the new function. |
| std::vector<ReferencedDecl> Parameters; |
| for (auto &RD: RangeInfo.ReferencedDecls) { |
| // If the referenced decl is declared elsewhere, no need to pass as parameter |
| if (RD.VD->getDeclContext() != DC) |
| continue; |
| |
| // We don't need to pass down implicitly declared variables, e.g. error in |
| // a catch block. |
| if (RD.VD->isImplicit()) { |
| SourceLoc Loc = RD.VD->getStartLoc(); |
| if (Loc.isValid() && |
| SM.isBeforeInBuffer(RangeInfo.ContentRange.getStart(), Loc) && |
| SM.isBeforeInBuffer(Loc, RangeInfo.ContentRange.getEnd())) |
| continue; |
| } |
| |
| // If the referenced decl is declared inside the range, no need to pass |
| // as parameter. |
| if (RangeInfo.DeclaredDecls.end() != |
| std::find_if(RangeInfo.DeclaredDecls.begin(), RangeInfo.DeclaredDecls.end(), |
| [RD](DeclaredDecl DD) { return RD.VD == DD.VD; })) |
| continue; |
| |
| // We don't need to pass down self. |
| if (auto PD = dyn_cast<ParamDecl>(RD.VD)) { |
| if (PD->isSelfParameter()) { |
| continue; |
| } |
| } |
| |
| Parameters.emplace_back(RD.VD, sanitizeType(RD.Ty)); |
| } |
| SmallString<64> Buffer; |
| unsigned FuncBegin = Buffer.size(); |
| unsigned FuncNameOffset; |
| { |
| llvm::raw_svector_ostream OS(Buffer); |
| |
| if (!InsertToDC->isLocalContext()) { |
| // Default to be file private. |
| OS << tok::kw_fileprivate << " "; |
| } |
| |
| // Inherit static if the containing function is. |
| if (DC->getContextKind() == DeclContextKind::AbstractFunctionDecl) { |
| if (auto FD = dyn_cast<FuncDecl>(static_cast<AbstractFunctionDecl*>(DC))) { |
| if (FD->isStatic()) { |
| OS << tok::kw_static << " "; |
| } |
| } |
| } |
| |
| OS << tok::kw_func << " "; |
| FuncNameOffset = Buffer.size() - FuncBegin; |
| OS << PreferredName; |
| OS << "("; |
| for (auto &RD : Parameters) { |
| OS << "_ " << RD.VD->getBaseName().userFacingName() << ": "; |
| RD.Ty->reconstituteSugar(/*Recursive*/true)->print(OS); |
| if (&RD != &Parameters.back()) |
| OS << ", "; |
| } |
| OS << ")"; |
| |
| if (RangeInfo.ThrowingUnhandledError) |
| OS << " " << tok::kw_throws; |
| |
| bool InsertedReturnType = false; |
| if (auto Ty = RangeInfo.getType()) { |
| // If the type of the range is not void, specify the return type. |
| if (!Ty->isVoid()) { |
| OS << " " << tok::arrow << " "; |
| sanitizeType(Ty)->reconstituteSugar(/*Recursive*/true)->print(OS); |
| InsertedReturnType = true; |
| } |
| } |
| |
| OS << " {\n"; |
| |
| // Add "return" if the extracted entity is an expression. |
| if (RangeInfo.Kind == RangeKind::SingleExpression && InsertedReturnType) |
| OS << tok::kw_return << " "; |
| OS << RangeInfo.ContentRange.str() << "\n}\n\n"; |
| } |
| unsigned FuncEnd = Buffer.size(); |
| |
| unsigned ReplaceBegin = Buffer.size(); |
| unsigned CallNameOffset; |
| { |
| llvm::raw_svector_ostream OS(Buffer); |
| if (RangeInfo.exit() == ExitState::Positive) |
| OS << tok::kw_return <<" "; |
| CallNameOffset = Buffer.size() - ReplaceBegin; |
| OS << PreferredName << "("; |
| for (auto &RD : Parameters) { |
| |
| // Inout argument needs "&". |
| if (RD.Ty->getKind() == TypeKind::InOut) |
| OS << "&"; |
| OS << RD.VD->getBaseName().userFacingName(); |
| if (&RD != &Parameters.back()) |
| OS << ", "; |
| } |
| OS << ")"; |
| } |
| unsigned ReplaceEnd = Buffer.size(); |
| |
| std::string ExtractedFuncName = PreferredName.str() + "("; |
| for (size_t i = 0; i < Parameters.size(); ++i) { |
| ExtractedFuncName += "_:"; |
| } |
| ExtractedFuncName += ")"; |
| |
| StringRef DeclStr(Buffer.begin() + FuncBegin, FuncEnd - FuncBegin); |
| auto NotableFuncRegions = getNotableRegions(DeclStr, FuncNameOffset, |
| ExtractedFuncName, |
| /*IsFunctionLike=*/true); |
| |
| StringRef CallStr(Buffer.begin() + ReplaceBegin, ReplaceEnd - ReplaceBegin); |
| auto NotableCallRegions = getNotableRegions(CallStr, CallNameOffset, |
| ExtractedFuncName, |
| /*IsFunctionLike=*/true); |
| |
| // Insert the new function's declaration. |
| EditConsumer.accept(SM, InsertLoc, DeclStr, NotableFuncRegions); |
| |
| // Replace the code to extract with the function call. |
| EditConsumer.accept(SM, RangeInfo.ContentRange, CallStr, NotableCallRegions); |
| |
| return false; |
| } |
| |
| class RefactoringActionExtractExprBase { |
| SourceFile *TheFile; |
| ResolvedRangeInfo RangeInfo; |
| DiagnosticEngine &DiagEngine; |
| const bool ExtractRepeated; |
| StringRef PreferredName; |
| SourceEditConsumer &EditConsumer; |
| |
| ASTContext &Ctx; |
| SourceManager &SM; |
| |
| public: |
| RefactoringActionExtractExprBase(SourceFile *TheFile, |
| ResolvedRangeInfo RangeInfo, |
| DiagnosticEngine &DiagEngine, |
| bool ExtractRepeated, |
| StringRef PreferredName, |
| SourceEditConsumer &EditConsumer) : |
| TheFile(TheFile), RangeInfo(RangeInfo), DiagEngine(DiagEngine), |
| ExtractRepeated(ExtractRepeated), PreferredName(PreferredName), |
| EditConsumer(EditConsumer), Ctx(TheFile->getASTContext()), |
| SM(Ctx.SourceMgr){} |
| bool performChange(); |
| }; |
| |
| /// This is to ensure all decl references in two expressions are identical. |
| struct ReferenceCollector: public SourceEntityWalker { |
| llvm::SmallVector<ValueDecl*, 4> References; |
| |
| ReferenceCollector(Expr *E) { walk(E); } |
| bool visitDeclReference(ValueDecl *D, CharSourceRange Range, |
| TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef, |
| Type T, ReferenceMetaData Data) override { |
| References.emplace_back(D); |
| return true; |
| } |
| bool operator==(const ReferenceCollector &Other) const { |
| if (References.size() != Other.References.size()) |
| return false; |
| return std::equal(References.begin(), References.end(), |
| Other.References.begin()); |
| } |
| }; |
| |
| struct SimilarExprCollector: public SourceEntityWalker { |
| SourceManager &SM; |
| |
| /// The expression under selection. |
| Expr *SelectedExpr; |
| llvm::ArrayRef<Token> AllTokens; |
| llvm::SetVector<Expr*> &Bucket; |
| |
| /// The tokens included in the expression under selection. |
| llvm::ArrayRef<Token> SelectedTokens; |
| |
| /// The referenced decls in the expression under selection. |
| ReferenceCollector SelectedReferences; |
| |
| bool compareTokenContent(ArrayRef<Token> Left, ArrayRef<Token> Right) { |
| if (Left.size() != Right.size()) |
| return false; |
| return std::equal(Left.begin(), Left.end(), Right.begin(), |
| [](const Token &L, const Token& R) { |
| return L.getText() == R.getText(); |
| }); |
| } |
| |
| /// Find all tokens included by an expression. |
| llvm::ArrayRef<Token> getExprSlice(Expr *E) { |
| return slice_token_array(AllTokens, E->getStartLoc(), E->getEndLoc()); |
| } |
| |
| SimilarExprCollector(SourceManager &SM, Expr* SelectedExpr, |
| llvm::ArrayRef<Token> AllTokens, |
| llvm::SetVector<Expr*> &Bucket): SM(SM), SelectedExpr(SelectedExpr), |
| AllTokens(AllTokens), Bucket(Bucket), |
| SelectedTokens(getExprSlice(SelectedExpr)), |
| SelectedReferences(SelectedExpr){} |
| |
| bool walkToExprPre(Expr *E) override { |
| // We don't extract implicit expressions. |
| if (E->isImplicit()) |
| return true; |
| if (E->getKind() != SelectedExpr->getKind()) |
| return true; |
| |
| // First check the underlying token arrays have the same content. |
| if (compareTokenContent(getExprSlice(E), SelectedTokens)) { |
| ReferenceCollector CurrentReferences(E); |
| |
| // Next, check the referenced decls are same. |
| if (CurrentReferences == SelectedReferences) |
| Bucket.insert(E); |
| } |
| return true; |
| } |
| }; |
| |
| bool RefactoringActionExtractExprBase::performChange() { |
| // Check if the new name is ok. |
| if (!Lexer::isIdentifier(PreferredName)) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| |
| // Find the enclosing brace statement; |
| ContextFinder Finder(*TheFile, RangeInfo.ContainedNodes.front(), |
| [](ASTNode N) { return N.isStmt(StmtKind::Brace); }); |
| |
| auto *SelectedExpr = RangeInfo.ContainedNodes[0].get<Expr*>(); |
| Finder.resolve(); |
| SourceLoc InsertLoc; |
| llvm::SetVector<ValueDecl*> AllVisibleDecls; |
| struct DeclCollector: public SourceEntityWalker { |
| llvm::SetVector<ValueDecl*> &Bucket; |
| DeclCollector(llvm::SetVector<ValueDecl*> &Bucket): Bucket(Bucket) {} |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) override { |
| if (auto *VD = dyn_cast<ValueDecl>(D)) |
| Bucket.insert(VD); |
| return true; |
| } |
| } Collector(AllVisibleDecls); |
| |
| llvm::SetVector<Expr*> AllExpressions; |
| |
| if (!Finder.getContexts().empty()) { |
| |
| // Get the innermost brace statement. |
| auto BS = static_cast<BraceStmt*>(Finder.getContexts().back().get<Stmt*>()); |
| |
| // Collect all value decls inside the brace statement. |
| Collector.walk(BS); |
| |
| if (ExtractRepeated) { |
| // Collect all expressions we are going to extract. |
| SimilarExprCollector(SM, SelectedExpr, |
| slice_token_array(TheFile->getAllTokens(), |
| BS->getStartLoc(), |
| BS->getEndLoc()), |
| AllExpressions).walk(BS); |
| } else { |
| AllExpressions.insert(SelectedExpr); |
| } |
| |
| assert(!AllExpressions.empty() && "at least one expression is extracted."); |
| for (auto Ele : BS->getElements()) { |
| // Find the element that encloses the first expression under extraction. |
| if (SM.rangeContains(Ele.getSourceRange(), |
| (*AllExpressions.begin())->getSourceRange())) { |
| |
| // Insert before the enclosing element. |
| InsertLoc = Ele.getStartLoc(); |
| } |
| } |
| } |
| |
| // Complain about no inserting position. |
| if (InsertLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::no_insert_position); |
| return true; |
| } |
| |
| // Correct name if collision happens. |
| PreferredName = correctNameInternal(TheFile->getASTContext(), PreferredName, |
| AllVisibleDecls.getArrayRef()); |
| |
| // Print the type name of this expression. |
| llvm::SmallString<16> TyBuffer; |
| |
| // We are not sure about the type of repeated expressions. |
| if (!ExtractRepeated) { |
| if (auto Ty = RangeInfo.getType()) { |
| llvm::raw_svector_ostream OS(TyBuffer); |
| OS << ": "; |
| Ty->getRValueType()->reconstituteSugar(true)->print(OS); |
| } |
| } |
| |
| llvm::SmallString<64> DeclBuffer; |
| llvm::raw_svector_ostream OS(DeclBuffer); |
| unsigned StartOffset, EndOffset; |
| OS << tok::kw_let << " "; |
| StartOffset = DeclBuffer.size(); |
| OS << PreferredName; |
| EndOffset = DeclBuffer.size(); |
| OS << TyBuffer.str() << " = " << RangeInfo.ContentRange.str() << "\n"; |
| |
| NoteRegion DeclNameRegion{ |
| RefactoringRangeKind::BaseName, |
| /*StartLine=*/1, /*StartColumn=*/StartOffset + 1, |
| /*EndLine=*/1, /*EndColumn=*/EndOffset + 1, |
| /*ArgIndex*/None |
| }; |
| |
| // Perform code change. |
| EditConsumer.accept(SM, InsertLoc, DeclBuffer.str(), {DeclNameRegion}); |
| |
| // Replace all occurrences of the extracted expression. |
| for (auto *E : AllExpressions) { |
| EditConsumer.accept(SM, |
| Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange()), |
| PreferredName, |
| {{ |
| RefactoringRangeKind::BaseName, |
| /*StartLine=*/1, /*StartColumn-*/1, /*EndLine=*/1, |
| /*EndColumn=*/static_cast<unsigned int>(PreferredName.size() + 1), |
| /*ArgIndex*/None |
| }}); |
| } |
| return false; |
| } |
| |
| bool RefactoringActionExtractExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::SingleExpression: |
| // We disallow extract literal expression for two reasons: |
| // (1) since we print the type for extracted expression, the type of a |
| // literal may print as "int2048" where it is not typically users' choice; |
| // (2) Extracting one literal provides little value for users. |
| return checkExtractConditions(Info, Diag).success(); |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| |
| bool RefactoringActionExtractExpr::performChange() { |
| if (!isApplicable(RangeInfo, DiagEngine)) |
| return true; |
| return RefactoringActionExtractExprBase(TheFile, RangeInfo, |
| DiagEngine, false, PreferredName, |
| EditConsumer).performChange(); |
| } |
| |
| bool RefactoringActionExtractRepeatedExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::SingleExpression: |
| return checkExtractConditions(Info, Diag). |
| success({CannotExtractReason::Literal}); |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| bool RefactoringActionExtractRepeatedExpr::performChange() { |
| if (!isApplicable(RangeInfo, DiagEngine)) |
| return true; |
| return RefactoringActionExtractExprBase(TheFile, RangeInfo, |
| DiagEngine, true, PreferredName, |
| EditConsumer).performChange(); |
| } |
| |
| struct CollapsibleNestedIfInfo { |
| IfStmt *OuterIf; |
| IfStmt *InnerIf; |
| bool FinishedOuterIf; |
| bool FoundNonCollapsibleItem; |
| CollapsibleNestedIfInfo(): |
| OuterIf(nullptr), InnerIf(nullptr), |
| FinishedOuterIf(false), FoundNonCollapsibleItem(false) {} |
| bool isValid() { |
| return OuterIf && InnerIf && FinishedOuterIf && !FoundNonCollapsibleItem; |
| } |
| }; |
| |
| static CollapsibleNestedIfInfo findCollapseNestedIfTarget(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::StmtStart) |
| return CollapsibleNestedIfInfo(); |
| struct IfStmtFinder: public SourceEntityWalker { |
| SourceLoc StartLoc; |
| CollapsibleNestedIfInfo IfInfo; |
| IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {} |
| bool finishedInnerIfButNotFinishedOuterIf() { |
| return IfInfo.InnerIf && !IfInfo.FinishedOuterIf; |
| } |
| bool walkToStmtPre(Stmt *S) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| |
| bool StmtIsOuterIfBrace = |
| IfInfo.OuterIf && !IfInfo.InnerIf && S->getKind() == StmtKind::Brace; |
| if (StmtIsOuterIfBrace) { |
| return true; |
| } |
| |
| auto *IFS = dyn_cast<IfStmt>(S); |
| if (!IFS) { |
| return false; |
| } |
| if (!IfInfo.OuterIf) { |
| IfInfo.OuterIf = IFS; |
| return true; |
| } else { |
| IfInfo.InnerIf = IFS; |
| return false; |
| } |
| } |
| bool walkToStmtPost(Stmt *S) { |
| assert(S != IfInfo.InnerIf && "Should not traverse inner if statement"); |
| if (S == IfInfo.OuterIf) { |
| IfInfo.FinishedOuterIf = true; |
| } |
| return true; |
| } |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| return true; |
| } |
| bool walkToExprPre(Expr *E) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| return true; |
| } |
| |
| } Walker(CursorInfo.TrailingStmt->getStartLoc()); |
| Walker.walk(CursorInfo.TrailingStmt); |
| return Walker.IfInfo; |
| } |
| |
| bool RefactoringActionCollapseNestedIfExpr::isApplicable(ResolvedCursorInfo Tok) { |
| return findCollapseNestedIfTarget(Tok).isValid(); |
| } |
| |
| bool RefactoringActionCollapseNestedIfExpr::performChange() { |
| auto Target = findCollapseNestedIfTarget(CursorInfo); |
| if (!Target.isValid()) |
| return true; |
| auto OuterIfConds = Target.OuterIf->getCond().vec(); |
| auto InnerIfConds = Target.InnerIf->getCond().vec(); |
| |
| llvm::SmallString<64> DeclBuffer; |
| llvm::raw_svector_ostream OS(DeclBuffer); |
| OS << tok::kw_if << " "; |
| for (auto CI = OuterIfConds.begin(); CI != OuterIfConds.end(); ++CI) { |
| OS << (CI != OuterIfConds.begin() ? ", " : ""); |
| OS << Lexer::getCharSourceRangeFromSourceRange( |
| SM, CI->getSourceRange()).str(); |
| } |
| for (auto CI = InnerIfConds.begin(); CI != InnerIfConds.end(); ++CI) { |
| OS << ", " << Lexer::getCharSourceRangeFromSourceRange( |
| SM, CI->getSourceRange()).str(); |
| } |
| auto ThenStatementText = Lexer::getCharSourceRangeFromSourceRange( |
| SM, Target.InnerIf->getThenStmt()->getSourceRange()).str(); |
| OS << " " << ThenStatementText; |
| |
| auto SourceRange = Lexer::getCharSourceRangeFromSourceRange( |
| SM, Target.OuterIf->getSourceRange()); |
| EditConsumer.accept(SM, SourceRange, DeclBuffer.str()); |
| return false; |
| } |
| |
| static std::unique_ptr<llvm::SetVector<Expr*>> |
| findConcatenatedExpressions(ResolvedRangeInfo Info, ASTContext &Ctx) { |
| if (Info.Kind != RangeKind::SingleExpression |
| && Info.Kind != RangeKind::PartOfExpression) |
| return nullptr; |
| Expr *E = Info.ContainedNodes[0].get<Expr*>(); |
| |
| struct StringInterpolationExprFinder: public SourceEntityWalker { |
| std::unique_ptr<llvm::SetVector<Expr*>> Bucket = llvm:: |
| make_unique<llvm::SetVector<Expr*>>(); |
| ASTContext &Ctx; |
| |
| bool IsValidInterpolation = true; |
| StringInterpolationExprFinder(ASTContext &Ctx): Ctx(Ctx) {} |
| |
| bool isConcatenationExpr(DeclRefExpr* Expr) { |
| if (!Expr) |
| return false; |
| auto *FD = dyn_cast<FuncDecl>(Expr->getDecl()); |
| if (FD == nullptr || (FD != Ctx.getPlusFunctionOnString() && |
| FD != Ctx.getPlusFunctionOnRangeReplaceableCollection())) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool walkToExprPre(Expr *E) { |
| if (E->isImplicit()) |
| return true; |
| auto ExprType = E->getType()->getNominalOrBoundGenericNominal(); |
| //Only binary concatenation operators should exist in expression |
| if (E->getKind() == ExprKind::Binary) { |
| auto *BE = dyn_cast<BinaryExpr>(E); |
| auto *OperatorDeclRef = BE->getSemanticFn()->getMemberOperatorRef(); |
| if (!(isConcatenationExpr(OperatorDeclRef) |
| && ExprType == Ctx.getStringDecl())) { |
| IsValidInterpolation = false; |
| return false; |
| } |
| return true; |
| } |
| // Everything that evaluates to string should be gathered. |
| if (ExprType == Ctx.getStringDecl()) { |
| Bucket->insert(E); |
| return false; |
| } |
| if (auto *DR = dyn_cast<DeclRefExpr>(E)) { |
| // Checks whether all function references in expression are concatenations. |
| auto *FD = dyn_cast<FuncDecl>(DR->getDecl()); |
| auto IsConcatenation = isConcatenationExpr(DR); |
| if (FD && IsConcatenation) { |
| return false; |
| } |
| } |
| // There was non-expected expression, it's not valid interpolation then. |
| IsValidInterpolation = false; |
| return false; |
| } |
| } Walker(Ctx); |
| Walker.walk(E); |
| |
| // There should be two or more expressions to convert. |
| if (!Walker.IsValidInterpolation || Walker.Bucket->size() < 2) |
| return nullptr; |
| |
| return std::move(Walker.Bucket); |
| } |
| |
| static void interpolatedExpressionForm(Expr *E, SourceManager &SM, |
| llvm::raw_ostream &OS) { |
| if (auto *Literal = dyn_cast<StringLiteralExpr>(E)) { |
| OS << Literal->getValue(); |
| return; |
| } |
| auto ExpStr = Lexer::getCharSourceRangeFromSourceRange(SM, |
| E->getSourceRange()).str().str(); |
| if (isa<InterpolatedStringLiteralExpr>(E)) { |
| ExpStr.erase(0, 1); |
| ExpStr.pop_back(); |
| OS << ExpStr; |
| return; |
| } |
| OS << "\\(" << ExpStr << ")"; |
| } |
| |
| bool RefactoringActionConvertStringsConcatenationToInterpolation:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| auto RangeContext = Info.RangeContext; |
| if (RangeContext) { |
| auto &Ctx = Info.RangeContext->getASTContext(); |
| return findConcatenatedExpressions(Info, Ctx) != nullptr; |
| } |
| return false; |
| } |
| |
| bool RefactoringActionConvertStringsConcatenationToInterpolation::performChange() { |
| auto Expressions = findConcatenatedExpressions(RangeInfo, Ctx); |
| if (!Expressions) |
| return true; |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| OS << "\""; |
| for (auto It = Expressions->begin(); It != Expressions->end(); It++) { |
| interpolatedExpressionForm(*It, SM, OS); |
| } |
| OS << "\""; |
| EditConsumer.accept(SM, RangeInfo.ContentRange, Buffer); |
| return false; |
| } |
| |
| /// The helper class analyzes a given nominal decl or an extension decl to |
| /// decide whether stubs are required to filled in and the context in which |
| /// these stubs should be filled. |
| class FillProtocolStubContext { |
| |
| std::vector<ValueDecl*> getUnsatisfiedRequirements(const DeclContext *DC); |
| |
| /// Context in which the content should be filled; this could be either a |
| /// nominal type declaraion or an extension declaration. |
| DeclContext *DC; |
| |
| /// The type that adopts the required protocol stubs. For nominal type decl, this |
| /// should be the declared type itself; for extension decl, this should be the |
| /// extended type at hand. |
| Type Adopter; |
| |
| /// The start location of the decl, either nominal type or extension, for the |
| /// printer to figure out the right indentation. |
| SourceLoc StartLoc; |
| |
| /// The location of '{' for the decl, thus we know where to insert the filling |
| /// stubs. |
| SourceLoc BraceStartLoc; |
| |
| /// The value decls that should be satisfied; this could be either function |
| /// decls, property decls, or required type alias. |
| std::vector<ValueDecl*> FillingContents; |
| |
| public: |
| FillProtocolStubContext(ExtensionDecl *ED) : DC(ED), |
| Adopter(ED->getExtendedType()), StartLoc(ED->getStartLoc()), |
| BraceStartLoc(ED->getBraces().Start), |
| FillingContents(getUnsatisfiedRequirements(ED)) {}; |
| |
| FillProtocolStubContext(NominalTypeDecl *ND) : DC(ND), |
| Adopter(ND->getDeclaredType()), StartLoc(ND->getStartLoc()), |
| BraceStartLoc(ND->getBraces().Start), |
| FillingContents(getUnsatisfiedRequirements(ND)) {}; |
| |
| FillProtocolStubContext() : DC(nullptr), Adopter(), FillingContents({}) {}; |
| |
| static FillProtocolStubContext getContextFromCursorInfo(ResolvedCursorInfo Tok); |
| |
| ArrayRef<ValueDecl*> getFillingContents() const { |
| return llvm::makeArrayRef(FillingContents); |
| } |
| |
| DeclContext *getFillingContext() const { return DC; } |
| |
| bool canProceed() const { |
| return StartLoc.isValid() && BraceStartLoc.isValid() && |
| !getFillingContents().empty(); |
| } |
| |
| Type getAdopter() const { return Adopter; } |
| SourceLoc getContextStartLoc() const { return StartLoc; } |
| SourceLoc getBraceStartLoc() const { return BraceStartLoc; } |
| }; |
| |
| FillProtocolStubContext FillProtocolStubContext:: |
| getContextFromCursorInfo(ResolvedCursorInfo CursorInfo) { |
| assert(CursorInfo.isValid()); |
| if (!CursorInfo.IsRef) { |
| // If the type name is on the declared nominal, e.g. "class A {}" |
| if (auto ND = dyn_cast<NominalTypeDecl>(CursorInfo.ValueD)) { |
| return FillProtocolStubContext(ND); |
| } |
| } else if (auto *ED = CursorInfo.ExtTyRef) { |
| // If the type ref is on a declared extension, e.g. "extension A {}" |
| return FillProtocolStubContext(ED); |
| } |
| return FillProtocolStubContext(); |
| } |
| |
| std::vector<ValueDecl*> FillProtocolStubContext:: |
| getUnsatisfiedRequirements(const DeclContext *DC) { |
| // The results to return. |
| std::vector<ValueDecl*> NonWitnessedReqs; |
| |
| // For each conformance of the extended nominal. |
| for(ProtocolConformance *Con : DC->getLocalConformances()) { |
| |
| // Collect non-witnessed requirements. |
| Con->forEachNonWitnessedRequirement(DC->getASTContext().getLazyResolver(), |
| [&](ValueDecl *VD) { NonWitnessedReqs.push_back(VD); }); |
| } |
| |
| return NonWitnessedReqs; |
| } |
| |
| bool RefactoringActionFillProtocolStub::isApplicable(ResolvedCursorInfo Tok) { |
| return FillProtocolStubContext::getContextFromCursorInfo(Tok).canProceed(); |
| }; |
| |
| bool RefactoringActionFillProtocolStub::performChange() { |
| // If the base class says no proceeding, respect it. |
| if (!CanProceed) |
| return true; |
| |
| // Get the filling protocol context from the input token. |
| FillProtocolStubContext Context = FillProtocolStubContext:: |
| getContextFromCursorInfo(CursorInfo); |
| |
| // If the filling context disallows continue, abort. |
| if (!Context.canProceed()) |
| return true; |
| assert(!Context.getFillingContents().empty()); |
| assert(Context.getFillingContext()); |
| llvm::SmallString<128> Text; |
| { |
| llvm::raw_svector_ostream SS(Text); |
| Type Adopter = Context.getAdopter(); |
| SourceLoc Loc = Context.getContextStartLoc(); |
| auto Contents = Context.getFillingContents(); |
| |
| // For each unsatisfied requirement, print the stub to the buffer. |
| std::for_each(Contents.begin(), Contents.end(), [&](ValueDecl *VD) { |
| printRequirementStub(VD, Context.getFillingContext(), Adopter, Loc, SS); |
| }); |
| } |
| |
| // Insert all stubs after '{' in the extension/nominal type decl. |
| EditConsumer.insertAfter(SM, Context.getBraceStartLoc(), Text); |
| return false; |
| } |
| |
| ArrayRef<RefactoringKind> |
| collectAvailableRefactoringsAtCursor(SourceFile *SF, unsigned Line, |
| unsigned Column, |
| std::vector<RefactoringKind> &Scratch, |
| llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) { |
| // Prepare the tool box. |
| ASTContext &Ctx = SF->getASTContext(); |
| SourceManager &SM = Ctx.SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| std::for_each(DiagConsumers.begin(), DiagConsumers.end(), |
| [&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); }); |
| CursorInfoResolver Resolver(*SF); |
| SourceLoc Loc = SM.getLocForLineCol(SF->getBufferID().getValue(), Line, Column); |
| if (Loc.isInvalid()) |
| return {}; |
| ResolvedCursorInfo Tok = Resolver.resolve(Lexer::getLocForStartOfToken(SM, Loc)); |
| return collectAvailableRefactorings(SF, Tok, Scratch, /*Exclude rename*/false); |
| } |
| |
| bool RefactoringActionExpandDefault::isApplicable(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::StmtStart) |
| return false; |
| if (auto *CS = dyn_cast<CaseStmt>(CursorInfo.TrailingStmt)) { |
| return CS->isDefault(); |
| } |
| return false; |
| } |
| |
| bool RefactoringActionExpandDefault::performChange() { |
| if (!isApplicable(CursorInfo)) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_default_location); |
| return true; |
| } |
| |
| // Try to find the switch statement enclosing the default statement. |
| auto *CS = static_cast<CaseStmt*>(CursorInfo.TrailingStmt); |
| auto IsSwitch = [](ASTNode Node) { |
| return Node.is<Stmt*>() && |
| Node.get<Stmt*>()->getKind() == StmtKind::Switch; |
| }; |
| ContextFinder Finder(*TheFile, CS, IsSwitch); |
| Finder.resolve(); |
| |
| // If failed to find the switch statement, issue error. |
| if (Finder.getContexts().empty()) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch); |
| return true; |
| } |
| auto *SwitchS = static_cast<SwitchStmt*>(Finder.getContexts().back(). |
| get<Stmt*>()); |
| |
| // To find the subject enum decl for this switch statement; if failing, |
| // issue errors. |
| EnumDecl *SubjectED = nullptr; |
| if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) { |
| SubjectED = SubjectTy->getAnyNominal()->getAsEnumOrEnumExtensionContext(); |
| } |
| if (!SubjectED) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_subject_enum); |
| return true; |
| } |
| |
| // Assume enum elements are not handled in the switch statement. |
| llvm::DenseSet<EnumElementDecl*> UnhandledElements; |
| SubjectED->getAllElements(UnhandledElements); |
| bool FoundDefault = false; |
| for (auto Current : SwitchS->getCases()) { |
| if (Current == CS) { |
| FoundDefault = true; |
| continue; |
| } |
| // For each handled enum element, remove it from the bucket. |
| for (auto Item : Current->getCaseLabelItems()) { |
| if (auto *EEP = dyn_cast_or_null<EnumElementPattern>(Item.getPattern())) { |
| UnhandledElements.erase(EEP->getElementDecl()); |
| } |
| } |
| } |
| |
| // If we've not seen the default statement inside the switch statement, issue |
| // error. |
| if (!FoundDefault) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch); |
| return true; |
| } |
| |
| // If all enum elements are handled in the switch statement, issue error. |
| if (UnhandledElements.empty()) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_remaining_cases); |
| return true; |
| } |
| |
| // Good to go, change the code! |
| SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| printEnumElementsAsCases(UnhandledElements, OS); |
| EditConsumer.accept(SM, Lexer::getCharSourceRangeFromSourceRange(SM, |
| CS->getLabelItemsRange()), Buffer.str()); |
| return false; |
| } |
| |
| static Expr *findLocalizeTarget(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::ExprStart) |
| return nullptr; |
| struct StringLiteralFinder: public SourceEntityWalker { |
| SourceLoc StartLoc; |
| Expr *Target; |
| StringLiteralFinder(SourceLoc StartLoc): StartLoc(StartLoc), Target(nullptr) {} |
| bool walkToExprPre(Expr *E) { |
| if (E->getStartLoc() != StartLoc) |
| return false; |
| if (E->getKind() == ExprKind::InterpolatedStringLiteral) |
| return false; |
| if (E->getKind() == ExprKind::StringLiteral) { |
| Target = E; |
| return false; |
| } |
| return true; |
| } |
| } Walker(CursorInfo.TrailingExpr->getStartLoc()); |
| Walker.walk(CursorInfo.TrailingExpr); |
| return Walker.Target; |
| } |
| |
| bool RefactoringActionLocalizeString::isApplicable(ResolvedCursorInfo Tok) { |
| return findLocalizeTarget(Tok); |
| } |
| |
| bool RefactoringActionLocalizeString::performChange() { |
| Expr* Target = findLocalizeTarget(CursorInfo); |
| if (!Target) |
| return true; |
| EditConsumer.accept(SM, Target->getStartLoc(), "NSLocalizedString("); |
| EditConsumer.insertAfter(SM, Target->getEndLoc(), ", comment: \"\")"); |
| return false; |
| } |
| |
| /// Given a cursor position, this function tries to collect a number literal |
| /// expression immediately following the cursor. |
| static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) { |
| // This cursor must point to the start of an expression. |
| if (Tok.Kind != CursorInfoKind::ExprStart) |
| return nullptr; |
| Expr *Parent = Tok.TrailingExpr; |
| assert(Parent); |
| |
| // Check if an expression is a number literal. |
| auto IsLiteralNumber = [&](Expr *E) -> NumberLiteralExpr* { |
| if (auto *NL = dyn_cast<NumberLiteralExpr>(E)) { |
| |
| // The sub-expression must have the same start loc with the outermost |
| // expression, i.e. the cursor position. |
| if (Parent->getStartLoc().getOpaquePointerValue() == |
| E->getStartLoc().getOpaquePointerValue()) { |
| return NL; |
| } |
| } |
| return nullptr; |
| }; |
| // For every sub-expression, try to find the literal expression that matches |
| // our criteria. |
| for (auto Pair: Parent->getDepthMap()) { |
| if (auto Result = IsLiteralNumber(Pair.getFirst())) { |
| return Result; |
| } |
| } |
| return nullptr; |
| } |
| |
| static std::string insertUnderscore(StringRef Text) { |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| for (auto It = Text.begin(); It != Text.end(); It++) { |
| unsigned Distance = It - Text.begin(); |
| if (Distance && !(Distance % 3)) { |
| OS << '_'; |
| } |
| OS << *It; |
| } |
| return OS.str().str(); |
| } |
| |
| static void insertUnderscoreInDigits(StringRef Digits, |
| llvm::raw_ostream &OS) { |
| std::string BeforePoint, AfterPoint; |
| std::tie(BeforePoint, AfterPoint) = Digits.split('.'); |
| |
| // Insert '_' for the part before the decimal point. |
| std::reverse(BeforePoint.begin(), BeforePoint.end()); |
| BeforePoint = insertUnderscore(BeforePoint); |
| std::reverse(BeforePoint.begin(), BeforePoint.end()); |
| OS << BeforePoint; |
| |
| // Insert '_' for the part after the decimal point, if necessary. |
| if (!AfterPoint.empty()) { |
| OS << '.'; |
| OS << insertUnderscore(AfterPoint); |
| } |
| } |
| |
| bool RefactoringActionSimplifyNumberLiteral:: |
| isApplicable(ResolvedCursorInfo Tok) { |
| if (auto *Literal = getTrailingNumberLiteral(Tok)) { |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| StringRef Digits = Literal->getDigitsText(); |
| insertUnderscoreInDigits(Digits, OS); |
| |
| // If inserting '_' results in a different digit sequence, this refactoring |
| // is applicable. |
| return OS.str() != Digits; |
| } |
| return false; |
| } |
| |
| bool RefactoringActionSimplifyNumberLiteral::performChange() { |
| if (auto *Literal = getTrailingNumberLiteral(CursorInfo)) { |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| StringRef Digits = Literal->getDigitsText(); |
| insertUnderscoreInDigits(Digits, OS); |
| EditConsumer.accept(SM, CharSourceRange(SM, Literal->getDigitsLoc(), |
| Lexer::getLocForEndOfToken(SM, Literal->getEndLoc())), |
| OS.str()); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool rangeStartMayNeedRename(ResolvedRangeInfo Info) { |
| switch(Info.Kind) { |
| case RangeKind::SingleExpression: { |
| Expr *E = Info.ContainedNodes[0].get<Expr*>(); |
| // We should show rename for the selection of "foo()" |
| if (auto *CE = dyn_cast<CallExpr>(E)) { |
| if (CE->getFn()->getKind() == ExprKind::DeclRef) |
| return true; |
| |
| // When callling an instance method inside another instance method, |
| // we have a dot syntax call whose dot and base are both implicit. We |
| // need to explicitly allow the specific case here. |
| if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) { |
| if (DSC->getBase()->isImplicit() && |
| DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc()) |
| return true; |
| } |
| } |
| return false; |
| } |
| case RangeKind::PartOfExpression: { |
| if (auto *CE = dyn_cast<CallExpr>(Info.CommonExprParent)) { |
| if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) { |
| if (DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc()) |
| return true; |
| } |
| } |
| return false; |
| } |
| case RangeKind::SingleDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| }// end of anonymous namespace |
| |
| StringRef swift::ide:: |
| getDescriptiveRefactoringKindName(RefactoringKind Kind) { |
| switch(Kind) { |
| case RefactoringKind::None: |
| llvm_unreachable("Should be a valid refactoring kind"); |
| #define REFACTORING(KIND, NAME, ID) case RefactoringKind::KIND: return NAME; |
| #include "swift/IDE/RefactoringKinds.def" |
| } |
| } |
| |
| StringRef swift::ide:: |
| getDescriptiveRenameUnavailableReason(RenameAvailableKind Kind) { |
| switch(Kind) { |
| case RenameAvailableKind::Available: |
| return ""; |
| case RenameAvailableKind::Unavailable_system_symbol: |
| return "symbol from system module cannot be renamed"; |
| case RenameAvailableKind::Unavailable_has_no_location: |
| return "symbol without a declaration location cannot be renamed"; |
| case RenameAvailableKind::Unavailable_has_no_name: |
| return "cannot find the name of the symbol"; |
| case RenameAvailableKind::Unavailable_has_no_accessibility: |
| return "cannot decide the accessibility of the symbol"; |
| case RenameAvailableKind::Unavailable_decl_from_clang: |
| return "cannot rename a Clang symbol from its Swift reference"; |
| } |
| } |
| |
| SourceLoc swift::ide::RangeConfig::getStart(SourceManager &SM) { |
| return SM.getLocForLineCol(BufferId, Line, Column); |
| } |
| |
| SourceLoc swift::ide::RangeConfig::getEnd(SourceManager &SM) { |
| return getStart(SM).getAdvancedLoc(Length); |
| } |
| |
| struct swift::ide::FindRenameRangesAnnotatingConsumer::Implementation { |
| std::unique_ptr<SourceEditConsumer> pRewriter; |
| Implementation(SourceManager &SM, unsigned BufferId, llvm::raw_ostream &OS) |
| : pRewriter(new SourceEditOutputConsumer(SM, BufferId, OS)) {} |
| static StringRef tag(RefactoringRangeKind Kind) { |
| switch (Kind) { |
| case RefactoringRangeKind::BaseName: |
| return "base"; |
| case RefactoringRangeKind::KeywordBaseName: |
| return "keywordBase"; |
| case RefactoringRangeKind::ParameterName: |
| return "param"; |
| case RefactoringRangeKind::DeclArgumentLabel: |
| return "arglabel"; |
| case RefactoringRangeKind::CallArgumentLabel: |
| return "callarg"; |
| case RefactoringRangeKind::CallArgumentColon: |
| return "callcolon"; |
| case RefactoringRangeKind::CallArgumentCombined: |
| return "callcombo"; |
| case RefactoringRangeKind::SelectorArgumentLabel: |
| return "sel"; |
| } |
| } |
| void accept(SourceManager &SM, const RenameRangeDetail &Range) { |
| std::string NewText; |
| llvm::raw_string_ostream OS(NewText); |
| StringRef Tag = tag(Range.RangeKind); |
| OS << "<" << Tag; |
| if (Range.Index.hasValue()) |
| OS << " index=" << *Range.Index; |
| OS << ">" << Range.Range.str() << "</" << Tag << ">"; |
| pRewriter->accept(SM, {Range.Range, OS.str(), {}}); |
| } |
| }; |
| |
| swift::ide::FindRenameRangesAnnotatingConsumer:: |
| FindRenameRangesAnnotatingConsumer(SourceManager &SM, unsigned BufferId, |
| llvm::raw_ostream &OS): Impl(*new Implementation(SM, BufferId, OS)) {} |
| |
| swift::ide::FindRenameRangesAnnotatingConsumer::~FindRenameRangesAnnotatingConsumer() { |
| delete &Impl; |
| } |
| |
| void swift::ide::FindRenameRangesAnnotatingConsumer:: |
| accept(SourceManager &SM, RegionType RegionType, |
| ArrayRef<RenameRangeDetail> Ranges) { |
| for (const auto &Range : Ranges) { |
| Impl.accept(SM, Range); |
| } |
| } |
| ArrayRef<RenameAvailabiliyInfo> |
| swift::ide::collectRenameAvailabilityInfo(const ValueDecl *VD, |
| std::vector<RenameAvailabiliyInfo> &Scratch) { |
| RenameAvailableKind AvailKind = RenameAvailableKind::Available; |
| if (getRelatedSystemDecl(VD)){ |
| AvailKind = RenameAvailableKind::Unavailable_system_symbol; |
| } else if (VD->getClangDecl()) { |
| AvailKind = RenameAvailableKind::Unavailable_decl_from_clang; |
| } else if (VD->getStartLoc().isInvalid()) { |
| AvailKind = RenameAvailableKind::Unavailable_has_no_location; |
| } else if (!VD->hasName()) { |
| AvailKind = RenameAvailableKind::Unavailable_has_no_name; |
| } else if (!VD->hasAccess()) { |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| if (isa<AbstractFunctionDecl>(VD)) { |
| // Disallow renaming accessors. |
| if (auto FD = dyn_cast<FuncDecl>(VD)) { |
| if (FD->isAccessor()) |
| return Scratch; |
| } |
| // Disallow renaming deinit. |
| if (isa<DestructorDecl>(VD)) |
| return Scratch; |
| } |
| |
| // Always return local rename for parameters. |
| // FIXME: if the cursor is on the argument, we should return global rename. |
| if (isa<ParamDecl>(VD)) { |
| Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind); |
| return Scratch; |
| } |
| |
| // If the indexer considers VD a global symbol, then we apply global rename. |
| if (index::isLocalSymbol(VD)) |
| Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind); |
| else |
| Scratch.emplace_back(RefactoringKind::GlobalRename, AvailKind); |
| |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| ArrayRef<RefactoringKind> swift::ide:: |
| collectAvailableRefactorings(SourceFile *SF, |
| ResolvedCursorInfo CursorInfo, |
| std::vector<RefactoringKind> &Scratch, |
| bool ExcludeRename) { |
| llvm::SmallVector<RefactoringKind, 2> AllKinds; |
| switch(CursorInfo.Kind) { |
| case CursorInfoKind::ModuleRef: |
| case CursorInfoKind::Invalid: |
| case CursorInfoKind::StmtStart: |
| case CursorInfoKind::ExprStart: |
| break; |
| case CursorInfoKind::ValueRef: { |
| auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD); |
| if (RenameOp.hasValue() && |
| RenameOp.getValue() == RefactoringKind::GlobalRename) |
| AllKinds.push_back(RenameOp.getValue()); |
| } |
| } |
| #define CURSOR_REFACTORING(KIND, NAME, ID) \ |
| if (RefactoringAction##KIND::isApplicable(CursorInfo)) \ |
| AllKinds.push_back(RefactoringKind::KIND); |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| // Exclude renames. |
| for(auto Kind: AllKinds) { |
| switch (Kind) { |
| case RefactoringKind::LocalRename: |
| case RefactoringKind::GlobalRename: |
| if (ExcludeRename) |
| break; |
| LLVM_FALLTHROUGH; |
| default: |
| Scratch.push_back(Kind); |
| } |
| } |
| |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| |
| |
| ArrayRef<RefactoringKind> swift::ide:: |
| collectAvailableRefactorings(SourceFile *SF, RangeConfig Range, |
| bool &RangeStartMayNeedRename, |
| std::vector<RefactoringKind> &Scratch, |
| llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) { |
| |
| if (Range.Length == 0) { |
| return collectAvailableRefactoringsAtCursor(SF, Range.Line, Range.Column, |
| Scratch, DiagConsumers); |
| } |
| // Prepare the tool box. |
| ASTContext &Ctx = SF->getASTContext(); |
| SourceManager &SM = Ctx.SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| std::for_each(DiagConsumers.begin(), DiagConsumers.end(), |
| [&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); }); |
| |
| RangeResolver Resolver(*SF, Range.getStart(SF->getASTContext().SourceMgr), |
| Range.getEnd(SF->getASTContext().SourceMgr)); |
| ResolvedRangeInfo Result = Resolver.resolve(); |
| |
| #define RANGE_REFACTORING(KIND, NAME, ID) \ |
| if (RefactoringAction##KIND::isApplicable(Result, DiagEngine)) \ |
| Scratch.push_back(RefactoringKind::KIND); |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| RangeStartMayNeedRename = rangeStartMayNeedRename(Result); |
| return Scratch; |
| } |
| |
| bool swift::ide:: |
| refactorSwiftModule(ModuleDecl *M, RefactoringOptions Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(Opts.Kind != RefactoringKind::None && "should have a refactoring kind."); |
| |
| // Use the default name if not specified. |
| if (Opts.PreferredName.empty()) { |
| Opts.PreferredName = getDefaultPreferredName(Opts.Kind); |
| } |
| |
| switch (Opts.Kind) { |
| #define SEMANTIC_REFACTORING(KIND, NAME, ID) \ |
| case RefactoringKind::KIND: return RefactoringAction##KIND(M, Opts, \ |
| EditConsumer, DiagConsumer).performChange(); |
| #include "swift/IDE/RefactoringKinds.def" |
| case RefactoringKind::GlobalRename: |
| case RefactoringKind::FindGlobalRenameRanges: |
| case RefactoringKind::FindLocalRenameRanges: |
| llvm_unreachable("not a valid refactoring kind"); |
| case RefactoringKind::None: |
| llvm_unreachable("should not enter here."); |
| } |
| } |
| |
| static std::vector<ResolvedLoc> |
| resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs, SourceFile &SF, |
| DiagnosticEngine &Diags) { |
| SourceManager &SM = SF.getASTContext().SourceMgr; |
| unsigned BufferID = SF.getBufferID().getValue(); |
| |
| std::vector<UnresolvedLoc> UnresolvedLocs; |
| for (const RenameLoc &RenameLoc : RenameLocs) { |
| DeclNameViewer OldName(RenameLoc.OldName); |
| SourceLoc Location = SM.getLocForLineCol(BufferID, RenameLoc.Line, |
| RenameLoc.Column); |
| |
| if (!OldName.isValid()) { |
| Diags.diagnose(Location, diag::invalid_name, RenameLoc.OldName); |
| return {}; |
| } |
| |
| if (!RenameLoc.NewName.empty()) { |
| DeclNameViewer NewName(RenameLoc.NewName); |
| ArrayRef<StringRef> ParamNames = NewName.args(); |
| bool newOperator = Lexer::isOperator(NewName.base()); |
| bool NewNameIsValid = NewName.isValid() && |
| (Lexer::isIdentifier(NewName.base()) || newOperator) && |
| std::all_of(ParamNames.begin(), ParamNames.end(), [](StringRef Label) { |
| return Label.empty() || Lexer::isIdentifier(Label); |
| }); |
| |
| if (!NewNameIsValid) { |
| Diags.diagnose(Location, diag::invalid_name, RenameLoc.NewName); |
| return {}; |
| } |
| |
| if (NewName.partsCount() != OldName.partsCount()) { |
| Diags.diagnose(Location, diag::arity_mismatch, RenameLoc.NewName, |
| RenameLoc.OldName); |
| return {}; |
| } |
| |
| if (RenameLoc.Usage == NameUsage::Call && !RenameLoc.IsFunctionLike) { |
| Diags.diagnose(Location, diag::name_not_functionlike, RenameLoc.NewName); |
| return {}; |
| } |
| } |
| |
| bool isOperator = Lexer::isOperator(OldName.base()); |
| UnresolvedLocs.push_back({ |
| Location, |
| (RenameLoc.Usage == NameUsage::Unknown || |
| (RenameLoc.Usage == NameUsage::Call && !isOperator)) |
| }); |
| } |
| |
| NameMatcher Resolver(SF); |
| return Resolver.resolve(UnresolvedLocs, SF.getAllTokens()); |
| } |
| |
| int swift::ide::syntacticRename(SourceFile *SF, ArrayRef<RenameLoc> RenameLocs, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| DiagEngine.addConsumer(DiagConsumer); |
| |
| auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine); |
| if (ResolvedLocs.size() != RenameLocs.size()) |
| return true; // Already diagnosed. |
| |
| size_t index = 0; |
| llvm::StringMap<char> ReplaceTextContext; |
| for(const RenameLoc &Rename: RenameLocs) { |
| ResolvedLoc &Resolved = ResolvedLocs[index++]; |
| TextReplacementsRenamer Renamer(SM, Rename.OldName, Rename.NewName, |
| ReplaceTextContext); |
| RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename); |
| if (Type == RegionType::Mismatch) { |
| DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename, |
| Rename.NewName); |
| EditConsumer.accept(SM, Type, None); |
| } else { |
| EditConsumer.accept(SM, Type, Renamer.getReplacements()); |
| } |
| } |
| |
| return false; |
| } |
| |
| int swift::ide::findSyntacticRenameRanges( |
| SourceFile *SF, llvm::ArrayRef<RenameLoc> RenameLocs, |
| FindRenameRangesConsumer &RenameConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| DiagEngine.addConsumer(DiagConsumer); |
| |
| auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine); |
| if (ResolvedLocs.size() != RenameLocs.size()) |
| return true; // Already diagnosed. |
| |
| size_t index = 0; |
| for (const RenameLoc &Rename : RenameLocs) { |
| ResolvedLoc &Resolved = ResolvedLocs[index++]; |
| RenameRangeDetailCollector Renamer(SM, Rename.OldName); |
| RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename); |
| if (Type == RegionType::Mismatch) { |
| DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename, |
| Rename.NewName); |
| RenameConsumer.accept(SM, Type, None); |
| } else { |
| RenameConsumer.accept(SM, Type, Renamer.Ranges); |
| } |
| } |
| |
| return false; |
| } |
| |
| int swift::ide::findLocalRenameRanges( |
| SourceFile *SF, RangeConfig Range, |
| FindRenameRangesConsumer &RenameConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine Diags(SM); |
| Diags.addConsumer(DiagConsumer); |
| |
| auto StartLoc = Lexer::getLocForStartOfToken(SM, Range.getStart(SM)); |
| |
| CursorInfoResolver Resolver(*SF); |
| ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc); |
| if (!CursorInfo.isValid() || !CursorInfo.ValueD) { |
| Diags.diagnose(StartLoc, diag::unresolved_location); |
| return true; |
| } |
| ValueDecl *VD = CursorInfo.ValueD; |
| llvm::SmallVector<DeclContext *, 8> Scopes; |
| analyzeRenameScope(VD, Diags, Scopes); |
| if (Scopes.empty()) |
| return true; |
| RenameRangeCollector RangeCollector(VD, StringRef()); |
| for (DeclContext *DC : Scopes) |
| indexDeclContext(DC, RangeCollector); |
| |
| return findSyntacticRenameRanges(SF, RangeCollector.results(), RenameConsumer, |
| DiagConsumer); |
| } |