Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
37ea50e
Core(Tests): add NoAsyncRunSynchronouslyInLibrary
webwarrior-ws Jan 6, 2026
69dc2d9
NoAsyncRunSynchronouslyInLibrary: implement rule
webwarrior-ws Nov 4, 2025
313aaec
NoAsyncRunSynchronouslyInLibrary: check assy name
webwarrior-ws Nov 4, 2025
d84c0e9
Core(Tests): add 2 more tests
webwarrior-ws Nov 5, 2025
bc406ba
NoAsyncRunSynchronouslyInLibrary: check for test
webwarrior-ws Nov 5, 2025
4179957
NoAsyncRunSynchronouslyInLibrary: add config
webwarrior-ws Nov 5, 2025
eb6e5a9
NoAsyncRunSynchronouslyInLibrary: fix assy check
webwarrior-ws Nov 5, 2025
33502fb
NoAsyncRunSynchronouslyInLibrary: refactoring
webwarrior-ws Nov 5, 2025
3d77372
Core: remove dead code in Application/Lint.fs
webwarrior-ws Nov 6, 2025
1c178bd
docs: NoAsyncRunSynchronouslyInLibrary rule docs
webwarrior-ws Nov 18, 2025
11da824
Core,Console,Tests: refactoring
webwarrior-ws Nov 12, 2025
df906fd
NoAsyncRunSynchronouslyInLibrary: more excludes
webwarrior-ws Nov 18, 2025
b2200cf
docs: updated NoAsyncRunSynchronouslyInLibrary
webwarrior-ws Nov 18, 2025
916861f
Core,Tests: include check project results
webwarrior-ws Nov 18, 2025
3f5cc90
NoAsyncRunSynchronouslyInLibrary: check assembly
webwarrior-ws Nov 18, 2025
f1fb444
NoAsyncRunSynchronouslyInLibrary: add 2 tests
webwarrior-ws Nov 18, 2025
2de968c
NoAsyncRunSynchronouslyInLibrary: fix rule
webwarrior-ws Nov 18, 2025
574c0e7
Core: reintroduce non-async parsing methods
webwarrior-ws Nov 18, 2025
c7117ed
NoAsyncRunSynchronouslyInLibrary: ignore obsolete
webwarrior-ws Nov 19, 2025
b80dc92
NoAsyncRunSynchronouslyInLibrary: updated docs
webwarrior-ws Nov 19, 2025
1772e71
NoAsyncRunSynchronouslyInLibrary: refactoring
webwarrior-ws Dec 22, 2025
2cca9d2
NoAsyncRunSynchronouslyInLibrary: moved to Smells
webwarrior-ws Jan 6, 2026
0ea5daa
NoAsyncRunSynchronouslyInLibrary: look at method attrs
webwarrior-ws Jan 6, 2026
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 @@ -130,3 +130,4 @@ The following rules can be specified for linting.
- [InterpolatedStringWithNoSubstitution (FL0087)](rules/FL0087.html)
- [IndexerAccessorStyleConsistency (FL0088)](rules/FL0088.html)
- [FavourSingleton (FL0089)](rules/FL0089.html)
- [NoAsyncRunSynchronouslyInLibrary (FL0090)](rules/FL0090.html)
68 changes: 68 additions & 0 deletions docs/content/how-tos/rules/FL0090.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: FL0090
category: how-to
hide_menu: true
---

# NoAsyncRunSynchronouslyInLibrary (FL0090)

*Introduced in `0.26.10`*

## 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

Your library code might be consumed by certain type of programs which have strict threading requirements (e.g. a long running operation shouldn't be run on the main thread of a desktop app, or it will make the app look like it's hanging for a while), so it's better to expose asynchronous code with `Async<'TResult>` or `Task`/`Task<'TResult>` return types, so that the consumer of your library can decide how/when to start the operation.

## 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.SomeMethodAsync 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 @@ -158,9 +158,9 @@ let private lint
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 @@ -169,9 +169,9 @@ let private lint
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 @@ -554,7 +554,8 @@ type Configuration =
EnsureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option
FavourAsKeyword:EnabledConfig option
InterpolatedStringWithNoSubstitution:EnabledConfig option
FavourSingleton:EnabledConfig option }
FavourSingleton:EnabledConfig option
NoAsyncRunSynchronouslyInLibrary:EnabledConfig option}
with
static member Zero = {
Global = None
Expand Down Expand Up @@ -656,6 +657,7 @@ with
FavourAsKeyword = None
InterpolatedStringWithNoSubstitution = None
FavourSingleton = None
NoAsyncRunSynchronouslyInLibrary = None
}

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

findDeprecation config deprecatedAllRules allRules
Loading
Loading