This repository has been archived by the owner on Sep 3, 2020. It is now read-only.
/
Rename.scala
154 lines (128 loc) · 5.93 KB
/
Rename.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
* Copyright 2005-2010 LAMP/EPFL
*/
package scala.tools.refactoring
package implementations
import common.Change
import transformation.TreeFactory
import analysis.TreeAnalysis
import tools.nsc.symtab.Flags
import scala.tools.refactoring.common.RenameSourceFileChange
import scala.tools.refactoring.common.PositionDebugging
import scala.reflect.internal.util.RangePosition
import scala.tools.refactoring.util.SourceWithMarker
import scala.tools.refactoring.util.SourceWithMarker.Movements
import scala.tools.refactoring.util.SourceWithMarker.MovementHelpers
import scala.tools.refactoring.util.SourceWithMarker.Movement
import scala.tools.refactoring.common.TextChange
import scala.tools.refactoring.common.RenameSourceFileChange
abstract class Rename extends MultiStageRefactoring with TreeAnalysis with analysis.Indexes with TreeFactory with common.InteractiveScalaCompiler {
import global._
case class PreparationResult(selectedTree: SymTree, hasLocalScope: Boolean)
type RefactoringParameters = String
def prepare(s: Selection) = {
def isLocalRename(t: Tree) = {
def isHiddenOrNoAccessor(s: Symbol) = {
s == NoSymbol || s.isPrivate
}
def hasHiddenOrNoAccessor = {
if (t.symbol.isVal || t.symbol.isVar) {
def getter = t.symbol.getter(t.symbol.owner)
def setter = t.symbol.setter(t.symbol.owner)
isHiddenOrNoAccessor(getter) && isHiddenOrNoAccessor(setter)
} else {
true
}
}
t.symbol.isLocal || (t.symbol.isPrivate && hasHiddenOrNoAccessor)
}
s.selectedSymbolTree match {
// Has been renamed.. also check for a matching importselector that did the rename
case Some(t: RefTree) if t.name != t.symbol.name =>
Right(PreparationResult(t, true))
case Some(t) =>
Right(PreparationResult(t, isLocalRename(t)))
case None => Left(PreparationError("no symbol selected found"))
}
}
def perform(selection: Selection, prepared: PreparationResult, newName: RefactoringParameters): Either[RefactoringError, List[Change]] = {
/*
* A lazy val is represented by a ValDef and an associated DefDef that contains the initialization code.
* Unfortunately, the Scala compiler does not set modifier positions for the DefDef, which is a problem for us,
* because we are relying on the DefDef when printing out source code. The following function patches the affected
* DefDefs accordingly.
*
* See #1002392 and #1002502
*/
def eventuallyFixModifierPositionsForLazyVals(t: Tree): Unit = t match {
case dd: DefDef if dd.mods.isLazy && dd.mods.positions.isEmpty =>
val vd = selection.root.find {
case vd: ValDef if vd.mods.isLazy && !vd.mods.positions.isEmpty && dd.pos.point == vd.pos.point => true
case _ => false
}
vd.foreach { vd =>
// Note that we patch the DefDef in place because we need the correctly set positions in the tree
// later in the refactoring process. A precursor of this code pretended to be pure, by copying
// dd and then calling 'mods.setPositions' on the copy. This had exactly the same (needed) side effects
// however, as the Modifier object affected by setPositions was the same anyway. If you are interested
// in the related discussion, take a look at https://github.com/scala-ide/scala-refactoring/pull/78.
dd.mods.setPositions(vd.asInstanceOf[ValDef].mods.positions)
}
case _ => ()
}
trace("Selected tree is %s", prepared.selectedTree)
val sym = prepared.selectedTree.symbol
val oldName = {
// We try to read the old name from the name position of the original symbol, since this seems to be the most
// reliable strategy that seems to work well with `backtick-identifiers`.
index.declaration(sym).flatMap { tree =>
tree.namePosition() match {
case rp: RangePosition => Some(rp.source.content.slice(rp.start, rp.end).mkString(""))
case _ => None
}
}.getOrElse {
trace("Cannot find old name of symbol reliably; attempting fallback")
prepared.selectedTree.nameString
}
}
trace(s"Old name is $oldName")
if (oldName == newName) {
Right(Nil)
} else {
val occurences = index.occurences(sym)
occurences.foreach { s =>
trace("Symbol is referenced at %s", PositionDebugging.formatCompact(s.pos))
eventuallyFixModifierPositionsForLazyVals(s)
}
import Movements._
val mvToSymStart = Movements.until(oldName, skipping = (comment | space | reservedName))
val textChangesWithTrees = occurences.flatMap { occ =>
occ.namePosition() match {
case np: RangePosition =>
// Unfortunately, there are cases when the name position cannot be used directly.
// Therefore we have to use an appropriate movement to make sure we capture the correct range.
val srcAtStart = SourceWithMarker.atStartOf(np)
mvToSymStart(srcAtStart).map { markerAtSymStart =>
(TextChange(np.source, markerAtSymStart, markerAtSymStart + oldName.length, newName), occ)
}
case _ =>
None
}
}
// Since the ASTs do not directly represent the user source code, it might be easily possible that
// some text changes are duplicated. The code below removes them.
val uniqTextChangesWithTrees = textChangesWithTrees.groupBy(_._1).map { case (change, changesWithTrees) =>
(change, changesWithTrees.head._2)
}.toList
val textChanges = uniqTextChangesWithTrees.map(_._1)
val newSourceChanges = uniqTextChangesWithTrees.flatMap { case (textChange, tree) =>
if (tree.pos.source.file.name == oldName + ".scala") {
Some(RenameSourceFileChange(tree.pos.source.file, newName + ".scala"))
} else {
None
}
}.distinct
Right(textChanges ::: newSourceChanges)
}
}
}