﻿---
title: Cleanup
description: curb cleanup fixes the semantic code style rules a build already reported, without ever loading a compilation.
url: https://docs-v3-preview.elastic.dev/workflow/cleanup
---

# Cleanup
Curb formats from syntax alone — no workspace, no compilation. That is what lets it run inside
`dotnet build` before the compiler runs. Semantic code style is out of that scope: whether a using
directive is unused, or a field is only ever assigned in a constructor, is a question only a compilation
can answer.
`curb cleanup` closes part of that gap. The compiler decides whether a rule applies and writes the
answer to a SARIF log. `curb cleanup` reads that log and applies the rewrite. No `CSharpCompilation`,
no `SemanticModel`, no Workspaces.
```sh
dotnet build         
curb cleanup         
dotnet build         
```


## Running it

`curb` sets `$(ErrorLog)` to `$(IntermediateOutputPath)curb.sarif` when the project has
not set one, so the log exists without anyone asking for it. `curb cleanup` with no arguments searches
the current directory for those logs.

| Flag                       | What it does                                                        |
|----------------------------|---------------------------------------------------------------------|
| `-s`, `--sarif-log <path>` | Name a specific log file, instead of searching for one. Repeatable. |
| `-f`, `--files <path>`     | Restrict cleanup to these files. Repeatable.                        |
| `--check`                  | Report what would change without writing.                           |
| `--forward`                | Hand the remainder to `dotnet format style` after cleanup finishes. |
| `curb rules`               | List every rule and which tool fixes it.                            |

The third build is the verify step you were going to run after any change anyway. A bad fix is a
compile error you see immediately.

## Opting out


| Mechanism                                                      | Effect                                                  |
|----------------------------------------------------------------|---------------------------------------------------------|
| `dotnet_diagnostic.IDEnnnn.severity = none`, or not setting it | The build never reports it, so cleanup never sees it.   |
| `#pragma warning disable IDEnnnn`                              | The compiler suppresses it before it reaches the log.   |
| `generated_code = true`, `<auto-generated>`                    | Reused from the formatter via `FormatOptions.Excluded`. |
| `Curb_Diagnostics=false`                                       | The MSBuild package stops setting `$(ErrorLog)`.        |
| `Curb_Bypass=true`                                             | Everything, formatter included.                         |
| A `$(ErrorLog)` the project already set                        | Curb stands down and never overwrites it.               |


## Safety

Three layers keep applying a verdict from a previous build safe:
1. The node-kind gate. Every fixer declares the syntax it applies to, and the reported position must
   resolve to it. A diagnostic pointing at a using directive that is no longer there simply does not
   apply.
2. The freshness gate. A file whose last-write time is newer than the log is skipped. A span is an
   offset into the bytes the compiler read; applying it to different bytes is how a tool corrupts source.
3. The declared-delta verifiers, unchanged from the formatter. A fix says which tokens it removes;
   `ContentVerifier` and `TokenStreamComparer` hold the rest of the file to a strict compare.

Overlapping fixes drop both, never one. Dropping both means a second pass sees the same overlap and
drops it again, so the output is a fixed point.

## What is fixed

`curb rules` is the live answer. Ten rules today: IDE0005, IDE0007, IDE0034, IDE0040, IDE0044, IDE0071,
IDE0090, IDE0240, IDE0250, IDE0251.

### IDE0005 — unnecessary using directives

Roslyn emits one IDE0005 per maximal contiguous run of unnecessary directives. The span is a delete
instruction. The rule refuses any file containing `#if`: the compiler decided for one symbol set, and
a directive needed only under another would be reported as unnecessary and then lost.

### Not fixed, and why


| Rule                             | Why                                                                                                                                             |
|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| IDE0008 use explicit type        | The diagnostic does not carry the type name, so the fix is not derivable from the span.                                                         |
| IDE0051 / IDE0052 unused members | The fix deletes a declaration. Curb never destroys code.                                                                                        |
| IDE1006 / IDE0130 naming         | A rename touches every reference site, can compile while changing which overload binds, and breaks reflection and serialisation strings. Never. |
| IDE0160 block namespace          | Curb converts to file-scoped and never back; removing braces can change what a name resolves to.                                                |


### The other nine


| Rule                           | Fix                                             | If the verdict were wrong                                                                                                                           |
|--------------------------------|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| IDE0040 accessibility          | Writes out the accessibility C# already applied | Nothing changes; the keyword was already in force.                                                                                                  |
| IDE0044 `readonly`             | Inserts `readonly` into a field's modifier list | A compile error — a write through `ref` or `Interlocked` the analyser missed.                                                                       |
| IDE0090 `new()`                | Drops the type name after `new`                 | A compile error: if the target type were not known, `new()` is an error.                                                                            |
| IDE0007 `var`                  | Replaces a local's type with `var`              | **Silent.** It compiles and may narrow the declared type. The only rule whose mistake is quiet; built last and leans hardest on the freshness gate. |
| IDE0250 readonly struct        | Inserts `readonly` on a struct                  | Does not compile if some member mutates.                                                                                                            |
| IDE0251 readonly member        | Inserts `readonly` on a struct member           | Does not compile if the member mutates.                                                                                                             |
| IDE0034 simplify `default`     | Drops `(T)` from `default(T)`                   | A bare `default` with no inferable target is an error.                                                                                              |
| IDE0071 simplify interpolation | Drops a redundant `.ToString()`                 | Refused when the call takes arguments, to avoid silently losing the format.                                                                         |
| IDE0240 redundant `#nullable`  | Removes the directive's line                    | Verified by `ContentVerifier` rather than `TokenStreamComparer`, since trivia is not in the token stream.                                           |


## Forwarding the remainder

Curb is not a replacement for `dotnet format style`. With `--forward`, it names the remaining
diagnostics exactly and hands them to `dotnet format`:
```
Cleaned 1 file(s) from 1 log(s) in 105ms — 2 fix(es) in 1 file(s), 0 refused, 0 stale, 0 skipped, 0 failed
  forwarding 1 rule(s) in 1 file(s) to `dotnet format style`: IDE0071
  curb 105ms · dotnet format 2366ms (22.5x) — the difference is the workspace load, which no amount of scoping avoids
```

Both timings are printed because the difference is the point. Nothing else tells you which half of
the wait belongs to which tool.