Skip to content

Commit c18ce72

Browse files
committed
SynchronousFunctionNames: check typed tree
If there is no explicit return type.
1 parent 0eb9295 commit c18ce72

File tree

3 files changed

+86
-21
lines changed

3 files changed

+86
-21
lines changed

src/FSharpLint.Core/Rules/Conventions/Naming/SynchronousFunctionNames.fs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ open FSharpLint.Framework
55
open FSharpLint.Framework.Suggestion
66
open FSharpLint.Framework.Ast
77
open FSharpLint.Framework.Rules
8-
open Helper.Naming.Asynchronous
98
open FSharp.Compiler.Syntax
10-
open FSharp.Compiler.Symbols
11-
12-
let asyncSuffixOrPrefix = "Async"
9+
open Helper.Naming.Asynchronous
10+
open Utilities.TypedTree
1311

1412
let runner (args: AstNodeRuleParams) =
1513
let emitWarning range (newFunctionName: string) =
@@ -21,28 +19,38 @@ let runner (args: AstNodeRuleParams) =
2119
TypeChecks = List.empty
2220
}
2321

22+
let checkIdentifier (funcIdent: SynLongIdent) identRange =
23+
match funcIdent with
24+
| HasAsyncPrefix name ->
25+
let startsWithLowercase = Char.IsLower name.[0]
26+
let nameWithoutAsync = name.Substring asyncSuffixOrPrefix.Length
27+
let suggestedName =
28+
if startsWithLowercase then
29+
sprintf "%c%s" (Char.ToLowerInvariant nameWithoutAsync.[0]) (nameWithoutAsync.Substring 1)
30+
else
31+
nameWithoutAsync
32+
emitWarning identRange suggestedName
33+
| HasAsyncSuffix name ->
34+
let nameWithoutAsync = name.Substring(0, name.Length - asyncSuffixOrPrefix.Length)
35+
emitWarning identRange nameWithoutAsync
36+
| HasNoAsyncPrefixOrSuffix _ -> Array.empty
37+
2438
match args.AstNode with
2539
| AstNode.Binding (SynBinding (_, _, _, _, attributes, _, _, SynPat.LongIdent(funcIdent, _, _, _, _, identRange), returnInfo, _, _, _, _))
2640
when not <| Helper.Naming.isAttribute "Obsolete" attributes ->
2741
match returnInfo with
2842
| Some ReturnsNonAsync ->
29-
match funcIdent with
30-
| HasAsyncPrefix name ->
31-
let startsWithLowercase = Char.IsLower name.[0]
32-
let nameWithoutAsync = name.Substring asyncSuffixOrPrefix.Length
33-
let suggestedName =
34-
if startsWithLowercase then
35-
sprintf "%c%s" (Char.ToLowerInvariant nameWithoutAsync.[0]) (nameWithoutAsync.Substring 1)
36-
else
37-
nameWithoutAsync
38-
emitWarning identRange suggestedName
39-
| HasAsyncSuffix name ->
40-
let nameWithoutAsync = name.Substring(0, name.Length - asyncSuffixOrPrefix.Length)
41-
emitWarning identRange nameWithoutAsync
42-
| HasNoAsyncPrefixOrSuffix _ -> Array.empty
43+
checkIdentifier funcIdent identRange
4344
| None ->
44-
// TODO: get type using typed tree in args.CheckInfo
45-
Array.empty
45+
match args.CheckInfo with
46+
| Some checkInfo ->
47+
match getFunctionReturnType checkInfo args.Lines funcIdent with
48+
| Some returnType ->
49+
match returnType with
50+
| FSharpTypeNonAsync -> checkIdentifier funcIdent identRange
51+
| _ -> Array.empty
52+
| None -> Array.empty
53+
| None -> Array.empty
4654
| _ ->
4755
Array.empty
4856
| _ -> Array.empty

src/FSharpLint.Core/Rules/NamingHelper.fs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,11 @@ module Asynchronous =
476476
let (|HasAsyncPrefix|HasAsyncSuffix|HasNoAsyncPrefixOrSuffix|) (pattern: SynLongIdent) =
477477
match List.tryLast pattern.LongIdent with
478478
| Some name ->
479-
if name.idText.StartsWith(asyncSuffixOrPrefix, StringComparison.InvariantCultureIgnoreCase) then
479+
if not (isNotDoubleBackTickedIdent name) then
480+
// Consider any identifier surrounded by double backticks (usually used as test names)
481+
// to not have Async prefix or suffix.
482+
HasNoAsyncPrefixOrSuffix name.idText
483+
elif name.idText.StartsWith(asyncSuffixOrPrefix, StringComparison.InvariantCultureIgnoreCase) then
480484
HasAsyncPrefix name.idText
481485
elif name.idText.EndsWith asyncSuffixOrPrefix then
482486
HasAsyncSuffix name.idText
@@ -493,3 +497,18 @@ module Asynchronous =
493497
| Some ident when ident.idText = "Task" -> ReturnsTask
494498
| _ -> ReturnsNonAsync
495499
| _ -> ReturnsNonAsync
500+
501+
let (|FSharpTypeAsync|FSharpTypeTask|FSharpTypeTaskNonGeneric|FSharpTypeNonAsync|) (fSharpType: FSharpType) =
502+
try
503+
// BasicQualifiedName may throw InvalidOperationException
504+
match fSharpType.BasicQualifiedName with
505+
| "Microsoft.FSharp.Control.FSharpAsync`1" -> FSharpTypeAsync
506+
| name when name.StartsWith "System.Threading.Tasks.Task" ->
507+
if fSharpType.GenericArguments.Count > 0 then
508+
FSharpTypeTask
509+
else
510+
FSharpTypeTaskNonGeneric
511+
| _ -> FSharpTypeNonAsync
512+
with
513+
| :? InvalidOperationException ->
514+
FSharpTypeNonAsync

src/FSharpLint.Core/Rules/Utilities.fs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
module FSharpLint.Rules.Utilities
22

3+
open FSharp.Compiler.CodeAnalysis
4+
open FSharp.Compiler.Symbols
5+
open FSharp.Compiler.Syntax
6+
7+
module TypedTree =
8+
let tryGetLastGenericArg (fSharpType: FSharpType) =
9+
Seq.tryLast fSharpType.GenericArguments
10+
11+
[<TailCall>]
12+
let rec private getReturnType (fSharpType: FSharpType) =
13+
if fSharpType.IsFunctionType then
14+
match tryGetLastGenericArg fSharpType with
15+
| Some argType -> getReturnType argType
16+
| None -> fSharpType
17+
else
18+
fSharpType
19+
20+
let getFunctionReturnType
21+
(checkInfo: FSharpCheckFileResults)
22+
(lines: array<string>)
23+
(funcIdent: SynLongIdent) : Option<FSharpType> =
24+
let maybeSymbolUse =
25+
match List.tryLast funcIdent.LongIdent with
26+
| Some ident ->
27+
checkInfo.GetSymbolUseAtLocation(
28+
ident.idRange.EndLine,
29+
ident.idRange.EndColumn,
30+
lines.[ident.idRange.EndLine - 1],
31+
List.singleton ident.idText)
32+
| None -> None
33+
match maybeSymbolUse with
34+
| Some symbolUse ->
35+
match symbolUse.Symbol with
36+
| :? FSharpMemberOrFunctionOrValue as func when func.IsFunction ->
37+
Some <| getReturnType func.FullType
38+
| _ -> None
39+
| _ -> None
40+
341
module LibraryHeuristics =
442
type LibraryHeuristicResultByProjectName =
543
| Likely

0 commit comments

Comments
 (0)