Skip to content

Commit

Permalink
Preserve back ticks in enum members (#662)
Browse files Browse the repository at this point in the history
* Added failing test for #626.

* Print ident between ticks for enum definition.
Fixes #626.

* Added additional unit tests.
  • Loading branch information
nojaf authored Feb 1, 2020
1 parent 767a268 commit 3f797ed
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 45 deletions.
57 changes: 56 additions & 1 deletion src/Fantomas.Tests/UnionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,59 @@ type DU = | Record
namespace meh
type DU = | Record
"""
"""

[<Test>]
let ``enum with back ticks, 626`` () =
formatSourceString false """type MyEnum =
| ``test-one`` = 0
""" config
|> prepend newline
|> should equal """
type MyEnum =
| ``test-one`` = 0
"""

[<Test>]
let ``enum with back ticks in signature file`` () =
formatSourceString true """namespace foo
type MyEnum =
| ``test-one`` = 0
""" config
|> prepend newline
|> should equal """
namespace foo
type MyEnum =
| ``test-one`` = 0
"""

[<Test>]
let ``discriminated union with back ticks`` () =
formatSourceString false """type MyEnum =
| ``test-one`` of int
| ``test-two`` of string
""" config
|> prepend newline
|> should equal """
type MyEnum =
| ``test-one`` of int
| ``test-two`` of string
"""

[<Test>]
let ``discriminated union with back ticks in signature file`` () =
formatSourceString true """namespace foo
type MyEnum =
| ``test-one`` of int
| ``test-two`` of string
""" config
|> prepend newline
|> should equal """
namespace foo
type MyEnum =
| ``test-one`` of int
| ``test-two`` of string
"""
58 changes: 28 additions & 30 deletions src/Fantomas/AstTransformer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ module private Ast =
Properties = p []
FsAstNode = ast
Childs = [visitSynModuleOrNamespace moduleOrNamespace]}

and visitSynExpr(synExpr: SynExpr): Node =
match synExpr with
| SynExpr.Paren(expr,leftParenRange,rightParenRange,range) ->
Expand Down Expand Up @@ -354,7 +354,7 @@ module private Ast =
| Sequentials xs -> // use tail-rec active pattern to avoid stack overflow
let rec cons xs =
match xs with
| [] -> failwith "should not happen" // expr2Opt is always Some in last item
| [] -> failwith "should not happen" // expr2Opt is always Some in last item
| ((isTrueSeq,expr1,expr2Opt,range)::rest) ->
{Type = "SynExpr.Sequential"
Range = r range
Expand Down Expand Up @@ -398,13 +398,14 @@ module private Ast =
FsAstNode = synExpr
Childs = []}
| SynExpr.LongIdent(isOptional,longDotId,_,range) ->
let ids = visitLongIdentWithDots longDotId
{Type = "SynExpr.LongIdent"
Range = r range
Properties =
p ["isOptional" ==> isOptional
"longDotId" ==> lid longDotId]
FsAstNode = synExpr
Childs = []}
Childs = ids}
| SynExpr.LongIdentSet(longDotId,expr,range) ->
{Type = "SynExpr.LongIdentSet"
Range = r range
Expand All @@ -413,17 +414,7 @@ module private Ast =
Childs = [yield visitSynExpr expr]}
| SynExpr.DotGet(expr,rangeOfDot,longDotId,range) ->
// Idents are collected as childs here to deal with unit test ``Fluent api with comments should remain on same lines``
let ids =
match longDotId with
| LongIdentWithDots(ids,_) ->
ids
|> List.map (fun (ident) -> {
Type = "Ident"
Range = Some ident.idRange
Properties = Map.empty
FsAstNode = ident
Childs = []
})
let ids = visitLongIdentWithDots longDotId
{Type = "SynExpr.DotGet"
Range = r range
Properties =
Expand Down Expand Up @@ -772,7 +763,7 @@ module private Ast =
[yield visitSynComponentInfo sci
yield visitSynTypeDefnSigRepr synTypeDefnSigReprs
yield! (memberSig |> List.map visitSynMemberSig)]}

and visitSynTypeDefnSigRepr(stdr: SynTypeDefnSigRepr): Node =
match stdr with
| SynTypeDefnSigRepr.ObjectModel(kind,members,range) ->
Expand Down Expand Up @@ -1367,7 +1358,7 @@ module private Ast =
yield "typeName" ==> lid attr.TypeName]
FsAstNode = attr
Childs = [visitSynExpr attr.ArgExpr]}

and visitSynAttributeList(attrs: SynAttributeList): Node =
{Type = "SynAttributeList"
Range = r attrs.Range
Expand Down Expand Up @@ -1411,9 +1402,9 @@ module private Ast =
| EnumCase(attrs,ident,_,_,range) ->
{Type = "EnumCase"
Range = r range
Properties = p ["ident" ==> i ident]
Properties = p []
FsAstNode = sec
Childs = [yield! attrs |> List.map visitSynAttributeList]}
Childs = [yield! attrs |> List.map visitSynAttributeList; yield visitIdent ident]}

