FsHotWatch.Analyzers
0.7.0-alpha.22
dotnet add package FsHotWatch.Analyzers --version 0.7.0-alpha.22
NuGet\Install-Package FsHotWatch.Analyzers -Version 0.7.0-alpha.22
<PackageReference Include="FsHotWatch.Analyzers" Version="0.7.0-alpha.22" />
<PackageVersion Include="FsHotWatch.Analyzers" Version="0.7.0-alpha.22" />
<PackageReference Include="FsHotWatch.Analyzers" />
paket add FsHotWatch.Analyzers --version 0.7.0-alpha.22
#r "nuget: FsHotWatch.Analyzers, 0.7.0-alpha.22"
#:package FsHotWatch.Analyzers@0.7.0-alpha.22
#addin nuget:?package=FsHotWatch.Analyzers&version=0.7.0-alpha.22&prerelease
#tool nuget:?package=FsHotWatch.Analyzers&version=0.7.0-alpha.22&prerelease
FsHotWatch.Analyzers
Plugin that runs F# analyzers in-process using the warm FSharpChecker's
check results. Compatible with G-Research F# Analyzers SDK
and custom [<CliAnalyzer>] implementations.
Status: early alpha, and a lot of it is AI-written. APIs shift between versions and rough edges are expected — your mileage may vary. Issues and PRs are very welcome.
Why
F# analyzers normally need to start their own compiler to get type information. With FsHotWatch, the compiler is already warm -- analyzers get parse results and check results instantly, so they run in milliseconds instead of minutes.
How it works
- You save a file
- The daemon type-checks it with the warm FSharpChecker
- AnalyzersPlugin receives
FileCheckedwith the results - It constructs a
CliContextfrom the warm results (via reflection to handle FCS version mismatches) - It runs all loaded analyzers against that context
- Diagnostics are reported to the error ledger
Configuration
In .fshw.json:
{
"analyzers": {
"paths": ["analyzers/"]
}
}
| Field | Type | Default | Description |
|---|---|---|---|
paths |
string[] |
-- | Directories containing analyzer DLLs. Relative paths are resolved from the repo root. |
Writing a custom analyzer
See the ExampleAnalyzer for a complete working example. Here's the key pattern:
open FSharp.Analyzers.SDK
[<CliAnalyzer("MyAnalyzer", "Description of what it checks")>]
let myAnalyzer: Analyzer<CliContext> =
fun (context: CliContext) ->
async {
// context.ParseFileResults has the AST
// context.CheckFileResults has type info
// Walk the AST, find issues, return diagnostics
return
[ { Type = "My Rule"
Message = "Something is wrong here"
Code = "MY-001"
Severity = Severity.Warning
Range = someRange
Fixes = [] } ]
}
Build the analyzer as a class library and point analyzers.paths at
the output directory.
The DLL's filename must contain
AnalyzerThis is the one thing that will silently cost you an afternoon. The FSharp.Analyzers.SDK loader (
Client.LoadAnalyzers) only opens assemblies whose filename matches*Analyzer*.dll. A DLL withoutAnalyzerin its name is never scanned — it loads zero analyzers, and the old failure mode was for the check to sail on green having run none of your rules. An unloaded analyzer and a clean one look identical from the outside.If your project is named something else, set
<AssemblyName>so the output file matches, even though the project file does not:<AssemblyName>MyCompany.ConventionAnalyzers</AssemblyName>FsHotWatch fails loud rather than quiet here: an
analyzers.pathsentry that resolves to 0 analyzers aborts startup withconfig error: Analyzer path(s) loaded 0 analyzers. Silence is not treated as success.
House rules: repo-local analyzers
Analyzers do not have to be a published package. Point analyzers.paths at a
project inside your own repo and you have house rules — conventions the type
system cannot reach, enforced on every check and confirm.
FsHotWatch does exactly this to itself. analyzers/FsHotWatch.Rules
(assembly FsHotWatch.ConventionAnalyzers, per the filename rule above) is
IsPackable=false — it is never shipped, because these are FsHotWatch's own
conventions, not something to impose on consumers:
| Rule | What it enforces |
|---|---|
FSHW-CLAIM-001 |
A RunClaim (the result of PluginCtx.RunExclusive) must be handled, never discarded. TreatWarningsAsErrors + FS0020 already force the value to be acknowledged, but \|> ignore remains a legal escape — and silently dropping a refused claim is dropped work. |
FSHW-CLOCK-001 |
No local clocks. Every timestamp in the daemon is UTC; one stray DateTime.Now skewed the elapsed a human reads. |
The pattern generalises: when a rule is "the type system should make this unrepresentable, but that refactor is out of scope today", an analyzer is where the class of bug goes to be caught in the meantime.
CLI
# Query analyzer diagnostics
fshw diagnostics
Programmatic usage
daemon.RegisterHandler(
AnalyzersPlugin.create
[ "/path/to/analyzers" ] // directories with analyzer DLLs
None // timeoutSec (None → no timeout)
DiagnosticSeverity.Hint // failOnSeverity threshold
)
Install
dotnet add package FsHotWatch.Analyzers
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Analyzers.SDK (>= 0.37.2)
- FSharp.Compiler.Service (>= 43.12.204)
- FSharp.Core (>= 10.1.302)
- FsHotWatch (>= 0.10.0-alpha.3)
- Nerdbank.MessagePack (>= 1.2.4)
- System.Security.Cryptography.Xml (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.0-alpha.22 | 32 | 7/15/2026 |
| 0.7.0-alpha.21 | 54 | 7/2/2026 |
| 0.7.0-alpha.20 | 57 | 6/24/2026 |
| 0.7.0-alpha.19 | 63 | 6/17/2026 |
| 0.7.0-alpha.18 | 160 | 6/17/2026 |
| 0.7.0-alpha.17 | 69 | 6/17/2026 |
| 0.7.0-alpha.16 | 57 | 6/12/2026 |
| 0.7.0-alpha.15 | 55 | 6/9/2026 |
| 0.7.0-alpha.14 | 58 | 6/2/2026 |
| 0.7.0-alpha.13 | 58 | 5/28/2026 |
| 0.7.0-alpha.12 | 66 | 5/4/2026 |
| 0.7.0-alpha.11 | 60 | 4/29/2026 |
| 0.7.0-alpha.10 | 75 | 4/26/2026 |
| 0.7.0-alpha.9 | 67 | 4/25/2026 |
| 0.7.0-alpha.8 | 64 | 4/23/2026 |
| 0.7.0-alpha.7 | 64 | 4/22/2026 |
| 0.7.0-alpha.6 | 66 | 4/20/2026 |
| 0.7.0-alpha.3 | 61 | 4/18/2026 |
| 0.7.0-alpha.2 | 71 | 4/15/2026 |
| 0.6.0-alpha.1 | 62 | 4/12/2026 |