diff --git a/src/Fantomas.Tests/AttributeTests.fs b/src/Fantomas.Tests/AttributeTests.fs index 137750753a..5cbc9356c3 100644 --- a/src/Fantomas.Tests/AttributeTests.fs +++ b/src/Fantomas.Tests/AttributeTests.fs @@ -266,9 +266,11 @@ let ``comments before attributes should be added correctly, issue 422`` () = Verified : bool } """ config + |> prepend newline |> should equal - """module RecordTypes = + """ +module RecordTypes = /// Records can also be represented as structs via the 'Struct' attribute. /// This is helpful in situations where the performance of structs outweighs diff --git a/src/Fantomas.Tests/CommentTests.fs b/src/Fantomas.Tests/CommentTests.fs index dbe6daffed..e73c646ed5 100644 --- a/src/Fantomas.Tests/CommentTests.fs +++ b/src/Fantomas.Tests/CommentTests.fs @@ -1252,3 +1252,82 @@ let a = 8 let a = 8 // foobar """ + +[] +let ``file end with newline followed by comment, 1649`` () = + formatSourceString + false + """ +#load "Hi.fsx" +open Something + +//// FOO +[ + 1 + 2 + 3 +] + +//// The end +""" + { config with + SpaceBeforeUppercaseInvocation = true + SpaceBeforeClassConstructor = true + SpaceBeforeMember = true + SpaceBeforeColon = true + SpaceBeforeSemicolon = true + MultilineBlockBracketsOnSameColumn = true + NewlineBetweenTypeDefinitionAndMembers = true + KeepIfThenInSameLine = true + AlignFunctionSignatureToIndentation = true + AlternativeLongMemberDefinitions = true + MultiLineLambdaClosingNewline = true + KeepIndentInBranch = true } + |> prepend newline + |> should + equal + """ +#load "Hi.fsx" +open Something + +//// FOO +[ 1 ; 2 ; 3 ] + +//// The end +""" + +[] +let ``meh 123`` () = + formatSourceString + false + """ +let a = 7 +let b = 8 +// foo +""" + config + |> prepend newline + |> should + equal + """ +let a = 7 + +let b = 8 +// foo +""" + +[] +let ``block comment above let binding`` () = + formatSourceString + false + """(* meh *) +let a = b +""" + config + |> prepend newline + |> should + equal + """ +(* meh *) +let a = b +""" diff --git a/src/Fantomas.Tests/CompilerDirectivesTests.fs b/src/Fantomas.Tests/CompilerDirectivesTests.fs index 096758074f..91ddb1431b 100644 --- a/src/Fantomas.Tests/CompilerDirectivesTests.fs +++ b/src/Fantomas.Tests/CompilerDirectivesTests.fs @@ -16,11 +16,14 @@ SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ #endif """ config + |> prepend newline |> should equal - """#if INTERACTIVE + """ +#if INTERACTIVE #load "../FSharpx.TypeProviders/SetupTesting.fsx" SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ + #load "__setup__.fsx" #endif """ @@ -38,12 +41,42 @@ SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ #endif """ config + |> prepend newline |> should equal - """#if INTERACTIVE + """ +#if INTERACTIVE #else #load "../FSharpx.TypeProviders/SetupTesting.fsx" SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ + +#load "__setup__.fsx" +#endif +""" + +[] +let ``should keep compiler directives, idempotent`` () = + formatSourceString + false + """ +#if INTERACTIVE +#else +#load "../FSharpx.TypeProviders/SetupTesting.fsx" +SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ + +#load "__setup__.fsx" +#endif +""" + config + |> prepend newline + |> should + equal + """ +#if INTERACTIVE +#else +#load "../FSharpx.TypeProviders/SetupTesting.fsx" +SetupTesting.generateSetupScript __SOURCE_DIRECTORY__ + #load "__setup__.fsx" #endif """ @@ -211,7 +244,6 @@ namespace Internal.Utilities.Text.Lexing""" """ #nowarn "47" namespace Internal.Utilities.Text.Lexing - """ [] @@ -1030,6 +1062,23 @@ let foo = 42 #endif """ +[] +let ``no code for inactive define, no defines`` () = + formatSourceStringWithDefines + [] + """#if SOMETHING +let foo = 42 +#endif""" + config + |> prepend newline + |> should + equal + """ +#if SOMETHING + +#endif +""" + [] let ``#if should not be printed twice, #482`` () = formatSourceString diff --git a/src/Fantomas.Tests/SynConstTests.fs b/src/Fantomas.Tests/SynConstTests.fs index 766435edc3..13026c0c5e 100644 --- a/src/Fantomas.Tests/SynConstTests.fs +++ b/src/Fantomas.Tests/SynConstTests.fs @@ -504,3 +504,18 @@ long triple quotes string thing \"\"\" " + +[] +let ``collect keyword string as separate trivia`` () = + formatSourceString + false + """ +__SOURCE_DIRECTORY__ +""" + config + |> prepend newline + |> should + equal + """ +__SOURCE_DIRECTORY__ +""" diff --git a/src/Fantomas.Tests/TriviaTests.fs b/src/Fantomas.Tests/TriviaTests.fs index 4a8cf15cd1..b66f537cd8 100644 --- a/src/Fantomas.Tests/TriviaTests.fs +++ b/src/Fantomas.Tests/TriviaTests.fs @@ -68,9 +68,9 @@ let ``line comment on same line, is after last AST item`` () = let triviaNodes = toTrivia source |> List.head match triviaNodes with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode SynConst_Int32 ContentAfter = [ Comment (LineCommentAfterSourceCode lineComment) ] } ] -> lineComment == "// should be 8" - | _ -> fail () + | _ -> Assert.Fail(sprintf "Unexpected trivia %A" triviaNodes) [] let ``newline pick up before let binding`` () = @@ -316,16 +316,16 @@ doSomething() let withoutDefine = Map.find [] triviaNodes match withoutDefine with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode SynModuleDecl_DoExpr ContentBefore = [ Directive "#if NOT_DEFINED\n#else" ] ContentAfter = [ Directive "#endif" ] } ] -> pass () - | _ -> fail () + | _ -> Assert.Fail(sprintf "Unexpected trivia %A" withoutDefine) match withDefine with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode LongIdent_ ContentBefore = [ Directive "#if NOT_DEFINED"; Directive "#else"; Directive "#endif" ] ContentAfter = [] } ] -> pass () - | _ -> fail () + | _ -> Assert.Fail(sprintf "Unexpected trivia %A" withDefine) [] let ``directive without else clause`` () = @@ -341,19 +341,19 @@ let x = 1 let withoutDefine = Map.find [] triviaNodes match withoutDefine with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode LongIdent_ ContentAfter = [ Directive "#if NOT_DEFINED\n\n#endif" ] ContentBefore = [] } ] -> pass () - | _ -> fail () + | _ -> Assert.Fail(sprintf "Unexpected trivia %A" withoutDefine) match withDefine with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode LongIdent_ ContentBefore = [ Directive "#if NOT_DEFINED" ] ContentAfter = [] }; { Type = MainNode SynModuleDecl_Let ContentBefore = [] ContentAfter = [ Directive "#endif" ] } ] -> pass () - | _ -> fail () + | _ -> Assert.Fail(sprintf "Unexpected trivia %A" withDefine) [] let ``unreachable directive should be present in trivia`` () = @@ -370,7 +370,7 @@ type ExtensibleDumper = A | B let trivias = Map.find [ "DEBUG" ] triviaNodes match trivias with - | [ { Type = MainNode Ident_ + | [ { Type = MainNode LongIdent_ ContentAfter = [ Directive "#if EXTENSIBLE_DUMPER\n#if DEBUG\n\n#endif\n#endif" ] } ] -> pass () | _ -> Assert.Fail(sprintf "Unexpected trivia %A" trivias) @@ -400,7 +400,7 @@ let foo = 42 toTriviaWithDefines source |> Map.find [] match trivia with - | [ { Type = MainNode SynModuleOrNamespace_AnonModule + | [ { Type = MainNode LongIdent_ ContentAfter = [ Directive "#if SOMETHING\n\n#endif" ] } ] -> pass () | _ -> fail () diff --git a/src/Fantomas/AstExtensions.fs b/src/Fantomas/AstExtensions.fs index 272c9f9c74..a534a39904 100644 --- a/src/Fantomas/AstExtensions.fs +++ b/src/Fantomas/AstExtensions.fs @@ -9,3 +9,8 @@ type SynTypeDefnSig with member this.FullRange : Range = match this with | SynTypeDefnSig.TypeDefnSig (comp, _, _, r) -> mkRange r.FileName comp.Range.Start r.End + +let longIdentFullRange (li: LongIdent) : Range = + match li with + | [] -> range.Zero + | h :: _ -> unionRanges h.idRange (List.last li).idRange diff --git a/src/Fantomas/AstTransformer.fs b/src/Fantomas/AstTransformer.fs index 10fd21d494..33aa2b6f39 100644 --- a/src/Fantomas/AstTransformer.fs +++ b/src/Fantomas/AstTransformer.fs @@ -9,13 +9,6 @@ open Fantomas type Id = { Ident: string; Range: Range } module Helpers = - let i (id: Ident) : Id = - { Ident = id.idText - Range = id.idRange } - - let li (id: LongIdent) = id |> List.map i - - let lid (id: LongIdentWithDots) = li id.Lid let mkNode (t: FsAstType) (r: range) = TriviaNodeAssigner(MainNode(t), r) module private Ast = @@ -23,27 +16,13 @@ module private Ast = let rec visitSynModuleOrNamespace (modOrNs: SynModuleOrNamespace) : TriviaNodeAssigner list = match modOrNs with - | SynModuleOrNamespace (longIdent, _, synModuleOrNamespaceKind, decls, _, attrs, _, range) -> - let collectIdents (idents: LongIdent) = - idents - |> List.map (fun ident -> mkNode Ident_ ident.idRange) - - let typeName = - match synModuleOrNamespaceKind with - | SynModuleOrNamespaceKind.AnonModule -> SynModuleOrNamespace_AnonModule - | SynModuleOrNamespaceKind.NamedModule -> SynModuleOrNamespace_NamedModule - | SynModuleOrNamespaceKind.DeclaredNamespace -> SynModuleOrNamespace_DeclaredNamespace - | SynModuleOrNamespaceKind.GlobalNamespace -> SynModuleOrNamespace_GlobalNamespace - - [ - // LongIdent inside Namespace is being processed as children. - if typeName <> SynModuleOrNamespace_DeclaredNamespace then - mkNode typeName range - yield! - if synModuleOrNamespaceKind = SynModuleOrNamespaceKind.DeclaredNamespace then - collectIdents longIdent - else - [] + | SynModuleOrNamespace (longIdent, _, kind, decls, _, attrs, _, range) -> + let longIdentNodes = + match kind, decls with + | SynModuleOrNamespaceKind.AnonModule, _ :: _ -> [] + | _ -> visitLongIdentIncludingFullRange longIdent + + [ yield! longIdentNodes yield! (visitSynAttributeLists range attrs) yield! (decls |> List.collect visitSynModuleDecl) ] @@ -348,7 +327,7 @@ module private Ast = Continuation.sequence continuations finalContinuation | SynExpr.Ident id -> - mkNode SynExpr_Ident (i id).Range + mkNode SynExpr_Ident id.idRange |> List.singleton |> finalContinuation | SynExpr.LongIdent (_, longDotId, _, range) -> @@ -1268,23 +1247,13 @@ module private Ast = and visitSynModuleOrNamespaceSig (modOrNs: SynModuleOrNamespaceSig) : TriviaNodeAssigner list = match modOrNs with - | SynModuleOrNamespaceSig (longIdent, _, synModuleOrNamespaceKind, decls, _, attrs, _, range) -> - let typeName = - match synModuleOrNamespaceKind with - | SynModuleOrNamespaceKind.AnonModule -> SynModuleOrNamespaceSig_AnonModule - | SynModuleOrNamespaceKind.NamedModule -> SynModuleOrNamespaceSig_NamedModule - | SynModuleOrNamespaceKind.DeclaredNamespace -> SynModuleOrNamespaceSig_DeclaredNamespace - | SynModuleOrNamespaceKind.GlobalNamespace -> SynModuleOrNamespaceSig_GlobalNamespace - - [ // LongIdent inside Namespace is being processed as children. - if typeName - <> SynModuleOrNamespaceSig_DeclaredNamespace then - mkNode typeName range - yield! - if synModuleOrNamespaceKind = SynModuleOrNamespaceKind.DeclaredNamespace then - visitLongIdent longIdent - else - [] + | SynModuleOrNamespaceSig (longIdent, _, kind, decls, _, attrs, _, range) -> + let longIdentNodes = + match kind, decls with + | SynModuleOrNamespaceKind.AnonModule, _ :: _ -> [] + | _ -> visitLongIdentIncludingFullRange longIdent + + [ yield! longIdentNodes yield! (visitSynAttributeLists range attrs) yield! (decls |> List.collect visitSynModuleSigDecl) ] @@ -1350,19 +1319,17 @@ module private Ast = match lid with | LongIdentWithDots (ids, _) -> List.map visitIdent ids - and visitLongIdent (li: LongIdent) : TriviaNodeAssigner list = List.map visitIdent li + and visitLongIdentIncludingFullRange (li: LongIdent) : TriviaNodeAssigner list = + // LongIdent is a bit of an artificial AST node + // meant to be used as namespace or module identifier + mkNode LongIdent_ (longIdentFullRange li) + :: List.map visitIdent li and visitIdent (ident: Ident) : TriviaNodeAssigner = mkNode Ident_ ident.idRange let astToNode (hds: ParsedHashDirective list) (mdls: SynModuleOrNamespace list) : TriviaNodeAssigner list = - let children = - [ yield! List.collect Ast.visitSynModuleOrNamespace mdls - yield! List.map Ast.visitParsedHashDirective hds ] - - children + [ yield! List.collect Ast.visitSynModuleOrNamespace mdls + yield! List.map Ast.visitParsedHashDirective hds ] let sigAstToNode (ast: SynModuleOrNamespaceSig list) : TriviaNodeAssigner list = - let children = - List.collect Ast.visitSynModuleOrNamespaceSig ast - - children + List.collect Ast.visitSynModuleOrNamespaceSig ast diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 52e53a04b6..27c2c19f27 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -102,14 +102,9 @@ and genParsedHashDirective (ParsedHashDirective (h, s, r)) = |> List.tryFind (fun t -> RangeHelpers.rangeEq t.Range r) |> Option.bind (fun t -> - t.ContentBefore - |> List.choose - (fun tc -> - match tc with - | Keyword ({ TokenInfo = { TokenName = "KEYWORD_STRING" } - Content = c }) -> Some c - | _ -> None) - |> List.tryHead) + match t.ContentItself with + | Some (KeywordString c) -> Some c + | _ -> None) |> function | Some kw -> !-kw | None -> col sepSpace s printArgument @@ -118,17 +113,19 @@ and genParsedHashDirective (ParsedHashDirective (h, s, r)) = !- "#" -- h +> sepSpace +> printIdent |> genTriviaFor ParsedHashDirective_ r -and genModuleOrNamespace astContext (ModuleOrNamespace (ats, px, ao, s, mds, isRecursive, moduleKind) as node) = +and genModuleOrNamespaceKind (kind: SynModuleOrNamespaceKind) = + match kind with + | SynModuleOrNamespaceKind.DeclaredNamespace -> !- "namespace " + | SynModuleOrNamespaceKind.NamedModule -> !- "module " + | SynModuleOrNamespaceKind.GlobalNamespace -> !- "namespace global" + | SynModuleOrNamespaceKind.AnonModule -> sepNone + +and genModuleOrNamespace astContext (ModuleOrNamespace (ats, px, ao, lids, mds, isRecursive, moduleKind)) = let sepModuleAndFirstDecl = let firstDecl = List.tryHead mds match firstDecl with - | None -> - if moduleKind.IsModule then - sepNlnForEmptyModule SynModuleOrNamespace_NamedModule node.Range - +> sepNln - else - sepNlnForEmptyNamespace node.Range +> sepNln + | None -> sepNone | Some mdl -> let attrs = getRangesFromAttributesFromModuleDeclaration mdl @@ -136,65 +133,38 @@ and genModuleOrNamespace astContext (ModuleOrNamespace (ats, px, ao, s, mds, isR sepNln +> sepNlnConsideringTriviaContentBeforeWithAttributesFor (synModuleDeclToFsAstType mdl) mdl.Range attrs - let genTriviaForLongIdent (f: Context -> Context) = - match node with - | SynModuleOrNamespace.SynModuleOrNamespace (lid, _, SynModuleOrNamespaceKind.DeclaredNamespace, _, _, _, _, _) -> - lid - |> List.fold (fun (acc: Context -> Context) (ident: Ident) -> acc |> (genTriviaFor Ident_ ident.idRange)) f - | _ -> f + let lidsFullRange = + match lids with + | [] -> range.Zero + | (_, r) :: _ -> Range.unionRanges r (List.last lids |> snd) let moduleOrNamespace = - ifElse moduleKind.IsModule (!- "module ") (!- "namespace ") - - let recursive = ifElse isRecursive (!- "rec ") sepNone - let namespaceFn = ifElse (s = "") (!- "global") (!-s) - let namespaceIsGlobal = not moduleKind.IsModule && s = "" - - let sep = - if namespaceIsGlobal then - sepNone - else - sepModuleAndFirstDecl - - let expr = - genPreXmlDoc px - +> genAttributes astContext ats - +> ifElse - (moduleKind = AnonModule) - sepNone - (genTriviaForLongIdent ( - moduleOrNamespace - +> opt sepSpace ao genAccess - +> recursive - +> namespaceFn - +> sep - )) - - if namespaceIsGlobal then - expr - +> genTriviaFor SynModuleOrNamespace_GlobalNamespace node.Range (genModuleDeclList astContext mds) - else - expr +> genModuleDeclList astContext mds - |> (match moduleKind with - | SynModuleOrNamespaceKind.AnonModule -> genTriviaFor SynModuleOrNamespace_AnonModule node.Range - | SynModuleOrNamespaceKind.NamedModule -> genTriviaFor SynModuleOrNamespace_NamedModule node.Range - | _ -> id) + genModuleOrNamespaceKind moduleKind + +> opt sepSpace ao genAccess + +> ifElse isRecursive (!- "rec ") sepNone + +> col (!- ".") lids (fun (lid, r) -> genTriviaFor Ident_ r (!-lid)) + |> genTriviaFor LongIdent_ lidsFullRange + + // Anonymous module do have a single (fixed) ident in the LongIdent + // We don't print the ident but it could have trivia assigned to it. + let genTriviaForAnonModuleIdent = + match lids with + | [ (_, r) ] -> genTriviaFor Ident_ r sepNone + | _ -> sepNone + |> genTriviaFor LongIdent_ lidsFullRange -and genSigModuleOrNamespace astContext (SigModuleOrNamespace (ats, px, ao, s, mds, _, moduleKind) as node) = - let range = - match node with - | SynModuleOrNamespaceSig (_, _, _, _, _, _, _, range) -> range + genPreXmlDoc px + +> genAttributes astContext ats + +> ifElse (moduleKind = AnonModule) genTriviaForAnonModuleIdent moduleOrNamespace + +> sepModuleAndFirstDecl + +> genModuleDeclList astContext mds +and genSigModuleOrNamespace astContext (SigModuleOrNamespace (ats, px, ao, lids, mds, isRecursive, moduleKind)) = let sepModuleAndFirstDecl = let firstDecl = List.tryHead mds match firstDecl with - | None -> - if moduleKind.IsModule then - sepNlnForEmptyModule SynModuleOrNamespaceSig_NamedModule range - +> rep 2 sepNln - else - sepNlnForEmptyNamespace range +> sepNln + | None -> sepNone | Some mdl -> match mdl with | SynModuleSigDecl.Types _ -> @@ -206,31 +176,23 @@ and genSigModuleOrNamespace astContext (SigModuleOrNamespace (ats, px, ao, s, md | _ -> sepNone +> sepNln - let genTriviaForLongIdent (f: Context -> Context) = - match node with - | SynModuleOrNamespaceSig (lid, _, SynModuleOrNamespaceKind.DeclaredNamespace, _, _, _, _, _) -> - lid - |> List.fold (fun (acc: Context -> Context) (ident: Ident) -> acc |> (genTriviaFor Ident_ ident.idRange)) f - | _ -> f + let lidsFullRange = + match lids with + | [] -> range.Zero + | (_, r) :: _ -> Range.unionRanges r (List.last lids |> snd) let moduleOrNamespace = - ifElse moduleKind.IsModule (!- "module ") (!- "namespace ") + genModuleOrNamespaceKind moduleKind + +> opt sepSpace ao genAccess + +> ifElse isRecursive (!- "rec ") sepNone + +> col (!- ".") lids (fun (lid, r) -> genTriviaFor Ident_ r (!-lid)) + |> genTriviaFor LongIdent_ lidsFullRange - // Don't generate trivia before in case the SynModuleOrNamespaceKind is a DeclaredNamespace - // The range of the namespace is not correct, see https://github.com/dotnet/fsharp/issues/7680 - ifElse moduleKind.IsModule (enterNodeFor SynModuleOrNamespaceSig_NamedModule range) sepNone - +> genPreXmlDoc px + genPreXmlDoc px +> genAttributes astContext ats - +> ifElse - (moduleKind = AnonModule) - sepNone - (genTriviaForLongIdent ( - moduleOrNamespace +> opt sepSpace ao genAccess - -- s - +> sepModuleAndFirstDecl - )) + +> ifElse (moduleKind = AnonModule) sepNone moduleOrNamespace + +> sepModuleAndFirstDecl +> genSigModuleDeclList astContext mds - +> leaveNodeFor SynModuleOrNamespaceSig_NamedModule range and genModuleDeclList astContext e = let rec collectItems e : ColMultilineItem list = @@ -1823,8 +1785,9 @@ and genExpr astContext synExpr ctx = let longAppExpr = let functionName argFn = match e with - | LongIdentPieces lids when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids argFn lids - | TypeApp (LongIdentPieces lids, ts) when (List.moreThanOne lids) -> + | LongIdentPiecesExpr lids when (List.moreThanOne lids) -> + genFunctionNameWithMultilineLids argFn lids + | TypeApp (LongIdentPiecesExpr lids, ts) when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids (genGenericTypeParameters astContext ts +> argFn) lids | _ -> genExpr astContext e +> argFn @@ -1852,8 +1815,8 @@ and genExpr astContext synExpr ctx = | DotGetApp (App (e, [ Paren (_, Lambda _, _, _) as px ]), es) -> let genLongFunctionName f = match e with - | LongIdentPieces lids when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids f lids - | TypeApp (LongIdentPieces lids, ts) when (List.moreThanOne lids) -> + | LongIdentPiecesExpr lids when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids f lids + | TypeApp (LongIdentPiecesExpr lids, ts) when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids (genGenericTypeParameters astContext ts +> f) lids | _ -> genExpr astContext e +> f @@ -1887,14 +1850,14 @@ and genExpr astContext synExpr ctx = | DotGetApp (e, es) -> let genLongFunctionName = match e with - | AppOrTypeApp (LongIdentPieces lids, ts, [ Paren _ as px ]) when (List.moreThanOne lids) -> + | AppOrTypeApp (LongIdentPiecesExpr lids, ts, [ Paren _ as px ]) when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids (optSingle (genGenericTypeParameters astContext) ts +> expressionFitsOnRestOfLine (genExpr astContext px) (genMultilineFunctionApplicationArguments sepOpenTFor sepCloseTFor astContext px)) lids - | AppOrTypeApp (LongIdentPieces lids, ts, [ e2 ]) when (List.moreThanOne lids) -> + | AppOrTypeApp (LongIdentPiecesExpr lids, ts, [ e2 ]) when (List.moreThanOne lids) -> genFunctionNameWithMultilineLids (optSingle (genGenericTypeParameters astContext) ts +> genExpr astContext e2) @@ -5350,7 +5313,8 @@ and genConst (c: SynConst) (r: Range) = |> List.tryFind (fun tv -> RangeHelpers.rangeEq tv.Range r) match trivia with - | Some ({ ContentItself = Some (StringContent sc) } as tn) -> + | Some ({ ContentItself = Some (StringContent sc) } as tn) + | Some ({ ContentItself = Some (KeywordString sc) } as tn) -> printContentBefore tn +> !-sc +> printContentAfter tn diff --git a/src/Fantomas/Context.fs b/src/Fantomas/Context.fs index d1e07fa354..bebe28b94b 100644 --- a/src/Fantomas/Context.fs +++ b/src/Fantomas/Context.fs @@ -1126,7 +1126,7 @@ let internal ifAlignBrackets f g = let internal printTriviaContent (c: TriviaContent) (ctx: Context) = let currentLastLine = lastWriteEventOnLastLine ctx - // Some items like #if of Newline should be printed on a newline + // Some items like #if or Newline should be printed on a newline // It is hard to always get this right in CodePrinter, so we detect it based on the current code. let addNewline = currentLastLine @@ -1160,7 +1160,8 @@ let internal printTriviaContent (c: TriviaContent) (ctx: Context) = | IdentOperatorAsWord _ | IdentBetweenTicks _ | CharContent _ - | EmbeddedIL _ -> sepNone // don't print here but somewhere in CodePrinter + | EmbeddedIL _ + | KeywordString _ -> sepNone // don't print here but somewhere in CodePrinter | Directive s | Comment (LineCommentOnSingleLine s) -> (ifElse addNewline sepNln sepNone) @@ -1237,46 +1238,6 @@ let internal leaveLeftToken (tokenName: FsTokenType) (range: Range) (ctx: Contex <| ctx let internal leaveLeftBrace = leaveLeftToken LBRACE -let internal leaveLeftBrack = leaveLeftToken LBRACK -let internal leaveLeftBrackBar = leaveLeftToken LBRACK_BAR - -let internal enterRightToken (tokenName: FsTokenType) (range: Range) (ctx: Context) = - (Map.tryFindOrEmptyList tokenName ctx.TriviaTokenNodes) - |> List.tryFind - (fun tn -> - tn.Range.EndLine = range.EndLine - && (tn.Range.EndColumn = range.EndColumn - || tn.Range.EndColumn + 1 = range.EndColumn)) - |> fun tn -> - match tn with - | Some { ContentBefore = [ TriviaContent.Comment (LineCommentOnSingleLine lineComment) ] } -> - let spacesBeforeComment = - let braceSize = if tokenName = RBRACK then 1 else 2 - - let spaceAround = - if ctx.Config.SpaceAroundDelimiter then - 1 - else - 0 - - !- String.Empty.PadLeft(braceSize + spaceAround) - - let spaceAfterNewline = - if ctx.Config.SpaceAroundDelimiter then - sepSpace - else - sepNone - - sepNln - +> spacesBeforeComment - +> !-lineComment - +> sepNln - +> spaceAfterNewline - | _ -> id - <| ctx - -let internal enterRightBracket = enterRightToken RBRACK -let internal enterRightBracketBar = enterRightToken BAR_RBRACK let internal hasPrintableContent (trivia: TriviaContent list) = trivia @@ -1479,7 +1440,7 @@ let internal colWithNlnWhenItemIsMultiline (items: ColMultilineItem list) = let rec impl items = match items with - | ColMultilineItem (f1, sepNln1, r1) :: ColMultilineItem (_, sepNln2, _) :: _ -> + | ColMultilineItem (f1, sepNln1, r1) :: (ColMultilineItem (_, sepNln2, _) :: _ as nextItems) -> let f1Expr = match firstItemRange with | Some fr1 when (fr1 = r1) -> @@ -1493,7 +1454,7 @@ let internal colWithNlnWhenItemIsMultiline (items: ColMultilineItem list) = f1 (autoNlnConsideringTriviaIfExpressionExceedsPageWidth sepNln1 f1) - addExtraNewlineIfLeadingWasMultiline f1Expr sepNln2 (impl (List.skip 1 items)) + addExtraNewlineIfLeadingWasMultiline f1Expr sepNln2 (impl nextItems) | [ (ColMultilineItem (f, sepNln, r)) ] -> match firstItemRange with | Some fr1 when (fr1 = r) -> diff --git a/src/Fantomas/Fantomas.fsproj b/src/Fantomas/Fantomas.fsproj index 8dd7337195..a4c0988277 100644 --- a/src/Fantomas/Fantomas.fsproj +++ b/src/Fantomas/Fantomas.fsproj @@ -23,6 +23,7 @@ + diff --git a/src/Fantomas/Queue.fs b/src/Fantomas/Queue.fs index ab36ca2f5e..46c2dfbdb4 100644 --- a/src/Fantomas/Queue.fs +++ b/src/Fantomas/Queue.fs @@ -121,5 +121,5 @@ module Queue = let inline append (q: Queue<'T>) xs = q.Append xs - /// Equivalent of q |> Queue.toSeq |> Seq.skip n |> Seq.exists f + /// Equivalent of q |> Queue.toSeq |> Seq.skip n |> Seq.skipWhile p |> Seq.exists f let inline skipExists (n: int) (f: 'T -> bool) (p: 'T [] -> bool) (q: Queue<'T>) : bool = q.SkipExists n f p diff --git a/src/Fantomas/SourceParser.fs b/src/Fantomas/SourceParser.fs index 8be6a72602..1511362d68 100644 --- a/src/Fantomas/SourceParser.fs +++ b/src/Fantomas/SourceParser.fs @@ -53,7 +53,16 @@ let (|LongIdent|) (li: LongIdent) = else s -let (|LongIdentPieces|_|) = +let (|LongIdentPieces|) (li: LongIdent) = + li + |> List.map + (fun x -> + if x.idText = MangledGlobalName then + "global", x.idRange + else + (|Ident|) x, x.idRange) + +let (|LongIdentPiecesExpr|_|) = function | SynExpr.LongIdent (_, LongIdentWithDots (lids, _), _, _) -> lids @@ -256,14 +265,22 @@ let (|ParsedImplFileInput|) (ParsedImplFileInput.ParsedImplFileInput (_, _, _, _ let (|ParsedSigFileInput|) (ParsedSigFileInput.ParsedSigFileInput (_, _, _, hs, mns)) = (hs, mns) let (|ModuleOrNamespace|) - (SynModuleOrNamespace.SynModuleOrNamespace (LongIdent s, isRecursive, isModule, mds, px, ats, ao, _)) + (SynModuleOrNamespace.SynModuleOrNamespace (LongIdentPieces lids, isRecursive, kind, mds, px, ats, ao, _)) = - (ats, px, ao, s, mds, isRecursive, isModule) + (ats, px, ao, lids, mds, isRecursive, kind) let (|SigModuleOrNamespace|) - (SynModuleOrNamespaceSig.SynModuleOrNamespaceSig (LongIdent s, isRecursive, isModule, mds, px, ats, ao, _)) + (SynModuleOrNamespaceSig.SynModuleOrNamespaceSig (LongIdentPieces lids, isRecursive, kind, mds, px, ats, ao, _)) = - (ats, px, ao, s, mds, isRecursive, isModule) + (ats, px, ao, lids, mds, isRecursive, kind) + +let (|EmptyFile|_|) (input: ParsedInput) = + match input with + | ImplFile (ParsedImplFileInput (_, [ ModuleOrNamespace (_, _, _, _, [], _, SynModuleOrNamespaceKind.AnonModule) ])) -> + Some input + | SigFile (ParsedSigFileInput (_, [ SigModuleOrNamespace (_, _, _, _, [], _, SynModuleOrNamespaceKind.AnonModule) ])) -> + Some input + | _ -> None let (|Attribute|) (a: SynAttribute) = let (LongIdentWithDots s) = a.TypeName diff --git a/src/Fantomas/TokenParser.fs b/src/Fantomas/TokenParser.fs index 5e655f7b88..16305d7703 100644 --- a/src/Fantomas/TokenParser.fs +++ b/src/Fantomas/TokenParser.fs @@ -721,6 +721,12 @@ let rec private (|HashTokens|_|) (tokens: Token list) = | _ -> Some(head :: tokensFromSameLine, rest) | _ -> None +let private (|KeywordString|_|) (token: Token) = + if token.TokenInfo.Tag = 192 then + Some token + else + None + let rec private getTriviaFromTokensThemSelves (mkRange: MkRange) (allTokens: Token list) @@ -878,6 +884,15 @@ let rec private getTriviaFromTokensThemSelves getTriviaFromTokensThemSelves mkRange allTokens nextTokens info + | KeywordString ks :: rest -> + let range = getRangeBetween mkRange ks ks + + let info = + Trivia.Create(KeywordString(ks.Content)) range + |> List.prependItem foundTrivia + + getTriviaFromTokensThemSelves mkRange allTokens rest info + | headToken :: rest when (isOperatorOrKeyword headToken && List.exists (fun k -> headToken.TokenInfo.TokenName = k) keywordTrivia) diff --git a/src/Fantomas/Trivia.fs b/src/Fantomas/Trivia.fs index e592e0ef7f..fdcb9d6b99 100644 --- a/src/Fantomas/Trivia.fs +++ b/src/Fantomas/Trivia.fs @@ -2,22 +2,12 @@ module internal Fantomas.Trivia open FSharp.Compiler.SourceCodeServices open Fantomas +open Fantomas.SourceParser open Fantomas.AstTransformer open Fantomas.TriviaTypes open FSharp.Compiler.Text open FSharp.Compiler.SyntaxTree -let inline private isMainNodeButNotAnonModule (node: TriviaNodeAssigner) = - match node.Type with - | MainNode t when (t <> SynModuleOrNamespace_AnonModule) -> true - | _ -> false - -let inline private isSynAnonModule (node: TriviaNodeAssigner) = - match node.Type with - | MainNode SynModuleOrNamespace_AnonModule - | MainNode SynModuleOrNamespaceSig_AnonModule -> true - | _ -> false - let isMainNode (node: TriviaNode) = match node.Type with | MainNode _ -> true @@ -43,10 +33,6 @@ let inline private mainNodeIs name (t: TriviaNodeAssigner) = | MainNode mn -> mn = name | _ -> false -let private nodesContainsBothAnonModuleAndOpen (nodes: TriviaNodeAssigner list) = - List.exists (mainNodeIs SynModuleOrNamespace_AnonModule) nodes - && List.exists (mainNodeIs SynModuleDecl_Open) nodes - // the member keyword is not part of an AST node range // so it is not an ideal candidate node to have trivia content let inline private isNotMemberKeyword (node: TriviaNodeAssigner) = @@ -54,20 +40,12 @@ let inline private isNotMemberKeyword (node: TriviaNodeAssigner) = | Token (MEMBER, _) -> false | _ -> true -let private findFirstNodeAfterLine - (nodes: TriviaNodeAssigner list) - (lineNumber: int) - (hasAnonModulesAndOpenStatements: bool) - : TriviaNodeAssigner option = +let private findFirstNodeAfterLine (nodes: TriviaNodeAssigner list) (lineNumber: int) : TriviaNodeAssigner option = nodes |> List.tryFind (fun tn -> tn.Range.StartLine > lineNumber - && isNotMemberKeyword tn - && not ( - hasAnonModulesAndOpenStatements - && mainNodeIs SynModuleOrNamespace_AnonModule tn - )) + && isNotMemberKeyword tn) let private findLastNodeOnLine (nodes: TriviaNodeAssigner list) lineNumber : TriviaNodeAssigner option = nodes @@ -89,7 +67,6 @@ let private findLastNode (nodes: TriviaNodeAssigner list) : TriviaNodeAssigner o | [] -> None | nodes -> nodes - |> List.filter isMainNodeButNotAnonModule |> List.maxBy (fun tn -> tn.Range.EndLine) |> Some @@ -180,6 +157,19 @@ let private findConstNumberNodeOnLineAndColumn (nodes: TriviaNodeAssigner list) && tn.Range.EndColumn = constantRange.EndColumn | _ -> false) +let private findNodeForKeywordString (nodes: TriviaNodeAssigner list) (range: Range) = + nodes + |> List.tryFind + (fun tn -> + match tn.Type with + | MainNode SynConst_String -> + tn.Range.StartLine = range.StartLine + && tn.Range.StartColumn = range.StartColumn + | MainNode ParsedHashDirective_ -> + tn.Range.StartLine = range.StartLine + && tn.Range.EndColumn >= range.EndColumn + | _ -> false) + let private findSynConstStringNodeAfter (nodes: TriviaNodeAssigner list) (range: Range) = nodes |> List.tryFind @@ -191,24 +181,10 @@ let private findSynConstStringNodeAfter (nodes: TriviaNodeAssigner list) (range: let private commentIsAfterLastTriviaNode (triviaNodes: TriviaNodeAssigner list) (range: Range) = let hasNoNodesAfterRange = triviaNodes - |> Seq.exists - (fun tn -> - tn.Range.EndLine > range.StartLine - && isMainNodeButNotAnonModule tn) + |> Seq.exists (fun tn -> tn.Range.EndLine > range.StartLine) |> not - let hasOnlyOneNamedModule = - triviaNodes - |> List.tryExactlyOne - |> Option.map - (fun mn -> - match mn.Type with - | MainNode SynModuleOrNamespace_NamedModule - | MainNode SynModuleOrNamespaceSig_NamedModule -> true - | _ -> false) - |> Option.defaultValue false - - hasNoNodesAfterRange || hasOnlyOneNamedModule + hasNoNodesAfterRange let private updateTriviaNode (lens: TriviaNodeAssigner -> unit) (triviaNodes: TriviaNodeAssigner list) triviaNode = match triviaNode with @@ -276,35 +252,42 @@ let private findASTNodeOfTypeThatContains (nodes: TriviaNodeAssigner list) typeN | _ -> false) |> List.tryHead -let private addAllTriviaToEmptySynModuleOrNamespace (trivias: Trivia list) (singleNode: TriviaNodeAssigner) = +let private addAllTriviaAsContentAfter (trivia: Trivia list) (singleNode: TriviaNodeAssigner) = + let contentAfter = + trivia + |> List.skipWhile (fun tn -> tn.Item = Newline) // skip leading newlines + |> List.map (fun tn -> tn.Item) + { Type = singleNode.Type Range = singleNode.Range ContentBefore = [] ContentItself = None - ContentAfter = List.map (fun t -> t.Item) trivias } + ContentAfter = contentAfter } |> List.singleton let private addTriviaToTriviaNode triviaBetweenAttributeAndParentBinding - hasAnonModulesAndOpenStatements (startOfSourceCode: int) (triviaNodes: TriviaNodeAssigner list) trivia = match trivia with - | { Item = Comment (LineCommentOnSingleLine _) as comment - Range = range } when (commentIsAfterLastTriviaNode triviaNodes range) -> - // Comment on is on its own line after all Trivia nodes, most likely at the end of a module - findLastNode triviaNodes - |> updateTriviaNode (fun tn -> tn.ContentAfter.Add(comment)) triviaNodes - | { Item = Comment (LineCommentOnSingleLine _ as comment) Range = range } -> match triviaBetweenAttributeAndParentBinding triviaNodes range.StartLine with | Some _ as node -> updateTriviaNode (fun tn -> tn.ContentAfter.Add(Comment(comment))) triviaNodes node | None -> - findFirstNodeAfterLine triviaNodes range.StartLine hasAnonModulesAndOpenStatements - |> updateTriviaNode (fun tn -> tn.ContentBefore.Add(Comment(comment))) triviaNodes + let nodeAfterLine = + findFirstNodeAfterLine triviaNodes range.StartLine + + match nodeAfterLine with + | Some _ -> + nodeAfterLine + |> updateTriviaNode (fun tn -> tn.ContentBefore.Add(Comment(comment))) triviaNodes + | None -> + // try and find a node above + findNodeBeforeLineFromStart triviaNodes range.StartLine + |> updateTriviaNode (fun tn -> tn.ContentAfter.Add(Comment(comment))) triviaNodes | { Item = Comment (BlockComment (comment, _, _)) Range = range } -> @@ -346,7 +329,7 @@ let private addTriviaToTriviaNode | Some _ as node -> updateTriviaNode (fun tn -> tn.ContentAfter.Add(Newline)) triviaNodes node | _ -> let nodeAfterLine = - findFirstNodeAfterLine triviaNodes range.StartLine hasAnonModulesAndOpenStatements + findFirstNodeAfterLine triviaNodes range.StartLine match nodeAfterLine with | Some _ -> @@ -357,6 +340,10 @@ let private addTriviaToTriviaNode findNodeBeforeLineFromStart triviaNodes range.StartLine |> updateTriviaNode (fun tn -> tn.ContentAfter.Add(Newline)) triviaNodes + | { Item = KeywordString _ + Range = range } -> + findNodeForKeywordString triviaNodes range + |> updateTriviaNode (fun tn -> tn.ContentItself <- Some trivia.Item) triviaNodes | { Item = Keyword ({ Content = keyword } as kw) Range = range } when (keyword = "override" @@ -431,7 +418,7 @@ let private addTriviaToTriviaNode match triviaBetweenAttributeAndParentBinding triviaNodes range.StartLine with | Some _ as node -> updateTriviaNode (fun tn -> tn.ContentAfter.Add(directive)) triviaNodes node | _ -> - match findFirstNodeAfterLine triviaNodes range.StartLine hasAnonModulesAndOpenStatements with + match findFirstNodeAfterLine triviaNodes range.StartLine with | Some _ as node -> updateTriviaNode (fun tn -> tn.ContentBefore.Add(directive)) triviaNodes node | None -> let findNode nodes = @@ -529,9 +516,6 @@ let collectTrivia (mkRange: MkRange) tokens (ast: ParsedInput) = triviaNodesFromAST @ triviaNodesFromTokens |> List.sortBy (fun n -> n.Range.Start.Line, n.Range.Start.Column) - let hasAnonModulesAndOpenStatements = - nodesContainsBothAnonModuleAndOpen triviaNodes - let trivias = TokenParser.getTriviaFromTokens mkRange tokens @@ -543,14 +527,11 @@ let collectTrivia (mkRange: MkRange) tokens (ast: ParsedInput) = match trivias with | [] -> [] | _ -> - match triviaNodes with - | [ singleNode ] when (isSynAnonModule singleNode) -> addAllTriviaToEmptySynModuleOrNamespace trivias singleNode + match ast, triviaNodes with + | EmptyFile _, h :: _ -> addAllTriviaAsContentAfter trivias h | _ -> List.fold - (addTriviaToTriviaNode - triviaBetweenAttributeAndParentBinding - hasAnonModulesAndOpenStatements - startOfSourceCode) + (addTriviaToTriviaNode triviaBetweenAttributeAndParentBinding startOfSourceCode) triviaNodes trivias |> List.choose diff --git a/src/Fantomas/TriviaTypes.fs b/src/Fantomas/TriviaTypes.fs index b156ffa200..1236a9b220 100644 --- a/src/Fantomas/TriviaTypes.fs +++ b/src/Fantomas/TriviaTypes.fs @@ -80,6 +80,7 @@ let a = 7 type TriviaContent = | Keyword of Token + | KeywordString of string | Number of string | StringContent of string | IdentOperatorAsWord of string @@ -99,10 +100,13 @@ type TriviaIndex = TriviaIndex of int * int type FsAstType = | Ident_ - | SynModuleOrNamespace_AnonModule - | SynModuleOrNamespace_DeclaredNamespace - | SynModuleOrNamespace_GlobalNamespace - | SynModuleOrNamespace_NamedModule + | LongIdent_ // namespace or module identifier + // Modules and namespaces cannot really be trusted + // Their range can be influenced by non code constructs (like comments) + // | SynModuleOrNamespace_AnonModule + // | SynModuleOrNamespace_DeclaredNamespace + // | SynModuleOrNamespace_GlobalNamespace + // | SynModuleOrNamespace_NamedModule | SynModuleDecl_ModuleAbbrev | SynModuleDecl_NestedModule | SynModuleDecl_Let @@ -317,10 +321,12 @@ type FsAstType = | SynValInfo_ | SynArgInfo_ | ParsedHashDirective_ - | SynModuleOrNamespaceSig_AnonModule - | SynModuleOrNamespaceSig_DeclaredNamespace - | SynModuleOrNamespaceSig_GlobalNamespace - | SynModuleOrNamespaceSig_NamedModule + // Modules and namespaces cannot really be trusted + // Their range can be influenced by non code constructs (like comments) +// | SynModuleOrNamespaceSig_AnonModule +// | SynModuleOrNamespaceSig_DeclaredNamespace +// | SynModuleOrNamespaceSig_GlobalNamespace +// | SynModuleOrNamespaceSig_NamedModule | SynModuleSigDecl_ModuleAbbrev | SynModuleSigDecl_NestedModule | SynModuleSigDecl_Types