Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@
},
"integrations/editor/__tests__/DiffViewProvider.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 310
"count": 306
}
},
"integrations/editor/__tests__/EditorUtils.spec.ts": {
Expand Down Expand Up @@ -1721,7 +1721,7 @@
},
"utils/safeWriteJson.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 4
"count": 3
}
},
"utils/tts.ts": {
Expand Down
98 changes: 68 additions & 30 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { arePathsEqual, getReadablePath } from "../../utils/path"
import { formatResponse } from "../../core/prompts/responses"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
import { Task } from "../../core/task/Task"
import { safeWriteText } from "../../services/file-safety/safeWriteText"

import { DecorationController } from "./DecorationController"

Expand Down Expand Up @@ -1151,12 +1152,15 @@ export class DiffViewProvider {
}> {
const absolutePath = path.resolve(this.cwd, relPath)

// Get diagnostics before editing the file
this.preDiagnostics = vscode.languages.getDiagnostics()
// Get diagnostics before editing the file. Capture the snapshot locally:
// overlapping saveDirectly calls (multi-file edits) must not let a later
// call overwrite this one's baseline before its diagnostics tail runs.
const preDiagnostics = vscode.languages.getDiagnostics()
this.preDiagnostics = preDiagnostics

// Write the content directly to the file
await createDirectoriesForFile(absolutePath)
await fs.writeFile(absolutePath, content, "utf-8")
await safeWriteText(absolutePath, content)

// Open the document to ensure diagnostics are loaded
// When openFile is false (PREVENT_FOCUS_DISRUPTION enabled), we only open in memory
Expand All @@ -1175,23 +1179,64 @@ export class DiffViewProvider {
await doc.save()
}

// Force a small delay to ensure diagnostics are triggered
await new Promise((resolve) => setTimeout(resolve, 100))
// The 100 ms diagnostics-settle wait is carried by the
// emitPostSaveDiagnostics tail (inMemoryDocument) instead of here:
// blocking the save path delayed every openFile=false save even when
// diagnostics were disabled or the write delay was 0.
}

let newProblemsMessage = ""

// L1 (A2): resolve without awaiting the LSP diagnostics settle. The
// diagnostics check becomes a fire-and-forget tail that emits any new
// problems via the existing "error" ClineSay type; the returned
// newProblemsMessage is therefore always undefined.
if (diagnosticsEnabled) {
// Add configurable delay to allow linters time to process
const safeDelayMs = Math.max(0, writeDelayMs)
// The method's outer try/catch guarantees it never rejects, so the
// fire-and-forget call needs no .catch wrapper.
void this.emitPostSaveDiagnostics(relPath, writeDelayMs, preDiagnostics, !openFile)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

try {
await delay(safeDelayMs)
} catch (error) {
console.warn(`Failed to apply write delay: ${error}`)
}
// Store the results for formatFileWriteResponse
this.newProblemsMessage = undefined
this.userEdits = undefined
this.relPath = relPath
this.newContent = content

const postDiagnostics = vscode.languages.getDiagnostics()
return {
newProblemsMessage: undefined,
userEdits: undefined,
finalContent: content,
}
}

// L1 (A2): fire-and-forget post-save diagnostics. After the write delay,
// collects new Error-severity problems and emits them via the existing
// "error" ClineSay type (only Error-severity diagnostics reach this branch;
// "error" carries no task-failure semantics in core). Abort-safe: say()
// rejects when the task is aborted, so the whole body sits inside a
// try/catch that degrades to a console.warn — the tail can never reject.
private async emitPostSaveDiagnostics(
relPath: string,
writeDelayMs: number,
preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
inMemoryDocument = false,
): Promise<void> {
try {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Add configurable delay to allow linters time to process. When the
// document was opened in memory (openFile=false), the tail also
// carries the 100 ms diagnostics-settle wait that used to block
// saveDirectly. delay() never rejects, so no catch is required here.
const safeDelayMs = Math.max(0, writeDelayMs) + (inMemoryDocument ? 100 : 0)
await delay(safeDelayMs)

// Filter to the saved file: saveDirectly resolves before this tail
// completes, so in a multi-file write sequence (e.g. apply_patch)
// a later file's problems must not be attributed to this relPath.
const savedFilePath = path.resolve(this.cwd, relPath)
// arePathsEqual: case-insensitive on Windows, where a relPath whose
// casing differs from the diagnostic URI is still the same file.
const postDiagnostics = vscode.languages
.getDiagnostics()
.filter(([uri]) => arePathsEqual(uri.fsPath, savedFilePath))

// Get diagnostic settings from state
const task = this.taskRef.deref()
Expand All @@ -1200,27 +1245,20 @@ export class DiffViewProvider {
const maxDiagnosticMessages = state?.maxDiagnosticMessages ?? 50

const newProblems = await diagnosticsToProblemsString(
getNewDiagnostics(this.preDiagnostics, postDiagnostics),
getNewDiagnostics(preDiagnostics, postDiagnostics),
[vscode.DiagnosticSeverity.Error],
this.cwd,
includeDiagnosticMessages,
maxDiagnosticMessages,
)

newProblemsMessage =
newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
}

// Store the results for formatFileWriteResponse
this.newProblemsMessage = newProblemsMessage
this.userEdits = undefined
this.relPath = relPath
this.newContent = content

return {
newProblemsMessage,
userEdits: undefined,
finalContent: content,
if (newProblems.length > 0) {
await task?.say("error", `New problems detected after saving file: ${relPath}\n\n${newProblems}`)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
// Abort-safe: never let a post-save diagnostic emit become an
// unhandled rejection (say() rejects when the task is aborted).
console.warn(`Post-save diagnostics emit failed: ${error}`)
}
}
}
Loading
Loading