diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d949cc887..d974233c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ # Changelog -## [Unreleased] +## [8.0.5] - 2026-09-25 ### Changed - Improved formatting performance. Formatting the source files of dotnet/fsharp allocates around 40% less memory and is roughly 1.4 times faster than `8.0.4`, depending on hardware. The fields of `WriterEvent`, returned by `CodeFormatter.GetWriterEventsAsync`, are renamed so that cases share them: `text` for the strings, `amount` for `IndentBy` and `UnIndentBy`, `column` for the indent and column cases, and `nodeType` and `range` for `NodeEnd`. [#3499](https://github.com/fsprojects/fantomas/pull/3499) +- Update FCS to 'Record the range of the `exception`in SynExceptionDefnRepr', commit 349d6aaf1e4685a72e43b7f4a0708205e4104b4c [#3500](https://github.com/fsprojects/fantomas/pull/3500) ### Fixed -- Comment between doc comment and exception keyword gets shifted. The syntax tree has no range for the `exception` keyword, so a comment between the XML doc or attributes and the keyword was placed in front of the exception name, after the keyword. The keyword is now looked up in the source text and the comment stays in front of it. [#3483](https://github.com/fsprojects/fantomas/issues/3483) +- Comment between doc comment and exception keyword gets shifted. The syntax tree had no range for the `exception` keyword, so a comment between the XML doc or attributes and the keyword was placed in front of the exception name, after the keyword. The parser now records that range, and the comment stays in front of the keyword. [#3483](https://github.com/fsprojects/fantomas/issues/3483) ## [8.0.4] - 2026-09-23 diff --git a/Directory.Build.props b/Directory.Build.props index b2c656f7da..8e1f8dc399 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -45,7 +45,7 @@ Some common use cases include: - 74ec4f7df70717a162d6ffd23007603cf298fb8b + 349d6aaf1e4685a72e43b7f4a0708205e4104b4c diff --git a/Directory.Packages.props b/Directory.Packages.props index 1eccb6024f..b504d8c2eb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,8 +12,8 @@ - - + + diff --git a/build.fsx b/build.fsx index ffdab50849..4742412163 100755 --- a/build.fsx +++ b/build.fsx @@ -261,6 +261,8 @@ pipeline "Init" { // Not a compiler source. This is the MSBuild task that turns FSComp.txt into the SR // module. Since dotnet/fsharp#20097 the generated diagnostic accessors return RichText // instead of string, and the task shipped in the .NET SDK cannot generate those yet. + // Since dotnet/fsharp#20506 it resolves its paths through TaskEnvironmentPaths. + "src/FSharp.Build/TaskEnvironmentPaths.fs" "src/FSharp.Build/FSharpEmbedResourceText.fs" "src/Compiler/FSComp.txt" "src/Compiler/FSStrings.resx" diff --git a/src/Fantomas.Core/ASTTransformer.fs b/src/Fantomas.Core/ASTTransformer.fs index a22da80173..c838c2cff3 100644 --- a/src/Fantomas.Core/ASTTransformer.fs +++ b/src/Fantomas.Core/ASTTransformer.fs @@ -37,10 +37,6 @@ let longIdentText (sli: SynLongIdent) : string = type CreationAide = { SourceText: ISourceText option - /// The ranges of the line and block comments in the source. - /// Only `mkExceptionKeyword` needs these, to find the `exception` keyword in the source text. - /// Remove this field once the syntax tree has a range for that keyword. - CodeComments: range list } member x.TextFromSource fallback range = @@ -2502,99 +2498,22 @@ let mkModuleName (SynComponentInfo(synType = synType; range = m) as info) : Iden | Some(SynType.LongIdent(SynLongIdent(lid, _, _))) -> mkLongIdent lid | _ -> invariantViolationAbout m info "module name is not an identifier" -/// The `exception` keyword of an exception definition. -/// -/// The syntax tree has no range for it, and the range of the definition starts at its XML doc. -/// It is looked up in the source text, starting after the XML doc and attributes and skipping over -/// the comments the parser found in between, so that a comment there stays in front of the keyword. -/// Without source text, or when anything else comes first, it is placed where the search started. -let mkExceptionKeyword - (creationAide: CreationAide) - (declRange: range) - (xmlDoc: XmlDocNode option) - (attributes: MultipleAttributeListNode option) - (nextStart: pos) - : SingleTextNode - = - let keyword: string = "exception" - - let searchStart: pos = - match attributes, xmlDoc with - | Some attributes, _ -> attributes.Range.End - | None, Some xmlDoc -> xmlDoc.Range.End - | None, None -> declRange.Start - - let comments: range list = - creationAide.CodeComments - |> List.filter (fun m -> Position.posGeq m.Start searchStart && Position.posLt m.Start nextStart) - - let isIdentChar (c: char) : bool = - System.Char.IsLetterOrDigit c || c = '_' || c = '\'' - - let isKeywordAt (text: string) (column: int) : bool = - let after: int = column + keyword.Length - - System.String.CompareOrdinal(text, column, keyword, 0, keyword.Length) = 0 - && (after = text.Length || not (isIdentChar text[after])) - - let find (sourceText: ISourceText) : pos = - let rec scan (p: pos) : pos = - if p.Line > sourceText.GetLineCount() || Position.posGeq p nextStart then - // reached the accessibility or the name without finding the keyword - searchStart - else - - let text: string = sourceText.GetLineString(p.Line - 1) - - if p.Column >= text.Length then - // end of the line, continue on the next one - scan (Position.mkPos (p.Line + 1) 0) - elif System.Char.IsWhiteSpace text[p.Column] then - // skip whitespace - scan (Position.mkPos p.Line (p.Column + 1)) - else - - match List.tryFind (fun (m: range) -> Position.posEq m.Start p) comments with - // jump over the comment - | Some comment -> scan comment.End - // the first code after the attributes or XML doc has to be the keyword - | None -> if isKeywordAt text p.Column then p else searchStart - - scan searchStart - - let start: pos = - match creationAide.SourceText with - | None -> searchStart - | Some sourceText -> find sourceText - - stn keyword (mkRange declRange.FileName start (Position.mkPos start.Line (start.Column + keyword.Length))) - let mkModuleDecl (creationAide: CreationAide) (decl: SynModuleDecl) = let declRange = decl.Range match decl with | SynModuleDecl.Expr(e, _) -> mkExpr creationAide e |> ModuleDecl.DeclExpr - | SynModuleDecl.Exception(SynExceptionDefn(SynExceptionDefnRepr(attrs, caseName, _, xmlDoc, vis, _), + | SynModuleDecl.Exception(SynExceptionDefn(SynExceptionDefnRepr(attrs, caseName, _, xmlDoc, vis, _, trivia), withKeyword, ms, _), _) -> - let xmlDoc: XmlDocNode option = mkXmlDoc xmlDoc - let attributes: MultipleAttributeListNode option = mkAttributes creationAide attrs - let accessibility: SingleTextNode option = mkSynAccess vis - let unionCase: UnionCaseNode = mkSynUnionCase creationAide caseName - - let nextStart: pos = - accessibility - |> Option.map (fun (n: SingleTextNode) -> n.Range.Start) - |> Option.defaultValue unionCase.Range.Start - ExceptionDefnNode( - xmlDoc, - attributes, - mkExceptionKeyword creationAide declRange xmlDoc attributes nextStart, - accessibility, - unionCase, + mkXmlDoc xmlDoc, + mkAttributes creationAide attrs, + stn "exception" trivia.ExceptionKeyword, + mkSynAccess vis, + mkSynUnionCase creationAide caseName, Option.map (stn "with") withKeyword, List.map (mkMemberDefn creationAide) ms, declRange @@ -3978,27 +3897,17 @@ let mkModuleSigDecl (creationAide: CreationAide) (decl: SynModuleSigDecl) = let declRange = decl.Range match decl with - | SynModuleSigDecl.Exception(SynExceptionSig(SynExceptionDefnRepr(attrs, caseName, _, xmlDoc, vis, _), + | SynModuleSigDecl.Exception(SynExceptionSig(SynExceptionDefnRepr(attrs, caseName, _, xmlDoc, vis, _, trivia), withKeyword, ms, _), _) -> - let xmlDoc: XmlDocNode option = mkXmlDoc xmlDoc - let attributes: MultipleAttributeListNode option = mkAttributes creationAide attrs - let accessibility: SingleTextNode option = mkSynAccess vis - let unionCase: UnionCaseNode = mkSynUnionCase creationAide caseName - - let nextStart: pos = - accessibility - |> Option.map (fun (n: SingleTextNode) -> n.Range.Start) - |> Option.defaultValue unionCase.Range.Start - ExceptionDefnNode( - xmlDoc, - attributes, - mkExceptionKeyword creationAide declRange xmlDoc attributes nextStart, - accessibility, - unionCase, + mkXmlDoc xmlDoc, + mkAttributes creationAide attrs, + stn "exception" trivia.ExceptionKeyword, + mkSynAccess vis, + mkSynUnionCase creationAide caseName, Option.map (stn "with") withKeyword, List.map (mkMemberSig creationAide) ms, declRange @@ -4414,21 +4323,7 @@ let mkFullTreeRange ast = includeTrivia astRange trivia let mkOak (sourceText: ISourceText option) (ast: ParsedInput) = - let codeComments: range list = - match ast with - | ParsedInput.ImplFile(ParsedImplFileInput(trivia = { CodeComments = codeComments })) - | ParsedInput.SigFile(ParsedSigFileInput(trivia = { CodeComments = codeComments })) -> codeComments - |> List.map ( - function - | CommentTrivia.LineComment m - | CommentTrivia.BlockComment m -> m - ) - - let creationAide: CreationAide = - { - SourceText = sourceText - CodeComments = codeComments - } + let creationAide: CreationAide = { SourceText = sourceText } let fullRange = mkFullTreeRange ast diff --git a/src/Fantomas.FCS.BuildTasks/FSharpEnvironment.fs b/src/Fantomas.FCS.BuildTasks/FSharpEnvironment.fs new file mode 100644 index 0000000000..6c94b58539 --- /dev/null +++ b/src/Fantomas.FCS.BuildTasks/FSharpEnvironment.fs @@ -0,0 +1,19 @@ +namespace Internal.Utilities + +open System.Runtime.InteropServices + +/// The part of the upstream FSharpEnvironment (src/Compiler/Facilities/CompilerLocation.fs) that the +/// vendored TaskEnvironmentPaths.fs refers to. The real module needs the generated UtilsStrings and +/// FSharp.BuildProperties, which this project does not have. +module internal FSharpEnvironment = + + let isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + + /// Only TaskEnvironmentPaths.defaultCompilerToolPath calls this, for the Fsc and Fsi tasks, and + /// FSharpEmbedResourceText never does. + let BinFolderOfDefaultFSharpCompilerUsingEnvironment + (_getEnvironmentVariable: string -> string | null) + (_probePoint: string option) + : string option + = + None diff --git a/src/Fantomas.FCS.BuildTasks/Fantomas.FCS.BuildTasks.fsproj b/src/Fantomas.FCS.BuildTasks/Fantomas.FCS.BuildTasks.fsproj index fb40afb0c5..b3ef91858d 100644 --- a/src/Fantomas.FCS.BuildTasks/Fantomas.FCS.BuildTasks.fsproj +++ b/src/Fantomas.FCS.BuildTasks/Fantomas.FCS.BuildTasks.fsproj @@ -5,7 +5,8 @@ The task shipped in the .NET SDK is older than the compiler sources we vendor: since dotnet/fsharp#20097 the generated diagnostic accessors return RichText instead of string, and the SDK's task cannot generate those. Upstream solves this by bootstrapping its own FSharp.Build, we - solve it by vendoring the one file and compiling it here. + solve it by vendoring the task, and the files it depends on, and compiling them here. + FSharpEnvironment.fs stands in for the upstream module of that name, see the file. This is build tooling. It is never packed and never referenced at runtime. --> @@ -27,6 +28,13 @@ + + NullHelpers.fs + + + + TaskEnvironmentPaths.fs + FSharpEmbedResourceText.fs