Dependably.CsLint 5.0.0

dotnet tool install --global Dependably.CsLint --version 5.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Dependably.CsLint --version 5.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
#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:

  1. EditorConfig enforcement — enforces every key in your .editorconfig. If a key isn't set, the rule stays silent. No opinions of its own.
  2. Syntactic SAST (--sast) — always-on security and safety checks that need no build step.
  3. Semantic analysis (--deep) — loads your project through Roslyn's MSBuildWorkspace for higher-accuracy checks and dotnet_diagnostic.* severity overrides.
  4. 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, plus critical and moderate. So severity=error (a.k.a. high) gates on errors and severity=warning (a.k.a. low) gates on warnings too.
  • count=<N> — trips when the total number of findings exceeds N.
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 files note, 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-limit prints 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/::warning annotations 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 with common.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-file dotnet_diagnostic.<id>.severity in .editorconfig wins over this baseline where both are set.
  • exceptions — suppress specific findings without disabling a rule globally. Each entry needs rule, at least one selector (path, symbol, or id), and a reason. Suppressed findings are still counted in the report; set expires to flag stale suppressions.
  • exclude — path globs; union of common.exclude and cslint.exclude.
  • failOn — file-level CI gate (severity and/or count); a CLI --fail-on overrides 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
5.0.0 210 8/14/2026
4.1.2 146 7/3/2026
4.1.1 123 7/3/2026