diff --git a/.deslop.toml b/.deslop.toml index abf62af6..b99a984b 100644 --- a/.deslop.toml +++ b/.deslop.toml @@ -1,6 +1,6 @@ # agent-pmo:a72c926 [threshold] -max_duplication_percent = 6.48 +max_duplication_percent = 5.95 [defaults] exclude = [ diff --git a/.github/actions/build-platform/action.yml b/.github/actions/build-platform/action.yml index 42b007fc..2729efe7 100644 --- a/.github/actions/build-platform/action.yml +++ b/.github/actions/build-platform/action.yml @@ -98,6 +98,18 @@ runs: if: ${{ inputs.archive == 'true' }} shell: bash run: make _build-coverage-tools + - name: Stage the precompiled source-builder tests + if: ${{ inputs.archive == 'true' }} + shell: bash + run: make _build-lint-source-tests DOTNET_TESTS_PREBUILT=1 + - name: Cache the source builder and verified upstream archive + if: ${{ inputs.archive == 'true' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: lint-source-builder + retention-days: 1 + if-no-files-found: error + path: target/lint-source-builder - name: Cache coverage report tools if: ${{ inputs.archive == 'true' }} uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 @@ -130,11 +142,11 @@ runs: include-hidden-files: true path: target/netcoredbg/${{ inputs.vsix-platform }}/netcoredbg - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 if: ${{ inputs.archive == 'true' }} with: tool: cargo-llvm-cov - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 if: ${{ inputs.archive == 'true' }} with: tool: nextest diff --git a/.github/actions/vsix-shard/action.yml b/.github/actions/vsix-shard/action.yml index 1be9baac..42ccfdc7 100644 --- a/.github/actions/vsix-shard/action.yml +++ b/.github/actions/vsix-shard/action.yml @@ -119,6 +119,18 @@ runs: name: coverage-vsix-shard-${{ inputs.platform }}-${{ inputs.chunk }} retention-days: 1 path: target/coverage-vsix-shard-${{ inputs.platform }}-${{ inputs.chunk }}.lcov + - name: Collect server and compiler logs + # The host and both sidecars log beneath the system temp folder, where the + # extension host's logs never reach: a compiler request that stops + # answering is visible only there. [DIST-CI-VSIX-SHARDS] + if: always() + shell: bash + run: | + logs="$(node -e 'process.stdout.write(require("os").tmpdir())')/sharplsp-logs" + if [ -d "$logs" ]; then + mkdir -p "${{ inputs.user-data-dir }}/logs" + cp -R "$logs" "${{ inputs.user-data-dir }}/logs/sharplsp-logs" + fi - name: Upload extension-host diagnostics # A chunk that fails or HANGS is exactly what the split exists to isolate, # so keep the extension-host logs for triage. Keep them for a GREEN chunk diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 89a89c03..946aa147 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -124,6 +124,37 @@ updates: applies-to: security-updates patterns: ["*"] + # Local video tooling has native image dependencies too. + - package-ecosystem: npm + directory: "/src/website/tools/video" + target-branch: "dependabot-upgrades" + schedule: + interval: weekly + open-pull-requests-limit: 5 + labels: ["dependencies"] + groups: + video-tooling: + applies-to: version-updates + patterns: ["*"] + video-tooling-security: + applies-to: security-updates + patterns: ["*"] + + - package-ecosystem: gradle + directory: "/src/editors/rider" + target-branch: "dependabot-upgrades" + schedule: + interval: weekly + open-pull-requests-limit: 5 + labels: ["dependencies"] + groups: + rider-build: + applies-to: version-updates + patterns: ["*"] + rider-build-security: + applies-to: security-updates + patterns: ["*"] + # ── GitHub Actions: all workflows ─────────────────────────────────── # Version-only: GitHub publishes no Dependabot security advisories for # actions, so there is no security channel to group. diff --git a/.github/workflows/ci-audit.yml b/.github/workflows/ci-audit.yml index a7869f9f..350b1eda 100644 --- a/.github/workflows/ci-audit.yml +++ b/.github/workflows/ci-audit.yml @@ -38,7 +38,7 @@ jobs: - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: toolchain: stable - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: cargo-audit - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 diff --git a/.github/workflows/ci-test-dotnet.yml b/.github/workflows/ci-test-dotnet.yml index 88429af1..3efcc479 100644 --- a/.github/workflows/ci-test-dotnet.yml +++ b/.github/workflows/ci-test-dotnet.yml @@ -3,8 +3,9 @@ # PHASE 4 — TEST. The .NET sidecar leg. # # EVERY TEST RUNS EXACTLY ONCE. The Ubuntu job runs the whole sidecar solution. -# The Windows job runs ONLY the classes whose behaviour is platform-dependent - -# named-pipe connection setup and the real sidecar handshake over those pipes +# The Windows job runs ONLY tests whose behaviour is platform-dependent - +# named-pipe connection setup, the real sidecar handshake, NTFS-held compiler reads +# and project metadata sharing with editor saves # ([DIST-CI-WIN-TRANSPORT]). # # It used to run the entire SharpLsp.Sidecar.Common.Tests project instead, which @@ -24,7 +25,13 @@ jobs: test-dotnet: name: Sidecars runs-on: ${{ vars.UBUNTU_RUNNER || 'ubuntu-latest' }} - timeout-minutes: 12 + # The whole sidecar solution runs here (Common + C# + F# in parallel) under + # code coverage. The C# assembly alone is 833 real-SDK cases: ~4m15s on a + # 10-core host without coverage, so the 4-core runner with coverlet, a cold + # NuGet cache for the Razor/ASP.NET fixtures and the concurrent restore + # suites needs headroom above the old 12-minute cap (PR #494 hit it while + # main's smaller suite finished in 6m47s). + timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -91,7 +98,7 @@ jobs: - uses: ./.github/actions/dotnet-tests with: artifact: win-lsp-binaries-tests - - name: Test win32 named pipes and linked paths + - name: Test win32 named pipes, linked paths, compiler cancellation and editor saves # Only the platform-dependent classes. The full-solution `dotnet restore` # that used to precede this existed to cache the FCS nuspec for the # dependency-consistency tests, which are platform-agnostic and now run diff --git a/.github/workflows/ci-test-editors.yml b/.github/workflows/ci-test-editors.yml index 8b407d3d..33371815 100644 --- a/.github/workflows/ci-test-editors.yml +++ b/.github/workflows/ci-test-editors.yml @@ -39,10 +39,10 @@ jobs: with: toolchain: stable components: llvm-tools-preview - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: cargo-llvm-cov - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: nextest - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 diff --git a/.github/workflows/ci-test-rust.yml b/.github/workflows/ci-test-rust.yml index 60cff653..4e897b51 100644 --- a/.github/workflows/ci-test-rust.yml +++ b/.github/workflows/ci-test-rust.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2] + shard: [ 1, 2 ] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -39,10 +39,10 @@ jobs: with: toolchain: stable components: llvm-tools-preview - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: cargo-llvm-cov - - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: nextest - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 @@ -54,8 +54,7 @@ jobs: with: path: "~/.nuget/packages" key: >- - ${{ runner.os }}-nuget-${{ hashFiles('src/sidecars/**/*.csproj', - 'src/sidecars/Directory.Build.props') }} + ${{ runner.os }}-nuget-${{ hashFiles('src/sidecars/**/*.csproj', 'src/sidecars/Directory.Build.props') }} restore-keys: "${{ runner.os }}-nuget-" - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: @@ -87,6 +86,17 @@ jobs: run: tar -xzf target/rust-fixtures.tar.gz - name: Test Rust shard with coverage run: env VSIX_PREBUILT=1 CARGO_TESTS_PREBUILT=1 make _test-rust-shard SHARD=${{ matrix.shard }} SHARD_COUNT=2 + - name: Upload sidecar logs + # The e2e sidecars log beneath the temp folder. A failing shard keeps them, + # so a compiler failure only this runner shows is read, not guessed. + # [DIST-CI-RUST-SHARDS] + if: failure() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: rust-sidecar-logs-shard-${{ matrix.shard }} + retention-days: 3 + if-no-files-found: ignore + path: /tmp/sharplsp-logs - name: Upload shard coverage uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: diff --git a/.github/workflows/ci-test-tooling.yml b/.github/workflows/ci-test-tooling.yml index 108e5e40..7510a3d9 100644 --- a/.github/workflows/ci-test-tooling.yml +++ b/.github/workflows/ci-test-tooling.yml @@ -12,8 +12,8 @@ # refusals: a digest mismatch must fail, must unpack nothing, and must NOT fall # back to compiling from source. # -# Node's built-in runner, so this leg installs no dependencies and needs no -# artifact from PHASE 2. It reports in well under a minute. +# Node's built-in runner executes the source builder compiled in PHASE 2. +# This consumer installs its runtime but never compiles the builder or product. name: CI / Test / Tooling 'on': workflow_call: {} @@ -31,5 +31,14 @@ jobs: - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '20' + - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 + with: + dotnet-version: 10.0.303 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: lint-source-builder + path: target/lint-source-builder - name: Test the build tooling + env: + TOOLING_TESTS_PREBUILT: '1' run: make _test-tooling diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e39b5795..f748b28f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,9 +20,7 @@ on: inputs: gate: description: >- - When true, fail the job on any High/Critical finding so the calling - workflow (release.yml) blocks publishing. PR and weekly runs leave this - false and stay advisory (the PR check-failure threshold governs merges). + When true, fail the job on any High/Critical finding so the calling workflow (release.yml) blocks publishing. PR and weekly runs leave this false and stay advisory (the PR check-failure threshold governs merges). type: boolean default: false @@ -70,7 +68,7 @@ jobs: with: submodules: true - name: Initialize CodeQL - uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3 + uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -78,7 +76,7 @@ jobs: # PR, weekly, and gated release scans stay identical. [DIST-CI-SECURITY] config-file: ./.github/codeql/codeql-config.yml - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3 + uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 with: category: "/language:${{ matrix.language }}" # Drop the SARIF on disk so the gate step can read it. `upload` stays on diff --git a/AGENTS.md b/AGENTS.md index 48057ea3..aef60909 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,8 @@ F# ahead of C# on every new feature. F# never takes the back seat. # Workflow - **Sub agents are illegal.** Delegate by spawning `pi` sessions — you are responsible for their output -- **Plans (`docs/plans/`) are the backlog.** Work toward checking off their items; delete a plan once it is complete +- **Plans (`docs/plans/`) are the backlog and GitHub issues is for problem tracking.** Work toward checking off their items; delete a plan once it is complete +- **Closing a GitHub issue requires testing a released, published version** No closing issues as you fix the bug - ⛔️ **Never attribute a commit to yourself**: no `Co-Authored-By`, no agent name. *Never overridable* - **Coordinate with other agents** through Too Many Cooks or built-in messaging: lock the files you edit, and never edit a file another agent has locked - **Never kill VS Code processes** — desktop or browser. They belong to the user @@ -28,6 +29,7 @@ F# ahead of C# on every new feature. F# never takes the back seat. - **No SharpLsp code is "legacy".** Code that doesn't match the specs is deleted, never copied — move files, don't duplicate them - **All screens must be 100% reactive** to data changes. Manage state with Signals in the VSIX and other extensions - **Zero code duplication.** Run Deslop (see *Duplication*) before adding code and after editing +- **Minimize GitHub spend** Check that action usage isn't unnecessarily spending money - **Paths are handled in *one* place per tier** — [SHARPLSP-ARCHITECTURE-PATHS]. Every path-string operation (normalise, compare, key, case rule, resolve, split, extension test, URI conversion) lives only in that tier's path module: `SharpLsp.Sidecar.Common.NativePaths` (sidecars), `src/sharplsp/src/paths.rs` (host), @@ -64,9 +66,8 @@ F# ahead of C# on every new feature. F# never takes the back seat. - Run clippy and fmt routinely; fix violations immediately - All lints at highest strictness (Cargo.toml `[lints]`) -- **`unsafe` is forbidden** (`unsafe_code = "deny"`) -- **`unwrap()` is always a violation** — use `?` with proper error types -- **No `panic!`, `todo!`, `unimplemented!`** — return `Result` +- **`unsafe`, `unwrap()` is forbidden** (`unsafe_code = "deny"`) +- **No `panic!`, `todo!`, `unimplemented!`** — return `Result` and use `?` ## .NET Sidecar Quality Standards @@ -106,7 +107,7 @@ F# ahead of C# on every new feature. F# never takes the back seat. # Git -> ⛔️ **Don't make git changes like creating new branches** unless the user asks or a skill demands it. +> ⛔️ **Don't make git changes like creating new branches or worktrees** unless the user asks or a skill demands it. - Commit and push to submodules before commiting and pushing the feature branch - Commit and push after each major change - **One feature branch**, never `git worktree`. Work the open one; several open → merge them immediately diff --git a/Cargo.lock b/Cargo.lock index 599c7ad9..1c63b409 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.104" @@ -83,6 +89,22 @@ version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -152,6 +174,34 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -199,6 +249,12 @@ dependencies = [ "syn 3.0.9", ] +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + [[package]] name = "equivalent" version = "1.0.3" @@ -251,6 +307,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -266,6 +331,23 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + [[package]] name = "futures-task" version = "0.3.34" @@ -279,6 +361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "slab", @@ -323,6 +406,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -334,6 +430,20 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown 0.17.1", +] [[package]] name = "http" @@ -569,6 +679,41 @@ dependencies = [ "str_stack", ] +[[package]] +name = "inotify" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" +dependencies = [ + "bitflags 2.13.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "intrusive-collections" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4275b20e6057cd7733fd8df8a5a31701e4fe44497dad0f3fa0e1c4fb971506be" + +[[package]] +name = "inventory" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" +dependencies = [ + "rustversion", +] + [[package]] name = "ipnet" version = "2.12.2" @@ -592,6 +737,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.1" @@ -644,6 +809,7 @@ dependencies = [ "async-trait", "humantime", "inferno", + "num-traits", "serde", "serde_json", "sysinfo", @@ -692,6 +858,68 @@ dependencies = [ "serde_repr", ] +[[package]] +name = "lspkit" +version = "0.0.1" +dependencies = [ + "async-trait", + "futures-core", + "tokio-util", +] + +[[package]] +name = "lspkit-live" +version = "0.0.1" +dependencies = [ + "async-trait", + "futures-util", + "lspkit", + "notify", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lspkit-server" +version = "0.0.1" +dependencies = [ + "async-trait", + "futures-util", + "lsp-types", + "lspkit", + "lspkit-vfs", + "salsa", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lspkit-sidecar" +version = "0.0.1" +dependencies = [ + "rmp-serde", + "serde", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lspkit-vfs" +version = "0.0.1" +dependencies = [ + "dashmap", + "ropey", + "thiserror", +] + [[package]] name = "matchers" version = "0.2.0" @@ -714,10 +942,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.13.2", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.2", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -824,6 +1080,16 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.12" @@ -849,6 +1115,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + [[package]] name = "potential_utf" version = "0.1.6" @@ -985,6 +1257,26 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1103,6 +1395,16 @@ dependencies = [ "serde", ] +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -1169,6 +1471,58 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "salsa" +version = "0.28.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d2e6e19e39591cd99c01ac4141544b4d012cfd0831348629b8187fa9fe6d34" +dependencies = [ + "boxcar", + "crossbeam-queue", + "crossbeam-utils", + "hashbrown 0.17.1", + "hashlink", + "indexmap", + "intrusive-collections", + "inventory", + "parking_lot", + "portable-atomic", + "rayon", + "rustc-hash", + "salsa-macro-rules", + "salsa-macros", + "smallvec", + "thin-vec", + "tracing", + "typeid", +] + +[[package]] +name = "salsa-macro-rules" +version = "0.28.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf90a551c38f25ed8489111d3d5827aa18e1b85963f5e6a298505b1d1e2cb7e" + +[[package]] +name = "salsa-macros" +version = "0.28.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69dd77f86ec10e72e314f4ddf7c44c8d349e73cdff1aad524ba9668603fb0549" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1279,10 +1633,14 @@ dependencies = [ "async-trait", "crossbeam-channel", "dashmap", + "globset", "lsp-profiler", "lsp-profiler-dotnet", "lsp-server", "lsp-types", + "lspkit-live", + "lspkit-server", + "lspkit-sidecar", "percent-encoding", "reqwest", "rmp-serde", @@ -1371,6 +1729,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + [[package]] name = "str_stack" version = "0.1.1" @@ -1465,6 +1829,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thin-vec" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a4b9ba8738cb4a4f399d37e266becfd475e75eb73425b87a05a2f2039ba63e" + [[package]] name = "thiserror" version = "2.0.21" @@ -1542,9 +1912,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.53.3" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -1577,6 +1947,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "1.1.8+spec-1.1.0" @@ -1793,6 +2177,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "unicode-ident" version = "1.0.27" @@ -1844,6 +2234,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.2" @@ -1969,6 +2369,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2082,7 +2491,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -2100,14 +2518,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2125,48 +2560,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "1.0.4" diff --git a/Makefile b/Makefile index ef12acdc..ddaa7e23 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ KOVER_PERCENT = $(if $(COVERAGE_TOOLS_PREBUILT),$(DOTNET) target/coverage-tools/ _package-vsix-win32-x64 _package-vsix-win32-arm64 \ _print-publish-commands \ _stamp-version \ - _build-rust _build-dotnet _build-dotnet-tests _build-coverage-tools _build-vsix _build-zed _build-rider \ + _build-rust _build-dotnet _build-dotnet-tests _build-lint-source-tests _build-coverage-tools _build-vsix _build-zed _build-rider \ _stage-vsix-binary _stage-vsix-binary-only _stage-sidecars \ _test-rust _prepare-rust-tests _test-rust-shard \ _test-zed _test-rider \ @@ -283,6 +283,12 @@ _build-dotnet-tests: _lint-dotnet mkdir -p target tar -czf target/dotnet-tests.tar.gz src/sidecars/*/bin src/sidecars/*/obj +# [DIST-CI-ARTIFACT-TESTS] Stage the source builder already compiled by the +# normal FSharpLint project reference, plus the verified upstream input. +_build-lint-source-tests: $(if $(DOTNET_TESTS_PREBUILT),,_build-dotnet-tests) + $(DOTNET) publish tools/fsharp-lint-source/LintSourceBuild.fsproj --configuration $(DOTNET_CFG) --no-build --no-restore --no-self-contained -o target/lint-source-builder + cp third_party/FSharpLint.Core/obj/pinned-source/upstream.tar.gz target/lint-source-builder/upstream.tar.gz + _build-coverage-tools: $(DOTNET) publish tools/coverage/merge-cobertura.cs -c Release --no-self-contained -p:PublishAot=false -p:PublishTrimmed=false -o target/coverage-tools/merge-cobertura $(DOTNET) publish tools/coverage/kover-line-percent.cs -c Release --no-self-contained -p:PublishAot=false -p:PublishTrimmed=false -o target/coverage-tools/kover-line-percent @@ -459,9 +465,10 @@ ci: lint test build audit # touches node_modules. AUDIT_LEVEL ?= moderate -AUDIT_CARGO_LOCKS = Cargo.lock $(ZED_DIR)/Cargo.lock -AUDIT_NPM_DIRS = $(VSCODE_DIR) src/website +AUDIT_CARGO_LOCKS = Cargo.lock $(ZED_DIR)/Cargo.lock lspkit/Cargo.lock +AUDIT_NPM_DIRS = $(VSCODE_DIR) src/website src/website/tools/video lspkit/packages/lspkit-debug lspkit/packages/lsp-profiler-ui AUDIT_DOTNET_JSON = target/audit-dotnet.json +AUDIT_DOTNET_TARGETS = $(SIDECAR_SLN) lspkit/dotnet/LspProfiler.DotNet.Engine/LspProfiler.DotNet.Engine.csproj .PHONY: audit _audit-rust _audit-dotnet _audit-npm @@ -489,11 +496,13 @@ _audit-rust: done; exit $$status _audit-dotnet: - @echo "==> dotnet list package --vulnerable $(SIDECAR_SLN)" @mkdir -p $(dir $(AUDIT_DOTNET_JSON)) - $(DOTNET) restore $(SIDECAR_SLN) --verbosity quiet - $(DOTNET) list $(SIDECAR_SLN) package --vulnerable --include-transitive --format json > $(AUDIT_DOTNET_JSON) - @node tools/audit/dotnet-vulnerable.mjs $(AUDIT_DOTNET_JSON) $(AUDIT_LEVEL) + @status=0; for project in $(AUDIT_DOTNET_TARGETS); do \ + echo "==> dotnet list package --vulnerable $$project"; \ + $(DOTNET) restore "$$project" --verbosity quiet && \ + $(DOTNET) list "$$project" package --vulnerable --include-transitive --format json > $(AUDIT_DOTNET_JSON) && \ + node tools/audit/dotnet-vulnerable.mjs $(AUDIT_DOTNET_JSON) $(AUDIT_LEVEL) || status=1; \ + done; exit $$status _audit-npm: @status=0; for dir in $(AUDIT_NPM_DIRS); do \ @@ -768,15 +777,17 @@ _test-dotnet: $(if $(VSIX_PREBUILT),,_build-dotnet) _check_cov SharpLsp.Sidecar.Common sharplsp-sidecar-common # [DIST-CI-WIN-TRANSPORT] The win32 arm of the sidecar IPC transport. ONLY -# the classes whose behaviour is platform-dependent run here - named-pipe +# the tests whose behaviour is platform-dependent run here - named-pipe # connection setup, the real sidecar handshake over those pipes, Windows # symlink resolution, and process supervision (Job Object containment, parent -# watch: [SIDECAR-PROCESS-TREE], [SIDECAR-PROCESS-PARENT]). Every other class in +# watch: [SIDECAR-PROCESS-TREE], [SIDECAR-PROCESS-PARENT]), NTFS compiler reads, +# and project metadata sharing with editor saves. Every other class in # SharpLsp.Sidecar.Common.Tests is platform-agnostic and already ran ONCE in # _test-dotnet on Ubuntu; running the whole project again on Windows executed # ~12 files' worth of identical assertions a second time, which the pipeline's # run-every-test-exactly-once rule forbids ([DIST-CI-LAYOUT]). DOTNET_WIN_TRANSPORT_FILTER = FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.IpcConnectionTests|FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.SidecarHostEndToEndTests|FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.NativePathsSymlinkTests|FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.ProcessContainmentTests|FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.ParentProcessWatchdogTests|FullyQualifiedName~SharpLsp.Sidecar.Common.Tests.SidecarHostSupervisionTests +DOTNET_WIN_FSHARP_FILTER = FullyQualifiedName~SharpLsp.Sidecar.FSharp.Tests.FSharpScriptCancellationEndToEndTests|FullyQualifiedName~FSharpProjectCrackingTests.project metadata remains accurate across editor saves _test-dotnet-win-transport: @echo "==> Running win32 named-pipe transport tests..." @@ -784,18 +795,23 @@ _test-dotnet-win-transport: $(if $(DOTNET_TESTS_PREBUILT),--no-build --no-restore,) \ --filter "$(DOTNET_WIN_TRANSPORT_FILTER)" \ --blame-hang-timeout 2min --blame-hang-dump-type none + $(DOTNET) test $(SIDECAR_FS).Tests/SharpLsp.Sidecar.FSharp.Tests.fsproj --configuration $(DOTNET_CFG) \ + $(if $(DOTNET_TESTS_PREBUILT),--no-build --no-restore,) \ + --filter "$(DOTNET_WIN_FSHARP_FILTER)" \ + --blame-hang-timeout 2min --blame-hang-dump-type none # Tests for the repo's own build tooling, as opposed to the product. Today that # is how the netcoredbg debug adapter is obtained ([DIST-DEBUGGER-BUNDLE]) - the # supply-chain path that every VSIX and every release depends on - and the local # install loop ([DIST-VSIX-DEV-INSTALL]), whose whole contract is an ordering -# that nothing else in the suite exercises. Node's built-in runner, so this -# needs no dependency of its own. -_test-tooling: +# that nothing else in the suite exercises. Source preparation executes the +# compiled F# builder; CI supplies that artifact without compiling in this leg. +_test-tooling: $(if $(TOOLING_TESTS_PREBUILT),,_build-lint-source-tests) node --test tools/vsix/chunk-once.test.mjs @echo "==> Running repo tooling tests..." node --test tools/netcoredbg/custody.test.mjs tools/make/reinstall-loop.test.mjs tools/make/vsix-rebuild.test.mjs tools/vsix/rebuild-contract.test.mjs tools/vsix/debug-cleanup.test.mjs tools/audit/dotnet-vulnerable.test.mjs \ tools/rider/gradle.test.mjs tools/vsix/resolve-symlink-stubs.test.mjs tools/make/vsix-profiles.test.mjs + node --test tools/fsharp-lint-source/source-build.test.mjs tools/fsharp-lint-source/source-artifact.test.mjs _website-build: @echo "==> Building website..." @@ -837,10 +853,10 @@ _lint-zed: cargo clippy --manifest-path $(ZED_DIR)/Cargo.toml --all-targets -- -D warnings _lint-vsix: _check-vsix-chunks _check-sdk-pin _check-spec-citations - # The profiler contributions are generated from the shared UI library ([PROFILER-SHARED-LIBRARY-VSCODE]). - node tools/vsix/sync-profiler-contributions.mjs --check - # The tutorial's types are generated from its typeDiagram model ([TUTORIAL-MODEL]). - node tools/vsix/tutorial-model.mjs --check + # The profiler contributions ([PROFILER-SHARED-LIBRARY-VSCODE]) and the tutorial's types + # ([TUTORIAL-MODEL]) are generated; the test checks both committed models on this + # platform, here where the extension's dependencies are installed. + node --test tools/vsix/model-generators.test.mjs node --test tools/ci/security-gates.test.mjs tools/ci/changed-files.test.mjs tools/ci/vsix-coverage.test.mjs \ tools/ci/spec-citations.test.mjs tools/vsix/signals-bundle.test.mjs npm run lint:eslint --prefix $(VSCODE_DIR) diff --git a/coverage-thresholds.json b/coverage-thresholds.json index 95763c8e..da259a2b 100644 --- a/coverage-thresholds.json +++ b/coverage-thresholds.json @@ -4,7 +4,7 @@ "default_threshold": 95, "projects": { "sharplsp": { - "threshold": 95, + "threshold": 95.8, "language": "rust" }, "sharplsp-sidecar-common": { @@ -12,7 +12,7 @@ "language": "dotnet" }, "sharplsp-sidecar-csharp": { - "threshold": 95.72, + "threshold": 96.2, "language": "dotnet" }, "sharplsp-sidecar-fsharp": { diff --git a/docs/plans/DIAGNOSTICS-PLAN.md b/docs/plans/DIAGNOSTICS-PLAN.md index 77cbe8fe..d6a18038 100644 --- a/docs/plans/DIAGNOSTICS-PLAN.md +++ b/docs/plans/DIAGNOSTICS-PLAN.md @@ -1,286 +1,134 @@ -# DIAGNOSTICS-PLAN +# [DIAG-PLAN] Diagnostic Truth and Refresh Plan + +Release regression: [#441](https://github.com/Nimblesite/SharpLsp/issues/441). Analyzer false negatives: [#343](https://github.com/Nimblesite/SharpLsp/issues/343). + +F# lint failures and missing findings: [#469](https://github.com/Nimblesite/SharpLsp/issues/469). The compiler-compatible dependency is built from a hash-pinned upstream archive through a regular project reference; the build prepares guarded AST corrections for optional type names and stale rule state. + +Behavior is defined by [DIAGNOSTICS-SPEC.md](../specs/DIAGNOSTICS-SPEC.md). The published pull-diagnostics article remains a promise to implement. Compiler build oracles and real editor observations determine success. + +## [DIAG-PLAN-PROOF] Regression Evidence + +Current qualification (2026-10-26): the unchanged complete diagnostic editor group passes all 60 cases on Windows against the normal published binaries in the extension's actual bundle layout, with private NTFS fixtures and profile. The current Linux editor group also passes all 60 cases. The native cancellation/workspace groups pass 17/21 cases on Windows and 17/22 on Linux; the additional Linux case fills the real inotify queue. The complete eleven-file cancellation suite remains byte-for-byte identical to both failing runs that exposed lost unsaved buffers during workspace replacement (#491). Assertions and response budgets were retained. The source fix is pushed as `09acd28b`; the shared toolkit was pushed first as `78ba3821`. + +Full branch CI is not green. The normal release host, sidecars, packaged extension and Zed artifact built before WSL restarted during the Rider build. A persistent-storage recovery attempt exhausted the system drive; after cache preservation and cleanup, the Linux Node and .NET binaries still report filesystem I/O errors. All source, qualified binaries and test evidence are retained. Complete CI, PR monitoring and the requested release remain pending a working build environment. Fresh Rust, transitive NuGet and five npm audits report no findings; the feature branch's Sharp 0.35.5 update covers the three high default-branch alerts, and GitHub withdrew the remaining `sprintf-js` advisory on 2026-10-22. + +- [x] Prove real native queue loss cannot freeze closed compiler reports ([#490](https://github.com/Nimblesite/SharpLsp/issues/490)). The frozen mixed-language process test fills the actual Linux inotify queue while only its owned Rust host is stopped, then repairs both closed sources and independently builds both SDK projects successfully. The old host keeps returning unchanged error identities and fails; the fixed host retires those errors, detects a second lost-event regression with exact FS0001/CS0029 ownership and ranges, and retains the same host and compiler PIDs. The test source remains byte-for-byte unchanged. Full current-host editor/native qualification and complete branch CI remain required. + +- [x] Prove malformed lint JSON and invalid hints cannot hide fresh unsaved FS0001 errors. Two actual VSIX cases fail before the fix and pass with a visible SLSPF0104 warning, preserved compiler errors, automatic lint recovery and retirement of the failure warning after configuration repair. Two more unchanged regressions prove live complexity thresholds 2 → 10 → 2 and 10 → 2 → 10 in one compiler process against independent upstream findings and exact ranges. All ten lint regressions pass on Windows and Ubuntu. The combined diagnostic accuracy, analyzer, lint and policy selection passes 31 Windows cases with normal editor exit; the preceding Ubuntu controls pass 29 cases. The complete 1,434 managed cases and unchanged coverage gates pass on Ubuntu. Source preparation preserves all 21 checked build outputs on an unchanged incremental build. Both new VSIX suites are selected by the shared Ubuntu/Windows chunk manifest. Full branch CI remains required. -Implementation plan for [DIAGNOSTICS-SPEC](../specs/DIAGNOSTICS-SPEC.md). - -> **Architecture pivot (current).** The previous push-based, eager-solution-scan plan produced phantom CS0246/CS0234 errors during workspace load and could not be repaired by the verification pass. The plan now mirrors `Microsoft.CodeAnalysis.LanguageServer` (the engine behind C# Dev Kit): NuGet restore gate → workspace open → pull-driven diagnostics with `global_state_version`-keyed `resultId` → debounced `workspace/diagnostic/refresh`. See [DIAG-ARCHITECTURE-PULL-REFRESH](../specs/DIAGNOSTICS-SPEC.md). Phases 1–2 below are partially completed; the parts that still apply are kept, the parts that contradict the new architecture are marked obsolete with rationale. +- [x] Real VSIX fixtures independently fail the F# and C# compiler with FS0001 and CS0029 while an unrelated clean file resolves through its real sidecar. +- [x] Confirm 12 genuine VSIX failures for capability negotiation, closed-file workspace reports, automatic Problems publication, disk fix/regression cycles, unsaved dependency edits and live solution scope. +- [x] Confirm three native LSP process failures for modern capability negotiation, closed-file pull reports and missing F# closed-file legacy publications. +- [x] Prove F# script reference failures appear exactly once at the source, clear after repair and return on regression, with and without explicit workspace reload. Three real VSIX cases pass after genuine missing-error, cascading-error and stale-reference failures (#454); the published interactive settings asset, valid `fsi` binding and real SDK-built relative DLL are verified, with hover before the first diagnostic pull. All 463 existing F# engine cases pass against the integrated script and restore-policy source; the combined editor suite remains required. +- [x] Confirm three real VSIX analyzer failures after a genuine CA1822 compiler build oracle: live fixes, analyzer configuration and suppression changes. +- [x] Run the integrated diagnostic regression assertions against reviewed matching binaries. All 65 real VSIX cases pass together with the native source-watching fix and matching compiler/Common payloads; all 1,432 .NET cases (130 Common, 832 C#, 470 F#) pass the complete .NET CI target and unchanged coverage gates. The four ownership fixtures prove actual editor-model closure and root removal (#467); all original diagnostic assertions and response budgets remain intact. Complete final-branch CI is tracked separately below. +- [x] Preserve the observable legacy restore-pending notice without speculative package errors, then clear it before the first resolved package hover. The unchanged native regression, all 832 C# cases and all 45 diagnostic VSIX cases pass after the reactive IPC fix. +- [x] Prove solution replacement removes previous workspace errors without a window restart; two real solutions switch and switch back in one native session, retiring old reports explicitly. +- [x] Prove sidecar failures/restarts cannot memoize an empty false-clean result. The unchanged native #453 regression fails against the prior compiler session and passes after input restoration: the first successful post-crash pull retains an unsaved FS0039 and closed-file CS0029, the real compiler PID changes, and the host survives. All 36 existing manager regressions pass. +- [x] Re-run the real FluentValidation and Serilog diagnostic edit/clear cases that failed the full Windows baseline. Both unchanged real-repository cases pass together against the reviewed native host and matching compiler/Common payloads: the FluentValidation constraint error and Serilog type error appear and clear after repair (2/2, 44.654s). No assertions, response budgets or semantic rescue calls were changed. The entire unchanged GitReader F# .NET Framework suite also passes all 11 cases and teardown (583.307s, normal editor exit). A subsequent combined run passes all 65 diagnostic cases followed by all 11 GitReader cases in one editor session (76/76, 961.264s), including the hook that failed the older full run and normal editor exit; final integrated CI remains required. + +## [DIAG-PLAN-PULL] Protocol and Query Ownership -## Phase 1: Per-Document Diagnostics IPC (P0) — DONE +- [x] Prefer textDocument.diagnostic when the client also supports publishDiagnostics; advertise stable sharplsp identifier and inter-file/workspace support. +- [x] Suppress publications to negotiated pull clients, preserving one diagnostic identity and exact counts. +- [x] Enumerate actual Roslyn/FCS source metadata without eager compilation; route every source to its language engine. +- [x] Keep all computed diagnostic memoization in shared lspkit Salsa infrastructure. Extension/sidecar state retains no parallel diagnostic result cache. +- [x] Return full/unchanged reports with opaque snapshot IDs; stable pulls skip IPC and semantic recomputation. +- [x] Stream workspace partial results and explicit empty reports for excluded documents; live solution opt-out proves retirement of closed-file results. +- [x] Keep compiler/IPC failures as retriable request errors and invalidate failed query results for retry, including unavailable language engines. +- [x] Prove cancellation (-32800), supersession (-32802/retrigger), malformed parameters (-32602), foreground hover responsiveness and no post-terminal/stale-version partial reports through a real 128-source mixed solution. +- [x] Propagate per-request cancellation through IPC into Roslyn/FCS compiler work. Real blocked compiler requests fail before the fix and pass unchanged afterward: cancellation returns -32800 while the original read remains held, fresh F# and C# diagnostics stay current, and compiler PIDs remain stable. Final source also passes all three Linux cancellation sessions, both Windows F# script sessions and the actual editor analyzer cancellation/edit/close case (1/1, 15s). The complete matching managed run passes 1,434 cases and all unchanged coverage gates; complete final-branch CI remains required. +- [x] Implement [DIAG-ANALYSIS-CANCELLATION]: supersede older pulls on changed previousResultId and actual editor input, including repeated previousResultId. Genuine native failures prove blocked document, workspace and CodeLens work delays fresh errors; real Roslyn change/close cases prove the analyzer token remains uncancelled before the fix. The unchanged regressions pass afterward, including an unrelated-input control and both cancellation orders. The promoted source passes 21 native cases on Windows and Ubuntu, 20 shared CLI cases, strict lint and the actual editor edit/clear/close regression (1/1, 15s, natural editor exit). Shared cancellation keeps one request registry and no parallel version/result store; final full-branch CI remains required. +- [x] Prove solution selection supersedes blocked document diagnostics, workspace scans and CodeLens before loading the selected compiler context. Three unchanged native regressions fail against the prior host on both Windows and Ubuntu, then pass after shared-registry supersession: actual A/B/A solution switches preserve compiler PIDs, return exact SDK-confirmed errors and explicitly retire excluded reports while the original read remains held. Final source passes all 17 cancellation/input/selection cases on both platforms, all 15 real VSIX accuracy/cancellation cases, 20 shared CLI cases and strict Rust lint; complete final-branch CI remains required. +- [x] Remove the bulk workspace/diagnostics/all RPC and its eager compilation/verification implementation ([#484](https://github.com/Nimblesite/SharpLsp/issues/484)). The frozen real C# IPC test fails while the route succeeds, then passes unchanged after the route, DTO and 168 lines of eager implementation are deleted. F#'s matching six-case protocol controls and the C# regression pass on both platforms. All 833 C# managed cases pass on Windows, and the complete Linux suite passes all 1,445 cases with unchanged coverage gates. Existing Razor, framework, empty-workspace, cancellation and serialization assertions use the actual lazy document APIs. Final editor integration and complete branch CI remain required. -The sidecar's per-document diagnostics path is correct and survives the pivot. The Rust host's push wiring stays as the LSP-3.16 fallback for editors that don't negotiate pull. +## [DIAG-PLAN-INPUTS] Changes and Lifetime -### Rust LSP Host - -- [x] Create `diagnostics` module in Rust host (`src/sharplsp/src/diagnostics.rs`) -- [x] Map sidecar `DiagnosticResult` → LSP `Diagnostic` struct - - Map severity: `"Error"` → 1, `"Warning"` → 2, `"Info"` → 3, `"Hidden"` → 4 - - Map code, message, range - - Set `source: "sharplsp-csharp"` -- [x] On `textDocument/didOpen` / `textDocument/didChange` / `textDocument/didSave`: request diagnostics from sidecar (background task) — kept as the push fallback path -- [x] Send `textDocument/publishDiagnostics` notification with mapped results — push fallback only; pull is primary (Phase 5) -- [x] Clear diagnostics on `textDocument/didClose` -- [x] Version-gate the push pipeline ([DIAG-PUSH-GATE](../specs/DIAGNOSTICS-SPEC.md)): per-URI push generations, stale results never published, failed fetch for the newest generation retried until published or superseded. Hardening found while investigating GitHub #160 — that issue's actual root cause was path-qualified `_._` placeholder references poisoning FCS ([PKG-ASSETS-FS](../specs/PACKAGE-MAINTENANCE-SPEC.md)), fixed in `FSharpAssets.packageAssemblies` with regression test `path-qualified placeholder compile entries are never handed to FCS as references`. Rust test: `failed_fetch_after_revert_must_not_strand_stale_published_diagnostics`; F# sidecar guard: `diagnostics clear after an error edit is reverted` -- [x] Republish a *provisional* set until it settles ([DIAG-PUSH-GATE](../specs/DIAGNOSTICS-SPEC.md)): a file-based app's first set is a tier-2 placeholder carrying `SLSPC0002` plus phantom `CS0246`s, and the background restore that replaces it fires no client event, so the push loop keeps fetching until the code clears. Rust test: `provisional_filebased_set_must_be_republished_once_restore_settles` -- [x] Advertise pull diagnostics only to clients without push support ([DIAG-LSP-CAPABILITIES-EXCLUSIVE](../specs/DIAGNOSTICS-SPEC.md)) — a client offered both keeps one `DiagnosticCollection` per model and reports every diagnostic twice. Rust e2e test: `test_push_capable_client_is_not_offered_pull_diagnostics` -- [ ] Add debounce (150ms window) before sidecar push request — superseded by Phase 5's 2000ms refresh debounce; only relevant if push fallback is in use -- [x] Rust e2e tests: `test_diagnostics_cleared_on_close`, `test_request_works_after_diagnostic_notification` -- [x] VSCode extension tests: `diagnostics.test.ts` (6 tests — error detection, missing type, clean file, edit cycle, range check, close clears) -- [ ] Full-stack test: open a file with real errors → errors appear in Problems panel (requires sidecar) - -### C# Sidecar - -- [x] `workspace/diagnostics` handler exists (`CSharpSidecar.HandleDiagnosticsAsync`) — extended in Phase 5 to accept `previousResultId` and emit `Changed` flag -- [x] `WorkspaceManager.GetDiagnosticsAsync` extracts from `SemanticModel.GetDiagnostics()` -- [x] `DiagnosticResult` MessagePack type defined +- [x] Invalidate dependent reports before and after source mutations, including unsaved overlays and closed-file disk changes; input revisions cannot block behind compiler queries. +- [x] Follow selected solutions outside editor workspace folders using reactive input watchers, without retaining diagnostic data in the extension. +- [x] Follow compiler-owned source and project directories outside the selected solution root. Three unchanged real VSIX regressions fail before the fix and pass after it: an external F# owner activates an empty F# slice beside a C# solution, and both languages clear and restore unopened external errors after SDK-verified disk edits. Two unchanged native protocol/SDK cases also pass after genuine failures, with standard refreshes, changed Salsa result identities, no editor file-change notifications and no semantic rescue after edits (#462, #463). Existing selected-project errors remain visible. +- [x] Reload changed project/analyzer/editorconfig inputs; real CA1822/NoWarn transitions prove policy changes. +- [x] Refresh inherited lint configuration above the selected solution without recursively watching parent trees. The unchanged real Windows regression first retains FL0065 after an independent linter proves zero findings, then passes creation/change/deletion after shallow ancestor watches are added. It also passes in Linux VS Code. The combined cohorts pass 33 Windows and 40 Linux editor cases; final source passes all 237 host unit cases, all 28 shared live-watcher cases, and strict host/toolkit clippy. Scope assertions cover ancestor-to-recursive replacement and unrelated input exclusion. Full branch CI remains required. +- [x] Preserve generated compiler errors through analyzer opt-out and reference changes: a real SDK-built IIncrementalGenerator emits CS0029, reported once at the owning project URI with a zero-length 0:0 range. Adding, removing and restoring its Analyzer ProjectReference produces error/clean/error in Problems and workspace pulls while ordinary sources stay clean; all three F#/C#/generated project editor cases pass together. +- [x] Prove unsaved F# overlay replay through actual SDK source creation and metadata reload, retire a deleted C# source explicitly, and restore saved F# errors when its overlay closes. Real SDK build oracles and native reports pass (9321d87d). Native creation and deletion also pass without editor file notifications: a new implicit SDK source reports its real CS0103, removal retires its previous result and removes its metadata entry, and the unsaved F# overlay stays current (#466). All 18 native diagnostic protocol cases pass, including exact workspace-completion counts after generated SDK sources are excluded from membership reloads. +- [x] Deliver typed background compiler notifications while IPC is idle; shared transport belongs to lspkit and refresh is explicitly negotiated for older callers. +- [x] Emit actual compiler workspace completion after successful loading in both engines, gated by negotiated IPC notifications. The unchanged native mixed-solution regression fails with zero notifications before the fix and passes through initial load, another solution and switching back with two completion notifications per selection. All 832 C#, 463 F# and 130 Common engine tests pass without skips (#460). +- [x] Preserve a visible terminal error for truncated native IPC prefixes/payloads, including after valid responses; zero-byte EOF remains clean (lspkit #21, native RED/GREEN regression). +- [x] Prove C# background SDK restore completion emits a standard refresh through idle typed IPC, independently of filesystem or semantic-request rescue (81da7b24). +- [x] Prove completed owning-project adoption refreshes closed-file diagnostics, including the first F# owner of a C#-only solution (#462, #463). +- [x] Verify the F# owning-file restore path recreates a fixture-owned missing package cache and updates closed-file Problems automatically. A real Newtonsoft API builds successfully before cache removal; an SDK build without restore then fails for that package. Opening the unselected external F# owner restores its cache and reveals the real closed FS0001, followed by SDK-verified repair and regression. All four ownership VSIX cases pass together without a manual pull or hover; direct idle-IPC notification proof remains separate. +- [x] Prove F# restore completion refreshes diagnostics independently of filesystem or semantic-request rescue (e40d30a6): the actual compiler IPC has no queued notification at the didChange acknowledgment, then emits a typed diagnostics refresh while idle before any further request. The real package cache is restored and the closed FS0001 retains its exact source and range; all 470 F# tests pass with the CI runsettings. +- [ ] Coalesce refreshes and preserve generation ordering across edits, workspace replacement and process recovery. +- [ ] Invalidate only the required dependency graph when a compiler input changes; verify unrelated result identities remain stable where safe. -## Phase 2: Solution-Wide Eager Scan (P0) — REMOVED - -> ⚠️ **Removed by architecture pivot.** This phase implemented the eager `workspace/diagnostics/all` bulk RPC and the post-load solution scan. The eager scan iterates `Solution.Projects` and calls `GetCompilationAsync()` on each, which produces phantom CS0246/CS0234 because consumer projects are compiled before their dependencies are cached as `CompilationReference`s. The replacement is Phase 5 (pull-driven workspace diagnostics) plus Phase 5.6 (NuGet restore gate). See [DIAG-ARCHITECTURE-EAGER-SCAN](../specs/DIAGNOSTICS-SPEC.md). - -### Rust LSP Host - -- [x] Read `diagnostics.solution_wide_analysis` from config (default: `true`) — kept; now controls whether the server answers `workspace/diagnostic` pulls -- [x] Read `diagnostics.project_filter` from config (default: empty = all projects) — kept; now restricts `workspace/diagnostic` results -- [x] On solution load: request solution-wide diagnostics — ⚠️ **OBSOLETE.** Removed. The host no longer triggers a scan on load; the editor pulls when it wants data. `request_solution_in_background` in `src/sharplsp/src/diagnostics.rs` will be deleted in Phase 5. -- [x] Stream diagnostics incrementally (batch by file) to avoid blocking — ⚠️ **OBSOLETE.** Replaced by `workspace/diagnostic` partial-result streaming in Phase 5. -- [ ] On file change: re-request diagnostics for changed file + dependents — ⚠️ **OBSOLETE.** Replaced by `diagnostics/refresh` IPC + debounced `workspace/diagnostic/refresh` in Phase 5. -- [x] Advertise `workspaceDiagnostics: true` in server capabilities — kept; required for pull workspace diagnostics +## [DIAG-PLAN-ENGINES] Compiler and Analyzer Truth -### C# Sidecar +- [x] Prove and fix F# compile-assembly resolution across every SDK `packageFolders` root ([#481](https://github.com/Nimblesite/SharpLsp/issues/481)). A real SDK-built local package lives only in a fixture-owned fallback cache: the unchanged final VSIX regression misses closed-file FS0193 before the fix and passes on Windows and Ubuntu after it, including compiler-verified repair and regression without query rescue. The original independent-package Windows failure also passes; all seven ownership cases pass with cache preconditions independently verified. A supplemental real-SDK managed regression verifies primary-root metadata, used-assembly identity and fallback → primary → fallback precedence without another restore. Final managed coverage and complete branch CI remain required; preserve the earlier disk-exhaustion run as invalid infrastructure evidence. +- [x] Complete integrated verification of imported restore inputs ([#482](https://github.com/Nimblesite/SharpLsp/issues/482)). The frozen Windows VSIX tests first omit a real FS0193 and a second CS0029 after only imported package versions change. The final unchanged editor cases and eight fallback/project/generated/restore-policy controls pass together on Windows and Ubuntu, including automatic live repair while unrelated errors remain. Three real-SDK direct/nested/inherited import cases fail before the fix and pass afterward. A supplemental cyclic-reference test captures a stack overflow in the first guard and passes after bounded project-graph inspection. Source53 passes all 1,443 managed cases and unchanged coverage gates on both platforms; broader restore and complete branch CI requirements remain open. +- [x] Complete integrated verification that restore cannot hide invalid imports ([#483](https://github.com/Nimblesite/SharpLsp/issues/483)). Missing and malformed imports each produce a genuine false-success failure in the new SDK tests. An independent SDK oracle proves restore exits zero while normal build/evaluation returns MSB4019. All six focused import/cycle/failure/recovery contracts pass after normal SDK evaluation is required. The final source53 candidate passes all 1,443 managed cases, unchanged coverage gates and ten editor controls per platform. Preserve the preceding Windows copy/out-of-memory failures and Linux coverage failure; they do not qualify as passing runs. Subsequent bulk-route changes require their own verification. +- [x] Complete integrated verification of timestamp-preserving package changes ([#485](https://github.com/Nimblesite/SharpLsp/issues/485)). Four frozen real Windows editor cases first omit package-signature/removal errors, then pass after comparing declared references with actual SDK dependency snapshots, including live repair and preserved unrelated errors. Five real SDK controls preserve exact project timestamps through removal, exact ranges, equivalent nested versions and metadata changes. The metadata and include-assets repairs fail before cached metadata is rejected, then all five pass unchanged. All 1,450 managed cases and unchanged coverage gates pass on both platforms. The fourteen-case editor selection passes on both; after required helper reuse, the final four cases still fail against the old runtime and all seven affected cases pass on both platforms with frozen assertions and normal editor exit. Broader restore inputs and complete branch CI remain open. +- [ ] Complete integrated verification of target-framework snapshot freshness ([#486](https://github.com/Nimblesite/SharpLsp/issues/486)). Three real SDK failures prove timestamp-preserving target switches/additions/removals are incorrectly current; the unchanged-framework-set reorder control passes. All fifteen package/import/framework contracts pass after comparing the SDK's actual framework snapshot. The real C# editor test observes old `net10.0` assets after the target becomes `netstandard2.1`; both focused editor cases now pass with the matching fixes, while final broad editor/managed/coverage verification remains required. +- [ ] Complete integrated verification of restored single-target F# SDK contexts ([#487](https://github.com/Nimblesite/SharpLsp/issues/487)). The Windows editor misses an independently SDK-proven FS0039, and two real managed cases omit globbed sources before the fix. All nine design-time cases pass after reusing the existing SDK pipeline, including exact compiler errors, target defines and compile ownership. The two frozen framework editor cases pass on Windows; broader package/analyzer/ownership/framework controls and full matching managed/coverage runs remain required. +- [ ] Complete integrated verification that unavailable SDK contexts never present fallback-only analysis as compiler truth ([#488](https://github.com/Nimblesite/SharpLsp/issues/488)). A real SDK oracle and fresh sidecar marker precede the frozen VSIX failure: diagnostic requests unexpectedly succeed. Rejecting unavailable F# inputs exposes a second failure: the host discards the SDK cause and endlessly requests retries. Preserving the typed backend failure makes the unchanged Linux failure/recovery test pass, including exact SDK cause, true compiler errors and fresh result identity. Independent-owner/framework-switch controls and final current Windows/Linux managed/editor/protocol qualification remain required; preserve supported unrestored-owner behavior. +- [x] Prove and fix the Windows project-file sharing contract behind repeated F# edit failures ([#489](https://github.com/Nimblesite/SharpLsp/issues/489)). A held real editor-write handle causes the unchanged regression to fail inside XDocument.Load. One shared Result-returning XML reader permits writes and atomic replacement; three actual saves retain all eighteen metadata assertions. The affected editor case and all sixty Windows diagnostic cases pass with the normal published payload. The original intermittent owner's PID was not captured; retain the deterministic sharing proof and the original EBUSY failures. +- [x] Prove and fix wildcard `.fsproj` analysis ([#455](https://github.com/Nimblesite/SharpLsp/issues/455)). A project declaring `` loaded with the XML reader's literal `*.fs` path, so glob-only files were neither owned nor checked and no FS0001 surfaced. Compile items now come from `dotnet msbuild -getItem:Compile` in project order, cached per project and cleared on reload; Exclude, Remove, Condition and `Directory.Build.props` imports are all MSBuild's. The two new wildcard tests failed with the literal `*.fs` list before the fix and pass with the exact ordered list and a real FS0001/hover after it; all 483 F# sidecar tests pass. +- [x] Complete all seventeen native compiler-cancellation contracts on Windows. Tracing proves duplicate automatic/manual initial loads supersede the fixture's workspace query before compiler entry. Removing only the redundant initial load preserves every barrier and assertion. The final host and current compiler pair pass all seventeen cases with explicit binary hashes. The earlier twenty-one workspace protocol cases pass separately; final complete branch CI remains required. +- [x] F# first: include every loaded project's compiled source and preserve compiler parse/type errors. +- [x] Share the C# analyzer pipeline with the existing resolver; honor the evaluated SDK/NuGet analyzer references and effective editorconfig options without injecting analyzers the project disables. IDE code-style analyzers run in the editor whatever `EnforceCodeStyleInBuild` says, reporting only rules promoted to a warning or error ([#343](https://github.com/Nimblesite/SharpLsp/issues/343)). +- [x] Prove unopened default Razor SDK components participate in workspace pulls, map CS0029 to the component source and clear after a compiler-verified disk repair. +- [x] Respect NoWarn, source suppressions, analyzer opt-out and effective severity without suppressing compiler errors. +- [x] Prove actual generated-source and global/project diagnostic errors remain visible at their owning project with a zero-length 0:0 range: real FS0084, CS5001 and generated CS0029 editor cases pass in the verified 61-case combined suite, with exact counts and SDK build oracles (8b325497, 57e4a445). +- [ ] Apply the NuGet restore gate before compiler workspace creation; surface restore failures honestly. +- [ ] Preserve F# FSharpLint/FSharp.Analyzers.SDK and SharpLsp static analyzer behavior. +- [x] Supply codeDescription URLs ([#343](https://github.com/Nimblesite/SharpLsp/issues/343)): each C# finding's descriptor `HelpLinkUri` and FSharpLint's per-rule page; F# compiler codes carry none. +- [ ] Supply unnecessary tags where specified. -- [x] Add `workspace/diagnostics/all` IPC handler — ⚠️ **TO BE DELETED in Phase 5.** Caused phantom CS0246s on workspace load. -- [x] Add `GetAllDiagnosticsAsync(string[]? projectFilter, CancellationToken)` to `WorkspaceManager` — ⚠️ **TO BE DELETED in Phase 5.** Same reason. - - The topological-order patch landed on `OrderProjectsByDependencies` (`WorkspaceManager.cs`) was a partial mitigation. It does not survive: source generators and NuGet still race even with topological iteration. The function is removed entirely. -- [ ] Stream results — send partial diagnostics as each project completes — **moved to Phase 5** as part of `workspace/diagnostic` partial-result streaming. - -### Configuration - -- [x] `DiagnosticsConfig.solution_wide_analysis` field exists (default: `true`) -- [x] `DiagnosticsConfig.analyzers_enabled` field exists (default: `true`) -- [x] Add `project_filter: Vec` to `DiagnosticsConfig` -- [ ] Add `min_severity: String` to `DiagnosticsConfig` (default: `"hint"`) -- [ ] Add `max_per_file: u32` to `DiagnosticsConfig` (default: `0` = unlimited) -- [ ] Hot-reload config via `workspace/didChangeConfiguration` +## [DIAG-PLAN-CONFIG] Configuration -## Phase 3: F# Diagnostics (P0 — parallel with C#) +- [x] Default solution_wide_analysis to true; live opt-out clears closed-file results while preserving open-file errors. +- [x] Match project names, native paths and selected-solution-relative globs for F# and C# workspace pulls; exclusions retire prior reports and direct document requests remain unfiltered. Four live filter transitions per engine and all 14 accuracy cases pass (21253bc3, #451). +- [x] Prove initial analyzer opt-out and live false/true/false transitions through real compiler sessions, preserving compiler errors (83836651). +- [x] Replay analyzer configuration after process recovery (#453): real F# crash restores unsaved FS0039 and closed C# CS0029; real Roslyn crash preserves SDK-confirmed CA1822 opt-out before the first successful pull, then live opt-in restores the analyzer error and opt-out clears it. Both native sessions pass with the same host and new owned compiler PIDs (0c8953e1). +- [x] Validate all four min_severity values and apply max_per_file after filtering, with zero unlimited. Four real F#/C# editor cases fail before the fix and pass afterward; all 21 accuracy/analyzer/policy editor cases and all 236 existing Rust unit cases pass (f4c5ef77, #452). +- [x] Preserve raw restore-pending state before publication filtering. With min_severity=error, a real legacy client receives the final CS0029 automatically after SDK restore, without a hover/pull rescue or visible informational notice (ee42c874). +- [x] Implement and validate refresh_debounce_ms: actual F# source bursts coalesce into one standard refresh at live 100 → 1000 → 100 millisecond settings; the 2000 millisecond default is exposed, and negative/fractional/string/unknown controls reject without replacing the default or last valid policy. Two native sessions pass after genuine pre-fix rejection (bb2a3d92, #457). +- [x] Implement and validate auto_restore_on_open in both engines: four real F#/C# editor cases compare default restoration, explicit opt-out/live opt-in, and missing fixture-owned package caches against actual SDK effects. All four pass together after genuine pre-fix failures; 130 Common and 236 Rust cases also pass (33cfa885, #459). The broader restore gate and props-trigger guarantees remain tracked separately. -- [ ] `FSharpCheckFileResults.Diagnostics` integration in F# sidecar -- [ ] Map F# diagnostic severity to LSP -- [ ] Solution-wide F# analysis via `FSharpChecker` -- [ ] FSharpLint integration for code style -- [ ] FSharp.Analyzers.SDK plugin loading +## [DIAG-PLAN-VERIFY] Integration and Delivery -## Phase 4: Analyzer Diagnostics (P0) +Current workspace-replay candidate (2026-10-25): all seventeen cancellation cases, all twenty-two workspace protocol cases and all sixty actual VS Code diagnostic cases pass against the same normally built immutable Linux host and compiler pair; strict all-target development Clippy and formatting pass. The editor exits naturally, and runtime hashes match before and after qualification. The formerly failing switchback test, shared session and selection helper are byte-identical to both failed full cohorts. Its twenty-second obsolete-I/O guard remains unchanged. The captured race delivered unsaved text just before workspace replacement discarded it; the canonical FIFO now replays selected-graph overlays before releasing the transport, without adopting excluded owners ([#491](https://github.com/Nimblesite/SharpLsp/issues/491)). The preceding native-rescan host also passed all sixty editor and twenty-two workspace cases; the overflow test remains causally RED/GREEN, including its protocol-correct bounded retry. The current source mirror measures 5.9373% duplication against the lowered 5.95% ceiling. Shared toolkit CI exposed and fixed an independent target-attachment race with unchanged actual-syscall and F# process-churn regressions ([lspkit #28](https://github.com/Nimblesite/lspkit/issues/28)). Preserve all earlier failures. Complete branch CI and final Windows host qualification remain required. -- [ ] Enable Roslyn `DiagnosticAnalyzer` framework in sidecar - - `CompilationWithAnalyzers.GetAnalyzerDiagnosticsAsync()` -- [ ] Load analyzers from project NuGet references -- [ ] Load .editorconfig severity overrides -- [ ] IDE0005 (unused usings) detection -- [ ] Map analyzer diagnostic codes to LSP `Diagnostic.code` + `codeDescription` URL +Current source-builder proof (2026-10-25): four real public CLI cases pass on Windows and Linux against the same normally compiled portable builder and genuine pinned archive. They exercise argument/path/hash rejection before extraction, both verified AST patterns and rule-state correction, corruption of each prepared source and every fingerprint field, invalid fingerprint JSON, hash revalidation despite matching output metadata, and unchanged bytes/timestamps on a verified no-op. Two new precompiled-handoff assertions fail before wiring and pass afterward without changes. The full tooling target passes on CI's Node 20.20.2, including all six new cases; the machine's Node 26 warning failure remains preserved. Phase 2 stages the normal project-reference builder and archive; phase 4 downloads and executes them without compilation. Full remote artifact integration and final branch CI remain required. -SharpLsp-owned static analyzers are tracked in -[DIAGNOSTICS-STATIC-ANALYZERS-PLAN.md](DIAGNOSTICS-STATIC-ANALYZERS-PLAN.md). -The first rules are monorepo-gated, solution-wide unused-public-code analyzers -for both C# and F#. +Current project-save candidate (2026-10-25): all 1,465 managed cases pass without skips on both platforms (151 Common, 833 C#, 481 F# each). Unchanged coverage gates pass: Windows 96.3675%/96.3850%/94.3274%; Linux 95.9402%/96.3850%/94.2464%. Fifty-one reviewed build inputs match both replicas, and all twelve built/tested/published assembly triples match exactly. The shared XML reader has 100% line coverage. All sixty Windows diagnostic editor cases pass with the normal published payload; all seventeen current native cancellation cases and the exact 38-case prebuilt Windows CI selection also pass. The frozen sharing regression retains all eighteen assertions and its causal Windows failure. A private mount namespace gives the normal Linux test user writable temporary sockets without changing the global mount; the complete managed suite then passes. Preserve the preceding read-only socket failures, collector warnings, rejected compiler-pair selection and failed copy into the old read-only editor tree. Final Linux host/editor/native qualification and complete branch CI remain open. -## Phase 5: Pull Diagnostics + Refresh Cycle (P0 — primary path) - -This is the target diagnostic pipeline. Until the `resultId` / refresh work below lands, push (Phase 1 wiring) is what actually converges, so the two models are advertised **exclusively**: `diagnosticProvider` is offered only to clients that declare no `textDocument.publishDiagnostics` support. A client given both builds a `DiagnosticCollection` per model and reports every diagnostic twice ([DIAG-LSP-CAPABILITIES-EXCLUSIVE](../specs/DIAGNOSTICS-SPEC.md)). Implements [DIAG-ARCHITECTURE-PULL-REFRESH](../specs/DIAGNOSTICS-SPEC.md), [DIAG-LSP-PULL](../specs/DIAGNOSTICS-SPEC.md), and [DIAG-LSP-REFRESH](../specs/DIAGNOSTICS-SPEC.md). - -### Rust LSP Host - -- [x] Implement `textDocument/diagnostic` request handler (LSP 3.17 pull model) — `pull_diagnostics.rs` -- [x] Implement `workspace/diagnostic` request handler — `pull_diagnostics.rs` -- [ ] Forward `previousResultId` to sidecar in IPC payload -- [ ] Return `RelatedFullDocumentDiagnosticReport` when changed, `RelatedUnchangedDocumentDiagnosticReport` (`{ kind: "unchanged" }`) when sidecar reports `Changed = false` -- [ ] Construct `resultId = "p:{project_version}|d:{doc_version}|g:{global_state_version}"` from sidecar response fields -- [ ] `workspace/diagnostic` partial-result streaming via `WorkDoneProgress` partialResultToken — emit one `WorkspaceDocumentDiagnosticReport` per project as it completes -- [ ] Subscribe to the [DIAG-IPC-REFRESH](../specs/DIAGNOSTICS-SPEC.md) sidecar notification -- [ ] Implement debounced refresh queue: `tokio::sync::Notify` + 2000ms `tokio::time::sleep` collapse, matches `Microsoft.CodeAnalysis.LanguageServer`'s `AsyncBatchingWorkQueue` -- [ ] Send LSP `workspace/diagnostic/refresh` notification when the debounce drains -- [ ] **Delete** `request_solution_in_background` from `src/sharplsp/src/diagnostics.rs` (the eager-scan trigger) -- [ ] **Delete** `verify_error_files`, `sync_text_to_sidecar` from `src/sharplsp/src/diagnostics.rs` (verification pass — see Phase 5.5) -- [ ] Cancel in-flight per-document IPC pulls when editor sends a fresh pull for the same document -- [ ] Server capability: add `diagnosticProvider.identifier = "sharplsp"` so editors distinguish SharpLsp's diagnostics - -### C# Sidecar - -- [ ] Extend `workspace/diagnostics` IPC payload to `DiagnosticsRequest { FilePath, PreviousResultId? }` -- [ ] Extend response to include `ResultId` and `Changed` flag; return empty items when `Changed = false` -- [ ] Add `workspace/diagnostics/pull` IPC handler — streams per-document `WorkspaceDocumentDiagnosticReport`s via partial responses (replaces the deleted `workspace/diagnostics/all`) -- [ ] Add `WorkspaceDiagnosticsCache` keyed by `(DocumentId, ProjectVersion, DocumentVersion, GlobalStateVersion)` — mirrors Roslyn's `VersionedPullCache` -- [ ] Add `IDiagnosticsRefresher` equivalent: `GlobalStateVersion: ulong` field + `RefreshRequested` event -- [ ] Subscribe to `_workspace.RegisterWorkspaceChangedHandler` — on `ProjectAdded`, `ProjectReloaded`, `ProjectRemoved`, `SolutionAdded`, `SolutionChanged`, `SolutionReloaded`, `DocumentChanged`, increment `GlobalStateVersion` and fire `RefreshRequested` -- [ ] Add `diagnostics/refresh` outbound IPC notification (sidecar → host) carrying `{ GlobalStateVersion, AffectedProjectIds[] }` -- [ ] Add `workspace/initializationComplete` outbound IPC notification fired once after restore + `OpenSolutionAsync` complete -- [ ] **Delete** `GetAllDiagnosticsAsync` from `WorkspaceManager` -- [ ] **Delete** `OrderProjectsByDependencies` (added as a partial mitigation; obsolete now that the eager scan is gone) -- [ ] **Delete** `workspace/diagnostics/all` handler from `CSharpSidecar` -- [ ] Skip auto-generated syntax trees (`syntaxTree.IsAutoGenerated`, `GeneratedKind.MarkedGenerated`) — matches OmniSharp's behavior; prevents diagnostics on `obj/Debug/.../*.AssemblyInfo.cs` etc. - -### Tests - -- [ ] Full-stack: pull diagnostics during workspace load returns "loading" or partial results, never CS0246 for resolvable types -- [ ] Full-stack: `previousResultId` round-trip — repeat pull returns `{ kind: "unchanged" }` -- [ ] Full-stack: workspace mutation triggers `workspace/diagnostic/refresh` within 2.5s (debounce + slack) -- [ ] Full-stack: editing a file invalidates only its project's `resultId`s; unrelated files' pulls return unchanged -- [ ] Full-stack: 8-project diamond graph (the current `test_full_stack_cross_project_references_no_false_positives` fixture) — pull never returns phantom CS0246 - -## Phase 5.5: Diagnostic Verification (P0) — REMOVED - -> ⚠️ **Removed by architecture pivot.** The verification pass re-sent `textDocument/didChange` with the same disk text and re-fetched diagnostics, expecting Roslyn to clear false positives. It does not work — `Solution.WithDocumentText` does not re-resolve metadata references or re-run source generators, so the same phantom errors come back. The pull + refresh model in Phase 5 removes the pass's reason to exist: SharpLsp no longer asserts diagnostics until the editor pulls. See [DIAG-ARCHITECTURE-EAGER-SCAN](../specs/DIAGNOSTICS-SPEC.md). - -Original tasks (kept here for traceability — every item is undone in Phase 5): - -- [x] `didOpen` syncs document text to sidecar via `notify_did_change` — kept (it's a correct text-sync operation, just no longer part of a verification pipeline) -- [x] After solution-wide scan, verification pass re-checks files with errors/warnings — ⚠️ **DELETED** (`verify_error_files` removed in Phase 5) -- [x] Verification publishes corrected diagnostics per-file (clears false positives) — ⚠️ **DELETED** (no push during load) -- [x] Verification yields between files to avoid starving other sidecar requests — ⚠️ **DELETED** -- [x] Test: `test_full_stack_diagnostics_cleared_after_error_fixed` (deterministic) — kept; now exercises pull semantics (close → fixed file → pull returns clean) -- [ ] Exponential backoff for persistent errors (1s, 2s, 4s, max 30s) — ⚠️ **NOT NEEDED.** Pulls run only when the editor asks; there is no retry loop to back off. -- [ ] Periodic re-verification for long-running sessions — ⚠️ **NOT NEEDED.** Workspace events drive `diagnostics/refresh`; the editor re-pulls on its own schedule. - -## Phase 5.6: NuGet Restore Gate (P0) - -The single biggest source of phantom CS0246 is unresolved NuGet `` items at workspace open. Mirrors `Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectDependencyHelper`. Implements [DIAG-RESTORE](../specs/DIAGNOSTICS-SPEC.md). - -### C# Sidecar - -- [ ] Add `Workspace/ProjectRestorer.cs`: inspect each project's `obj/project.assets.json`, compare timestamp + `PackageReference` set against `.csproj` -- [ ] Add `Workspace/DotnetCliHelper.cs`: shell `dotnet restore --verbosity quiet`, capture stdout/stderr, surface failures as a structured error -- [ ] In `WorkspaceManager.OpenCoreAsync`: call `ProjectRestorer.RestoreIfStaleAsync(target, ct)` BEFORE `MSBuildWorkspace.Create(...)` -- [ ] Honor `diagnostics.auto_restore_on_open` config flag (default true) — when false, skip restore and surface a server-side warning notification -- [ ] After restore completes successfully, increment `GlobalStateVersion` and fire `diagnostics/refresh` -- [ ] After restore + `OpenSolutionAsync` complete, fire `workspace/initializationComplete` outbound IPC notification - -### Rust LSP Host - -- [ ] On workspace open: create LSP `$/progress` work-done token (`workspace/sharplsp-load`) -- [ ] Forward sidecar restore progress (start, per-project status, end) via `$/progress` notifications -- [ ] On `workspace/initializationComplete` from sidecar: send LSP `workspace/projectInitializationComplete` (custom notification, matches Roslyn LSP contract); end the work-done progress -- [ ] Add `diagnostics.auto_restore_on_open` to `DiagnosticsConfig` (default true) - -### Tests - -- [ ] Full-stack: workspace with stale `assets.json` (delete it) → server triggers restore on open, no CS0246 for NuGet types after init complete -- [ ] Full-stack: workspace with `diagnostics.auto_restore_on_open = false` → server publishes a warning, leaves diagnostics in incomplete state, does not lie about success -- [ ] Full-stack: `workspace/projectInitializationComplete` notification fires exactly once per workspace open - -## Phase 6: Background Analysis Optimization (P1) - -- [ ] Cache `Project.GetCompilationAsync` results across pulls within the same `Solution` snapshot (Roslyn does this internally — verify our IPC layer doesn't defeat it) -- [ ] Cancel stale per-document IPC pulls on new pulls for the same document -- [ ] Memory budget: cap `WorkspaceDiagnosticsCache` size; LRU-evict by `GlobalStateVersion` age -- [ ] Progress reporting via `$/progress` for `workspace/diagnostic` pulls (in addition to load progress from Phase 5.6) - ---- - -## TODO - -### Done -- [x] **Rust host**: Create `diagnostics` module (`src/sharplsp/src/diagnostics.rs`) -- [x] **Rust host**: Map sidecar `DiagnosticResult` → LSP `Diagnostic` -- [x] **Rust host**: On `didOpen`/`didChange`/`didSave` → request diagnostics from sidecar (push fallback path) -- [x] **Rust host**: Send `textDocument/publishDiagnostics` to editor (push fallback path) -- [x] **Rust host**: Clear diagnostics on `textDocument/didClose` -- [x] **Rust host**: Read `diagnostics.solution_wide_analysis` from config -- [x] **Rust host**: Read `diagnostics.project_filter` from config -- [x] **Rust host**: `didOpen` syncs text to sidecar via `notify_did_change` (text-sync, kept) -- [x] **Rust host**: Advertise `workspaceDiagnostics: true` in capabilities -- [x] **Rust host**: Implement `textDocument/diagnostic` pull handler (LSP 3.17) — `pull_diagnostics.rs` -- [x] **Rust host**: Implement `workspace/diagnostic` pull handler — `pull_diagnostics.rs` -- [x] **Config**: Add `project_filter: Vec` to `DiagnosticsConfig` -- [x] **Test**: Rust e2e — didClose sends empty publishDiagnostics -- [x] **Test**: Rust e2e — request() skips diagnostic notifications -- [x] **Test**: VSCode — file with type error shows diagnostics -- [x] **Test**: VSCode — file with missing type shows diagnostics -- [x] **Test**: VSCode — valid file has no error diagnostics -- [x] **Test**: VSCode — fixing an error clears the diagnostic -- [x] **Test**: VSCode — diagnostics have correct severity and range -- [x] **Test**: VSCode — closing a document clears its diagnostics -- [x] **Test**: Full-stack — close/reopen with fixed source clears stale diagnostics -- [x] **Test**: Full-stack — `test_full_stack_cross_project_references_no_false_positives` (8-project diamond, regression guard) - -### Removed (obsolete after architecture pivot) -- [x] ~~**Rust host**: On solution load, request solution-wide diagnostics~~ — `request_solution_in_background` to be deleted in Phase 5; eager scan caused phantom CS0246s -- [x] ~~**Rust host**: Stream diagnostics incrementally by file~~ — push streaming replaced by `workspace/diagnostic` partial-result streaming -- [x] ~~**Rust host**: Verification pass re-checks files with errors after solution-wide scan~~ — `verify_error_files` to be deleted in Phase 5; band-aid that didn't work -- [x] ~~**C# sidecar**: Add `workspace/diagnostics/all` IPC handler~~ — to be deleted in Phase 5 -- [x] ~~**C# sidecar**: Add `GetAllDiagnosticsAsync` to `WorkspaceManager`~~ — to be deleted in Phase 5 -- [ ] ~~**Rust host**: Add debounce (150ms) before sidecar request~~ — superseded by Phase 5's 2000ms refresh debounce -- [ ] ~~**Rust host**: Re-request diagnostics for changed file + dependents~~ — replaced by `diagnostics/refresh` in Phase 5 -- [ ] ~~**Rust host**: Priority queue: active > visible > recent > rest~~ — pull model makes the editor the prioritizer; not needed -- [ ] ~~**Rust host**: Cancel stale analysis on new edits~~ — moved to Phase 5 (cancel stale per-document IPC pulls) - -### Phase 5 — Pull + refresh (P0, primary path) -- [ ] **Rust host**: Forward `previousResultId` to sidecar in IPC payload -- [ ] **Rust host**: Return `{ kind: "unchanged" }` when sidecar reports `Changed = false` -- [ ] **Rust host**: Construct `resultId = "p:{pv}|d:{dv}|g:{gsv}"` from sidecar response -- [ ] **Rust host**: `workspace/diagnostic` partial-result streaming via `partialResultToken` -- [ ] **Rust host**: Subscribe to `diagnostics/refresh` IPC notification -- [ ] **Rust host**: Debounced refresh queue (2000ms) → LSP `workspace/diagnostic/refresh` -- [ ] **Rust host**: Delete `request_solution_in_background`, `verify_error_files`, `sync_text_to_sidecar` -- [ ] **Rust host**: Cancel in-flight per-document IPC pulls when fresher pull arrives -- [ ] **Rust host**: Server capability `diagnosticProvider.identifier = "sharplsp"` -- [ ] **C# sidecar**: Extend `workspace/diagnostics` IPC payload with `PreviousResultId`; response with `ResultId` + `Changed` -- [ ] **C# sidecar**: Add `workspace/diagnostics/pull` handler with partial-response streaming -- [ ] **C# sidecar**: Add `WorkspaceDiagnosticsCache` keyed on `(DocumentId, ProjectVersion, DocumentVersion, GlobalStateVersion)` -- [ ] **C# sidecar**: Add `IDiagnosticsRefresher` equivalent (`GlobalStateVersion: ulong` + `RefreshRequested` event) -- [ ] **C# sidecar**: Subscribe to `_workspace.RegisterWorkspaceChangedHandler`; bump version on relevant `WorkspaceChangeKind`s -- [ ] **C# sidecar**: Emit `diagnostics/refresh` outbound notification with `{ GlobalStateVersion, AffectedProjectIds[] }` -- [ ] **C# sidecar**: Emit `workspace/initializationComplete` outbound notification once after init -- [ ] **C# sidecar**: Delete `GetAllDiagnosticsAsync`, `OrderProjectsByDependencies`, `workspace/diagnostics/all` handler -- [ ] **C# sidecar**: Skip auto-generated syntax trees (`syntaxTree.IsAutoGenerated`) -- [ ] **Test**: Pull during workspace load returns "loading" or partial — never CS0246 for resolvable types -- [ ] **Test**: `previousResultId` round-trip → `{ kind: "unchanged" }` -- [ ] **Test**: Workspace mutation triggers `workspace/diagnostic/refresh` within 2.5s -- [ ] **Test**: Editing a file invalidates only its project's `resultId`s - -### Phase 5.6 — NuGet restore gate (P0) -- [ ] **C# sidecar**: `Workspace/ProjectRestorer.cs` (assets.json staleness check) -- [ ] **C# sidecar**: `Workspace/DotnetCliHelper.cs` (shell `dotnet restore`) -- [ ] **C# sidecar**: Call `RestoreIfStaleAsync` in `OpenCoreAsync` before `MSBuildWorkspace.Create` -- [ ] **C# sidecar**: Honor `auto_restore_on_open` config flag -- [ ] **C# sidecar**: Bump `GlobalStateVersion` + `diagnostics/refresh` after restore -- [ ] **Rust host**: `$/progress` work-done token for workspace load -- [ ] **Rust host**: Forward sidecar restore progress as `$/progress` events -- [ ] **Rust host**: Send LSP `workspace/projectInitializationComplete` on init complete -- [ ] **Config**: Add `auto_restore_on_open: bool` to `DiagnosticsConfig` (default true) -- [ ] **Config**: Add `refresh_debounce_ms: u32` to `DiagnosticsConfig` (default 2000) -- [ ] **Test**: Stale `assets.json` → restore on open → no CS0246 for NuGet types -- [ ] **Test**: `auto_restore_on_open = false` → server warns, does not lie about success -- [ ] **Test**: `workspace/projectInitializationComplete` fires exactly once per open - -### Analyzer pipeline (Phase 4 unchanged, depends on Phase 5) -- [ ] **C# sidecar**: Enable `CompilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync()` in per-document pull path -- [ ] **C# sidecar**: Load NuGet analyzer references -- [ ] **C# sidecar**: Load .editorconfig severity overrides -- [ ] **C# sidecar**: IDE0005 (unused usings) detection -- [ ] **C# sidecar**: Map analyzer codes to `codeDescription` URLs -- [ ] **Static analyzers**: Implement - [DIAGNOSTICS-STATIC-ANALYZERS-PLAN.md](DIAGNOSTICS-STATIC-ANALYZERS-PLAN.md) - for monorepo-only unused public C#/F# code elements - -### F# sidecar (Phase 3 unchanged) -- [ ] **F# sidecar**: `FSharpCheckFileResults.Diagnostics` integration -- [ ] **F# sidecar**: Map F# diagnostic severity to LSP -- [ ] **F# sidecar**: Solution-wide F# analysis via `FSharpChecker` (per-document, pull-driven) -- [ ] **F# sidecar**: Equivalent `IDiagnosticsRefresher` + workspace event subscription -- [ ] **F# sidecar**: FSharpLint integration -- [ ] **F# sidecar**: FSharp.Analyzers.SDK plugin loading - -### Config remaining -- [ ] **Config**: Add `min_severity: String` to `DiagnosticsConfig` -- [ ] **Config**: Add `max_per_file: u32` to `DiagnosticsConfig` -- [ ] **Config**: Hot-reload via `workspace/didChangeConfiguration` (bump `global_state_version`, fire refresh) - -### Tests remaining -- [ ] **Test**: Full-stack — open file with errors → errors in Problems panel via pull -- [ ] **Test**: Solution-wide pull surfaces errors in unopened files -- [ ] **Test**: Project filter excludes specified projects from `workspace/diagnostic` -- [ ] **Test**: Sidecar crash recovery preserves last-known `resultId` cache (or invalidates atomically) - -## Phase 7: Advanced Analysis - -- [ ] Code metrics (cyclomatic complexity) -- [ ] Value tracking / data flow analysis +Current transport contracts (2026-10-25): four added native cases use the actual compiled reference backend and real files, covering repair/regression, cancelled-terminal drainage, honest EOF/cancellation-write failure, and repeated natural watcher exit/respawn on Windows. All 37 transport tests pass in a fresh instrumented cohort, with 99.0826% production coverage against the unchanged 99% floor and 100% coverage of the cancellation module. Strict all-target Clippy passes. The preceding 97.7064% cohort and all 44 original profiles are preserved; added portable lifecycle assertions close that Windows gap. Deslop finds no clusters in the final test file after canonical fixture reuse. This is scoped transport proof, not a complete workspace CI pass. Linux host compilation is being recovered from a fresh RAM cache after 682 cached libraries and several compiler files report physical I/O errors; preserve those failures and keep the remaining delivery items open. + +Current SDK-context candidate (2026-10-25): all 480 F# managed cases and unchanged coverage gates pass on both platforms (94.2432% Linux, 94.3243% Windows). Forty-seven managed/build inputs match both replicas; twelve built/tested/published assembly triples match exactly. All eight focused SDK/ownership editor cases pass per platform. The final lint-clean SDK regression still fails against the preserved old payload and passes against the fixed payload with every assertion retained. Windows passes all 21 native workspace protocol cases and strict native formatting/all-target development Clippy. Its full editor run passes 58 cases and reports three failures: disk exhaustion in analyzer setup and cleanup, plus a project-write sharing error. The unchanged affected selection then passes three cases but reproduces the F# sharing failure. Fifteen of seventeen native cancellation cases pass; both remaining barrier failures remain open. Earlier overbroad error classification, cache, disk-capacity and UNC failures remain preserved. The Linux build filesystem is emergency read-only with I/O errors; exact release CI and final Linux editor/native qualification remain required. Complete branch CI is still red. + +Earlier framework/reference candidate (2026-10-25): all 1,463 managed cases pass without skips on Windows and Ubuntu (151 Common, 833 C#, 479 F# per platform), with all unchanged coverage gates passing. Eight frozen framework/reference test files remain unchanged; forty-six reviewed managed/build files match both replicas. The three real IPC reference controls preserve one authoritative error per missing reference and clear after repair. The final normal-publish Linux editor run passes all 59 cases and all 21 native protocol cases pass. The complete Windows editor run passes 56 cases but fails three fixture-owned package-cache preconditions because its wrapper adds a user-wide fallback cache; SDK A/B restoration proves that cause. Current SDK-context integration supersedes that earlier payload. Fresh audits pass for all three Rust lockfiles, both .NET targets with transitive packages and all five npm graphs. Two npm advisories first fail the existing gate, then disappear after the scoped MSAL Node 7.1.0 override and serializer 7.1.3 update; the real VSIX packaging CLI and full extension lint/type checks pass with the patched dependencies. Complete branch CI remains red as detailed below. + +Latest package-snapshot candidate (2026-10-25): all 1,450 managed cases pass without skips on Windows and Ubuntu (141 Common, 833 C#, 476 F#), and all unchanged coverage gates pass. Both build replicas match the forty reviewed managed/build source files. Fourteen real editor cases pass per platform; the final seven affected cases also pass after duplication cleanup. The final frozen Windows tests retain four causal failures against the preserved old runtime, while the same assertions pass with the repaired runtime. Deslop measures 5.9463% against the unchanged 6.15% ceiling, with no identical/nearly-identical clusters remaining in the changed helpers. Formatting, TypeScript, ESLint, manifest scheduling and whitespace checks pass. This qualifies package-reference freshness and subsequent bulk-route editor integration; framework/other restore inputs and complete branch CI remain required. + +Latest bulk-route candidate (2026-10-25): all 1,445 Linux managed cases and the unchanged coverage gates pass (136 Common, 833 C#, 476 F#). All 833 Windows C# cases and their unchanged coverage gate pass; the matching six-case F# controls also pass. The new protocol assertions are byte-for-byte unchanged from the frozen failing C# run. Required Deslop rescanning measures 5.9553% against the unchanged 6.15% ceiling; formatting, spec citations and whitespace checks pass. Editor integration and full branch CI remain required for this subsequent candidate. + +Latest restore candidate (2026-10-25): immutable source53 passes all 1,443 managed cases (136 Common, 832 C#, 475 F#), all unchanged coverage gates and the final ten real editor controls on both Windows and Ubuntu. The frozen final Windows editor regression still fails against the preceding restore gate after teardown reuse; it passes with the repaired gate and normal SDK evaluation. This qualifies the imported-input fixes, not subsequent source changes or complete branch CI. + +Latest fallback candidate (2026-10-25): all 1,437 managed cases and unchanged coverage gates pass on both Ubuntu and Windows (130 Common, 832 C#, 475 F# per platform). The fixed runtime passes 40 diagnostic VSIX cases together on Windows. After moving the existing SDK build assertion into a shared helper without weakening it, the final frozen fallback regression still fails against the old resolver, and all four focused fallback/project/generated-source cases pass on both platforms. The unchanged Windows NTFS cancellation assertions also pass after correcting CodeDom's UNC source-file handling in the shared native-lock fixture; a real helper startup failure was captured before that correction, and the five focused managed cases and final complete Windows run pass afterward. Earlier disk-exhaustion, alternate-layout, inherited-configuration and excessive-temp-path failures remain preserved. Complete branch CI is still required. + +Previous combined candidate (2026-10-24): all 1,434 managed cases and their unchanged coverage gates pass on both Ubuntu and Windows. The three native cancellation, five recovery and two external-owner sessions pass on both platforms. The actual combined release host, current compiler pair and profiler engine pass all ten selected editor regressions together, with running binary hashes verified. Windows CI now selects the existing F# NTFS cancellation cases alongside transport tests; the exact prebuilt target passes 35 Common and two F# cases. Extension formatting, ESLint, TypeScript, specification/chunk checks and all 36 tooling assertions pass; Deslop measures 6.0267% against the unchanged 6.15% ceiling. + +The shared toolkit's exact Linux workflow now passes cleanly on its current 362-file source checkpoint: formatting, UI install/build/path checks, strict release Clippy and specification citations, all 458 Rust cases without skips, every unchanged coverage floor, coverage-gate regressions, typeDiagram checks, and the complete release build. Measured live/server/sidecar coverage is 94.48%/98.25%/99.54%. Platform-specific URI branches compile only on their applicable OS, preserving their existing conversions. The normally formatted syscall-isolation tests remain byte-for-byte unchanged across the profiler fix. Earlier generation 353 and subsequent cohort failures remain preserved, including the unrelated-process attachment race. The final combined branch still needs a complete clean root `make ci` pass and final Windows qualification; keep this plan open. + +- [ ] Meet source size/function limits; reuse canonical helpers and pass the existing Deslop duplication ratchet. +- [ ] Run strict Rust format/clippy, sidecar checks, VSIX type/lint/format checks and relevant existing suites. +- [ ] Promote only reviewed, proven private changes with coordinated source locks; preserve the separate CI collector's artifacts until it exits naturally. +- [x] Commit/push shared lspkit changes before the parent feature branch pointer. +- [ ] Run the exact complete CI preparation workflow on the final branch, then coordinate PR/CI/release ownership. +- [ ] Add automated real source-builder CLI proofs for hash/archive/AST guards, fingerprint tampering and unchanged incremental output; verify fixture/tool inputs survive the precompiled CI artifact boundary. +- [x] Make the shared toolkit coverage gate fail closed for missing tools/inputs and invalid thresholds; twelve real CLI regressions pass on Windows and Ubuntu without lowering any coverage floor (lspkit #22). + +## [DIAG-PLAN-ADVANCED] Subsequent Analysis + +- [ ] Code metrics (cyclomatic complexity). +- [ ] Value tracking/data-flow analysis. diff --git a/docs/plans/RAZOR-PLAN.md b/docs/plans/RAZOR-PLAN.md deleted file mode 100644 index 5197f243..00000000 --- a/docs/plans/RAZOR-PLAN.md +++ /dev/null @@ -1,161 +0,0 @@ -# Razor and Blazor Implementation Plan - -**Spec:** [RAZOR-SPEC.md](../specs/RAZOR-SPEC.md) - -## Context - -SharpLsp has not supported `.razor` or `.cshtml` files until now. Research confirmed three things ([RAZOR-RESEARCH]): - -- The SDK's own Razor source generator already runs inside the C# sidecar's design-time workspace. -- Its enhanced `#line` directives map every C# fragment back to Razor. -- The public compiler IR supplies everything the mapping misses. - -So the C# sidecar owns Razor end to end, the host routes the documents to it, and the editors sync and forward HTML. - -The phases below are in dependency order. Each item is checked only when a test that exercises it passes. - -## TODO - -### Phase 1: Foundation - -- [x] Fixture solution `src/sharplsp/tests/fixtures/razor/` with `BlazorApp`, `Widgets` (RCL), `PagesApp` (Pages + MVC) and `Domain` (F#), isolated by its own `Directory.Build.props` ([RAZOR-TESTING]) -- [x] Host: add `LangId::Razor` for `.razor` and `.cshtml`, owned by the C# sidecar ([RAZOR-ARCHITECTURE-ROUTING]) -- [x] Host: route `documentSymbol`, `foldingRange` and `linkedEditingRange` for Razor to the sidecar, and answer `selectionRange` empty (the Deferred selection-range item has since routed it to the sidecar too) -- [x] Host: add `<` and `@` completion triggers, and short-circuit them for C# and F# documents -- [x] Host: publish Razor diagnostics under the source `sharplsp-razor` -- [x] Sidecar: bundle the Razor compiler from the build SDK, and list it in `THIRD-PARTY-NOTICES.md` ([RAZOR-ARCHITECTURE-ENGINE]) -- [x] Sidecar: find Razor files as additional documents, and sync their text in every framework context, including the pending-edit replay ([RAZOR-ARCHITECTURE-DOCUMENTS]) -- [x] Sidecar: build the C# projection — find the generated document by its checksum pragma, and build the verified span map in both directions ([RAZOR-ARCHITECTURE-PROJECTION]) - - [x] Verify the checksum lookup, the mappings that never map (hidden, fileless, foreign, outside either text), carets at a fragment's end, and rename's check that a span holds the old name — `RazorProjectionTests`: 6 passing cases over hand-written generator output -- [x] Sidecar: build the Razor engine per project — configuration from the analyzer and parse options, an in-memory file system, tag helpers per compilation, the capture pass and markup facts - - [x] Verify configuration fallbacks, target-path metadata, current file/import reads, missing files, missing paths, cancellation and failure recovery — `RazorEngineSettingsTests`: 12 passing cases; settings, filesystem and engine each have 100% line coverage -- [x] Sidecar: build the HTML projection and the markup-context tokenizer ([RAZOR-ARCHITECTURE-HTML]) - - [x] HTML projection from compiler IR, preserving offsets/newlines and current unsaved text — `RazorHtmlProjectionTests`: 19 passing fixture cases (Blazor and MVC, comments, UTF-16, incomplete tags, unsaved edits) - - [x] The C# in an HTML attribute value (`disabled="@(…)"`) and every directive attribute, including a minimized parameter the compiler gives no span (`@onclick:stopPropagation`) and a binding parameter's value (`@bind:event="oninput"`), is blank in the projection, while the attribute's name, quotes and literal text stay (#407, review R10, Claude2, 694b48c0). Three regressions failed with the fix off, then passed unchanged; the full .NET leg passes and C# coverage ratchets to 95.54% - - [x] HTML markup-context tokenizer — `HtmlMarkupContextTests`: 36 passing cases with 100% line coverage, including malformed start/end tag recovery; the three character-reconsumption regressions were verified failing before the fix and pass unchanged afterward - - [x] Host: `sharplsp/razor/htmlProjection` answers `{ text }`, or `null` for a file no project lists — `test_full_stack_razor_routing_session` - - [x] Verify `sharplsp/razor/htmlProjection` through editor integration — `razor-lsp.test.ts`: the projection keeps the editor's offsets, line breaks and unsaved text, and feeds VS Code's HTML completion and hover - -- [x] Sidecar: Razor C# features answer whatever SDK a project resolves (#399). When the project's SDK Razor generator was built against a newer Roslyn than the sidecar's, the bundled Razor compiler loads in its place, and a warning names both Roslyn versions ([RAZOR-ARCHITECTURE-GENERATOR]) - - [x] Root cause: a newer SDK's Razor compiler has a higher assembly version than the bundled copy. Roslyn's analyzer loader stands the sidecar's own copy in only once that copy is loaded. Otherwise it loads the SDK's copy, finds it references a newer `Microsoft.CodeAnalysis`, and refuses it for the session. xUnit's discovery always loads the bundled copy before any test runs, which is why the sidecar's own Razor tests passed on runners with SDK 10.0.401 while the end-to-end suites failed. Confirmed on SDK 10.0.401 itself: its compiler is version 10.4.0.0 and references `Microsoft.CodeAnalysis` 5.9, against the sidecar's 5.6 - - [x] Failing tests, failing for the bug: `RazorNewerSdkTests` gives the fixture's design-time builds a newer SDK's Razor compiler (a higher assembly version that references a newer Roslyn). In a fresh sidecar process, hover in `Counter.razor` answers null: the process loads that compiler from the SDK's folder, never its own copy, and Roslyn refuses it. In process, the projects keep the newer SDK's generator files. The fixture copies now keep the repository's SDK pin, as the editor suites' copies do - - [x] Fix: at project load, an SDK Razor compiler that references a newer `Microsoft.CodeAnalysis` than the running one is swapped, with its two companion files, for the bundled copies, and a warning names both versions (`RazorGenerator`, Codex1, 029fc3a) - - [x] Pass: the two failing tests pass unchanged, and a third proves a compiler that cannot be read leaves the project as it loaded - - [x] Full sidecar suite and coverage gates with the fix: C# 693/693, F# 447/447, Common 125/125; C# coverage 96.50% ratchets its threshold to 95.49% - - [x] CI proof on a real newer SDK: a Rust end-to-end Razor test whose fixture copy keeps no SDK pin, so runners carrying SDK 10.0.401 load it with that SDK (Codex1, 029fc3a). It passes locally against an isolated real SDK 10.0.401, and in CI: `test_latest_sdk_razor_generator_remains_usable` passes on Rust Shard 2/2 of run 39364686929, whose ubuntu-24.04 image (20261012.334) ships SDK 10.0.401 beside the pinned 10.0.303 - - [x] Docs and blog: drop the advice to pin SDK 10.0.303, and explain the bundled-compiler fallback (Claude1, 8c418ced) - -**Validation (2026-10-20):** the Release sidecar solution builds with zero warnings and errors; 671 C#, 440 F# and 125 Common tests pass, and all three coverage gates pass. C# coverage is 96.39%, with all 1,530 new C# executable lines covered. `RazorSidecarTests` exercises the real IPC contract and malformed-request recovery; `RazorCompletionEditsTests` covers mapped edits, import deduplication, aliases, static imports and LF/CRLF insertion. - -**Editor validation (2026-10-20):** the final Razor chunk passes all 6 cases, the workspace chunk passes all 446 cases (including server-start lifecycle), the debugger chunk passes all 80 cases, and editor lint and formatting pass. The debugger group-outcome test that previously timed out passes unchanged in the full chunk in 6.5 seconds. - -**Template validation (2026-10-20):** the real host and sidecars run over a fresh `dotnet new blazor` app (.NET 10). Its files report no errors and keep their shape in the HTML projection. Hover, definition, references, member completion and signature help answer on its real code: layouts, `@inherits`, `@Body`, `NavLinkMatch`, `@Assets`, `typeof(Program)`. The one false report found was usings marked unnecessary: every using in `_Imports.razor`, and, as review showed, a component's usings that only its markup needs. Razor files no longer report unnecessary usings (`Import_files_never_report_their_usings_as_unnecessary`, `Components_never_report_a_using_their_markup_may_need_as_unnecessary`). - -### Phase 2: C# Inside Razor - -- [x] Diagnostics: C# diagnostics from projections, `RZ` diagnostics from facts, and Razor files in solution-wide diagnostics ([RAZOR-FEATURES-DIAGNOSTICS]) - - [x] The compiler's `RZ` diagnostics reach the file and the solution scan with LSP severities (Claude2). `RazorDiagnosticsTests` failed for the gap: the scan held no entry for a file whose only diagnostics were Razor's. It passed unchanged once both paths add the markup facts' diagnostics. The full-stack session's new step publishes RZ1034 as an Error and RZ10012 as a Warning under `sharplsp-razor`; it failed against the old sidecar and passes against the new one -- [x] Hover at mapped positions ([RAZOR-FEATURES-HOVER]) -- [x] Definition, type definition, declaration and implementation from mapped positions, with generated component classes landing in their `.razor` file ([RAZOR-FEATURES-DEFINITION]) - - [x] `RazorNavigationTests` (Claude2): - - Type definition from `@ref="tally"` and from the field lands in `Tally.razor`. - - The declaration of `Dispose` is `IDisposable.Dispose`. - - The implementations of `@implements IDisposable` include `Board.razor`. - - [x] Type definition at a declaration's name answered nothing, in C# as well. `TypeDefinitionFromSymbolTests` failed for it, then passed once the resolver falls back to the symbol's type ([DEFINITION-CSHARP-TYPE]) -- [x] References and document highlights across projections, with hidden scaffolding dropped ([RAZOR-FEATURES-REFERENCES]) -- [x] C# completion, with mapped edits and `using` → `@using` ([RAZOR-FEATURES-COMPLETION]) - - [x] Completion right after a typed dot offers members: `@scores.` and an unclosed `@(scores.` (#401, review R2). Red tests `RazorCompletionRecoveryTests` (Codex1) failed for the bug, then passed unchanged once completion asks a copy of the file with the expression finished at the caret (Claude2). An unclosed `@{` already completed and is kept as a control - - [x] Coverage gates with the #401 fix; the full C# sidecar suite passes with it - - [x] Completion inside a call or lambda of an unfinished `@(` offers members (#404, review R8). Red tests from Claude1's report failed for the bug, then passed unchanged once the copy closes every construct Roslyn's parser reads as open, innermost first, and its map keeps the parts of a fragment outside the C# it inserted. Completion now reads the compiler's diagnostics only when the projection's C# does not parse, and forks only the file's active framework -- [x] Signature help ([RAZOR-FEATURES-SIGNATURE-HELP]) - - [x] Signature help after `@(` no longer lists the generator's scaffolding (`AddContent`, `AddAttribute`) (#400, review R1, Codex1): the regression cases failed for the bug, then passed unchanged -- [x] Semantic tokens for C# fragments ([RAZOR-FEATURES-SEMANTIC-TOKENS]) -- [x] Rename and prepareRename — Razor usages and `@code` declarations, from `.cs` and `.razor` origins ([RAZOR-FEATURES-RENAME]) - - [x] Escaped identifiers (`@class`) get their Razor rename edits (review R4, Codex1): the real-fixture regression failed for the bug, then passed unchanged - - [x] `RazorRenameTests` (Claude2): - - Renaming `Badge.Text` from its code-behind edits `@Text` and Board's `Text=`. - - Renaming `Tally.Label` turns `@bind-Label` into `@bind-Title`. - - prepareRename refuses on a component's start and end tags. - - The F# half of the rename is Phase 5 - -### Phase 3: Blazor Markup - -- [x] Component tags: hover, definition, references, highlights and linked editing (Claude2) - - Hover: `RazorMarkupHoverTests`. A start or end tag answers its component's type and summary, ranged on the tag name. - - Definition: `RazorMarkupDefinitionTests`. A tag goes to the component's `.razor` file and its code-behind partial, or a framework component to its decompiled source. - - References: `RazorMarkupReferencesTests`. A component's references gain every tag that instantiates it, from markup or from C#, in every Razor file, the app's tags of a class-library component included. - - Highlights: a component's start and end tags highlight with its C# uses. - - Linked editing: `RazorStructureTests`. - - Each new test failed against the C#-only answers, then passed unchanged. -- [x] Component attributes and `@bind-X`: hover, definition, references and rename (Claude2) - - Hover: a parameter answers its property; on `@bind-` the markup answers, ranged on the whole name, and on `Label` the C# the projection maps answers. - - Definition: `@bind-` goes to the parameter it binds. - - References: a parameter's references include every `@bind-X` that names it. - - Rename: `RazorRenameTests`. -- [x] Directive attributes and directives: hover (Claude2). `@onclick` answers its descriptor's documentation and `@page` its directive's description, both from the compiler; HTML answers nothing -- [x] Completion: components, parameters (with `[EditorRequired]` first), `@bind-X`, directive attributes and their parameters, and directives (Claude2, `RazorMarkupCompletionTests`) - - After `<`, components complete by their short name where their namespace is in scope: imported, the file's own, or one containing it, as the compiler binds them. Otherwise they complete by their full name, with their documentation. Inside a component, its child-content parameters complete too. - - In a component start tag: the parameters not yet set, `[EditorRequired]` first and marked, then `@bind-X` and the directive attributes. - - In an element start tag: the directive attributes and each of their parameters (`@bind:event`, `@onclick:preventDefault`). - - An `@` opening a markup line offers the parser's directives for the file's kind. - - Every item replaces the name typed at the caret. - - The host carries the new `documentation` and `sortText` to the client. The full-stack session proves both, and that the required parameter sorts first -- [x] Semantic tokens for markup: component tags, attributes, directive attributes and directives (Claude2, `RazorMarkupTokensTests`) - - Component tags are classes, their parameters properties, directive attributes decorators and directives keywords. - - Where a name holds C#, the C# token wins and the markup keeps its part before it, so no tokens overlap. - - The C# token test now requires a markup line's markup token alone, where it used to require no token at all -- [x] Folding ranges and document symbols ([RAZOR-FEATURES-STRUCTURE]) (Claude2) - - `RazorStructureTests` covers element, `@code` and `@functions` folds and the outline. - - C# code blocks (`@{ … }`) did not fold. They do now, from the code nodes the IR holds outside every directive. - -### Phase 4: Razor Pages and MVC - -- [x] `@model`, `@inject`, `@section` and `@functions` through the projection ([RAZOR-MVC-DIRECTIVES]) - - `RazorMvcTests.Model_inject_and_functions_answer_through_the_projection`: `@model EditModel` and `Model.Title` hover their docs, `@inject` hovers `ILogger<…>`, and `@Shout` goes to its `@functions` declaration. - - `Model_expressions_and_section_code_get_csharp_features`: `asp-for="Title"` hovers and goes to `EditModel.Title`, finds its 4 references in `Edit.cshtml`, and renames them all with no generated-file edit; `@Model.Price` inside `@section` hovers. `Section_blocks_fold_and_outline` folds and outlines `@section`. - - Full stack: `test_full_stack_razor_pages_and_mvc_views` (`razor_mvc.rs`). -- [x] Tag helper elements and attributes: hover and completion ([RAZOR-MVC-TAGHELPERS], [RAZOR-MVC-SCOPE]) - - Hover: `Tag_helper_elements_and_attributes_answer_hover_from_their_descriptors` (`` answers `InputTagHelper`, `asp-for` its `ModelExpression`). - - Completion: `Tag_helper_attributes_complete_in_an_element_start_tag` (`asp-page`, `asp-controller`, `asp-route-`) and `Tag_helper_elements_complete_after_an_open_angle_bracket` (`partial`, `environment`, `a` once, the `th:` prefix, `@removeTagHelper`). That test found components completing after `<` in `.cshtml` files, where no component binds by tag; they no longer do. - - Scope: `Tag_helper_directives_scope_completion_and_binding`. VS Code: `a Razor Page completes tag helper elements once, before the HTML service` (an element that both HTML and a tag helper name is listed once). -- [x] `_ViewImports.cshtml` imports resolved by the engine ([RAZOR-MVC-IMPORTS]) — `ViewImports_imports_reach_the_views_below_them`: `@model Item` resolves through `Views/_ViewImports.cshtml`'s `@using`, the view reports no error, and its `@addTagHelper` binds `` - -### Phase 5: F# - -- [x] Hover, completion and definition from Razor into F# types of a referenced F# project ([RAZOR-FSHARP-METADATA], [RAZOR-FSHARP-DEFINITION]) - - Hover shows the F# XML docs: the design-time build references Domain's `refint` assembly, which has no XML, so the sidecar swaps in the built DLL with its documentation. `RazorFSharpTests.Hover_and_signature_help_read_FSharp_symbols_from_their_assembly` and `Completion_offers_FSharp_members_in_markup_and_code`. - - Definition lands in `Scores.fs`, not decompiled source: the C# sidecar names each F# symbol by assembly and XML doc ID, and the F# sidecar finds its declaration (`workspace/declarationForeign`, [DEFINITION-CROSSLANG-IDENTITY]). `Definition_names_each_FSharp_symbol_for_the_FSharp_sidecar`; F#: `FSharpForeignSymbolsTests`. The C#→F# definition e2e now lands in `Shapes.fs` too. - - References merge both sides by the same identity (`workspace/referencesForeign`, [REFERENCES-CROSS-LANGUAGE]): `Foreign_references_and_rename_reach_Razor_uses_of_an_FSharp_symbol`. - - F# references and highlights placed a dotted use (`player.Score`) over its whole path. They now sit on the name, the same span the foreign path reports (`F# references and foreign uses place each use on its name alike`). -- [x] F#-origin rename that includes Razor usages, mapped back from the projections ([RAZOR-FSHARP-RENAME]) - - From F#: `Foreign_references_and_rename_reach_Razor_uses_of_an_FSharp_symbol` renames `Leaders.razor`'s `@player.Score`. - - From Razor or C#: the rename resolves to the F# symbol and renames it everywhere (`Rename_from_Razor_renames_an_FSharp_symbol_in_CSharp_and_Razor`). - - Full stack: `test_full_stack_fsharp_in_razor_session` (hover, definition into `Scores.fs`, references with and without the declaration, rename from Razor and from F#). VS Code: `F# symbols in Razor navigate to, find and rename in their F# source`. - - That full-stack test only restored the fixture, so C# had no `Domain.dll` to read and every F# symbol stayed unresolved ([DEFINITION-CROSSLANG]: the F# project must be built). Its workspace now builds the F# library (`create_razor_workspace_with_fsharp_built`). - -### Phase 6: Editors - -- [x] VS Code: sync `razor` and `aspnetcorerazor` documents, and activate on `onLanguage:razor` ([RAZOR-EDITORS]) — `razor-lsp.test.ts`: a Razor file opens as Razor, and its unsaved edits reach the server -- [x] VS Code: forward HTML completion and hover over `sharplsp/razor/htmlProjection` ([RAZOR-FEATURES-HTML]) — `razor-lsp.test.ts`: element names and HTML hovers in markup, C# completion and hover in code - - [x] A C# position offers C# items alone: no word suggestions from the HTML projection, including after a typed dot and inside an unfinished `@(` (#405, review R9, Codex1, fb1fff3). The client sends the caret, and the projection answers null where the compiler-backed completion target is C#, while tag and attribute names stay markup. The host, sidecar and real-editor regressions failed for the bug, then passed unchanged -- [x] VS Code: add a `razor` end-to-end chunk on both platforms — `test-chunks.json`, in both matrices -- [x] Zed: Razor language registration (Zed has no built-in Razor grammar) — `the_server_attaches_to_the_csharp_fsharp_and_razor_languages` - -### Deferred - -- [x] Razor formatting — resolved as out of scope: SharpLsp formats no language ([SHARPLSP-FEATURES-FORMATTING]), so the spec now says so for Razor ([RAZOR-SCOPE-FORMATTING]) and nothing is advertised -- [x] Selection ranges ([RAZOR-FEATURES-SELECTION]) — `RazorSelectionRangesTests` (6 cases: C# chains through syntax nodes, tag names through enclosing elements, attribute names and values, implicit expressions, legacy files, unlisted files); `test_full_stack_razor_selection_ranges`; the routing session's `selectionRange` now asserts the exact chain. An attribute's whole span comes from the compiler's spans (a new `MarkupAttribute.End` fact), not a scan of the text -- [x] Code actions: extract a block to code-behind, extract a component, generate an event handler, add `@using` ([RAZOR-CODE-ACTIONS]) — `RazorCodeActionsTests` (5 cases). A new `@using` goes after a Razor Page's `@page` line, never above it (`A_new_import_follows_the_page_directive_a_Razor_Page_opens_with`). New files arrive as LSP `CreateFile` operations (`map_workspace_edit_creates_new_files_before_editing_them`) -- [x] Inlay hints, code lens, and call and type hierarchy from Razor positions ([RAZOR-FEATURES-INLAY-HINTS], [RAZOR-FEATURES-CODE-LENS], [RAZOR-FEATURES-HIERARCHY]) — `RazorHintsAndLensesTests` (2), `RazorHierarchyTests` (3), `test_full_stack_razor_hierarchy_and_hints` - - Outgoing calls from a Razor projection threw: the resolver read the declaration through `Solution.GetDocument`, which is null for a source-generated document. It now reads each declaration through its own tree - - Markup callers are the component, named by its file with kind `Class`, never the generated `BuildRenderTree` - - The C# sidecar named hierarchy kinds in lowercase, while the host maps PascalCase (the F# sidecar's casing). So every C# class, property or interface item reached the client as a method or class. The C# kinds are now PascalCase, and the e2e asserts the LSP kinds -- [x] `.razor.css` and `.razor.js` collocation awareness ([RAZOR-COLLOCATION]) — `RazorCollocationTests` (6), `RazorStylesheetTests` (10), `HtmlMarkupContextTests` (attribute at a caret), `test_full_stack_razor_collocated_styles_and_scripts`, VS Code `a component completes and defines its stylesheet classes, and nests its collocated files` - - Classes come from a CSS Syntax Level 3 tokenizer over rule preludes; a class completes as LSP `Value` and defines to every selector that declares it. A module literal in an `"import"` call defines to its `.razor.js`, from Razor, a code-behind and plain C# - - The attribute at a caret is read by the HTML tokenizer, continued past the caret. Review found three bugs in a text-scanning first version: an attribute after a bare one (`