diff --git a/src/FSharpLint.Core/FSharpLint.Core.fsproj b/src/FSharpLint.Core/FSharpLint.Core.fsproj index c445f1374..9c2f7bf85 100644 --- a/src/FSharpLint.Core/FSharpLint.Core.fsproj +++ b/src/FSharpLint.Core/FSharpLint.Core.fsproj @@ -80,9 +80,9 @@ + - diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs index b585015f8..c71d3fc51 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/AsynchronousFunctionNames.fs @@ -5,6 +5,7 @@ open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open Helper.Naming.Asynchronous open FSharp.Compiler.Syntax let runner (args: AstNodeRuleParams) = @@ -18,7 +19,8 @@ let runner (args: AstNodeRuleParams) = } match args.AstNode with - | AstNode.Binding (SynBinding (_, _, _, _, _, _, _, SynPat.LongIdent(funcIdent, _, _, _, (None | Some(SynAccess.Public _)), identRange), returnInfo, _, _, _, _)) -> + | AstNode.Binding (SynBinding (_, _, _, _, attributes, _, _, SynPat.LongIdent(funcIdent, _, _, _, (None | Some(SynAccess.Public _)), identRange), returnInfo, _, _, _, _)) + when not <| Helper.Naming.isAttribute "Obsolete" attributes -> let parents = args.GetParents args.NodeIndex let hasEnclosingFunctionOrMethod = parents @@ -32,21 +34,21 @@ let runner (args: AstNodeRuleParams) = Array.empty else match returnInfo with - | Some SynchronousFunctionNames.ReturnsAsync -> + | Some ReturnsAsync -> match funcIdent with - | SynchronousFunctionNames.HasAsyncPrefix _ -> + | HasAsyncPrefix _ -> Array.empty - | SynchronousFunctionNames.HasAsyncSuffix name - | SynchronousFunctionNames.HasNoAsyncPrefixOrSuffix name -> - let nameWithAsync = SynchronousFunctionNames.asyncSuffixOrPrefix + name + | HasAsyncSuffix name + | HasNoAsyncPrefixOrSuffix name -> + let nameWithAsync = asyncSuffixOrPrefix + name emitWarning identRange nameWithAsync "Async" - | Some SynchronousFunctionNames.ReturnsTask -> + | Some ReturnsTask -> match funcIdent with - | SynchronousFunctionNames.HasAsyncSuffix _ -> + | HasAsyncSuffix _ -> Array.empty - | SynchronousFunctionNames.HasAsyncPrefix name - | SynchronousFunctionNames.HasNoAsyncPrefixOrSuffix name -> - let nameWithAsync = name + SynchronousFunctionNames.asyncSuffixOrPrefix + | HasAsyncPrefix name + | HasNoAsyncPrefixOrSuffix name -> + let nameWithAsync = name + asyncSuffixOrPrefix emitWarning identRange nameWithAsync "Task" | None -> // TODO: get type using typed tree in args.CheckInfo diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs index 0d10e6f86..1f8f99e78 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs @@ -464,3 +464,27 @@ let getFunctionIdents (pattern:SynPat) = | Some ident -> Array.singleton (ident, ident.idText, None) | None -> Array.empty | _ -> Array.empty + +module Asynchronous = + let asyncSuffixOrPrefix = "Async" + + let (|HasAsyncPrefix|HasAsyncSuffix|HasNoAsyncPrefixOrSuffix|) (pattern: SynLongIdent) = + match List.tryLast pattern.LongIdent with + | Some name -> + if name.idText.StartsWith(asyncSuffixOrPrefix, StringComparison.InvariantCultureIgnoreCase) then + HasAsyncPrefix name.idText + elif name.idText.EndsWith asyncSuffixOrPrefix then + HasAsyncSuffix name.idText + else + HasNoAsyncPrefixOrSuffix name.idText + | _ -> HasNoAsyncPrefixOrSuffix String.Empty + + let (|ReturnsTask|ReturnsAsync|ReturnsNonAsync|) (returnInfo: SynBindingReturnInfo) = + match returnInfo with + | SynBindingReturnInfo(SynType.LongIdent(SynLongIdent(typeIdent, _, _)), _, _, _) + | SynBindingReturnInfo(SynType.App(SynType.LongIdent(SynLongIdent(typeIdent, _, _)), _, _, _, _, _, _), _, _, _) -> + match List.tryLast typeIdent with + | Some ident when ident.idText = "Async" -> ReturnsAsync + | Some ident when ident.idText = "Task" -> ReturnsTask + | _ -> ReturnsNonAsync + | _ -> ReturnsNonAsync diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/SynchronousFunctionNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/SynchronousFunctionNames.fs index 073fb1497..e0e28c556 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/SynchronousFunctionNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/SynchronousFunctionNames.fs @@ -5,32 +5,12 @@ open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open Helper.Naming.Asynchronous open FSharp.Compiler.Syntax open FSharp.Compiler.Symbols let asyncSuffixOrPrefix = "Async" -let (|HasAsyncPrefix|HasAsyncSuffix|HasNoAsyncPrefixOrSuffix|) (pattern: SynLongIdent) = - match List.tryLast pattern.LongIdent with - | Some name -> - if name.idText.StartsWith(asyncSuffixOrPrefix, StringComparison.InvariantCultureIgnoreCase) then - HasAsyncPrefix name.idText - elif name.idText.EndsWith asyncSuffixOrPrefix then - HasAsyncSuffix name.idText - else - HasNoAsyncPrefixOrSuffix name.idText - | _ -> HasNoAsyncPrefixOrSuffix String.Empty - -let (|ReturnsTask|ReturnsAsync|ReturnsNonAsync|) (returnInfo: SynBindingReturnInfo) = - match returnInfo with - | SynBindingReturnInfo(SynType.LongIdent(SynLongIdent(typeIdent, _, _)), _, _, _) - | SynBindingReturnInfo(SynType.App(SynType.LongIdent(SynLongIdent(typeIdent, _, _)), _, _, _, _, _, _), _, _, _) -> - match List.tryLast typeIdent with - | Some ident when ident.idText = "Async" -> ReturnsAsync - | Some ident when ident.idText = "Task" -> ReturnsTask - | _ -> ReturnsNonAsync - | _ -> ReturnsNonAsync - let runner (args: AstNodeRuleParams) = let emitWarning range (newFunctionName: string) = Array.singleton @@ -42,7 +22,8 @@ let runner (args: AstNodeRuleParams) = } match args.AstNode with - | AstNode.Binding (SynBinding (_, _, _, _, _, _, _, SynPat.LongIdent(funcIdent, _, _, _, _, identRange), returnInfo, _, _, _, _)) -> + | AstNode.Binding (SynBinding (_, _, _, _, attributes, _, _, SynPat.LongIdent(funcIdent, _, _, _, _, identRange), returnInfo, _, _, _, _)) + when not <| Helper.Naming.isAttribute "Obsolete" attributes -> match returnInfo with | Some ReturnsNonAsync -> match funcIdent with diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/AsynchronousFunctionNames.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/AsynchronousFunctionNames.fs index a8ccafa52..4ccd41dc4 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/AsynchronousFunctionNames.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/AsynchronousFunctionNames.fs @@ -135,3 +135,41 @@ type Foo() = """ Assert.IsTrue this.NoErrorsExist + + [] + member this.``Functions returning Async or Task with [] attribute should not give violations``() = + this.Parse """ +module Foo = + [] + let Foo(): Async = + async { return 1 } + + [] + let Bar(): Task = + null + + [] + let Baz(): Task = + null +""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Methods returning Async or Task with [] attribute should not give violations``() = + this.Parse """ +type Foo() = + [] + member this.Foo(): Async = + async { return 1 } + + [] + member this.Bar(): Task = + null + + [] + member this.Baz(): Task = + null +""" + + Assert.IsTrue this.NoErrorsExist diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/SynchronousFunctionNames.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/SynchronousFunctionNames.fs index 78438bbba..191abec13 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/SynchronousFunctionNames.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/Naming/SynchronousFunctionNames.fs @@ -171,3 +171,41 @@ type Foo() = """ Assert.IsTrue this.NoErrorsExist + + [] + member this.``Non-asynchronous functions marked with [] attribute should not give violations``() = + this.Parse """ +module Foo = + [] + let AsyncBar(): int = + 1 + + [] + let BarAsync(): int = + 1 + + [] + let private AsyncBar(): int = + 1 +""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Non-asynchronous methods marked with [] attribute should not give violations``() = + this.Parse """ +type Foo() = + [] + member this.AsyncBar(): int = + 1 + + [] + member this.BarAsync(): int = + 1 + + [] + member private this.AsyncBar(): int = + 1 +""" + + Assert.IsTrue this.NoErrorsExist