Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
36609bd
Core(Tests): add NoAsyncRunSynchronouslyInLibrary
webwarrior-ws Nov 3, 2025
88b23fc
NoAsyncRunSynchronouslyInLibrary: implement rule
webwarrior-ws Nov 4, 2025
3c9e224
NoAsyncRunSynchronouslyInLibrary: check assy name
webwarrior-ws Nov 4, 2025
feb589e
Core(Tests): add 2 more tests
webwarrior-ws Nov 5, 2025
73edbf2
NoAsyncRunSynchronouslyInLibrary: check for test
webwarrior-ws Nov 5, 2025
f1cfe3b
NoAsyncRunSynchronouslyInLibrary: add config
webwarrior-ws Nov 5, 2025
663547a
NoAsyncRunSynchronouslyInLibrary: fix assy check
webwarrior-ws Nov 5, 2025
4c6edf8
NoAsyncRunSynchronouslyInLibrary: refactoring
webwarrior-ws Nov 5, 2025
52b27c6
Core: remove dead code in Application/Lint.fs
webwarrior-ws Nov 6, 2025
b0d2bb9
docs: NoAsyncRunSynchronouslyInLibrary rule docs
webwarrior-ws Nov 18, 2025
e680d17
Core,Console,Tests: refactoring
webwarrior-ws Nov 12, 2025
5ae4086
Benchmarks: suppress rule
webwarrior-ws Nov 13, 2025
01d7f41
NoAsyncRunSynchronouslyInLibrary: more excludes
webwarrior-ws Nov 18, 2025
7b87a1a
docs: updated NoAsyncRunSynchronouslyInLibrary
webwarrior-ws Nov 18, 2025
57e1b37
Core,Tests: include check project results
webwarrior-ws Nov 18, 2025
e451662
NoAsyncRunSynchronouslyInLibrary: check assembly
webwarrior-ws Nov 18, 2025
db25b91
Revert "Benchmarks: suppress rule"
webwarrior-ws Nov 18, 2025
de85e8d
NoAsyncRunSynchronouslyInLibrary: add 2 tests
webwarrior-ws Nov 18, 2025
25099aa
NoAsyncRunSynchronouslyInLibrary: fix rule
webwarrior-ws Nov 18, 2025
1288527
Core: reintroduce non-async parsing methods
webwarrior-ws Nov 18, 2025
9e628a3
NoAsyncRunSynchronouslyInLibrary: ignore obsolete
webwarrior-ws Nov 19, 2025
a6e889a
NoAsyncRunSynchronouslyInLibrary: updated docs
webwarrior-ws Nov 19, 2025
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
1 change: 1 addition & 0 deletions docs/content/how-tos/rule-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,4 @@ The following rules can be specified for linting.
- [EnsureTailCallDiagnosticsInRecursiveFunctions (FL0085)](rules/FL0085.html)
- [FavourAsKeyword (FL0086)](rules/FL0086.html)
- [InterpolatedStringWithNoSubstitution (FL0087)](rules/FL0087.html)
- [NoAsyncRunSynchronouslyInLibrary (FL0088)](rules/FL0088.html)
68 changes: 68 additions & 0 deletions docs/content/how-tos/rules/FL0088.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: FL0088
category: how-to
hide_menu: true
---

# NoAsyncRunSynchronouslyInLibrary (FL0088)

*Introduced in `0.26.8`*

## Cause

`Async.RunSynchronously` method is used to run async computation in library code.

The rule assumes the code is in the library if none of the following is true:
- The code is inside NUnit or MSTest test.
- Namespace or project name contains "test" or "console".
- Assembly has `[<EntryPoint>]` attribute one one of the functions/methods.

## Rationale

Using `Async.RunSynchronously` outside of scripts, tests, and console projects can lead to program becoming non-responsive.

## How To Fix

Remove `Async.RunSynchronously` and wrap the code that uses `async` computations in `async` computation, using `let!`, `use!`, `match!`, or `return!` keyword to get the result.

Example:

```fsharp
type SomeType() =
member self.SomeMethod someParam =
let foo =
asyncSomeFunc someParam
|> Async.RunSynchronously
processFoo foo
```

The function can be modified to be asynchronous. In that case it might be better to prefix its name with Async:

```fsharp
type SomeType() =
member self.AsyncSomeMethod someParam = async {
let! foo = asyncSomeFunc someParam
return processFoo foo
}
```

In case the method/function is public, a nice C#-friendly overload that returns `Task<'T>` could be provided, suffixed with Async, that just calls the previous method with `Async.StartAsTask`:

```fsharp
type SomeType() =
member self.AsyncSomeMethod someParam = async {
let! foo = asyncSomeFunc someParam
return processFoo foo
}
member self.SomeMethodsync someParam =
self.AsyncSomeMethod someParam
|> Async.StartAsTask
```

## Rule Settings

{
"noAsyncRunSynchronouslyInLibrary": {
"enabled": true
}
}
10 changes: 5 additions & 5 deletions src/FSharpLint.Console/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
try
let lintResult =
match fileType with
| FileType.File -> Lint.lintFile lintParams target
| FileType.Source -> Lint.lintSource lintParams target
| FileType.Solution -> Lint.lintSolution lintParams target toolsPath
| FileType.File -> Lint.asyncLintFile lintParams target |> Async.RunSynchronously
| FileType.Source -> Lint.asyncLintSource lintParams target |> Async.RunSynchronously
| FileType.Solution -> Lint.asyncLintSolution lintParams target toolsPath |> Async.RunSynchronously
| FileType.Wildcard ->
output.WriteInfo "Wildcard detected, but not recommended. Using a project (slnx/sln/fsproj) can detect more issues."
let files = expandWildcard target
Expand All @@ -176,9 +176,9 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
LintResult.Success List.empty
else
output.WriteInfo $"Found %d{List.length files} file(s) matching pattern '%s{target}'."
Lint.lintFiles lintParams files
Lint.asyncLintFiles lintParams files |> Async.RunSynchronously
| FileType.Project
| _ -> Lint.lintProject lintParams target toolsPath
| _ -> Lint.asyncLintProject lintParams target toolsPath |> Async.RunSynchronously
handleLintResult lintResult
with
| exn ->
Expand Down
5 changes: 4 additions & 1 deletion src/FSharpLint.Core/Application/Configuration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ type Configuration =
SuggestUseAutoProperty:EnabledConfig option
EnsureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option
FavourAsKeyword:EnabledConfig option
InterpolatedStringWithNoSubstitution:EnabledConfig option }
InterpolatedStringWithNoSubstitution:EnabledConfig option
NoAsyncRunSynchronouslyInLibrary:EnabledConfig option}
with
static member Zero = {
Global = None
Expand Down Expand Up @@ -570,6 +571,7 @@ with
EnsureTailCallDiagnosticsInRecursiveFunctions = None
FavourAsKeyword = None
InterpolatedStringWithNoSubstitution = None
NoAsyncRunSynchronouslyInLibrary = None
}

// fsharplint:enable RecordFieldNames
Expand Down Expand Up @@ -766,6 +768,7 @@ let flattenConfig (config:Configuration) =
config.EnsureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule)
config.FavourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule)
config.InterpolatedStringWithNoSubstitution |> Option.bind (constructRuleIfEnabled InterpolatedStringWithNoSubstitution.rule)
config.NoAsyncRunSynchronouslyInLibrary |> Option.bind (constructRuleIfEnabled NoAsyncRunSynchronouslyInLibrary.rule)
|]

findDeprecation config deprecatedAllRules allRules
Loading
Loading