Skip to content

Commit 4f89750

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

File tree

3 files changed

+69
-20
lines changed

3 files changed

+69
-20
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,17 @@ module Asynchronous =
493493
| Some ident when ident.idText = "Task" -> ReturnsTask
494494
| _ -> ReturnsNonAsync
495495
| _ -> ReturnsNonAsync
496+
497+
let (|FSharpTypeAsync|FSharpTypeTask|FSharpTypeTaskNonGeneric|FSharpTypeNonAsync|) (fSharpType: FSharpType) =
498+
try
499+
match fSharpType.BasicQualifiedName with
500+
| "Microsoft.FSharp.Control.FSharpAsync`1" -> FSharpTypeAsync
501+
| name when name.StartsWith "System.Threading.Tasks.Task" ->
502+
if fSharpType.GenericArguments.Count > 0 then
503+
FSharpTypeTask
504+
else
505+
FSharpTypeTaskNonGeneric
506+
| _ -> FSharpTypeNonAsync
507+
with
508+
| :? InvalidOperationException ->
509+
FSharpTypeNonAsync

src/FSharpLint.Core/Rules/Utilities.fs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
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 getFunctionReturnType
9+
(checkInfo: FSharpCheckFileResults)
10+
(lines: array<string>)
11+
(funcIdent: SynLongIdent) : Option<FSharpType> =
12+
let maybeSymbolUse =
13+
checkInfo.GetSymbolUseAtLocation(
14+
funcIdent.Range.EndLine,
15+
funcIdent.Range.EndColumn,
16+
lines.[funcIdent.Range.EndLine],
17+
funcIdent.LongIdent |> List.map (fun ident -> ident.idText))
18+
match maybeSymbolUse with
19+
| Some symbolUse ->
20+
match symbolUse.Symbol with
21+
| :? FSharpMemberOrFunctionOrValue as func when func.IsFunction ->
22+
let lastGenericArg = func.FullType.GenericArguments[func.FullType.GenericArguments.Count - 1]
23+
if func.IsMember then
24+
Some <| lastGenericArg.GenericArguments[lastGenericArg.GenericArguments.Count - 1]
25+
else
26+
Some lastGenericArg
27+
| _ -> None
28+
| _ -> None
29+
330
module LibraryHeuristics =
431
type LibraryHeuristicResultByProjectName =
532
| Likely

0 commit comments

Comments
 (0)