Skip to content

Integrate the WinUI 3 XAML language service into the winapp extension - #50

Open
Nikola Metulev (nmetulev) wants to merge 75 commits into
mainfrom
nmetulev/winui
Open

Integrate the WinUI 3 XAML language service into the winapp extension#50
Nikola Metulev (nmetulev) wants to merge 75 commits into
mainfrom
nmetulev/winui

Conversation

@nmetulev

@nmetulev Nikola Metulev (nmetulev) commented Jul 13, 2026

Copy link
Copy Markdown
Member

Description

Integrates the WinUI 3 XAML language service into the shipping winapp VS Code extension as a single additive extension experience. Existing CLI commands, debugging, AppxManifest editing, and the new manifest IntelliSense from main remain intact.

What lands:

  • LSP client module (src/xaml/) with lazy XAML activation, serialized start/stop/restart lifecycle, workspace-trust gating, automatic settings restarts, and actionable degraded-mode notifications.
  • .NET LSP server (server/) providing completion, hover, definition, diagnostics, references, rename, code actions, formatting, semantic tokens, folding, links, and project-aware Roslyn/MSBuild features.
  • Framework-dependent delivery. The VSIX bundles the server assemblies and BuildHost-netcore, but no .NET runtime and no apphost — scripts/validate-vsix-server.ps1 fails the build if either appears.
  • Runtime acquired through the .NET Install Tool. The extension does not probe for .NET itself. It calls dotnet.findPath on ms-dotnettools.vscode-dotnet-runtime — the same component the C# extension and C# Dev Kit use — pinned to 10.0 with versionSpecRequirement: "equal" so any 10.0.x is accepted. The Install Tool is deliberately not an extensionDependencies entry (that would install it for every WinApp user); it is installed silently on demand the first time a XAML file is opened in a trusted workspace.
  • Signed release artifact flow: local development publishes from source with dotnet publish; CI/release builds the server once, signs it, downloads the signed artifact, and packages without unsigned fallback.
  • Language contributions for XAML syntax highlighting and editing configuration.
  • CI and release validation covering server tests, ARM64, LSP smoke, integration tests, VSIX payload validation, and marketplace publication gated on signing.

Usage Example

Opening any .xaml file in a trusted workspace activates the language service. There is nothing to configure: the winui-xaml.server.path and winui-xaml.server.dotnetPath settings were removed in this PR, and the .NET runtime is resolved through the Install Tool. The WINUI_XAML_SERVER_PATH environment variable remains, for development and integration testing only.

Commands: WinUI XAML: Show Info and WinUI XAML: Restart Language Server.

Prerequisites

  • Running the server: .NET 10 runtime, acquired automatically via the Install Tool. No user action required.
  • Project-aware IntelliSense: a machine .NET SDK, because MSBuild comes from the SDK toolset rather than from the runtime hosting the server. Without one, the server still starts and the tolerant parser still reports syntax diagnostics, but no type information is available.

Type of Change

  • ✨ New feature
  • 🔧 Config/build/release
  • 🧪 Test update
  • 📚 Documentation

Checklist

  • New tests added for server, client lifecycle, delivery, .NET host resolution, workspace boundaries, JSON-RPC concurrency/cancellation, and integration behavior
  • Canonical .\scripts\package-vsc.ps1 packaging flow run locally
  • Tested locally on Windows, including ARM64
  • README.md, CONTRIBUTING.md, and release documentation updated
  • package.json language, grammar, commands, configuration, activation, and dependency surfaces updated
  • Latest main merged and conflicts resolved
  • PR review loop completed with no remaining critical/high/medium findings

Verification

  • Extension unit suite: passing
  • Server suites: 511 passing / 0 failing (58 parser, 44 workspace, 409 language-server)
  • Client lifecycle integration: 10 passing / 0 failing
  • Full XAML integration: 1,605 passing / 0 failing / 28 pending
  • ARM64 framework-dependent smoke (project-aware MSBuild/Roslyn flow): passing
  • VSIX payload validated: required server assemblies, dependency metadata, runtime config, and BuildHost-netcore present; apphost and bundled runtime files rejected
  • VSIX size is reported per-build by the metrics workflow (most recent run: ~118.6 MB)
  • TypeScript type-check and lint: passing (existing warnings only)

