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 3, 2021
1 parent 3b87c69 commit d971395
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/content/how-tos/rule-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,5 @@ The following rules can be specified for linting.
- [GenericTypesNames (FL0069)](rules/FL0069.html)
- [FavourTypedIgnore (FL0070)](rules/FL0070.html)
- [CyclomaticComplexity (FL0071)](rules/FL0071.html)
- [FailwithBadUsage (FL0072)](rules/FL0072.html)
- [FailwithBadUsage (FL0072)](rules/FL0072.html)
- [FriendlyAsyncOverload (FL0078)](rules/FL0078.html)
29 changes: 29 additions & 0 deletions docs/content/how-tos/rules/FL0078.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: FL0078
category: how-to
hide_menu: true
---

# FriendlyAsyncOverload (FL0078)

*Introduced in `0.20.3`*

## Cause

Rule to suggest adding C#-friendly async overloads.

## Rationale

Exposing public F#-async APIs in a C#-friendly manner for better C# interoperability.

## How To Fix

Add an `Async` version of the API that returns a `Task<'T>`

## Rule Settings

{
"friendlyAsyncOverload": {
"enabled": true
}
}
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": false },
"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 d971395

Please sign in to comment.