Skip to content

prop-flow: resolve JSX spreads instead of reporting them as manual - #935

Merged
fxOne merged 2 commits into
masterfrom
issue-933-prop-flow-resolve-jsx-spreads-instead-of
Aug 2, 2026
Merged

prop-flow: resolve JSX spreads instead of reporting them as manual#935
fxOne merged 2 commits into
masterfrom
issue-933-prop-flow-resolve-jsx-spreads-instead-of

Conversation

@fxOne

@fxOne fxOne commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

prop-flow reported every JSX element carrying a {...spread} as manual, and because the pass-through climb stopped there, one spread suppressed the verdict for that call site and for every leaf below it. Spreads are now resolved wherever the type system can answer the question, so manual is reserved for cases that genuinely cannot be decided statically. Two correctness bugs surfaced on the way — attribute precedence and pass-throughs inside render callbacks — are fixed here too.

Changes

  • Spread resolution in three tiers: a spread whose type provably lacks the prop is skipped, {...props} and {...rest} are followed one level up into the enclosing component, and a spread of an object literal (or of a const bound to one) is read key by key. any, index signatures, and unions carrying the prop in only some constituents still report manual, as does an optional prop in a spread contesting an earlier value.
  • Attribute precedence fix: <C title="x" {...props} /> reported "x", but JSX is last-wins, so the spread overrides the attribute. AttrLookup drops the 'spread' sentinel for an explicit { attr, spreadsAfter } shape that makes the ordering decidable.
  • Pass-throughs inside render callbacks: items.map(() => <C x={props.x} />) was classified as a local value, which could turn into a wrong justified or unnecessary-optional.
  • Optional props declared only in a dependency are no longer reported. A component spreading React.ComponentProps<'button'> inherits some 250 optional DOM and ARIA props; a verdict on those is true but not actionable, and it buried the props the author owns. A prop redeclared in the component's own type is still reported.
  • A pass-through that climbs into a function which is called rather than rendered (a renderX({ … }) test helper) now reports manual instead of counting its invisible callers as zero — which would report a live prop as caller-dead.
  • SiteKind loses its spread member; former spread sites are reported as passthrough / real / omit / manual with a note. This changes --json output.
  • Classification and component discovery move out of analyzer.ts into classify.ts and component.ts; Component is now declared in component.ts and re-exported unchanged from the package index.
  • New fixtures (spread.tsx, sources.tsx, vendored.tsx) plus a checked-in fixtures/basic/node_modules/vendor stand-in — with a matching .gitignore negation — so the dependency-prop rule can be exercised against a real external-library boundary.
  • README limitations section and CHANGELOG updated to match the new behaviour.

Related issues

Closes #933

@fxOne
fxOne merged commit 113513b into master Aug 2, 2026
1 check passed
@fxOne
fxOne deleted the issue-933-prop-flow-resolve-jsx-spreads-instead-of branch August 2, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prop-flow: resolve JSX spreads instead of reporting them as manual

1 participant