Skip to content

Commit 894f29a

Browse files
su8898parhamsaremi
authored andcommitted
Add new rule C#-FriendlyAsyncOverload
Fixes #517
1 parent 0daea9c commit 894f29a

File tree

11 files changed

+193
-2
lines changed

11 files changed

+193
-2
lines changed

docs/content/how-tos/rule-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,4 @@ The following rules can be specified for linting.
117117
- [FavourConsistentThis (FL0074)](rules/FL0074.html)
118118
- [AvoidTooShortNames (FL0075)](rules/FL0075.html)
119119
- [FavourStaticEmptyFields (FL0076)](rules/FL0076.html)
120+
- [CSharpFriendlyAsyncOverload (FL0077)](rules/FL0077.html)

docs/content/how-tos/rules/FL0075.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ Use longer names for the flagged occurrences.
2424

2525
{
2626
"avoidTooShortNames": {
27-
"enabled": false
27+
"enabled": false
2828
}
2929
}

docs/content/how-tos/rules/FL0077.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: FL0077
3+
category: how-to
4+
hide_menu: true
5+
---
6+
7+
# CSharpFriendlyAsyncOverload (FL0077)
8+
9+
*Introduced in `0.21.1`*
10+
11+
## Cause
12+
13+
Rule to suggest adding C#-friendly async overloads.
14+
15+
## Rationale
16+
17+
Exposing public async APIs in a C#-friendly manner for better C# interoperability.
18+
19+
## How To Fix
20+
21+
Add an `Async`-suffixed version of the API that returns a `Task<'T>`
22+
23+
## Rule Settings
24+
25+
{
26+
"csharpFriendlyAsyncOverload": {
27+
"enabled": false
28+
}
29+
}

src/FSharpLint.Core/Application/Configuration.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,8 @@ type Configuration =
454454
MaxLinesInFile:RuleConfig<MaxLinesInFile.Config> option
455455
TrailingNewLineInFile:EnabledConfig option
456456
NoTabCharacters:EnabledConfig option
457-
NoPartialFunctions:RuleConfig<NoPartialFunctions.Config> option }
457+
NoPartialFunctions:RuleConfig<NoPartialFunctions.Config> option
458+
CSharpFriendlyAsyncOverload:EnabledConfig option }
458459
with
459460
static member Zero = {
460461
Global = None
@@ -539,6 +540,7 @@ with
539540
TrailingNewLineInFile = None
540541
NoTabCharacters = None
541542
NoPartialFunctions = None
543+
CSharpFriendlyAsyncOverload = None
542544
}
543545

544546
// fsharplint:enable RecordFieldNames
@@ -687,6 +689,7 @@ let flattenConfig (config:Configuration) =
687689
config.TrailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule)
688690
config.NoTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule)
689691
config.NoPartialFunctions |> Option.bind (constructRuleWithConfig NoPartialFunctions.rule)
692+
config.CSharpFriendlyAsyncOverload |> Option.bind (constructRuleIfEnabled CSharpFriendlyAsyncOverload.rule)
690693
|] |> Array.choose id
691694

