Skip to content

Commit d8f4a1a

Browse files
CopilotT-Gro
andcommitted
Fix off-by-one errors by also handling attributes in top-level modules
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
1 parent 9d82e79 commit d8f4a1a

File tree

2 files changed

+23
-4
lines changed

2 files changed

+23
-4
lines changed

src/Compiler/Service/ServiceParsedInputOps.fs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,7 +2428,7 @@ module ParsedInput =
24282428
List.iter (walkSynModuleOrNamespace []) file.Contents
24292429

24302430
and walkSynModuleOrNamespace (parent: LongIdent) modul =
2431-
let (SynModuleOrNamespace(longId = ident; kind = kind; decls = decls; range = range)) =
2431+
let (SynModuleOrNamespace(longId = ident; kind = kind; decls = decls; range = range; trivia = trivia)) =
24322432
modul
24332433

24342434
if range.EndLine >= currentLine then
@@ -2444,7 +2444,14 @@ module ParsedInput =
24442444

24452445
let fullIdent = parent @ ident
24462446

2447-
let startLine = if isModule then range.StartLine else range.StartLine - 1
2447+
// Use trivia to get the actual module/namespace keyword line, which excludes attributes
2448+
let startLine =
2449+
match trivia.LeadingKeyword with
2450+
| SynModuleOrNamespaceLeadingKeyword.Module moduleRange
2451+
| SynModuleOrNamespaceLeadingKeyword.Namespace moduleRange -> moduleRange.StartLine
2452+
| SynModuleOrNamespaceLeadingKeyword.None ->
2453+
// No keyword (implicit module), use range.StartLine
2454+
if isModule then range.StartLine else range.StartLine - 1
24482455

24492456
let scopeKind =
24502457
match isModule, parent with

vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,27 @@ type internal AddOpenCodeFixProvider [<ImportingConstructor>] (assemblyContentPr
3131
}
3232

3333
// With the fix in ServiceParsedInputOps, the InsertionContext now correctly
34-
// points to the line after the module keyword (or namespace), so we can trust it.
34+
// points to the line after the module/namespace keyword (excluding attributes).
35+
// However, we still need to handle implicit top-level modules and nested modules.
3536
let getOpenDeclaration (sourceText: SourceText) (ctx: InsertionContext) (ns: string) =
3637
// insertion context counts from 2, make the world sane
3738
let insertionLineNumber = ctx.Pos.Line - 2
3839
let margin = String(' ', ctx.Pos.Column)
3940

4041
let startLineNumber, openDeclaration =
4142
match ctx.ScopeKind with
42-
| ScopeKind.TopModule -> insertionLineNumber + 2, $"{margin}open {ns}{br}{br}"
43+
| ScopeKind.TopModule ->
44+
match sourceText.Lines[insertionLineNumber].ToString().Trim() with
45+
46+
// explicit top level module
47+
| line when line.StartsWith "module" && not (line.EndsWith "=") -> insertionLineNumber + 2, $"{margin}open {ns}{br}{br}"
48+
49+
// nested module, shouldn't be here
50+
| line when line.StartsWith "module" -> insertionLineNumber, $"{margin}open {ns}{br}{br}"
51+
52+
// implicit top level module
53+
| _ -> insertionLineNumber, $"{margin}open {ns}{br}{br}"
54+
4355
| ScopeKind.Namespace -> insertionLineNumber + 3, $"{margin}open {ns}{br}{br}"
4456
| ScopeKind.NestedModule -> insertionLineNumber + 2, $"{margin}open {ns}{br}{br}"
4557
| ScopeKind.OpenDeclaration -> insertionLineNumber + 1, $"{margin}open {ns}{br}"

0 commit comments

Comments
 (0)