FSharpLint

repository·master·Indexed 43 days ago

https://github.com/fsprojects/fsharplint

A static analysis tool for F# designed to enforce coding conventions, identify code smells, and check formatting. It analyzes source code using typed and untyped syntax trees and can be used as a dotnet tool, an MSBuild task, or via the FSharpLint.Core API.

Tokens
28.5K
Snippets
147
Records
192
Agent score
52%

What's inside FSharpLint

  1. Overview of FSharpLint

    master

    FSharpLint is a static analysis tool for F# that analyzes source code using both typed and untyped syntax trees to highlight violations of coding rules. It categorizes rules into three types:

    • Conventions: Ensures API members follow naming standards or that code structure stays within configurable length limits.
    • Smells: Warns about logic that might indicate defects or maintainability issues.
    • Formatting: Checks indentation and cosmetic standards (Note: formatting rules are deprecated in favor of tools like fantomas).

    The tool is configurable via JSON and can be used as a console application, an MSBuild task, or integrated into other software via its API.

  2. What is FSharpLint

    master

    FSharpLint is a static analysis tool for F# that analyzes source code using both typed and untyped syntax trees to identify violations of coding rules. It categorizes rules into three types:

    • Conventions: Ensures API members follow project- or solution-wide naming conventions (e.g., PascalCase).
    • Smells: Warns about logic that may pose maintainability problems or potential downsides.
    • Formatting: Checks that code is indented and structured according to specified cosmetic standards.

    The tool can analyze all F# implementation files within a .fsproj (F# project) or .sln / .slnx / .slnf (F# solution) file.

  3. Control order of operations in Hints

    master

    Use parentheses to explicitly define the order of application in a hint. This is particularly important when dealing with function application operators like |>, ||>, |||>, <|, <||, and <|||.

    Note that a rule written with parentheses like someFunc (not x) will match both the parenthesized version and the pipe version (not x |> someFunc) in the source code, because the hint matches the underlying AST structure.

    someFunc (not x) ===> someOtherFunc x
  4. How Hints work in FSharpLint

    master

    The Hints analyser allows users to write custom rules that match specific F# code patterns and suggest refactorings.

    A hint consists of two parts separated by the ===> operator:

    1. Match: An AST (Abstract Syntax Tree) pattern that is searched for in the linted code.
    2. Suggestion: An AST pattern that represents the recommended refactored code.

    When the Match pattern is found in the source code, FSharpLint provides the Suggestion as a refactoring tip to the user.

    match_expression ===> suggestion_expression
  5. Use variables and wildcards in Hints

    master

    To match arbitrary expressions within a hint, you can use variables or wildcards:

    • Wildcard (_): Matches any expression but cannot be referenced in the suggestion. Use this when you don't care about the content of the matched part.
    • Variable (single letter, e.g., x, a): Matches any expression and allows you to refer to that specific expression in the suggestion. This is useful for moving matched code to a different position.

    Example: To transform not (a >= b) into a < b:

    not (a >= b) ===> a < b
    not (a >= b) ===> a < b
  6. Understand the SynchronousFunctionNames (FL0095) rule

    master

    The FL0095 rule flags functions that are not asynchronous (i.e., they do not return an Async or Task type) but use Async as a prefix or suffix in their name.

    In F#, it is a convention that:

    • An Async prefix indicates the function returns an Async<'T> type.
    • An Async suffix indicates the function returns a System.Threading.Task<'T> type.

    Using these terms for synchronous functions can mislead developers about the function's execution model.

    // Incorrect: Prefix used for synchronous function
    let AsyncFoo() = 1
    
    // Correct: Prefix used for Async return type
    let AsyncFoo(): Async<int> =
        async { return 1 }
    
    // Incorrect: Suffix used for synchronous function
    let FooAsync() = 1
    
    // Correct: Suffix used for Task return type
    open System.Threading
    let FooAsync(): Task<int> =
        task { return 1 }
  7. Fix FL0094: FavourNamedMembers

    master

    The FL0094 rule flags discriminated union (DU) cases where the fields are unnamed. Using named fields is preferred because it conveys the purpose of each field and improves clarity, especially when multiple fields share the same type.

    Exception: DUs with a single case containing a single field do not require named members.

    // Avoid unnamed fields:
    type Data =
        | TwoParts of string * string
        | OnePart of string
    
    // Use named fields instead:
    type Data =
        | TwoParts of part1: string * part2: string
        | OnePart of part1: string
  8. Fix FL0070: FavourTypedIgnore

    master

    The FL0070 rule flags the use of the untyped ignore function. Using an untyped ignore can lead to mistakes and reduces maintainability because the compiler cannot verify the type of the value being discarded.

    To fix this, provide the type of the element being ignored as a type parameter to the ignore invocation.

    Example Fix:

    Instead of:

    ignore someValue

    Use:

    ignore<string> someValue
  9. Fix InvalidArgWithTwoArguments (FL0020)

    master

    The FL0020 rule detects instances where the invalidArg function is passed more than two arguments. This is typically a mistake as invalidArg is intended to take exactly two arguments (e.g., the argument name and the error message).

    Cause

    An example of the violation is: invalidArg "month" "Expected value between 1 and 12" "extra argument"

    How To Fix

    Remove the extra arguments so that only two remain.

    Rule Settings

    You can enable or disable this rule in your configuration file using the following JSON structure:

    {
        "invalidArgWithTwoArguments": {
            "enabled": true
        }
    }