fsprojects / fsprojects/FSharpLint

Async-related rules

Open
#517 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
F#
Stars
327
Forks
74
PR merge metrics
No merged PRs in 30d

Description

Testcases for SynchronousFunctionNames rule v1
module Foo =
    let AsyncBar(): int =
        1

Should give violation offering renaming function removing Async prefix (rename AsyncBar to Bar) because return type is not Async<'T>.

module Foo =
    let BarAsync(): int =
        1

Should give violation offering renaming function removing Async suffix (rename BarAsync to Bar) because return type is not Task or Task<'T>.

module Foo =
    let private AsyncBar(): int =
        1

Should give violation (even if it's not public) offering renaming function removing Async prefix (rename AsyncBar to Bar) because return type is not Async<'T>.

module Foo =
    let internal AsyncBar(): int =
        1

Should give violation (even if it's not public) offering renaming function removing Async prefix (rename AsyncBar to Bar) because return type is not Async<'T>.

    let AsyncFoo(): Async<int> =
        async { return 1 }
    let AsyncBar(): Async<unit> =
        async { return () }
    let AsyncBaz(): Async<unit> =
        async { do! Async.Sleep(1000) }

Should not give any violations.

    let FooAsync(): Task =
        null
    let BarAsync(): Task<int> =
        null

Should not give any violations.

module Foo =
    let Bar(): int =
        let BazAsync(): int =
            0
        1

Should give violation (even if it's not public (nested)) offering renaming function removing Async suffix (rename BazAsync to Baz) because return type is not Async<'T>.

Testcases for AsynchronousFunctionNames rule v1
module Foo =
    let Bar(): Async<int> =
        async { return 1 }

Should give violations offering rename Bar to AsyncBar.

module Foo =
    let Bar(): Task<int> =
        null

Should give violations offering renaming Bar to BarAsync.

module Foo =
    let Bar(): Task =
        null

Should give violations offering renaming Bar to BarAsync.

module Foo =
    let private Bar1(): Async<int> =
        async { return 1 }
    let internal BarBaz1(): Async<int> =
        async { return 1 }
    let private Bar2(): Task<int> =
        null
    let internal BarBaz2(): Task =
        null

Should give no violations (because members are not public).

Testcases for v1.1 of rules SynchronousFunctionNames and AsynchronousFunctionNames

Same as v1 but with methods instead of functions.

Testcases for v1.2 of rules SynchronousFunctionNames and AsynchronousFunctionNames

Using [<Obsolete>] methods or functions should not give violations.

Testcases for rule SimpleAsyncComplementaryHelpers v1
module Foo =
    let AsyncBar(): Async<int> =
        async { return 0 }

Should give violations offering creation of BarAsync(): Task<int> that just calls Async.StartAsTask(AsyncBar()).

module Foo =
    let internal AsyncBar(): Async<int> =
        async { return 0 }
    let private AsyncBaz(): Async<int> =
        async { return 0 }

Should not give any violations (because they are not public).

module Foo =
    let AsyncBar(): Async<int> =
        async { return 0 }
    let BarAsync(): Task<int> =
        Async.StartAsTask(AsyncBar())

Should not give any violations (because it complies with conventions).

module Foo =
    let BarAsync(): Task<int> =
        Task.FromResult(1)

Should give violation offering creation of AsyncBar(): Async<int> that just calls async { return Async.AwaitTask (BarAsync()) }.

module Foo =
    let internal BarAsync(): Task<int> =
        Task.FromResult(1)
    let private BazAsync(): Task<int> =
        Task.FromResult(1)

Should not give any violation (because they are not public).

module Foo =
    let BarAsync(): Task<int> =
        Task.FromResult(1)
    let AsyncBar(): Async<int> =
        async { return Async.AwaitTask (BarAsync()) }

Should not give any violation (because it complies with conventions).

module Foo =
    let Bar(): Async<int> =
        async { return 0 }

Should not give linter violation offering to create Async-suffixed Task function because the first priority of the rule is first to offer renaming Bar to AsyncBar (and not offer new overloads until the name is correct first).

module Foo =
    let AsyncBar(): Async<unit> =
        Async.Sleep 5.0

Should give linter violation offering creation of BarAsync(): Task (because unit in C# doesn't exist) that just calls Async.StartAsTask(AsyncBar()).

module Foo =
    let Bar(): Async<unit> =
        Async.Sleep 5.0

Should not give linter violation offering to create Async-suffixed Task function because the first priority is renaming Bar to AsyncBar (so, this rule should run under-the-hood the AsynchronousFunctionNames rule and if it finds any violation, give a warning saying that the rule had to be silenced and recommend enabling&fixing AsynchronousFunctionNames first).

RecommendSimpleAsynchronousHelpers v2 and AsynchronousFunctionNames v2

Introduce a config setting for these two rules:

        "config": {
            "mode": "OnlyPublicAPIsInLibraries"
        }

Mode's DU will have 3 elements: OnlyPublicAPIsInLibraries (default when not specified), AnyPublicAPIs, AllAPIs.

The mode OnlyPublicAPIsInLibraries will use the LibraryHeuristicResultByProjectName DU and logic that we introduced in the rule NoAsyncRunSynchronouslyInLibrary. So it will only run in projects whose name resolves to .Likely.

Mode AnyPublicAPIs will not try to guess LibraryHeuristicResultByProjectName: it will flag all public APIs like in v1.x.

Mode AllAPIs will also flag (give violations about) internal, private, and nested APIs.

Please develop testcases accordingly.

v2.2 of all three rules

Add testcases where output type of functions/methods is not specified, but by examining the typed syntax tree it can be queried. (e.g. overriding a parent class, implementing an interface, returning a Task/Async element inside the body of the function very deeply into the implementation of it (not easy to guess by looking at the untyped tree).

v2.3 of all three rules

Add testcases where CE task is used (and no type is specified in signature), to make sure the 2.2 implementation covers this.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the SynchronousFunctionNames, AsynchronousFunctionNames, SimpleAsyncComplementaryHelpers, and RecommendSimpleAsynchronousHelpers rules described in the issue. Review their existing test coverage and add cases for the three API modes, typed syntax-tree return types, and task computation expressions. Done means the requested naming, visibility, helper-generation, and configuration behavior is covered without violations for compliant cases.

Written by the indexing model from the issue text.

Assessment

Domain
testing, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.