Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support directives around attributes on "and" type #2631

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixed
* Indenting problem with `match` workaround for single-line stroustrup expressions [#2586](https://github.com/fsprojects/fantomas/issues/2586)
* System.Exception: Fantomas is trying to format the input multiple times due to the detect of multiple defines. [#628](https://github.com/fsprojects/fantomas/issues/628).

## [5.1.3] - 2022-11-14

Expand Down
82 changes: 82 additions & 0 deletions src/Fantomas.Core.Tests/CompilerDirectivesTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2983,3 +2983,85 @@ let ``dead code in active block`` () =
b
#endif
"""

[<Test>]
let ``dont delete #if attr in "and" type, 628`` () =
formatSourceStringWithDefines
[ "NET2" ]
"""
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
[<NoComparison>]
type internal T1 = T1
and [<NoComparison>]
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
internal T2 = T2
"""
config
|> prepend newline
|> should
equal
"""
#if NET2
[<Attr1>]
#else
#endif
[<NoComparison>]
type internal T1 = T1

and [<NoComparison>]
#if NET2
[<Attr1>]
#else
#endif
internal T2 = T2
"""

[<Test>]
let ``attributes with directives in "and" type, 628`` () =
formatSourceString
false
"""
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
[<NoComparison>]
type internal T1 = T1
and [<NoComparison>]
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
internal T2 = T2
"""
config
|> prepend newline
|> should
equal
"""
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
[<NoComparison>]
type internal T1 = T1

and [<NoComparison>]
#if NET2
[<Attr1>]
#else
[<Attr2>]
#endif
internal T2 = T2
"""
14 changes: 9 additions & 5 deletions src/Fantomas.Core/CodePrinter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,14 @@ and genOnelinerAttributes ats =
/// Try to group attributes if they are on the same line
/// Separate same-line attributes by ';'
/// Each bucket is printed in a different line
and genAttributes (ats: SynAttributes) =
colPost sepNlnUnlessLastEventIsNewline sepNln ats (fun a ->
(genAttributesCore a.Attributes |> genTriviaFor SynAttributeList_ a.Range)
+> sepNlnWhenWriteBeforeNewlineNotEmpty)
and genAttributes' nlnAfterOneliner (ats: SynAttributes) =
let addNln = nlnAfterOneliner || ats.Length > 1

colPost (ifElse addNln sepNlnUnlessLastEventIsNewline sepSpace) sepNln ats (fun a ->
let expr = genAttributesCore a.Attributes |> genTriviaFor SynAttributeList_ a.Range
expr)

and genAttributes (ats: SynAttributes) = genAttributes' true ats

and genPreXmlDoc (PreXmlDoc(lines, _)) =
colPost sepNln sepNln lines (sprintf "///%s" >> (!-))
Expand Down Expand Up @@ -2916,7 +2920,7 @@ and genTypeDefn (TypeDef(ats, px, leadingKeyword, ao, tds, tcs, equalsRange, tdr
let genLeadingKeyword =
match leadingKeyword with
| SynTypeDefnLeadingKeyword.Type mType -> genAttributes ats +> genTriviaFor SynTypeDefn_Type mType !- "type "
| SynTypeDefnLeadingKeyword.And mAnd -> genTriviaFor SynTypeDefn_And mAnd !- "and " +> genOnelinerAttributes ats
| SynTypeDefnLeadingKeyword.And mAnd -> genTriviaFor SynTypeDefn_And mAnd !- "and " +> genAttributes' false ats
| SynTypeDefnLeadingKeyword.StaticType _
| SynTypeDefnLeadingKeyword.Synthetic -> sepNone

Expand Down
13 changes: 10 additions & 3 deletions src/Fantomas.Core/Context.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ let sepSemi (ctx: Context) =
let ifAlignBrackets f g =
ifElseCtx (fun ctx -> ctx.Config.MultilineBlockBracketsOnSameColumn) f g

let printTriviaContent (c: TriviaContent) (ctx: Context) =
let printTriviaContent (c: TriviaContent) addBefore (ctx: Context) =
let currentLastLine = ctx.WriterModel.Lines |> List.tryHead

// Some items like #if or Newline should be printed on a newline
Expand Down Expand Up @@ -1073,11 +1073,18 @@ let printTriviaContent (c: TriviaContent) (ctx: Context) =
+> ifElse after sepNlnForTrivia sepNone
| Newline -> (ifElse addNewline (sepNlnForTrivia +> sepNlnForTrivia) sepNlnForTrivia)
| Directive s
| Comment(CommentOnSingleLine s) -> (ifElse addNewline sepNlnForTrivia sepNone) +> !-s +> sepNlnForTrivia
| Comment(CommentOnSingleLine s) ->
(ifElse addNewline sepNlnForTrivia sepNone)
+> !-s
+> (ifElse addBefore sepNlnForTrivia sepNone)
<| ctx

let printTriviaInstructions (triviaInstructions: TriviaInstruction list) =
col sepNone triviaInstructions (fun { Trivia = trivia } -> printTriviaContent trivia.Item)
col
sepNone
triviaInstructions
(fun { Trivia = trivia
AddBefore = addBefore } -> printTriviaContent trivia.Item addBefore)

let enterNodeFor (mainNodeName: FsAstType) (range: Range) (ctx: Context) =
match Map.tryFind mainNodeName ctx.TriviaBefore with
Expand Down
39 changes: 29 additions & 10 deletions src/Fantomas.Core/Trivia.fs
Original file line number Diff line number Diff line change
Expand Up @@ -246,22 +246,41 @@ let triviaBeforeOrAfterEntireTree (rootNode: TriviaNode) (trivia: Trivia) : Triv
AddBefore = isBefore }

/// Try to put the trivia on top of the closest node
/// (or after closest node before in some special cases)
/// If that didn't work put it after the last node
let simpleTriviaToTriviaInstruction (containerNode: TriviaNode) (trivia: Trivia) : TriviaInstruction option =
containerNode.Children
|> Array.tryFind (fun node -> node.Range.StartLine > trivia.Range.StartLine)
|> Option.map (fun node ->
let childrenTypes = containerNode.Children |> Array.map (fun n -> n.Type)

let mkTriviaNode addBefore (node: TriviaNode) =
{ Trivia = trivia
Type = node.Type
Range = node.Range
AddBefore = true })
AddBefore = addBefore }

let findChildNodeToAddAfter mainType prevType =
containerNode.Children
|> Array.indexed
|> Array.rev
|> Seq.tryFind (fun (i, node) ->
node.Type = mainType
&& node.Range.StartLine < trivia.Range.StartLine
&& childrenTypes[0 .. i - 1] |> Array.contains prevType)
|> Option.map snd

let addAfterNode =
match trivia.Item with
| Directive _ ->
// link directive after attribute for "... and ..." type
findChildNodeToAddAfter FsAstType.SynAttributeList_ FsAstType.SynTypeDefn_And
| _ -> None

addAfterNode
|> Option.map (mkTriviaNode false)
|> Option.orElseWith (fun () ->
Array.tryLast containerNode.Children
|> Option.map (fun node ->
{ Trivia = trivia
Type = node.Type
Range = node.Range
AddBefore = false }))
containerNode.Children
|> Array.tryFind (fun node -> node.Range.StartLine > trivia.Range.StartLine)
|> Option.map (mkTriviaNode true)
|> Option.orElseWith (fun () -> Array.tryLast containerNode.Children |> Option.map (mkTriviaNode false)))

/// Try and find the smallest possible node
let lineCommentAfterSourceCodeToTriviaInstruction
Expand Down