diff --git a/CHANGELOG.md b/CHANGELOG.md index d23db1531297..5d980e89930b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - `[@jest/transform]`: New package extracted from `jest-runtime` ([#7915](https://github.com/facebook/jest/pull/7915)) - `[babel-plugin-jest-hoist]`: Migrate to TypeScript ([#7898](https://github.com/facebook/jest/pull/7898)) - `[@jest/core]` Create new package, which is `jest-cli` minus `yargs` and `prompts` ([#7696](https://github.com/facebook/jest/pull/7696)) +- `[@jest/transform]`: Migrate to TypeScript ([#7918](https://github.com/facebook/jest/pull/7918)) ### Performance diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index ea8a5bde75cd..346a948fa280 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -27,7 +27,7 @@ const createTransformer = ( ): Transform.Transformer => { options = { ...options, - // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/32955 + // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/33118 caller: { name: 'babel-jest', supportsStaticESM: false, diff --git a/packages/jest-transform/package.json b/packages/jest-transform/package.json index 345321986283..2fb3d06fa0cd 100644 --- a/packages/jest-transform/package.json +++ b/packages/jest-transform/package.json @@ -10,6 +10,7 @@ "main": "build/index.js", "dependencies": { "@babel/core": "^7.1.0", + "@jest/types": "^24.1.0", "babel-plugin-istanbul": "^5.1.0", "chalk": "^2.0.1", "convert-source-map": "^1.4.0", @@ -26,6 +27,7 @@ "devDependencies": { "@types/babel__core": "^7.0.4", "@types/convert-source-map": "^1.5.1", + "@types/fast-json-stable-stringify": "^2.0.0", "@types/graceful-fs": "^4.1.2", "@types/micromatch": "^3.1.0", "@types/write-file-atomic": "^2.1.1" diff --git a/packages/jest-transform/src/ScriptTransformer.js b/packages/jest-transform/src/ScriptTransformer.ts similarity index 82% rename from packages/jest-transform/src/ScriptTransformer.js rename to packages/jest-transform/src/ScriptTransformer.ts index 44b5959fb39e..b91f1955db77 100644 --- a/packages/jest-transform/src/ScriptTransformer.js +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -3,57 +3,54 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Path, ProjectConfig} from 'types/Config'; -import type { - Transformer, - TransformedSource, - TransformResult, -} from 'types/Transform'; -import type {ErrorWithCode} from 'types/Errors'; -import type {Options} from './types'; - import crypto from 'crypto'; import path from 'path'; import vm from 'vm'; +import {Config, Transform} from '@jest/types'; import {createDirectory} from 'jest-util'; import fs from 'graceful-fs'; import {transformSync as babelTransform} from '@babel/core'; +// @ts-ignore: should just be `require.resolve`, but the tests mess that up import babelPluginIstanbul from 'babel-plugin-istanbul'; import convertSourceMap from 'convert-source-map'; import HasteMap from 'jest-haste-map'; import stableStringify from 'fast-json-stable-stringify'; import slash from 'slash'; -import {version as VERSION} from '../package.json'; -import shouldInstrument from './shouldInstrument'; import writeFileAtomic from 'write-file-atomic'; import {sync as realpath} from 'realpath-native'; +import {Options} from './types'; +import shouldInstrument from './shouldInstrument'; import enhanceUnexpectedTokenMessage from './enhanceUnexpectedTokenMessage'; -type ProjectCache = {| - configString: string, - ignorePatternsRegExp: ?RegExp, - transformedFiles: Map, -|}; +type ProjectCache = { + configString: string; + ignorePatternsRegExp: RegExp | null; + transformedFiles: Map; +}; + +// Use `require` to avoid TS rootDir +const {version: VERSION} = require('../package.json'); // This data structure is used to avoid recalculating some data every time that // we need to transform a file. Since ScriptTransformer is instantiated for each // file we need to keep this object in the local scope of this module. -const projectCaches: WeakMap = new WeakMap(); +const projectCaches: WeakMap< + Config.ProjectConfig, + ProjectCache +> = new WeakMap(); // To reset the cache for specific changesets (rather than package version). const CACHE_VERSION = '1'; export default class ScriptTransformer { static EVAL_RESULT_VARIABLE: string; - _cache: ProjectCache; - _config: ProjectConfig; - _transformCache: Map; + private _cache: ProjectCache; + private _config: Config.ProjectConfig; + private _transformCache: Map; - constructor(config: ProjectConfig) { + constructor(config: Config.ProjectConfig) { this._config = config; this._transformCache = new Map(); @@ -72,7 +69,11 @@ export default class ScriptTransformer { this._cache = projectCache; } - _getCacheKey(fileData: string, filename: Path, instrument: boolean): string { + private _getCacheKey( + fileData: string, + filename: Config.Path, + instrument: boolean, + ): string { const configString = this._cache.configString; const transformer = this._getTransformer(filename); @@ -99,11 +100,12 @@ export default class ScriptTransformer { } } - _getFileCachePath( - filename: Path, + private _getFileCachePath( + filename: Config.Path, content: string, instrument: boolean, - ): Path { + ): Config.Path { + // @ts-ignore: not properly exported (needs ESM) const baseCacheDir = HasteMap.getCacheFilePath( this._config.cacheDirectory, 'jest-transform-cache-' + this._config.name, @@ -124,7 +126,7 @@ export default class ScriptTransformer { return cachePath; } - _getTransformPath(filename: Path) { + private _getTransformPath(filename: Config.Path) { for (let i = 0; i < this._config.transform.length; i++) { if (new RegExp(this._config.transform[i][0]).test(filename)) { return this._config.transform[i][1]; @@ -133,8 +135,8 @@ export default class ScriptTransformer { return null; } - _getTransformer(filename: Path) { - let transform: ?Transformer; + private _getTransformer(filename: Config.Path) { + let transform: Transform.Transformer | null = null; if (!this._config.transform || !this._config.transform.length) { return null; } @@ -146,8 +148,7 @@ export default class ScriptTransformer { return transformer; } - // $FlowFixMe - transform = (require(transformPath): Transformer); + transform = require(transformPath) as Transform.Transformer; if (typeof transform.createTransformer === 'function') { transform = transform.createTransformer(); } @@ -161,10 +162,11 @@ export default class ScriptTransformer { return transform; } - _instrumentFile(filename: Path, content: string): string { + private _instrumentFile(filename: Config.Path, content: string): string { const result = babelTransform(content, { auxiliaryCommentBefore: ' istanbul ignore next ', babelrc: false, + // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/33118 caller: { name: '@jest/transform', supportsStaticESM: false, @@ -185,10 +187,18 @@ export default class ScriptTransformer { ], }); - return result ? result.code : content; + if (result) { + const {code} = result; + + if (code) { + return code; + } + } + + return content; } - _getRealPath(filepath: Path): Path { + private _getRealPath(filepath: Config.Path): Config.Path { try { return realpath(filepath) || filepath; } catch (err) { @@ -198,15 +208,15 @@ export default class ScriptTransformer { // We don't want to expose transformers to the outside - this function is just // to warm up `this._transformCache` - preloadTransformer(filepath: Path): void { + preloadTransformer(filepath: Config.Path): void { this._getTransformer(filepath); } - transformSource(filepath: Path, content: string, instrument: boolean) { + transformSource(filepath: Config.Path, content: string, instrument: boolean) { const filename = this._getRealPath(filepath); const transform = this._getTransformer(filename); const cacheFilePath = this._getFileCachePath(filename, content, instrument); - let sourceMapPath = cacheFilePath + '.map'; + let sourceMapPath: Config.Path | null = cacheFilePath + '.map'; // Ignore cache if `config.cache` is set (--no-cache) let code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null; @@ -233,7 +243,7 @@ export default class ScriptTransformer { }; } - let transformed: TransformedSource = { + let transformed: Transform.TransformedSource = { code: content, map: null, }; @@ -290,12 +300,12 @@ export default class ScriptTransformer { }; } - _transformAndBuildScript( - filename: Path, - options: ?Options, + private _transformAndBuildScript( + filename: Config.Path, + options: Options | null, instrument: boolean, fileSource?: string, - ): TransformResult { + ): Transform.TransformResult { const isInternalModule = !!(options && options.isInternalModule); const isCoreModule = !!(options && options.isCoreModule); const content = stripShebang( @@ -303,7 +313,7 @@ export default class ScriptTransformer { ); let wrappedCode: string; - let sourceMapPath: ?string = null; + let sourceMapPath: string | null = null; let mapCoverage = false; const willTransform = @@ -354,13 +364,13 @@ export default class ScriptTransformer { } transform( - filename: Path, + filename: Config.Path, options: Options, fileSource?: string, - ): TransformResult { + ): Transform.TransformResult | string { let scriptCacheKey = null; let instrument = false; - let result = ''; + let result: Transform.TransformResult | string | undefined = ''; if (!options.isCoreModule) { instrument = shouldInstrument(filename, options, this._config); @@ -386,7 +396,7 @@ export default class ScriptTransformer { return result; } - _shouldTransform(filename: Path): boolean { + private _shouldTransform(filename: Config.Path): boolean { const ignoreRegexp = this._cache.ignorePatternsRegExp; const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false; @@ -396,13 +406,13 @@ export default class ScriptTransformer { } } -const removeFile = (path: Path) => { +const removeFile = (path: Config.Path) => { try { fs.unlinkSync(path); } catch (e) {} }; -const stripShebang = content => { +const stripShebang = (content: string) => { // If the file data starts with a shebang remove it. Leaves the empty line // to keep stack trace line numbers correct. if (content.startsWith('#!')) { @@ -419,7 +429,7 @@ const stripShebang = content => { * it right away. This is not a great system, because source map cache file * could get corrupted, out-of-sync, etc. */ -function writeCodeCacheFile(cachePath: Path, code: string) { +function writeCodeCacheFile(cachePath: Config.Path, code: string) { const checksum = crypto .createHash('md5') .update(code) @@ -433,7 +443,7 @@ function writeCodeCacheFile(cachePath: Path, code: string) { * could happen if an older version of `jest-runtime` writes non-atomically to * the same cache, for example. */ -function readCodeCacheFile(cachePath: Path): ?string { +function readCodeCacheFile(cachePath: Config.Path): string | null { const content = readCacheFile(cachePath); if (content == null) { return null; @@ -455,7 +465,7 @@ function readCodeCacheFile(cachePath: Path): ?string { * two processes to write to the same file at the same time. It also reduces * the risk of reading a file that's being overwritten at the same time. */ -const writeCacheFile = (cachePath: Path, fileData: string) => { +const writeCacheFile = (cachePath: Config.Path, fileData: string) => { try { writeFileAtomic.sync(cachePath, fileData, {encoding: 'utf8'}); } catch (e) { @@ -479,12 +489,15 @@ const writeCacheFile = (cachePath: Path, fileData: string) => { * If the target file exists we can be reasonably sure another process has * legitimately won a cache write race and ignore the error. */ -const cacheWriteErrorSafeToIgnore = (e: ErrorWithCode, cachePath: Path) => +const cacheWriteErrorSafeToIgnore = ( + e: Error & {code: string}, + cachePath: Config.Path, +) => process.platform === 'win32' && e.code === 'EPERM' && fs.existsSync(cachePath); -const readCacheFile = (cachePath: Path): ?string => { +const readCacheFile = (cachePath: Config.Path): string | null => { if (!fs.existsSync(cachePath)) { return null; } @@ -510,12 +523,14 @@ const readCacheFile = (cachePath: Path): ?string => { return fileData; }; -const getScriptCacheKey = (filename, instrument: boolean) => { +const getScriptCacheKey = (filename: Config.Path, instrument: boolean) => { const mtime = fs.statSync(filename).mtime; return filename + '_' + mtime.getTime() + (instrument ? '_instrumented' : ''); }; -const calcIgnorePatternRegexp = (config: ProjectConfig): ?RegExp => { +const calcIgnorePatternRegexp = ( + config: Config.ProjectConfig, +): RegExp | null => { if ( !config.transformIgnorePatterns || config.transformIgnorePatterns.length === 0 @@ -526,7 +541,7 @@ const calcIgnorePatternRegexp = (config: ProjectConfig): ?RegExp => { return new RegExp(config.transformIgnorePatterns.join('|')); }; -const wrap = (content, ...extras) => { +const wrap = (content: string, ...extras: Array) => { const globals = new Set([ 'module', 'exports', diff --git a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.js b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts similarity index 99% rename from packages/jest-transform/src/enhanceUnexpectedTokenMessage.js rename to packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts index af941fa7cf53..e16129fe7c95 100644 --- a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.js +++ b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts @@ -1,7 +1,5 @@ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// @flow - import chalk from 'chalk'; const DOT = ' \u2022 '; diff --git a/packages/jest-transform/src/index.js b/packages/jest-transform/src/index.ts similarity index 96% rename from packages/jest-transform/src/index.js rename to packages/jest-transform/src/index.ts index 37cbb258b92a..56881982c5b3 100644 --- a/packages/jest-transform/src/index.js +++ b/packages/jest-transform/src/index.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ export {default as ScriptTransformer} from './ScriptTransformer'; diff --git a/packages/jest-transform/src/shouldInstrument.js b/packages/jest-transform/src/shouldInstrument.ts similarity index 88% rename from packages/jest-transform/src/shouldInstrument.js rename to packages/jest-transform/src/shouldInstrument.ts index c0b869d4318b..d8585c37a8b2 100644 --- a/packages/jest-transform/src/shouldInstrument.js +++ b/packages/jest-transform/src/shouldInstrument.ts @@ -3,26 +3,23 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Path, ProjectConfig} from 'types/Config'; -import type {Options} from './types'; - import path from 'path'; +import {Config} from '@jest/types'; import {escapePathForRegex} from 'jest-regex-util'; import {replacePathSepForGlob} from 'jest-util'; import micromatch from 'micromatch'; +import {Options} from './types'; const MOCKS_PATTERN = new RegExp( escapePathForRegex(path.sep + '__mocks__' + path.sep), ); export default function shouldInstrument( - filename: Path, + filename: Config.Path, options: Options, - config: ProjectConfig, + config: Config.ProjectConfig, ): boolean { if (!options.collectCoverage) { return false; @@ -38,7 +35,7 @@ export default function shouldInstrument( if ( !config.testPathIgnorePatterns || - !config.testPathIgnorePatterns.some(pattern => filename.match(pattern)) + !config.testPathIgnorePatterns.some(pattern => !!filename.match(pattern)) ) { if ( config.testRegex && @@ -79,7 +76,7 @@ export default function shouldInstrument( if ( config.coveragePathIgnorePatterns && - config.coveragePathIgnorePatterns.some(pattern => filename.match(pattern)) + config.coveragePathIgnorePatterns.some(pattern => !!filename.match(pattern)) ) { return false; } diff --git a/packages/jest-transform/src/types.js b/packages/jest-transform/src/types.js deleted file mode 100644 index 00f577bdad80..000000000000 --- a/packages/jest-transform/src/types.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import type {Glob, Path} from 'types/Config'; - -// TODO: Pick from `GlobalConfig` -export type Options = {| - changedFiles: ?Set, - collectCoverage: boolean, - collectCoverageFrom: Array, - collectCoverageOnlyFrom: ?{[key: string]: boolean, __proto__: null}, - extraGlobals?: Array, - isCoreModule?: boolean, - isInternalModule?: boolean, -|}; diff --git a/packages/jest-transform/src/types.ts b/packages/jest-transform/src/types.ts new file mode 100644 index 000000000000..0e66ae444fbf --- /dev/null +++ b/packages/jest-transform/src/types.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {Config} from '@jest/types'; + +export type Options = Pick< + Config.GlobalConfig, + | 'collectCoverage' + | 'collectCoverageFrom' + | 'collectCoverageOnlyFrom' + | 'extraGlobals' +> & { + changedFiles: Set | undefined; + isCoreModule?: boolean; + isInternalModule?: boolean; +}; diff --git a/packages/jest-transform/tsconfig.json b/packages/jest-transform/tsconfig.json new file mode 100644 index 000000000000..73d865a32cda --- /dev/null +++ b/packages/jest-transform/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "references": [ + {"path": "../jest-haste-map"}, + {"path": "../jest-regex-util"}, + {"path": "../jest-types"}, + {"path": "../jest-util"} + ] +} diff --git a/packages/jest-types/src/Transform.ts b/packages/jest-types/src/Transform.ts index 473434637f90..63bbb0bad081 100644 --- a/packages/jest-types/src/Transform.ts +++ b/packages/jest-types/src/Transform.ts @@ -26,7 +26,7 @@ export type TransformedSource = { export type TransformResult = { script: Script; mapCoverage: boolean; - sourceMapPath?: string; + sourceMapPath: string | null; }; export type TransformOptions = { @@ -41,7 +41,7 @@ export type CacheKeyOptions = { export type Transformer = { canInstrument?: boolean; - createTransformer?: (options: any) => Transformer; + createTransformer?: (options?: any) => Transformer; getCacheKey: ( fileData: string, diff --git a/yarn.lock b/yarn.lock index 2ca7de58e22a..59b677434489 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1575,6 +1575,11 @@ resolved "https://registry.yarnpkg.com/@types/exit/-/exit-0.1.30.tgz#7078b736a7d166c80b6394dc0d9de1577ca76daf" integrity sha1-cHi3NqfRZsgLY5TcDZ3hV3ynba8= +"@types/fast-json-stable-stringify@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#40363bb847cb86b2c2e1599f1398d11e8329c921" + integrity sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ== + "@types/fb-watchman@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/fb-watchman/-/fb-watchman-2.0.0.tgz#ca60ded406baa8c81c65ac1f86763a5d00aa7c55"