Skip to content

map_keys_ordering

v1.0.0WarningConfigurableCode Organization

Flags a map literal whose keys are not in the order you configure.

A long map literal is a lookup table, and an unordered one has to be read end to end to answer “is this key here?”. Ordering keeps a diff honest too: a new key lands in the middle where it can be seen, rather than appended at the end beside a duplicate nobody noticed.

This rule is in the pedantic preset, which sets order: alphabetical. Under any other preset it reports nothing until you set order: — many map literals are deliberately ordered by meaning, and alphabetising a theme map that runs lightest to darkest is a regression.

analysis_options.yaml
many_lints:
rules:
map_keys_ordering:
order: alphabetical
min_entries: 5

With order: alphabetical:

const labels = {
'banana': 'Banana',
'apple': 'Apple',
'cherry': 'Cherry',
'date': 'Date',
'elder': 'Elderberry',
};
const labels = {
'apple': 'Apple',
'banana': 'Banana',
'cherry': 'Cherry',
'date': 'Date',
'elder': 'Elderberry',
};

Below the threshold a reader takes in the whole literal at once and order costs nothing. The default is 5, so a four-entry map is never reported:

rules:
map_keys_ordering:
order: alphabetical
// Not reported — four entries, under the default min_entries: 5
const shortcuts = {
'save': 'Ctrl+S',
'copy': 'Ctrl+C',
'cut': 'Ctrl+X',
'undo': 'Ctrl+Z',
};

Lower it if you want short maps checked too:

rules:
map_keys_ordering:
order: alphabetical
min_entries: 3
// Don't — now reported at three entries
const shortcuts = {
'save': 'Ctrl+S',
'copy': 'Ctrl+C',
'undo': 'Ctrl+Z',
};
// Do
const shortcuts = {
'copy': 'Ctrl+C',
'save': 'Ctrl+S',
'undo': 'Ctrl+Z',
};

Identifier and enum keys sort by their name

Section titled “Identifier and enum keys sort by their name”

Keys do not have to be strings. A plain identifier, an enum constant or any dotted access sorts on the final name:

rules:
map_keys_ordering:
order: alphabetical
enum Status { active, archived, closed, draft, pending }
// Don't — sorted on `closed`, `active`, ... not on `Status.`
const badges = {
Status.closed: 'Closed',
Status.active: 'Active', // LINT
Status.archived: 'Archived',
Status.draft: 'Draft',
Status.pending: 'Pending',
};
// Do
const badges = {
Status.active: 'Active',
Status.archived: 'Archived',
Status.closed: 'Closed',
Status.draft: 'Draft',
Status.pending: 'Pending',
};
rules:
map_keys_ordering:
order: by_length
// Don't
const units = {
'kilometre': 'km',
'metre': 'm', // LINT
'foot': 'ft',
'inch': 'in',
'yard': 'yd',
};
// Do
const units = {
'foot': 'ft',
'inch': 'in',
'yard': 'yd',
'metre': 'm',
'kilometre': 'km',
};
rules:
map_keys_ordering:
order: alphabetical
exclude: ['lib/**/theme/**']
// lib/design/theme/shades.dart — not reported, the order is the point
const grey = {
'lightest': 0xFFF5F5F5,
'light': 0xFFE0E0E0,
'mid': 0xFF9E9E9E,
'dark': 0xFF616161,
'darkest': 0xFF212121,
};
Option Type Default Description
order string — alphabetical, alphabetical_case_sensitive or by_length. Unset means the rule is silent
min_entries int 5 How many entries a literal needs before order is checked

One computed key skips the whole literal. A key that is a function call, an interpolated string or any other expression has no name to sort by. Rather than order the rest around an entry it cannot place, the rule leaves the literal alone entirely.

// Not reported — `resolveKey()` has no sortable name
final config = {
'zebra': 1,
resolveKey(): 2,
'apple': 3,
'mango': 4,
'kiwi': 5,
};

A spread, if or for element skips it too. The literal’s contents are not statically ordered at all, so there is nothing to check:

// Not reported
final merged = {
'zebra': 1,
...defaults,
'apple': 2,
'mango': 3,
'kiwi': 4,
};

Only the first out-of-order key is reported, so one misplaced entry does not produce a wall of diagnostics. No quick fix is offered — reordering entries is a mechanical edit, but a map literal frequently carries comments tied to particular lines, and moving keys past them silently mismatches the two.

To disable this rule:

many_lints.yaml
rules:
map_keys_ordering: false

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