and visitSynField(sfield: SynField): Node =
match sfield with
Expand Down Expand Up @@ -1601,18 +1592,10 @@ module private Ast =
"longIdent" ==> longIdent]
FsAstNode = hash
Childs = []}

and visitSynModuleOrNamespaceSig(modOrNs: SynModuleOrNamespaceSig): Node =
match modOrNs with
| SynModuleOrNamespaceSig(longIdent,isRecursive,isModule,decls,_,attrs,access,range) ->
let collectIdents (idents: LongIdent) =
idents
|> List.map (fun ident ->
{ Type = "Ident"
Range = r ident.idRange
Properties = Map.empty
FsAstNode = ident
Childs = [] })
{Type = sprintf "SynModuleOrNamespaceSig.%A" isModule
Range = r range
Properties =
Expand All @@ -1622,10 +1605,10 @@ module private Ast =
if access.IsSome then yield "access" ==> (access.Value |> visitSynAccess)]
FsAstNode = modOrNs
Childs =
[yield! (if isModule = SynModuleOrNamespaceKind.DeclaredNamespace then collectIdents longIdent else [])
[yield! (if isModule = SynModuleOrNamespaceKind.DeclaredNamespace then visitLongIdent longIdent else [])
yield! attrs |> List.map visitSynAttributeList
yield! (decls |> List.map visitSynModuleSigDecl)]}

and visitSynModuleSigDecl(ast: SynModuleSigDecl) : Node =
match ast with
| SynModuleSigDecl.ModuleAbbrev(ident,longIdent,range) ->
Expand Down Expand Up @@ -1676,7 +1659,7 @@ module private Ast =
Properties = p []
FsAstNode = ast
Childs = [visitSynExceptionSig synExceptionSig]}

and visitSynExceptionSig(exceptionDef: SynExceptionSig): Node =
match exceptionDef with
| SynExceptionSig(sedr,members,range) ->
Expand All @@ -1688,6 +1671,21 @@ module private Ast =
[yield visitSynExceptionDefnRepr sedr
yield! (members |> List.map visitSynMemberSig)]}

and visitLongIdentWithDots (lid: LongIdentWithDots): Node list =
match lid with
| LongIdentWithDots(ids,_) ->
List.map visitIdent ids

and visitLongIdent (li: LongIdent) : Node list =
List.map visitIdent li

and visitIdent (ident: Ident) : Node =
{ Type = "Ident"
Range = r ident.idRange
Properties = Map.empty
FsAstNode = ident
Childs = [] }

