//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2023 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 "Renamer.h" #include "swift/AST/ASTContext.h" #include "swift/AST/DiagnosticsRefactoring.h" #include "swift/AST/SourceFile.h" #include "swift/Parse/Lexer.h" #include "swift/Refactoring/Refactoring.h" using namespace swift; using namespace swift::ide; using namespace swift::refactoring; class TextReplacementsRenamer : public Renamer { llvm::StringSet<> &ReplaceTextContext; SmallVector Replacements; public: const DeclNameViewer New; private: StringRef registerText(StringRef Text) { if (Text.empty()) return Text; return ReplaceTextContext.insert(Text).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((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((Twine(" ") + OldArgLabel).str()); return registerText(OldParam); } StringRef getDeclArgumentLabelReplacement(StringRef OldLabelRange, StringRef NewArgLabel) { // OldLabelRange is subscript([]a: Int), foo([a]: Int) or foo([a] b: Int) if (NewArgLabel.empty()) return OldLabelRange.empty() ? "" : "_"; if (OldLabelRange.empty()) return registerText((Twine(NewArgLabel) + " ").str()); return registerText(NewArgLabel); } 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::NoncollapsibleParameterName: return LabelRange; case RefactoringRangeKind::DeclArgumentLabel: return getDeclArgumentLabelReplacement(LabelRange, NewLabel); case RefactoringRangeKind::SelectorArgumentLabel: return NewLabel.empty() ? "_" : registerText(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({/*Path=*/{}, LabelRange, /*BufferName=*/{}, Text, /*RegionsWorthNote=*/{}}); } 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({/*Path=*/{}, Range, /*BufferName=*/{}, registerText(New.base()), /*RegionsWorthNote=*/{}}); } public: TextReplacementsRenamer(const SourceManager &SM, StringRef OldName, StringRef NewName, llvm::StringSet<> &ReplaceTextContext) : Renamer(SM, OldName), ReplaceTextContext(ReplaceTextContext), New(NewName) { assert(Old.isValid() && New.isValid()); assert(Old.partsCount() == New.partsCount()); } ArrayRef getReplacements() const { return Replacements; } }; std::vector swift::ide::resolveRenameLocations(ArrayRef RenameLocs, SourceFile &SF, DiagnosticEngine &Diags) { SourceManager &SM = SF.getASTContext().SourceMgr; unsigned BufferID = SF.getBufferID().value(); std::vector 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 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 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::StringSet<> 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, llvm::None); } else { EditConsumer.accept(SM, Type, Renamer.getReplacements()); } } return false; } int swift::ide::findSyntacticRenameRanges( SourceFile *SF, ArrayRef 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, llvm::None); } else { RenameConsumer.accept(SM, Type, Renamer.Ranges); } } return false; }