Skip to content

Conversation

@toomuchdesign
Copy link

@toomuchdesign toomuchdesign commented May 26, 2025

Motivation

TS config isolatedModules disables type check. #4859

Solution (just a RFC)

  • ConfigSet: force diagnostics to false when isolatedModules is true
  • tsTranspileModule: perform type check when reportDiagnostics is true

Demo

I've temporarily tested the change in this repro and it seems to work.

Does this PR introduce a breaking change?

  • Yes :)
  • No :)

Technically yes (in case of isolatedModules and diagnostics === true). But it could be interpreted as a fix.

Other information

This PR is an attempt to implement the suggestions kindly provided in this thread: #4859

I updated ConfigSet and tsTranspileModule unit tests but I'm wondering whether there other higher level tests we might extend to cover this scenario.

Summary by CodeRabbit

  • Refactor

    • Improved clarity and maintainability of configuration setup and diagnostics handling.
    • Standardized test setup and teardown for better isolation and readability.
  • New Features

    • Enhanced diagnostics reporting during TypeScript transpilation to include explicit type-checking errors.
    • Added new tests covering diagnostics behavior with various configuration options.
  • Tests

    • Expanded and clarified test cases related to diagnostics, including scenarios for type-checking and compiler options.

@toomuchdesign toomuchdesign requested a review from kulshekhar as a code owner May 26, 2025 10:58
@coderabbitai
Copy link

coderabbitai bot commented May 26, 2025

Walkthrough

The changes refactor test setup and teardown for diagnostics and configuration logic, add new and more precise diagnostics-related test cases, and reorganize the configuration initialization sequence for clarity. The TypeScript transpile module now explicitly performs type-checking and includes type-check diagnostics in its output, improving diagnostic coverage.

Changes

File(s) Change Summary
src/legacy/config/config-set.spec.ts Refactored test setup/teardown with hooks; added parameterized tests for diagnostics and isolatedModules logic.
src/legacy/config/config-set.ts Reorganized and consolidated config and diagnostics setup; clarified control flow and logging.
src/transpilers/typescript/transpile-module.spec.ts Clarified test descriptions; added test for type-check diagnostics when reportDiagnostics is enabled.
src/transpilers/typescript/transpile-module.ts Added explicit type-checking step; appended type-check diagnostics to diagnostics array in transpileWorker.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant transpileWorker
    participant ts.Program

    Caller->>transpileWorker: Call with input, options, and diagnostics enabled
    transpileWorker->>ts.Program: Create for syntactic analysis
    ts.Program-->>transpileWorker: Return syntactic diagnostics
    transpileWorker->>ts.Program: Create with noEmit: true for type-checking
    ts.Program-->>transpileWorker: Return type-check diagnostics
    transpileWorker->>Caller: Return output with combined diagnostics
Loading

Possibly related issues

Poem

A bunny hopped through code so neat,
Tidying tests with careful feet.
Now diagnostics shine with light,
Type errors caught in plainest sight!
With configs clear and logic sound,
The burrow’s bugs are tightly bound.
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-26T10_59_57_197Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment on lines -800 to -802
const findConfig = jest.spyOn(ts, 'findConfigFile')
const readConfig = jest.spyOn(ts, 'readConfigFile')
const parseConfig = jest.spyOn(ts, 'parseJsonConfigFileContent')
Copy link
Author

Choose a reason for hiding this comment

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

Addressing undesired side effects between tests.

})

afterEach(() => {
findConfig.mockClear()
Copy link
Author

Choose a reason for hiding this comment

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

Already restoring mocks above

spyFindTsConfigFile.mockRestore()
})

