avoid_banned_annotations
Flags annotations you ban, optionally only inside the directories you name. The motivating case is scope: @visibleForTesting is reasonable on a helper and wrong in a production directory.
@visibleForTesting says “this is private, except to tests”. That is a fair trade in a utility, but in a core production type it is how encapsulation erodes — the annotation makes each widening explicit without making it rare.
This rule is in no preset and reports nothing until you add banned:. Every example below shows the configuration that produces it.
many_lints: rules: avoid_banned_annotations: banned: - deny: ['visibleForTesting'] in: ['lib/production/**'] message: 'Production code must not widen visibility for tests.'rules: avoid_banned_annotations: banned: - deny: ['visibleForTesting'] in: ['lib/production/**'] message: 'Production code must not widen visibility for tests.'Write the annotation name without the @. Both @visibleForTesting and the prefixed @meta.visibleForTesting match a single visibleForTesting entry.
Examples
Section titled “Examples”Stop production code opening itself up for tests
Section titled “Stop production code opening itself up for tests”- deny: ['visibleForTesting'] in: ['lib/production/**'] message: 'Production code must not widen visibility for tests.'// Don't — in lib/production/payment_service.dartclass PaymentService { @visibleForTesting // LINT: widens visibility in production code void resetLedger() {}}// Do — in lib/production/payment_service.dart: inject the seam insteadabstract class Ledger { void reset();}
class PaymentService { const PaymentService(this._ledger);
final Ledger _ledger;
void refundAll() => _ledger.reset();}The same annotation under lib/testing/** is left alone — only the directories
named in in: are checked.
Keep new code off a deprecated API surface
Section titled “Keep new code off a deprecated API surface”- deny: ['deprecated'] in: ['lib/features/**'] message: 'Delete it or promote it; a feature module keeps no deprecations.'// Don't — in lib/features/checkout/coupon.dartclass Coupon { @deprecated // LINT String get label => code;
final String code = '';}// Doclass Coupon { const Coupon(this.code);
final String code;}Ban an annotation family with a pattern
Section titled “Ban an annotation family with a pattern”deny_pattern is anchored to the whole name, so .*Override matches
JsonKeyOverride but not OverrideBehaviour:
- deny_pattern: ['.*Override'] in: ['lib/domain/**'] message: 'Codegen overrides belong on the DTO, not the entity.'// Don't — in lib/domain/order.dartclass JsonKeyOverride { const JsonKeyOverride();}
class Order { @JsonKeyOverride() // LINT final int total = 0;}Ban an experimental annotation everywhere
Section titled “Ban an experimental annotation everywhere”Omit in: and the entry applies to the whole package:
- deny: ['experimental'] message: 'Experimental APIs must not be used outside a spike branch.'Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
banned |
list of entries | [] |
The entries to enforce. With none, the rule reports nothing |
Per entry:
| Key | Type | Required | Description |
|---|---|---|---|
deny |
string or list | one of deny / deny_pattern |
Annotation names banned by exact match, written without the @ |
deny_pattern |
string or list | one of deny / deny_pattern |
Regular expressions, anchored to the whole name |
in |
list of globs | no | Paths, relative to the package root, where the entry applies. Omit to apply everywhere |
message |
string | no | A project-specific explanation appended to the diagnostic |
Known limitations
Section titled “Known limitations”Names, not libraries. An entry matches by annotation name alone, so banning deprecated also catches an unrelated @deprecated you declared yourself. There is no package:uri#Name form here as there is on avoid_banned_types.
Constructor arguments are ignored. @Deprecated('use X') matches a Deprecated entry whatever it is passed; you cannot ban only some of its arguments.
A malformed entry is skipped silently. An invalid regular expression costs you that pattern and nothing else — a plugin cannot report problems against a YAML file.
Related rules
Section titled “Related rules”avoid_banned_exports— Ban re-exports of specific libraries, optionally scoped by directory.avoid_banned_imports— Ban imports of specific libraries, optionally scoped by directory.avoid_banned_names— Ban specific identifiers from being used as declaration names.avoid_banned_types— Ban specific types from being named, optionally scoped by directory.