Skip to content

Comments

npm: Use fs API to read package.json, avoid LSP notification spam#292380

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/fix-json-document-selector-bug
Draft

npm: Use fs API to read package.json, avoid LSP notification spam#292380
Copilot wants to merge 6 commits intomainfrom
copilot/fix-json-document-selector-bug

Conversation

Copy link
Contributor

Copilot AI commented Feb 2, 2026

The npm extension was opening every package.json as a text document during task discovery, triggering textDocument/didOpen|didClose to all LSP servers with JSON documentSelector. Large projects with 100+ package.json files would generate 200+ notifications.

Changes

  • Replace workspace.openTextDocument() with workspace.fs.readFile() in getScripts()

    • Reads file content without entering text document manager
    • Uses TextDecoder for cross-platform portability
    • Creates IPositionOffsetTransformer interface for position/offset transformation
    • Implements PositionOffsetTransformer class that provides only the minimal required functionality (uri, positionAt())
  • Add LRU cache with workspace storage persistence for parsed package.json scripts

    • 128 entry limit with least-recently-used eviction
    • Loads cache from workspace storage on activation
    • Persists cache changes to workspace storage asynchronously
    • Skip re-reading unchanged files using mtime validation
    • Clear cache in invalidateTasksCache()

Implementation

// Before: triggered LSP notifications
const document = await workspace.openTextDocument(packageJsonUri);
return readScripts(document);

// After: direct file read with LRU caching and proper interface
const stat = await workspace.fs.stat(packageJsonUri);
const cached = packageJsonCache.get(packageJson);
if (cached && cached.mtime === stat.mtime) {
    return cached.scripts;
}
const content = await workspace.fs.readFile(packageJsonUri);
const text = new TextDecoder('utf-8').decode(content);
const transformer = new PositionOffsetTransformer(packageJsonUri, text);
const scripts = readScripts(transformer, text);
packageJsonCache.set(packageJson, { mtime: stat.mtime, scripts });

The IPositionOffsetTransformer interface defines the minimal contract needed for position/offset conversion. The PositionOffsetTransformer class implements this interface, handling both Unix and Windows line endings, using binary search for positionAt() performance. The readScripts() function is refactored to accept IPositionOffsetTransformer instead of TextDocument, maintaining backward compatibility since TextDocument already implements the required interface.

The PackageJsonLRUCache class implements an LRU eviction policy with workspace storage persistence, ensuring the cache survives VS Code restarts while preventing unbounded memory growth in large projects.

File content decoding uses the standard TextDecoder API for better portability across Node.js and browser environments.

Original prompt

This section details on the original issue you should resolve

<issue_title>documentSelector json for LSP-Connection will spam textDocument/didOpen|didClose request for all package.json</issue_title>
<issue_description>
Type: Bug

coming from: oxc-project/oxc#17615 (comment)
Possible duplicate, but the server is just waiting for textDocument/formatting: #238935

my extension want to use the following code:

// This list is not used as-is for implementation to determine whether formatting processing is possible.
    const supportedExtensions = [
      "json"
       // ... other extension
    ];

    const clientOptions: LanguageClientOptions = {
      // Register the server for plain text documents
      documentSelector: [
        {
          pattern: `**/*.{${supportedExtensions.join(",")}}`,
          scheme: "file",
        },
      ],
      ...
  }

I tried with a new documentSelector on clientOptions. With {language: "json"} too, same bug.

When the server is started, VS Code will send, for every package.json in the project, a textDocument/didOpen and textDocument/didClose notification to the server. Even if you have only a TypeScript file open.
With a big project, this can send over 100 requests to the server.
Without json, the server is just waiting, after dynamic registrations, for the formatting request.

Try it yourself with the oxc git pull request and these settings.

{
  "oxc.trace.server": "verbose",
  "oxc.path.oxfmt": "apps/oxfmt/dist/cli.js", // debug with local oxfmt build
  "oxc.fmt.configPath": "oxfmtrc.jsonc",
  "editor.defaultFormatter": "oxc.oxc-vscode",
  "editor.formatOnSave": true
}

You need to rebase, execute these commands and restart the editor after it:

cd editors/vscode
pnpm install
pnpm oxfmt:build:debug
pnpm compile
pnpm package
pnpm install-extension

These are my export from the output channel: Oxc (Fmt).log, after some minutes these will happend again (without doing something): Oxc (Fmt)2.log

VS Code version: Code 1.107.1 (994fd12, 2025-12-17T14:15:14.850Z)
OS version: Windows_NT x64 10.0.26200
Modes:
Remote OS version: Linux x64 6.6.87.2-microsoft-standard-WSL2

