From d0a5731f06bfe4372eadf8bb6e4d8db6598c905b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 19 Mar 2026 12:59:18 +0000 Subject: [PATCH] Fix trailing comments in record types not indented to field level (#2482) Comments appearing after the last field in a record type declaration (e.g., commented-out fields) were being placed at the brace indent level rather than the field indent level. Root cause: in genRecordFields, the ContentBefore trivia of the closing brace (}) is written via genSingleTextNode *after* the unindent that ends the field block. This means the WriterModel's current indent level is at the brace level (e.g. column 4) rather than the field level (e.g. column 8) when the comments are emitted. Fix: call enterNode closingBrace *inside* the indent block so that the comments are written at field-level indent. Then, after unindent, fix the pre-committed blank line that the last comment's trailing newline created at the wrong (field) indent level, replacing it with a correctly-indented blank line at the brace level. The closing brace is then written without re-emitting ContentBefore (using recordCursorNode + leaveNode directly). This affects the aligned and Stroustrup MultilineBracketStyle modes (the cramped mode uses a separate code path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SynTypeDefnSimpleReprRecordTests.fs | 24 ++++++++++++++++ .../TypeDeclarationTests.fs | 26 +++++++++++++++++ src/Fantomas.Core/CodePrinter.fs | 28 +++++++++++++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/Fantomas.Core.Tests/Stroustrup/SynTypeDefnSimpleReprRecordTests.fs b/src/Fantomas.Core.Tests/Stroustrup/SynTypeDefnSimpleReprRecordTests.fs index e3f7356446..0512022cbd 100644 --- a/src/Fantomas.Core.Tests/Stroustrup/SynTypeDefnSimpleReprRecordTests.fs +++ b/src/Fantomas.Core.Tests/Stroustrup/SynTypeDefnSimpleReprRecordTests.fs @@ -360,3 +360,27 @@ type Event = Metadata: AssessmentMetadata } """ + +[] +let ``commented-out fields inside record type are indented at field level in Stroustrup mode, 2482`` () = + formatSourceString + """ +type UserInfo = { + UserId: int + AcsId: int + // Roles: Role list + // NetworkStatus: UserStatus +} + """ + config + |> prepend newline + |> should + equal + """ +type UserInfo = { + UserId: int + AcsId: int + // Roles: Role list + // NetworkStatus: UserStatus +} +""" diff --git a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs index 92943e771f..4d7ce059ea 100644 --- a/src/Fantomas.Core.Tests/TypeDeclarationTests.fs +++ b/src/Fantomas.Core.Tests/TypeDeclarationTests.fs @@ -3692,3 +3692,29 @@ type X // oh dear 23 """ + +[] +let ``commented-out fields inside record type are indented at field level, 2482`` () = + formatSourceString + """ +type UserInfo = + { + UserId: int + AcsId: int + // Roles: Role list + // NetworkStatus: UserStatus + } + """ + config + |> prepend newline + |> should + equal + """ +type UserInfo = + { + UserId: int + AcsId: int + // Roles: Role list + // NetworkStatus: UserStatus + } +""" diff --git a/src/Fantomas.Core/CodePrinter.fs b/src/Fantomas.Core/CodePrinter.fs index f92edf16a5..2a4d5fb992 100644 --- a/src/Fantomas.Core/CodePrinter.fs +++ b/src/Fantomas.Core/CodePrinter.fs @@ -3580,10 +3580,34 @@ let genTypeDefn (td: TypeDefn) = let multilineExpression (ctx: Context) = let genRecordFields = + let closingBrace = node.ClosingBrace + genSingleTextNode node.OpeningBrace - +> indentSepNlnUnindent (atCurrentColumn (col sepNln node.Fields genField)) + +> indent +> sepNln - +> genSingleTextNode node.ClosingBrace + +> atCurrentColumn (col sepNln node.Fields genField) + // Write any ContentBefore trivia (e.g., commented-out fields) inside the + // indent block so they align with the record fields rather than the braces. + +> enterNode closingBrace + +> unindent + +> (fun ctx -> + // After writing ContentBefore inside the indent block, the last trivia's + // trailing newline pre-commits a blank line at field-level indent. After + // unindent we fix that blank line to use brace-level indent so that the + // closing brace lands at the correct column. + match ctx.WriterModel.Lines with + | head :: rest when head.Trim() = "" -> + let targetIndent = ctx.WriterModel.Indent + + { ctx with + WriterModel = + { ctx.WriterModel with + Lines = String.replicate targetIndent " " :: rest + Column = targetIndent } } + | _ -> ctx) + +> sepNlnUnlessLastEventIsNewline + +> recordCursorNode (!-closingBrace.Text) closingBrace + +> leaveNode closingBrace let genMembers = onlyIf hasMembers (sepNln +> sepNlnBetweenTypeAndMembers typeDefnNode +> genMemberDefnList members)