Skip to content

Commit c1f4a1d

Browse files
committed
Improving lintting/diagnostic reporting - ability to cancel the progress, ensure stale data are not recorded SourceFile(s), always start linting on change.
1 parent f0be2db commit c1f4a1d

File tree

5 files changed

+80
-28
lines changed

5 files changed

+80
-28
lines changed

server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class KotlinTextDocumentService(
4242
) : TextDocumentService, Closeable {
4343
private lateinit var client: LanguageClient
4444
private val async = AsyncExecutor()
45-
private var linting = false
4645

4746
var debounceLint = Debouncer(Duration.ofMillis(config.linting.debounceTime))
4847
val lintTodo = mutableSetOf<URI>()
@@ -65,7 +64,7 @@ class KotlinTextDocumentService(
6564
get() = uriContentProvider.contentOf(parseURI(uri))
6665

6766
private enum class Recompile {
68-
ALWAYS, AFTER_DOT, WAIT_FOR_LINT, NEVER
67+
ALWAYS, AFTER_DOT, NEVER
6968
}
7069

7170
private fun recover(position: TextDocumentPositionParams, recompile: Recompile): Pair<CompiledFile, Int> {
@@ -75,10 +74,6 @@ class KotlinTextDocumentService(
7574
val shouldRecompile = when (recompile) {
7675
Recompile.ALWAYS -> true
7776
Recompile.AFTER_DOT -> offset > 0 && content[offset - 1] == '.'
78-
Recompile.WAIT_FOR_LINT -> {
79-
debounceLint.waitForPendingTask()
80-
false
81-
}
8277
Recompile.NEVER -> false
8378
}
8479
val compiled = if (shouldRecompile) sp.currentVersion(uri) else sp.latestCompiledVersion(uri)
@@ -254,27 +249,22 @@ class KotlinTextDocumentService(
254249

255250
private fun lintLater(uri: URI) {
256251
lintTodo.add(uri)
257-
if (!linting) {
258-
debounceLint.submit(::doLint)
259-
}
252+
debounceLint.submit(::doLint)
260253
}
261254

262255
private fun lintNow(file: URI) {
263256
lintTodo.add(file)
264257
debounceLint.submitImmediately(::doLint)
265258
}
266259

267-
private fun doLint() {
268-
linting = true
269-
try {
270-
LOG.info("Linting {}", describeURIs(lintTodo))
271-
val files = clearLint()
272-
val context = sp.compileFiles(files)
260+
private fun doLint(cancelCallback : () -> Boolean) {
261+
LOG.info("Linting {}", describeURIs(lintTodo))
262+
val files = clearLint()
263+
val context = sp.compileFiles(files)
264+
if (!cancelCallback.invoke()) {
273265
reportDiagnostics(files, context.diagnostics)
274-
lintCount++
275-
} finally {
276-
linting = false
277266
}
267+
lintCount++
278268
}
279269

280270
private fun reportDiagnostics(compiled: Collection<URI>, kotlinDiagnostics: Diagnostics) {
@@ -312,6 +302,10 @@ class KotlinTextDocumentService(
312302
override fun close() {
313303
shutdownExecutors(awaitTermination = true)
314304
}
305+
306+
fun setTestLintRecompilationCallback(callback: () -> Unit) {
307+
sp.beforeCompileCallback = callback;
308+
}
315309
}
316310

317311
private inline fun<T> reportTime(block: () -> T): T {

server/src/main/kotlin/org/javacs/kt/SourcePath.kt

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import org.jetbrains.kotlin.container.ComponentProvider
66
import org.jetbrains.kotlin.psi.KtFile
77
import org.jetbrains.kotlin.resolve.BindingContext
88
import org.jetbrains.kotlin.resolve.CompositeBindingContext
9+
import kotlin.concurrent.withLock
910
import java.nio.file.Path
1011
import java.nio.file.Paths
1112
import java.net.URI
13+
import java.util.concurrent.locks.ReentrantLock
1214

1315
class SourcePath(
1416
private val cp: CompilerClassPath,
1517
private val contentProvider: URIContentProvider
1618
) {
1719
private val files = mutableMapOf<URI, SourceFile>()
20+
private val parseDataWriteLock = ReentrantLock()
1821

1922
private inner class SourceFile(
2023
val uri: URI,
@@ -60,9 +63,11 @@ class SourcePath(
6063
LOG.debug("Compiling {}", path?.fileName)
6164

6265
val (context, container) = cp.compiler.compileFile(parsed!!, allIncludingThis(), kind)
63-
compiledContext = context
64-
compiledContainer = container
65-
compiledFile = parsed
66+
parseDataWriteLock.withLock {
67+
compiledContext = context
68+
compiledContainer = container
69+
compiledFile = parsed
70+
}
6671
}
6772

6873
return this
@@ -136,6 +141,8 @@ class SourcePath(
136141
fun latestCompiledVersion(uri: URI): CompiledFile =
137142
sourceFile(uri).prepareCompiledFile()
138143

144+
var beforeCompileCallback: () -> Unit = { };
145+
139146
/**
140147
* Compile changed files
141148
*/
@@ -148,14 +155,21 @@ class SourcePath(
148155
// Compile changed files
149156
fun compileAndUpdate(changed: List<SourceFile>, kind: CompilationKind): BindingContext? {
150157
if (changed.isEmpty()) return null
151-
val parse = changed.map { it.parseIfChanged().parsed!! }
152-
val (context, container) = cp.compiler.compileFiles(parse, all(), kind)
158+
val parse = changed.associateWith { it.parseIfChanged().parsed!! }
159+
val all = all()
160+
beforeCompileCallback.invoke()
161+
val (context, container) = cp.compiler.compileFiles(parse.values, all, kind)
153162

154163
// Update cache
155-
for (f in changed) {
156-
f.compiledFile = f.parsed
157-
f.compiledContext = context
158-
f.compiledContainer = container
164+
for ((f, parsed) in parse) {
165+
parseDataWriteLock.withLock {
166+
if (f.parsed == parsed) {
167+
//only updated if the parsed file didn't change:
168+
f.compiledFile = parsed
169+
f.compiledContext = context
170+
f.compiledContainer = container
171+
}
172+
}
159173
}
160174

161175
return context

server/src/test/kotlin/org/javacs/kt/DebouncerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class DebouncerTest {
1212

1313
@Test fun callQuickly() {
1414
for (i in 1..10) {
15-
debounce.submit {
15+
debounce.submit { ->
1616
counter++
1717
}
1818
}

server/src/test/kotlin/org/javacs/kt/LintTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.javacs.kt
22

3+
import java.util.concurrent.CancellationException
34
import org.eclipse.lsp4j.Diagnostic
5+
import org.eclipse.lsp4j.DocumentSymbolParams
6+
import org.eclipse.lsp4j.PublishDiagnosticsParams
7+
import org.eclipse.lsp4j.TextDocumentIdentifier
48
import org.hamcrest.Matchers.*
59
import org.junit.Assert.assertThat
610
import org.junit.Test
@@ -26,4 +30,27 @@ class LintTest : SingleFileTestFixture("lint", "LintErrors.kt") {
2630
assertThat(diagnostics, not(empty<Diagnostic>()))
2731
assertThat(languageServer.textDocumentService.lintCount, lessThan(5))
2832
}
33+
34+
@Test fun `lintting should not be dropped if another lintting is running`() {
35+
var callbackCount = 0;
36+
languageServer.textDocumentService.debounceLint.waitForPendingTask()
37+
languageServer.textDocumentService.setTestLintRecompilationCallback({
38+
if (callbackCount++ == 0) {
39+
diagnostics.clear()
40+
replace(file, 3, 9, "return 11", "")
41+
languageServer.textDocumentService.documentSymbol(DocumentSymbolParams(TextDocumentIdentifier(uri(file).toString()))).get()
42+
}
43+
})
44+
replace(file, 3, 16, "", "1")
45+
46+
while (callbackCount < 2) {
47+
try {
48+
languageServer.textDocumentService.debounceLint.waitForPendingTask()
49+
} catch (ex: CancellationException) {}
50+
}
51+
52+
languageServer.textDocumentService.debounceLint.waitForPendingTask()
53+
assertThat(diagnostics, empty<Diagnostic>())
54+
languageServer.textDocumentService.setTestLintRecompilationCallback({})
55+
}
2956
}

shared/src/main/kotlin/org/javacs/kt/util/Debouncer.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.javacs.kt.util
33
import org.javacs.kt.LOG
44
import java.time.Duration
55
import java.util.function.Supplier
6+
import java.util.concurrent.atomic.AtomicReference
67
import java.util.concurrent.ScheduledExecutorService
78
import java.util.concurrent.Executors
89
import java.util.concurrent.TimeUnit
@@ -25,11 +26,27 @@ class Debouncer(
2526
pendingTask = executor.submit(task)
2627
}
2728

29+
fun submitImmediately(task: (cancelCallback : () -> Boolean) -> Unit) {
30+
pendingTask?.cancel(false)
31+
val currentTaskRef = AtomicReference<Future<*>>()
32+
val currentTask = executor.submit({ -> task.invoke({ -> val f = currentTaskRef.get(); f?.isCancelled()?:false})})
33+
currentTaskRef.set(currentTask);
34+
pendingTask = currentTask
35+
}
36+
2837
fun submit(task: () -> Unit) {
2938
pendingTask?.cancel(false)
3039
pendingTask = executor.schedule(task, delayMs, TimeUnit.MILLISECONDS)
3140
}
3241

42+
fun submit(task: (cancelCallback : () -> Boolean) -> Unit) {
43+
pendingTask?.cancel(false)
44+
val currentTaskRef = AtomicReference<Future<*>>()
45+
val currentTask = executor.schedule({ -> task.invoke({ -> val f = currentTaskRef.get(); f?.isCancelled()?:false})}, delayMs, TimeUnit.MILLISECONDS)
46+
currentTaskRef.set(currentTask);
47+
pendingTask = currentTask
48+
}
49+
3350
fun waitForPendingTask() {
3451
pendingTask?.get()
3552
}

0 commit comments

Comments
 (0)