let astToNode (hds: ParsedHashDirective list) (mdls: SynModuleOrNamespace list): Node =
let children =
[ yield! List.map Ast.visit mdls
Expand Down
19 changes: 14 additions & 5 deletions src/Fantomas/CodePrinter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,10 @@ and genExpr astContext synExpr =
(atCurrentColumn formatIfElseExpr) ctx

// At this stage, all symbolic operators have been handled.
| OptVar(s, isOpt) -> ifElse isOpt (!- "?") sepNone -- s
| OptVar(s, isOpt, ranges) ->
// In case s is f.ex `onStrongDiscard.IsNone`, last range is the range of `IsNone`
let lastRange = List.tryLast ranges
ifElse isOpt (!- "?") sepNone -- s +> opt id lastRange (fun r ctx -> leaveNode r ctx)
| LongIdentSet(s, e, _) ->
!- (sprintf "%s <- " s) +> autoIndentNlnByFuture (genExpr astContext e)
| DotIndexedGet(e, es) -> addParenIfAutoNln e (genExpr astContext) -- "." +> sepOpenLFixed +> genIndexers astContext es +> sepCloseLFixed
Expand Down Expand Up @@ -1811,10 +1814,16 @@ and genUnionCase astContext (UnionCase(ats, px, _, s, UnionCaseType fs) as node)
|> genTrivia node.Range

and genEnumCase astContext (EnumCase(ats, px, _, (_,r)) as node) =
let genCase =
match node with
| SynEnumCase.EnumCase(_, ident, c,_,_) ->
!- ident.idText +> !- " = " +> genConst c r
let genCase (ctx: Context) =
let expr =
match node with
| EnumCase(_, _, identInAST, (c,r)) ->
let ident =
match TriviaHelpers.``has content itself is ident between ticks`` r ctx.Trivia with
| Some identBetweenTicks -> identBetweenTicks
| None -> identInAST
!- ident +> !- " = " +> genConst c r
expr ctx

genPreXmlDoc px
+> genTriviaBeforeClausePipe node.Range
Expand Down
20 changes: 13 additions & 7 deletions src/Fantomas/SourceParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ type Identifier =
xs
|> Seq.map (fun x -> if x.idText = MangledGlobalName then "global" else x.idText)
|> String.concat "."
member x.Ranges =
match x with
| Id x -> List.singleton x.idRange
| LongId xs -> List.map (fun (x:Ident) -> x.idRange) xs

/// Different from (|Ident|), this pattern also accepts keywords
let inline (|IdentOrKeyword|) (s : Ident) = Id s
Expand All @@ -96,6 +100,7 @@ let (|OpName|) (x : Identifier) =

/// Operators in their declaration form
let (|OpNameFull|) (x : Identifier) =
let r = x.Ranges
let s = x.Text
let s' = DecompileOpName s
if IsActivePatternName s || IsInfixOperator s || IsPrefixOperator s || IsTernaryOperator s || s = "op_Dynamic" then
Expand All @@ -107,6 +112,7 @@ let (|OpNameFull|) (x : Identifier) =
match x with
| Id(Ident s) | LongId(LongIdent s) ->
DecompileOpName s
|> fun s -> (s, r)

// Type params

Expand Down Expand Up @@ -619,10 +625,10 @@ let (|Indexer|) = function
| SynIndexerArg.One e -> Single e

let (|OptVar|_|) = function
| SynExpr.Ident(IdentOrKeyword(OpNameFull s)) ->
Some(s, false)
| SynExpr.LongIdent(isOpt, LongIdentWithDots.LongIdentWithDots(LongIdentOrKeyword(OpNameFull s), _), _, _) ->
Some(s, isOpt)
| SynExpr.Ident(IdentOrKeyword(OpNameFull (s,r))) ->
Some(s, false, r)
| SynExpr.LongIdent(isOpt, LongIdentWithDots.LongIdentWithDots(LongIdentOrKeyword(OpNameFull (s,r)), _), _, _) ->
Some(s, isOpt, r)
| _ -> None

/// This pattern is escaped by using OpName
Expand Down Expand Up @@ -912,12 +918,12 @@ let (|PatTyped|_|) = function
| _ -> None

let (|PatNamed|_|) = function
| SynPat.Named(p, IdentOrKeyword(OpNameFull s), _, ao, _) ->
| SynPat.Named(p, IdentOrKeyword(OpNameFull (s,_)), _, ao, _) ->
Some(ao, p, s)
| _ -> None

let (|PatLongIdent|_|) = function
| SynPat.LongIdent(LongIdentWithDots.LongIdentWithDots(LongIdentOrKeyword(OpNameFull s), _), _, tpso, xs, ao, _) ->
| SynPat.LongIdent(LongIdentWithDots.LongIdentWithDots(LongIdentOrKeyword(OpNameFull (s,_)), _), _, tpso, xs, ao, _) ->
match xs with
| SynConstructorArgs.Pats ps ->
Some(ao, s, List.map (fun p -> (None, p)) ps, tpso)
Expand Down Expand Up @@ -1214,7 +1220,7 @@ let (|MSMember|MSInterface|MSInherit|MSValField|MSNestedType|) = function
| SynMemberSig.ValField(f, _) -> MSValField f
| SynMemberSig.NestedType(tds, _) -> MSNestedType tds

let (|Val|) (ValSpfn(ats, IdentOrKeyword(OpNameFull s), tds, t, vi, _, _, px, ao, _, _)) =
let (|Val|) (ValSpfn(ats, IdentOrKeyword(OpNameFull (s,_)), tds, t, vi, _, _, px, ao, _, _)) =
(ats, px, ao, s, t, vi, tds)

// Misc
Expand Down
3 changes: 2 additions & 1 deletion src/Fantomas/Trivia.fs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ let private addTriviaToTriviaNode (triviaNodes: TriviaNode list) trivia =
let isIdent =
match t.Type with
| MainNode("SynExpr.Ident")
| MainNode("SynPat.Named") -> true
| MainNode("SynPat.Named")
| MainNode("Ident") -> true
| _ -> false
isIdent && (t.Range.StartColumn = range.StartColumn || t.Range.StartColumn = range.StartColumn + 1) && t.Range.StartLine = range.StartLine
)
Expand Down
10 changes: 9 additions & 1 deletion src/Fantomas/TriviaHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,12 @@ module TriviaHelpers =
triviaNodes
|> List.tryFind (fun tv -> tv.Range = range)
|> Option.map (fun tv -> tv.ContentBefore |> List.exists (function | Comment(LineCommentOnSingleLine(_)) -> true | _ -> false))
|> Option.defaultValue false
|> Option.defaultValue false

let internal ``has content itself is ident between ticks`` range (triviaNodes: TriviaNode list) =
triviaNodes
|> List.choose (fun tn ->
match tn.Range = range, tn.ContentItself with
| true, Some(IdentBetweenTicks(ident)) -> Some ident
| _ -> None)
|> List.tryHead

0 comments on commit 3f797ed

Please sign in to comment.