Security and runtime notes

The server only evaluates projects under trusted workspace-folder roots. Paths are canonicalized across reparse points before containment checks, preventing junction/symlink escapes. Untrusted and empty windows remain syntax-only.

Project-independent features remain available when no MSBuild toolset is present; project-aware features surface an actionable prerequisite warning rather than failing silently.

Review history

The implementation went through iterative multi-dimension review and an independent different-model cross-check. The final pass found no critical/high/medium defects. Non-blocking deduplication refactors were filed as follow-up issues rather than expanding this release-critical PR.

Scope note

Three VS Code test-harness fixes under .github/skills/vsce-testing/ were committed to this branch by accident and have been extracted to #218 (Isolate the VS Code test harness from the user's extension install). They were unrelated to the language service, so this PR no longer touches .github/skills/ and its diff dropped from 273 to 270 files.

Comment thread test/xaml-integration/redteam-gap4.test.js Fixed
Comment thread test/xaml-integration/redteam19.test.js Fixed
Nikola Metulev (nmetulev) added a commit that referenced this pull request Jul 14, 2026
- test:xaml-smoke now runs restore:fixture before the smoke harness so
  element completion has WinUI SDK metadata in CI. The fixture's obj/ is
  gitignored and was previously only restored by the integration pretest,
  so the standalone smoke step failed in CI with 0 completion items. This
  unblocks build-and-test and the packaged VSIX artifact.
- redteam-gap4.test.js: use replaceAll for the caret-marker strip
  (CodeQL "incomplete string escaping or encoding").
- redteam19.test.js: drop the no-op self-replacement
  (CodeQL "replacement of a substring with itself").
- pr-description.yml: tolerate the REST diff API's 20k-line cap (HTTP 406)
  on very large PRs instead of failing; fall back to the changed-file list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
@chiaramooney
Chiara Mooney (chiaramooney) marked this pull request as ready for review August 5, 2026 03:02
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Build in progress — the VSIX size below is from a previous commit and will update when the current build finishes.

VSIX Build

Artifact Baseline Current Delta
VS Code Extension (VSIX) 22.68 MB 118.61 MB 📈 +95.93 MB (+423.08%)

Updated 2026-08-19 18:36:09 UTC · commit 5dcad91 · workflow run

Chiara Mooney (chiaramooney) pushed a commit that referenced this pull request Aug 5, 2026
- test:xaml-smoke now runs restore:fixture before the smoke harness so
  element completion has WinUI SDK metadata in CI. The fixture's obj/ is
  gitignored and was previously only restored by the integration pretest,
  so the standalone smoke step failed in CI with 0 completion items. This
  unblocks build-and-test and the packaged VSIX artifact.
- redteam-gap4.test.js: use replaceAll for the caret-marker strip
  (CodeQL "incomplete string escaping or encoding").
- redteam19.test.js: drop the no-op self-replacement
  (CodeQL "replacement of a substring with itself").
- pr-description.yml: tolerate the REST diff API's 20k-line cap (HTTP 406)
  on very large PRs instead of failing; fall back to the changed-file list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
@chiaramooney
Chiara Mooney (chiaramooney) marked this pull request as draft August 6, 2026 17:05
Nikola Metulev (nmetulev) and others added 15 commits August 6, 2026 14:49
Folds the hardened XAML language service prototype (nmetulev/winui-vsc) into the shipping winapp VS Code extension as a single unified extension. Additive and non-breaking to the existing winapp CLI / debug / manifest features.

Server: copy the .NET 10 LSP server (WinUiXaml.LanguageServer/Workspace/Xaml + 3 xUnit test projects + WinUiXaml.slnx + lsp-smoke) under server/. Builds 0/0; 489/489 unit tests pass.

