11module GR.FSharp.Analyzers.SyncBlockingAnalyzer
22
33open System
4+ open FSharp.Analyzers .Comments
45open FSharp.Analyzers .SDK
56open FSharp.Analyzers .SDK .TASTCollecting
67open FSharp.Compiler .CodeAnalysis
@@ -17,7 +18,7 @@ let SwitchOffComment = "synchronous blocking call allowed"
1718
1819let problematicMethods =
1920 [
20- " Microsoft.FSharp.Control.RunSynchronously" // This is how it appears in the TAST
21+ " Microsoft.FSharp.Control.RunSynchronously" // This is how it appears in the TAST
2122 " Microsoft.FSharp.Control.Async.RunSynchronously"
2223 " Microsoft.FSharp.Control.FSharpAsync.RunSynchronously"
2324 " System.Threading.Tasks.Task.Wait"
@@ -32,67 +33,50 @@ let problematicMethods =
3233
3334let problematicProperties =
3435 [
35- " System.Threading.Tasks.Task.get_Result" // Note: F# doesn't include `1 in FullName for generic type property getters
36- " System.Threading.Tasks.ValueTask.get_Result" // Same here
36+ " System.Threading.Tasks.Task.get_Result" // Note: F# doesn't include `1 in FullName for generic type property getters
37+ " System.Threading.Tasks.ValueTask.get_Result" // Same here
3738 ]
3839 |> Set.ofList
3940
40- let isSwitchedOffPerComment ( sourceText : ISourceText ) ( comments : CommentTrivia list ) ( range : Range ) =
41- comments
42- |> List.exists ( fun c ->
43- match c with
44- | CommentTrivia.LineComment r ->
45- // Check same line or line above
46- if r.StartLine = range.StartLine || r.StartLine = range.StartLine - 1 then
47- let lineOfComment = sourceText.GetLineString( r.StartLine - 1 ) // 0-based
48- lineOfComment.Contains( SwitchOffComment, StringComparison.OrdinalIgnoreCase)
49- else
50- false
51- | CommentTrivia.BlockComment r ->
52- // Check if block comment is on same line or line above
53- if r.StartLine = range.StartLine || r.EndLine = range.StartLine - 1 then
54- let startLine = sourceText.GetLineString( r.StartLine - 1 )
55- startLine.Contains( SwitchOffComment, StringComparison.OrdinalIgnoreCase)
56- else
57- false
58- )
59-
60- let analyze ( sourceText : ISourceText ) ( ast : ParsedInput ) ( checkFileResults : FSharpCheckFileResults ) =
41+ let analyze ( sourceText : ISourceText ) ( ast : ParsedInput ) ( checkFileResults : FSharpCheckFileResults ) =
6142 let comments =
6243 match ast with
6344 | ParsedInput.ImplFile parsedImplFileInput -> parsedImplFileInput.Trivia.CodeComments
6445 | _ -> []
6546
66- let violations = ResizeArray< range * string * string>()
47+ let violations = ResizeArray< range * string * string> ()
6748
6849 let walker =
6950 { new TypedTreeCollectorBase() with
70- override _.WalkCall _ ( mfv : FSharpMemberOrFunctionOrValue ) _ _ _ ( m : range ) =
71-
51+ override _.WalkCall _ ( mfv : FSharpMemberOrFunctionOrValue ) _ _ _ ( m : range ) =
52+
7253 // Check for regular method calls
7354 if problematicMethods.Contains mfv.FullName then
74- if not ( isSwitchedOffPerComment sourceText comments m) then
55+ if not ( isSwitchedOffPerComment SwitchOffComment comments sourceText m) then
7556 let methodName =
76- if mfv.DisplayName.Contains( " . " ) then
57+ if mfv.DisplayName.Contains '.' then
7758 mfv.DisplayName
78- else
59+ else if
7960 // Special handling for Async.RunSynchronously
80- if mfv.FullName = " Microsoft.FSharp.Control.RunSynchronously" then
81- " Async.RunSynchronously"
61+ mfv.FullName = " Microsoft.FSharp.Control.RunSynchronously"
62+ then
63+ " Async.RunSynchronously"
64+ else
65+ // Get more context for better error messages
66+ let parts = mfv.FullName.Split '.'
67+
68+ if parts.Length >= 2 then
69+ $" {parts.[parts.Length - 2]}.{parts.[parts.Length - 1]}"
8270 else
83- // Get more context for better error messages
84- let parts = mfv.FullName.Split( '.' )
85- if parts.Length >= 2 then
86- $" {parts.[parts.Length - 2]}.{parts.[parts.Length - 1]}"
87- else
88- mfv.DisplayName
89- violations.Add( m, " method" , methodName)
90-
71+ mfv.DisplayName
72+
73+ violations.Add ( m, " method" , methodName)
74+
9175 // Check for property getters (Result property access)
9276 elif problematicProperties.Contains mfv.FullName then
93- if not ( isSwitchedOffPerComment sourceText comments m) then
77+ if not ( isSwitchedOffPerComment SwitchOffComment comments sourceText m) then
9478 // For property getters, use just the property name
95- violations.Add( m, " property" , " Result" )
79+ violations.Add ( m, " property" , " Result" )
9680 }
9781
9882 match checkFileResults.ImplementationFile with
@@ -104,10 +88,10 @@ let analyze (sourceText: ISourceText) (ast: ParsedInput) (checkFileResults: FSha
10488 {
10589 Type = " SyncBlockingAnalyzer"
10690 Message =
107- $" Synchronous blocking call '%s {name}' should be avoided. " +
108- " This can cause deadlocks and thread pool starvation. " +
109- " Consider using `let!` in a `task` or `async` computation expression. " +
110- " Suppress with comment including text 'synchronous blocking call allowed'."
91+ $" Synchronous blocking call '%s {name}' should be avoided. "
92+ + " This can cause deadlocks and thread pool starvation. "
93+ + " Consider using `let!` in a `task` or `async` computation expression. "
94+ + " Suppress with comment including text 'synchronous blocking call allowed'."
11195 Code = Code
11296 Severity = Severity.Warning
11397 Range = range
@@ -128,16 +112,16 @@ let HelpUri =
128112 " https://g-research.github.io/fsharp-analyzers/analyzers/SyncBlockingAnalyzer.html"
129113
130114[<CliAnalyzer( Name, ShortDescription, HelpUri) >]
131- let syncBlockingCliAnalyzer : Analyzer < CliContext > =
132- fun ( ctx : CliContext ) ->
115+ let syncBlockingCliAnalyzer : Analyzer < CliContext > =
116+ fun ( ctx : CliContext ) ->
133117 async { return analyze ctx.SourceText ctx.ParseFileResults.ParseTree ctx.CheckFileResults }
134118
135119[<EditorAnalyzer( Name, ShortDescription, HelpUri) >]
136- let syncBlockingEditorAnalyzer : Analyzer < EditorContext > =
137- fun ( ctx : EditorContext ) ->
120+ let syncBlockingEditorAnalyzer : Analyzer < EditorContext > =
121+ fun ( ctx : EditorContext ) ->
138122 async {
139123 return
140124 ctx.CheckFileResults
141125 |> Option.map ( analyze ctx.SourceText ctx.ParseFileResults.ParseTree)
142126 |> Option.defaultValue []
143- }
127+ }
0 commit comments