fix(lsp-gate): lint-tagged diagnostics never fail a stage (#311)
tsserver tags TS6133 (unused import) Unnecessary, but a tsconfig with noUnusedLocals promotes it to error severity — which hard-failed whole runs no rewrite could clear. Carry LSP DiagnosticTag through the LspDiagnostic event and gate on untagged errors only; lint diagnostics stay recorded and visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,12 @@ data class LspDiagnostic(
|
|||||||
val severity: String,
|
val severity: String,
|
||||||
val code: String? = null,
|
val code: String? = null,
|
||||||
val message: String,
|
val message: String,
|
||||||
)
|
/** LSP `DiagnosticTag` names, lowercased ("unnecessary", "deprecated"). Lint class, not severity. */
|
||||||
|
val tags: List<String> = emptyList(),
|
||||||
|
) {
|
||||||
|
/** Lint-class diagnostic: reported and recorded, but never a reason to fail a stage. */
|
||||||
|
val isLint: Boolean get() = tags.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
/** Recorded LSP 3.17 pull-diagnostic observation; replay never re-queries a language server. */
|
/** Recorded LSP 3.17 pull-diagnostic observation; replay never re-queries a language server. */
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.correx.core.events.events
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class LspDiagnosticTest {
|
||||||
|
private fun diagnostic(tags: List<String>) = LspDiagnostic(
|
||||||
|
path = "src/App.tsx",
|
||||||
|
line = 0,
|
||||||
|
character = 0,
|
||||||
|
severity = "error",
|
||||||
|
code = "6133",
|
||||||
|
message = "'React' is declared but its value is never read.",
|
||||||
|
tags = tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tagged diagnostic is lint class even at error severity`() {
|
||||||
|
assertTrue(diagnostic(listOf("unnecessary")).isLint)
|
||||||
|
assertTrue(diagnostic(listOf("deprecated")).isLint)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `untagged diagnostic still gates`() {
|
||||||
|
assertFalse(diagnostic(emptyList()).isLint)
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -97,7 +97,9 @@ internal suspend fun SessionOrchestrator.runLspDiagnostics(
|
|||||||
sessionId,
|
sessionId,
|
||||||
LspDiagnosticsCompletedEvent(sessionId, stageId, result.server, diagnostics, result.skippedReason),
|
LspDiagnosticsCompletedEvent(sessionId, stageId, result.server, diagnostics, result.skippedReason),
|
||||||
)
|
)
|
||||||
val errors = diagnostics.filter { it.severity.equals("error", ignoreCase = true) }
|
// Lint-class diagnostics (unused import, deprecated) are recorded above but never gate: a
|
||||||
|
// tsconfig with noUnusedLocals promotes them to "error" severity, which no rewrite can clear.
|
||||||
|
val errors = diagnostics.filter { it.severity.equals("error", ignoreCase = true) && !it.isLint }
|
||||||
if (errors.isEmpty()) return StageExecutionResult.Success(emptyList())
|
if (errors.isEmpty()) return StageExecutionResult.Success(emptyList())
|
||||||
val detail = errors.joinToString("\n") {
|
val detail = errors.joinToString("\n") {
|
||||||
"- ${it.path}:${it.line + 1}:${it.character + 1} ${it.code.orEmpty()} ${it.message}".trim()
|
"- ${it.path}:${it.line + 1}:${it.character + 1} ${it.code.orEmpty()} ${it.message}".trim()
|
||||||
|
|||||||
+1
@@ -132,6 +132,7 @@ class Lsp4jDiagnosticsRunner(
|
|||||||
severity = diagnostic.severity?.name?.lowercase() ?: "error",
|
severity = diagnostic.severity?.name?.lowercase() ?: "error",
|
||||||
code = diagnostic.code?.let { if (it.isLeft) it.left else it.right.toString() },
|
code = diagnostic.code?.let { if (it.isLeft) it.left else it.right.toString() },
|
||||||
message = diagnostic.message,
|
message = diagnostic.message,
|
||||||
|
tags = diagnostic.tags.orEmpty().map { it.name.lowercase() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user