Skip to content

Commit bee7ea7

Browse files
authored
Merge pull request #210 from ionide/performance-tweaks
Performance tweaks for larger solutions
2 parents 1923622 + 29fba38 commit bee7ea7

File tree

2 files changed

+66
-47
lines changed

2 files changed

+66
-47
lines changed

src/FSharp.Analyzers.Cli/CustomLogging.fs

+12-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,18 @@ type CustomFormatter(options: IOptionsMonitor<CustomOptions>) as this =
3939
this.WritePrefix(textWriter, logEntry.LogLevel)
4040
textWriter.WriteLine(message)
4141

42-
member private _.WritePrefix(textWriter: TextWriter, logLevel: LogLevel) =
42+
member private x.WritePrefix(textWriter: TextWriter, logLevel: LogLevel) =
43+
if not (isNull formatterOptions.TimestampFormat) then
44+
let dateTime =
45+
if formatterOptions.UseUtcTimestamp then
46+
DateTime.UtcNow
47+
else
48+
DateTime.Now
49+
50+
let timestamp = dateTime.ToString(formatterOptions.TimestampFormat)
51+
52+
textWriter.Write($"{timestamp} ")
53+
4354
match logLevel with
4455
| LogLevel.Trace -> textWriter.Write("trace: ")
4556
| LogLevel.Debug -> textWriter.Write("debug: ")

src/FSharp.Analyzers.Cli/Program.fs

+54-46
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ type Arguments =
3939
interface IArgParserTemplate with
4040
member s.Usage =
4141
match s with
42-
| Project _ -> "Path to your .fsproj file."
43-
| Analyzers_Path _ -> "Path to a folder where your analyzers are located."
42+
| Project _ -> "List of paths to your .fsproj file."
43+
| Analyzers_Path _ ->
44+
"List of path to a folder where your analyzers are located. This will search recursively."
4445
| Property _ -> "A key=value pair of an MSBuild property."
4546
| Configuration _ -> "The configuration to use, e.g. Debug or Release."
4647
| Runtime _ -> "The runtime identifier (RID)."
@@ -118,28 +119,39 @@ let rec mkKn (ty: Type) =
118119

119120
let mutable logger: ILogger = Abstractions.NullLogger.Instance
120121

121-
let loadProject toolsPath properties projPath =
122+
let loadProjects toolsPath properties (projPaths: string list) =
122123
async {
124+
let projPaths =
125+
projPaths
126+
|> List.map (fun proj -> Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath)
127+
128+
for proj in projPaths do
129+
logger.LogInformation("Loading project {0}", proj)
130+
123131
let loader = WorkspaceLoader.Create(toolsPath, properties)
124-
let parsed = loader.LoadProjects [ projPath ] |> Seq.toList
132+
let projectOptions = loader.LoadProjects projPaths
125133

126-
if parsed.IsEmpty then
127-
logger.LogError("Failed to load project '{0}'", projPath)
128-
exit 1
134+
let failedLoads =
135+
projPaths
136+
|> Seq.filter (fun path -> not (projectOptions |> Seq.exists (fun p -> p.ProjectFileName = path)))
137+
|> Seq.toList
129138

130-
let fcsPo = FCS.mapToFSharpProjectOptions parsed.Head parsed
139+
if Seq.length failedLoads > 0 then
140+
logger.LogError("Failed to load project '{0}'", failedLoads)
141+
exit 1
131142

132-
return fcsPo
143+
return FCS.mapManyOptions projectOptions |> Seq.toList
133144
}
134145