Client: add src/xaml/xamlLanguageService.ts (activateXaml/deactivateXaml, resolveServerDll bundled->dev fallback) wired additively from src/extension.ts; degrades to syntax-only on server-start failure. Contribute the xaml language, TextMate grammar, language-configuration, winui-xaml.showInfo|restartServer commands, winui-xaml.server.* config, onLanguage:xaml activation, and the vscode-languageclient ^9 dependency in package.json.

Tests: port the 81-file integration harness to test/xaml-integration/ (kept as source of truth) with a clean committed WinAppSDK fixture at test/fixtures/xaml/fixture/ and a root .vscode-test.mjs. Full suite: 1596 passing / 28 pending / 0 failing. smoke.mjs and features.test.js (218) green.

Packaging/CI: esbuild + package-vsc.ps1 publish the server into dist/server (bundled in the VSIX); .vscodeignore excludes C# sources/tests; release-vsc.yml adds UseDotNet@2, a server publish step, and a conditional ESRP DLL-signing step for our own WinUiXaml.*.dll.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
Automated fix->review loop iteration 1 — resolves all 17 findings from the
post-migration PR review (2 High, 9 Medium, 6 Low):

- H1 security: machine-scope winui-xaml.server.path/dotnetPath + workspace-trust
  gate (isTrusted + onDidGrantWorkspaceTrust) + capabilities.untrustedWorkspaces
- H2: deterministic fixture NuGet restore (restore:fixture in pretest)
- M1: run XAML/server/smoke suites in CI with .NET 10 (setup-dotnet)
- M2: document .NET-dependent suites; unit tests stay dotnet-free
- M3/M4: README Features + Requirements for the XAML language service
- M5: serialize client start/stop lifecycle (no torn-down pending start)
- M6: handle workspace/didChangeWatchedFiles (invalidate stale Roslyn cache)
- M7: user-facing "degraded to syntax-only" notification with actions
- M8: client command/lifecycle/degradation integration tests
- M9: fold server bundling into vscode:prepublish (ensure-server-bundle.mjs)
- L1: share bundled-asset path resolver (firstExistingPath)
- L2: tighten .vscodeignore + VSIX-contents assertion
- L3/L4: README command table + CONTRIBUTING .NET 10 SDK prereq
- L5/L6: docs/RELEASE.md signing checklist + server/nuget.config feed pin

Verified: tsc clean, eslint 0 errors, server build 0/0, 489/489 server tests,
smoke PASS, integration 1601 passing / 0 failing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
Automated fix->review loop iteration 2 — resolves the 5 residual/new findings
the independent GPT-5.6 review raised on iteration 1:

- N1 (CRITICAL security): do not start the semantic server while the workspace
  is untrusted (Roslyn/MSBuild would evaluate an attacker-controlled .csproj and
  could execute code). Stay syntax-only until trust is granted; test host runs
  with --disable-workspace-trust so the feature suite still exercises the server.
- N2 (correctness): replace the partial start-guard with a single lifecycle
  queue (runExclusive) so stop fully completes before any start — two servers
  can never coexist across concurrent restarts.
- N3 (correctness): null-guard workspace/didChangeWatchedFiles (changes:null).
- N4 (test-coverage): WINUI_XAML_FORCE_NO_SERVER test seam + a real absent-DLL
  degradation test (syntax-only, no throw).
- N5 (packaging): ensure-server-bundle publishes fresh by default; reuse only
  under WINUI_REUSE_SIGNED_SERVER=1 (set by CI -SkipServerBuild) to preserve
  signed DLLs, so a bare vsce package can never ship a stale server.

Verified: tsc clean, eslint 0 errors, server build 0/0, 489/489 server tests,
smoke PASS, integration 1602 passing / 0 failing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
…(P1-P3)

P1 (M5 residual): guard against server resurrection when restart races
deactivate. Add module-level `disposing` flag set synchronously at the top
of deactivateXaml and reset in activateXaml; doStart early-returns when
disposing. Replace the separate stop+start in the restartServer command and
onDidGrantWorkspaceTrust with a single restartClient() that runs
stop-then-start inside one runExclusive op (atomic; skips start if disposing).

