From 289a3183a39ddb7d074894d079f5c281fd1c0a81 Mon Sep 17 00:00:00 2001 From: nojaf Date: Mon, 26 Nov 2018 08:03:24 +0100 Subject: [PATCH] Extracted Context types and functions from FormatConfig. --- src/Fantomas.Tests/TestHelpers.fs | 2 +- src/Fantomas/CodeFormatter.fsx | 3 +- src/Fantomas/CodeFormatterImpl.fs | 16 +- src/Fantomas/CodePrinter.fs | 1 + src/Fantomas/Context.fs | 359 ++++++++++++++++++++++++++++++ src/Fantomas/Fantomas.fsproj | 3 +- src/Fantomas/FormatConfig.fs | 358 ----------------------------- src/Fantomas/SourceParser.fs | 2 +- src/Fantomas/SourceTransformer.fs | 2 +- src/Fantomas/TokenMatcher.fs | 6 +- 10 files changed, 379 insertions(+), 373 deletions(-) create mode 100644 src/Fantomas/Context.fs diff --git a/src/Fantomas.Tests/TestHelpers.fs b/src/Fantomas.Tests/TestHelpers.fs index 749f0607a1..b00b5e9e64 100644 --- a/src/Fantomas.Tests/TestHelpers.fs +++ b/src/Fantomas.Tests/TestHelpers.fs @@ -84,7 +84,7 @@ let printAST isFsiFile sourceCode = let printContext sourceCode = let normalizedSourceCode = Fantomas.String.normalizeNewLine sourceCode - let context = Fantomas.FormatConfig.Context.create config normalizedSourceCode + let context = Fantomas.Context.Context.create config normalizedSourceCode printfn "directives:" context.Directives |> Seq.iter (fun kv -> printfn "%A %s" kv.Key kv.Value) diff --git a/src/Fantomas/CodeFormatter.fsx b/src/Fantomas/CodeFormatter.fsx index b2222f9055..ed9cbac308 100644 --- a/src/Fantomas/CodeFormatter.fsx +++ b/src/Fantomas/CodeFormatter.fsx @@ -1,8 +1,9 @@ #r "../../packages/FSharp.Compiler.Service/lib/net45/FSharp.Compiler.Service.dll" #load "Utils.fs" -#load "TokenMatcher.fs" #load "FormatConfig.fs" +#load "TokenMatcher.fs" +#load "Context.fs" #load "SourceParser.fs" #load "SourceTransformer.fs" #load "CodePrinter.fs" diff --git a/src/Fantomas/CodeFormatterImpl.fs b/src/Fantomas/CodeFormatterImpl.fs index 26641bc9c6..e3302b0421 100644 --- a/src/Fantomas/CodeFormatterImpl.fs +++ b/src/Fantomas/CodeFormatterImpl.fs @@ -359,11 +359,11 @@ let formatWith ast formatContext config = let sourceCode = defaultArg input String.Empty let normalizedSourceCode = String.normalizeNewLine sourceCode let formattedSourceCode = - Context.create config normalizedSourceCode + Fantomas.Context.Context.create config normalizedSourceCode |> genParsedInput { ASTContext.Default with TopLevelModuleName = moduleName } ast - |> dump + |> Context.dump |> if config.StrictMode then id - else integrateComments config.PreserveEndOfLine config.SpaceAroundDelimiter formatContext.ProjectOptions.ConditionalCompilationDefines normalizedSourceCode + else integrateComments config formatContext.ProjectOptions.ConditionalCompilationDefines normalizedSourceCode // Sometimes F# parser gives a partial AST for incorrect input if input.IsSome && String.IsNullOrWhiteSpace normalizedSourceCode <> String.IsNullOrWhiteSpace formattedSourceCode then @@ -536,13 +536,13 @@ let formatRange returnFormattedContentOnly (range : range) (lines : _ []) config let reconstructSourceCode startCol formatteds pre post = Debug.WriteLine("Formatted parts: '{0}' at column {1}", sprintf "%A" formatteds, startCol) // Realign results on the correct column - Context.create config String.Empty + Context.Context.create config String.Empty // Mono version of indent text writer behaves differently from .NET one, // So we add an empty string first to regularize it - |> if returnFormattedContentOnly then str String.Empty else str pre - |> atIndentLevel startCol (col sepNln formatteds str) - |> if returnFormattedContentOnly then str String.Empty else str post - |> dump + |> if returnFormattedContentOnly then Context.str String.Empty else Context.str pre + |> Context.atIndentLevel startCol (Context.col Context.sepNln formatteds Context.str) + |> if returnFormattedContentOnly then Context.str String.Empty else Context.str post + |> Context.dump async { match patch with diff --git a/src/Fantomas/CodePrinter.fs b/src/Fantomas/CodePrinter.fs index 3afce03d62..57a50a9892 100644 --- a/src/Fantomas/CodePrinter.fs +++ b/src/Fantomas/CodePrinter.fs @@ -6,6 +6,7 @@ open Fantomas open Fantomas.FormatConfig open Fantomas.SourceParser open Fantomas.SourceTransformer +open Fantomas.Context /// This type consists of contextual information which is important for formatting type ASTContext = diff --git a/src/Fantomas/Context.fs b/src/Fantomas/Context.fs new file mode 100644 index 0000000000..486e582434 --- /dev/null +++ b/src/Fantomas/Context.fs @@ -0,0 +1,359 @@ +module Fantomas.Context + +open System +open System.IO +open System.Collections.Generic +open System.CodeDom.Compiler +open Microsoft.FSharp.Compiler.Range +open Fantomas.FormatConfig +open Fantomas.TokenMatcher + +/// Wrapping IndentedTextWriter with current column position +type ColumnIndentedTextWriter(tw : TextWriter) = + let indentWriter = new IndentedTextWriter(tw, " ") + let mutable col = indentWriter.Indent + + member __.Write(s : string) = + match s.LastIndexOf('\n') with + | -1 -> col <- col + s.Length + | i -> col <- s.Length - i - 1 + indentWriter.Write(s) + + member __.WriteLine(s : string) = + col <- indentWriter.Indent + indentWriter.WriteLine(s) + + /// Current column of the page in an absolute manner + member __.Column + with get() = col + and set i = col <- i + + member __.Indent + with get() = indentWriter.Indent + and set i = indentWriter.Indent <- i + + member __.InnerWriter = indentWriter.InnerWriter + + interface IDisposable with + member __.Dispose() = + indentWriter.Dispose() + +type internal Context = + { Config : FormatConfig; + Writer : ColumnIndentedTextWriter; + mutable BreakLines : bool; + BreakOn : string -> bool; + /// The original source string to query as a last resort + Content : string; + /// Positions of new lines in the original source string + Positions : int []; + /// Comments attached to appropriate locations + Comments : Dictionary; + /// Compiler directives attached to appropriate locations + Directives : Dictionary } + + /// Initialize with a string writer and use space as delimiter + static member Default = + { Config = FormatConfig.Default; + Writer = new ColumnIndentedTextWriter(new StringWriter()); + BreakLines = true; BreakOn = (fun _ -> false); + Content = ""; Positions = [||]; Comments = Dictionary(); Directives = Dictionary() } + + static member create config (content : string) = + let content = String.normalizeNewLine content + let positions = + content.Split('\n') + |> Seq.map (fun s -> String.length s + 1) + |> Seq.scan (+) 0 + |> Seq.toArray + let (comments, directives) = filterCommentsAndDirectives content + { Context.Default with + Config = config; Content = content; Positions = positions; + Comments = comments; Directives = directives } + + member x.With(writer : ColumnIndentedTextWriter) = + writer.Indent <- x.Writer.Indent + writer.Column <- x.Writer.Column + // Use infinite column width to encounter worst-case scenario + let config = { x.Config with PageWidth = Int32.MaxValue } + { x with Writer = writer; Config = config } + +let internal dump (ctx: Context) = + ctx.Writer.InnerWriter.ToString() + +// A few utility functions from https://github.com/fsharp/powerpack/blob/master/src/FSharp.Compiler.CodeDom/generator.fs + +/// Indent one more level based on configuration +let internal indent (ctx : Context) = + ctx.Writer.Indent <- ctx.Writer.Indent + ctx.Config.IndentSpaceNum + ctx + +/// Unindent one more level based on configuration +let internal unindent (ctx : Context) = + ctx.Writer.Indent <- max 0 (ctx.Writer.Indent - ctx.Config.IndentSpaceNum) + ctx + +/// Increase indent by i spaces +let internal incrIndent i (ctx : Context) = + ctx.Writer.Indent <- ctx.Writer.Indent + i + ctx + +/// Decrease indent by i spaces +let internal decrIndent i (ctx : Context) = + ctx.Writer.Indent <- max 0 (ctx.Writer.Indent - i) + ctx + +/// Apply function f at an absolute indent level (use with care) +let internal atIndentLevel level (f : Context -> Context) ctx = + if level < 0 then + invalidArg "level" "The indent level cannot be negative." + let oldLevel = ctx.Writer.Indent + ctx.Writer.Indent <- level + let result = f ctx + ctx.Writer.Indent <- oldLevel + result + +/// Write everything at current column indentation +let internal atCurrentColumn (f : _ -> Context) (ctx : Context) = + atIndentLevel ctx.Writer.Column f ctx + +/// Function composition operator +let internal (+>) (ctx : Context -> Context) (f : _ -> Context) x = + f (ctx x) + +/// Break-line and append specified string +let internal (++) (ctx : Context -> Context) (str : string) x = + let c = ctx x + c.Writer.WriteLine("") + c.Writer.Write(str) + c + +/// Break-line if config says so +let internal (+-) (ctx : Context -> Context) (str : string) x = + let c = ctx x + if c.BreakOn str then + c.Writer.WriteLine("") + else + c.Writer.Write(" ") + c.Writer.Write(str) + c + +/// Append specified string without line-break +let internal (--) (ctx : Context -> Context) (str : string) x = + let c = ctx x + c.Writer.Write(str) + c + +let internal (!-) (str : string) = id -- str +let internal (!+) (str : string) = id ++ str + +/// Print object converted to string +let internal str (o : 'T) (ctx : Context) = + ctx.Writer.Write(o.ToString()) + ctx + +/// Similar to col, and supply index as well +let internal coli f' (c : seq<'T>) f (ctx : Context) = + let mutable tryPick = true + let mutable st = ctx + let mutable i = 0 + let e = c.GetEnumerator() + while (e.MoveNext()) do + if tryPick then tryPick <- false else st <- f' st + st <- f i (e.Current) st + i <- i + 1 + st + +/// Process collection - keeps context through the whole processing +/// calls f for every element in sequence and f' between every two elements +/// as a separator. This is a variant that works on typed collections. +let internal col f' (c : seq<'T>) f (ctx : Context) = + let mutable tryPick = true + let mutable st = ctx + let e = c.GetEnumerator() + while (e.MoveNext()) do + if tryPick then tryPick <- false else st <- f' st + st <- f (e.Current) st + st + +/// Similar to col, apply one more function f2 at the end if the input sequence is not empty +let internal colPost f2 f1 (c : seq<'T>) f (ctx : Context) = + if Seq.isEmpty c then ctx + else f2 (col f1 c f ctx) + +/// Similar to col, apply one more function f2 at the beginning if the input sequence is not empty +let internal colPre f2 f1 (c : seq<'T>) f (ctx : Context) = + if Seq.isEmpty c then ctx + else col f1 c f (f2 ctx) + +/// If there is a value, apply f and f' accordingly, otherwise do nothing +let internal opt (f' : Context -> _) o f (ctx : Context) = + match o with + | Some x -> f' (f x ctx) + | None -> ctx + +/// Similar to opt, but apply f2 at the beginning if there is a value +let internal optPre (f2 : _ -> Context) (f1 : Context -> _) o f (ctx : Context) = + match o with + | Some x -> f1 (f x (f2 ctx)) + | None -> ctx + +/// b is true, apply f1 otherwise apply f2 +let internal ifElse b (f1 : Context -> Context) f2 (ctx : Context) = + if b then f1 ctx else f2 ctx + +/// Repeat application of a function n times +let internal rep n (f : Context -> Context) (ctx : Context) = + [1..n] |> List.fold (fun c _ -> f c) ctx + +let internal wordAnd = !- " and " +let internal wordOr = !- " or " +let internal wordOf = !- " of " + +// Separator functions + +let internal sepDot = !- "." +let internal sepSpace = !- " " +let internal sepNln = !+ "" +let internal sepStar = !- " * " +let internal sepEq = !- " =" +let internal sepArrow = !- " -> " +let internal sepWild = !- "_" +let internal sepNone = id +let internal sepBar = !- "| " + +/// opening token of list +let internal sepOpenL (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str "[ " ctx else str "[" ctx + +/// closing token of list +let internal sepCloseL (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str " ]" ctx else str "]" ctx + +/// opening token of list +let internal sepOpenLFixed = !- "[" + +/// closing token of list +let internal sepCloseLFixed = !- "]" + +/// opening token of array +let internal sepOpenA (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str "[| " ctx else str "[|" ctx + +/// closing token of array +let internal sepCloseA (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str " |]" ctx else str "|]" ctx + +/// opening token of list +let internal sepOpenAFixed = !- "[|" +/// closing token of list +let internal sepCloseAFixed = !- "|]" + +/// opening token of sequence +let internal sepOpenS (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str "{ " ctx else str "{" ctx + +/// closing token of sequence +let internal sepCloseS (ctx : Context) = + if ctx.Config.SpaceAroundDelimiter then str " }" ctx else str "}" ctx + +/// opening token of sequence +let internal sepOpenSFixed = !- "{" + +/// closing token of sequence +let internal sepCloseSFixed = !- "}" + +/// opening token of tuple +let internal sepOpenT = !- "(" + +/// closing token of tuple +let internal sepCloseT = !- ")" + +let internal autoNlnCheck f sep (ctx : Context) = + if not ctx.BreakLines then false else + // Create a dummy context to evaluate length of current operation + use colWriter = new ColumnIndentedTextWriter(new StringWriter()) + let dummyCtx = ctx.With(colWriter) + let col = (dummyCtx |> sep |> f).Writer.Column + // This isn't accurate if we go to new lines + col > ctx.Config.PageWidth + +let internal futureNlnCheck f sep (ctx : Context) = + if not ctx.BreakLines then false else + // Create a dummy context to evaluate length of current operation + use colWriter = new ColumnIndentedTextWriter(new StringWriter()) + let dummyCtx = ctx.With(colWriter) + let writer = (dummyCtx |> sep |> f).Writer + let str = writer.InnerWriter.ToString() + let withoutStringConst = + str.Replace("\\\\", System.String.Empty).Replace("\\\"", System.String.Empty).Split([|'"'|]) + |> Seq.indexed |> Seq.filter (fun (i, _) -> i % 2 = 0) |> Seq.map snd |> String.concat System.String.Empty + let lines = withoutStringConst.Split([|Environment.NewLine|], StringSplitOptions.RemoveEmptyEntries) + + (lines |> Seq.length) > 2 + +/// Set a checkpoint to break at an appropriate column +let internal autoNlnOrAddSep f sep (ctx : Context) = + let isNln = autoNlnCheck f sep ctx + if isNln then + f (sepNln ctx) + else + f (sep ctx) + +let internal autoNln f (ctx : Context) = autoNlnOrAddSep f sepNone ctx + +let internal autoNlnOrSpace f (ctx : Context) = autoNlnOrAddSep f sepSpace ctx + +/// Similar to col, skip auto newline for index 0 +let internal colAutoNlnSkip0i f' (c : seq<'T>) f (ctx : Context) = + coli f' c (fun i c -> if i = 0 then f i c else autoNln (f i c)) ctx + +/// Similar to col, skip auto newline for index 0 +let internal colAutoNlnSkip0 f' c f = colAutoNlnSkip0i f' c (fun _ -> f) + +/// Skip all auto-breaking newlines +let internal noNln f (ctx : Context) : Context = + ctx.BreakLines <- false + let res = f ctx + ctx.BreakLines <- true + res + +let internal sepColon (ctx : Context) = + if ctx.Config.SpaceBeforeColon then str " : " ctx else str ": " ctx + +let internal sepColonFixed = !- ":" + +let internal sepComma (ctx : Context) = + if ctx.Config.SpaceAfterComma then str ", " ctx else str "," ctx + +let internal sepSemi (ctx : Context) = + if ctx.Config.SpaceAfterSemicolon then str "; " ctx else str ";" ctx + +let internal sepSemiNln (ctx : Context) = + // sepNln part is essential to indentation + if ctx.Config.SemicolonAtEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx + +let internal sepBeforeArg (ctx : Context) = + if ctx.Config.SpaceBeforeArgument then str " " ctx else str "" ctx + +/// Conditional indentation on with keyword +let internal indentOnWith (ctx : Context) = + if ctx.Config.IndentOnTryWith then indent ctx else ctx + +/// Conditional unindentation on with keyword +let internal unindentOnWith (ctx : Context) = + if ctx.Config.IndentOnTryWith then unindent ctx else ctx + +let internal sortAndDeduplicate by l (ctx : Context) = + if ctx.Config.ReorderOpenDeclaration then + l |> Seq.distinctBy by |> Seq.sortBy by |> List.ofSeq + else l + +/// Don't put space before and after these operators +let internal NoSpaceInfixOps = set [".."; "?"] + +/// Always break into newlines on these operators +let internal NewLineInfixOps = set ["|>"; "||>"; "|||>"; ">>"; ">>="] + +/// Never break into newlines on these operators +let internal NoBreakInfixOps = set ["="; ">"; "<";] diff --git a/src/Fantomas/Fantomas.fsproj b/src/Fantomas/Fantomas.fsproj index 8f002d979a..606faa4244 100644 --- a/src/Fantomas/Fantomas.fsproj +++ b/src/Fantomas/Fantomas.fsproj @@ -9,8 +9,9 @@ - + + diff --git a/src/Fantomas/FormatConfig.fs b/src/Fantomas/FormatConfig.fs index 8966686090..0cc3752da2 100644 --- a/src/Fantomas/FormatConfig.fs +++ b/src/Fantomas/FormatConfig.fs @@ -1,13 +1,6 @@ module Fantomas.FormatConfig open System -open System.IO -open System.Collections.Generic -open System.CodeDom.Compiler - -open Microsoft.FSharp.Compiler.Range -open Fantomas -open Fantomas.TokenMatcher type FormatException(msg : string) = inherit Exception(msg) @@ -85,354 +78,3 @@ type FormatConfig = ReorderOpenDeclaration = reorderOpenDeclaration; SpaceAroundDelimiter = spaceAroundDelimiter; StrictMode = strictMode } - -/// Wrapping IndentedTextWriter with current column position -type ColumnIndentedTextWriter(tw : TextWriter) = - let indentWriter = new IndentedTextWriter(tw, " ") - let mutable col = indentWriter.Indent - - member __.Write(s : string) = - match s.LastIndexOf('\n') with - | -1 -> col <- col + s.Length - | i -> col <- s.Length - i - 1 - indentWriter.Write(s) - - member __.WriteLine(s : string) = - col <- indentWriter.Indent - indentWriter.WriteLine(s) - - /// Current column of the page in an absolute manner - member __.Column - with get() = col - and set i = col <- i - - member __.Indent - with get() = indentWriter.Indent - and set i = indentWriter.Indent <- i - - member __.InnerWriter = indentWriter.InnerWriter - - interface IDisposable with - member __.Dispose() = - indentWriter.Dispose() - -type internal Context = - { Config : FormatConfig; - Writer : ColumnIndentedTextWriter; - mutable BreakLines : bool; - BreakOn : string -> bool; - /// The original source string to query as a last resort - Content : string; - /// Positions of new lines in the original source string - Positions : int []; - /// Comments attached to appropriate locations - Comments : Dictionary; - /// Compiler directives attached to appropriate locations - Directives : Dictionary } - - /// Initialize with a string writer and use space as delimiter - static member Default = - { Config = FormatConfig.Default; - Writer = new ColumnIndentedTextWriter(new StringWriter()); - BreakLines = true; BreakOn = (fun _ -> false); - Content = ""; Positions = [||]; Comments = Dictionary(); Directives = Dictionary() } - - static member create config (content : string) = - let content = String.normalizeNewLine content - let positions = - content.Split('\n') - |> Seq.map (fun s -> String.length s + 1) - |> Seq.scan (+) 0 - |> Seq.toArray - let (comments, directives) = filterCommentsAndDirectives content - { Context.Default with - Config = config; Content = content; Positions = positions; - Comments = comments; Directives = directives } - - member x.With(writer : ColumnIndentedTextWriter) = - writer.Indent <- x.Writer.Indent - writer.Column <- x.Writer.Column - // Use infinite column width to encounter worst-case scenario - let config = { x.Config with PageWidth = Int32.MaxValue } - { x with Writer = writer; Config = config } - -let internal dump (ctx: Context) = - ctx.Writer.InnerWriter.ToString() - -// A few utility functions from https://github.com/fsharp/powerpack/blob/master/src/FSharp.Compiler.CodeDom/generator.fs - -/// Indent one more level based on configuration -let internal indent (ctx : Context) = - ctx.Writer.Indent <- ctx.Writer.Indent + ctx.Config.IndentSpaceNum - ctx - -/// Unindent one more level based on configuration -let internal unindent (ctx : Context) = - ctx.Writer.Indent <- max 0 (ctx.Writer.Indent - ctx.Config.IndentSpaceNum) - ctx - -/// Increase indent by i spaces -let internal incrIndent i (ctx : Context) = - ctx.Writer.Indent <- ctx.Writer.Indent + i - ctx - -/// Decrease indent by i spaces -let internal decrIndent i (ctx : Context) = - ctx.Writer.Indent <- max 0 (ctx.Writer.Indent - i) - ctx - -/// Apply function f at an absolute indent level (use with care) -let internal atIndentLevel level (f : Context -> Context) ctx = - if level < 0 then - invalidArg "level" "The indent level cannot be negative." - let oldLevel = ctx.Writer.Indent - ctx.Writer.Indent <- level - let result = f ctx - ctx.Writer.Indent <- oldLevel - result - -/// Write everything at current column indentation -let internal atCurrentColumn (f : _ -> Context) (ctx : Context) = - atIndentLevel ctx.Writer.Column f ctx - -/// Function composition operator -let internal (+>) (ctx : Context -> Context) (f : _ -> Context) x = - f (ctx x) - -/// Break-line and append specified string -let internal (++) (ctx : Context -> Context) (str : string) x = - let c = ctx x - c.Writer.WriteLine("") - c.Writer.Write(str) - c - -/// Break-line if config says so -let internal (+-) (ctx : Context -> Context) (str : string) x = - let c = ctx x - if c.BreakOn str then - c.Writer.WriteLine("") - else - c.Writer.Write(" ") - c.Writer.Write(str) - c - -/// Append specified string without line-break -let internal (--) (ctx : Context -> Context) (str : string) x = - let c = ctx x - c.Writer.Write(str) - c - -let internal (!-) (str : string) = id -- str -let internal (!+) (str : string) = id ++ str - -/// Print object converted to string -let internal str (o : 'T) (ctx : Context) = - ctx.Writer.Write(o.ToString()) - ctx - -/// Similar to col, and supply index as well -let internal coli f' (c : seq<'T>) f (ctx : Context) = - let mutable tryPick = true - let mutable st = ctx - let mutable i = 0 - let e = c.GetEnumerator() - while (e.MoveNext()) do - if tryPick then tryPick <- false else st <- f' st - st <- f i (e.Current) st - i <- i + 1 - st - -/// Process collection - keeps context through the whole processing -/// calls f for every element in sequence and f' between every two elements -/// as a separator. This is a variant that works on typed collections. -let internal col f' (c : seq<'T>) f (ctx : Context) = - let mutable tryPick = true - let mutable st = ctx - let e = c.GetEnumerator() - while (e.MoveNext()) do - if tryPick then tryPick <- false else st <- f' st - st <- f (e.Current) st - st - -/// Similar to col, apply one more function f2 at the end if the input sequence is not empty -let internal colPost f2 f1 (c : seq<'T>) f (ctx : Context) = - if Seq.isEmpty c then ctx - else f2 (col f1 c f ctx) - -/// Similar to col, apply one more function f2 at the beginning if the input sequence is not empty -let internal colPre f2 f1 (c : seq<'T>) f (ctx : Context) = - if Seq.isEmpty c then ctx - else col f1 c f (f2 ctx) - -/// If there is a value, apply f and f' accordingly, otherwise do nothing -let internal opt (f' : Context -> _) o f (ctx : Context) = - match o with - | Some x -> f' (f x ctx) - | None -> ctx - -/// Similar to opt, but apply f2 at the beginning if there is a value -let internal optPre (f2 : _ -> Context) (f1 : Context -> _) o f (ctx : Context) = - match o with - | Some x -> f1 (f x (f2 ctx)) - | None -> ctx - -/// b is true, apply f1 otherwise apply f2 -let internal ifElse b (f1 : Context -> Context) f2 (ctx : Context) = - if b then f1 ctx else f2 ctx - -/// Repeat application of a function n times -let internal rep n (f : Context -> Context) (ctx : Context) = - [1..n] |> List.fold (fun c _ -> f c) ctx - -let internal wordAnd = !- " and " -let internal wordOr = !- " or " -let internal wordOf = !- " of " - -// Separator functions - -let internal sepDot = !- "." -let internal sepSpace = !- " " -let internal sepNln = !+ "" -let internal sepStar = !- " * " -let internal sepEq = !- " =" -let internal sepArrow = !- " -> " -let internal sepWild = !- "_" -let internal sepNone = id -let internal sepBar = !- "| " - -/// opening token of list -let internal sepOpenL (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str "[ " ctx else str "[" ctx - -/// closing token of list -let internal sepCloseL (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str " ]" ctx else str "]" ctx - -/// opening token of list -let internal sepOpenLFixed = !- "[" - -/// closing token of list -let internal sepCloseLFixed = !- "]" - -/// opening token of array -let internal sepOpenA (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str "[| " ctx else str "[|" ctx - -/// closing token of array -let internal sepCloseA (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str " |]" ctx else str "|]" ctx - -/// opening token of list -let internal sepOpenAFixed = !- "[|" -/// closing token of list -let internal sepCloseAFixed = !- "|]" - -/// opening token of sequence -let internal sepOpenS (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str "{ " ctx else str "{" ctx - -/// closing token of sequence -let internal sepCloseS (ctx : Context) = - if ctx.Config.SpaceAroundDelimiter then str " }" ctx else str "}" ctx - -/// opening token of sequence -let internal sepOpenSFixed = !- "{" - -/// closing token of sequence -let internal sepCloseSFixed = !- "}" - -/// opening token of tuple -let internal sepOpenT = !- "(" - -/// closing token of tuple -let internal sepCloseT = !- ")" - -let internal autoNlnCheck f sep (ctx : Context) = - if not ctx.BreakLines then false else - // Create a dummy context to evaluate length of current operation - use colWriter = new ColumnIndentedTextWriter(new StringWriter()) - let dummyCtx = ctx.With(colWriter) - let col = (dummyCtx |> sep |> f).Writer.Column - // This isn't accurate if we go to new lines - col > ctx.Config.PageWidth - -let internal futureNlnCheck f sep (ctx : Context) = - if not ctx.BreakLines then false else - // Create a dummy context to evaluate length of current operation - use colWriter = new ColumnIndentedTextWriter(new StringWriter()) - let dummyCtx = ctx.With(colWriter) - let writer = (dummyCtx |> sep |> f).Writer - let str = writer.InnerWriter.ToString() - let withoutStringConst = - str.Replace("\\\\", System.String.Empty).Replace("\\\"", System.String.Empty).Split([|'"'|]) - |> Seq.indexed |> Seq.filter (fun (i, _) -> i % 2 = 0) |> Seq.map snd |> String.concat System.String.Empty - let lines = withoutStringConst.Split([|Environment.NewLine|], StringSplitOptions.RemoveEmptyEntries) - - (lines |> Seq.length) > 2 - -/// Set a checkpoint to break at an appropriate column -let internal autoNlnOrAddSep f sep (ctx : Context) = - let isNln = autoNlnCheck f sep ctx - if isNln then - f (sepNln ctx) - else - f (sep ctx) - -let internal autoNln f (ctx : Context) = autoNlnOrAddSep f sepNone ctx - -let internal autoNlnOrSpace f (ctx : Context) = autoNlnOrAddSep f sepSpace ctx - -/// Similar to col, skip auto newline for index 0 -let internal colAutoNlnSkip0i f' (c : seq<'T>) f (ctx : Context) = - coli f' c (fun i c -> if i = 0 then f i c else autoNln (f i c)) ctx - -/// Similar to col, skip auto newline for index 0 -let internal colAutoNlnSkip0 f' c f = colAutoNlnSkip0i f' c (fun _ -> f) - -/// Skip all auto-breaking newlines -let internal noNln f (ctx : Context) : Context = - ctx.BreakLines <- false - let res = f ctx - ctx.BreakLines <- true - res - -let internal sepColon (ctx : Context) = - if ctx.Config.SpaceBeforeColon then str " : " ctx else str ": " ctx - -let internal sepColonFixed = !- ":" - -let internal sepComma (ctx : Context) = - if ctx.Config.SpaceAfterComma then str ", " ctx else str "," ctx - -let internal sepSemi (ctx : Context) = - if ctx.Config.SpaceAfterSemicolon then str "; " ctx else str ";" ctx - -let internal sepSemiNln (ctx : Context) = - // sepNln part is essential to indentation - if ctx.Config.SemicolonAtEndOfLine then (!- ";" +> sepNln) ctx else sepNln ctx - -let internal sepBeforeArg (ctx : Context) = - if ctx.Config.SpaceBeforeArgument then str " " ctx else str "" ctx - -/// Conditional indentation on with keyword -let internal indentOnWith (ctx : Context) = - if ctx.Config.IndentOnTryWith then indent ctx else ctx - -/// Conditional unindentation on with keyword -let internal unindentOnWith (ctx : Context) = - if ctx.Config.IndentOnTryWith then unindent ctx else ctx - -let internal sortAndDeduplicate by l (ctx : Context) = - if ctx.Config.ReorderOpenDeclaration then - l |> Seq.distinctBy by |> Seq.sortBy by |> List.ofSeq - else l - -/// Don't put space before and after these operators -let internal NoSpaceInfixOps = set [".."; "?"] - -/// Always break into newlines on these operators -let internal NewLineInfixOps = set ["|>"; "||>"; "|||>"; ">>"; ">>="] - -/// Never break into newlines on these operators -let internal NoBreakInfixOps = set ["="; ">"; "<";] - diff --git a/src/Fantomas/SourceParser.fs b/src/Fantomas/SourceParser.fs index f0114b9436..9da60b38ed 100644 --- a/src/Fantomas/SourceParser.fs +++ b/src/Fantomas/SourceParser.fs @@ -6,7 +6,7 @@ open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.Ast open Microsoft.FSharp.Compiler.PrettyNaming open Fantomas -open Fantomas.FormatConfig +open Fantomas.Context open Microsoft.FSharp.Compiler.SourceCodeServices.PrettyNaming type Composite<'a, 'b> = diff --git a/src/Fantomas/SourceTransformer.fs b/src/Fantomas/SourceTransformer.fs index 2ef2f097db..77176ad085 100644 --- a/src/Fantomas/SourceTransformer.fs +++ b/src/Fantomas/SourceTransformer.fs @@ -1,7 +1,7 @@ module internal Fantomas.SourceTransformer open Fantomas -open Fantomas.FormatConfig +open Fantomas.Context open Fantomas.SourceParser open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.Range diff --git a/src/Fantomas/TokenMatcher.fs b/src/Fantomas/TokenMatcher.fs index 56df897cac..ff604bb085 100644 --- a/src/Fantomas/TokenMatcher.fs +++ b/src/Fantomas/TokenMatcher.fs @@ -445,7 +445,9 @@ let (|OpenChunk|_|) = function /// Assume that originalText and newText are derived from the same AST. /// Pick all comments and directives from originalText to insert into newText -let integrateComments isPreserveEOL isSpaceAroundDelimiter compilationDefines (originalText : string) (newText : string) = +let integrateComments (config:Fantomas.FormatConfig.FormatConfig) compilationDefines (originalText : string) (newText : string) = + let isPreserveEOL = config.PreserveEndOfLine + let trim (txt : string) = if not isPreserveEOL then txt else Regex.Replace(String.normalizeNewLine txt, @"[ \t]+$", "", RegexOptions.Multiline) @@ -719,7 +721,7 @@ let integrateComments isPreserveEOL isSpaceAroundDelimiter compilationDefines (o moreNewTokens else match moreNewTokens with - | Space _::LParen _::rs when (not isSpaceAroundDelimiter) -> + | Space _::LParen _::rs when (not config.SpaceAroundDelimiter) -> List.skip 1 moreNewTokens | Space t::rs -> addText " "