System Info
Item Value
CPUs AMD Ryzen 9 7900X 12-Core Processor (24 x 4700)
GPU Status 2d_canvas: enabled
direct_rendering_display_compositor: disabled_off_ok
gpu_compositing: enabled
multiple_raster_threads: enabled_on
opengl: enabled_on
rasterization: enabled
raw_draw: disabled_off_ok
skia_graphite: disabled_off
trees_in_viz: disabled_off
video_decode: enabled
video_encode: enabled
webgl: enabled
webgl2: enabled
webgpu: enabled
webnn: disabled_off
Load (avg) undefined
Memory (System) 31.10GB (5.32GB free)
Process Argv --folder-uri=vscode-remote://wsl+Ubuntu/home/sysix/dev/oxc --remote=wsl+Ubuntu --crash-reporter-id 2b3612a4-60c2-4760-b864-459fece876bf
Screen Reader no
VM 0%
Item Value
Remote WSL: Ubuntu
OS Linux x64 6.6.87.2-microsoft-standard-WSL2
CPUs AMD Ryzen 9 7900X 12-Core Processor (24 x 0)
Memory (System) 15.17GB (8.94GB free)
VM 0%
Extensions (39)
Extension Author (truncated) Version
vscode-jetbrains-keybindings isu 0.1.9
remote-containers ms- 0.434.0
remote-wsl ms- 0.104.3
vscode-icons vsc 12.15.0
rust-bundle 1Yi 1.0.0
vscode-intelephense-client bme 1.16.3
vscode-eslint dba 3.0.20
composer-php-vscode DEV 1.64.18270
intelli-php-vscode DEV 0.12.17700
phptools-vscode DEV 1.64.18270
profiler-php-vscode DEV 1.64.18270
docker doc 0.18.0
githistory don 0.6.20
EditorConfig Edi 0.17.4
prettier-vscode esb 11.0.2
dependi fil 0.7.21
copilot-chat Git 0.35.3
vscode-github-actions git 0.29.1
gti-vscode Gra 0.6.1
php-cs-fixer jun 0.3.21
git-graph mhu 1.30.0
vscode-containers ms- 2.3.0
vscode-docker ms- 2.0.0
extension-test-runner ms- 0.0.14
live-server ms- 0.4.16
vscode-just-syntax nef 0.8.0
indent-rainbow ode 8.3.1
oxc-vscode oxc 1.38.0
vscode-yaml red 1.19.1
rust-analyzer rus 0.3.2743
vscode-coverage-gutters rya 2.14.0
markdown-preview-enhanced shd 0.8.20
code-spell-checker str 4.4.0
even-better-toml tam 0.21.2
typos-vscode tek 0.1.47
vscode-lldb vad 1.11.4
volar Vue 3.2.2
gitblame wad 11.2.0
markdown-all-in-one yzh 3.6.3
A/B Experiments
vsliv368:30146709
pythonvspyt551:31249599
binariesv615:30325510
nativeloc1:31344060
dwcopilot:31170013
dwoutputs:31242946
copilot_t_ci:31333650
e5gg6876:31282496
pythonrdcb7:31342333
6518g693:31436602
aj953862:31281341
6abeh943:31336334
cloudbuttont:31379625
3efgi100_wstrepl:31403338
use-responses-a...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes microsoft/vscode#286455

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI and others added 2 commits February 2, 2026 20:20
- Use workspace.fs.readFile instead of workspace.openTextDocument to read package.json
- This prevents textDocument/didOpen and didClose notifications to LSP servers
- Add mtime-based caching to avoid unnecessary file reads
- Create minimal TextDocument stub for parsing scripts without triggering LSP

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
- Avoid potential out-of-bounds array access when checking for line endings
- Use safer approach to detect Windows line endings (\r\n)

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix json documentSelector to prevent spamming LSP requests npm: Use fs API to read package.json, avoid LSP notification spam Feb 2, 2026
Copilot AI requested a review from connor4312 February 2, 2026 20:25
}

// Helper to create a minimal TextDocument-like object from file content
function createTextDocumentStub(uri: Uri, content: string): TextDocument {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't try to fake a text document. Instead, make readScripts's first document be an IPositionOffsetTransformer, which matches the minimal types of the document but otherwise has an implementation that can be used for strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 849be58. Created IPositionOffsetTransformer interface and refactored readScripts to accept it instead of TextDocument. The PositionOffsetTransformer class now properly implements this interface without faking VS Code APIs.

- Create IPositionOffsetTransformer interface in readScripts.ts
- Change readScripts to accept IPositionOffsetTransformer instead of TextDocument
- Replace createTextDocumentStub with PositionOffsetTransformer class
- TextDocument already implements IPositionOffsetTransformer, so existing callers work unchanged
- Cleaner architecture that doesn't fake VS Code APIs

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
}

// Cache for package.json file reading to avoid unnecessary reads
const packageJsonCache = new Map<string, IPackageJsonCache>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting there. Now a few more things:

  1. Use an LRU cache for package.json entries (set to 128 items)
  2. Persist the LRU cache in the workspace storage on the vscode.ExtensionContext (passed to the activate() method)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 2a2cf9a. Implemented PackageJsonLRUCache class with:

  1. 128 entry limit with LRU eviction
  2. Loads from workspace storage on initialization
  3. Persists to workspace storage asynchronously on changes
  4. Initialized in activate() with the ExtensionContext

- Implement PackageJsonLRUCache with 128 entry limit
- Load cache from workspace storage on activation
- Persist cache changes to workspace storage asynchronously
- Initialize cache in activate() with ExtensionContext
- LRU eviction: oldest accessed entries removed when cache exceeds limit

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
// Use workspace.fs.readFile instead of openTextDocument to avoid triggering
// textDocument/didOpen notifications to language servers
const content = await workspace.fs.readFile(packageJsonUri);
const text = Buffer.from(content).toString('utf8');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use TextDecoder here for portability @copilot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit fc51b35. Replaced Buffer.from(content).toString('utf8') with new TextDecoder('utf-8').decode(content) for better portability.

- Replace Buffer.from().toString('utf8') with TextDecoder
- Improves cross-platform compatibility

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants