banned_usage
Flags uses of a member you ban — one method, getter or constructor of a type rather than the whole type. The common need is DateTime.now() and Random() in code that should take an injected clock or seed.
This rule reports nothing until you configure it.
Why use this rule
Section titled “Why use this rule”DateTime.now() is a hidden input. A type that calls it cannot be tested at a chosen moment: you cannot check that a session expires at midnight without either waiting or mocking global state. The same is true of Random() — the test that fails once in fifty runs is usually a seed nobody controls.
Injecting a clock fixes both, and this rule is what keeps it fixed. Banning DateTime.now inside your domain layer while leaving it available in the composition root puts the constraint exactly where it belongs: the code that decides when is allowed to know the time; the code that decides what is not.
See also: package:clock, Flutter testing guidance
// in lib/domain/session.dart//// With an entry banning 'DateTime.now' in lib/domain/**:class Session { const Session(this.expiresAt);
final DateTime expiresAt;
// LINT: cannot be tested without waiting for real time to pass. bool get isExpired => DateTime.now().isAfter(expiresAt);}// in lib/domain/session.dartabstract class Clock { DateTime now();}
class Session { const Session({required this.expiresAt, required Clock clock}) : _clock = clock;
final DateTime expiresAt; final Clock _clock;
bool get isExpired => _clock.now().isAfter(expiresAt);}Turning this rule off
Section titled “Turning this rule off”This rule is in no preset, so it is off unless you enable it by name:
rules: banned_usage: trueTo turn it off again:
rules: banned_usage: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”many_lints: rules: banned_usage: banned: - deny: ['DateTime.now', 'Random.new'] in: ['lib/domain/**'] message: 'Inject a clock or seed so this stays testable.'rules: banned_usage: banned: - deny: ['DateTime.now', 'Random.new'] in: ['lib/domain/**'] message: 'Inject a clock or seed so this stays testable.'| Option | Type | Default | Description |
|---|---|---|---|
banned |
list of maps | [] |
The entries to enforce. With none, the rule reports nothing |
Each entry accepts:
| Key | Type | Required | Description |
|---|---|---|---|
deny |
string or list | one of deny / deny_pattern |
Members banned by exact match, written Type.member or as a bare member |
deny_pattern |
string or list | one of deny / deny_pattern |
Regular expressions, anchored to the whole member 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 |
Writing member names
Section titled “Writing member names”Type.member matches on the type that declares the member, so a subclass cannot slip past: with Iterable.first banned, a List receiver still reports.
The unnamed constructor is spelled new, so Random.new bans Random() while leaving Random.secure() alone.
A bare member name bans it on every type. deny: ['now'] catches DateTime.now() and anything else named now — prefer the qualified form unless that is really what you mean.
Worked example: scope a layer, then carve out one exception
Section titled “Worked example: scope a layer, then carve out one exception”in: and exclude: do different jobs and compose. in: scopes a banned
entry to the paths where the policy applies; exclude: (available on every
rule) skips paths for the whole rule. Together they express “banned across
this layer, except in the one place that legitimately does it” — which is
otherwise a shell script:
many_lints: rules: banned_usage: exclude: ['lib/src/cli/output/**'] banned: - deny: ['print', 'Stdout.write', 'Stdout.writeln', 'Stdout.add'] in: ['lib/**'] message: 'Return the value and let the renderer emit it.'rules: banned_usage: exclude: ['lib/src/cli/output/**'] banned: - deny: ['print', 'Stdout.write', 'Stdout.writeln', 'Stdout.add'] in: ['lib/**'] message: 'Return the value and let the renderer emit it.'Writing to stdout is now an error everywhere under lib/ except the one
renderer that owns the terminal. Domain code returns a value; a single layer
decides how it is displayed, which is what makes the output testable.
Related rules
Section titled “Related rules”avoid_banned_annotations— Ban specific annotations, optionally scoped by directory.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.