it('should show warning log when isolatedModules: true is used in transformer options when a tsconfig file path for tests exists', () => {
Copy link
Author

Choose a reason for hiding this comment

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

This test suffered from the side effect described above

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/transpilers/typescript/transpile-module.spec.ts (2)

352-359: Make the assertion resilient to future TypeScript wording changes

messageText is compared for full string equality. TypeScript occasionally tweaks diagnostic strings (punctuation / wording / quotes) between patch releases which can lead to brittle test failures even though the behaviour is still correct.
A looser stringContaining (or checking only code) is usually sufficient.

-expect(result.diagnostics).toContainEqual(
-  expect.objectContaining({
-    code: 5109,
-    messageText:
-      "Option 'moduleResolution' must be set to 'Node16' (or left unspecified) when option 'module' is set to 'Node16'.",
-  }),
-)
+expect(result.diagnostics).toEqual(
+  expect.arrayContaining([
+    expect.objectContaining({
+      code: 5109,
+      messageText: expect.stringContaining('moduleResolution'),
+    }),
+  ]),
+)

388-393: Reduce brittleness of the type-error expectation

Same concern as above: asserting the full diagnostic text ties the test to one TS version. Matching the error code (and maybe a substring) is enough to prove the feature works.

-  expect(result.diagnostics).toContainEqual(
-    expect.objectContaining({
-      code: 2322,
-      messageText: "Type 'number' is not assignable to type 'string'.",
-    }),
-  )
+  expect(result.diagnostics).toEqual(
+    expect.arrayContaining([
+      expect.objectContaining({
+        code: 2322,
+        messageText: expect.stringContaining('not assignable'),
+      }),
+    ]),
+  )
src/legacy/config/config-set.ts (1)

301-315: Simplify the ternary – remove the “useless” boolean literal branch

Static-analysis is right here: this.parsedTsConfig.options.isolatedModules === true ? false : true can be replaced with !this.parsedTsConfig.options.isolatedModules, which is shorter and clearer.

-    const diagnosticsOpt =
-      options.diagnostics !== undefined
-        ? options.diagnostics
-        : /**
-         * Ensure that diagnostics always has false value when isolatedModules: true
-         * when users don't provide the value for it in Jest config.
-         * @TODO This behavior will be switched oppositely in the next major version.
-         * https://github.com/kulshekhar/ts-jest/issues/4859
-         */
-        this.parsedTsConfig.options.isolatedModules === true
-        ? false
-        : true
+    const diagnosticsOpt =
+      options.diagnostics !== undefined
+        ? options.diagnostics
+        : /**
+           * Default: disable diagnostics when `isolatedModules` is true.
+           * @TODO Switch behaviour in next major – see https://github.com/kulshekhar/ts-jest/issues/4859
+           */
+          !this.parsedTsConfig.options.isolatedModules
🧰 Tools
🪛 Biome (1.9.4)

[error] 311-313: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 132e5dd and 52a0a4d.

📒 Files selected for processing (4)
  • src/legacy/config/config-set.spec.ts (4 hunks)
  • src/legacy/config/config-set.ts (3 hunks)
  • src/transpilers/typescript/transpile-module.spec.ts (2 hunks)
  • src/transpilers/typescript/transpile-module.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/legacy/config/config-set.ts

[error] 311-313: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (4)
src/transpilers/typescript/transpile-module.ts (1)

175-184:

Details

❌ Incorrect review comment

Avoid building a second Program – reuse the one you already have

Creating a brand-new ts.Program for type-checking:

  1. Duplicates work the first program has already done.
  2. Increases memory/time footprint for every file transpiled.
  3. Risks double-reporting syntactic diagnostics.

All semantic diagnostics can be queried from the existing program:

-    // Explicitly perform typecheck, if required
-    const typeCheckProgram = ts.createProgram(inputs, {
-      ...transpileOptions.compilerOptions,
-      noEmit: true, // We're only type-checking
-    })
-    diagnostics.push(...ts.getPreEmitDiagnostics(typeCheckProgram))
+    // Get semantic / global diagnostics from the existing program
+    diagnostics.push(...program.getSemanticDiagnostics())
+    diagnostics.push(...program.getGlobalDiagnostics())

If the intent is specifically to bypass the isolatedModules limitation, consider cloning options with { isolatedModules: false } before building the first Program instead of paying the cost twice.

Would you like a follow-up PR benchmark (or proof-of-concept) showing the performance delta between both approaches?


🏁 Script executed:

#!/bin/bash
set -e

# Locate the transpile-module.ts file
file=$(fd --hidden --absolute-path "transpile-module.ts")
echo "Found file: $file"

# Display context around where 'program' and 'typeCheckProgram' are defined
sed -n '1,200p' "$file"
echo "----"
sed -n '150,260p' "$file"

Length of output: 9195


Ignore the “reuse the existing Program” suggestion

The primary program is built with a custom CompilerHost that only loads the in-memory source file (and a barebones lib). A second ts.Program using the default TS host is required to resolve modules on disk and perform a full type‐check of imports and project files.

Likely an incorrect or invalid review comment.

src/legacy/config/config-set.spec.ts (3)

800-815: Excellent refactoring for better test isolation!

The migration from top-level spy creation with mockClear() to proper beforeEach/afterEach hooks significantly improves test isolation and follows Jest best practices. This ensures each test starts with fresh spies and prevents potential test interference.


1133-1173: Comprehensive test coverage for the core PR functionality!

This new test section excellently covers the interaction between diagnostics and isolatedModules settings, which is the core functionality addressed in this PR. The parameterized test approach efficiently validates all key scenarios:

  • Default behavior when isolatedModules is true (diagnostics ignored)
  • Override behavior when diagnostics is explicitly set to true
  • Standard behavior in other combinations

The use of @ts-expect-error to test the private method _shouldIgnoreDiagnosticsForFile is appropriate for this testing context.


1177-1185: Consistent application of improved spy management!

This change maintains consistency with the earlier spy setup improvements, applying the same beforeEach/afterEach pattern for proper test isolation in the isolatedModules test section.

* @internal
*/
private _setupConfigSet(options: TsJestTransformOptions['transformerConfig']): void {
// tsconfig
Copy link
Author

Choose a reason for hiding this comment

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

Moved this block up to make parsedTsConfig available at diagnostic handling time

this.raiseDiagnostics(this.parsedTsConfig.errors, configFilePath)

this.logger.debug({ tsconfig: this.parsedTsConfig }, 'normalized typescript config via ts-jest option')
this.logger.debug({ diagnostics: this._diagnostics }, 'normalized diagnostics config via ts-jest option')
Copy link
Author

Choose a reason for hiding this comment

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

This log was previously executed in the // tsconfig block.

diagnostics.push(...program.getSyntacticDiagnostics(sourceFile))

// Explicitly perform typecheck, if required
const typeCheckProgram = ts.createProgram(inputs, {
Copy link
Author

Choose a reason for hiding this comment

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

Please double check this implementation is solid enough :)

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.

1 participant