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 code: String? = null,
|
||||
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. */
|
||||
@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,
|
||||
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())
|
||||
val detail = errors.joinToString("\n") {
|
||||
"- ${it.path}:${it.line + 1}:${it.character + 1} ${it.code.orEmpty()} ${it.message}".trim()
|
||||
|
||||
Reference in New Issue
Block a user