fsprojects/FSharpLint
43.2
Weak · 24 September 2026
11.5k
lines of production code
F#
with JavaScript
5
measurements over time
What this system is
FSharpLint is a static analysis tool for the F\# programming language that enforces coding standards, naming conventions, and formatting rules. It operates by parsing source code into an abstract syntax tree to detect issues ranging from cyclomatic complexity and code smells to specific stylistic violations. The system provides structured error reporting, including MSBuild-compatible output for CI integration, and supports automated fixes for many detected issues.
How it got here
2014–2016 — Core engine redesign and modernization
13 changes.
The project underwent a comprehensive architectural overhaul, replacing the previous hint-matching mechanism with a new AST-based framework and expanding the rule set to include naming conventions and structural checks. This period also saw the migration to .NET 9, the introduction of CI-friendly MSBuild output, and the establishment of robust functional and unit test coverage for the core linting engine.
2019 — Expansion of linting rules and test coverage
19 changes.
This period focused on significantly expanding the F\# linter's rule set, introducing new conventions for code complexity, naming, source length, and formatting. It also involved refactoring the naming rules to a new AST-based structure and optimizing the hint matching engine for better performance. Comprehensive test coverage was added to validate the new rules and ensure correct behavior across various linting categories.
2020–2025 — linting rules and test expansion
5 changes.
This period focused on expanding the FSharpLint rule set with new checks for async patterns, string formatting, and typography, alongside comprehensive unit tests for these features. The work also included establishing a benchmark suite to profile the linter's performance and adding test coverage for console file type inference.
Features
Expanded linter rule set and naming conventions support
The linter core now includes a significantly larger set of rules (92 identifiers defined in Identifiers.fs), adding new checks for naming conventions (such as SynchronousFunctionNames, AsynchronousFunctionNames, GenericTypesNames, AvoidTooShortNames), code style (IndexerAccessorStyleConsistency, FavourTypedIgnore, FavourReRaise, FavourSingleton), and structural issues (CyclomaticComplexity, NoAsyncRunSynchronouslyInLibrary, DisallowShadowing). The NamingHelper module provides robust support for enforcing PascalCase, CamelCase, and case constraints with configurable underscore handling, while the XmlDocumentation module adds rules to enforce XML documentation headers on modules, types, members, and other definitions based on access level.
src/FSharpLint.Core/Rules · high confidence
FSharpLint.Core initialization with default rules and configuration
The FSharpLint.Core module is introduced, establishing the foundational linting engine. This includes a default configuration file (fsharplint.json) that enables specific rules such as interface naming, exception naming, type naming, and checks for \failwith\ usage, while keeping formatting and indentation rules disabled by default. The core also provides localized error messages via Text.resx and exposes internal helpers to the FSharpLint.Benchmarks assembly for performance profiling.
src/FSharpLint.Core · high confidence
Introduce optimized hint matching engine
Added a new hint matching system in the Core rules that uses a two-pass approach (fuzzy hash-based matching followed by AST verification) to significantly improve linting performance. The implementation introduces \HintMatcher.fs\ for detailed expression and variable binding logic, and \HintsHelper.fs\ for trie-based traversal against an abstract syntax array, replacing previous linear scanning methods.
src/FSharpLint.Core/Rules/Hints · high confidence
Introduces MSBuild-compatible output format for CI integration
The console linter now supports an MSBuild output format (selectable via the -f flag) in addition to the existing standard colored console output. This new format emits warnings in the standard MSBuild warning syntax (e.g., file(line,col): warning rule: message), allowing build systems and IDEs to automatically parse and display linting issues in their native error lists. The implementation adds a new Output module defining the MSBuild formatter and updates Program.fs to handle the format selection and route output accordingly.
src/FSharpLint.Console · high confidence
New binding convention rules for F\# linting
The linter now includes several new rules in the Binding conventions folder to enforce idiomatic F\# patterns. These rules detect and suggest fixes for: using \let \_ =\ instead of \ignore\ (FavourIgnoreOverLetWild), preferring typed \ignore\ over untyped usage (FavourTypedIgnore), replacing \x = constant\ patterns with \constant as x\ (FavourAsKeyword), simplifying tuples of wildcards (TupleOfWildcards), removing unnecessary \as\ bindings for wildcards (WildcardNamedWithAsPattern), and identifying bindings that are immediately shadowed or unused (UselessBinding).
src/FSharpLint.Core/Rules/Conventions/Binding · high confidence
New formatting rules for typed item spacing and union definition indentation
Added two new linting rules to the Formatting category. The TypedItemSpacing rule enforces configurable spacing around colons in typed items (patterns and fields), supporting styles for no spaces, space after, or spaces around the colon, and provides suggested fixes. The UnionDefinitionIndentation rule ensures that union case members are indented consistently relative to the type definition and checks that all cases share the same indentation level.
src/FSharpLint.Core/Rules/Formatting · high confidence
New lint rules for function reimplementation patterns
Added two new convention rules to the linter: 'CanBeReplacedWithComposition' detects lambdas that can be simplified using function composition (the '\>\>' operator) and suggests a fix, while 'ReimplementsFunction' identifies pointless lambdas that merely wrap an existing function call and suggests replacing the lambda with the function reference directly.
src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation · high confidence
New linting rules for async patterns, string formatting, and argument validation
This update introduces several new code-smell detection rules in the F\# linter. It adds checks for async code patterns, including a new rule that flags synchronous execution of async workflows in library projects (NoAsyncRunSynchronouslyInLibrary) and warns when exceptions are raised without being returned (AsyncExceptionWithoutReturn). It also introduces rules to detect interpolated strings and printf-style functions (sprintf, failwithf) that lack format specifiers (InterpolatedStringWithNoSubstitution), as well as a suite of rules under RaiseWithTooManyArguments that flag incorrect argument counts for standard exception-throwing functions like raise, invalidArg, nullArg, and failwith. Finally, a new rule warns against using underscore-prefixed identifiers that are actually used in the code (UsedUnderscorePrefixedElements).
src/FSharpLint.Core/Rules/Smells · high confidence
New linting rules for code conventions and complexity
The linter introduces several new convention rules: AvoidSinglePipeOperator flags single-line pipe operator usage and suggests inlining; CyclomaticComplexity (FL0069) allows configuring a maximum complexity threshold for functions; DisallowShadowing prevents variable shadowing (ignoring names starting with underscore); DiscourageStringInterpolationWithStringFormat warns against using String.Format with interpolated-style placeholders; EnsureTailCallDiagnosticsInRecursiveFunctions suggests adding TailCall attributes to recursive functions; FailwithBadUsage detects empty, duplicate, or null error messages in failwith/failwithf calls and suggests using raise with inner exceptions; FavourConsistentThis enforces a consistent 'this' identifier in methods; FavourNamedMembers discourages unnamed fields in union types; FavourNestedFunctions suggests moving private functions used in only one other function into that function; FavourNonMutablePropertyInitialization warns against mutating properties in initializers; FavourReRaise suggests using reraise instead of raise with the current exception; FavourSingleton flags computed arrays/lists that could be singletons; and FavourStaticEmptyFields suggests using String.Empty, List.Empty, or Array.empty instead of empty literals.
src/FSharpLint.Core/Rules/Conventions · high confidence
New linting rules for limiting counts of boolean operators, function parameters, tuple items, and type members
The linter now includes four new convention rules that flag code when specific counts exceed configured thresholds: \MaxNumberOfBooleanOperatorsInCondition\ checks boolean operators in conditions, \MaxNumberOfFunctionParameters\ limits constructor parameters, \MaxNumberOfItemsInTuple\ restricts tuple size (excluding those in application nodes), and \MaxNumberOfMembers\ caps public members in type definitions. These rules use a shared \Helper.NumberOfItems.Config\ to allow users to configure the maximum allowed items for each category.
src/FSharpLint.Core/Rules/Conventions/NumberOfItems · high confidence
New pattern match formatting rules for indentation and line breaks
The linter now includes five new rules in the PatternMatchFormatting module to enforce consistent formatting in match expressions: PatternMatchClauseIndentation ensures clauses are indented correctly (with support for single-line lambdas), PatternMatchClausesOnNewLine requires each clause to start on a new line, PatternMatchExpressionIndentation checks that expression bodies are indented relative to their clauses, PatternMatchOrClausesOnNewLine enforces new lines for alternatives in or-patterns, and a helper module filters out false positives in function parameters. These changes improve code readability by standardizing how pattern matches are structured.
src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting · high confidence
New source length rules for classes, constructors, enums, files, functions, and more
This change introduces a suite of new linting rules in the SourceLength category that enforce maximum line counts on specific F\# code constructs. The new rules include MaxLinesInClass, MaxLinesInConstructor, MaxLinesInEnum, MaxLinesInFile, MaxLinesInFunction, MaxLinesInLambdaFunction, MaxLinesInMatchLambdaFunction, MaxLinesInMember, MaxLinesInModule, MaxLinesInProperty, MaxLinesInRecord, MaxLinesInUnion, and MaxLinesInValue. A shared helper (SourceLengthHelper) calculates effective line counts by stripping multiline comments, ignoring blank lines, and optionally excluding specified ranges (e.g., nested functions in MaxLinesInFunction). Users can configure the maximum allowed lines per construct via the rule configs, and violations will be reported with clear messages indicating the construct type and the exceeded limit.
src/FSharpLint.Core/Rules/Conventions/SourceLength · high confidence
New spacing rules for class members and module declarations
Added two new linting rules, ClassMemberSpacing and ModuleDeclSpacing, to enforce consistent blank-line spacing between members within classes and declarations within modules or namespaces. The ClassMemberSpacing rule checks that class members are separated by the expected number of lines (accounting for preceding comments), while the ModuleDeclSpacing rule applies similar logic to module and namespace declarations, ensuring a uniform visual structure in F\# source files.
src/FSharpLint.Core/Rules/Formatting/Spacing · high confidence
New tuple formatting lint rules for spacing, indentation, and parentheses
The linter now includes three new rules in the TupleFormatting module to enforce consistent tuple style: TupleCommaSpacing ensures a single space follows commas in tuples, TupleIndentation requires consistent indentation for tuple items on separate lines, and TupleParentheses flags tuples missing surrounding parentheses. These rules provide suggested fixes where applicable and correctly ignore cons operators (op\_ColonColon) which are parsed as tuples by the compiler.
src/FSharpLint.Core/Rules/Formatting/TupleFormatting · high confidence
New typography linting rules for indentation, line length, and whitespace
Added five new formatting rules to the Typography category: Indentation (validates indentation levels for records, tuples, arrays, and infix operators), MaxCharactersOnLine (enforces a configurable line length limit), NoTabCharacters (flags tabs and suggests replacing them with spaces, excluding string literals), TrailingNewLineInFile (flags files ending with a newline), and TrailingWhitespaceOnLine (flags trailing whitespace with configurable allowances for operators and blank lines).
src/FSharpLint.Core/Rules/Formatting/Typography · high confidence
Repository initialization and project structure setup
The repository has been initialized with a new project structure, including a FAKE-based build script (build.fsx) and a Makefile for standard build, test, and documentation targets. The solution now uses the .slnx format (FSharpLint.slnx) and is configured to target .NET 9.0 via global.json. The project license has been changed from GPLv3 to MIT, and the README has been updated to reflect the new tool name (FSharpLint), usage instructions, and maintainer information. Additional configuration files include .gitattributes, .gitignore, Directory.Build.props, and .mcp.json for GitHub Copilot integration.
(repo-wide) · high confidence
Behavioural changes
Redesigned linter API with async support and structured error handling
The core application layer has been refactored to provide a more robust and modern API for integrating FSharpLint. The linter now exposes dedicated asynchronous methods (e.g., \lintProjectAsync\, \lintSourceAsync\) alongside synchronous ones, allowing callers to handle linting operations without blocking threads. Configuration loading has been standardized through a \ConfigurationParam\ union type, enabling users to pass configuration objects, file paths, or explicitly request defaults. Error handling is now structured via a \LintFailure\ discriminated union that clearly distinguishes between missing files, build failures, and parsing errors, replacing previous exception-based or opaque failure modes. Additionally, the API supports passing pre-parsed source information (\ParsedFileInformation\) to avoid redundant parsing when the host application has already processed the AST.
src/FSharpLint.Core/Application · high confidence
Redesigned linting framework with new AST and hint-matching infrastructure
The core linting engine has been completely redesigned to improve performance and maintainability. This change introduces a new Abstract Syntax Tree (AST) abstraction in \Ast.fs\ that normalizes F\# compiler nodes, including specific handling for pipe operators and lambda structures. It replaces the previous hint-matching mechanism with a new \HintParser\ and \MergeSyntaxTrees\ module that uses a typed, hash-based trie structure for faster and more accurate rule matching against the AST. Additionally, the framework now supports structured suppression comments (e.g., \fsharplint: disable\), provides detailed \SuggestedFix\ information for automated corrections, and updates the parsing logic to work with modern \FSharp.Compiler.Service\ APIs.
src/FSharpLint.Core/Framework · high confidence
Refactored naming convention rules to use a new AST-based rule structure
The naming convention rules in the F\# linter have been refactored to use a new AST-based rule structure. This change introduces a consistent pattern for defining rules, where each rule now implements a \rule\ function that takes a configuration and returns an \AstNodeRule\. The refactoring includes the addition of new rules such as \ActivePatternNames\, \AsynchronousFunctionNames\, \AvoidTooShortNames\, \EnumCasesNames\, \ExceptionNames\, \GenericTypesNames\, \InterfaceNames\, \InternalValuesNames\, \LiteralNames\, \MeasureTypeNames\, \MemberNames\, \ModuleNames\, \NamespaceNames\, \NestedFunctionNames\, \ParameterNames\, \PrivateValuesNames\, \PublicValuesNames\, \RecordFieldNames\, \SynchronousFunctionNames\, \TypeNames\, \UnionCasesNames\, and \UnnestedFunctionNames\. These rules cover a wide range of naming conventions, including active patterns, asynchronous functions, short names, enum cases, exceptions, generic types, interfaces, internal values, literals, measure types, members, modules, namespaces, nested functions, parameters, private values, public values, record fields, synchronous functions, types, union cases, and unnested functions. The refactoring also includes the removal of the \syntax array\ and the implementation of modes for asynchronous and synchronous function names.
src/FSharpLint.Core/Rules/Conventions/Naming · high confidence
Test coverage
Added benchmark suite for profiling FSharpLint performance; Added functional and acceptance tests for the F\# linter; Added test coverage for Typography linting rules; Added test documentation and source files; Added test fixture project for functional linting tests; Added test infrastructure and coverage for formatting rules; Added test infrastructure for linting rules; Added tests for HintMatcher rule logic; Added tests for InterpolatedStringWithNoSubstitution and NoAsyncRunSynchronouslyInLibrary rules; Added tests for NumberOfItems lint rules; Added tests for TypePrefixing, TypedItemSpacing, and UnionDefinitionIndentation rules; Added tests for binding rules: FavourAsKeyword, FavourIgnoreOverLetWild, FavourTypedIgnore, TupleOfWildcards, UselessBinding, WildcardNamedWithAsPattern; Added tests for console file type inference and wildcard expansion; Added tests for pattern match formatting rules; Added unit tests for FSharpLint core framework components; Added unit tests for multiple linter convention rules; Expanded test coverage for F\# naming convention rules.
Dependencies
Migrate to .NET 9 and centralize package version management
The project has been updated to target .NET 9.0 (alongside .NET 8.0 for the core library and console tool), replacing previous .NET Core 3.1 and .NET 5/6 targets. To support this, the build system now uses Central Package Versions (Directory.Packages.props) to manage dependencies, including upgrades to FSharp.Core (10.1.201), FSharp.Compiler.Service (43.12.201), and Microsoft.Build (17.14.x). The console tool is now packaged as a .NET CLI tool (dotnet-fsharplint) with RollForward set to LatestMajor to ensure compatibility across .NET 8 and 9 runtimes.
(dependencies) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Score
- CAI 50 → 43 (-7.2)
- Rubric changed (rubric-2026.08.19 → rubric-2026.09.15) — scores are not directly comparable.
Lenses
- Code Health 87 → 87 (-0.5)
- Architecture 100 → 96 (-3.8)
- Maturity 53 → 53 (+0.0)
- Readiness 49 → 61 (+12.2)
- Security 40 → 57 (+17.3)
- Accessibility 24 (new)
Resolved (48)
- Code Coverage not included (time budget)
- Dependency hygiene not measured — no packages were read
- Duplicated block (10 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/UnnestedFunctionNames.fs)
- Duplicated block (10 lines × 2) (src/FSharpLint.Core/Rules/Formatting/Spacing/ModuleDeclSpacing.fs)
- Duplicated block (11 lines × 2) (src/FSharpLint.Core/Application/Lint.fs)
- Duplicated block (11 lines × 3) (src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs)
- Duplicated block (16 lines × 2) (src/FSharpLint.Core/Framework/HintParserUtilities.fs)
- Duplicated block (5 lines × 2) (src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs)
- Duplicated block (5 lines × 2) (src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs)
- Duplicated block (6 lines × 2) (src/FSharpLint.Core/Framework/HintParserUtilities.fs)
- Duplicated block (6 lines × 2) (src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/MeasureTypeNames.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs)
- Duplicated block (8 lines × 4) (src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInUnion.fs)
- Duplicated block (8 lines × 4) (src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInValue.fs)
- Duplicated block (9 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/GenericTypesNames.fs)
- Duplicated block (9 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/NamespaceNames.fs)
- Duplicated block (9 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/PrivateValuesNames.fs)
- Duplicated block (9 lines × 2) (src/FSharpLint.Core/Rules/Hints/HintMatcher.fs)
- …and 28 more
New (57)
- Coverage not measured — no coverage collector is wired up
- Documentation: no installation or build instructions (README.md)
- Duplicated block (10 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs)
- Duplicated block (11 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/NestedFunctionNames.fs)
- Duplicated block (13 lines × 2) (src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfItemsInTuple.fs)
- Duplicated block (13 lines × 3) (src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs)
- Duplicated block (16 lines × 3) (src/FSharpLint.Core/Rules/Conventions/Naming/InterfaceNames.fs)
- Duplicated block (17 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs)
- Duplicated block (19 lines × 2) (src/FSharpLint.Core/Framework/AbstractSyntaxArray.fs)
- Duplicated block (19–20 lines × 2) (src/FSharpLint.Core/Application/Lint.fs)
- Duplicated block (5 lines × 2) (src/FSharpLint.Core/Framework/HintParserUtilities.fs)
- Duplicated block (5 lines × 2) (src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs)
- Duplicated block (5 lines × 2) (src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs)
- Duplicated block (7 lines × 4) (src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInConstructor.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/FailwithBadUsage.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/AvoidTooShortNames.fs)
- Duplicated block (8 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs)
- Duplicated block (8 lines × 4) (src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInClass.fs)
- Duplicated block (9 lines × 2) (src/FSharpLint.Core/Rules/Conventions/Naming/ModuleNames.fs)
- …and 37 more
Changes since last survey
- 12 commits — 5 feature/other, 7 fixes
By area
- src/FSharpLint.Core — 8 commits
- tests/FSharpLint.Core.Tests — 3 commits
- (repo) — 1 commit
Notable commits
- fix: Core,Tests(Console): fixed selfCheck violations
- fix: Core,Tests: fixed selfCheck violations
- fix: FavourSingleton: fixed rule
- fix: FavourSingleton: fixed rule
- fix: Merge PR #883 from webwarrior-ws/fix-favour-singleton
- fix: Tests(Core): added regression test for FavourSingleton
- fix: Tests(Core): fixed indices in FavourSingleton tests
- change: Tests(Core): more tests for FavourSingleton
- change: perf(Core): Avoid boxing tuple comparisons
- change: perf(Core): tweak tryFindTextOfRange
- change: perf(Core/SourceLength): Use Regex.Count
- change: perf(SourceLength): String.Concat -> StringBuilder
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
fsprojects/FSharpLint was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 24 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit d2607f5c68fe0e8c0366e815692cb8d314e254a3 — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-923689c465cf.