Skip to content

refactor(react, docs): derive component props from the API schema, drop react-docgen - #10621

Open
luvkapur wants to merge 7 commits into
masterfrom
remove-react-docgen
Open

refactor(react, docs): derive component props from the API schema, drop react-docgen#10621
luvkapur wants to merge 7 commits into
masterfrom
remove-react-docgen

Conversation

@luvkapur

@luvkapur luvkapur commented Aug 14, 2026

Copy link
Copy Markdown
Member

Summary

The docs UI shows a properties table for each React component. Before this change, react-docgen 5.3.1 produced that table through the legacy ConsumerComponent.docs doclets. After this change, ReactMain.getDocs reads the TypeScript API schema. The react-docgen package is removed.

Two UI surfaces use getDocs:

  • The properties tab on the Compositions page (compositions.tsxuseDocs).
  • The properties table on the Overview page (docs-appPropertiesTableuseFetchDocs).

The UI components do not change. Only the data source of the resolver changes.

Why

react-docgen ran on each non-test file on each cold component load. In this workspace that was 3,224 parses. 2,942 of them produced nothing and fell through to the jsdoc parser. react-docgen also had no type information. It could only describe what it could match in the source text.

The schema extractor already computes this data for the API Reference tab. It describes props with their types. The react-docgen package was pinned at 5.3.1. The 7.x upstream has a different API, so an update was not possible.

How the props are resolved

The React aspect does not inspect other schema node types. The schema resolves its own members. This is a polymorphic capability of SchemaNode, like getNodes(), toString() and diff().

  • SchemaNode.getMembers(context). The default returns no members. An interface and a type literal return their members. An interface also appends the members it inherits with extends. An intersection combines the members of its parts. An alias, a parenthesized type and an export wrapper delegate to the node they wrap. A type reference resolves through context.resolveRef. A visited set stops self-referencing types.

  • ModuleSchema.listExports() and listDeclarations(). These return the declarations with export wrappers and namespaces unwrapped. They do not change the module. flatExportsRecursively() does.

  • APISchema.findDeclaration(), resolveRef() and getMembersOf(). These resolve a reference by name inside the component. A reference to another component or to a package resolves to nothing, because that declaration is not part of this schema.

  • ParameterSchema.getBindingDefaults(). This returns the default values of destructured bindings, for example { size = 32 }.

  • APISchema.listExportedDeclarations(). This follows an exported reference to the local declaration, for example export default Button. findDeclaration() also accepts the name a declaration is exported under, for example export { Props as ButtonProps }.

  • TypeUnionSchema.getMembers(). A union contributes one member per name. If the alternatives give a member different types, the member gets the union of those types. A member is required only when every alternative requires it.

react-docs-from-schema.ts is now about 20 lines. It knows only ReactSchema and this API. Inherited props, for example interface ButtonProps extends BaseProps, are included. react-docgen did not include them.

ReactAPITransformer decides what is a React component. It now also accepts a class that extends React.Component, Component or PureComponent. The props of a class component are the first type argument of the base class. .js files count as React files, like .tsx and .jsx.

Compatibility

More than one version of the schema can be in use at the same time. This change keeps them compatible:

  • The serialized form does not change. All new capabilities are methods. No field is added or removed. Old artifacts hydrate into the new classes without change.
  • The extractor fix for inline defaults writes defaultValue on a VariableLikeSchema binding. That class has always serialized this field. An old artifact has no default for this case, which is the current behavior.
  • A schema built by another copy of the semantic-schema package has the serialized fields but not the new methods. The mapper detects this and hydrates the schema again through APISchema.fromObject(api.toObject()), with the schema classes and ReactSchema registered in its own copy. The serialized form is the contract between versions. No code walks node internals.
  • Class components become ReactSchema at extraction time. A component that was built before this change shows the class-component props after it is built again.

Verification

  • npm run lint (tsc and oxlint) passes.
  • bit compile passes.
  • 25 unit specs cover the schema generics. 13 cover the mapper. 4 cover the parameter extractor. 4 cover the React API transformer.

The mapper ran against real schema artifacts on a running bit start. The prop counts are equal to the react-docgen counts, before and after the generics refactor:

component schema react-docgen
design/ui/avatar 8 8
preview/ui/component-preview 15 15
lanes/ui/inputs/lane-selector 14 14
component/ui/version-dropdown 7 7
design/ui/tooltip 3 3
design/ui/time-ago 2 2

The schema output has more detail. For example, it gives isTag?: (version?: string): boolean = (version) => semver.valid(version) !== null. react-docgen gave only a bare signature and no default value.

Measured on a workspace component: a cold getDocs call takes 0.4 s to 1.1 s. A warm call takes about 190 ms. Six concurrent calls for one component run one extraction. A non-React component returns an empty table and no error.

The aspect graph has no new edge. SchemaAspect was already a dependency of ReactAspect, and schemaMain was already injected into the provider. This change only passes the instance to the constructor. There is no schema → react edge. bit status loads all components without a circular-dependency error.

