From 1c770f2acebf535e379d9531cd69651438118671 Mon Sep 17 00:00:00 2001 From: webwarrior-ws Date: Tue, 25 Aug 2026 14:11:54 +0200 Subject: [PATCH 1/5] NoImpureFunctions: added rule And tests for it. --- docs/content/how-tos/rule-configuration.md | 1 + docs/content/how-tos/rules/FL0098.md | 34 +++++++++++++++++++ .../Application/Configuration.fs | 6 +++- src/FSharpLint.Core/FSharpLint.Core.fsproj | 1 + .../Rules/Conventions/NoImpureFunctions.fs | 26 ++++++++++++++ src/FSharpLint.Core/Rules/Identifiers.fs | 1 + src/FSharpLint.Core/Text.resx | 6 ++++ src/FSharpLint.Core/fsharplint.json | 7 ++++ .../FSharpLint.Core.Tests.fsproj | 1 + .../Rules/Conventions/NoImpureFunctions.fs | 29 ++++++++++++++++ 10 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 docs/content/how-tos/rules/FL0098.md create mode 100644 src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs create mode 100644 tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs diff --git a/docs/content/how-tos/rule-configuration.md b/docs/content/how-tos/rule-configuration.md index 50e819956..d06320689 100644 --- a/docs/content/how-tos/rule-configuration.md +++ b/docs/content/how-tos/rule-configuration.md @@ -137,3 +137,4 @@ The following rules can be specified for linting. - [FavourNamedMembers (FL0094)](rules/FL0094.html) - [SynchronousFunctionNames (FL0095)](rules/FL0095.html) - [AsynchronousFunctionNames (FL0096)](rules/FL0096.html) +- [NoImpureFunctions (FL0098)](rules/FL0098.html) diff --git a/docs/content/how-tos/rules/FL0098.md b/docs/content/how-tos/rules/FL0098.md new file mode 100644 index 000000000..b165e8331 --- /dev/null +++ b/docs/content/how-tos/rules/FL0098.md @@ -0,0 +1,34 @@ +--- +title: FL0098 +category: how-to +hide_menu: true +--- + +# NoImpureFunctions (FL0098) + +## Cause + +An impure function (that mutates one or more of its parameters) was used. + +## Rationale + +Using pure functions without side-effects is preferrable in functional programming. + +## How To Fix + +Use pure function. + +## Rule Settings + + { + "noImpureFunctions": { + "enabled": false, + "config": { + "allowedImpureFunctions": [], + "additionalImpureFunctions": [] + } + } + } + +* *allowedImpureFunctions* - list of strings representing impure functions to allow (e.g. `"Array.set"`). +* *additionalImpureFunctions* - list of strings representing additional impure functions to disallow. diff --git a/src/FSharpLint.Core/Application/Configuration.fs b/src/FSharpLint.Core/Application/Configuration.fs index 122de462e..5be5dcb39 100644 --- a/src/FSharpLint.Core/Application/Configuration.fs +++ b/src/FSharpLint.Core/Application/Configuration.fs @@ -558,7 +558,8 @@ type Configuration = FavourNamedMembers:EnabledConfig option SynchronousFunctionNames:EnabledConfig option AsynchronousFunctionNames:RuleConfig option - SimpleAsyncComplementaryHelpers:RuleConfig option } + SimpleAsyncComplementaryHelpers:RuleConfig option + NoImpureFunctions:RuleConfig option } with // Method Zero is too big but can't be split into parts because it returns a record // and it requires all fields to be set. @@ -671,6 +672,7 @@ with SynchronousFunctionNames = None AsynchronousFunctionNames = None SimpleAsyncComplementaryHelpers = None + NoImpureFunctions = None } // fsharplint:enable MaxLinesInMember @@ -797,6 +799,7 @@ let flattenConfig (config:Configuration) = config.Hints |> Option.map (fun hintsConfig -> HintMatcher.rule { HintMatcher.Config.HintTrie = parseHints (getOrEmptyList hintsConfig.add) }) |> Option.toArray |] +// fsharplint:disable MaxLinesInValue let allPossibleRules = [| config.TypedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) @@ -896,6 +899,7 @@ let flattenConfig (config:Configuration) = config.SynchronousFunctionNames |> Option.bind (constructRuleIfEnabled SynchronousFunctionNames.rule) config.AsynchronousFunctionNames |> Option.bind (constructRuleWithConfig AsynchronousFunctionNames.rule) config.SimpleAsyncComplementaryHelpers |> Option.bind (constructRuleWithConfig SimpleAsyncComplementaryHelpers.rule) + config.NoImpureFunctions |> Option.bind (constructRuleWithConfig NoImpureFunctions.rule) |] let allEnabledRules = Array.choose id allPossibleRules diff --git a/src/FSharpLint.Core/FSharpLint.Core.fsproj b/src/FSharpLint.Core/FSharpLint.Core.fsproj index 7fa0f6aeb..dd6859aab 100644 --- a/src/FSharpLint.Core/FSharpLint.Core.fsproj +++ b/src/FSharpLint.Core/FSharpLint.Core.fsproj @@ -53,6 +53,7 @@ + diff --git a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs new file mode 100644 index 000000000..cd38ef5d3 --- /dev/null +++ b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs @@ -0,0 +1,26 @@ +module FSharpLint.Rules.NoImpureFunctions + +open System +open FSharpLint.Framework.Rules + +[] +type Config = { + AllowedImpureFunctions:string list + AdditionalImpureFunctions:string list +} + +let runner (_config:Config) (_args:AstNodeRuleParams) = + failwith "Not yet implemented" + +let rule config = + AstNodeRule + { + Name = "NoImpureFunctions" + Identifier = Identifiers.NoImpureFunctions + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } + diff --git a/src/FSharpLint.Core/Rules/Identifiers.fs b/src/FSharpLint.Core/Rules/Identifiers.fs index b52ce8547..ad99a1f41 100644 --- a/src/FSharpLint.Core/Rules/Identifiers.fs +++ b/src/FSharpLint.Core/Rules/Identifiers.fs @@ -102,3 +102,4 @@ let FavourNamedMembers = identifier 94 let SynchronousFunctionNames = identifier 95 let AsynchronousFunctionNames = identifier 96 let SimpleAsyncComplementaryHelpers = identifier 97 +let NoImpureFunctions = identifier 98 diff --git a/src/FSharpLint.Core/Text.resx b/src/FSharpLint.Core/Text.resx index 846703ac5..82f41b4b2 100644 --- a/src/FSharpLint.Core/Text.resx +++ b/src/FSharpLint.Core/Text.resx @@ -420,4 +420,10 @@ Consider creating twin helper function/method `{0}{1}` that just calls `async {{ return Async.AwaitTask ({2}Async{3}) }}` under the hood. + + Consider not using impure function '{0}'. + + + Consider using '{0}' instead of impure function '{1}'. + diff --git a/src/FSharpLint.Core/fsharplint.json b/src/FSharpLint.Core/fsharplint.json index d005ee763..2cd4b7a29 100644 --- a/src/FSharpLint.Core/fsharplint.json +++ b/src/FSharpLint.Core/fsharplint.json @@ -361,6 +361,13 @@ "mode": "OnlyPublicAPIsInLibraries" } }, + "noImpureFunctions": { + "enabled": false, + "config": { + "allowedImpureFunctions": [], + "additionalImpureFunctions": [] + } + }, "hints": { "add": [ "not (a = b) ===> a <> b", diff --git a/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj b/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj index 862bc1494..9d5ec17fd 100644 --- a/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj +++ b/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj @@ -40,6 +40,7 @@ + diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs new file mode 100644 index 000000000..888ca7f3e --- /dev/null +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs @@ -0,0 +1,29 @@ +module FSharpLint.Core.Tests.Rules.Conventions.NoImpureFunctions + + +open NUnit.Framework +open FSharpLint.Rules +open FSharpLint.Core.Tests + +[] +type TestConventionsNoImpureFunctions() = + inherit TestAstNodeRuleBase.TestAstNodeRuleBase(NoImpureFunctions.rule { AdditionalImpureFunctions = ["Custom.impure"]; AllowedImpureFunctions = ["Array.set"] }) + + [] + member this.``Error for impure function which should be replaced with another function``() = + this.Parse("let x = Array.sortInPlace [2; 1; 4]") + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Error for user-specified impure function``() = + this.Parse("let x = Custom.impure (ref 4)") + + Assert.IsTrue this.ErrorsExist + + [] + member this.``No error for user-specified allowed partial function``() = + this.Parse("let x = [| Some 4; None |] +Array.set x 0 None") + + this.AssertNoWarnings() From 354f5e6f5f9de72a8ae210331165a16cb081607e Mon Sep 17 00:00:00 2001 From: webwarrior-ws Date: Tue, 25 Aug 2026 14:16:58 +0200 Subject: [PATCH 2/5] NoImpureFunctions: implemented rule --- .../Rules/Conventions/NoImpureFunctions.fs | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs index cd38ef5d3..402aa6628 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs @@ -1,6 +1,10 @@ module FSharpLint.Rules.NoImpureFunctions open System +open FSharp.Compiler.Text +open FSharpLint.Framework +open FSharpLint.Framework.Suggestion +open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules [] @@ -9,8 +13,58 @@ type Config = { AdditionalImpureFunctions:string list } -let runner (_config:Config) (_args:AstNodeRuleParams) = - failwith "Not yet implemented" +let private impureFunctionIdentifiers = + Map.ofList + [ + ("Array.sortInPlace", Some "Array.sort") + ("Array.sortInPlaceBy", Some "Array.Array.sortBy") + ("Array.sortInPlaceWith", Some "Array.sortWith") + + ("Array.set", Some "Array.updateAt") + + ("Array.fill", None) + ("Array.blit", None) + + ("Array2D.fill", None) + ("Array2D.blit", None) + ] + +let runner (config:Config) (args:AstNodeRuleParams) = + let checkIfImpureIdentifier (identifier:string) (range:Range) = + let issueWarning maybeReplacementFunction = + match maybeReplacementFunction with + | Some replacementFunction -> + { + Range = range + Message = String.Format(Resources.GetString "RulesConventionsNoImpureFunctionsReplacementError", replacementFunction, identifier) + SuggestedFix = Some (lazy ( Some { FromText = identifier; FromRange = range; ToText = replacementFunction })) + TypeChecks = List.Empty + } + | None -> + { + Range = range + Message = String.Format(Resources.GetString "RulesConventionsNoImpureFunctionsError", identifier) + SuggestedFix = None + TypeChecks = List.Empty + } + + if List.contains identifier config.AllowedImpureFunctions then + None + elif List.contains identifier config.AdditionalImpureFunctions then + issueWarning None |> Some + else + Map.tryFind identifier impureFunctionIdentifiers + |> Option.filter (fun _ -> not (List.contains identifier config.AllowedImpureFunctions)) + |> Option.map issueWarning + + match args.AstNode with + | AstNode.Identifier (identifier, range) -> + match checkIfImpureIdentifier (String.concat "." identifier) range with + | Some impureIdentifierWarning -> + Array.singleton impureIdentifierWarning + | _ -> + Array.Empty() + | _ -> Array.empty let rule config = AstNodeRule @@ -23,4 +77,3 @@ let rule config = Cleanup = ignore } } - From 9c2d5f712befa7f1db73ae0a9b0c97c03b456a4e Mon Sep 17 00:00:00 2001 From: webwarrior-ws Date: Wed, 26 Aug 2026 14:46:13 +0200 Subject: [PATCH 3/5] NoImpureFunctions: option to disallow types Added an option to specify types that are not allowed to be instantiated. --- docs/content/how-tos/rules/FL0098.md | 2 +- .../Rules/Conventions/NoImpureFunctions.fs | 71 ++++++++++++++++++- src/FSharpLint.Core/Text.resx | 3 + .../Rules/Conventions/NoImpureFunctions.fs | 32 +++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/docs/content/how-tos/rules/FL0098.md b/docs/content/how-tos/rules/FL0098.md index b165e8331..d20cc7d5a 100644 --- a/docs/content/how-tos/rules/FL0098.md +++ b/docs/content/how-tos/rules/FL0098.md @@ -31,4 +31,4 @@ Use pure function. } * *allowedImpureFunctions* - list of strings representing impure functions to allow (e.g. `"Array.set"`). -* *additionalImpureFunctions* - list of strings representing additional impure functions to disallow. +* *additionalImpureFunctions* - list of strings representing additional impure functions to disallow. If a string ends in `.*` (e.g. `Foo.Bar.Baz.*`), then it is treated as type `Foo.Bar.Baz` (without type parameters), using constructors of which is disallowed. diff --git a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs index 402aa6628..250cf9b63 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs @@ -1,11 +1,14 @@ module FSharpLint.Rules.NoImpureFunctions open System -open FSharp.Compiler.Text open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open FSharp.Compiler.Text +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Symbols +open FSharp.Compiler.Syntax [] type Config = { @@ -27,6 +30,15 @@ let private impureFunctionIdentifiers = ("Array2D.fill", None) ("Array2D.blit", None) + + // Disallowed types + ("System.Collections.Generic.List.*", None) + ("System.Collections.Generic.Dictionary.*", None) + ("System.Collections.Generic.HashSet.*", None) + ("System.Collections.Generic.LinkedList.*", None) + ("System.Collections.Generic.SortedDictionary.*", None) + ("System.Collections.Generic.SortedSet.*", None) + ("System.Collections.Generic.Stack.*", None) ] let runner (config:Config) (args:AstNodeRuleParams) = @@ -56,14 +68,67 @@ let runner (config:Config) (args:AstNodeRuleParams) = Map.tryFind identifier impureFunctionIdentifiers |> Option.filter (fun _ -> not (List.contains identifier config.AllowedImpureFunctions)) |> Option.map issueWarning + + let disallowedTypes = + let builtinIdentifiers = Map.keys impureFunctionIdentifiers + (Seq.append config.AdditionalImpureFunctions builtinIdentifiers) + |> Seq.choose (fun definition -> + let wildcardSuffix = ".*" + if definition.EndsWith wildcardSuffix then + Some <| definition.Substring(0, definition.Length - wildcardSuffix.Length) + else + None) + |> Seq.toList + + let checkForDisallowedType (checkResults: FSharpCheckFileResults) (range: Range) = + let allSymbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() + let maybeSymbolUse = + allSymbolUses + |> Seq.tryFind (fun symbolUse -> range = symbolUse.Range) + match maybeSymbolUse with + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as value when value.IsConstructor || value.IsImplicitConstructor -> + let declaringEntity = value.DeclaringEntity + match declaringEntity with + | Some entity -> + let fullNameWithoutTypeParams = + let lestBackTickIndex = entity.FullName.LastIndexOf '`' + if lestBackTickIndex > 0 then + entity.FullName.Substring(0, lestBackTickIndex) + else + entity.FullName + + if disallowedTypes |> List.contains fullNameWithoutTypeParams then + Array.singleton + { + Range = range + Message = String.Format(Resources.GetString "RulesConventionsNoImpureFunctionsDisallowedTypeError", entity.FullName) + SuggestedFix = None + TypeChecks = List.Empty + } + else + Array.empty + | _ -> Array.empty + | _ -> Array.empty + | None -> Array.empty - match args.AstNode with - | AstNode.Identifier (identifier, range) -> + match (args.AstNode, args.CheckInfo) with + | AstNode.Identifier (identifier, range), _ -> match checkIfImpureIdentifier (String.concat "." identifier) range with | Some impureIdentifierWarning -> Array.singleton impureIdentifierWarning | _ -> Array.Empty() + | AstNode.Expression(SynExpr.App(_, false, funcExpr, _, _range)), Some(checkResults) when not disallowedTypes.IsEmpty -> + match funcExpr with + | SynExpr.LongIdent(_, SynLongIdent(_), _, identRange) -> + checkForDisallowedType checkResults identRange + | SynExpr.TypeApp(SynExpr.LongIdent(_, SynLongIdent(_), _, identRange),_,_,_,_,_,_) -> + checkForDisallowedType checkResults identRange + | _ -> Array.empty + | AstNode.Expression(SynExpr.New(_, targetType, _, _)), Some(checkResults) when not disallowedTypes.IsEmpty -> + checkForDisallowedType checkResults targetType.Range | _ -> Array.empty let rule config = diff --git a/src/FSharpLint.Core/Text.resx b/src/FSharpLint.Core/Text.resx index 82f41b4b2..2a60d8d47 100644 --- a/src/FSharpLint.Core/Text.resx +++ b/src/FSharpLint.Core/Text.resx @@ -426,4 +426,7 @@ Consider using '{0}' instead of impure function '{1}'. + + Consider not using mutable type '{0}'. + diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs index 888ca7f3e..a53c73643 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs @@ -27,3 +27,35 @@ type TestConventionsNoImpureFunctions() = Array.set x 0 None") this.AssertNoWarnings() + + [] + member this.``Error for creating a mutable list``() = + this.Parse("let x = System.Collections.Generic.List()") + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Error for creating a mutable list without explicit type param``() = + this.Parse("let x = System.Collections.Generic.List [ 1; 2 ]") + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Error for creating a mutable list with new keyword``() = + this.Parse("open System.Collections.Generic + +let x = new List()") + + Assert.IsTrue this.ErrorsExist + + [] + member this.``No error for using an existing mutable list``() = + this.Parse("open System.Collections.Generic + +type IHasList = + abstract List: List + +let x (hasList: IHasList) = hasList.List") + + this.AssertNoWarnings() + From c3ce1735656cca4f7978b6a94018796987a04e45 Mon Sep 17 00:00:00 2001 From: webwarrior-ws Date: Tue, 1 Sep 2026 12:45:27 +0200 Subject: [PATCH 4/5] NoImpureFunctions: allow whitelisting of types Using AllowedImpureFunctions setting similar to how types can be blacklisted in AdditionalImpureFunctions setting. Modified docs and added a test. --- docs/content/how-tos/rules/FL0098.md | 4 +++- .../Rules/Conventions/NoImpureFunctions.fs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/content/how-tos/rules/FL0098.md b/docs/content/how-tos/rules/FL0098.md index d20cc7d5a..5e74f6155 100644 --- a/docs/content/how-tos/rules/FL0098.md +++ b/docs/content/how-tos/rules/FL0098.md @@ -31,4 +31,6 @@ Use pure function. } * *allowedImpureFunctions* - list of strings representing impure functions to allow (e.g. `"Array.set"`). -* *additionalImpureFunctions* - list of strings representing additional impure functions to disallow. If a string ends in `.*` (e.g. `Foo.Bar.Baz.*`), then it is treated as type `Foo.Bar.Baz` (without type parameters), using constructors of which is disallowed. +* *additionalImpureFunctions* - list of strings representing additional impure functions to disallow. + +If a string in either of those settings ends in `.*` (e.g. `Foo.Bar.Baz.*`), then it is treated as type `Foo.Bar.Baz` (without type parameters). In this case, usage of type's constructors is checked. diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs index a53c73643..c8963c149 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/NoImpureFunctions.fs @@ -7,7 +7,11 @@ open FSharpLint.Core.Tests [] type TestConventionsNoImpureFunctions() = - inherit TestAstNodeRuleBase.TestAstNodeRuleBase(NoImpureFunctions.rule { AdditionalImpureFunctions = ["Custom.impure"]; AllowedImpureFunctions = ["Array.set"] }) + inherit TestAstNodeRuleBase.TestAstNodeRuleBase( + NoImpureFunctions.rule { + AdditionalImpureFunctions = ["Custom.impure"] + AllowedImpureFunctions = ["Array.set"; "System.Collections.Generic.Dictionary.*"] + }) [] member this.``Error for impure function which should be replaced with another function``() = @@ -59,3 +63,11 @@ let x (hasList: IHasList) = hasList.List") this.AssertNoWarnings() + [] + member this.``No error for creating a type from allowed list``() = + this.Parse("open System.Collections.Generic + +let x = new Dictionary()") + + this.AssertNoWarnings() + From 3cc9094e37d036667b50b333c4fdcac149234d85 Mon Sep 17 00:00:00 2001 From: webwarrior-ws Date: Tue, 1 Sep 2026 12:45:46 +0200 Subject: [PATCH 5/5] NoImpureFunctions: implemented whitelisting of types --- .../Rules/Conventions/NoImpureFunctions.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs index 250cf9b63..de7999e10 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NoImpureFunctions.fs @@ -70,15 +70,15 @@ let runner (config:Config) (args:AstNodeRuleParams) = |> Option.map issueWarning let disallowedTypes = - let builtinIdentifiers = Map.keys impureFunctionIdentifiers - (Seq.append config.AdditionalImpureFunctions builtinIdentifiers) + let builtinIdentifiers = Map.keys impureFunctionIdentifiers |> Set.ofSeq + (Set.ofList config.AdditionalImpureFunctions) + builtinIdentifiers - (Set.ofList config.AllowedImpureFunctions) |> Seq.choose (fun definition -> let wildcardSuffix = ".*" if definition.EndsWith wildcardSuffix then Some <| definition.Substring(0, definition.Length - wildcardSuffix.Length) else None) - |> Seq.toList + |> Set.ofSeq let checkForDisallowedType (checkResults: FSharpCheckFileResults) (range: Range) = let allSymbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() @@ -99,7 +99,7 @@ let runner (config:Config) (args:AstNodeRuleParams) = else entity.FullName - if disallowedTypes |> List.contains fullNameWithoutTypeParams then + if disallowedTypes |> Set.contains fullNameWithoutTypeParams then Array.singleton { Range = range