Skip to content

Commit

Permalink
Only load the .fantomasignore file once, except for the daemon (#2097)
Browse files Browse the repository at this point in the history
* Only load the .fantomasignore file once, except for the daemon

* Update documentation

* Also traverse upwards in default case

* Make comment less confusing

* Format

* Fix logic to relativise to ignorefile location

* Refactor to add unit tests

* Fix relative path logic again

* Move references to minimal locations

* Update Documentation.md after re-adding search
  • Loading branch information
Smaug123 committed Mar 27, 2022
1 parent b44ad04 commit e76fd06
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 37 deletions.
3 changes: 3 additions & 0 deletions docs/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@ Exclusion applies both to formatting and the format checking.
*.fsx
```

Note that Fantomas only searches for a `.fantomasignore` file in or above its current working directory, if one exists; unlike Git, it does not traverse the filesystem for each input file to find an appropriate ignore file.
(This is not true of the Fantomas daemon. The daemon can't rely on being invoked from the right place, and indeed there may not even be a well-defined notion of "right place" for the formatting tasks the daemon is required to perform, so it does search the filesystem for every file individually.)

## Using the API

See [CodeFormatter.fsi](../src/Fantomas/CodeFormatter.fsi) to view the public API of Fantomas.
Expand Down
9 changes: 9 additions & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ NUGET
System.Runtime (>= 4.3)
System.Text.Encoding (>= 4.3)
System.Threading.Tasks (>= 4.3)
System.IO.Abstractions (16.1.10)
System.IO.FileSystem.AccessControl (>= 4.7) - restriction: || (&& (== net6.0) (< net5.0)) (&& (== net6.0) (< netstandard2.1)) (== netcoreapp3.1) (== netstandard2.0)
System.IO.Abstractions.TestingHelpers (16.1.10)
System.IO.Abstractions (>= 16.1.10)
System.IO.FileSystem (4.3)
Microsoft.NETCore.Platforms (>= 1.1)
Microsoft.NETCore.Targets (>= 1.1)
Expand All @@ -336,6 +340,11 @@ NUGET
System.Runtime.Handles (>= 4.3)
System.Text.Encoding (>= 4.3)
System.Threading.Tasks (>= 4.3)
System.IO.FileSystem.AccessControl (5.0) - restriction: || (&& (== net6.0) (< net5.0)) (&& (== net6.0) (< netstandard2.1)) (== netcoreapp3.1) (== netstandard2.0)
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= monoandroid) (< netstandard1.3)) (&& (== net6.0) (>= monotouch)) (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (>= xamarinios)) (&& (== net6.0) (>= xamarinmac)) (&& (== net6.0) (>= xamarintvos)) (&& (== net6.0) (>= xamarinwatchos)) (&& (== netcoreapp3.1) (>= monoandroid) (< netstandard1.3)) (&& (== netcoreapp3.1) (>= monotouch)) (&& (== netcoreapp3.1) (< netcoreapp2.0)) (&& (== netcoreapp3.1) (>= xamarinios)) (&& (== netcoreapp3.1) (>= xamarinmac)) (&& (== netcoreapp3.1) (>= xamarintvos)) (&& (== netcoreapp3.1) (>= xamarinwatchos)) (== netstandard2.0)
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (< netcoreapp2.0)) (&& (== net6.0) (< netcoreapp2.1)) (&& (== net6.0) (>= uap10.1)) (&& (== netcoreapp3.1) (< netcoreapp2.0)) (&& (== netcoreapp3.1) (< netcoreapp2.1)) (&& (== netcoreapp3.1) (>= uap10.1)) (== netstandard2.0)
System.Security.AccessControl (>= 5.0)
System.Security.Principal.Windows (>= 5.0)
System.IO.FileSystem.Primitives (4.3)
System.Runtime (>= 4.3)
System.Linq (4.3)
Expand Down
6 changes: 5 additions & 1 deletion src/Fantomas.CoreGlobalTool/Daemon.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
open System
open System.Diagnostics
open System.IO
open System.IO.Abstractions
open System.Threading
open System.Threading.Tasks
open FSharp.Compiler.Text.Range
Expand All @@ -15,6 +16,7 @@ open Fantomas
open Fantomas.SourceOrigin
open Fantomas.FormatConfig
open Fantomas.Extras.EditorConfig
open Fantomas.Extras

type FantomasDaemon(sender: Stream, reader: Stream) as this =
let rpc: JsonRpc = JsonRpc.Attach(sender, reader, this)
Expand All @@ -30,6 +32,8 @@ type FantomasDaemon(sender: Stream, reader: Stream) as this =

let exit () = disconnectEvent.Set() |> ignore

let fs = FileSystem()

do rpc.Disconnected.Add(fun _ -> exit ())

interface IDisposable with
Expand All @@ -44,7 +48,7 @@ type FantomasDaemon(sender: Stream, reader: Stream) as this =
[<JsonRpcMethod(Methods.FormatDocument, UseSingleObjectParameterDeserialization = true)>]
member _.FormatDocumentAsync(request: FormatDocumentRequest) : Task<FormatDocumentResponse> =
async {
if Fantomas.Extras.IgnoreFile.isIgnoredFile request.FilePath then
if IgnoreFile.isIgnoredFile (IgnoreFile.find fs IgnoreFile.loadIgnoreList request.FilePath) request.FilePath then
return FormatDocumentResponse.IgnoredFile request.FilePath
else
let config =
Expand Down
9 changes: 5 additions & 4 deletions src/Fantomas.CoreGlobalTool/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ let rec allFiles isRec path =
|> Seq.filter (fun f ->
isFSharpFile f
&& not (isInExcludedDir f)
&& not (IgnoreFile.isIgnoredFile f))
&& not (IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) f))

/// Fantomas assumes the input files are UTF-8
/// As is stated in F# language spec: https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25
Expand Down Expand Up @@ -209,7 +209,7 @@ let runCheckCommand (recurse: bool) (inputPath: InputPath) : int =
| InputPath.StdIn _ ->
eprintfn "No input path provided. Nothing to do."
0
| InputPath.File f when (IgnoreFile.isIgnoredFile f) ->
| InputPath.File f when (IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) f) ->
printfn "'%s' was ignored" f
0
| InputPath.File path ->
Expand Down Expand Up @@ -418,7 +418,7 @@ let main argv =
let filesAndFolders (files: string list) (folders: string list) : unit =
files
|> List.iter (fun file ->
if (IgnoreFile.isIgnoredFile file) then
if (IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) file) then
printfn "'%s' was ignored" file
else
processFile file file)
Expand Down Expand Up @@ -448,7 +448,8 @@ let main argv =
| InputPath.Unspecified, _ ->
eprintfn "Input path is missing..."
exit 1
| InputPath.File f, _ when (IgnoreFile.isIgnoredFile f) -> printfn "'%s' was ignored" f
| InputPath.File f, _ when (IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) f) ->
printfn "'%s' was ignored" f
| InputPath.Folder p1, OutputPath.NotKnown -> processFolder p1 p1
| InputPath.File p1, OutputPath.NotKnown -> processFile p1 p1
| InputPath.File p1, OutputPath.IO p2 -> processFile p1 p2
Expand Down
7 changes: 7 additions & 0 deletions src/Fantomas.Extras/AssemblyInfo.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Fantomas.Extras.AssemblyInfo

open System.Runtime.CompilerServices

[<assembly: InternalsVisibleTo("Fantomas.Tests")>]

do ()
9 changes: 6 additions & 3 deletions src/Fantomas.Extras/FakeHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ let private formatContentInternalAsync
(file: string)
(originalContent: string)
: Async<FormatResult> =
if IgnoreFile.isIgnoredFile file then
if IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) file then
async { return IgnoredFile file }
else
async {
Expand Down Expand Up @@ -99,7 +99,7 @@ let formatContentAsync = formatContentInternalAsync false
let private formatFileInternalAsync (compareWithoutLineEndings: bool) (file: string) =
let config = EditorConfig.readConfiguration file

if IgnoreFile.isIgnoredFile file then
if IgnoreFile.isIgnoredFile (IgnoreFile.current.Force()) file then
async { return IgnoredFile file }
else
let originalContent = File.ReadAllText file
Expand Down Expand Up @@ -167,7 +167,10 @@ let checkCode (filenames: seq<string>) =
async {
let! formatted =
filenames
|> Seq.filter (IgnoreFile.isIgnoredFile >> not)
|> Seq.filter (
IgnoreFile.isIgnoredFile (IgnoreFile.current.Force())
>> not
)
|> Seq.map (formatFileInternalAsync true)
|> Async.Parallel

Expand Down
1 change: 1 addition & 0 deletions src/Fantomas.Extras/Fantomas.Extras.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<None Include="paket.references" />
<Compile Include="AssemblyInfo.fs" />
<Compile Include="EditorConfig.fs" />
<Compile Include="IgnoreFile.fs" />
<Compile Include="FakeHelpers.fs" />
Expand Down
80 changes: 54 additions & 26 deletions src/Fantomas.Extras/IgnoreFile.fs
Original file line number Diff line number Diff line change
@@ -1,47 +1,75 @@
namespace Fantomas.Extras

open System.IO.Abstractions
open MAB.DotIgnore

/// The string argument is taken relative to the location
/// of the ignore-file.
type IsPathIgnored = string -> bool

type IgnoreFile =
{ Location: IFileInfo
IsIgnored: IsPathIgnored }

[<RequireQualifiedAccess>]
module IgnoreFile =

open System.IO
open MAB.DotIgnore

[<Literal>]
let IgnoreFileName = ".fantomasignore"

let rec private findIgnoreFile (filePath: string) : string option =
let allParents =
let rec addParent (di: DirectoryInfo) (finalContinuation: string list -> string list) =
if isNull di.Parent then
finalContinuation [ di.FullName ]
else
addParent di.Parent (fun parents -> di.FullName :: parents |> finalContinuation)
/// Find the `.fantomasignore` file above the given filepath, if one exists.
/// Note that this is intended for use only in the daemon; the command-line tool
/// does not support `.fantomasignore` files anywhere other than the current
/// working directory.
let find (fs: IFileSystem) (loadIgnoreList: string -> IsPathIgnored) (filePath: string) : IgnoreFile option =
let rec walkUp (currentDirectory: IDirectoryInfo) : IgnoreFile option =
if isNull currentDirectory then
None
else
let potentialFile =
fs.Path.Combine(currentDirectory.FullName, IgnoreFileName)
|> fs.FileInfo.FromFileName

addParent (Directory.GetParent filePath) id
if potentialFile.Exists then
{ Location = potentialFile
IsIgnored = loadIgnoreList potentialFile.FullName }
|> Some
else
walkUp currentDirectory.Parent

allParents
|> List.tryFind (fun p -> Path.Combine(p, IgnoreFileName) |> File.Exists)
|> Option.map (fun p -> Path.Combine(p, IgnoreFileName))
walkUp (fs.FileInfo.FromFileName(filePath).Directory)

let private relativePathPrefix = sprintf ".%c" Path.DirectorySeparatorChar
let loadIgnoreList (path: string) : IsPathIgnored =
let list = IgnoreList(path)
fun path -> list.IsIgnored(path, false)

let private removeRelativePathPrefix (path: string) =
if path.StartsWith(relativePathPrefix) then
path.Substring(2)
else
path
let internal current' (fs: IFileSystem) (currentDirectory: string) (loadIgnoreList: string -> IsPathIgnored) =
lazy find fs loadIgnoreList (fs.Path.Combine(currentDirectory, "_"))

let isIgnoredFile (file: string) =
let fullPath = Path.GetFullPath(file)
/// When executed from the command line, Fantomas will not dynamically locate
/// the most appropriate `.fantomasignore` for each input file; it only finds
/// a single `.fantomasignore` file. This is that file.
let current: Lazy<IgnoreFile option> =
current' (FileSystem()) System.Environment.CurrentDirectory loadIgnoreList

match findIgnoreFile fullPath with
let isIgnoredFile (ignoreFile: IgnoreFile option) (file: string) : bool =
match ignoreFile with
| None -> false
| Some ignoreFile ->
let ignores = IgnoreList(ignoreFile)
let fs = ignoreFile.Location.FileSystem
let fullPath = fs.Path.GetFullPath(file)

try
let path = removeRelativePathPrefix file
ignores.IsIgnored(path, false)
let path =
if fullPath.StartsWith ignoreFile.Location.Directory.FullName then
fullPath.[ignoreFile.Location.Directory.FullName.Length + 1 ..]
else
// This scenario is a bit unexpected - it suggests that we are
// trying to ask an ignorefile whether a file that is outside
// its dependency tree is ignored.
fullPath

ignoreFile.IsIgnored path
with
| ex ->
printfn "%A" ex
Expand Down
3 changes: 2 additions & 1 deletion src/Fantomas.Extras/paket.references
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ FSharp.Core
Ionide.KeepAChangelog.Tasks copy_local: true
Dotnet.ReproducibleBuilds copy_local: true
editorconfig
MAB.DotIgnore
MAB.DotIgnore
System.IO.Abstractions
1 change: 1 addition & 0 deletions src/Fantomas.Tests/Fantomas.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<Compile Include="IndexSyntaxTests.fs" />
<Compile Include="InsertFinalNewlineTests.fs" />
<Compile Include="SingleExprTests.fs" />
<Compile Include="IgnoreFileTests.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fantomas.Extras\Fantomas.Extras.fsproj" />
Expand Down
Loading

0 comments on commit e76fd06

Please sign in to comment.