Skip to content

Commit

Permalink
chore: migrate @jest/transform to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Feb 16, 2019
1 parent 6af2f67 commit d1c361c
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 95 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/babel-jest/src/index.ts
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-transform/package.json
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
Expand Up @@ -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<string, TransformResult>,
|};
type ProjectCache = {
configString: string;
ignorePatternsRegExp: RegExp | null;
transformedFiles: Map<string, Transform.TransformResult | string>;
};

// 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<ProjectConfig, ProjectCache> = 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<Path, ?Transformer>;
private _cache: ProjectCache;
private _config: Config.ProjectConfig;
private _transformCache: Map<Config.Path, Transform.Transformer>;

constructor(config: ProjectConfig) {
constructor(config: Config.ProjectConfig) {
this._config = config;
this._transformCache = new Map();

Expand All @@ -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);

Expand All @@ -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,
Expand All @@ -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];
Expand All @@ -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;
}
Expand All @@ -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();
}
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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;

Expand All @@ -233,7 +243,7 @@ export default class ScriptTransformer {
};
}

let transformed: TransformedSource = {
let transformed: Transform.TransformedSource = {
code: content,
map: null,
};
Expand Down Expand Up @@ -290,20 +300,20 @@ 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(
fileSource || fs.readFileSync(filename, 'utf8'),
);

let wrappedCode: string;
let sourceMapPath: ?string = null;
let sourceMapPath: string | null = null;
let mapCoverage = false;

const willTransform =
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand All @@ -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('#!')) {
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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<string>) => {
const globals = new Set([
'module',
'exports',
Expand Down
@@ -1,7 +1,5 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

// @flow

import chalk from 'chalk';

const DOT = ' \u2022 ';
Expand Down
Expand Up @@ -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';
Expand Down

0 comments on commit d1c361c

Please sign in to comment.