P2: rewrite package.json untrustedWorkspaces capability description to match
actual behavior (syntax highlighting only until the workspace is trusted).

P3 (M9 residual): package-vsc.ps1 -SkipServerBuild set WINUI_REUSE_SIGNED_SERVER
into the caller's environment and never cleaned it up. Capture the prior value
before the try and restore/remove it in the finally block.

Full integration suite: 1602 passing / 0 failing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
…packaging)

Final 8-dimension PR-review gate over the whole branch surfaced 10 findings the
per-iteration reviews (scoped to their own residuals) never looked at. Fix all.

G1 (HIGH security): the isTrusted gate is necessary but not sufficient — an empty
window is trusted, so opening a loose XAML file outside any workspace folder made
the server warm up and MSBuild-evaluate its nearest .csproj (code-exec vector).
Client now passes workspaceFolders as initializationOptions.allowedRoots; the
server (ResolveAllowedRoots/IsPathUnderAllowedRoot/ResolveIfAllowedAsync) only
runs project discovery + OpenProjectAsync for documents under a trusted root.
Empty allow-list => no project evaluation; out-of-root/untitled files are served
project-less (tolerant parser only). Boundary-aware, case-insensitive path check.
allowedRoots is authoritative when present; legacy clients fall back to rootUri.

G3 (HIGH test-coverage): add WINUI_XAML_FORCE_UNTRUSTED seam (mirrors
WINUI_XAML_FORCE_NO_SERVER) so the trust gate + onDidGrantWorkspaceTrust recovery
are exercised under the trusted harness. New client tests cover degrade+recover.

G4 (docs): document winui-xaml.server.path in README.
G5 (correctness): retain the synchronize.fileEvents FileSystemWatcher and dispose
it in doStop / failed-start catch (was leaking one per start/restart/trust cycle).
G6 (UX): untrusted degradation now shows a once-per-transition warning with a
Manage Workspace Trust action (was log-only).
G7 (test-coverage): extract pure buildDegradedNotification() + unit test message
and action mapping.
G8 (test-coverage): client tests for explicit server.path (valid -> semantic
completion; invalid -> syntax-only, no throw).
G9 (test-coverage): lsp-smoke sends didChangeWatchedFiles with omitted/null/empty
changes and asserts the server stays responsive.
G10 (packaging): drop the redundant bundle:server in package-vsc.ps1 (vscode:
prepublish/ensure-server-bundle.mjs is the single publish path).
G2 (packaging): narrow .vscodeignore to exclude only the apphost exe so Roslyn's
net472 MSBuild BuildHost.exe ships; assert a BuildHost is present in the VSIX.

Full integration 1605 passing / 0 failing; server 489, unit 595, smoke green.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
…indings)

The final 8-dimension confirmation gate (incl. a cross-model check) found 5 issues
the per-iteration reviews never scoped. Fix all 5.

G15 (HIGH security): the allow-list was purely lexical — Path.GetFullPath does not
resolve reparse points, so a junction/symlink placed inside a trusted workspace root
that targets an external directory passed the containment check, and the resolver
(FindOwningProject -> MSBuild GetOrLoadAsync) then evaluated the external .csproj.
Fix: CanonicalizePath resolves the final physical path (GetFinalPathNameByHandle) for
both the workspace roots (NormalizeRoots) and the document (IsPathUnderAllowedRoot)
before containment; falls back to the lexical full path for non-existent/untitled
docs or on any OS-call failure. A junction escaping the root now canonicalizes
outside it and is served project-less.

G11 (MEDIUM correctness): a workspace opened at a bare drive root (C:\) rejected all
its children — NormalizeRoots keeps C:\ with its trailing separator, but PathIsWithin
required path[root.Length] to be a separator (it was the first path-segment letter).
Fix: when the root already ends in a separator (bare drive/UNC root) the prefix match
is itself the boundary. The boundary-char check still guards C:\root vs C:\rootEvil.

