Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Missing docs for SyncTransformer#createTransformer #12403

Closed
fatso83 opened this issue Feb 16, 2022 · 7 comments
Closed

[Bug]: Missing docs for SyncTransformer#createTransformer #12403

fatso83 opened this issue Feb 16, 2022 · 7 comments

Comments

@fatso83
Copy link
Contributor

fatso83 commented Feb 16, 2022

Version

27.5.1

Steps to reproduce

I created a minimal repro repo before figuring out what was wrong, but the essence is this:

Create a debug-transformer.js that wraps another, like this wrapping babel-jest:

const debug = require('debug')('jest-debug-transform')
const babelJest = require('babel-jest').default
const originalProcess = babelJest.process
const originalProcessAsync = babelJest.processAsync

module.exports  = {
  ...babelJest, // <-- source of confusion: the presence of `createTransformer` will void the effect of implemented methods
  process(src, filename, options) {
    debug(`Transforming '${filename}'`)
    return originalProcess(src, filename, options)
  },
  processAsync(src, filename, options) {
    debug(`Async Transforming '${filename}'`)
    return originalProcessAsync(src, filename, options)
  },
}

and use it in the jest config:

  transform: { '\\.[jt]sx?$': [ './debug-transformer.js', {} ]  }

Now, when running DEBUG="jest-debug*" npx jest --no-cache I would have assumed it would print messages such as "Transforming './src/myfile.test.tsx'", but it never does. Unless I manually add a line where I delete module.exports.createTransformer, that is.

Expected behavior

I would have expected it to use the implemented process and processAsync methods when implemented, as the effects of createTransformer is not documented.

Actual behavior

createTransformer() is being called if present. This is not documented in the transform docs, and makes the interface not really reflect reality. There is no point in implementing process if it is never called.

Additional context

I have been looking into/debugging code transformation related issues in Jest for the last day and a recurring theme is that the SyncTransformer#createTransformer method is a constant source of surprise and it is not really documented why it exists.

The SyncTransformer interface only has a single field one has to implement: process. But it seems that if one implements createTransformer those other methods will not be used: instead Jest seems to create a new transformer using createTransformer, which caused my to lose a few hairs until I figured what was going on. This behaviour does not seem to be documented.

The babel-jest source for Jest 27.

Environment

System:
    OS: macOS 12.2
    CPU: (8) arm64 Apple M1
  Binaries:
    Node: 17.4.0 - /usr/local/bin/node
    npm: 8.3.1 - /usr/local/bin/npm
  npmPackages:
    jest: ^27.5.1 => 27.5.1
@SimenB
Copy link
Member

SimenB commented Feb 16, 2022

Yeah, this is so that you can have transformer config in your jest config, and it is passed at runtime. You should call createTransformer yourself if you don't want this behaviour (which is a named export as of Jest 28).

This of course should be documented though. Any help with that would be highly appreciated 😀

@fatso83
Copy link
Contributor Author

fatso83 commented Feb 16, 2022

I have no issues in documenting it, I was just not sure what to write, but now I have deep dived into ScriptTransformer and its likes, so I understand a bit more 🤓

Looking into the validation errors and associated tests, it seems the interfaces should be updated as well?

This is what I seem to understand is lacking documentation (as explicit docs or via typing):

  • There is nothing magical about process. The code actually requires that one implements createTransformer or processAsync or process - in that order of precedence. Supplying all three makes no sense.
  • If you implement createTransformer then implementing any of the other methods makes no sense (to me), as they will not be used. To me this says createTransformer does not belong inside of the Transformer interfaces.
  • If you implement processAsync then process will never be used. To me that says that these fields should never co-exist.

Suggested changes

  • remove createTransformer from the Transformer interface
  • Remove process from AsyncTransformer and processAsync from SyncTransformer
  • make the interface to implement look like type TransformerOrTransformerCreator = Transformer | { createTransformer: TransformerCreator }

The last point would potentially be breaking, so for backwards compatibility could be type TransformerAndOrTransformerCreator = Transformer & { createTransformer?: TransformerCreator } | { createTransformer: TransformerCreator } (head-coded those, so not sure if compiles)

👍 / 👎 ?

@SimenB
Copy link
Member

SimenB commented Feb 16, 2022

There is nothing magical about process. The code actually requires that one implements createTransformer or processAsync or process - in that order of precedence. Supplying all three makes no sense.

Supplying createTransformer makes the other 2 redundant, yes, as they won't be used.

However, we have both sync (process) and async (processAsync) code transformation, which both can be provided. require will always use process, and import will use processAsync if it exists, otherwise fall back to process. So if you use import exclusively you don't need process, but in most cases supplying both makes sense. Jest transpiles on demand rather than ahead of time, so the sync one needs to exist.

If you implement createTransformer then implementing any of the other methods makes no sense (to me), as they will not be used.

Ish, createTransformer needs to return process and/or processAsync (plus the cache key variants), but it doesn't really make sense to implement them at the same level as createTransformer.

To me this says createTransformer does not belong inside of the Transformer interfaces.

Agreed!

If you implement processAsync then process will never be used. To me that says that these fields should never co-exist.

As mentioned, process is used by require, processAsync by import.

remove createTransformer from the Transformer interface

👍

Remove process from AsyncTransformer and processAsync from SyncTransformer

👎

make the interface to implement look like type TransformerOrTransformerCreator = Transformer | { createTransformer: TransformerCreator }

👍

The last point would potentially be breaking

We're currently releasing alphas for Jest 28, so breaking changes are fine 🙂

fatso83 added a commit to fatso83/jest that referenced this issue Feb 16, 2022
fatso83 added a commit to fatso83/jest that referenced this issue Feb 16, 2022
Basically conclusions from a discussion in jestjs#12403
fatso83 added a commit to fatso83/jest that referenced this issue Feb 25, 2022
fatso83 added a commit to fatso83/jest that referenced this issue Feb 25, 2022
Basically conclusions from a discussion in jestjs#12403
@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Mar 18, 2022
@SimenB
Copy link
Member

SimenB commented Apr 4, 2022

I think this was fixed in #12407

@SimenB SimenB closed this as completed Apr 4, 2022
@SimenB
Copy link
Member

SimenB commented Apr 5, 2022

@github-actions
Copy link

github-actions bot commented May 6, 2022

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants