Repository navigation
Razor: Pages and MVC, F# in Razor, editing features, collocated files, WebAssembly debugging and Hot Reload - #422
Merged
Conversation
…e completion Legacy tag helper elements and attributes become markup facts, with names read where the compiler gives no span; @addTagHelper, @removeTagHelper and @tagHelperPrefix scope MVC attribute completion. Builds; tests not yet run.
…, WebAssembly debugging and Hot Reload Completes the Razor plan: - Razor Pages and MVC views: tag helper hover, definition, completion and scope; @model, @Inject and @functions through the projection. - F# in Razor solutions: hover with F# docs, definition into the F# source, and references and rename across F#, C# and Razor, found by assembly and XML doc identity. - Editing features from Razor positions: selection ranges, code actions (add @using, generate a handler, extract to code-behind or a component), inlay hints, code lens, and call and type hierarchy. C# hierarchy kinds are PascalCase, as the host maps them. - Collocated files: .razor.css classes complete as LSP values and define to their selectors, an "import" call defines to its .razor.js, and VS Code nests the files. The attribute at a caret is read by the HTML tokenizer. - Blazor WebAssembly launches through the sharplsp-blazorwasm debugger and js-debug. - Razor Hot Reload: a Razor save swaps the additional document in every framework copy. A re-save of committed text answers applied with nothing to apply, for .cs saves too. - Review fixes: the host maps the Value completion kind, the F# Razor e2e builds its F# library, and fixture copies and hierarchy runners are shared.
MelbourneDeveloper
enabled auto-merge (squash)
October 21, 2026 05:13
…t new advisories RUSTSEC-2026-0382 (rustls-webpki 0.103.15 -> 0.103.16), RUSTSEC-2026-0381 (webpki-roots 1.0.9 -> 1.0.10), and GHSA-vfgh-jcmw-g37q / GHSA-gfr2-rv44-r23c (markdown-it 14.3.2 -> 14.3.3 under Eleventy) failed the dependency audit.
On Windows the rename's edit for Scores.fs and the document the test opened spell the drive letter differently, so a lookup by Uri.path found no edits. Key the edits by comparablePath(fsPath), merging spellings of one file, and assert exactly two files are edited.
MelbourneDeveloper
added a commit
that referenced
this pull request
Oct 26, 2026
1 task
MelbourneDeveloper
added a commit
that referenced
this pull request
Oct 27, 2026
…ate all branches (#494) Consolidates every open SharpLsp branch into one, per the one-feature-branch rule. ## What's in it - **Test Explorer (#429):** test builds default to Debug and build only the selected project graph, incrementally. The tree refreshes after builds, and every test host a debug run owns is stopped. Reactive discovery contains failures: a target that cannot be listed becomes an error row with a logged warning, never an unhandled rejection. - **Diagnostics:** solution-wide accuracy, freshness, cancellation and reactive refresh for C# and F#, with restore convergence, sidecar-restart recovery, severity and per-file limits and project filters. No result reports clean before the compiler is ready (#471, #472). - **Analyzer diagnostics (#343):** IDE code-style rules and external analyzers such as StyleCop now report in the editor. Each diagnostic carries its rule's documentation link (`codeDescription`), in C# and in F# (FSharpLint). - **Watched-file reloads** run on a background worker, so requests never wait behind a compiler load: - Each compiler reopens the workspace it last opened. A reload never reopens a superseded or settings-replaced selection. - A reload replays the buffers the compiler's previous graph owned, so owners adopted for open files survive, and documents an explicit selection excluded stay excluded. - Restore outputs a compiler's own open wrote are not reloaded again, and a reload never restores again, so restores cannot feed reload storms. - A completed reload or disk sync retires the identities that racing pulls minted. - Only the compilers whose loaded graph a change concerns reload; a project no compiler loaded reloads none. - **Restore gate:** stale assets are restored before a workspace opens. Inputs include inherited props, central package versions, NuGet configuration and `global.json`, including a remembered input that has since been deleted. `dotnet` children resolve the SDK their workspace selects instead of inheriting the C# sidecar's MSBuild registration, and every restore stage is bounded at two minutes. - **F#:** - Wildcard `<Compile>` items come from MSBuild evaluation (#455). - A superseded owner adoption never joins a replacement workspace. - Script diagnostics track current references, completion resolution goes back to FCS, and function symbols are classified. - A design-time build whose project is deleted while it runs is abandoned at once, instead of holding every F# request for three minutes. - **C# owner adoption:** a project loaded for a file outside the workspace loads under the path its restore recorded. Loading it under another spelling (a Windows drive letter in the other case, or a link) rewrote its `obj` inputs, so the user's next build recompiled it. - **Compiler recovery:** a respawned compiler is restored without a selected workspace that no longer exists (a deleted scratch solution), instead of failing and respawning forever, and with it again once it is back. - **Debugger:** a `disconnect` the adapter exits before answering is answered by the router. On Windows netcoredbg exits as its debuggee ends, and VS Code held the dead session until its own timeout. - **Push clients:** a framework switch publishes its diagnostics before it is announced, even when other pushes compete. - **CI:** - The SDK diagnostic suites have their own chunk. - Fixtures build as users' projects, outside SharpLsp's lint policy. - Failing Rust shards keep their sidecar logs, and every VS Code shard keeps the server's and sidecars' logs. - **Merged branches:** - `main`. - `chore/netfx-run-exit-flake`: the .NET Framework run proof, already on this branch. - `feature/397-razor-phase4`: squash-merged into main as #422, then recorded with `-s ours` because its remaining diff was superseded here. - `dependabot-upgrades`: all grouped bumps. - **lspkit:** points at lspkit `main` (`53fd4c6`, Nimblesite/lspkit#30). Pull revisions separate supersession from engine generations, and the .NET profiler refuses non-.NET targets before connecting. An adversarial review of this branch found eight defects (one critical, five high, one medium, one low). Each is fixed here behind a test that failed first. ## Test plan - [ ] CI green on every leg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Completes Razor support (#397): Razor Pages and MVC views, F# symbols in Razor, the editing features (selection, code actions, hints, lenses, hierarchy), collocated
.razor.css/.razor.jsfiles, Blazor WebAssembly debugging and Razor Hot Reload.Closes #397
Details
Razor Pages and MVC ([RAZOR-MVC])
@addTagHelper/@removeTagHelper/@tagHelperPrefixand_ViewImports.cshtml(RazorTagHelperScope.cs); tag helper elements and attributes hover, define and complete, each once beside the HTML service's items (razor-html.tsde-duplicates by label).RazorIr.Attributes.cs,MarkupAttribute.End).F# in Razor solutions ([RAZOR-FSHARP], [DEFINITION-CROSSLANG-IDENTITY], [REFERENCES-CROSS-LANGUAGE])
FSharpProjectSymbols.cs) and reads the built DLL with its XML docs, so hover shows F#///comments.cross_language.rs: a decompiled F# location is replaced by its F# declaration (workspace/declarationForeign), and references merge both sides' uses (workspace/referencesForeign). An answer the F# sidecar cannot place yet is not cached.FSharpForeignSymbols.fs; F# references now sit on the name of a dotted use (player.Score).Editing features from Razor positions
RazorSelectionRanges.cs, hostrazor::handle_selection_ranges(was[]).@using, generate an event handler, extract a block to code-behind, extract elements to a new component (RazorQuickFixes.cs,RazorExtractActions.cs). New files arrive as LSPCreateFileoperations (code_actions.rs).SymbolKind).Collocated files ([RAZOR-COLLOCATION])
.razor.cssclasses complete as LSPValueitems inside aclassvalue and define to every selector (CssTokenizer.cs, a CSS Syntax Level 3 tokenizer, andRazorStylesheet.cs). The attribute at the caret is read by the HTML tokenizer (HtmlMarkupContext.Attributes.cs).InvokeAsync<IJSObjectReference>("import", "…")defines to its.razor.js, from Razor, a code-behind or plain C#.configurationDefaultsnest a component's.cs/.css/.jsunder it. Paths go throughNativePaths.StylesheetOf/FromModulePath.Blazor WebAssembly debugging ([DEBUG-FEATURES-LAUNCH-BLAZOR-WASM])
sharplsp-blazorwasmdebugger (blazor-wasm-debug.ts,blazor-wasm-launch.ts): runsdotnet run, expands the profile'sinspectUrionto the announced address, hands the app to js-debug, and stops the app with the browser session. It declares nolanguages, so the F5 auto-pick stayssharplsp-coreclr.Razor Hot Reload ([RAZOR-HOT-RELOAD])
HotReloadSession.cs). The guard now ignores all trivia, because doc-comment edits were refused as rude edits.notCompilable("save again" forever). Roslyn reports Status None with nothing pending, so it now answersappliedwith no deltas, for.cssaves too.Specs and plan: RAZOR-SPEC gains the sections above and loses its "Not Yet Supported" list. DEFINITION/REFERENCES/RENAME/DEBUGGING specs cover the cross-language identity and the WebAssembly debugger. Every RAZOR-PLAN item is checked, with test evidence.
No new dependencies.
How Do The Automated Tests Prove It Works?
Host end-to-end (real sidecars, real fixture solution)
test_full_stack_razor_pages_and_mvc_views:@model/@injecthover,@functionsdefinition,<input>andasp-forfrom tag helpers,partial/environmentandasp-page(kind 10) completions, andIndex.cshtmlresolving@modelthrough_ViewImports.cshtmlwith no errors.test_full_stack_fsharp_in_razor_session: F# record hover with its docs, definition intoScores.fs, references with and without the declaration, and rename from Razor and from F#. Its workspace now builds the F# library, because C# reads only a built DLL.test_full_stack_razor_selection_ranges,test_full_stack_razor_hierarchy_and_hints: exact nested chains, and hierarchy items and LSP kinds.test_full_stack_razor_collocated_styles_and_scripts: five.titleselectors, each ranged on the name; module definition atCard.razor.js0:0 from@codeand plain C#;titlecompletes once as kind 12, replacing onlyti.test_cross_language_definition_csharp_to_fsharpnow also asserts a C# local's references stay in C# once the F# sidecar is loaded.Sidecar (C# 803/803, F# 457/457, Common 125/125)
RazorMvcTests,RazorFSharpTests,RazorSelectionRangesTests,RazorCodeActionsTests(including whitespace-trimmed, dedented extraction, and refusal when an outside local is captured),RazorHintsAndLensesTests,RazorHierarchyTests,CallHierarchyKindTests.RazorCollocationTests,RazorStylesheetTests, andHtmlMarkupContextTestsfor the attribute at a caret. The last caught three bugs, each failing first: a bare attribute beforeclass, a caret on a name, a caret after a value.RazorHotReloadTests(5): markup and@codebody edits emit real deltas; a mixed.cs+Razor batch applies as one; an@codesignature change isrestartRequirednaming the method; an unlisted file is refused by name. Each fails with the Razor branch disabled.HotReloadSessionRegistryTestsasserts the quiet.csre-save leaves nothing to commit.RazorSidecarTestsdrivestextDocument/selectionRangeandworkspace/referencesForeignover the real socket.FSharpForeignSymbolsTestsandFSharpForeignSymbolsWireTestscover declarations, uses and the wire handlers.VS Code
razor-lsp.test.ts: F# navigation, references and rename from Razor; a Razor Page completes tag helpers once; stylesheet class definition and completion, and the nesting defaults VS Code applies.blazor-wasm-debug.test.ts(a realdotnet runof the WebAssembly fixture) andblazor-wasm-debug-resolution.test.ts(profiles,inspectUri, run arguments, the app stopped with the session, a browser that never starts, an app that dies during the handoff).debug-hot-reload-e2e.test.ts: a saved.razorreaches the sidecar and is refused by name, and the session survives.fsi-build-output-e2e.test.tscovers thedotnet watchpath.Coverage of changed lines (local): C# 2005/2079 before the review's added tests, with the project at 96.5%; F# 100/101; Common 19/19; host 441/447; new WebAssembly files 887/911 (97.4%). What stays uncovered is the real js-debug handoff, the 180 s startup timer and some unreachable
catcharms.