G12 (HIGH test-coverage): the security-critical boundary had no direct test. Made
NormalizeRoots/PathIsWithin/CanonicalizePath internal (test project already has
InternalsVisibleTo) and added AllowedRootBoundaryTests (exact/child/out-of-root/
sibling-prefix/drive-root/cross-drive/case/trailing-sep/normalize-trim + a non-admin
mklink /J junction regression). Added a behavioral lsp-smoke case: a document under
os.tmpdir (outside the sole allowedRoot) gets no project-backed resolution while the
in-root fixture still resolves.

G13 (MEDIUM UX): the degraded-notification guard was a single boolean reset only on a
successful start, so after a user granted trust but the server still could not start
(e.g. no dotnet) the server-missing guidance was suppressed. Fix: track the last
degraded cause and notify once per distinct cause (untrusted vs server), still
non-nagging; reset on successful start.

G14 (LOW docs): README used the abbreviated server.dotnetPath; use the full
winui-xaml.server.dotnetPath.

Full integration 1605 passing / 0 failing; server 505, unit 595, smoke green;
server debug build 0 warnings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
…trust boundary

reviewer-iter5 (independent cross-model) found two HIGH residuals in the iter5
allow-list hardening. Both are closed here.

G15 (security) — the canonicalizer only resolved reparse points for an EXISTING
leaf (CreateFileW/OPEN_EXISTING). A not-yet-created XAML leaf under an in-root
junction (e.g. C:\trusted\link\Page.xaml, no file) failed that call and fell back
to the lexical path, which still passed lexical containment. XamlProjectResolver
.FindOwningProject only needs the DIRECTORY (new FileInfo(path).Directory +
dir.GetFiles("*.csproj")), so the external junction target's .csproj was still
MSBuild-evaluated — a code-exec bypass. CanonicalizePath now walks up to the
deepest EXISTING ancestor (which follows any junction/symlink in the ancestry),
resolves it via GetFinalPathNameByHandle, and re-appends the missing tail; lexical
fallback is used only when no ancestor exists or the OS call fails. Legit
not-yet-saved files under a real (non-junction) dir still resolve in-root, so
IntelliSense for new files is preserved.

G12 (test-coverage) — the junction regression test never created the linked file,
so it exercised the same missing-leaf fallback, and its Assert.False threw inside a
broad catch(Exception) that swallowed the failure: a green test over a live hole.
The test now creates the linked file and asserts BOTH the existing-leaf and the
missing-leaf paths canonicalize OUTSIDE the trusted root, with the assertions moved
out of the swallowing catch (it skips only on genuine junction-creation failure).
The out-of-root lsp-smoke case gains a sibling .csproj + code-behind so the null F12
result is attributable to the trust gate rather than to an absent project.

Verification: build:server:debug 0 warnings/0 errors; test:server 505/505 (junction
test genuinely exercised — junctions confirmed creatable in this env);
test:xaml-smoke PASS incl. the strengthened out-of-root case; full integration
1605 passing / 0 failing / 28 pending.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
- test:xaml-smoke now runs restore:fixture before the smoke harness so
  element completion has WinUI SDK metadata in CI. The fixture's obj/ is
  gitignored and was previously only restored by the integration pretest,
  so the standalone smoke step failed in CI with 0 completion items. This
  unblocks build-and-test and the packaged VSIX artifact.
- redteam-gap4.test.js: use replaceAll for the caret-marker strip
  (CodeQL "incomplete string escaping or encoding").
- redteam19.test.js: drop the no-op self-replacement
  (CodeQL "replacement of a substring with itself").
- pr-description.yml: tolerate the REST diff API's 20k-line cap (HTTP 406)
  on very large PRs instead of failing; fall back to the changed-file list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
CodeQL's full scan of the migrated test files (features.test.js, smoke.mjs)
flagged sanitization patterns in test helpers. All fixes are behavior
preserving (smoke PASS; integration 1605 passing / 0 failing):

- caret-marker strip: .replace("|", "") -> .replaceAll("|", "")
  (js/incomplete-sanitization).
- exact completion-label membership: array.includes(<uri>) ->
  array.some((l) => l === <uri>) — these were array-membership checks that
  CodeQL misread as URL substring checks (js/incomplete-url-substring-sanitization).