Review notes

  • Performance. getDocs changed from a field read to a possible schema extraction. For a built component it reads the build artifact. For a workspace component it runs the extractor. The extractor is warm after tsserver is up. An in-flight map removes duplicate concurrent requests for one component. Nothing is cached after a request settles, so a workspace component never shows a stale schema. Check this on a large workspace.
  • bit show --legacy changes. Docs are now jsdoc only, so React components do not show a prop table in that command. A component comment without an explicit @name shows a blank name, as it does for all other files, because the jsdoc parser does not infer declaration names. That command is the only remaining consumer of ConsumerComponent.docs. The jsdoc parser still fills that field. Version.id() does not include docs, so no version hash changes.
  • pnpm-lock.yaml is not regenerated. CI runs plain bit install without --frozen-lockfile. A local regeneration produced about 33k lines of unrelated changes.

Known limitations

  • Type arguments are not substituted. GenericProps<string> shows member types as T. The API Reference tab shows the same. This is a schema-wide capability for a follow-up.
  • propTypes and Component.defaultProps are not read. The schema extractor does not model them, and the API Reference tab does not show them. The destructured default form, { size = 32 }, is captured.

Pre-existing issues seen during this work

  • bit schema <pattern> --json fails with Unknown argument: json and prints the help text.
  • bit test on components/* specs fails in this workspace with Unexpected token. The same failure occurs on master for doc-parser. These specs run in the CI capsules.
…op react-docgen

The properties tables in the docs UI (the Compositions "properties" tab and the
Overview properties table) were fed by react-docgen 5.3.1 via the legacy
`ConsumerComponent.docs` doclets. Point `ReactMain.getDocs` at the TypeScript
schema extractor instead and remove react-docgen entirely.

react-docgen ran for every non-test file on every cold-cache component load —
3,224 parses in this workspace, of which 2,942 produced nothing — and it read
props without type information. The schema extractor already computes this data
for the API reference.

Measured against real schema artifacts, prop counts match react-docgen exactly
(avatar 8, component-preview 15, lane-selector 14, version-dropdown 7, tooltip 3,
time-ago 2) with more precise types and default values react-docgen missed.

`bit show --legacy` now reports jsdoc-only docs for React components.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@luvkapur

luvkapur commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

CI note

The master merge is in this branch. Status on the current head:

The earlier version of this note described the file-count guard failure in e2e/performance/filesystem-read.e2e.ts. #10599 fixed that on master on 2026-08-26, and it no longer fails here.

@luvkapur
luvkapur marked this pull request as ready for review August 28, 2026 13:35
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Derive React property docs from API schemas

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Derive React property documentation from typed API schemas instead of legacy consumer doclets.
• Deduplicate concurrent schema extraction while avoiding stale workspace documentation.
• Remove react-docgen parsing, fixtures, tests, and dependency configuration.
Diagram

sequenceDiagram
  actor UI as Docs UI
  participant GQL as GraphQL
  participant React as React Main
  participant Schema as Schema Main
  participant Mapper as Schema Mapper
  UI->>GQL: Request properties
  GQL->>React: getDocs
  React->>Schema: getSchema
  Schema-->>React: API schema
  React->>Mapper: Map props
  Mapper-->>React: React docs
  React-->>GQL: Docs result
  GQL-->>UI: Properties
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persist extracted docs cache
  • ➕ Reduces repeated schema extraction after requests settle
  • ➕ Improves repeated property-table latency in large workspaces
  • ➖ Requires precise invalidation for workspace source changes
  • ➖ Adds memory and cache-lifecycle complexity
2. Expose API schema directly to the UI
  • ➕ Avoids maintaining a server-side compatibility mapper
  • ➕ Lets clients consume the richest schema representation
  • ➖ Requires client and transport rewiring
  • ➖ Couples documentation surfaces to semantic-schema details

Recommendation: Keep the PR's server-side mapper and in-flight-only deduplication because it reuses the existing schema service while preserving the GraphQL and UI contracts. Consider a version-aware persistent cache only if large-workspace measurements show unacceptable repeated extraction latency.

Files changed (6) +358 / -28

Enhancement (2) +180 / -15
react-docs-from-schema.tsMap semantic API schemas into React property docs +148/-0

Map semantic API schemas into React property docs

• Introduces schema traversal that unwraps exports, indexes public and internal types, resolves supported prop structures, and merges destructured defaults. It emits the existing abstract, file path, and properties shape while tolerating duplicate semantic-schema module instances.

scopes/react/react/react-docs-from-schema.ts

react.main.runtime.tsSource React docs from SchemaMain with request deduplication +32/-15

Source React docs from SchemaMain with request deduplication

• Injects the existing SchemaMain dependency into ReactMain and derives docs from component API schemas. Concurrent requests share an in-flight promise, settled entries are removed to prevent stale workspace data, and extraction failures return null after debug logging.

scopes/react/react/react.main.runtime.ts

Refactor (2) +2 / -12
parser.tsRestrict legacy documentation parsing to JSDoc +1/-11

Restrict legacy documentation parsing to JSDoc

• Removes the react-docgen-first parsing path and sends source files directly through the JSDoc parser. Cached legacy doclets therefore remain available without generating React prop tables.

components/semantics/doc-parser/parser.ts

react.graphql.tsAwait asynchronous React documentation extraction +1/-1

Await asynchronous React documentation extraction

• Updates the existing GraphQL resolver to await ReactMain.getDocs while preserving its response contract and empty fallback.

scopes/react/react/react.graphql.ts

Tests (1) +176 / -0
react-docs-from-schema.spec.tsCover React schema-to-docs mapping behavior +176/-0

Cover React schema-to-docs mapping behavior

• Adds tests for aliases, interfaces, private types, intersections, defaults, export wrappers, unresolved props, and component comments. The cases verify property shape and component-selection behavior.

scopes/react/react/react-docs-from-schema.spec.ts

Other (1) +0 / -1
workspace.jsoncRemove the react-docgen dependency +0/-1

Remove the react-docgen dependency

• Deletes the pinned react-docgen 5.3.1 package from workspace dependency policy because React props no longer use its parser.

workspace.jsonc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 28, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. JavaScript components lose docs ✓ Resolved 🐞 Bug ≡ Correctness
Description
reactDocsFromSchema() only accepts ReactSchema exports, but ReactAPITransformer creates those
nodes only for .tsx and .jsx files, so React components authored in .js now return an empty
properties table. The repository's HeroButton.js fixture is such a component and declares
documented propTypes that the removed docgen path could consume.
Code

scopes/react/react/react-docs-from-schema.ts[R60-61]

+  const reactNodes = api.module.listExports().filter(ReactSchema.isReactSchema);
+  if (!reactNodes.length) return undefined;
Evidence
The mapper filters exclusively for ReactSchema, while the only transformer that creates that
schema explicitly limits detection to .tsx and .jsx. The checked-in JavaScript React fixture has
documented propTypes, demonstrating a supported component shape that cannot enter the new mapper.

scopes/react/react/react-docs-from-schema.ts[58-65]
scopes/react/react/react.api.transformer.ts[6-15]
e2e/fixtures/components/hero-button/HeroButton.js[12-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Schema-derived React docs return no properties for components authored in `.js`, because those exports are never transformed into `ReactSchema` nodes.
## Issue Context
The previous docgen path supported JavaScript `propTypes`; retain that coverage when switching the docs source.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[60-61]
- scopes/react/react/react.api.transformer.ts[6-15]
- e2e/fixtures/components/hero-button/HeroButton.js[12-28]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. DefaultProps values are dropped 🐞 Bug ≡ Correctness
Description
The new mapper sources defaults exclusively from destructured parameter bindings, so
Component.defaultProps assignments are never reflected in the properties table. Components using
that supported React convention lose all displayed default values after this data-source switch.
Code

scopes/react/react/react-docs-from-schema.ts[R64-65]

+    const members = node.props ? api.getMembersOf(node.props.type) : [];
+    const defaults = node.props?.getBindingDefaults() || new Map<string, string>();
Evidence
The only defaults map passed to toProperty() comes from ParameterSchema.getBindingDefaults(),
which scans only destructured binding nodes. HeroButton.js demonstrates defaults stored instead on
the component's defaultProps assignment, for which the new mapper has no lookup path.

scopes/react/react/react-docs-from-schema.ts[63-70]
components/entities/semantic-schema/schemas/parameter.ts[15-25]
e2e/fixtures/components/hero-button/HeroButton.js[30-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Schema-derived docs ignore defaults declared through `Component.defaultProps`, because only parameter binding initializers are queried.
## Issue Context
The repository contains React components that declare their defaults through a post-declaration `defaultProps` assignment; the replaced docgen path exposed those values.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[63-70]
- components/entities/semantic-schema/schemas/parameter.ts[15-25]
- e2e/fixtures/components/hero-button/HeroButton.js[30-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Aliased props cannot resolve 🐞 Bug ≡ Correctness
Description
resolveRef() searches only unwrapped declarations' original names, so a props reference using an
exported alias such as ButtonProps cannot match the underlying declaration named Props. The
mapper then emits an empty properties table even though the aliased declaration is present in the
component schema.
Code

components/entities/semantic-schema/api-schema.ts[R160-162]

+    const candidates = this.listDeclarations().filter((node) => node.name === ref.name);
+    if (!ref.internalFilePath) return candidates[0];
+    return candidates.find((node) => node.location.filePath === ref.internalFilePath);
Evidence
Export aliases are stored on their wrapper, but the new declaration listing unwraps that wrapper
before the new resolver performs a name-only lookup. Type-reference member expansion depends
directly on this resolver, so an alias mismatch produces no props.

components/entities/semantic-schema/schemas/export.ts[6-21]
components/entities/semantic-schema/schemas/module.ts[31-45]
components/entities/semantic-schema/api-schema.ts[148-170]
components/entities/semantic-schema/schemas/type-ref.ts[60-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
References to exported aliases cannot resolve because declaration listing discards `ExportSchema` while `resolveRef()` compares only the underlying node name.
## Issue Context
`ExportSchema` stores the public alias separately from the wrapped declaration. Resolution must match the referenced exported name while still returning the wrapped declaration, and retain file-local disambiguation.
## Fix Focus Areas
- components/entities/semantic-schema/api-schema.ts[148-162]
- components/entities/semantic-schema/schemas/module.ts[31-45]
- components/entities/semantic-schema/schema-members.spec.ts[125-180]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



4. Default exports lose docs ✓ Resolved 🐞 Bug ≡ Correctness
Description
For const Button = …; export default Button, the extractor exports a TypeRefSchema while the
actual ReactSchema remains internal, but reactDocsFromSchema() filters only direct unwrapped
exports for ReactSchema. It therefore returns no docs for this common default-export form.
Code

scopes/react/react/react-docs-from-schema.ts[R43-44]

+  const reactNodes = api.module.listExports().filter(ReactSchema.isReactSchema);
+  if (!reactNodes.length) return undefined;
Evidence
The export-assignment transformer explicitly represents export default Button as an ExportSchema
wrapping TypeRefSchema('Button'). listExports() only unwraps the wrapper, and the new mapper
checks the resulting node directly rather than resolving that reference to the internal transformed
React declaration.

scopes/typescript/typescript/transformers/export-assignment.ts[22-50]
components/entities/semantic-schema/schemas/module.ts[31-45]
scopes/react/react/react.api.transformer.ts[9-42]
scopes/react/react/react-docs-from-schema.ts[42-58]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Assignment-style default exports unwrap to a type reference rather than a `ReactSchema`, so the new export-only filter misses the component.
## Issue Context
Resolve exported references to their component-local declarations before applying the React predicate, while still excluding non-exported React declarations that are not reachable from an export.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[42-58]
- components/entities/semantic-schema/api-schema.ts[148-170]
- scopes/react/react/react-docs-from-schema.spec.ts[54-170]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Class components lose props ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new mapper accepts only ReactSchema, while the React API transformer converts only
function-like schemas and leaves React classes as ClassSchema. Consequently every class component
now returns no properties docs, including typed React.Component components that the replaced
docgen path could describe.
Code

scopes/react/react/react-docs-from-schema.ts[R43-44]

+  const reactNodes = api.module.listExports().filter(ReactSchema.isReactSchema);
+  if (!reactNodes.length) return undefined;
Evidence
Class declarations have a distinct schema type carrying their extends clauses, but the only React
transformer rejects every node that is not FunctionLikeSchema. The new mapper then explicitly
filters exports to ReactSchema, leaving no path by which a class component can be documented.

components/entities/semantic-schema/schemas/class.ts[10-23]
components/entities/semantic-schema/schemas/class.ts[53-62]
scopes/react/react/react.api.transformer.ts[8-29]
scopes/react/react/react-docs-from-schema.ts[42-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The schema-backed docs path excludes class React components because only function-like declarations are transformed to `ReactSchema`.
## Issue Context
Recognize classes extending React component base classes and derive their props type argument, either in the React API transformer or in a dedicated schema-to-docs path. Preserve the current functional-component behavior.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[42-58]
- scopes/react/react/react.api.transformer.ts[8-42]
- components/entities/semantic-schema/schemas/class.ts[10-23]
- scopes/react/react/react-docs-from-schema.spec.ts[54-220]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Generic props remain unsubstituted 🐞 Bug ≡ Correctness
Description
TypeRefSchema.getMembers() resolves a generic declaration but ignores the reference's typeArgs,
so props such as GenericProps are emitted with member types like T instead of string. The same
defect affects generic base interfaces such as interface Props extends Base.
Code

components/entities/semantic-schema/schemas/type-ref.ts[R63-64]

+  getMembers(context: GetMembersContext = {}) {
+    return SchemaNode.membersOf(context.resolveRef?.(this), context);
Evidence
The schema stores generic arguments and declaration parameters, but the newly added expansion path
only resolves the declaration and returns its members without creating or applying any substitution
mapping.

components/entities/semantic-schema/schemas/type-ref.ts[20-24]
components/entities/semantic-schema/schemas/type-ref.ts[63-64]
components/entities/semantic-schema/schemas/interface.ts[14-22]
components/entities/semantic-schema/schemas/interface.ts[35-37]
components/entities/semantic-schema/schemas/expression-with-arguments.ts[5-17]
scopes/react/react/react-docs-from-schema.ts[20-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Member expansion resolves generic declarations without applying the type arguments supplied by the reference, producing incorrect prop types.
## Issue Context
`TypeRefSchema.typeArgs`, `InterfaceSchema.typeParams`, and heritage-clause `ExpressionWithTypeArgumentsSchema.typeArgs` preserve the needed generic information. Carry a type-parameter substitution context through member expansion and apply it recursively to returned member types, including inherited generic interfaces.
## Fix Focus Areas
- components/entities/semantic-schema/schema-node.ts[10-19]
- components/entities/semantic-schema/schemas/type-ref.ts[63-64]
- components/entities/semantic-schema/schemas/interface.ts[35-37]
- components/entities/semantic-schema/schemas/expression-with-arguments.ts[5-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Inherited props are omitted ✓ Resolved 🐞 Bug ≡ Correctness
Description
membersOf() returns only an interface's directly declared members and ignores
InterfaceSchema.extendsNodes, so components using interface Props extends BaseProps lose every
inherited property from both docs surfaces.
Code

scopes/react/react/react-docs-from-schema.ts[R76-78]

+  if (isSchema(node, 'TypeLiteralSchema') || isSchema(node, 'InterfaceSchema')) {
+    return (node as unknown as { members: SchemaNode[] }).members;
+  }
Evidence
The semantic schema keeps direct members and inherited bases in separate fields, while the new
mapper reads only the direct field. The TypeScript interface transformer populates extendsNodes,
confirming inherited members are not folded into members.

scopes/react/react/react-docs-from-schema.ts[62-81]
components/entities/semantic-schema/schemas/interface.ts[10-30]
scopes/typescript/typescript/transformers/interface-declaration.ts[23-50]
components/entities/semantic-schema/schemas/expression-with-arguments.ts[5-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
React props inherited through interface `extends` clauses are omitted because the mapper reads only `InterfaceSchema.members`.
## Issue Context
`InterfaceSchema` stores inherited types separately in `extendsNodes`; each entry exposes the resolved declaration through its `expression` field.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[62-81]
- components/entities/semantic-schema/schemas/interface.ts[14-24]
- components/entities/semantic-schema/schemas/expression-with-arguments.ts[5-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Type references resolve incorrectly ✓ Resolved 🐞 Bug ≡ Correctness
Description
indexByName() collapses declarations from all files into one bare-name map and membersOf()
ignores a reference's internalFilePath, componentId, and packageName; duplicate local names
can select another file's props, while external references can incorrectly resolve to a same-named
local declaration.
Code

scopes/react/react/react-docs-from-schema.ts[R70-72]

+  if (isSchema(node, 'TypeRefSchema')) {
+    return node.name ? membersOf(index.get(node.name), index, seen) : [];
+  }
Evidence
TypeRefSchema explicitly distinguishes same-component, file-internal, other-component, and package
references, but the mapper discards all of those discriminators and looks up only node.name in one
component-wide map.

scopes/react/react/react-docs-from-schema.ts[42-53]
scopes/react/react/react-docs-from-schema.ts[70-72]
components/entities/semantic-schema/schemas/type-ref.ts[7-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Props type references are resolved by bare name even though schema references carry file, component, and package identity, allowing collisions to return unrelated members.
## Issue Context
Resolve only genuinely local references and include `internalFilePath`/source location in local declaration keys; do not resolve package or other-component references through the component-local index.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[42-53]
- scopes/react/react/react-docs-from-schema.ts[70-72]
- components/entities/semantic-schema/schemas/type-ref.ts[7-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

9. Legacy docs lose names 🐞 Bug ≡ Correctness ⭐ New
Description
After parse() switches exclusively to the JSDoc parser, React component comments without an
explicit @name produce doclets whose name is an empty string because this parser does not infer
declaration names. Those doclets still populate ConsumerComponent.docs, so the legacy show
documentation table renders a blank component name where react-docgen previously supplied the
display name.
Code

components/semantics/doc-parser/parser.ts[17]

+  const results = await jsDocParse(file.contents.toString(), file.relative);
Evidence
The changed parser now always delegates to jsDocParse; that implementation only extracts comment
blocks, and extractDataRegex leaves name empty unless an @name tag exists. Component loading
stores these results in docs, and the legacy show formatter uses doc.name directly as its table
header.

components/semantics/doc-parser/parser.ts[14-19]
components/semantics/doc-parser/jsdoc/jsdoc-parser.ts[20-30]
components/semantics/doc-parser/extract-data-regex.ts[44-55]
components/semantics/doc-parser/extract-data-regex.ts[92-104]
components/legacy/consumer-component/consumer-component.ts[553-581]
scopes/component/component/show/legacy-show/docs-template.ts[23-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The react-docgen removal makes ordinary React component comments produce legacy doclets with blank names unless they contain an explicit `@name` tag.

## Issue Context
The JSDoc extractor initializes `name` to an empty string and explicitly leaves declaration-name inference as a TODO, while legacy component loading and `show` still consume these doclets.

## Fix Focus Areas
- components/semantics/doc-parser/parser.ts[14-19]
- components/semantics/doc-parser/extract-data-regex.ts[44-55]
- components/semantics/doc-parser/extract-data-regex.ts[92-104]

Make the parser associate each JSDoc block with its following declaration and infer the declaration's name when `@name` is absent; preserve explicit `@name` as an override and add coverage for function/class React exports.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Union property types truncated ✓ Resolved 🐞 Bug ≡ Correctness
Description
TypeUnionSchema.getMembers() emits each branch's member separately, but reactDocsFromSchema()
deduplicates by name and keeps only the first occurrence. For props such as `{ value: string } | {
value: number }, the table incorrectly documents value as only string instead of string |
number`.
Code

scopes/react/react/react-docs-from-schema.ts[70]

+      properties: uniqBy(compact(members.map((member) => toProperty(member, defaults))), 'name'),
Evidence
The union resolver deliberately returns a flattened member from every alternative, while the
mapper's uniqBy(..., 'name') retains the first same-name member and discards all subsequent types.

components/entities/semantic-schema/schemas/type-union.ts[21-42]
scopes/react/react/react-docs-from-schema.ts[36-48]
scopes/react/react/react-docs-from-schema.ts[63-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Union alternatives that declare the same property with different types are flattened and then deduplicated, so only the first type is documented.
## Issue Context
Preserve one property row per name, but combine the distinct types contributed by union alternatives while retaining the already-computed optionality semantics.
## Fix Focus Areas
- components/entities/semantic-schema/schemas/type-union.ts[21-42]
- scopes/react/react/react-docs-from-schema.ts[63-71]
- scopes/react/react/react-docs-from-schema.spec.ts[192-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Unrelated classes become React ✓ Resolved 🐞 Bug ≡ Correctness
Description
Class detection matches only the textual suffix of an extends clause, so an exported class such as
class Model extends Some.Component in a React-source file is converted into ReactSchema even
when Some.Component is unrelated to React. This fabricates a React return type and can expose the
wrong class and documentation through the React schema/docs surfaces.
Code

scopes/react/react/react.api.transformer.ts[R91-93]

+  private reactBaseOf(node: ClassSchema): ExpressionWithTypeArgumentsSchema | undefined {
+    if (!this.isReactFile(node)) return undefined;
+    return node.extendsNodes?.find((base) => REACT_BASE_CLASS.test(base.name));
Evidence
The TypeScript extractor retains the resolved base expression separately but stores the textual
heritage clause as base.name; the new predicate tests only that name's suffix and ignores the
resolved expression's origin before transforming the class into ReactSchema.

scopes/typescript/typescript/transformers/class-declaration.ts[42-56]
components/entities/semantic-schema/schemas/expression-with-arguments.ts[5-18]
scopes/react/react/react.api.transformer.ts[38-46]
scopes/react/react/react.api.transformer.ts[72-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The class transformer recognizes any heritage name ending in `.Component` or `.PureComponent`, regardless of the base symbol's package or component origin.
## Issue Context
Use the resolved `base.expression` metadata to verify that the base is React's `Component`/`PureComponent`; add negative coverage for an unrelated namespace with the same terminal class name.
## Fix Focus Areas
- scopes/react/react/react.api.transformer.ts[28-31]
- scopes/react/react/react.api.transformer.ts[91-98]
- scopes/react/react/react.api.transformer.spec.ts[26-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



12. Aliased defaults use wrong key ✓ Resolved 🐞 Bug ≡ Correctness
Description
For { foo: bar = value }, the transformer records the newly captured initializer on a binding
named bar, while the docs mapper retrieves defaults by the prop member name foo. The foo row
therefore loses its default value even though extraction captured it.
Code

scopes/typescript/typescript/transformers/parameter.ts[94]

+      const defaultValue = elem.initializer ? elem.initializer.getText() : undefined;
Evidence
The extractor looks up and names bindings from elem.name, and only treats computed propertyName
as an alias. getBindingDefaults() then keys by that node name, whereas toProperty() queries the
map with the declared props member name.

scopes/typescript/typescript/transformers/parameter.ts[91-125]
components/entities/semantic-schema/schemas/parameter.ts[18-24]
scopes/react/react/react-docs-from-schema.ts[36-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Ordinary object-destructuring aliases store their initializer under the local binding name rather than the source property name, preventing the docs property from finding its default.
## Issue Context
In `{ foo: bar = 1 }`, `propertyName` is `foo` and `name` is `bar`; computed and ordinary aliases need distinct handling, but defaults must ultimately be addressable by `foo`.
## Fix Focus Areas
- scopes/typescript/typescript/transformers/parameter.ts[91-125]
- components/entities/semantic-schema/schemas/parameter.ts[18-24]
- scopes/react/react/react-docs-from-schema.ts[36-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Union props marked required ✓ Resolved 🐞 Bug ≡ Correctness
Description
Union members are flattened and toProperty() copies each member's branch-local isOptional, so
type Props = { a: string } | { b: string } documents both a and b as required. Neither
property is required across the union, making the generated table's required flags incorrect.
Code

scopes/react/react/react-docs-from-schema.ts[R39-41]

+  const shape = VariableLikeSchema.isVariableLikeSchema(member)
+    ? { type: member.type.toString(), required: !member.isOptional }
+    : { type: member.toString(), required: false };
Evidence
TypeUnionSchema.getMembers() concatenates branch members without preserving branch membership, and
the mapper directly negates the first member's isOptional before uniqBy deduplicates names. The
mapper's union test confirms that members from alternatives are combined into one flat property
list.

components/entities/semantic-schema/schemas/type-union.ts[15-20]
scopes/react/react/react-docs-from-schema.ts[36-47]
scopes/react/react/react-docs-from-schema.spec.ts[192-205]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Properties that are required in one union alternative but absent from another are incorrectly displayed as universally required.
## Issue Context
Requiredness must be computed across all alternatives before flattening/deduplicating members; a property is globally required only when every applicable union branch requires it.
## Fix Focus Areas
- components/entities/semantic-schema/schemas/type-union.ts[15-20]
- scopes/react/react/react-docs-from-schema.ts[36-47]
- scopes/react/react/react-docs-from-schema.ts[63-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Inline binding defaults disappear ✓ Resolved 🐞 Bug ≡ Correctness
Description
getBindingDefaults() cannot recover a destructuring initializer when the parameter has an inline
object type, because the extractor reuses the matching type-literal member rather than creating a
binding node containing that initializer. For `function Button({ text = 'click' }: { text?: string
}), the new docs mapper therefore emits no defaultValue for text`.
Code

components/entities/semantic-schema/schemas/parameter.ts[R20-23]

+    this.objectBindingNodes?.forEach((node) => {
+      if (!(node instanceof InferenceTypeSchema || node instanceof VariableLikeSchema)) return;
+      if (!node.name || node.defaultValue === undefined || defaults.has(node.name)) return;
+      defaults.set(node.name, node.defaultValue);
Evidence
For explicitly typed parameters the extractor computes the inline type literal, then binding
extraction finds and returns its existing member before reading elem.initializer; the new defaults
collector only records defaultValue from those returned nodes.

scopes/typescript/typescript/transformers/parameter.ts[53-56]
scopes/typescript/typescript/transformers/parameter.ts[83-98]
components/entities/semantic-schema/schemas/parameter.ts[18-25]
scopes/react/react/react-docs-from-schema.ts[47-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Inline object-typed destructured parameters lose their binding initializers before `getBindingDefaults()` reads them, so React prop defaults are omitted.
## Issue Context
`ParameterTransformer.getObjectBindingNodes()` currently returns an existing non-inference member unchanged. Preserve the binding initializer separately or clone/augment the binding node without conflating a type-property default with the parameter's destructuring default, then cover the inline typed case with a mapper test.
## Fix Focus Areas
- scopes/typescript/typescript/transformers/parameter.ts[83-109]
- components/entities/semantic-schema/schemas/parameter.ts[18-25]
- scopes/react/react/react-docs-from-schema.spec.ts[123-142]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. Union props render empty ✓ Resolved 🐞 Bug ≡ Correctness
Description
membersOf() has no TypeUnionSchema branch, so a valid React props alias such as `type Props =
CommonProps | LoadingProps` resolves to no properties and the UI shows an empty prop table.
Code

scopes/react/react/react-docs-from-schema.ts[R79-82]

+  if (isSchema(node, 'TypeIntersectionSchema')) {
+    return (node as unknown as { types: SchemaNode[] }).types.flatMap((type) => membersOf(type, index, seen));
+  }
+  return [];
Evidence
The TypeScript transformer emits a TypeUnionSchema containing all constituent types, while the new
mapper handles intersections but falls through to an empty array for unions.

scopes/react/react/react-docs-from-schema.ts[62-83]
scopes/typescript/typescript/transformers/union-type.ts[18-24]
components/entities/semantic-schema/schemas/type-union.ts[5-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Props represented by `TypeUnionSchema` are silently treated as having no members.
## Issue Context
The TypeScript extractor preserves every union constituent in the schema node's `types` array. Define and test the desired deduplication/requiredness behavior when collecting members across alternatives.
## Fix Focus Areas
- scopes/react/react/react-docs-from-schema.ts[62-83]
- scopes/typescript/typescript/transformers/union-type.ts[18-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
…own generics

`react-docs-from-schema` knew the internals of seven node types — it matched
`__schema` strings for Export/Module/TypeRef/Type/TypeLiteral/Interface/
TypeIntersection and cast through `as unknown as` to reach their fields. That
coupled the React aspect to the shape of every schema node it might meet and
sidestepped the polymorphic design of `SchemaNode`.

Each node now owns the answer to "which members do you contribute as an
object-like type", the same way it owns `getNodes()`/`toString()`/`diff()`:

- `SchemaNode.getMembers(context)` — default: none. Interface and type literal
  return their members (interfaces append what they inherit via `extends`);
  intersection combines its parts; alias, parentheses and export wrappers
  delegate; a type reference resolves through `context.resolveRef`. A visited
  set makes self-referencing types terminate.
- `ModuleSchema.listExports()` / `listDeclarations()` — non-mutating views with
  export wrappers and namespaces unwrapped (`flatExportsRecursively` mutates).
- `APISchema.findDeclaration()` / `resolveRef()` / `getMembersOf()` — resolve
  references by name within the component; references to other components or
  packages resolve to nothing, since their declarations aren't in this schema.
- `ParameterSchema.getBindingDefaults()` — defaults from destructured bindings.
- `VariableLikeSchema.isVariableLikeSchema()` / `ReactSchema.isReactSchema()`
  guards, in the package's existing `isTypeRefSchema` idiom.

The React mapper is now ~20 lines that only know `ReactSchema` and the generic
API. Inherited props (`interface ButtonProps extends BaseProps`) are described
too, which neither react-docgen nor the previous mapper did.

18 new specs for the entity, 10 for the mapper (one new, for inheritance).
Note: `bit test` can't run `components/*` specs in this workspace locally
(pre-existing — same failure on `doc-parser`); they run in CI's capsules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread components/entities/semantic-schema/schemas/type-ref.ts
Comment thread components/entities/semantic-schema/schemas/parameter.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 89bbe61

…nions contribute members

- `APISchema.resolveRef()`: a reference that carries `internalFilePath` only
  resolves to a declaration in that file, so same-named declarations in other
  files of the component are never mistaken for it. Refs to other components or
  packages already resolved to nothing.
- `TypeUnionSchema.getMembers()`: a union of object types contributes the
  members of every alternative, in order; a member declared by only some
  alternatives is listed as the first alternative declares it. Previously a
  union props type rendered an empty table.

Specs: entity 20 (+2), mapper 11 (+1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread components/entities/semantic-schema/api-schema.ts Outdated
Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit ab51bb7

… from other package copies

- `ParameterTransformer.getObjectBindingNodes()`: for `({ text = 'click' }: { text?: string })`
  the binding reuses the matching member of the inline props type, which knows
  nothing of the initializer — the default was lost. The member is now cloned
  with the binding's initializer as `defaultValue` (a field `VariableLikeSchema`
  has always serialized), so no wire-format change.
- `reactDocsFromSchema()`: a schema built by another copy of the semantic-schema
  package (an env's extractor graph, an artifact hydrated elsewhere) has the
  serialized fields but not this package's members API. Such a schema is
  re-hydrated through `APISchema.fromObject(api.toObject())`, with the schema
  classes and `ReactSchema` registered in this copy's registry. The serialized
  form is the contract between versions; nothing walks node internals.

Specs: extractor 3 (new), mapper 12 (+1), entity 20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/react/react/react-docs-from-schema.ts Outdated
Comment thread scopes/react/react/react-docs-from-schema.ts
Comment thread scopes/typescript/typescript/transformers/parameter.ts
Comment thread scopes/react/react/react-docs-from-schema.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c51c71c

…rences, union requiredness

- `ReactAPITransformer` also recognises classes extending React's `Component`
  / `PureComponent` (namespaced or not) and takes their props from the base
  class's type argument; `.js` files count as React files alongside `.tsx`/`.jsx`.
- `ModuleSchema.findExport()` + `APISchema.findDeclaration()` resolve the name
  a declaration is exported under (`export { Props as ButtonProps }`);
  `APISchema.listExportedDeclarations()` follows an exported reference to its
  local declaration (`export default Button`). The mapper uses the latter.
- `TypeUnionSchema.getMembers()` computes requiredness across alternatives: a
  member some alternative lacks or leaves optional is contributed as optional.
- `SchemaNode.membersOf()` tracks the current path rather than the whole
  traversal, so a base type shared by two branches contributes to each.
- `getObjectBindingNodes()` keys `{ text: label = 'x' }` by the prop `text`.

Specs: entity 24 (+4), mapper 13 (+1), extractor 4 (+1), transformer 3 (new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/react/react/react-docs-from-schema.ts
Comment thread scopes/react/react/react.api.transformer.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8b96fce

…ase must be React's

- `TypeUnionSchema.getMembers()` contributes one member per name: the types
  the alternatives give it are unioned (`{ value: string } | { value: number }`
  → `value: string | number`), requiredness stays "required everywhere".
- `ReactAPITransformer` accepts a `Component`/`PureComponent` base only when
  it resolved to the `react` package or did not resolve at all — a base from
  another package, another component or a file of this one is not React's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread components/semantics/doc-parser/parser.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7157077

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant