Skip to content

v0.5.0

Choose a tag to compare

@rboudrouss rboudrouss released this 05 Sep 14:12
· 8 commits to main since this release

reactant-analyzer v0.5.0

One soundness fix, one output fix, and the documentation rewritten to be read
by someone who has not already read it.

A JSX callee is resolved by the file that writes it (#7)

Two defects, and both of them ended in ✓ ... no issues found, the one line
limitations.md promises is printed only when the run read everything it was
pointed at.

eval_comp_app resolved a JSX callee through get_by_name, which answers with
the first (file, name) key in sort order. App.tsx importing ./b/Widget
with an unrelated a/Widget.tsx elsewhere in the tree got a's body inlined,
and the cross-setter-in-render Error that depended on the real body vanished.
Underneath it, undiscovered: discovery and ImportResolver spelled paths
differently, ./b/W.tsx against b/W.tsx, so every (file, name) lookup built
from a resolved import missed on any .-rooted run, for hooks and contexts as
much as components. reactant . lost a finding that reactant <abs> reported.

Expr::CompApp now carries the component its own file proved it names, the same
fact HookEntry::Custom::resolved_file already carried for a hook call.
ComponentRegistry::resolve_child is the one resolution: the proven origin,
else the name when only one file defines it, else Ambiguous, which makes the
child unanalysable and says so rather than inlining a body the program never
renders there. Root detection and SymbolGraph read the same fact, so the three
consumers can no longer disagree. Aliased imports (import { Widget as Panel })
resolve, which they never did.

Refusing the guess was measured before it was chosen: 1,347 ambiguous references
across the fourteen corpus repositories run separately, eight of which have
none, against 24,500 unknown-component references already reported. Analysing
every candidate instead is sound and strictly more precise, and rejected on that
measurement: <Button/> has 1,453 ambiguous sites when the corpus is one tree,
and a child analysis per candidate is the shape of #86's O(C²) hang.

Component identity is now an interned ComponentId, with the display name
minted only at render (ADR-040, superseding ADR-038 §5, which had unified on the
display name). The display name is content-dependent by construction, so keying
the results map, the shared-state store, every Versioned label and every
setter owner by it meant an unrelated file re-keyed all of them at once. That
half is behaviour-preserving and was measured as such: identical digest, bit for
bit, over 35,541 files.

Corpus: 1317 → 1348 distinct (file, line, col, message) locations, 29
removed and 60 added. The additions are the soundness half: root detection
marked a component referenced by name, so one <Demo/> anywhere demoted every
Demo out of the root set and into phase 2, where custom hooks stay opaque and
findings inside them are lost. The removals are dominated by sites analysed
against a body the program does not render there. dub's Badge, Logo and
QRCode come from an unresolvable @dub/ui, and the old code answered each by
inlining whichever same-named file sorted first.

873s against 858s over the corpus: one clean measurement each side, so no
regression worth reporting and no claim of a gain.

The CLI output says whole sentences again

Every em dash is gone from the diagnostic messages, the explain and rules
text, the clap help, the driver summary and the npm CLI. Each one was rewritten
as a full stop, a comma with a conjunction, or a colon before a list, whichever
the sentence asked for. A mechanical substitution would have produced comma
splices in half of them.

Doing it surfaced twelve messages that a previous strip had already broken by
removing a dash without replacing it, so they had been shipping as run-ons:

... to new values on every run potential infinite render loop
hook `X` not found in registry pass its source file or add a HookSummary
mount-only effect flips state `X` from `false` to `true` the SSR ...

All twenty-nine distinct message shapes the corpus produces were read back after
the change. Counts are untouched. Only the prose moved.

Documentation

The README leads with the bug instead of the method, and shows the run instead
of describing it. The capture is reactant check src/ --trace on a render loop
between a parent and a child: Dashboard owns the state, Filters normalizes
it in an effect and hands it back through a prop, and the spread allocates a
fresh object every run. Both files are individually correct, every deps array
lists what its effect reads, and ESLint has nothing to say. The rules table is
split in two, which is the actual pitch: the rules ESLint has no counterpart
for, then the three that overlap but carry through indirection.

limitations.md, usage.md, plugins.md, custom-rules.md and
precision-log.md are in English, and they read forward: what the analyzer does
and does not do now, rather than the order in which it got there.
limitations.md was re-checked issue by issue against the tracker, closing
comments included, because a wontfix is still a limitation and a completed one
is not. Nine closed issues were being presented as live limitations. The
"Recently fixed" table is gone with them: a changelog of what used to be broken
is the tracker's job. The 45 descriptions in pack.schema.json come from doc
comments and moved with them.


1436 tests. Corpus baseline 1348 findings over 14 pinned repositories and 35,541
files. The wasm build stays byte-identical to the native CLI.