SquiggleCop.Tool
1.0.1
See the version list below for details.
dotnet tool install --global SquiggleCop.Tool --version 1.0.1
dotnet new tool-manifest # if you are setting up this repo dotnet tool install --local SquiggleCop.Tool --version 1.0.1
#tool dotnet:?package=SquiggleCop.Tool&version=1.0.1
nuke :add-package SquiggleCop.Tool --version 1.0.1
<div align="center"> <img alt="SquiggleCop logo" height="200px" src="https://raw.githubusercontent.com/MattKotsenas/SquiggleCop/main/icon.png"> </div>
SquiggleCop
Prevent unintended configuration changes to .NET (Roslyn) analyzers.
There are many ways to configure diagnostic warning and error levels in a .NET build, and understanding how they all interact can be tricky to get correct. .NET / MSBuild support all these mechanisms (and probably more!) to configure what analyzers are enabled and at what severity level:
- In-box analyzers (docs)
- Analyzer NuGet packages (example)
.editorconfig
(docs).globalconfig
(docs).ruleset
(docs)WarningLevel
(docs)AnalysisLevel
(docs)TreatWarningsAsErrors
(docs)WarningsAsErrors
(docs)WarningsNotAsErrors
(docs)NoWarn
(docs)
That's a lot to keep track of!
With SquiggleCop, any change to project files or build scripts produces an easy to understand (and diff!) baseline file that shows the consequences of the change:
<table> <tr> <th>Code Change</th> <th>Baseline File Diff</th> </tr> <tr> <td>
--- a/sample.csproj
+++ b/sample.csproj
@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
- <AnalysisLevel>5</AnalysisLevel>
+ <AnalysisLevel>latest</AnalysisLevel>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</td> <td>
--- a/SquiggleCop.Baseline.yaml
+++ b/SquiggleCop.Baseline.yaml
@@ -476,39 +476,39 @@
- Id: CA1419
Title: Provide a parameterless constructor that is as visible as the containing type for concrete types derived from 'System.Runtime.InteropServices.SafeHandle'
Category: Interoperability
DefaultSeverity: Note
IsEnabledByDefault: true
EffectiveSeverities:
- - None
- IsEverSuppressed: true
+ - Note
+ IsEverSuppressed: false
- Id: CA1420
Title: Property, type, or attribute requires runtime marshalling
Category: Interoperability
DefaultSeverity: Warning
IsEnabledByDefault: true
EffectiveSeverities:
- - None
- IsEverSuppressed: true
+ - Warning
+ IsEverSuppressed: false
</td> </tr> </table>
SquiggleCop parses compiler output to create a baseline file of all .NET (Roslyn) analyzer rules and their configured severity levels. This baseline file should be checked into source control. On subsequent runs the new baseline is compared to the existing file. If baselines don't match, either an unexpected configuration change was made and should be fixed, or the baseline should be updated to document the new, expected configuration.
SquiggleCop is available in both MSBuild task and CLI tool form to make it easy to integrate into your existing development processes.
Getting Started
Enabling SARIF logs
SquiggleCop uses SARIF v2.1 files to work its magic. If you aren't already
producing SARIF files as part of your build, set the
ErrorLog
property, either in a
Directory.Build.props
so it automatically applies to all projects:
<ErrorLog>$(MSBuildProjectFile).diagnostics.sarif,version=2.1</ErrorLog>
[!TIP] We recommend you add
*.sarif
to your.gitignore
file
or on the command-line for ad-hoc validation:
dotnet build -p:ErrorLog=diagnostics.sarif%2cversion=2.1
[!IMPORTANT] The comma or semi-colon character in the log path must be XML-escaped
CLI Tool
The CLI tool is designed for ad-hoc validation and interacting with baseline files. Install the CLI tool by running this command:
dotnet tool install SquiggleCop.Tool
then generate a baseline like this:
dotnet squigglecop generate ./path/to/diagnostics.sarif --auto-baseline
MSBuild Tasks
The Tasks package automatically integrates SquiggleCop into the build using MSBuild. This package is designed for generating baselines as part of a large build and to continuously validate baselines and prevent unintentional build changes.
Add the Tasks package like this:
dotnet add package SquiggleCop.Tasks
If you use Central Package Management (CPM), you can use a
GlobalPackageReference
to add SquiggleCop to every project automatically.
<Project>
<ItemGroup>
<GlobalPackageReference Include="SquiggleCop.Tasks" Version="{{version}}" />
</ItemGroup>
</Project>
If a new baseline doesn't match the existing file, SquiggleCop emits MSBuild warning SQ2000: Baseline mismatch
.
Either use the SquiggleCop CLI to create a new baseline, or enable automatic baselining by setting:
<Project>
<PropertyGroup>
<SquiggleCop_AutoBaseline>true</SquiggleCop_AutoBaseline>
</PropertyGroup>
</Project>
If autobaseline is on, be sure to review any changes to the baseline file before committing your code.
[!CAUTION] If you turn autobaseline on, be sure to turn it off in CI. Otherwise SquiggleCop may not be able to warn about potential issues!
Anatomy of a baseline file
A baseline file is a YAML file with a repeating structure. Here's a single rule entry:
- Id: CA1000
Title: Do not declare static members on generic types
Category: Design
DefaultSeverity: Note
IsEnabledByDefault: true
EffectiveSeverities:
- Note
- None
IsEverSuppressed: true
ID
This is the ID of the diagnostic.
Title
The title of the diagnostic. Note that some diagnostics have multiple titles for a single ID. For instance, depending
on how it's configured, the title of IDE0053
can be either "Use block body for lambda expression" or "Use expression
body for lambda expression".
Category
The category of the diagnostic. See Analyzer Configuration for more information.
DefaultSeverity
The diagnostic's default severity.
IsEnabledByDefault
If the diagnostic is enabled by default.
EffectiveSeverities
The severity or severities of a diagnostic once all options have been considered (i.e. rulesets, .editorconfig,
.globalconfig, etc.). One common way to end up with multiple effective severities is to use different .editorconfig files
for different parts of the codebase. Note that inline suppressions will not show up here, instead they show up in
IsEverSuppressed
.
IsEverSuppressed
true
if the diagnostic is ever suppressed at a call site. Common ways to do this are:
#pragma
[SuppressMessage]
<AnalysisLevel>
Advanced configuration
Alternate baseline paths
By default, SquiggleCop expects the baseline file to be named SquiggleCop.Baseline.yaml
and placed next to the project
file. To specify a custom path to the baseline file, add an item to AdditionalFiles
that points to the baseline file:
<Project>
<ItemGroup>
<AdditionalFiles Include="/path/to/SquiggleCop.Baseline.yaml" />
</ItemGroup>
</Project>
TreatWarningsAsErrors
Often, projects use TreatWarningsAsErrors
in CI builds to prevent warnings from making into the main branch. Some
projects go further and also enable it locally so that the CI and local development experience match.
However, toggling TreatWarningsAsErrors also changes the effective severity of analyzer diagnostics, which can lead to
unnecessary churn in baseline files. If your project or development workflow toggles TreatWarningsAsErrors between CI
and local development, also toggle the SquiggleCop_Enabled
property based on the same logic.
Here's an example project that toggles TreatWarningsAsErrors based on the
ContinuousIntegrationBuild
property:
<Project>
<PropertyGroup>
<PedanticMode Condition=" '$(_PedanticMode)' == '' ">$([MSBuild]::ValueOrDefault('$(ContinuousIntegrationBuild)', 'false'))</PedanticMode>
<TreatWarningsAsErrors>$(PedanticMode)</TreatWarningsAsErrors>
<SquiggleCop_Enabled>$(PedanticMode)</SquiggleCop_Enabled>
</PropertyGroup>
</Project>
Icon 'fractal' by Bohdan Burmich from Noun Project (CC BY 3.0)
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
This package has no dependencies.