- diagnostic display string: drop the nested template literal in favor of
  string concatenation (js/bad-code-sanitization).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03c713cc-41ae-4c28-ab49-3c851aa8e2bd
Build local native bundles, consume signed release artifacts, harden workspace boundaries and lifecycle behavior, and validate both packaged architectures.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Keep bundled mode fail-closed for fallback resolution while allowing authoritative custom path settings to exercise degradation and recovery. Avoid secondary artifact-upload failures when an earlier CI gate fails.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Keep the canonical x:Bind diagnostics in redteam15 and clarify that the prefix-sharing resource tests exercise references and document highlights separately.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Add merged resource traversal, semantic binding diagnostics, namespace cleanup, on-type formatting, snippets, and surround-with refactors with full server and live VS Code coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Distinguish language-server completions from VS Code word suggestions and align resource and attached-property assertions with current behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Shorten comments, split oversized server and test files, remove unrelated release and CLI downloader changes, and rename red-team tests by scenario.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Remove static color, font-weight, and theme-resource tables; source completion data from project metadata and generic.xaml. Also remove standalone snippets, Surround With, and user-facing server path overrides.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d48e3dcd-70ba-416d-b66e-e5f046facd10
Build graph keys and source edges from ResourceScopeIndex root visibility so keyed nested dictionaries and child resource owners cannot leak globally. Preserve merged and theme dictionary traversal and runtime precedence, with cross-document completion and diagnostic coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e68b9365-64fc-4da1-8c62-c9a2c0c10329
Treat a resource file's root ResourceDictionary Source as a visible graph edge without reopening traversal through keyed or child scopes. Add focused and live App.xaml proxy-chain coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e68b9365-64fc-4da1-8c62-c9a2c0c10329
Derive resource definition locations from the fixture and align smoke expectations with authoritative diagnostics and DependencyObject-only TargetType completion. Keep watcher invalidation targeted at SmokePage after additional fixture types.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e9f981c-7b33-4ddf-926e-45668fc6b0a2
Update live tests for referenced namespace discovery, scoped StaticResource ordering, prefixed property elements, DependencyObject-only TargetType completion, and fixture-derived resource locations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e9f981c-7b33-4ddf-926e-45668fc6b0a2
Address the remaining battle-test and PR-review findings across binding accessibility, static paths, diagnostics, code actions, reload resilience, generated-handler safety, and isolated live testing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e9f981c-7b33-4ddf-926e-45668fc6b0a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e9f981c-7b33-4ddf-926e-45668fc6b0a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e9f981c-7b33-4ddf-926e-45668fc6b0a2
Fixes from the pr-review skill loop:

- Resolve the .NET host explicitly before running package restore instead of
  spawning a bare `dotnet`, and surface an actionable error when no compatible
  runtime is found (xamlLanguageService.ts).
- Evict in-flight project loads on Invalidate so a restore that completes while
  a load is pending no longer leaves a stale workspace cached
  (XamlProjectResolver.cs).
- Drop the pinned --configfile from restore:fixture so the fixture restore
  honors the machine NuGet configuration.
- Ignore .drive-extensions/ test-harness output.
- Mention the XAML language service in the extension description.
- Comment the pre-apply document re-check in promptedTextEdit so it is not
  mistaken for a duplicated guard.

Remaining review findings are tracked as issues #210-#214.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Follow-up self-review of 6de338c caught three problems it introduced:

- Revert the unscoped `!IsCompleted` clause in XamlProjectResolver.Invalidate.
  It was not scoped to the invalidated key, so it evicted every in-flight load
  across all roots and disposed workspaces that active ResolveAsync callers
  were still awaiting. The narrow staleness gap it aimed at is tracked in #215.
- Only set DOTNET_HOST_PATH when the resolved dotnet path is absolute.
  findCompatibleDotnet can return the bare "dotnet" PATH fallback, and
  DOTNET_HOST_PATH must be an absolute path to the host executable.
