Skip to content

Enhance RenameHandler to handle null capabilities gracefully and add tests for registration options#2296

Open
mgreenegit wants to merge 3 commits into
PowerShell:mainfrom
mgreenegit:renamehandler-0
Open

Enhance RenameHandler to handle null capabilities gracefully and add tests for registration options#2296
mgreenegit wants to merge 3 commits into
PowerShell:mainfrom
mgreenegit:renamehandler-0

Conversation

@mgreenegit
Copy link
Copy Markdown
Member

PR Summary

This was discovered and recommended by Copilot while working on a separate effort. Human review and discretion requested.

Microsoft.PowerShell.EditorServices.Handlers.PrepareRenameHandler.GetRegistrationOptions
(line 23) and RenameHandler.GetRegistrationOptions (line 39)
dereferenced capability.PrepareSupport without null-checking
capability. The OmniSharp framework passes a null RenameCapability
to these methods when the client's initialize request doesn't
advertise anything under textDocument.rename, causing PSES to throw
NullReferenceException during initialize.

This blocks every minimal LSP client that doesn't speak rename — for
example a completion-only client, or any client that opts in only to
the capabilities it actually consumes.

Fix

Two parts, applied identically to both handlers:

  1. Make the parameter nullable (RenameCapability?) so the signature
    matches the real OmniSharp runtime contract.
  2. Use the null-conditional operator so a null capability is treated
    the same as "PrepareSupport not advertised".

Test

A single parameterized xUnit [Theory],
GetRegistrationOptionsReflectsPrepareSupport, in
test/PowerShellEditorServices.Test/Refactoring/RenameHandlerTests.cs
covers both handlers and both capability states.

Reviewer FAQ

Q: Is this a regression from a previous PSES version?

No — this code has been present since the rename handler shipped
in v4.6.0 (commit d2112c21, PR #2292). The bug is triggered by
client capability shapes that mainstream clients (vscode-powershell,
Pester, Neovim's bundled config) happen to all populate, so it has
gone unnoticed.

Q: Doesn't annotating the override RenameCapability? break the
interface contract / emit CS8767?

No. The OmniSharp interfaces (IRenameHandler /
IPrepareRenameHandler) declare the parameter as non-nullable
RenameCapability. Implementing an interface method with a more
permissive
(nullable) parameter is contravariantly safe — the
override accepts a superset of inputs — and the C# compiler allows it
without warning. CS8767 fires only in the opposite direction
(declaring non-nullable where the interface says nullable). Verified:
the project builds with 0 warnings / 0 errors. The annotation now
documents the real OmniSharp runtime contract under nullable analysis,
which is preferable to an [AllowNull] attribute or a warning
suppression.

Q: Assert.Equal(false, opts.PrepareProvider) for the unsupported
case — could PrepareProvider be a nullable bool? left as null?

No. RenameRegistrationOptions.PrepareProvider is a non-nullable
System.Boolean (confirmed by reflection on
OmniSharp.Extensions.LanguageProtocol.dll), so new() defaults it to
false. Asserting strict equality against false is correct and is a
stronger check than == true, which would not distinguish false from
a hypothetical null.

Q: Is there a corresponding upstream OmniSharp issue?

The OmniSharp.Extensions framework's contract is that
GetRegistrationOptions may receive a null capability. PSES (and
other consumers) need to honor that contract. Not an OmniSharp bug.


Filed by — see commit history. Co-authored-by Copilot.

Copilot AI review requested due to automatic review settings June 4, 2026 22:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR hardens rename/prepare-rename LSP registration against clients that omit textDocument.rename capabilities, preventing null dereferences during server initialization, and adds tests to validate the registration behavior.

Changes:

  • Make GetRegistrationOptions null-safe when RenameCapability is omitted by the client.
  • Add unit tests covering rename vs prepare-rename handlers’ registration options based on PrepareSupport.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
test/PowerShellEditorServices.Test/Refactoring/RenameHandlerTests.cs Adds theory-driven coverage for registration options when RenameCapability is null or present.
src/PowerShellEditorServices/Services/TextDocument/Handlers/RenameHandler.cs Handles null RenameCapability safely via null-conditional checks to avoid initialization crashes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/PowerShellEditorServices/Services/TextDocument/Handlers/RenameHandler.cs Outdated
Comment thread src/PowerShellEditorServices/Services/TextDocument/Handlers/RenameHandler.cs Outdated
…alse capability

- Revert GetRegistrationOptions parameter to the non-nullable RenameCapability
  declared by IPrepareRenameHandler/IRenameHandler, and rely on the
  null-conditional check (capability?.PrepareSupport == true) for the runtime
  case where the framework passes a null capability. This matches the OmniSharp
  interface signature exactly, avoiding any nullable-annotation mismatch
  (e.g. CS8765) while remaining null-safe during initialize.
- Add explicit { false, false } theory cases for both handler kinds so the
  three distinct PrepareSupport inputs (omitted/null, true, false) are all
  covered and a regression that treats false as enabled would be caught.

Verified: src and test projects build with 0 warnings under #nullable enable;
all 6 GetRegistrationOptionsReflectsPrepareSupport cases pass on net462 and net8.0.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants