Skip to content

feat(typescript): expose latest Program to transformers in watch mode#1923

Merged
shellscape merged 8 commits intomasterfrom
ai-1892-rollup-plugin-typescript-support-accessing
Oct 23, 2025
Merged

feat(typescript): expose latest Program to transformers in watch mode#1923
shellscape merged 8 commits intomasterfrom
ai-1892-rollup-plugin-typescript-support-accessing

Conversation

@charliecreates
Copy link
Contributor

Adds a small, additive API to @rollup/plugin-typescript so custom transformers can always access the current TypeScript Program during watch-mode rebuilds.

Changes

  • Types: add an optional getProgram?: () => Program second parameter to ProgramTransformerFactory.factory(program, getProgram) so factories can fetch the latest Program across rebuilds.
  • Runtime: pass a getProgram getter to program-scoped transformer factories and rebuild custom transformers after each afterProgramCreate (watch) so both function-style and object-style transformers see the current Program/TypeChecker.
  • Docs: document the new getProgram parameter and watch-mode behavior in the transformers section.
  • Tests: add a watch-like regression that simulates two rebuilds and asserts getProgram() returns the latest Program.

No breaking changes: the new parameter is optional and existing usage continues to work unchanged.

Verification

# Package build: success
$ pnpm --filter @rollup/plugin-typescript build

# Tests (AVA, serial): 88 passed, 2 skipped
$ pnpm --filter @rollup/plugin-typescript test -- --verbose --serial

# TypeScript: no errors
$ pnpm --filter @rollup/plugin-typescript run test:ts

# ESLint (scoped to package): 0 errors, 2 warnings
$ pnpm exec eslint packages/typescript --ext .js,.ts,.mjs
  • Scoped checks to @rollup/plugin-typescript only; other packages were not touched.
  • Warnings are preexisting in the package (unchanged by this PR).
Self review
  • Refactored mergeTransformers to lazily initialize program/typeChecker only when needed.
  • Closed the bundle in the new test to avoid leaking resources.
  • Skipped enhancing the README example further (deferred access pattern) to keep it concise; happy to adjust if reviewers prefer that style.

Closes #1892.

…\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
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 leaves fixtures/transformers/main.js on disk after running. You clean it up at the start, but tidying it in finally prevents 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 unnecessary getTypeChecker() work for program-only factories. There’s a small duplication in computing currentProgram/currentTypeChecker across branches; a tiny helper (e.g., ensureProgram() / ensureTypeChecker()) could improve readability without changing behavior.

Summary of changes
  • Types: added an optional getProgram?: () => Program second parameter to ProgramTransformerFactory.factory(program, getProgram) in types/index.d.ts.
  • Runtime: in customTransformers.ts, pass a getProgram getter to program-scoped transformer factories and lazily initialize program/typeChecker. In watchProgram.ts, invalidate cached transformers on every afterProgramCreate to rebuild them per watch cycle.
  • Docs: updated README to document the new getProgram parameter 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 latest Program.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 22, 2025 18:29
@charliecreates charliecreates bot requested a review from CharlieHelps October 22, 2025 23:23
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 calling getProgram() immediately in the factory, which can lead readers to capture a snapshot rather than deferring to the latest Program. Since the primary benefit of getProgram is 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 computing currentProgram/currentTypeChecker across 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?: () => Program parameter for program-scoped transformer factories and adds a watch-mode note. Fixed a minor grammar issue (“it's” → “its”).
  • Runtime: mergeTransformers now lazily initializes program/typeChecker and passes a getProgram getter to program-based factories. In watch mode, afterProgramCreate clears cached transformers to rebuild them per cycle.
  • Types: ProgramTransformerFactory.factory signature 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 program and typeChecker track the latest values; tests also tidy emitted files in finally.

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].

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 22, 2025 23:26
…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
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Docs currently imply getProgram is a watch-only concern; the runtime always provides it. Clarifying this in README and type JSDoc will prevent misuse and conditional code paths.
  • mergeTransformers is 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?: () => Program for program-scoped transformer factories, adjusted examples, fixed grammar, and added a watch-mode note.
  • Runtime (customTransformers.ts): Lazily initialize program/typeChecker. Pass a getProgram getter to program-type transformer factories so they can fetch the latest Program in watch mode.
  • Runtime (watchProgram.ts): In watch mode, reset cached transformers on each afterProgramCreate so factories are recreated per rebuild; replaced undefined with void 0 per 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.factory signature to accept (program: Program, getProgram?: () => Program) with JSDoc.

