Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.18.0 - 2025-08-27

### Changed
* The magic string for the DisposedBeforeAsyncRunAnalyzer has changed: it is now "disposed before returned workflow runs". [#94](https://github.com/G-Research/fsharp-analyzers/pull/94)

## 0.17.0 - 2025-07-12

### Changed
Expand Down
4 changes: 2 additions & 2 deletions docs/analyzers/DisposedBeforeAsyncRunAnalyzer.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ let f () =
}
```

If the `use` before the `async` is really your intent, you can disable the warning with a line comment on top of the `use` statement containing `disposed before returned async runs` or `disposed before returned task runs`.
If the `use` before the `async` is really your intent, you can disable the warning with a line comment on top of the `use` statement containing `disposed before returned workflow runs`.
```fsharp
let f () =
// Note: disposed before returned async runs
// Note: disposed before returned workflow runs
use t = new DisposableThing()
async {
return "hi"
Expand Down
28 changes: 28 additions & 0 deletions src/FSharp.Analyzers/Comments.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module FSharp.Analyzers.Comments

open System
open FSharp.Compiler.SyntaxTrivia
open FSharp.Compiler.Text

/// A standard pattern within an analyzer is to look for a specific comment preceding a problematic line,
/// indicating "suppress the analyzer on this line".
/// This function performs that common check.
let isSwitchedOffPerComment
(magicComment : string)
(comments : CommentTrivia list)
(sourceText : ISourceText)
(analyzerTriggeredOn : Range)
: bool
=
comments
|> List.exists (fun c ->
match c with
| CommentTrivia.LineComment r ->
if r.StartLine <> analyzerTriggeredOn.StartLine - 1 then
false
else
let lineOfComment = sourceText.GetLineString (r.StartLine - 1) // 0-based

lineOfComment.Contains (magicComment, StringComparison.OrdinalIgnoreCase)
| _ -> false
)
29 changes: 9 additions & 20 deletions src/FSharp.Analyzers/DisposedBeforeAsyncRunAnalyzer.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module GR.FSharp.Analyzers.DisposedBeforeAsyncRunAnalyzer

open System
open FSharp.Analyzers.Comments
open FSharp.Analyzers.SDK
open FSharp.Analyzers.SDK.ASTCollecting
open FSharp.Compiler.CodeAnalysis
Expand Down Expand Up @@ -65,32 +66,14 @@ let pathContainsComputationExpr (path : SyntaxVisitorPath) =
)

[<Literal>]
let SwitchOffAsyncComment = "disposed before returned async runs"

[<Literal>]
let SwitchOffTaskComment = "disposed before returned task runs"
let SwitchOffComment = "disposed before returned workflow runs"

let collectUses (sourceText : ISourceText) (ast : ParsedInput) (checkFileResults : FSharpCheckFileResults) =
let comments =
match ast with
| ParsedInput.ImplFile parsedImplFileInput -> parsedImplFileInput.Trivia.CodeComments
| _ -> []

let isSwitchedOffPerComment (range : Range) =
comments
|> List.exists (fun c ->
match c with
| CommentTrivia.LineComment r ->
if r.StartLine <> range.StartLine - 1 then
false
else
let lineOfComment = sourceText.GetLineString (r.StartLine - 1) // 0-based

lineOfComment.Contains (SwitchOffAsyncComment, StringComparison.OrdinalIgnoreCase)
|| lineOfComment.Contains (SwitchOffTaskComment, StringComparison.OrdinalIgnoreCase)
| _ -> false
)

let uses = ResizeArray<range * string> ()

// Note: not tailrecursive
Expand All @@ -117,7 +100,13 @@ let collectUses (sourceText : ISourceText) (ast : ParsedInput) (checkFileResults
match synExpr with
| SynExpr.LetOrUse (isUse = true ; bindings = [ binding ] ; body = body) ->
if
not (isSwitchedOffPerComment binding.RangeOfBindingWithoutRhs)
not (
isSwitchedOffPerComment
SwitchOffComment
comments
sourceText
binding.RangeOfBindingWithoutRhs
)
&& hasAsyncOrTaskInBody body
then
match pathContainsAsyncOrTaskReturningFunc checkFileResults sourceText path with
Expand Down
3 changes: 2 additions & 1 deletion src/FSharp.Analyzers/FSharp.Analyzers.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Util.fs" />
<Compile Include="Comments.fs" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a great name, I guess.

<Compile Include="StringAnalyzer.fsi" />
<Compile Include="StringAnalyzer.fs" />
<Compile Include="JsonSerializerOptionsAnalyzer.fs" />
Expand Down Expand Up @@ -43,4 +44,4 @@
<TfmSpecificPackageFile Include="$(OutputPath)\$(AssemblyName).dll" PackagePath="analyzers/dotnet/fs" />
</ItemGroup>
</Target>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type DisposableThing () =
member _.Dispose() = ()

let asyncReturningFunc () =
// Note: disposed before returned async runs
// Note: disposed before returned workflow runs
use t = new DisposableThing()
async {
return "hi"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type DisposableThing () =
member _.Dispose() = ()

let taskReturningFunc () =
// Note: disposed before returned task runs
// Note: disposed before returned workflow runs
use t = new DisposableThing()
task {
return "hi"
Expand Down