Skip to content

Commit

Permalink
Extract shared logic between json config and editorconfig. (#663)
Browse files Browse the repository at this point in the history
* Extracted json specific logic from ConfigFile.fs

* Setting up editor format parsing.

* Clean up some warnings.
  • Loading branch information
nojaf authored Feb 3, 2020
1 parent b3db683 commit 129e65d
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 153 deletions.
5 changes: 3 additions & 2 deletions src/Fantomas.Tests/Fantomas.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\netfx.props" />
<PropertyGroup>
<Version>3.2.0-beta-002</Version>
Expand Down Expand Up @@ -57,7 +57,8 @@
<Compile Include="ElmishTests.fs" />
<Compile Include="LambdaTests.fs" />
<Compile Include="IfThenElseTests.fs" />
<Compile Include="FormatConfigConfigurationFileTests.fs" />
<Compile Include="FormatConfigJsonConfigurationFileTests.fs" />
<Compile Include="FormatConfigEditorConfigurationFileTests.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fantomas\Fantomas.fsproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Fantomas.Tests.FormatConfigEditorConfigurationFileTests

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Fantomas.Tests.FormatConfigConfigurationFileTests
module Fantomas.Tests.FormatConfigJsonConfigurationFileTests

open System
open Fantomas
Expand All @@ -7,7 +7,8 @@ open NUnit.Framework
open System.IO
open Fantomas.Tests.TestHelper

let private getTempFolder () = Path.GetTempPath()
[<Literal>]
let private configFileName ="fantomas-config.json"

let private configToJson config =
Reflection.getRecordFields config
Expand All @@ -24,24 +25,12 @@ let private configToJson config =
|> String.concat ",\n "
|> sprintf "{ %s }"

let private mkConfigPath folder =
match folder with
| Some folder ->
let folderPath = Path.Combine(getTempFolder(), folder)
Directory.CreateDirectory(folderPath) |> ignore
Path.Combine(folderPath, "fantomas-config.json")
| None ->
Path.Combine(getTempFolder(), "fantomas-config.json")
let private mkConfig folder fantomasConfig =
let file = mkConfigPath folder
let content = configToJson fantomasConfig
File.WriteAllText(file, content)
file

let private mkConfigFromJson folder json =
let file = mkConfigPath folder
File.WriteAllText(file, json)
file
let private mkConfig subFolder config =
let json = configToJson config
mkConfigFromContent configFileName subFolder json

let private mkConfigFromJson subFolder json =
mkConfigFromContent configFileName subFolder json

let rec private delete fileOrFolder =
if File.Exists(fileOrFolder) then
Expand All @@ -54,6 +43,10 @@ let rec private delete fileOrFolder =
()
let private uniqueString () = System.Guid.NewGuid().ToString("N")

let private applyOptionsToConfig config path =
let json = File.ReadAllText path
let options, warnings = JsonConfig.parseOptionsFromJson json
FormatConfig.applyOptions(config, options),warnings

[<Test>]
let ``single configuration file`` () =
Expand Down Expand Up @@ -101,24 +94,24 @@ let ``pointing to config in a subfolder should return parent config file as well
[<Test>]
let ``simple config file parses valid option`` () =
let path = mkConfigFromJson None "{\"KeepNewlineAfter\":true}"
let config, warnings = ConfigFile.applyOptionsToConfig FormatConfig.Default path
let config, warnings = applyOptionsToConfig FormatConfig.Default path
true == config.KeepNewlineAfter
[] == warnings

[<Test>]
let ``keys should not necessarily have quotes to be parsed`` () =
let path = mkConfigFromJson None "{KeepNewlineAfter:true}"
let config, warnings = ConfigFile.applyOptionsToConfig FormatConfig.Default path
let config, warnings = applyOptionsToConfig FormatConfig.Default path
true == config.KeepNewlineAfter
[] == warnings

[<Test>]
let ``invalid option returns a warning`` () =
let path = mkConfigFromJson None "{ \"Foo\": true }"
let config, warnings = ConfigFile.applyOptionsToConfig FormatConfig.Default path
let config, warnings = applyOptionsToConfig FormatConfig.Default path
config == FormatConfig.Default
match warnings with
| [ error ] ->
| [| error |] ->
StringAssert.IsMatch("\"Foo\":true is no valid setting for Fantomas v(.*)", error)
| _ -> fail()

Expand Down Expand Up @@ -176,20 +169,7 @@ let ``$schema key should not return warning`` () =
"IndentOnTryWith": true
}
"""
let _, warnings = ConfigFile.applyOptionsToConfig FormatConfig.Default path
[] == warnings

/// Ok to remove test once ReorderOpenDeclaration is removed from the code-base
[<Test>]
let ``configuration with ReorderOpenDeclaration should return a warning`` () =
let path = mkConfigFromJson None """
{
"ReorderOpenDeclaration": true
}
"""
let _, warnings = ConfigFile.applyOptionsToConfig FormatConfig.Default path
match warnings with
| [ reorderWarning ] ->
StringAssert.IsMatch("ReorderOpenDeclaration will be deprecated in the next major version. Using this feature can lead to compilation errors after formatting.", reorderWarning)
| _ -> fail()
let _, warnings =
File.ReadAllText path
|> JsonConfig.parseOptionsFromJson
[] == warnings
17 changes: 17 additions & 0 deletions src/Fantomas.Tests/TestHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ open FSharp.Compiler.Ast
open FSharp.Compiler.Range
open NUnit.Framework
open FsCheck
open System.IO

let config = FormatConfig.Default
let newline = "\n"
Expand Down Expand Up @@ -180,3 +181,19 @@ type NUnitRunner () =
// TODO : Log more information about the test failure.
Runner.onFinishedToString name result
|> Assert.Fail

let private getTempFolder () = Path.GetTempPath()

let private mkConfigPath fileName folder =
match folder with
| Some folder ->
let folderPath = Path.Combine(getTempFolder(), folder)
Directory.CreateDirectory(folderPath) |> ignore
Path.Combine(folderPath, fileName)
| None ->
Path.Combine(getTempFolder(), fileName)

let mkConfigFromContent fileName folder content =
let file = mkConfigPath fileName folder
File.WriteAllText(file, content)
file
24 changes: 2 additions & 22 deletions src/Fantomas/CodeFormatter.fs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace Fantomas

open Fantomas
open FormatConfig

[<Sealed>]
type CodeFormatter =
static member ParseAsync(fileName, source, parsingOptions, checker) =
Expand Down Expand Up @@ -35,23 +32,6 @@ type CodeFormatter =
static member MakeRange(fileName, startLine, startCol, endLine, endCol) =
CodeFormatterImpl.makeRange fileName startLine startCol endLine endCol

static member GetVersion() = Fantomas.Version.fantomasVersion.Value

static member ReadConfiguration(fileOrFolder) =
try
let configurationFiles =
ConfigFile.findConfigurationFiles fileOrFolder

if List.isEmpty configurationFiles then failwithf "No configuration files were found for %s" fileOrFolder

let (config,warnings) =
List.fold (fun (currentConfig, warnings) configPath ->
let updatedConfig, warningsForPath = ConfigFile.applyOptionsToConfig currentConfig configPath
(updatedConfig, warnings @ warningsForPath)
) (FormatConfig.Default, []) configurationFiles
static member GetVersion() = Version.fantomasVersion.Value

This comment has been minimized.

Copy link
@auduchinok

auduchinok Apr 26, 2021

Contributor

Thinking out loud: probably it could also be great to have some kind of an API to get available editor.config Fantomas options (and their default values?), so tooling could update its integrations based on Fantomas version used.

This comment has been minimized.

Copy link
@nojaf

nojaf Apr 27, 2021

Author Contributor

Had that in mind for the daemon mode, https://github.com/nojaf/fantomas/blob/b2f1a540b7cf6d8ff60b6fe8265615fc58067d7a/src/Fantomas.CoreGlobalTool/Daemon.fs#L134-L170.
Editorconfig stuff should not be part of Fantomas lib though.


match warnings with
| [] -> FormatConfigFileParseResult.Success config
| w -> FormatConfigFileParseResult.PartialSuccess (config, w)
with
| exn -> FormatConfigFileParseResult.Failure exn
static member ReadConfiguration(fileOrFolder) = CodeFormatterImpl.readConfiguration fileOrFolder
34 changes: 33 additions & 1 deletion src/Fantomas/CodeFormatterImpl.fs
Original file line number Diff line number Diff line change
Expand Up @@ -669,4 +669,36 @@ type internal BlockType =
| Tuple

/// Make a position at (line, col) to denote cursor position
let makePos line col = mkPos line col
let makePos line col = mkPos line col

let readConfiguration fileOrFolder =
try
let configurationFiles =
ConfigFile.findConfigurationFiles fileOrFolder

if List.isEmpty configurationFiles then failwithf "No configuration files were found for %s" fileOrFolder

let (config,warnings) =
List.fold (fun (currentConfig, warnings) configPath ->
let configContent = System.IO.File.ReadAllText(configPath)
let options, warningFromConfigPath =
match System.IO.Path.GetFileName(configPath) with
| json when (json = ConfigFile.jsonConfigFileName) ->
JsonConfig.parseOptionsFromJson configContent
| editorconfig when (editorconfig = ConfigFile.editorConfigFileName) ->
EditorConfig.parseOptionsFromEditorConfig configContent
| _ ->
failwithf "Filename is not supported!"
let updatedConfig = FormatConfig.applyOptions(currentConfig, options)
let locationAwareWarnings =
List.ofArray warningFromConfigPath
|> List.map (ConfigFile.makeWarningLocationAware configPath)

(updatedConfig, warnings @ locationAwareWarnings)
) (FormatConfig.Default, []) configurationFiles

match warnings with
| [] -> FormatConfigFileParseResult.Success config
| w -> FormatConfigFileParseResult.PartialSuccess (config, w)
with
| exn -> FormatConfigFileParseResult.Failure exn
62 changes: 62 additions & 0 deletions src/Fantomas/ConfigFile.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module internal Fantomas.ConfigFile

open System.IO
open Fantomas.FormatConfig
open Fantomas.Version

let jsonConfigFileName = "fantomas-config.json"
let editorConfigFileName = ".editor-config"

let private allowedFileNames = [jsonConfigFileName; editorConfigFileName]

let rec private getParentFolders acc current =
let parent = Directory.GetParent(current) |> Option.ofObj
match parent with
| Some p -> getParentFolders (current::acc) p.FullName
| None -> current::acc

/// Returns all the found configuration files for the given path
/// fileOrFolder can be a concrete json file or a directory path
let rec findConfigurationFiles fileOrFolder : string list =
let findConfigInFolder folderPath =
allowedFileNames
|> List.map (fun fn -> Path.Combine(folderPath, fn))
|> List.filter (File.Exists)

if Path.GetExtension(fileOrFolder) = "" && Directory.Exists fileOrFolder then
getParentFolders [] fileOrFolder
|> List.collect findConfigInFolder
elif File.Exists(fileOrFolder) then
let parentFolder =
Directory.GetParent(Path.GetDirectoryName(fileOrFolder))
|> Option.ofObj
match parentFolder with
| Some pf -> findConfigurationFiles pf.FullName @ [fileOrFolder]
| None -> [fileOrFolder]
else
[]

let makeWarningLocationAware configPath warning =
sprintf "%s, in %s" warning configPath

let private fantomasFields = Reflection.getRecordFields FormatConfig.Default |> Array.map fst
let private (|FantomasSetting|_|) (s:string) =
let s = s.Trim('\"')
Array.tryFind ((=) s) fantomasFields

let private (|Number|_|) d =
match System.Int32.TryParse(d) with
| true, d -> Some (box d)
| _ -> None
let private (|Boolean|_|) b =
if b = "true" then Some (box true)
elif b = "false" then Some (box false)
else None

let processSetting originalLine key value =
match (key, value) with
| (FantomasSetting(fs), Number(v))
| (FantomasSetting(fs), Boolean(v)) -> Ok (fs, v)
| _ ->
let warning = sprintf "%s is no valid setting for Fantomas v%s" originalLine (fantomasVersion.Value)
Error warning
7 changes: 7 additions & 0 deletions src/Fantomas/EditorConfig.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module internal Fantomas.EditorConfig

/// Similar to how to processing of the JSON file happens, this function should the options found in the editor config.
/// The first argument in the return tuple are the options. Listed as (key,value) where key is a member name of the FormatConfig record. Value is either a boolean or an int boxed as object.
/// The second argument in the return tuple are warnings. F.ex. invalid settings
let parseOptionsFromEditorConfig editorConfig : (string * obj) array * string array =
Array.empty, Array.empty
3 changes: 3 additions & 0 deletions src/Fantomas/Fantomas.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<Compile Include="Dbg.fs" />
<Compile Include="Utils.fs" />
<Compile Include="FormatConfig.fs" />
<Compile Include="ConfigFile.fs" />
<Compile Include="JsonConfig.fs" />
<Compile Include="EditorConfig.fs" />
<Compile Include="TriviaTypes.fs" />
<Compile Include="TokenParserBoolExpr.fs" />
<Compile Include="TriviaHelpers.fs" />
Expand Down
Loading

0 comments on commit 129e65d

Please sign in to comment.