-
Notifications
You must be signed in to change notification settings - Fork 250
Expand file tree
/
Copy pathSymbolIndex.kt
More file actions
218 lines (183 loc) · 8.68 KB
/
Copy pathSymbolIndex.kt
File metadata and controls
218 lines (183 loc) · 8.68 KB
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
package org.javacs.kt.index
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.name.FqName
import org.javacs.kt.LOG
import org.javacs.kt.database.DatabaseService
import org.javacs.kt.progress.Progress
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.*
import kotlin.sequences.Sequence
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
private const val MAX_FQNAME_LENGTH = 255
private const val MAX_SHORT_NAME_LENGTH = 80
private const val MAX_URI_LENGTH = 511
private object Symbols : IntIdTable() {
val fqName = varchar("fqname", length = MAX_FQNAME_LENGTH).index()
val shortName = varchar("shortname", length = MAX_SHORT_NAME_LENGTH)
val kind = integer("kind")
val visibility = integer("visibility")
val extensionReceiverType = varchar("extensionreceivertype", length = MAX_FQNAME_LENGTH).nullable()
val location = optReference("location", Locations)
val byShortName = index("symbol_shortname_index", false, shortName)
}
private object Locations : IntIdTable() {
val uri = varchar("uri", length = MAX_URI_LENGTH)
val range = reference("range", Ranges)
}
private object Ranges : IntIdTable() {
val start = reference("start", Positions)
val end = reference("end", Positions)
}
private object Positions : IntIdTable() {
val line = integer("line")
val character = integer("character")
}
class SymbolEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<SymbolEntity>(Symbols)
var fqName by Symbols.fqName
var shortName by Symbols.shortName
var kind by Symbols.kind
var visibility by Symbols.visibility
var extensionReceiverType by Symbols.extensionReceiverType
var location by LocationEntity optionalReferencedOn Symbols.location
}
class LocationEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<LocationEntity>(Locations)
var uri by Locations.uri
var range by RangeEntity referencedOn Locations.range
}
class RangeEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<RangeEntity>(Ranges)
var start by PositionEntity referencedOn Ranges.start
var end by PositionEntity referencedOn Ranges.end
}
class PositionEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<PositionEntity>(Positions)
var line by Positions.line
var character by Positions.character
}
/**
* A global view of all available symbols across all packages.
*/
class SymbolIndex(
private val databaseService: DatabaseService
) {
private val db: Database by lazy {
databaseService.db ?: Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver")
}
var progressFactory: Progress.Factory = Progress.Factory.None
init {
transaction(db) {
SchemaUtils.create(Symbols, Locations, Ranges, Positions)
}
}
/** Rebuilds the entire index. May take a while. */
fun refresh(module: ModuleDescriptor, exclusions: Sequence<DeclarationDescriptor>) {
val started = System.currentTimeMillis()
LOG.info("Updating full symbol index...")
progressFactory.create("Indexing").thenApplyAsync { progress ->
try {
transaction(db) {
// Remove everything first.
Symbols.deleteAll()
// Add new ones.
addDeclarations(allDescriptors(module, exclusions))
val finished = System.currentTimeMillis()
val count = Symbols.select(Symbols.fqName.count()).first()[Symbols.fqName.count()]
LOG.info("Updated full symbol index in ${finished - started} ms! (${count} symbol(s))")
}
} catch (e: Exception) {
LOG.error("Error while updating symbol index")
LOG.printStackTrace(e)
}
progress.close()
}
}
// Removes a list of indexes and adds another list. Everything is done in the same transaction.
fun updateIndexes(remove: Sequence<DeclarationDescriptor>, add: Sequence<DeclarationDescriptor>) {
val started = System.currentTimeMillis()
LOG.info("Updating symbol index...")
try {
transaction(db) {
removeDeclarations(remove)
addDeclarations(add)
val finished = System.currentTimeMillis()
val count = Symbols.select(Symbols.fqName.count()).first()[Symbols.fqName.count()]
LOG.info("Updated symbol index in ${finished - started} ms! (${count} symbol(s))")
}
} catch (e: Exception) {
LOG.error("Error while updating symbol index")
LOG.printStackTrace(e)
}
}
private fun removeDeclarations(declarations: Sequence<DeclarationDescriptor>) =
declarations.forEach { declaration ->
val (descriptorFqn, extensionReceiverFqn) = getFqNames(declaration)
if (validFqName(descriptorFqn) && (extensionReceiverFqn?.let { validFqName(it) } != false)) {
Symbols.deleteWhere {
(fqName eq descriptorFqn.toString()) and (extensionReceiverType eq extensionReceiverFqn?.toString())
}
} else {
LOG.warn("Excluding symbol {} from index since its name is too long", descriptorFqn.toString())
}
}
private fun addDeclarations(declarations: Sequence<DeclarationDescriptor>) =
declarations.forEach { declaration ->
val (descriptorFqn, extensionReceiverFqn) = getFqNames(declaration)
if (validFqName(descriptorFqn) && (extensionReceiverFqn?.let { validFqName(it) } != false)) {
SymbolEntity.new {
fqName = descriptorFqn.toString()
shortName = descriptorFqn.shortName().toString()
kind = declaration.accept(ExtractSymbolKind, Unit).rawValue
visibility = declaration.accept(ExtractSymbolVisibility, Unit).rawValue
extensionReceiverType = extensionReceiverFqn?.toString()
}
} else {
LOG.warn("Excluding symbol {} from index since its name is too long", descriptorFqn.toString())
}
}
private fun getFqNames(declaration: DeclarationDescriptor): Pair<FqName, FqName?> {
val descriptorFqn = declaration.fqNameSafe
val extensionReceiverFqn = declaration.accept(ExtractSymbolExtensionReceiverType, Unit)?.takeIf { !it.isRoot }
return Pair(descriptorFqn, extensionReceiverFqn)
}
private fun validFqName(fqName: FqName) =
fqName.toString().length <= MAX_FQNAME_LENGTH
&& fqName.shortName().toString().length <= MAX_SHORT_NAME_LENGTH
fun query(prefix: String, receiverType: FqName? = null, limit: Int = 20, suffix: String = "%"): List<Symbol> = transaction(db) {
// TODO: Extension completion currently only works if the receiver matches exactly,
// ideally this should work with subtypes as well
SymbolEntity.find {
(Symbols.shortName like "$prefix$suffix") and (Symbols.extensionReceiverType eq receiverType?.toString())
}.limit(limit)
.map { Symbol(
fqName = FqName(it.fqName),
kind = Symbol.Kind.fromRaw(it.kind),
visibility = Symbol.Visibility.fromRaw(it.visibility),
extensionReceiverType = it.extensionReceiverType?.let(::FqName)
) }
}
private fun allDescriptors(module: ModuleDescriptor, exclusions: Sequence<DeclarationDescriptor>): Sequence<DeclarationDescriptor> = allPackages(module)
.map(module::getPackage)
.flatMap {
try {
it.memberScope.getContributedDescriptors(
DescriptorKindFilter.ALL
) { name -> !exclusions.any { declaration -> declaration.name == name } }
} catch (e: IllegalStateException) {
LOG.warn("Could not query descriptors in package $it")
emptyList()
}
}
private fun allPackages(module: ModuleDescriptor, pkgName: FqName = FqName.ROOT): Sequence<FqName> = module
.getSubPackagesOf(pkgName) { it.toString() != "META-INF" }
.asSequence()
.flatMap { sequenceOf(it) + allPackages(module, it) }
}