feat(typescript): expose latest Program to transformers in watch mode#1923
Conversation
…\n\n- Add optional getProgram to ProgramTransformerFactory.factory\n- Pass getProgram() to program-scoped transformer factories\n- Recompute custom transformers after each TS watch rebuild\n- Docs: document getProgram and watch-mode behavior\n- Test: ensure factories are recreated and getProgram returns the latest Program
…factor customTransformers for clearer lazy initialization
There was a problem hiding this comment.
Overall implementation is sound: transformers are rebuilt per watch-cycle and getProgram exposes the latest Program. The README example could better showcase deferred use of getProgram() to avoid capturing a stale Program. Consider adding a test that validates typeChecker-based factories are also recreated across rebuilds. Minor JSDoc wording could better reflect availability of getProgram beyond watch mode.
Additional notes (3)
-
Maintainability |
packages/typescript/test/test.js:1711-1776
The new test leavesfixtures/transformers/main.json disk after running. You clean it up at the start, but tidying it infinallyprevents cross-test interference if the test aborts early or is reordered. -
Readability |
packages/typescript/types/index.d.ts:31-36
The JSDoc currently implies the getter is only relevant in watch mode. While its primary purpose is watch-mode freshness, the implementation passes a getter regardless. Clarifying that the getter may be provided in all modes (and will reflect the latest Program in watch mode) would make the contract clearer for users reading the types. -
Readability |
packages/typescript/src/customTransformers.ts:41-50
The lazy initialization looks good and avoids unnecessarygetTypeChecker()work forprogram-only factories. There’s a small duplication in computingcurrentProgram/currentTypeCheckeracross branches; a tiny helper (e.g.,ensureProgram()/ensureTypeChecker()) could improve readability without changing behavior.
Summary of changes
- Types: added an optional
getProgram?: () => Programsecond parameter toProgramTransformerFactory.factory(program, getProgram)intypes/index.d.ts. - Runtime: in
customTransformers.ts, pass agetProgramgetter toprogram-scoped transformer factories and lazily initializeprogram/typeChecker. InwatchProgram.ts, invalidate cached transformers on everyafterProgramCreateto rebuild them per watch cycle. - Docs: updated README to document the new
getProgramparameter and added a note about watch-mode behavior; fixed minor grammar. - Tests: added a watch-like regression test to ensure factories are recreated per rebuild and
getProgram()returns the latestProgram.
… emitted-file cleanup in program test
There was a problem hiding this comment.
Overall implementation is correct and aligns with the stated goals: transformers are rebuilt per watch cycle and getProgram exposes the current Program. The primary gaps are documentation clarity: both the README and JSDoc suggest the getter is watch-only, while the runtime always provides it. There’s also minor duplication in mergeTransformers that could be simplified with tiny helpers. Tests look solid and now clean up emitted artifacts in finally.
Additional notes (6)
-
Readability |
packages/typescript/types/index.d.ts:31-36
The JSDoc implies the getter is only relevant in watch mode. In practice, the implementation always passes a getter; it simply reflects the same Program when not watching and the latest Program across rebuilds in watch mode. Clarifying this avoids users assuming the second param is undefined outside watch mode. -
Readability |
packages/typescript/README.md:172-176
The example encourages callinggetProgram()immediately in the factory, which can lead readers to capture a snapshot rather than deferring to the latest Program. Since the primary benefit ofgetProgramis freshness across watch rebuilds, the docs would be clearer if the example showed deferring access to when the transformer runs. -
Readability |
packages/typescript/README.md:153-156
This line suggests the getter only exists in watch mode. The runtime always passes a getter; in non-watch it simply returns the current Program (identical to the first arg). Tightening the wording improves accuracy. -
Maintainability |
packages/typescript/src/customTransformers.ts:41-50
Nice lazy initialization. There’s still minor duplication in computingcurrentProgram/currentTypeCheckeracross branches, which slightly obscures the control flow. A tiny helper would centralize the logic and make future changes (e.g., additional caching/metrics) less error-prone. -
Readability |
packages/typescript/src/watchProgram.ts:165-168
Good addition. One small accuracy tweak: the getter is useful beyond watch mode (it’s always provided); in watch mode it guarantees freshness across rebuilds. Adjusting the comment will prevent misunderstandings when reading the source. -
Maintainability |
packages/typescript/test/test.js:1698-1779
Good addition and cleanup. There’s a bit of duplication between the two new tests (fake watch program setup, file cleanup, and pass-through transforms). Extracting a tiny helper in the test suite would reduce repetition and make future changes less error-prone.
Summary of changes
- Docs: README now documents a second, optional
getProgram?: () => Programparameter forprogram-scoped transformer factories and adds a watch-mode note. Fixed a minor grammar issue (“it's” → “its”). - Runtime:
mergeTransformersnow lazily initializesprogram/typeCheckerand passes agetProgramgetter toprogram-based factories. In watch mode,afterProgramCreateclears cached transformers to rebuild them per cycle. - Types:
ProgramTransformerFactory.factorysignature updated to accept(program: Program, getProgram?: () => Program)with JSDoc. - Tests: Added two serial tests simulating watch rebuilds to verify factories are recreated and that both
programandtypeCheckertrack the latest values; tests also tidy emitted files infinally.
See modified files: [packages/typescript/src/customTransformers.ts], [packages/typescript/src/watchProgram.ts], [packages/typescript/types/index.d.ts], [packages/typescript/README.md], and [packages/typescript/test/test.js].
…n- watchProgram: use void 0 when resetting cached transformers in watch mode\n- tests: use void 0 in new watch-mode transformer test to satisfy no-undefined\n\nRefs PR #1923 review feedback from @shellscape
There was a problem hiding this comment.
- Docs currently imply
getProgramis a watch-only concern; the runtime always provides it. Clarifying this in README and type JSDoc will prevent misuse and conditional code paths. mergeTransformersis correct but has small duplication—tiny helpers would improve readability and future maintenance.- Watch host comment could better reflect getter availability beyond watch mode.
- New tests are solid; extracting shared helpers would reduce duplication and ease future changes.
Additional notes (3)
-
Maintainability |
packages/typescript/src/customTransformers.ts:41-50
Good lazy initialization and passing of a getter. There’s still minor duplication in computing the current Program/TypeChecker across branches, which makes the control flow slightly harder to follow. -
Readability |
packages/typescript/src/watchProgram.ts:165-168
Nit: the comment implies the benefit only exists in watch mode. The getter is also present in non-watch mode (it just returns the current Program). Clarifying here prevents misunderstandings for readers scanning the source. -
Maintainability |
packages/typescript/test/test.js:1698-1858
The two new tests duplicate setup and teardown (fake watch program, emitted-file cleanup, and no-op transform). Extracting tiny helpers will reduce repetition and make future changes less error-prone.
Summary of changes
- Docs (README): Documented an optional second parameter
getProgram?: () => Programfor program-scoped transformer factories, adjusted examples, fixed grammar, and added a watch-mode note. - Runtime (customTransformers.ts): Lazily initialize
program/typeChecker. Pass agetProgramgetter toprogram-type transformer factories so they can fetch the latest Program in watch mode. - Runtime (watchProgram.ts): In watch mode, reset cached transformers on each
afterProgramCreateso factories are recreated per rebuild; replacedundefinedwithvoid 0per repo conventions. - Tests: Added two serial tests simulating watch rebuilds to verify transformers are recreated and
getProgram()/TypeChecker reflect the current builder. Tidied emitted-file cleanup. - Types: Extended
ProgramTransformerFactory.factorysignature to accept(program: Program, getProgram?: () => Program)with JSDoc.
| /** | ||
| * Factory that may receive a getter for the latest Program when running in watch mode. | ||
| * The second parameter is optional to preserve backwards compatibility with existing | ||
| * transformer factories. | ||
| */ | ||
| factory(program: Program, getProgram?: () => Program): StagedTransformerFactory<T>; |
There was a problem hiding this comment.
The JSDoc suggests the getter is only received "when running in watch mode", which isn’t accurate given the runtime always supplies a getter. Clarifying the contract helps users relying on types alone.
Suggestion
Refine the JSDoc to reflect availability in all modes:
- /**
- * Factory that may receive a getter for the latest Program when running in watch mode.
- * The second parameter is optional to preserve backwards compatibility with existing
- * transformer factories.
- */
+ /**
+ * Factory receives the current Program and may also receive a getter for retrieving
+ * the current Program at call time. The getter is provided in all modes: in non-watch
+ * it returns the same Program argument; in watch mode it reflects the latest Program
+ * across rebuilds. The second parameter remains optional for backwards compatibility.
+ */Reply with "@CharlieHelps yes please" if you'd like me to update the JSDoc.
… to legacy watch behavior\n\n- Gate watch-mode transformer recreation behind new option (default false)\n- Plumb option through plugin options -> watch host\n- Types/README: document option and clarify `getProgram` semantics\n- Tests: enable option for watch-mode freshness tests
There was a problem hiding this comment.
Overall implementation is correct and aligns with the intended behavior: the getter is passed to program-scoped factories and watch-mode factory recreation is gated behind recreateTransformersOnRebuild (defaulting to legacy behavior). The primary opportunities are minor maintainability/readability improvements: reduce small duplication in mergeTransformers, clarify a watch host comment and README wording about getter availability, and factor duplicated test scaffolding. No correctness or performance issues observed in the modified code. The changes appear backward compatible given the new option defaults to false.
Additional notes (3)
-
Maintainability |
packages/typescript/src/customTransformers.ts:41-50
Good change overall. There’s a small duplication in computingcurrentProgram/currentTypeCheckeracross the two branches. A tiny helper makes the control flow clearer and centralizes the lazy init logic without behavior change. -
Readability |
packages/typescript/src/watchProgram.ts:172-176
The new comment is helpful. One small accuracy tweak: the getter is also provided in non-watch builds; enablingrecreateTransformersOnRebuildchanges freshness across rebuilds. Clarifying this here will align with the README and types and prevent readers from assuming the getter is only meaningful in watch mode. -
Maintainability |
packages/typescript/test/test.js:1698-1924
These three tests are solid and complementary, but there’s noticeable duplication in the fake TS watch setup, emitted-file cleanup, and no-op transform. Extracting small helpers would make future changes safer and reduce noise in individual tests.
Summary of changes
- Added an optional getter parameter to program-scoped transformer factories and updated docs to explain watch vs. non-watch semantics.
- Introduced
recreateTransformersOnRebuild(default false) to control whether custom transformer factories are rebuilt per watch-cycle; plumbed through options, index, and watch host. - Adjusted
mergeTransformersto pass agetProgramgetter and to lazily initializeprogram/typeChecker. - In watch mode, optionally invalidate cached transformers on each
afterProgramCreateto ensure freshness when the new option is enabled. - Expanded tests to cover: per-rebuild factory recreation with latest Program/TypeChecker; legacy (default) behavior; ensured emitted files are cleaned up in
finally. - Updated type definitions and README with the new API and option, and improved example guidance to defer
getProgram()usage.
|
@CharlieHelps please make sure the README is correct for the latest commit |
Expand this to see my work.
|
…EADME (its/TypeScript factory, below)
|
Updated the README for @rollup/plugin-typescript to match the latest behavior on this branch.
Commit: 7b1f643 What changed
Re-requested review from @shellscape and @CharlieHelps. |
There was a problem hiding this comment.
No correctness or performance issues detected in the modified code. Minor maintainability/readability opportunities remain in mergeTransformers (duplication), watch-host comments, and the README wording. The boolean normalization for recreateTransformersOnRebuild could be stricter to avoid surprising JS configs. Tests are solid but can be refactored to reduce duplication.
Additional notes (2)
-
Maintainability |
packages/typescript/src/customTransformers.ts:41-50
Good improvement overall. There’s still minor duplication here initializingcurrentProgram/currentTypeCheckeracross the two branches, which makes the control flow a bit harder to scan. A tiny local helper would DRY this up and centralize the lazy-init logic without changing behavior. -
Maintainability |
packages/typescript/test/test.js:1698-1924
The three new tests are solid, but they duplicate the fake watch setup, pass‑through transform, and emitted‑file cleanup. Extracting tiny helpers will make future additions/changes safer and reduce noise in each test.
Summary of changes
- Docs: Fixed typos (“bellow” → “below”), clarified
program-factory signature to include an optionalgetProgramgetter and added a new “Watch mode” note documentingrecreateTransformersOnRebuild. - Runtime:
mergeTransformersnow passes agetProgramgetter toprogram-scoped factories and lazily initializesprogram/typeCheckerusingcurrentProgram/currentTypeChecker. - Options: Plumbed a new
recreateTransformersOnRebuildoption throughgetPluginOptions,index.ts, and the watch host. - Watch host: Optionally resets cached transformers on each rebuild when
recreateTransformersOnRebuildis true. - Types: Updated
ProgramTransformerFactory.factorysignature and documented the new plugin option. - Tests: Added three serial tests covering per-rebuild recreation (program/typeChecker) and legacy default behavior; ensured emitted files are cleaned up.
…sOnRebuild\n\nUse === true instead of Boolean(...) to avoid enabling the option for truthy strings in JS configs. Refs PR #1923 review by @shellscape.
There was a problem hiding this comment.
Overall, the implementation looks correct and backward compatible. Minor maintainability opportunities remain: small duplication in mergeTransformers and duplicated scaffolding in the new tests. A tiny comment tweak in the watch host would better reflect getter availability beyond watch mode. No correctness or performance issues observed in the modified code.
Additional notes (2)
-
Maintainability |
packages/typescript/src/customTransformers.ts:41-50
Good change passing a getter and delaying initialization. There’s still minor duplication when computingcurrentProgram/currentTypeCheckeracross branches, which makes the flow slightly harder to scan. A tiny helper to centralize lazy init would reduce repetition without behavior changes. -
Maintainability |
packages/typescript/test/test.js:1698-1924
The three new tests are solid, but they duplicate watch scaffolding (fake TS builder emitting twice), pass‑through transform setup, and emitted-file cleanup. Extracting tiny helpers will reduce repetition and make future changes safer.
Summary of changes
- Docs: Clarified Program-scoped transformer factory signature to include an optional
getProgramgetter; fixed typos (“bellow” → “below”, “it's” → “its”); added a new watch-mode note and documented therecreateTransformersOnRebuildoption with examples. - Runtime:
mergeTransformersnow passes agetProgramgetter toprogram-scoped factories and lazily initializesprogram/typeChecker. Watch host optionally resets cached transformers per rebuild whenrecreateTransformersOnRebuildis enabled (defaults to legacy reuse). - Options: Plumbed
recreateTransformersOnRebuildthrough plugin options and watch host; normalized strictly via=== trueto avoid truthy pitfalls. - Tests: Added three serial tests to validate per-rebuild recreation for both Program and TypeChecker, and a legacy-behavior regression; ensured emitted fixture cleanup in
finally. - Types: Updated type defs and JSDoc for the new getter parameter and the new plugin option.
Adds a small, additive API to @rollup/plugin-typescript so custom transformers can always access the current TypeScript Program during watch-mode rebuilds.
Changes
getProgram?: () => Programsecond parameter toProgramTransformerFactory.factory(program, getProgram)so factories can fetch the latest Program across rebuilds.getProgramgetter to program-scoped transformer factories and rebuild custom transformers after eachafterProgramCreate(watch) so both function-style and object-style transformers see the current Program/TypeChecker.getProgramparameter and watch-mode behavior in the transformers section.getProgram()returns the latest Program.No breaking changes: the new parameter is optional and existing usage continues to work unchanged.
Verification
@rollup/plugin-typescriptonly; other packages were not touched.Self review
mergeTransformersto lazily initializeprogram/typeCheckeronly when needed.Closes #1892.