-
-
Notifications
You must be signed in to change notification settings - Fork 190
/
FakeHelpers.fs
206 lines (172 loc) · 6.44 KB
/
FakeHelpers.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
module Fantomas.Extras.FakeHelpers
open System
open System.IO
open FSharp.Compiler.SourceCodeServices
open Fantomas
open Fantomas.FormatConfig
open Fantomas.Extras
// Share an F# checker instance across formatting calls
let sharedChecker = lazy (FSharpChecker.Create())
exception CodeFormatException of (string * Option<Exception>) array with
override x.ToString() =
let errors =
x.Data0
|> Array.choose
(fun z ->
match z with
| file, Some ex -> Some(file, ex)
| _ -> None)
|> Array.map
(fun z ->
let file, ex = z
file + ":\r\n" + ex.Message + "\r\n\r\n")
let files =
x.Data0
|> Array.map
(fun z ->
match z with
| file, Some _ -> file + " !"
| file, None -> file)
String.Join(String.Empty, errors)
+ "The following files aren't formatted properly:"
+ "\r\n- "
+ String.Join("\r\n- ", files)
type FormatResult =
| Formatted of filename: string * formattedContent: string
| Unchanged of filename: string
| Error of filename: string * formattingError: Exception
| IgnoredFile of filename: string
let createParsingOptionsFromFile fileName =
{ FSharpParsingOptions.Default with
SourceFiles = [| fileName |] }
let private formatContentInternalAsync
(compareWithoutLineEndings: bool)
(config: FormatConfig)
(file: string)
(originalContent: string)
: Async<FormatResult> =
if IgnoreFile.isIgnoredFile file then
async { return IgnoredFile file }
else
async {
try
let fileName =
if Path.GetExtension(file) = ".fsi" then
"tmp.fsi"
else
"tmp.fsx"
let! formattedContent =
CodeFormatter.FormatDocumentAsync(
fileName,
SourceOrigin.SourceString originalContent,
config,
createParsingOptionsFromFile fileName,
sharedChecker.Value
)
let contentChanged =
if compareWithoutLineEndings then
let stripNewlines (s: string) =
System.Text.RegularExpressions.Regex.Replace(s, @"\n|\r", String.Empty)
(stripNewlines originalContent)
<> (stripNewlines formattedContent)
else
originalContent <> formattedContent
if contentChanged then
let! isValid =
CodeFormatter.IsValidFSharpCodeAsync(
fileName,
(SourceOrigin.SourceString(formattedContent)),
createParsingOptionsFromFile fileName,
sharedChecker.Value
)
if not isValid then
raise
<| FormatException "Formatted content is not valid F# code"
return Formatted(filename = file, formattedContent = formattedContent)
else
return Unchanged(filename = file)
with
| ex -> return Error(file, ex)
}
let formatContentAsync = formatContentInternalAsync false
let private formatFileInternalAsync (compareWithoutLineEndings: bool) (file: string) =
let config = EditorConfig.readConfiguration file
if IgnoreFile.isIgnoredFile file then
async { return IgnoredFile file }
else
let originalContent = File.ReadAllText file
async {
let! formatted =
originalContent
|> formatContentInternalAsync compareWithoutLineEndings config file
return formatted
}
let formatFileAsync = formatFileInternalAsync false
let formatFilesAsync files =
files |> Seq.map formatFileAsync |> Async.Parallel
let formatCode files =
async {
let! results = formatFilesAsync files
// Check for formatting errors:
let errors =
results
|> Array.choose
(fun x ->
match x with
| Error (file, ex) -> Some(file, Some(ex))
| _ -> None)
if not <| Array.isEmpty errors then
raise <| CodeFormatException errors
// Overwrite source files with formatted content
let result =
results
|> Array.choose
(fun x ->
match x with
| Formatted (source, formatted) ->
File.WriteAllText(source, formatted)
Some source
| _ -> None)
return result
}
type CheckResult =
{ Errors: (string * exn) list
Formatted: string list }
member this.HasErrors = List.isNotEmpty this.Errors
member this.NeedsFormatting = List.isNotEmpty this.Formatted
member this.IsValid =
List.isEmpty this.Errors
&& List.isEmpty this.Formatted
/// Runs a check on the given files and reports the result to the given output:
///
/// * It shows the paths of the files that need formatting
/// * It shows the path and the error message of files that failed the format check
///
/// Returns:
///
/// A record with the file names that were formatted and the files that encounter problems while formatting.
let checkCode (filenames: seq<string>) =
async {
let! formatted =
filenames
|> Seq.filter (IgnoreFile.isIgnoredFile >> not)
|> Seq.map (formatFileInternalAsync true)
|> Async.Parallel
let getChangedFile =
function
| FormatResult.Unchanged _
| FormatResult.IgnoredFile _ -> None
| FormatResult.Formatted (f, _)
| FormatResult.Error (f, _) -> Some f
let changes =
formatted
|> Seq.choose getChangedFile
|> Seq.toList
let getErrors =
function
| FormatResult.Error (f, e) -> Some(f, e)
| _ -> None
let errors =
formatted |> Seq.choose getErrors |> Seq.toList
return { Errors = errors; Formatted = changes }
}