From dd76beaf7bfcdd730dc58a5927a96de20911e0df Mon Sep 17 00:00:00 2001 From: Richard Webb Date: Wed, 11 Feb 2026 21:16:56 +0000 Subject: [PATCH] perf(Core): Pass a ProjectOptions into the lint Rather than reading it from FSharpCheckProjectResults on every use. This is an attempt at making the FSharpProjectOptions directly available through the lint options and then constructing it once on first use rather than on every access, which has a substantional performance overhead. The project options is stored as a Lazy because it's expensive to calculate and only used by a few rules, so the work can be avoided altogether if those rules aren't being run. This is more like how its done in the F# Analyzers SDK, where the project check results and the project options are both passed down from the top - https://github.com/ionide/FSharp.Analyzers.SDK/blob/main/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi#L104 See https://github.com/fsprojects/FSharpLint/pull/770#issuecomment-3886871671 --- src/FSharpLint.Core/Application/Lint.fs | 6 ++++++ src/FSharpLint.Core/Application/Lint.fsi | 1 + src/FSharpLint.Core/Framework/Rules.fs | 1 + .../Rules/Conventions/Naming/AsynchronousFunctionNames.fs | 4 ++-- .../Conventions/Naming/SimpleAsyncComplementaryHelpers.fs | 4 ++-- .../Rules/Smells/NoAsyncRunSynchronouslyInLibrary.fs | 6 +++--- tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs | 1 + tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs | 1 + tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs | 1 + tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs | 1 + .../FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs | 1 + 11 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/FSharpLint.Core/Application/Lint.fs b/src/FSharpLint.Core/Application/Lint.fs index 4e0c7db22..ac91adea9 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 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 74dfb5f70..38c8b2355 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