# [SuppressWarning]

> Accept ONE named warning about ONE declaration. Put it on whatever the finding is reported against — a function, an entity, one FIELD of an entity, a component or a component member, a workflow state or one of its members — naming the code the warning itself prints. Everything else that declaration earns still arrives, errors are never affected, and a MUST-tier lint finding is reported however it is annotated.

<!-- id: diagnostics-suppress-warning · area: diagnostics · stability: stable · html: https://osysharp.com/reference/diagnostics/suppress-warning/ -->

## Summary        {#summary}
**`[SuppressWarning("CODE")]`** is how you say *I read that one, and I mean it*.

```osy syntax
[SuppressWarning("cost-unbounded-read")]
live var everyRoom = Room.ToList();
```

A warning tells you something about your program that is usually a mistake. Some programs are correct **and** earn
one anyway. Without a way to say so, you are left choosing between changing correct code and living with a message
you have to re-read and re-dismiss every build — and a warning people learn to skip past has stopped working, for
your case and for the next one.

## Signature      {#signature}

```text
[SuppressWarning("CODE" [, "CODE"…])]
```

- **`CODE`** — the diagnostic code, exactly as the warning prints it — a compiler code (`ENUM_VALUE_REINTERPRETED`)
  or an `osy lint` rule id (`cost-unbounded-read`). String literals only.
