Skip to content

Refactoring to convert between a namespace with one nested module and a root-level module - #20536

Open
xperiandri wants to merge 4 commits into
dotnet:mainfrom
xperiandri:feature/namespace-module-refactoring
Open

xperiandri wants to merge 4 commits into
dotnet:mainfrom
xperiandri:feature/namespace-module-refactoring

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a refactoring (Ctrl+., caret on the header line) that converts a file written as namespace A.B with a single nested module C = into a root-level module A.B.C, and a root-level module A.B.C back into namespace A.B with a nested module C =.

The compiler already lowers module [rec] A.B.M into namespace [rec] A.B + module M = internally, so the two forms are the same module by construction; the refactoring only rewrites the header and re-indents the body by line, the same technique Roslyn's block ⟷ file-scoped namespace conversion uses (no formatter call, deliberately).

Applicability, nested → root: exactly one namespace in the file, whose declarations are a single nested module, optionally preceded by opens (no type, no second module, no open after the module — moving those would change their compiled name or what they resolve against). The leading opens move to just below the new module A.B.C header, since a root-style module declaration must come first in the file; comments and XML doc between the namespace and the module stay in front of the new header. rec on the namespace without rec on the module becomes module rec A.B.C. Root → nested is the mirror. Both directions refuse when the file has a paired .fsi: converting only the implementation would unpair it from its signature by QualifiedNameOfFile, which the compiler reports as FS0240. Attributes, XML doc and accessibility on the module stay attached to it; lines inside multi-line string literals are never re-indented.

Covered cases

Converts

  • namespace My.Company + module private Helpers =module private My.Company.Helpers, both directions, with the caret on the namespace or the module line; covers an XML doc comment, [<AutoOpen; RequireQualifiedAccess>], let inline and a multi-line triple-quoted string whose lines are not re-indented
  • rec on the namespace, the module or both: namespace rec A.B / module rec C =module rec A.B.C
  • A comment or XML doc comment before the nested module stays in front of the new header: // Helpers for B. above module A.B.C
  • #if DEBUG / #endif inside the body are dedented along with it
  • opens between the namespace and the module move below the new header: namespace A.B + open System + module C =module A.B.C followed by open System; also with several opens, a doc comment on the module, and a comment above the opens (it stays above the header and the opens are not left behind)
  • module A.B.Cnamespace A.B + module C = with the body indented and blank lines left empty
  • module rec A.B.Cmodule rec C =; header attributes and accessibility are kept: module [<AutoOpen>] internal A.B.Cmodule [<AutoOpen>] internal C =
  • Backticked namespace and a trailing header comment: module ``A-B``.C // headernamespace ``A-B`` + module C = // header
  • CRLF kept in both directions: namespace A.B\r\n\r\nmodule C =\r\n let x = 1\r\nmodule A.B.C\r\nlet x = 1\r\n
  • Round trip: module A.B.C converted to a nested module and back gives the original text

Not offered

  • The namespace holds more than one module (module C =module D =) or no module (type T = int)
  • namespace global with a nested module
  • A single-segment root module: module C
  • A module written as module C = begin … end
  • A comment after the namespace name: namespace A.B // B
  • Caret outside the header lines, e.g. on let x = 1, in either shape
  • An empty nested module: module C = with no declarations
  • An open after the module, or a declaration other than open before it (type T = int)
  • The implementation file has a signature file

Checklist

  • Test cases added
  • Performance benchmarks added in case of performance changes (not applicable)
  • Release notes entry updated

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`vsintegration/src` docs/release-notes/.VisualStudio/18.vNext.md

@xperiandri
xperiandri force-pushed the feature/namespace-module-refactoring branch from e51d4cd to 7863f7e Compare September 14, 2026 11:09
@xperiandri
xperiandri marked this pull request as ready for review September 14, 2026 19:40
@xperiandri
xperiandri requested a review from a team as a code owner September 14, 2026 19:40
xperiandri and others added 4 commits September 14, 2026 21:46
…ot-level module

With the caret on the header, 'namespace A.B' holding a single 'module C =' converts to 'module A.B.C', and 'module A.B.C' converts back. The compiler treats the two forms as the same module, so the edit only rewrites the header and moves the body by the nested module's indentation; lines inside multi-line string literals keep their text. Files with a signature file are not offered the conversion, because converting one side would unpair it from the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The namespace line and the opens were deleted as one block ending at the
first kept line, so a comment between the namespace and the opens left
the opens in place while copies were added under the new header. Each
of them is now deleted on its own, with the blank lines after it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e of its section

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feature/namespace-module-refactoring branch from 5f7ae33 to afc3127 Compare September 14, 2026 19:46
@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Design-Time
Affects-Design-Time: Modifies Visual Studio code executed while projects are open.

Generated by PR Tooling Safety Check · gpt56 1.8M ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

1 participant