Summary#
[SuppressWarning("CODE")] is how you say I read that one, and I mean it.
[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#
[SuppressWarning("CODE" [, "CODE"…])]CODE— the diagnostic code, exactly as the warning prints it — a compiler code (ENUM_VALUE_REINTERPRETED) or anosy lintrule id (cost-unbounded-read). String literals only.- Placed on whatever the finding is reported against — see Where does it go?.
- Several codes in one attribute, or several attributes, both work.
Description#
Where does it go?#
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#
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#
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#
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#
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#
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:
// 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#
Accepting the commit-agency warning#
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.
[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?#
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.
[SuppressWarning("cost-unbounded-read")]
live var everyRoom = Room.ToList(); // accepted
live var everyBooking = Booking.ToList(); // still reported — nobody has said anything about this oneAccepting a finding about one 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.
[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](/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#
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.
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#
- Log.* — writing to your app's own log, which is a different question from the compiler's.