Skip to content

[finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

Description

@baozhoutao

Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

What was measured

Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

file shape
validate-object-references.ts suggest(target, known) + distance(a, b), budget max(2, floor(len/3))
validate-sortable-fields.ts suggest(target, known) + distance(a, b), byte-for-byte the same budget
validate-widget-bindings.ts didYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
object-graph.ts (added by #14267) nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

Why this is worth a card rather than a shrug

This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

Shape of the fix

object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions