Repository navigation
Core: Share the program-backed half of a component-meta project - #35820
Conversation
eafef19 to
07e3954
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds generic program and provider contracts with a reusable ChangesProgram-backed component metadata
Sequence Diagram(s)sequenceDiagram
participant ComponentMetaProject
participant LanguageService
participant ProgramBackedProject
participant ProjectFileTracker
ComponentMetaProject->>LanguageService: create language service and access program
ComponentMetaProject->>ProgramBackedProject: query source files and ensure files
ComponentMetaProject->>ProgramBackedProject: forward file changes
ProgramBackedProject->>ProjectFileTracker: process changes with one program snapshot
ComponentMetaProject->>LanguageService: extract story and meta symbols
✨ Finishing Touches📝 Generate docstrings
Comment |
Package BenchmarksCommit: No significant changes detected, all good. 👏 |
07e3954 to
2981834
Compare
2981834 to
fdec7f7
Compare
A renderer's component-meta project is two things bolted together: how its TypeScript program gets built and how metadata is read out of it, which differ completely, and answering the manager's membership and lifecycle questions against that program, which does not differ at all. Only the second half was ever renderer-agnostic, and it was being written out per renderer. `ProgramBackedProject` owns that half. A renderer assigns `service` and `files` and gets `dispose`, `ensureFiles`, `hasSourceFile`, `getSourceFilePaths` and `onFilesChanged`; `dispose` and `onFilesChanged` stay overridable because a renderer scheduling background work has to cancel and reschedule around them. React does exactly that for its warmup timer and inherits the rest. Both hook points are fields rather than constructor parameters: a language service host usually closes over the file tracker, so the service cannot exist before `super()` runs. The contracts stay structural. Naming `ts.Program` here would tie every renderer to core's copy of the compiler API, which is the same reason `ProjectCommandLine` is not `ts.ParsedCommandLine`. `normalizePath` is exported alongside, so path normalization stops being a private const copied into each project.
The guard twenty lines above returns unless `componentProp.valueDeclaration` exists and is a property assignment, so re-testing both before picking the symbol can only ever take the first arm, and `componentType.getSymbol?.()` was never reachable. The initializer had already been read into `metaComponentInitializer` and null-checked, too. let selectedSymbol = checker.getSymbolAtLocation(metaComponentInitializer);
The header claimed props are read from the story "so a component is only ever described the way a story actually uses it", which reads as a design principle and is what it is not. Gating the story-JSX path off and running the suite: Tests 2 failed | 146 passed (148) Of the two, only one is a capability - a generic component falls back from `string[]` to `T[]`. The other is `forwardRef`, whose sole diff is two enum members swapping order because resolving the story's JSX interns its literal first; it resolves identically either way. So the read exists for type-parameter instantiation, and the comment now says that, along with the consequence nobody would guess: the first matching JSX element wins, so the documented type is whichever story pins it earliest.
fdec7f7 to
8effccd
Compare
Adds a Source type parameter (default unknown) to ProgramLike, ProgramProvider, and ProgramBackedProject, so a renderer whose program really is a ts.Program can instantiate it with ts.SourceFile | undefined without core ever naming the typescript package. React's ComponentMetaProject now does exactly that.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@code/core/src/component-meta/ProgramBackedProject.ts`:
- Around line 14-16: Update the ProgramLike.getSourceFile method signature to
return Source | undefined instead of Source, matching the nullable result of
TypeScript program APIs while preserving the existing getSourceFiles contract.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: be22bfb4-d060-4bfd-8826-d239d797573d
📒 Files selected for processing (3)
code/core/src/component-meta/ProgramBackedProject.tscode/core/src/component-meta/ProjectFileTracker.tscode/renderers/react/src/componentManifest/componentMeta/ComponentMetaProject.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- code/renderers/react/src/componentManifest/componentMeta/ComponentMetaProject.ts
- code/core/src/component-meta/ProjectFileTracker.ts
…ta-program-project # Conflicts: # code/core/src/component-meta/ProjectFileTracker.ts
What I did
A renderer's component-meta project is two things bolted together. How its TypeScript program gets built, and how metadata is read out of it, differ completely between renderers. Answering the manager's membership and lifecycle questions against that program does not differ at all — but it was written out once per renderer anyway.
This moves the second half into core as
ProgramBackedProject, and puts React's project on it.A renderer assigns two fields and gets five methods:
Fields rather than constructor parameters, because a language service host usually closes over the file tracker — so the service cannot exist before
super()runs.disposeandonFilesChangedstay overridable. React schedules a background warmup re-extract and has to cancel and reschedule around both; it overrides those two and inherits the other three.Why the contracts are structural
ProgramLikeandProgramProviderdescribe only the slice ofts.Program/ts.LanguageServicethis base touches, rather than importing them. Naming the real types would tie every renderer to core's copy of the compiler API — the same reasonProjectCommandLineis notts.ParsedCommandLine, documented on that type.ts.LanguageServicesatisfiesProgramProviderstructurally.What it removes
ComponentMetaProject.ts(React)project.ts(Angular, in the child PR)normalizePathis exported alongside, so path normalization stops being a private const copied into each project.Not included, deliberately
importClosureandfindDefaultExportedClassNamein the Angular project are also generic-looking, and I was going to move them here too. They have exactly one caller each and no second renderer needs them — React resolves components from an explicit batch and Vue from its own checker. Core surface with one consumer is the speculative generalityAGENTS.mdrules out, so they stay where they are used.Vue cannot extend this base:
vue-component-meta's checker owns its own snapshot cache, so it has noProjectFileTracker, and exposesclearCache()rather thandispose().Checklist for contributors
Manual testing
Run from the repository root.
yarn nx run-many -t check -p core react— expect no type errors.yarn vitest run code/core/src/component-meta code/renderers/react/src/componentManifest— expect 279 passing.hasSourceFilefromProgramBackedProjectand re-run step 1. Both React and Angular fail to compile, since neither declares it any more.Documentation