From b3db6835bdfd3381a00889f7678a67ab26277660 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Sat, 1 Feb 2020 16:38:26 +0100 Subject: [PATCH] Formatting original char from trivia. Fixes #632. (#665) --- src/Fantomas.Tests/SynConstTests.fs | 11 ++++++++++ src/Fantomas.Tests/TokenParserTests.fs | 20 ++++++++++++++++++ src/Fantomas.Tests/TriviaTests.fs | 18 +++++++++++++++-- src/Fantomas/CodePrinter.fs | 12 ++++++++--- src/Fantomas/Context.fs | 1 + src/Fantomas/TokenParser.fs | 7 +++++++ src/Fantomas/Trivia.fs | 4 ++++ src/Fantomas/TriviaHelpers.fs | 28 +++++++++++++++++--------- src/Fantomas/TriviaTypes.fs | 1 + 9 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/Fantomas.Tests/SynConstTests.fs b/src/Fantomas.Tests/SynConstTests.fs index d1fa3c0e1e..4f0326dc6b 100644 --- a/src/Fantomas.Tests/SynConstTests.fs +++ b/src/Fantomas.Tests/SynConstTests.fs @@ -89,4 +89,15 @@ namespace SomeNamespace module SomeModule = let backspace = '\b' let formFeed = '\f' +""" + +[] +let ``escape unicode null, 632`` () = + formatSourceString false """let nulchar = '\u0000' +let nullstr = "\u0000" +""" config + |> prepend newline + |> should equal """ +let nulchar = '\u0000' +let nullstr = "\u0000" """ \ No newline at end of file diff --git a/src/Fantomas.Tests/TokenParserTests.fs b/src/Fantomas.Tests/TokenParserTests.fs index cba669422b..cb81a83426 100644 --- a/src/Fantomas.Tests/TokenParserTests.fs +++ b/src/Fantomas.Tests/TokenParserTests.fs @@ -396,3 +396,23 @@ let ``ident between tickets `` () = | [{ Item = IdentBetweenTicks("``/ operator combines paths``") }] -> pass() | _ -> fail() + +[] +let ``simple char content`` () = + let source = "let someChar = \'s\'" + let (tokens,lineCount) = tokenize [] source + let trivia = getTriviaFromTokens tokens lineCount + match trivia with + | [{ Item = CharContent("\'s\'") }] -> + pass() + | _ -> fail() + +[] +let ``escaped char content`` () = + let source = "let nulchar = \'\\u0000\'" + let (tokens,lineCount) = tokenize [] source + let trivia = getTriviaFromTokens tokens lineCount + match trivia with + | [{ Item = CharContent("\'\\u0000\'") }] -> + pass() + | _ -> fail() diff --git a/src/Fantomas.Tests/TriviaTests.fs b/src/Fantomas.Tests/TriviaTests.fs index f291b7cc99..42de320169 100644 --- a/src/Fantomas.Tests/TriviaTests.fs +++ b/src/Fantomas.Tests/TriviaTests.fs @@ -42,7 +42,7 @@ let a = 9 failwith "Expected line comment" [] -let ``Line comment that is alone on the single, preceded by whitespaces`` () = +let ``line comment that is alone on the single, preceded by whitespaces`` () = let source = """ // foo let a = 'c' """ @@ -52,7 +52,8 @@ let a = 'c' |> List.head match triviaNodes with - | [{ ContentBefore = [Comment(LineCommentOnSingleLine(lineComment))]; }] -> + | [{ ContentBefore = [Comment(LineCommentOnSingleLine(lineComment))]; } + { ContentItself = Some(CharContent("\'c\'")) }] -> lineComment == "// foo" | _ -> failwith "Expected line comment" @@ -511,4 +512,17 @@ with empty lines" | [{ ContentItself = Some(StringContent(sc)) Type = TriviaNodeType.MainNode("SynExpr.Const") }] -> sc == sprintf "\"\"\"%s\"\"\"" multilineString + | _ -> fail() + +[] +let ``char content`` () = + let source = "let nulchar = \'\\u0000\'" + let trivia = + toTrivia source + |> List.head + + match trivia with + | [{ ContentItself = Some(CharContent("\'\\u0000\'")) + Type = TriviaNodeType.MainNode("SynExpr.Const") }] -> + pass() | _ -> fail() \ No newline at end of file diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 52c339dbf0..6e44fd8878 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -2292,8 +2292,15 @@ and genConst (c:SynConst) (r:range) = !- (sprintf "\"%s\"" escaped) <| ctx | SynConst.Char(c) -> - let escapedChar = Char.escape c - !- (sprintf "\'%s\'" escapedChar) + fun (ctx: Context) -> + let charContentFromTrivia = TriviaHelpers.``get CharContent`` r ctx.Trivia + let expr = + match charContentFromTrivia with + | Some content -> !- content + | None -> + let escapedChar = Char.escape c + !- (sprintf "\'%s\'" escapedChar) + expr ctx | SynConst.Bytes(bytes,_) -> genConstBytes bytes r | SynConst.Measure(c, m) -> let measure = @@ -2367,4 +2374,3 @@ and infixOperatorFromTrivia range fallback (ctx: Context) = | Some iiw -> !- iiw | None -> !- fallback <| ctx - diff --git a/src/Fantomas/Context.fs b/src/Fantomas/Context.fs index 6aaf24fd62..e9e881169a 100644 --- a/src/Fantomas/Context.fs +++ b/src/Fantomas/Context.fs @@ -537,6 +537,7 @@ let internal printTriviaContent (c: TriviaContent) (ctx: Context) = | IdentOperatorAsWord _ | IdentBetweenTicks _ | NewlineAfter + | CharContent _ -> sepNone // don't print here but somewhere in CodePrinter | Directive(s) | Comment(LineCommentOnSingleLine s) -> diff --git a/src/Fantomas/TokenParser.fs b/src/Fantomas/TokenParser.fs index eb1e8fa310..28554ba3ca 100644 --- a/src/Fantomas/TokenParser.fs +++ b/src/Fantomas/TokenParser.fs @@ -402,6 +402,13 @@ let rec private getTriviaFromTokensThemSelves (config: FormatConfig) (allTokens: |> List.prependItem foundTrivia getTriviaFromTokensThemSelves config allTokens rest info + | head::rest when (head.TokenInfo.TokenName = "CHAR") -> + let range = getRangeBetween head.TokenInfo.TokenName head head + let info = + Trivia.Create (CharContent(head.Content)) range + |> List.prependItem foundTrivia + getTriviaFromTokensThemSelves config allTokens rest info + | (_)::rest -> getTriviaFromTokensThemSelves config allTokens rest foundTrivia | [] -> foundTrivia diff --git a/src/Fantomas/Trivia.fs b/src/Fantomas/Trivia.fs index 0d12799a80..03bc725442 100644 --- a/src/Fantomas/Trivia.fs +++ b/src/Fantomas/Trivia.fs @@ -310,6 +310,10 @@ let private addTriviaToTriviaNode (triviaNodes: TriviaNode list) trivia = | { Item = Number(_) as number; Range = range } -> findNodeOnLineAndColumn triviaNodes range.StartLine range.StartColumn |> updateTriviaNode (fun tn -> { tn with ContentItself = Some number }) triviaNodes + + | { Item = CharContent(_) as chNode; Range = range } -> + findNodeOnLineAndColumn triviaNodes range.StartLine range.StartColumn + |> updateTriviaNode (fun tn -> { tn with ContentItself = Some chNode }) triviaNodes | { Item = IdentOperatorAsWord(_) as ifw; Range = range } -> findBindingThatStartsWith triviaNodes range.StartColumn range.StartLine diff --git a/src/Fantomas/TriviaHelpers.fs b/src/Fantomas/TriviaHelpers.fs index 6546a9cf7f..302b39ff63 100644 --- a/src/Fantomas/TriviaHelpers.fs +++ b/src/Fantomas/TriviaHelpers.fs @@ -4,31 +4,31 @@ open FSharp.Compiler.Range open Fantomas.TriviaTypes [] -module TriviaHelpers = - let internal findByRange (trivia: TriviaNode list) (range: range) = +module internal TriviaHelpers = + let findByRange (trivia: TriviaNode list) (range: range) = trivia |> List.tryFind (fun t -> t.Range = range) - let internal findFirstContentBeforeByRange (trivia: TriviaNode list) (range: range) = + let findFirstContentBeforeByRange (trivia: TriviaNode list) (range: range) = findByRange trivia range |> Option.bind (fun t -> t.ContentBefore |> List.tryHead) - let internal ``has content after after that matches`` (findTrivia: TriviaNode -> bool) (contentAfter: TriviaContent -> bool) (trivia: TriviaNode list) = + let ``has content after after that matches`` (findTrivia: TriviaNode -> bool) (contentAfter: TriviaContent -> bool) (trivia: TriviaNode list) = List.tryFind findTrivia trivia |> Option.map (fun t -> t.ContentAfter |> List.exists contentAfter) |> Option.defaultValue false - let internal ``has content after that ends with`` (findTrivia: TriviaNode -> bool) (contentAfterEnd: TriviaContent -> bool) (trivia: TriviaNode list) = + let ``has content after that ends with`` (findTrivia: TriviaNode -> bool) (contentAfterEnd: TriviaContent -> bool) (trivia: TriviaNode list) = List.tryFind findTrivia trivia |> Option.bind (fun t -> t.ContentAfter |> List.tryLast |> Option.map contentAfterEnd) |> Option.defaultValue false - let internal ``is token of type`` tokenName (triviaNode: TriviaNode) = + let ``is token of type`` tokenName (triviaNode: TriviaNode) = match triviaNode.Type with | Token({ TokenInfo = ti }) -> ti.TokenName = tokenName | _ -> false - let internal ``keyword tokens inside range`` keywords range (trivia: TriviaNode list) = + let ``keyword tokens inside range`` keywords range (trivia: TriviaNode list) = trivia |> List.choose(fun t -> match t.Type with @@ -38,7 +38,7 @@ module TriviaHelpers = | _ -> None ) - let internal ``has line comment after`` triviaNode = + let ``has line comment after`` triviaNode = triviaNode.ContentAfter |> List.filter(fun tn -> match tn with @@ -47,13 +47,21 @@ module TriviaHelpers = ) |> (List.isEmpty >> not) - let internal ``has line comment before`` range triviaNodes = + let ``has line comment before`` range triviaNodes = triviaNodes |> List.tryFind (fun tv -> tv.Range = range) |> Option.map (fun tv -> tv.ContentBefore |> List.exists (function | Comment(LineCommentOnSingleLine(_)) -> true | _ -> false)) |> Option.defaultValue false - let internal ``has content itself is ident between ticks`` range (triviaNodes: TriviaNode list) = + let ``get CharContent`` range triviaNodes = + triviaNodes + |> List.tryFind (fun tv -> tv.Range = range) + |> Option.bind (fun tv -> + match tv.ContentItself with + | Some(CharContent c) -> Some c + | _ -> None) + + let ``has content itself is ident between ticks`` range (triviaNodes: TriviaNode list) = triviaNodes |> List.choose (fun tn -> match tn.Range = range, tn.ContentItself with diff --git a/src/Fantomas/TriviaTypes.fs b/src/Fantomas/TriviaTypes.fs index b4a25a04c5..c392ea1797 100644 --- a/src/Fantomas/TriviaTypes.fs +++ b/src/Fantomas/TriviaTypes.fs @@ -36,6 +36,7 @@ type TriviaContent = | Newline | Directive of directive:string | NewlineAfter + | CharContent of string type Trivia = { Item: TriviaContent