/
ScalaIndexBuilder.scala
246 lines (209 loc) · 8.12 KB
/
ScalaIndexBuilder.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/*
* Copyright 2005-2010 LAMP/EPFL
*/
// $Id$
package scala.tools.eclipse.javaelements
import org.eclipse.core.resources.IFile
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants
import scala.tools.nsc.symtab.Flags
import scala.tools.eclipse.{ ScalaPlugin, ScalaPresentationCompiler,
ScalaSourceIndexer, SettingConverterUtil }
import scala.tools.eclipse.properties.ScalaPluginSettings
/** Add entries to the JDT index. This class traverses an *unattributed* Scala AST. This
* means a tree without symbols or types. However, a tree that was typed may still get here
* (usually during reconciliation, after editing and saving a file). That adds some complexity
* because the Scala typechecker modifies the tree. One prime example is annotations, which
* after type-checking are removed from the tree and placed in the corresponding Symbol.
*
* The indexer builds a map from names to documents that mention that name. Names are
* categorized (for instance, as method definitions, method references, annotation references, etc.).
*
* The indexer is later used by the JDT to narrow the scope of a search. For instance, a search
* for test methods would first check the index for documents that have annotation references to
* 'Test' in 'org.junit', and then pass those documents to the structure builder for
* precise parsing, where names are actually resolved.
*/
trait ScalaIndexBuilder { self : ScalaPresentationCompiler =>
object IndexBuilderTraverser {
lazy val store = ScalaPlugin.plugin.getPreferenceStore
lazy val infoName =
SettingConverterUtil.convertNameToProperty(ScalaPluginSettings.YPlugininfo.name)
@inline def isInfo = store.getBoolean(infoName)
}
import IndexBuilderTraverser.isInfo
class IndexBuilderTraverser(indexer : ScalaSourceIndexer) extends Traverser {
var packageName = new StringBuilder
def addPackage(p : PackageDef) = {
if (!packageName.isEmpty) packageName.append('.')
if (p.name != nme.EMPTY_PACKAGE_NAME && p.name != nme.ROOTPKG) {
if (isInfo) logger.info("Package defn: "+p.name+" ["+this+"]")
packageName.append(p.name)
}
}
def getSuperNames(supers: List[Tree]): Array[Array[Char]] = supers map (_ match {
case Ident(id) => id.toChars
case Select(_, name) => name.toChars
case AppliedTypeTree(fun: RefTree, args) => fun.name.toChars
case tpt @ TypeTree() => mapType(tpt).toCharArray // maybe the tree was typed
case parent =>
logger.info("superclass not understood: %s".format(parent))
"$$NoRef".toCharArray
}) toArray
/** Add annotations on the given tree. If the tree is not yet typed,
* it uses the (unresolved) annotations in the tree (part of modifiers).
* If the modifiers are empty, it uses the Symbol for finding them (the type-checker
* moves the annotations from the tree to the symbol).
*/
def addAnnotations(tree: MemberDef) {
if (tree.mods.annotations.isEmpty && tree.symbol.isInitialized) // don't force any symbols
addAnnotations(tree.symbol)
else
tree.mods.annotations.foreach(addAnnotationRef)
}
private def addAnnotations(sym: Symbol) =
for (ann <- sym.annotations) {
if (isInfo) logger.info("added annotation %s [using symbols]".format(ann.atp))
indexer.addAnnotationTypeReference(ann.atp.toString.toCharArray)
}
private def addAnnotationRef(tree: Tree) {
for (t <- tree) t match {
case New(tpt) =>
if (isInfo) logger.info("added annotation %s [using trees]".format(tpt))
indexer.addAnnotationTypeReference(tpt.toString.toCharArray)
case _ => ()
}
}
def addClass(c : ClassDef) {
if (isInfo) {
logger.info("Class defn: "+c.name+" ["+this+"]")
logger.info("Parents: "+c.impl.parents)
}
indexer.addClassDeclaration(
mapModifiers(c.mods),
packageName.toString.toCharArray,
c.name.toChars,
enclClassNames.reverse.toArray,
Array.empty,
getSuperNames(c.impl.parents),
Array.empty,
true
)
addAnnotations(c)
}
def addModule(m : ModuleDef) {
if (isInfo)
logger.info("Module defn: "+m.name+" ["+this+"]")
indexer.addClassDeclaration(
mapModifiers(m.mods),
packageName.toString.toCharArray,
m.name.append("$").toChars,
enclClassNames.reverse.toArray,
Array.empty,
getSuperNames(m.impl.parents),
Array.empty,
true
)
indexer.addClassDeclaration(
mapModifiers(m.mods),
packageName.toString.toCharArray,
m.name.toChars,
Array.empty,
Array.empty,
Array.empty,
Array.empty,
true
)
}
def addVal(v : ValDef) {
if (isInfo)
logger.info("Val defn: >"+nme.getterName(v.name)+"< ["+this+"]")
indexer.addMethodDeclaration(
nme.getterName(v.name).toChars,
Array.empty,
mapType(v.tpt).toArray,
Array.empty
)
if(v.mods.hasFlag(Flags.MUTABLE))
indexer.addMethodDeclaration(
nme.getterToSetter(nme.getterName(v.name)).toChars,
Array.empty,
mapType(v.tpt).toArray,
Array.empty
)
addAnnotations(v)
}
def addDef(d : DefDef) {
if (isInfo)
logger.info("Def defn: "+d.name+" ["+this+"]")
val name = if(nme.isConstructorName(d.name)) enclClassNames.head else d.name.toChars
val fps = for(vps <- d.vparamss; vp <- vps) yield vp
val paramTypes = fps.map(v => mapType(v.tpt))
indexer.addMethodDeclaration(
name,
paramTypes.map(_.toCharArray).toArray,
mapType(d.tpt).toArray,
Array.empty
)
addAnnotations(d)
}
def addType(td : TypeDef) {
// We don't care what to add, java doesn't see types anyway.
indexer.addClassDeclaration(
mapModifiers(td.mods),
packageName.toString.toCharArray,
td.name.toChars,
Array.empty,
Array.empty,
Array.empty,
Array.empty,
true
)
}
var enclClassNames = List[Array[Char]]()
override def traverse(tree: Tree): Unit = {
def inClass(c : Array[Char])(block : => Unit) {
val old = enclClassNames
enclClassNames = c::enclClassNames
block
enclClassNames = old
}
tree match {
case pd : PackageDef => addPackage(pd)
case cd : ClassDef => addClass(cd)
case md : ModuleDef => addModule(md)
case vd : ValDef => addVal(vd)
case td : TypeDef => addType(td)
case dd : DefDef if dd.name != nme.MIXIN_CONSTRUCTOR => addDef(dd)
case _ =>
}
tree match {
case cd : ClassDef => inClass(cd.name.toChars) { super.traverse(tree) }
case md : ModuleDef => inClass(md.name.append("$").toChars) { super.traverse(tree) }
case Apply(rt : RefTree, args) =>
if (isInfo)
logger.info("method reference: "+rt.name+" ["+args.length+"]")
indexer.addMethodReference(rt.name.toChars, args.length)
super.traverse(tree)
// Partial apply.
case Typed(ttree, Function(_, _)) =>
ttree match {
case rt : RefTree =>
val name = rt.name.toChars
for (i <- 0 to maxArgs) indexer.addMethodReference(name, i)
case Apply(rt : RefTree, args) =>
val name = rt.name.toChars
for (i <- args.length to maxArgs) indexer.addMethodReference(name, i)
case _ =>
}
super.traverse(tree)
case rt : RefTree =>
val name = rt.name.toChars
indexer.addTypeReference(name)
indexer.addFieldReference(name)
super.traverse(tree)
case _ => super.traverse(tree)
}
}
val maxArgs = 22 // just arbitrary choice.
}
}