- Restore the --configfile pin on restore:fixture. The fixture lives outside
  server/, so NuGet hierarchy discovery never finds server/nuget.config; the
  pin is the only thing keeping that restore deterministic in CI.

Also corrects the comment on the pre-apply re-check in promptedTextEdit: there
is no await between it and the preceding guard, so the live re-read that
actually protects the file is the one in the applyEdit dependency.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
findCompatibleDotnet can return the bare dotnet PATH fallback. The restore
path was already guarded; apply the same check when launching the server so the
Roslyn build host never receives a non-absolute DOTNET_HOST_PATH.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Any workspace containing a .xaml.cs also contains the .xaml it is code-behind for, so workspaceContains:**/*.xaml already activates the extension everywhere the C# Dev Kit recommendation can pass its .xaml.cs gate. onLanguage:csharp only added activation in C# workspaces where the gate always returns false, loading the whole extension to test a filename suffix and do nothing.

Replace the package.json string assertion in xaml-workspace-activation.test.ts with tests that state the invariant: activate for XAML-containing workspaces, do not activate on plain C#.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
VS Code cannot activate on an individual file open, so the C# Dev Kit recommendation needs a workspaceContains glob to register its handler. Scope that glob to **/*.xaml.cs, which is the reason it exists, instead of **/*.xaml.

Narrows startup activation: C++/WinRT WinUI and XAML-only workspaces no longer activate the extension, and onLanguage:xaml still covers opening XAML there.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
DOTNET_HOST_PATH is the first candidate findCompatibleDotnet probes, so falling through to the bare 'dotnet' PATH fallback means the inherited value was absent or failed the .NET 10 check. Forwarding it unchanged let MSBuild and Roslyn's build host resolve a host we had already rejected -- e.g. DOTNET_HOST_PATH pointing at a .NET 8 host while .NET 10 is on PATH.

Move the rule into createDotnetChildEnvironment so the server-start and restore paths share one implementation, and cover both branches with tests. The guard previously existed only as duplicated inline expressions, which is how the server-start path kept an unguarded assignment after restore was fixed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
The extension hand-rolled .NET host discovery: probe an ordered candidate list with 'dotnet --list-runtimes' and take the first reporting Microsoft.NETCore.App 10.x. That never validated host architecture and never read the Windows registry install records, so it could hand back an x64 host on arm64 Windows and could not see a host installed off PATH.

Delegate to the .NET Install Tool's dotnet.findPath instead, as the C# extension and C# Dev Kit do. The probe is deleted rather than kept as a fallback, so there is one behaviour to reason about.

The request pins 10.0 with versionSpecRequirement 'equal' rather than the 'greater_than_or_equal' the C# extension uses: the Install Tool compares major.minor, so greater_than_or_equal on 10.0 also accepts a .NET 11 host, which cannot run a net10.0 app without cross-major roll-forward. rejectPreviews excludes prerelease runtimes for the same reason.

The tool is installed silently on demand rather than declared in extensionDependencies, which is static and would install it for every WinApp user. Resolution stays behind the trust check, so an untrusted workspace never triggers an install. The resolved host is cached for the session and invalidated by Restart Language Server. A distinct degraded state covers a blocked marketplace, so a user who already has .NET 10 is not told to install it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
The harness launches VS Code with --disable-extensions, so the .NET Install Tool cannot be installed or queried there. With host discovery now delegated to that tool, all 85 integration tests would have failed to start the server.

Add a WINUI_XAML_DOTNET_PATH override that bypasses the Install Tool, alongside the existing WINUI_XAML_SERVER_PATH dev/test hook, and resolve a host in .vscode-test.mjs. The probing stays in test infrastructure rather than shipped code, so users still have a single discovery path.

Verified: test/xaml-integration/smoke.test.js passes end to end against the real server.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
The isolated .drive-extensions changes and the diagnostic code field are
harness fixes with no relationship to the XAML language service, and they
landed on this branch by accident. They now have their own pull request
against main, so revert them here to keep this branch's diff limited to
the language service integration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a32116a-78ec-415a-aa5e-f98a06c74e9d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants