Skip to content

no_magic_number

v1.0.0WarningConfigurableCode Quality

This rule flags a numeric literal used without a name to explain it.

if (retries > 3) states a policy the reader cannot check and the next person cannot find: the same 3 appears in four other files, and changing the policy means finding all five. if (retries > maxRetries) says what the number is for and gives the change one place to happen.

class UploadPolicy {
bool shouldGiveUp(int attempts) => attempts > 3;
Duration backoffFor(int attempt) => Duration(milliseconds: attempt * 250);
}

Name the policy; the Duration argument was never reported, because a measurement is not a policy:

const maxRetries = 3;
const backoffStep = 250;
class UploadPolicy {
bool shouldGiveUp(int attempts) => attempts > maxRetries;
Duration backoffFor(int attempt) =>
Duration(milliseconds: attempt * backoffStep);
}

If your project has its own spacing or geometry types, add them rather than turning the rule off:

analysis_options.yaml
many_lints:
rules:
no_magic_number:
additional_ignored_invocations: [Insets, AppSpacing]
class Insets {
const Insets.all(double value);
}
// Not reported under the configuration above.
final gutter = Insets.all(17);

Allowing a number your domain uses everywhere

Section titled “Allowing a number your domain uses everywhere”

allowed: replaces the defaults, so restate them unless you mean to drop them. additional_allowed: adds to them instead:

many_lints.yaml
rules:
no_magic_number:
additional_allowed: [100, 360]

These never report, and none of them is configurable away:

-1, 0, 1 and 2. The vocabulary of indexing, counting and halving. Naming them makes code worse:

int firstOrSentinel(List<int> xs) => xs.isEmpty ? -1 : xs.length ~/ 2;

A literal that initialises a declaration. It is already named, and this is the shape the rule asks people to move towards. The check sees through arithmetic, so this counts as one named value:

const maximumStoredBytes = 100 * 1024 * 1024;

const declarations, enums and annotation arguments, for the same reason.

Measurements. EdgeInsets, Gap, SizedBox, Size, Offset, Duration, BorderRadius, Radius, Rect, Alignment and their siblings take numbers that are a measurement, not a policy. On a real Flutter app these accounted for 416 of 490 reports; spacing8 is a worse name than 8. See ignored_invocations for the full default list.

Tests. A fixture’s numbers are the test data, and naming each one buries the case it describes. Set ignore_tests: false to include them.

Two things this rule deliberately does still report: a const constructor argument — const EdgeInsets.all(17) still hides what 17 means, unless the type is in the ignore list — and seed or demo data. If a file is data, reach for exclude rather than weakening the rule.

This rule is in the pedantic preset: what counts as magic is a house style, and in a Flutter codebase full of layout numbers the honest default is off.

analysis_options.yaml
many_lints:
rules:
no_magic_number:
allowed: [-1, 0, 1, 2]
additional_ignored_invocations: [Insets]
ignore_tests: true
Option Type Default Description
allowed list of numbers [-1, 0, 1, 2] Numbers that never report
additional_allowed list of numbers [] Numbers to add to the defaults, instead of restating them
ignored_invocations list of strings [BorderRadius, Radius, EdgeInsets, EdgeInsetsDirectional, EdgeInsetsGeometry, Gap, SliverGap, SizedBox, Size, Offset, Duration, Rect, Alignment] Constructors and methods whose numeric arguments are a measurement
additional_ignored_invocations list of strings [] Names to add to that list
ignore_tests bool true Skip files under test/

To disable this rule:

many_lints.yaml
rules:
no_magic_number: false

To keep the rule on but skip certain paths, use per-rule exclude.