692695
if config.NonPublicValuesNames.IsSome &&

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<Compile Include="Rules\Conventions\CyclomaticComplexity.fs" />
5151
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
5252
<Compile Include="Rules\Conventions\FavourConsistentThis.fs" />
53+
<Compile Include="Rules\Conventions\CSharpFriendlyAsyncOverload.fs" />
5354
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithTooManyArgumentsHelper.fs" />
5455
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\FailwithWithSingleArgument.fs" />
5556
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithSingleArgument.fs" />
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module FSharpLint.Rules.CSharpFriendlyAsyncOverload
2+
3+
open FSharpLint.Framework
4+
open FSharpLint.Framework.Suggestion
5+
open FSharp.Compiler.Syntax
6+
open FSharp.Compiler.Text
7+
open FSharpLint.Framework.Ast
8+
open FSharpLint.Framework.Rules
9+
open System
10+
11+
type NodeDetails = { Ident: string; Range: range }
12+
13+
let rec private getIdentFromSynPat =
14+
function
15+
| SynPat.LongIdent (longDotId = longDotId) ->
16+
longDotId
17+
|> ExpressionUtilities.longIdentWithDotsToString
18+
|> Some
19+
| SynPat.Typed (pat, _, _) -> getIdentFromSynPat pat
20+
| _ -> None
21+
22+
let runner (args: AstNodeRuleParams) =
23+
let hasAsync (syntaxArray: array<AbstractSyntaxArray.Node>) nodeIndex fnIdent =
24+
let rec hasAsync index =
25+
if index >= syntaxArray.Length then
26+
None
27+
else
28+
let node = syntaxArray.[index].Actual
29+
match node with
30+
| AstNode.Binding (SynBinding (_, _, _, _, _attributes, _, _, pattern, _, _, range, _)) ->
31+
match getIdentFromSynPat pattern with
32+
| Some ident when ident = fnIdent + "Async" ->
33+
{ Ident = fnIdent
34+
Range = range } |> Some
35+
| _ -> hasAsync (index + 1)
36+
| _ -> hasAsync (index + 1)
37+
38+
hasAsync nodeIndex
39+
40+
match args.AstNode with
41+
| AstNode.Binding (SynBinding (_, _, _, _, _, _, _, pattern, synInfo, _, range, _)) ->
42+
match synInfo with
43+
| Some (SynBindingReturnInfo (SynType.App(SynType.LongIdent(LongIdentWithDots(ident, _)), _, _, _, _, _, _), _, _)) ->
44+
match ident with
45+
| head::_ when head.idText = "Async" ->
46+
let idents = getIdentFromSynPat pattern
47+
match idents with
48+
| Some ident when not (ident.EndsWith "Async") ->
49+
match hasAsync args.SyntaxArray args.NodeIndex ident with
50+
| Some _ -> Array.empty
51+
| None ->
52+
{ Range = range
53+
Message = String.Format(Resources.GetString "RulesCSharpFriendlyAsyncOverload", ident)
54+
SuggestedFix = None
55+
TypeChecks = List.Empty }
56+
|> Array.singleton
57+
| _ -> Array.empty
58+
| _ -> Array.empty
59+
| _ -> Array.empty
60+
| _ -> Array.empty
61+
62+
63+
let rule =
64+
{ Name = "CSharpFriendlyAsyncOverload"
65+
Identifier = Identifiers.CSharpFriendlyAsyncOverload
66+
RuleConfig =
67+
{ AstNodeRuleConfig.Runner = runner
68+
Cleanup = ignore } }
69+
|> AstNodeRule

src/FSharpLint.Core/Rules/Identifiers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,4 @@ let FavourReRaise = identifier 73
8181
let FavourConsistentThis = identifier 74
8282
let AvoidTooShortNames = identifier 75
8383
let FavourStaticEmptyFields = identifier 76
84+
let CSharpFriendlyAsyncOverload = identifier 77

src/FSharpLint.Core/Text.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,7 @@
345345
<data name="RulesFavourStaticEmptyFieldsForArray" xml:space="preserve">
346346
<value>Consider using 'Array.empty' instead.</value>
347347
</data>
348+
<data name="RulesCSharpFriendlyAsyncOverload" xml:space="preserve">
349+
<value>Consider using a C#-friendly async overload for {0}.</value>
350+
</data>
348351
</root>

src/FSharpLint.Core/fsharplint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"invalidArgWithTwoArguments": { "enabled": true },
4646
"failwithfWithArgumentsMatchingFormatString": { "enabled": true },
4747
"failwithBadUsage": { "enabled": true },
48+
"csharpFriendlyAsyncOverload": { "enabled": false },
4849
"maxLinesInLambdaFunction": {
4950
"enabled": false,
5051
"config": {

tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
4040
<Compile Include="Rules\Conventions\FavourConsistentThis.fs" />
4141
<Compile Include="Rules\Conventions\AvoidTooShortNames.fs" />
42+
<Compile Include="Rules\Conventions\CSharpFriendlyAsyncOverload.fs" />
4243
<Compile Include="Rules\Conventions\Naming\NamingHelpers.fs" />
4344
<Compile Include="Rules\Conventions\Naming\InterfaceNames.fs" />
4445
<Compile Include="Rules\Conventions\Naming\ExceptionNames.fs" />

0 commit comments

Comments
 (0)