diff --git a/src/FSharpLint.Core/Application/Lint.fs b/src/FSharpLint.Core/Application/Lint.fs index 4e0c7db22..e0e138b69 100644 --- a/src/FSharpLint.Core/Application/Lint.fs +++ b/src/FSharpLint.Core/Application/Lint.fs @@ -125,6 +125,7 @@ module Lint = GlobalConfig: Rules.GlobalRuleConfig TypeCheckResults: FSharpCheckFileResults option ProjectCheckResults: FSharpCheckProjectResults option + ProjectOptions: Lazy FilePath: string FileContent: string Lines: string[] @@ -149,6 +150,7 @@ module Lint = Lines = config.Lines CheckInfo = config.TypeCheckResults ProjectCheckInfo = config.ProjectCheckResults + ProjectOptions = config.ProjectOptions GlobalConfig = config.GlobalConfig } // Build state for rules with context. @@ -263,6 +265,10 @@ module Lint = GlobalConfig = enabledRules.GlobalConfig TypeCheckResults = fileInfo.TypeCheckResults ProjectCheckResults = fileInfo.ProjectCheckResults + ProjectOptions = lazy( + fileInfo.ProjectCheckResults + |> Option.map _.ProjectContext.ProjectOptions + ) FilePath = fileInfo.File FileContent = fileInfo.Text Lines = lines @@ -430,9 +436,9 @@ module Lint = Option.iter (fun func -> func warning) optionalParams.ReceivedWarning - let checker = FSharpChecker.Create(keepAssemblyContents=true) + let checker = FSharpChecker.Create(keepAssemblyContents=true, parallelReferenceResolution=true) - let parseFilesInProject files projectOptions = async { + let parseFilesInProject files (projectOptions:FSharpProjectOptions) = async { let lintInformation = { Configuration = config CancellationToken = optionalParams.CancellationToken @@ -446,21 +452,31 @@ module Lint = Configuration.IgnoreFiles.shouldFileBeIgnored parsedIgnoreFiles filePath) |> Option.defaultValue false - let! parsedFiles = - files - |> List.filter (not << isIgnoredFile) + let filesToLint = files |> List.filter (not << isIgnoredFile) + + // Run project check and per-file parse+check concurrently. + // The project check warms the FCS incremental builder; parallel + // per-file checks share the builder and benefit from its warmed state. + let projectCheckAsync = checker.ParseAndCheckProject projectOptions + let perFileAsync = + filesToLint |> List.map (fun file -> ParseFile.parseFile file checker (Some projectOptions)) - |> Async.Sequential + |> Async.Parallel + + let! results = Async.Parallel [| + async { let! r = projectCheckAsync in return box r } + async { let! r = perFileAsync in return box r } + |] + let projectCheckResults = results.[0] :?> FSharpCheckProjectResults + let parsedFiles = results.[1] :?> ParseFile.ParseFileResult[] let failedFiles = Array.choose getFailedFiles parsedFiles if Array.isEmpty failedFiles then - let! projectCheckResults = checker.ParseAndCheckProject projectOptions - parsedFiles |> Array.choose getParsedFiles - |> Array.iter (fun fileParseResult -> - lint + |> Array.iter (fun fileParseResult -> + lint lintInformation { fileParseResult with ProjectCheckResults = Some projectCheckResults }) diff --git a/src/FSharpLint.Core/Application/Lint.fsi b/src/FSharpLint.Core/Application/Lint.fsi index 7653223d6..7128eadb7 100644 --- a/src/FSharpLint.Core/Application/Lint.fsi +++ b/src/FSharpLint.Core/Application/Lint.fsi @@ -129,6 +129,7 @@ module Lint = GlobalConfig: Rules.GlobalRuleConfig TypeCheckResults: FSharpCheckFileResults option ProjectCheckResults: FSharpCheckProjectResults option + ProjectOptions: Lazy FilePath: string FileContent: string Lines: string[] diff --git a/src/FSharpLint.Core/Framework/Rules.fs b/src/FSharpLint.Core/Framework/Rules.fs index d53629800..413b6c932 100644 --- a/src/FSharpLint.Core/Framework/Rules.fs +++ b/src/FSharpLint.Core/Framework/Rules.fs @@ -31,6 +31,7 @@ type AstNodeRuleParams = Lines:string [] CheckInfo:FSharpCheckFileResults option ProjectCheckInfo:FSharpCheckProjectResults option + ProjectOptions: Lazy GlobalConfig:GlobalRuleConfig } type LineRuleParams = diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs index e329ece0c..e95c008ea 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs @@ -32,8 +32,8 @@ let runner (config: Config) (args: AstNodeRuleParams) = | _ -> config.Mode = AllAPIs let likelyhoodOfBeingInLibrary = - match args.ProjectCheckInfo with - | Some projectInfo -> howLikelyProjectIsLibrary projectInfo.ProjectContext.ProjectOptions.ProjectFileName + match args.ProjectOptions.Value with + | Some projectOptions -> howLikelyProjectIsLibrary projectOptions.ProjectFileName | None -> Unlikely if config.Mode = OnlyPublicAPIsInLibraries && likelyhoodOfBeingInLibrary <> Likely then diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/SimpleAsyncComplementaryHelpers.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/SimpleAsyncComplementaryHelpers.fs index ee590aaa7..27aa57f5a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/SimpleAsyncComplementaryHelpers.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/SimpleAsyncComplementaryHelpers.fs @@ -205,8 +205,8 @@ let runner (config: Config) (args: AstNodeRuleParams) = Array.append (checkFuncs asyncFuncs taskFuncs) (checkFuncs taskFuncs asyncFuncs) let likelyhoodOfBeingInLibrary = - match args.ProjectCheckInfo with - | Some projectInfo -> howLikelyProjectIsLibrary projectInfo.ProjectContext.ProjectOptions.ProjectFileName + match args.ProjectOptions.Value with + | Some projectOptions -> howLikelyProjectIsLibrary projectOptions.ProjectFileName | None -> Unlikely if config.Mode = OnlyPublicAPIsInLibraries && likelyhoodOfBeingInLibrary <> Likely then diff --git a/src/FSharpLint.Core/Rules/Smells/NoAsyncRunSynchronouslyInLibrary.fs b/src/FSharpLint.Core/Rules/Smells/NoAsyncRunSynchronouslyInLibrary.fs index f74304f96..2eab65600 100644 --- a/src/FSharpLint.Core/Rules/Smells/NoAsyncRunSynchronouslyInLibrary.fs +++ b/src/FSharpLint.Core/Rules/Smells/NoAsyncRunSynchronouslyInLibrary.fs @@ -95,9 +95,9 @@ let checkIfInLibrary (args: AstNodeRuleParams) (range: range) : array - let projectFile = System.IO.FileInfo checkProjectResults.ProjectContext.ProjectOptions.ProjectFileName + match (args.CheckInfo, args.ProjectOptions.Value) with + | Some checkFileResults, Some projectOptions -> + let projectFile = System.IO.FileInfo projectOptions.ProjectFileName match howLikelyProjectIsLibrary projectFile.Name with | Likely -> false | Unlikely -> true diff --git a/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs index aa4162fba..ee77ae33b 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs @@ -43,6 +43,7 @@ type TestAstNodeRuleBase (rule:Rule) = GlobalConfig = resolvedGlobalConfig TypeCheckResults = checkResult ProjectCheckResults = None + ProjectOptions = Lazy<_>(None) FilePath = (Option.defaultValue String.Empty maybeFileName) FileContent = input Lines = (input.Split("\n")) diff --git a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs index 842ec077d..fd52c56f4 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs @@ -65,6 +65,7 @@ type TestHintMatcherBase () = GlobalConfig = resolvedGlobalConfig TypeCheckResults = checkResult ProjectCheckResults = None + ProjectOptions = Lazy<_>() FilePath = (Option.defaultValue String.Empty maybeFileName) FileContent = input Lines = (input.Split("\n")) diff --git a/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs index f52b54b6b..20c94fac0 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs @@ -38,6 +38,7 @@ type TestIndentationRuleBase (rule:Rule) = GlobalConfig = resolvedGlobalConfig TypeCheckResults = None ProjectCheckResults = None + ProjectOptions = Lazy<_>(None) FilePath = resolvedFileName FileContent = input Lines = lines diff --git a/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs index 078d025fa..c53e93377 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs @@ -38,6 +38,7 @@ type TestLineRuleBase (rule:Rule) = GlobalConfig = resolvedGlobalConfig TypeCheckResults = None ProjectCheckResults = None + ProjectOptions = Lazy<_>(None) FilePath = resolvedFileName FileContent = input Lines = lines diff --git a/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs index 40d9c1652..5e534bb22 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs @@ -38,6 +38,7 @@ type TestNoTabCharactersRuleBase (rule:Rule) = GlobalConfig = resolvedGlobalConfig TypeCheckResults = None ProjectCheckResults = None + ProjectOptions = Lazy<_>() FilePath = resolvedFileName FileContent = input Lines = lines