135-
let runProjectAux
146+
let runProject
136147
(client: Client<CliAnalyzerAttribute, CliContext>)
137148
(fsharpOptions: FSharpProjectOptions)
138149
(excludeIncludeFiles: Choice<Glob list, Glob list>)
139150
(mappings: SeverityMappings)
140151
: Async<Result<AnalyzerMessage list, AnalysisFailure> list>
141152
=
142153
async {
154+
logger.LogInformation("Checking project {0}", fsharpOptions.ProjectFileName)
143155
let! checkProjectResults = fcs.ParseAndCheckProject(fsharpOptions)
144156

145157
let! messagesPerAnalyzer =
@@ -160,21 +172,23 @@ let runProjectAux
160172
| None -> false
161173
)
162174
|> Array.map (fun fileName ->
163-
let fileContent = File.ReadAllText fileName
164-
let sourceText = SourceText.ofString fileContent
175+
async {
176+
let! fileContent = File.ReadAllTextAsync fileName |> Async.AwaitTask
177+
let sourceText = SourceText.ofString fileContent
178+
logger.LogDebug("Checking file {0}", fileName)
179+
180+
// Since we did ParseAndCheckProject, we can be sure that the file is in the project.
181+
// See https://fsharp.github.io/fsharp-compiler-docs/fcs/project.html for more information.
182+
let! parseAndCheckResults = fcs.GetBackgroundCheckResultsForFileInProject(fileName, fsharpOptions)
183+
184+
let ctx =
185+
Utils.createContext checkProjectResults fileName sourceText parseAndCheckResults
186+
187+
logger.LogInformation("Running analyzers for {0}", ctx.FileName)
188+
let! results = client.RunAnalyzers ctx
189+
return Ok results
190+
}
165191

166-
Utils.typeCheckFile fcs logger fsharpOptions fileName (Utils.SourceOfSource.SourceText sourceText)
167-
|> Result.map (Utils.createContext checkProjectResults fileName sourceText)
168-
)
169-
|> Array.map (fun ctx ->
170-
match ctx with
171-
| Error e -> async.Return(Error e)
172-
| Ok ctx ->
173-
async {
174-
logger.LogInformation("Running analyzers for {0}", ctx.FileName)
175-
let! results = client.RunAnalyzers ctx
176-
return Ok results
177-
}
178192
)
179193
|> Async.Parallel
180194

@@ -188,20 +202,6 @@ let runProjectAux
188202
|> Seq.toList
189203
}
190204

191-
let runProject
192-
(client: Client<CliAnalyzerAttribute, CliContext>)
193-
toolsPath
194-
properties
195-
proj
196-
(excludeIncludeFiles: Choice<Glob list, Glob list>)
197-
(mappings: SeverityMappings)
198-
=
199-
async {
200-
let path = Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath
201-
let! option = loadProject toolsPath properties path
202-
return! runProjectAux client option excludeIncludeFiles mappings
203-
}
204-
205205
let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |]
206206

207207
let isFSharpFile (file: string) =
@@ -249,7 +249,7 @@ let runFscArgs
249249
Stamp = None
250250
}
251251

252-
runProjectAux client projectOptions excludeIncludeFiles mappings
252+
runProject client projectOptions excludeIncludeFiles mappings
253253

254254
let printMessages (msgs: AnalyzerMessage list) =
255255

@@ -482,7 +482,11 @@ let main argv =
482482
use factory =
483483
LoggerFactory.Create(fun builder ->
484484
builder
485-
.AddCustomFormatter(fun options -> options.UseAnalyzersMsgStyle <- false)
485+
.AddCustomFormatter(fun options ->
486+
options.UseAnalyzersMsgStyle <- false
487+
options.TimestampFormat <- "[HH:mm:ss.fff]"
488+
options.UseUtcTimestamp <- true
489+
)
486490
.SetMinimumLevel(logLevel)
487491
|> ignore
488492
)
@@ -610,7 +614,6 @@ let main argv =
610614
match projOpts, fscArgs with
611615
| [], None ->
612616
logger.LogError("No project given. Use `--project PATH_TO_FSPROJ`.")
613-
614617
None
615618
| _ :: _, Some _ ->
616619
logger.LogError("`--project` and `--fsc-args` cannot be combined.")
@@ -625,11 +628,16 @@ let main argv =
625628
logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath)
626629
exit 1
627630

628-
projects
629-
|> List.map (fun projPath ->
630-
runProject client toolsPath properties projPath exclInclFiles severityMapping
631-
)
632-
|> Async.Sequential
631+
async {
632+
let! loadedProjects = loadProjects toolsPath properties projects
633+
634+
return!
635+
loadedProjects
636+
|> List.map (fun (projPath: FSharpProjectOptions) ->
637+
runProject client projPath exclInclFiles severityMapping
638+
)
639+
|> Async.Parallel
640+
}
633641
|> Async.RunSynchronously
634642
|> List.concat
635643
|> Some

0 commit comments

Comments
 (0)