Comment on lines 31 to 36
/**
* 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>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 22, 2025 23:47
… 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
@charliecreates charliecreates bot requested a review from CharlieHelps October 23, 2025 00:50
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 computing currentProgram/currentTypeChecker across 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; enabling recreateTransformersOnRebuild changes 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 mergeTransformers to pass a getProgram getter and to lazily initialize program/typeChecker.
  • In watch mode, optionally invalidate cached transformers on each afterProgramCreate to 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.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 23, 2025 00:55
@shellscape
Copy link
Collaborator

@CharlieHelps please make sure the README is correct for the latest commit

@charliecreates
Copy link
Contributor Author

charliecreates bot commented Oct 23, 2025

Expand this to see my work.
  • Searched packages/typescript/README.md and found 4 instances of the typo “bellow” (lines 145, 146, 225, 226) in the Supported transformer factories lists for TransformerFactory/CustomTransformerFactory (search summary: 4 matches).
  • Drafted README edits: “bellow”→“below”, corrected “it’s”→“its” in the TypeChecker example comment, and clarified the getProgram note in the type: 'program' factory section referencing recreateTransformersOnRebuild and Program.
  • Attempted to apply the doc patch but the change hit two identical blocks and the automated apply conflicted on duplicate context; isolated exact ranges 138–152 and 218–234 to target the updates safely.
  • Re-checked the file after the failed apply; the 4 “bellow” occurrences are still present, confirming the changes haven’t been applied yet.

@charliecreates charliecreates bot requested a review from CharlieHelps October 23, 2025 02:57
@charliecreates
Copy link
Contributor Author

Updated the README for @rollup/plugin-typescript to match the latest behavior on this branch.

  • Clarified that the optional getProgram getter is provided in all modes; in watch mode it reflects the latest Program only when recreateTransformersOnRebuild is enabled (default remains legacy: false).
  • Fixed minor typos/grammar in the transformer docs and examples.

Commit: 7b1f643

What changed
  • Supported transformer factories (program-scoped):
    • Wording now states: “An optional getProgram getter is provided in all modes. In non‑watch it returns the same Program… In watch mode, when recreateTransformersOnRebuild is enabled, the getter reflects the latest Program across rebuilds; otherwise it refers to the initial Program.”
  • Example updated to prefer deferring getProgram() usage to transformation time.
  • Typo fixes:
    • “bellow” → “below”
    • “it’s factory” → “its factory”

Re-requested review from @shellscape and @CharlieHelps.

Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 initializing currentProgram/currentTypeChecker across 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 optional getProgram getter and added a new “Watch mode” note documenting recreateTransformersOnRebuild.
  • Runtime: mergeTransformers now passes a getProgram getter to program-scoped factories and lazily initializes program/typeChecker using currentProgram/currentTypeChecker.
  • Options: Plumbed a new recreateTransformersOnRebuild option through getPluginOptions, index.ts, and the watch host.
  • Watch host: Optionally resets cached transformers on each rebuild when recreateTransformersOnRebuild is true.
  • Types: Updated ProgramTransformerFactory.factory signature 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.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 23, 2025 03:03
…sOnRebuild\n\nUse === true instead of Boolean(...) to avoid enabling the option for truthy strings in JS configs. Refs PR #1923 review by @shellscape.
@charliecreates charliecreates bot requested a review from CharlieHelps October 23, 2025 03:15
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 computing currentProgram/currentTypeChecker across 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 getProgram getter; fixed typos (“bellow” → “below”, “it's” → “its”); added a new watch-mode note and documented the recreateTransformersOnRebuild option with examples.
  • Runtime: mergeTransformers now passes a getProgram getter to program-scoped factories and lazily initializes program/typeChecker. Watch host optionally resets cached transformers per rebuild when recreateTransformersOnRebuild is enabled (defaults to legacy reuse).
  • Options: Plumbed recreateTransformersOnRebuild through plugin options and watch host; normalized strictly via === true to 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.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 23, 2025 03:18
@shellscape shellscape merged commit b6f027b into master Oct 23, 2025
9 checks passed
@shellscape shellscape deleted the ai-1892-rollup-plugin-typescript-support-accessing branch October 23, 2025 13:27
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.

@rollup/plugin-typescript Support accessing the latest Program in transformers when using watch mode

2 participants