Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only load the .fantomasignore file once, except for the daemon #2097

Merged
merged 12 commits into from
Mar 27, 2022
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 reads the `.fantomasignore` file in its current working directory, if one exists; unlike Git, it does not traverse the filesystem to find an appropriate ignore file.
(Technically 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.)

## Using the API

See [CodeFormatter.fsi](../src/Fantomas/CodeFormatter.fsi) to view the public API of Fantomas.
Expand Down
3 changes: 2 additions & 1 deletion src/Fantomas.CoreGlobalTool/Daemon.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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 Down Expand Up @@ -44,7 +45,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 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
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
65 changes: 45 additions & 20 deletions src/Fantomas.Extras/IgnoreFile.fs
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
namespace Fantomas.Extras

open System.IO
open MAB.DotIgnore

type IgnoreFile =
{ Location: FileInfo
IgnoreList: IgnoreList }

[<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 (filePath: string) : IgnoreFile option =
let rec walkUp (currentDirectory: DirectoryInfo) : IgnoreFile option =
if isNull currentDirectory then
None
else
let potentialFile =
Path.Combine(currentDirectory.FullName, IgnoreFileName)
|> FileInfo

addParent (Directory.GetParent filePath) id
if potentialFile.Exists then
{ Location = potentialFile
IgnoreList = IgnoreList(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 (FileInfo(filePath).Directory)

let private relativePathPrefix = sprintf ".%c" Path.DirectorySeparatorChar

Expand All @@ -31,17 +43,30 @@ module IgnoreFile =
else
path

let isIgnoredFile (file: string) =
let fullPath = Path.GetFullPath(file)
/// When executed from the command line, Fantomas only supports a `.fantomasignore` file in the
/// current working directory. This is that `.fantomasignore` file.
let current: Lazy<IgnoreFile option> =
lazy
let path =
Smaug123 marked this conversation as resolved.
Show resolved Hide resolved
Path.Combine(System.Environment.CurrentDirectory, IgnoreFileName)
|> FileInfo

if path.Exists then
{ Location = path
IgnoreList = IgnoreList(path.FullName) }
|> Some
else
None

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

try
let path = removeRelativePathPrefix file
ignores.IsIgnored(path, false)
let path = removeRelativePathPrefix fullPath
ignoreFile.IgnoreList.IsIgnored(path, false)
with
| ex ->
printfn "%A" ex
Expand Down