Skip to content

Commit

Permalink
Add new rule FriendlyAsyncOverload
Browse files Browse the repository at this point in the history
  • Loading branch information
su8898 committed Nov 2, 2021
1 parent 3b87c69 commit e72a50b
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/FSharpLint.Core/FSharpLint.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<Compile Include="Rules\Conventions\NestedStatements.fs" />
<Compile Include="Rules\Conventions\NoPartialFunctions.fs" />
<Compile Include="Rules\Conventions\CyclomaticComplexity.fs" />
<Compile Include="Rules\Conventions\FriendlyAsyncOverload.fs" />
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithTooManyArgumentsHelper.fs" />
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\FailwithWithSingleArgument.fs" />
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithSingleArgument.fs" />
Expand Down
71 changes: 71 additions & 0 deletions src/FSharpLint.Core/Rules/Conventions/FriendlyAsyncOverload.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module FSharpLint.Rules.FriendlyAsyncOverload

open FSharpLint.Framework
open FSharpLint.Framework.Suggestion
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open FSharpLint.Framework.Ast
open FSharpLint.Framework.Rules
open System

type NodeDetails = { Ident: string; Range: range }

let rec private getIdentFromSynPat =
function
| SynPat.LongIdent (longDotId = longDotId) ->
longDotId
|> ExpressionUtilities.longIdentWithDotsToString
|> Some
| SynPat.Typed (pat, _, _) -> getIdentFromSynPat pat
| _ -> None

let runner (args: AstNodeRuleParams) =
let hasAsync (syntaxArray: AbstractSyntaxArray.Node []) nodeIndex fnIdent =
let rec hasAsync index =
if index >= syntaxArray.Length then
None
else
let node = syntaxArray.[index].Actual
match node with
| AstNode.Binding (SynBinding (_, _, _, _, _attributes, _, _, pattern, _, _, range, _)) ->
match getIdentFromSynPat pattern with
| Some ident when ident = fnIdent + "Async" ->
Some(
{ Ident = fnIdent
Range = range }
)
| _ -> hasAsync (index + 1)
| _ -> hasAsync (index + 1)

hasAsync nodeIndex

match args.AstNode with
| AstNode.Binding (SynBinding (_, _, _, _, _, _, _, pattern, synInfo, _, range, _)) ->
match synInfo with
| Some (SynBindingReturnInfo ((SynType.App(SynType.LongIdent(LongIdentWithDots(ident,_)),_,_,_,_,_,_)),_,_)) ->
match ident with
| head::_ when head.idText = "Async" ->
let idents = getIdentFromSynPat pattern
match idents with
| Some ident when ident.EndsWith("Async") = false ->
match hasAsync args.SyntaxArray args.NodeIndex ident with
| Some nod -> Array.empty
| None ->
{ Range = range
Message = String.Format(Resources.GetString "RulesFriendlyAsyncOverload", ident)
SuggestedFix = None
TypeChecks = [] }
|> Array.singleton
| _ -> Array.empty
| _ -> Array.empty
| _ -> Array.empty
| _ -> Array.empty


let rule =
{ Name = "FriendlyAsyncOverload"
Identifier = Identifiers.FriendlyAsyncOverload
RuleConfig =
{ AstNodeRuleConfig.Runner = runner
Cleanup = ignore } }
|> AstNodeRule
1 change: 1 addition & 0 deletions src/FSharpLint.Core/Rules/Identifiers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ let GenericTypesNames = identifier 69
let FavourTypedIgnore = identifier 70
let CyclomaticComplexity = identifier 71
let FailwithBadUsage = identifier 72
let FriendlyAsyncOverload = identifier 77
3 changes: 3 additions & 0 deletions src/FSharpLint.Core/Text.resx
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,7 @@
<data name="RulesFailwithBadUsage" xml:space="preserve">
<value>Bad usage of failwith: {0}.</value>
</data>
<data name="RulesFriendlyAsyncOverload" xml:space="preserve">
<value>Consider using a friendly async overload for {0}.</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/FSharpLint.Core/fsharplint.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"invalidArgWithTwoArguments": { "enabled": true },
"failwithfWithArgumentsMatchingFormatString": { "enabled": true },
"failwithBadUsage": { "enabled": true },
"friendlyAsyncOverload": { "enabled": true },
"maxLinesInLambdaFunction": {
"enabled": false,
"config": {
Expand Down
1 change: 1 addition & 0 deletions tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Compile Include="Rules\Conventions\FunctionReimplementation.fs" />
<Compile Include="Rules\Conventions\SourceLength.fs" />
<Compile Include="Rules\Conventions\NoPartialFunctions.fs" />
<Compile Include="Rules\Conventions\FriendlyAsyncOverload.fs" />
<Compile Include="Rules\Conventions\Naming\NamingHelpers.fs" />
<Compile Include="Rules\Conventions\Naming\InterfaceNames.fs" />
<Compile Include="Rules\Conventions\Naming\ExceptionNames.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module FSharpLint.Core.Tests.Rules.Conventions.FriendlyAsyncOverload

open NUnit.Framework
open FSharpLint.Rules

[<TestFixture>]
type TestConventionsFriendlyAsyncOverload() =
inherit TestAstNodeRuleBase.TestAstNodeRuleBase(FriendlyAsyncOverload.rule)

[<Test>]
member this.``async function must suggest friendly implementation``() =
this.Parse("""
module Foo =
let Bar(): Async<unit> =
Async.Sleep 5""")

Assert.IsTrue(this.ErrorExistsAt(3, 8))

[<Test>]
member this.``async function with friendly implementation must not have errors``() =
this.Parse("""
module Foo =
let Bar(): Async<unit> =
Async.Sleep 5
let BarAsync(): Task<unit> =
Bar() |> Async.StartAsTask""")

this.AssertNoWarnings()

[<Test>]
member this.``non async function must not create warnings``() =
this.Parse("""
module Foo =
let Bar() =
()""")

this.AssertNoWarnings()

0 comments on commit e72a50b

Please sign in to comment.