From 6956543a8b50e34805fe9857b01d371fe5fb1ea3 Mon Sep 17 00:00:00 2001 From: Boshen Date: Fri, 16 Jan 2026 15:04:50 +0000 Subject: [PATCH] fix(linter): fix LSP panic from stale directive spans (#18082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Clear cached disable directives before re-linting a file to prevent using outdated spans - Add `remove` method to `DirectivesStore` for per-path directive removal ## Root Cause The `DirectivesStore` caches disable directives per file path. When a file is edited and re-linted: 1. If linting succeeds and produces directives → they are updated in the store 2. If linting fails or doesn't produce directives (e.g., parse errors, partial loader files) → **old directives remain in the store** When the file content changes between linting runs and the new linting fails to update directives, the stored directives contain spans calculated from the OLD source text. Using these stale spans with the NEW source text/rope causes the "byte index out of bounds" panic. ## Fix 1. Added `remove` method to `DirectivesStore` for clearing directives for a specific file 2. Call `remove` before linting in `IsolatedLintHandler::lint_path()` to ensure stale directives are cleared This ensures that when a file is re-linted, any stale directives from previous runs are cleared first. If the new linting run produces fresh directives, they'll be stored; if not, no stale directives will be used. ## Test plan - Existing LSP tests pass (73 tests) - Unused directives tests pass Closes #15415 🤖 Generated with [Claude Code](https://claude.ai/code) --- apps/oxlint/src/lsp/isolated_lint_handler.rs | 4 ++++ crates/oxc_linter/src/lint_runner.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/apps/oxlint/src/lsp/isolated_lint_handler.rs b/apps/oxlint/src/lsp/isolated_lint_handler.rs index 9456c2833d84a..3e66a075a5cc5 100644 --- a/apps/oxlint/src/lsp/isolated_lint_handler.rs +++ b/apps/oxlint/src/lsp/isolated_lint_handler.rs @@ -153,6 +153,10 @@ impl IsolatedLintHandler { ); } + // Clear any stale directives because they are no longer needed. + // This prevents using outdated directive spans if the new linting run fails. + self.runner.directives_coordinator().remove(path); + Ok(messages) } diff --git a/crates/oxc_linter/src/lint_runner.rs b/crates/oxc_linter/src/lint_runner.rs index 35a6f7f1d8b12..9b454daa0b6ec 100644 --- a/crates/oxc_linter/src/lint_runner.rs +++ b/crates/oxc_linter/src/lint_runner.rs @@ -114,6 +114,18 @@ impl DirectivesStore { pub fn clear(&self) { self.map.lock().expect("DirectivesStore mutex poisoned in clear").clear(); } + + /// Remove disable directives for a specific file + /// + /// This should be called before re-linting a file to ensure stale directives + /// from previous linting runs are not used if the new linting run fails to + /// produce directives (e.g., due to parse errors). + /// + /// # Panics + /// Panics if the mutex is poisoned. + pub fn remove(&self, path: &Path) { + self.map.lock().expect("DirectivesStore mutex poisoned in remove").remove(path); + } } impl Default for DirectivesStore {