- Placed on **whatever the finding is reported against** — see [Where does it go?](#where).
- Several codes in one attribute, or several attributes, both work.

## Description    {#description}

### Where does it go?        {#where}

**On the thing the finding is reported against, at the granularity it names.** Every finding prints a target and a
line; put the attribute on the declaration that line belongs to. All of these carry it:

| the finding names… | write it on |
|---|---|
| an app-wide shape | the `app { }` manifest |
| a function | the `void Foo(…)` declaration |
| an entity, or the entity's `security { }` | the `entity Foo` declaration |
| **one FIELD of an entity** (`User.Email`) | **that field** |
| a component, or its page | the `component Foo()` declaration |
| a component member | that `action` / `live var` / field / lifecycle hook |
| a workflow state (`Flow.Pending`) | that `state` |
| **a member of a state** (`Flow.Pending.Acceptance`) | **that member** — the `subscribe` slot, the `enter` / `exit` block, the `on` handler, or the `Assigned` / `Finished` milestone the finding is reported in |
| a workflow event | the `event` declaration, beside its `[Authorize]` |

A dotted target is the tell: `User.Email` is a **field**, so the attribute goes on the field, not on `User`. Putting
it on the entity would work too — an entity's suppression covers its whole body — but it accepts more than you
looked at, and the next field to earn the same finding is silenced by a decision nobody made about it.

### A MUST is not suppressible        {#must}

`osy lint` sorts findings into MUST / SHOULD / CONSIDER. A **SHOULD or a CONSIDER is advice**, and recording a
decision about advice is exactly what this attribute is for. A **MUST is the ship gate** — `osy lint --strict`
exits non-zero on one — so it stays reported however it is annotated, and the finding tells you so in a sentence
rather than going quiet. That holds at every placement, a field included: widening *where* the attribute may be
written never widens *what* it can silence.

### It covers ONE declaration        {#scope}

The suppression applies to the declaration it sits on, and to nothing else. There is deliberately no file-wide or
app-wide form: you are accepting **one warning about one thing**. A file-level switch would go on silencing the same
warning in code written later, by someone who never made that judgement — which is the moment a suppression stops
being a decision and becomes a blindfold.

### It covers ONE code        {#one-code}

Naming a code silences that code. Every other warning the declaration earns — today's and tomorrow's — still
arrives. That is what makes the attribute safe to leave in place: it does not turn anything off, it answers one
question.

There is no bare `[SuppressWarning]`. It would mean "and whatever else this earns", which is the one thing you are
not in a position to agree to yet, so the compiler asks for the code instead.

### It never touches an error        {#not-errors}

Only warnings can be suppressed. An error says your program does not have a defined meaning; that is not a matter of
opinion and there is nothing to accept.

### Say WHY, in a comment        {#why}

The attribute records that you decided; it cannot record what you knew. A reader six months later needs the reason,
and the reason is usually a sentence:

```osy syntax
// There are eleven rooms and there will never be more — this is a fixed set, not a growing table.
[SuppressWarning("cost-unbounded-read")]
live var everyRoom = Room.ToList();
```

If you cannot write that sentence, the warning is probably right.

## Examples       {#examples}

### Accepting the commit-agency warning        {#example-commit}

A board reads its whole column list on every visit. `cost-unbounded-read` is right that an unbounded read is usually
a paging bug waiting to happen — and here the set is fixed by the domain, so it is not.

```osy title="a read that is unbounded on purpose" test app=diagnostics-suppress-warning
[Principal] entity User { [MaxLength(255)] string Email; }

entity Column {
  [MaxLength(80)] string Title;
  security { allow read, create when IsAuthenticated; }
}

[Page("/board")]
component BoardPage() {
  // A board has four columns and always will — this is a fixed set, not a growing table.
  [SuppressWarning("cost-unbounded-read")]
  live var columns = Column.ToList();

  render {
    Stack(gap: 2) {
      foreach (var c in columns) { Text(c.Title); }
    }
  }
}
```

### Does one suppression cover the rest of the file?        {#example-scope}

A suppression on one member says nothing about another. If a second member earns the same finding, it gets it — and
that is the point: the first decision was about the first member.

```osy syntax
[SuppressWarning("cost-unbounded-read")]
live var everyRoom = Room.ToList();       // accepted

live var everyBooking = Booking.ToList(); // still reported — nobody has said anything about this one
```

### Accepting a finding about one FIELD        {#example-field}

`data-constraint-lets-null-through` asks a real question about a `[Unique]` field that can be absent: two rows with
no value do not collide, so the constraint does not mean "every row has one". Here the field is genuinely optional,
so the answer is "yes, that is what I meant" — and the finding is scoped to the field, so the attribute goes there.

```osy title="a constraint that lets null through, on purpose" test app=diagnostics-suppress-warning-field
[Principal] entity User {
  [Unique, MaxLength(200)] string Email;
  [MaxLength(200)] string PasswordHash;

  // A referral code is optional — most people sign up without one, and two blanks are not a clash.
  [SuppressWarning("data-constraint-lets-null-through")]
  [Unique, MaxLength(40)] string? ReferralCode;

  security {
    allow read when IsAuthenticated;
    // Nothing to do with suppression — a credential on the [Principal] rides `Session.CurrentUser` to the
    // browser without it. See [app.Auth — how the platform authenticates a user of your app](https://osysharp.com/reference/security/password-auth/).
    deny read PasswordHash when IsAuthenticated;
  }
}
```

Note the `?`. A bare `string ReferralCode;` is **required by its spelling**, so it has no absent case and the
finding never fires on it — a suppression there would silence nothing.

### Accepting a finding about a workflow slot, or an `enter` block        {#example-workflow}

An invitation is answered by whoever holds the emailed link. Two findings are right to ask about that: the slot
declares no `Candidates` (`workflow-slot-open-to-everyone`), and the link minted in `enter` steps around the event's
`[Authorize]` (`security-callback-url-widens-an-authorize`). Both are the design here, so both are answered where
they are reported — on the slot, and on the `enter` block. A state member takes the attribute exactly as a
function or a field does, and so does the `state` itself.

```osy title="a slot that is open on purpose, and a link that is meant to widen the rule" test app=diagnostics-suppress-warning-workflow
enum InviteStatus { Pending, Accepted, Expired }

[Principal] entity Person {
  [MaxLength(80)] string Email;
  security { allow read when IsAuthenticated; }
}

entity Invite {
  [MaxLength(200)] string Email;
  InviteStatus Status;
  [MaxLength(400)] string? Link;
  security { allow read, create, update when IsAuthenticated; }
}

workflow Invitation {
  Tracks = Invite.Status; Autostart = true; Initial = Pending;

  [Authorize(u => u.Email == this.Item.Email)]
  event Accept();

  state Pending {
    // Anyone holding the link may accept — the slot is open by design, not by omission.
    [SuppressWarning("workflow-slot-open-to-everyone")]
    subscribe Accept() as Acceptance {
      Finished { Within = TimeSpan.FromDays(14); Unfinished { goto Expired; } }
    }

    // The emailed link is the whole point: the [Authorize] above governs the in-app button, the link the mail.
    [SuppressWarning("security-callback-url-widens-an-authorize")]
    enter {
      this.Item.Link = Acceptance.CallbackUrl();
    }

    on Acceptance { goto Accepted; }
  }

  terminal success Accepted { }
  terminal cancel Expired { }
}
```

Each attribute covers the one member it sits on. A second slot in `Pending` with no `Candidates` would still be
reported — that is a second decision, and nobody has made it yet.

## See also       {#see-also}

- [Log.*](https://osysharp.com/reference/diagnostics/log/) — writing to your app's own log, which is a different question from the compiler's.
