Dependably.CsLint
5.0.0
dotnet tool install --global Dependably.CsLint --version 5.0.0
dotnet new tool-manifest
dotnet tool install --local Dependably.CsLint --version 5.0.0
#tool dotnet:?package=Dependably.CsLint&version=5.0.0
nuke :add-package Dependably.CsLint --version 5.0.0
cslint
A fast, build-free C# linter with four tiers:
- EditorConfig enforcement — enforces every key in your
.editorconfig. If a key isn't set, the rule stays silent. No opinions of its own. - Syntactic SAST (
--sast) — always-on security and safety checks that need no build step. - Semantic analysis (
--deep) — loads your project through Roslyn'sMSBuildWorkspacefor higher-accuracy checks anddotnet_diagnostic.*severity overrides. - Compliance scan (
--scan) — repo-hygiene checks that no off-the-shelf tool ships: development provenance in comments, emoji in source, silently skipped tests, and XML-comment essays in project files. Ported from the Dependably codebase's compliance test suite, where they gate every merge.
Install
Requires the .NET SDK 8.0 or later.
dotnet tool install --global Dependably.CsLint
This puts the cslint command on your PATH.
Usage
cslint # enforce .editorconfig on staged .cs files
cslint --sast # + security/safety checks
cslint --scan --global # + compliance scan, over all files in the repo
cslint --deep --project src/App.csproj # + semantic analysis (run after dotnet build)
cslint --explain src/MyService.cs # show which rules apply to a file and why
Failing the build (--fail-on)
--fail-on <key>=<value> is the CI gate (repeatable). The process exits 1 if any rule trips:
severity=<error|warning|suggestion|info>— trips when a finding is at or above the level. This is the canonical severity vocabulary cslint prints on every finding and in the summary. The old shared-suite ladder words are still accepted as aliases:error=high,warning=low,suggestion=info, pluscriticalandmoderate. Soseverity=error(a.k.a.high) gates on errors andseverity=warning(a.k.a.low) gates on warnings too.count=<N>— trips when the total number of findings exceedsN.
cslint --sast # default: errors fail, warnings don't
cslint --sast --fail-on severity=warning # warnings fail too
cslint --global --fail-on count=0 # any finding fails
A bad value exits 2.
Output (--format)
human(default) — readable console report grouped by severity, errors first, so a handful of high-severity findings are never buried under a flood of warnings. Repeats of a noisy rule collapse into a+N more <RULE> in M filesnote, and the report ends with a per-rule frequency table. By default it prints up to 200 findings;--max-findings <N>changes the cap and--no-limitprints everything (no cap, no collapse).json— one JSON object on stdout (status to stderr); stable, machine-parseable. The JSON envelope keeps the shared-suite severity ladder (high/low/info) so it stays consistent across the Dependably suite.github— GitHub Actions::error/::warningannotations that appear as inline PR comments.
Producible severity range. cslint has three internal levels — error, warning, and
info/suggestion — and they map onto the five-word shared ladder as error→high,
warning→low, info/suggestion→info. It therefore never emits a critical or a
moderate finding, and summary.bySeverity.critical and summary.bySeverity.moderate are
always zero. Those zeroes are a histogram of what was emitted, not a claim that cslint looked
for critical findings and found none — read them as "not produced by this tool" rather than
"clean". critical and moderate stay accepted as --fail-on severity= aliases so a gate
configuration shared with the rest of the suite still parses.
Pre-commit hook
cslint --install-hook
Installs a hook that runs cslint --sast --fail-on severity=warning, and blocks commits that stage .editorconfig changes so rules can't be silently relaxed.
CI
Run one job without a build and, optionally, a second after dotnet build for semantic analysis:
on:
pull_request:
paths: ['**.cs', '.editorconfig']
jobs:
cslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '10.x' }
- run: dotnet tool install --global Dependably.CsLint
- run: cslint --sast --fail-on severity=warning --format github
Rules
EditorConfig (always on): EC001–EC006 (indent, whitespace, final newline, EOL, line length, charset), FMT (all csharp_space_*/indent_*/new_line_* keys via Roslyn's formatter), and CS010–CS040 (var, expression bodies, namespaces, pattern matching, qualification, naming, and other csharp_style_*/dotnet_style_* keys). Each rule is active only if the corresponding key is set in your .editorconfig.
SAST (--sast):
| Rule | Catches |
|---|---|
| SAST001 | Empty or discard-only catch blocks |
| SAST002 | Console.WriteLine / Debug.Write in non-test files |
| SAST003 | SQL injection via interpolated strings in query sinks |
| SAST004 | Hardcoded credentials and placeholder secrets |
| SAST005 | Fire-and-forget async (unawaited *Async() calls) |
| SAST006 | #pragma warning disable without a justification |
| SAST007 | Thread.Sleep() inside async methods |
| SAST008 | dynamic used in type positions |
Compliance scan (--scan):
| Rule | Catches | Opt-out |
|---|---|---|
| CC001 | Development provenance in comments: issue numbers (#123), milestone tags (M2.1), ephemeral PR/plan pointers — that history belongs in git and the tracker, where it stays true |
— |
| CC002 | Emoji codepoints anywhere in source (defaults off in test files, where emoji fixtures are legitimate) | — |
| CC003 | Skipped tests without a stated reason: [Fact(Skip = …)], [Theory(Skip = …)], [Ignore] — a stray skip passes CI green while silently dropping coverage |
// skip-ok: <reason> on the skip line or the line above |
| CC004 | XML comments in *.csproj / *.props — rationale essays that drift from the truth; under --scan, project files are discovered and linted alongside .cs sources |
`` |
These were born as hand-rolled compliance tests in the Dependably codebase (and are classic AI-slop tells); cslint ships them as reusable rules. The former opinionated rules OP004–OP006 (magic numbers, boolean flags, missing CancellationToken) were removed in v5.0.0 — they are commodity checks better served by Roslyn analyzers or Sonar, which cover them with full semantic context.
Configuration
.dependably (shared suite config)
cslint participates in the Dependably suite's shared config file, .dependably, committed at the repo root. It reads the common section and its own cslint section. Supported keys:
{
"version": 1,
"common": {
"exclude": ["tests/fixtures/**"]
},
"cslint": {
"rules": {
"CC002": "off",
"SAST002": "warn"
},
"exceptions": [
{
"rule": "CC001",
"path": "src/Generated/**",
"reason": "generated code carries emitter provenance",
"expires": "2027-01-01"
}
],
"exclude": ["**/Generated/**"],
"failOn": { "severity": "warning" }
}
}
rules— per-rule severity ("error","warn","info","off"), merged withcommon.rules(tool section wins per rule-id). Applies to every rule id — EC/CS/SAST/CC alike:"off"disables the rule repo-wide, the other tokens set its baseline severity. A per-filedotnet_diagnostic.<id>.severityin.editorconfigwins over this baseline where both are set.exceptions— suppress specific findings without disabling a rule globally. Each entry needsrule, at least one selector (path,symbol, orid), and areason. Suppressed findings are still counted in the report; setexpiresto flag stale suppressions.exclude— path globs; union ofcommon.excludeandcslint.exclude.failOn— file-level CI gate (severityand/orcount); a CLI--fail-onoverrides it.
The deprecated .dependably-check filename is still read (with a stderr warning). The legacy strict key still works but emits a deprecation warning; prefer failOn.severity. The legacy scan key is tolerated but inert — it addressed the removed OP004–OP006 rules — and existing exceptions entries for those retired rule ids still parse, so an upgraded repo's config never turns fatal.
.editorconfig per-file severity
Any rule's severity is set per file or glob from .editorconfig — the same mechanism cslint enforces:
[*.cs]
dotnet_diagnostic.SAST002.severity = none # silence console output in a CLI app
dotnet_diagnostic.CC003.severity = error # promote skipped tests to errors
[*.csproj]
dotnet_diagnostic.CC004.severity = none # project-file rules need a section matching THEIR files
Levels are none/silent (drop the finding), suggestion, warning, and error, and apply to every rule. Note the section glob must match the file the rule runs on: CC004 findings live on *.csproj/*.props, so a [*.cs] section never affects them.
Exclude paths with --exclude <glob> (repeatable). A pattern with no wildcard is a substring match; otherwise **/*/? glob against the path.
--global file discovery
Under --global, cslint discovers .cs files — plus *.csproj/*.props when --scan is active (they carry exactly one rule, CC004, and are skipped in every other mode). It walks the tree without following directory symlinks/junctions (so node_modules symlink cycles can't loop), and prunes a built-in set of directories — node_modules, bin, obj, .git, .claude, packages — so vendored dependencies, build output, and throwaway worktrees don't drown first-party code. On top of that, when the root is a git repository it honors .gitignore (including nested ignore files and negation) by delegating to git check-ignore, so generated code and vendored paths your project already ignores are skipped too. Outside a git repo the built-in excludes are the sole guard. It prints how many files it skipped. Pass --no-default-excludes to walk the built-in-excluded and .gitignore'd directories too.
Test files (name ending Test/Tests/Spec, or under a Tests/Specs directory) default CC002 (emoji) to off — emoji fixtures are idiomatic when the code under test must handle them (password policies, encoders, truncation). Re-enable per glob with an explicit dotnet_diagnostic.CC002.severity in .editorconfig. CC003 (skipped tests) runs everywhere — test files are exactly its subject.
License
Apache-2.0. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
This package has no dependencies.