From 9f769d838c9cb1a07dc0c6616871f5bebe91bb40 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Sat, 4 Feb 2023 14:46:10 +0100 Subject: [PATCH] Capture index without dot inside a chain. (#2764) * Capture index without dot inside a chain. * Add changelog entry. --- CHANGELOG.md | 4 +- src/Fantomas.Core.Tests/ChainTests.fs | 51 ++++++++++++++++++ src/Fantomas.Core/ASTTransformer.fs | 74 +++++++++++++++++++++------ src/Fantomas.Core/Utils.fs | 15 ++++++ src/Fantomas.Core/Utils.fsi | 2 + 5 files changed, 129 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d87bd7ea2..cae9e095fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog -## [Unreleased] +## [5.2.1] - 2023-02-04 ### Fixed * Conditional defines around selfIdentifier in implicit type constructor. [#2733](https://github.com/fsprojects/fantomas/issues/2733) +* Insert extra spaces around index between method calling and member variable accessing. [#2760](https://github.com/fsprojects/fantomas/issues/2760) +* Exception caused by long line over 80 characters including method calling and member indexing. [#2761](https://github.com/fsprojects/fantomas/issues/2761) ### Changed * Update FCS to 'Add SynMemberDefnImplicitCtorTrivia', commit 924a64e8e40c840f05fbe7113796f267dd603282 diff --git a/src/Fantomas.Core.Tests/ChainTests.fs b/src/Fantomas.Core.Tests/ChainTests.fs index 136b1785a2..22f62a128c 100644 --- a/src/Fantomas.Core.Tests/ChainTests.fs +++ b/src/Fantomas.Core.Tests/ChainTests.fs @@ -387,3 +387,54 @@ Fooooooooooo.Baaaaaaaaaaaaaaaaar .Moooooooooooooooo.Booooooooooooooooooooh .Yooooooooooooooou.Meeeeeeh.Meh2 """ + +[] +let ``dot get with index without dot expression , 2761`` () = + formatSourceString + false + """ +x().y[0].zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +""" + config + |> prepend newline + |> should + equal + """ +x().y[0].zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +""" + +[] +let ``don't add extra space in index without dot expression, 2760`` () = + formatSourceString + false + """ +x().y[0].z // spaces inserted around index +x().y.[0].z // no spaces inserted +x().y[0] // no spaces inserted +x.y[0].z // no spaces inserted +""" + config + |> prepend newline + |> should + equal + """ +x().y[0].z // spaces inserted around index +x().y.[0].z // no spaces inserted +x().y[0] // no spaces inserted +x.y[0].z // no spaces inserted +""" + +[] +let ``multiple idents in dotget with index without dot`` () = + formatSourceString + false + """ +v().w.x.y.z['a'].b +""" + config + |> prepend newline + |> should + equal + """ +v().w.x.y.z['a'].b +""" diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index 7e7466fbd1..ed1b120ea2 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -389,6 +389,18 @@ let (|InfixApp|_|) synExpr = argExpr = e2) -> Some(e1, stn operator operatorIdent.idRange, e2) | _ -> None +let (|IndexWithoutDot|_|) expr = + match expr with + | SynExpr.App(ExprAtomicFlag.Atomic, false, identifierExpr, SynExpr.ArrayOrListComputed(false, indexExpr, _), _) -> + Some(identifierExpr, indexExpr) + | SynExpr.App(ExprAtomicFlag.NonAtomic, + false, + identifierExpr, + (SynExpr.ArrayOrListComputed(isArray = false; expr = indexExpr) as argExpr), + _) when (RangeHelpers.isAdjacentTo identifierExpr.Range argExpr.Range) -> + Some(identifierExpr, indexExpr) + | _ -> None + let (|MultipleConsInfixApps|_|) expr = let rec visit expr (headAndLastOperator: (SynExpr * SingleTextNode) option) (xs: Queue) = match expr with @@ -636,8 +648,7 @@ let mkLinksFromFunctionName (mkLinkFromExpr: SynExpr -> LinkExpr) (functionName: m ) - [ yield! List.take (leftLinks.Length - 1) leftLinks - yield mkLinkFromExpr typeAppExpr ] + [ yield! List.cutOffLast leftLinks; yield mkLinkFromExpr typeAppExpr ] | SynExpr.LongIdent(longDotId = sli) -> match sli.IdentsWithTrivia with @@ -647,7 +658,7 @@ let mkLinksFromFunctionName (mkLinkFromExpr: SynExpr -> LinkExpr) (functionName: let leftLinks = mkLinksFromSynLongIdent sli let lastSynIdent = List.last synIdents - [ yield! List.take (leftLinks.Length - 1) leftLinks + [ yield! List.cutOffLast leftLinks yield (mkLongIdentExprFromSynIdent lastSynIdent |> mkLinkFromExpr) ] | e -> [ mkLinkFromExpr e ] @@ -685,7 +696,7 @@ let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = | _ -> [] | _ -> [] - let leftLinks = List.take (leftLinks.Length - 1) leftLinks + let leftLinks = List.cutOffLast leftLinks continuation [ yield! leftLinks; yield! lastLink ]) @@ -712,15 +723,53 @@ let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = |> LinkExpr.Identifier ] | _ -> [] - let leftLinks = List.take (leftLinks.Length - 1) leftLinks + let leftLinks = List.cutOffLast leftLinks continuation [ yield! leftLinks; yield! lastLink ]) + + // Transform `x().y[0]` into `x()` , `dot`, `y[0]` + | IndexWithoutDot(SynExpr.DotGet(expr, mDot, sli, _), indexExpr) -> + visit expr (fun leftLinks -> + let middleLinks, lastExpr = + match List.tryLast sli.IdentsWithTrivia with + | None -> [], indexExpr + | Some lastMiddleLink -> + let middleLinks = mkLinksFromSynLongIdent sli |> List.cutOffLast + + let indexWithDotExpr = + let identifierExpr = mkLongIdentExprFromSynIdent lastMiddleLink + + // Create an adjacent range for the `[`,`]` in the index expression. + let adjacentRange = + mkRange + indexExpr.Range.FileName + (Position.mkPos + identifierExpr.Range.StartLine + (identifierExpr.Range.StartColumn + 1)) + (Position.mkPos indexExpr.Range.EndLine (indexExpr.Range.EndColumn - 1)) + + SynExpr.App( + ExprAtomicFlag.Atomic, + false, + identifierExpr, + SynExpr.ArrayOrListComputed(false, indexExpr, adjacentRange), + unionRanges identifierExpr.Range indexExpr.Range + ) + + middleLinks, indexWithDotExpr + + continuation + [ yield! leftLinks + yield LinkExpr.Dot mDot + yield! middleLinks + yield LinkExpr.Expr lastExpr ]) + | SynExpr.App(isInfix = false; funcExpr = SynExpr.DotGet _ as funcExpr; argExpr = argExpr) -> visit funcExpr (fun leftLinks -> match List.tryLast leftLinks with | Some(LinkExpr.Identifier(identifierExpr)) -> match argExpr with | UnitExpr mUnit -> - let leftLinks = List.take (leftLinks.Length - 1) leftLinks + let leftLinks = List.cutOffLast leftLinks // Compose a function application by taking the last identifier of the SynExpr.DotGet // and the following argument expression. @@ -730,7 +779,7 @@ let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = continuation [ yield! leftLinks; yield rightLink ] | ParenExpr(lpr, e, rpr, pr) -> - let leftLinks = List.take (leftLinks.Length - 1) leftLinks + let leftLinks = List.cutOffLast leftLinks // Example: A().B(fun b -> b) let rightLink = LinkExpr.AppParen(identifierExpr, lpr, e, rpr, pr) continuation [ yield! leftLinks; yield rightLink ] @@ -772,7 +821,7 @@ let (|ChainExpr|_|) (e: SynExpr) : LinkExpr list option = |> LinkExpr.Expr ] | _ -> [] - let leftLinks = List.take (leftLinks.Length - 1) leftLinks + let leftLinks = List.cutOffLast leftLinks continuation [ yield! leftLinks; yield! app ]) | SynExpr.TypeApp _ as typeApp -> mkLinksFromFunctionName LinkExpr.Identifier typeApp |> continuation @@ -1104,14 +1153,7 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr = ExprInfixAppNode(mkExpr creationAide e1, operator, mkExpr creationAide e2, exprRange) |> Expr.InfixApp - | SynExpr.App(ExprAtomicFlag.Atomic, false, identifierExpr, SynExpr.ArrayOrListComputed(false, indexExpr, _), _) -> - ExprIndexWithoutDotNode(mkExpr creationAide identifierExpr, mkExpr creationAide indexExpr, exprRange) - |> Expr.IndexWithoutDot - | SynExpr.App(ExprAtomicFlag.NonAtomic, - false, - identifierExpr, - (SynExpr.ArrayOrListComputed(isArray = false; expr = indexExpr) as argExpr), - _) when (RangeHelpers.isAdjacentTo identifierExpr.Range argExpr.Range) -> + | IndexWithoutDot(identifierExpr, indexExpr) -> ExprIndexWithoutDotNode(mkExpr creationAide identifierExpr, mkExpr creationAide indexExpr, exprRange) |> Expr.IndexWithoutDot diff --git a/src/Fantomas.Core/Utils.fs b/src/Fantomas.Core/Utils.fs index 2924ab13bc..deec0b0ffa 100644 --- a/src/Fantomas.Core/Utils.fs +++ b/src/Fantomas.Core/Utils.fs @@ -2,6 +2,7 @@ namespace Fantomas.Core open System open System.Text.RegularExpressions +open Microsoft.FSharp.Core.CompilerServices [] module String = @@ -102,6 +103,20 @@ module List = visit xs id + let cutOffLast list = + let mutable headList = ListCollector<'a>() + + let rec visit list = + match list with + | [] + | [ _ ] -> () + | head :: tail -> + headList.Add(head) + visit tail + + visit list + headList.Close() + module Async = let map f computation = async.Bind(computation, f >> async.Return) diff --git a/src/Fantomas.Core/Utils.fsi b/src/Fantomas.Core/Utils.fsi index 2973a861ec..16f65f0b52 100644 --- a/src/Fantomas.Core/Utils.fsi +++ b/src/Fantomas.Core/Utils.fsi @@ -15,6 +15,8 @@ module List = val moreThanOne: ('a list -> bool) val partitionWhile: f: (int -> 'a -> bool) -> xs: 'a list -> 'a list * 'a list val mapWithLast: f: ('a -> 'b) -> g: ('a -> 'b) -> xs: 'a list -> 'b list + /// Removes the last element of a list + val cutOffLast: 'a list -> 'a list module Async = val map: f: ('a -> 'b) -> computation: Async<'a> -> Async<'b>