516 lines
15 KiB
JavaScript
516 lines
15 KiB
JavaScript
|
'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _crypto;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
function _load_crypto() {return _crypto = _interopRequireDefault(require('crypto'));}var _path;
|
||
|
function _load_path() {return _path = _interopRequireDefault(require('path'));}var _vm;
|
||
|
function _load_vm() {return _vm = _interopRequireDefault(require('vm'));}var _jestUtil;
|
||
|
function _load_jestUtil() {return _jestUtil = require('jest-util');}var _gracefulFs;
|
||
|
function _load_gracefulFs() {return _gracefulFs = _interopRequireDefault(require('graceful-fs'));}var _babelCore;
|
||
|
function _load_babelCore() {return _babelCore = require('babel-core');}var _babelPluginIstanbul;
|
||
|
function _load_babelPluginIstanbul() {return _babelPluginIstanbul = _interopRequireDefault(require('babel-plugin-istanbul'));}var _convertSourceMap;
|
||
|
function _load_convertSourceMap() {return _convertSourceMap = _interopRequireDefault(require('convert-source-map'));}var _jestHasteMap;
|
||
|
function _load_jestHasteMap() {return _jestHasteMap = _interopRequireDefault(require('jest-haste-map'));}var _jsonStableStringify;
|
||
|
function _load_jsonStableStringify() {return _jsonStableStringify = _interopRequireDefault(require('json-stable-stringify'));}var _slash;
|
||
|
function _load_slash() {return _slash = _interopRequireDefault(require('slash'));}var _package;
|
||
|
function _load_package() {return _package = require('../package.json');}var _should_instrument;
|
||
|
function _load_should_instrument() {return _should_instrument = _interopRequireDefault(require('./should_instrument'));}var _writeFileAtomic;
|
||
|
function _load_writeFileAtomic() {return _writeFileAtomic = _interopRequireDefault(require('write-file-atomic'));}var _realpathNative;
|
||
|
function _load_realpathNative() {return _realpathNative = require('realpath-native');}function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
const cache = new Map(); /**
|
||
|
* Copyright (c) 2014-present, Facebook, Inc. 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.
|
||
|
*
|
||
|
*
|
||
|
*/const configToJsonMap = new Map(); // Cache regular expressions to test whether the file needs to be preprocessed
|
||
|
const ignoreCache = new WeakMap(); // To reset the cache for specific changesets (rather than package version).
|
||
|
const CACHE_VERSION = '1';class ScriptTransformer {
|
||
|
|
||
|
|
||
|
|
||
|
constructor(config) {
|
||
|
this._config = config;
|
||
|
this._transformCache = new Map();
|
||
|
}
|
||
|
|
||
|
_getCacheKey(
|
||
|
fileData,
|
||
|
filename,
|
||
|
instrument,
|
||
|
mapCoverage)
|
||
|
{
|
||
|
if (!configToJsonMap.has(this._config)) {
|
||
|
// We only need this set of config options that can likely influence
|
||
|
// cached output instead of all config options.
|
||
|
configToJsonMap.set(this._config, (0, (_jsonStableStringify || _load_jsonStableStringify()).default)(this._config));
|
||
|
}
|
||
|
const configString = configToJsonMap.get(this._config) || '';
|
||
|
const transformer = this._getTransformer(filename);
|
||
|
|
||
|
if (transformer && typeof transformer.getCacheKey === 'function') {
|
||
|
return (_crypto || _load_crypto()).default.
|
||
|
createHash('md5').
|
||
|
update(
|
||
|
transformer.getCacheKey(fileData, filename, configString, {
|
||
|
instrument,
|
||
|
mapCoverage,
|
||
|
rootDir: this._config.rootDir })).
|
||
|
|
||
|
|
||
|
update(CACHE_VERSION).
|
||
|
digest('hex');
|
||
|
} else {
|
||
|
return (_crypto || _load_crypto()).default.
|
||
|
createHash('md5').
|
||
|
update(fileData).
|
||
|
update(configString).
|
||
|
update(instrument ? 'instrument' : '').
|
||
|
update(mapCoverage ? 'mapCoverage' : '').
|
||
|
update(CACHE_VERSION).
|
||
|
digest('hex');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_getFileCachePath(
|
||
|
filename,
|
||
|
content,
|
||
|
instrument,
|
||
|
mapCoverage)
|
||
|
{
|
||
|
const baseCacheDir = (_jestHasteMap || _load_jestHasteMap()).default.getCacheFilePath(
|
||
|
this._config.cacheDirectory,
|
||
|
'jest-transform-cache-' + this._config.name, (_package || _load_package()).version);
|
||
|
|
||
|
|
||
|
const cacheKey = this._getCacheKey(
|
||
|
content,
|
||
|
filename,
|
||
|
instrument,
|
||
|
mapCoverage);
|
||
|
|
||
|
// Create sub folders based on the cacheKey to avoid creating one
|
||
|
// directory with many files.
|
||
|
const cacheDir = (_path || _load_path()).default.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
|
||
|
const cachePath = (0, (_slash || _load_slash()).default)(
|
||
|
(_path || _load_path()).default.join(
|
||
|
cacheDir,
|
||
|
(_path || _load_path()).default.basename(filename, (_path || _load_path()).default.extname(filename)) + '_' + cacheKey));
|
||
|
|
||
|
|
||
|
(0, (_jestUtil || _load_jestUtil()).createDirectory)(cacheDir);
|
||
|
|
||
|
return cachePath;
|
||
|
}
|
||
|
|
||
|
_getTransformPath(filename) {
|
||
|
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];
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
_getTransformer(filename) {
|
||
|
let transform;
|
||
|
if (!this._config.transform || !this._config.transform.length) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const transformPath = this._getTransformPath(filename);
|
||
|
if (transformPath) {
|
||
|
const transformer = this._transformCache.get(transformPath);
|
||
|
if (transformer != null) {
|
||
|
return transformer;
|
||
|
}
|
||
|
|
||
|
// $FlowFixMe
|
||
|
transform = require(transformPath);
|
||
|
if (typeof transform.process !== 'function') {
|
||
|
throw new TypeError(
|
||
|
'Jest: a transform must export a `process` function.');
|
||
|
|
||
|
}
|
||
|
if (typeof transform.createTransformer === 'function') {
|
||
|
transform = transform.createTransformer();
|
||
|
}
|
||
|
this._transformCache.set(transformPath, transform);
|
||
|
}
|
||
|
return transform;
|
||
|
}
|
||
|
|
||
|
_instrumentFile(filename, content) {
|
||
|
return (0, (_babelCore || _load_babelCore()).transform)(content, {
|
||
|
auxiliaryCommentBefore: ' istanbul ignore next ',
|
||
|
babelrc: false,
|
||
|
filename,
|
||
|
plugins: [
|
||
|
[(_babelPluginIstanbul || _load_babelPluginIstanbul()).default,
|
||
|
|
||
|
{
|
||
|
// files outside `cwd` will not be instrumented
|
||
|
cwd: this._config.rootDir,
|
||
|
exclude: [],
|
||
|
useInlineSourceMaps: false }]],
|
||
|
|
||
|
|
||
|
|
||
|
retainLines: true }).
|
||
|
code;
|
||
|
}
|
||
|
|
||
|
_getRealPath(filepath) {
|
||
|
try {
|
||
|
return (0, (_realpathNative || _load_realpathNative()).sync)(filepath) || filepath;
|
||
|
} catch (err) {
|
||
|
return filepath;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
transformSource(
|
||
|
filepath,
|
||
|
content,
|
||
|
instrument,
|
||
|
mapCoverage)
|
||
|
{
|
||
|
const filename = this._getRealPath(filepath);
|
||
|
const transform = this._getTransformer(filename);
|
||
|
const cacheFilePath = this._getFileCachePath(
|
||
|
filename,
|
||
|
content,
|
||
|
instrument,
|
||
|
mapCoverage);
|
||
|
|
||
|
let sourceMapPath = cacheFilePath + '.map';
|
||
|
// Ignore cache if `config.cache` is set (--no-cache)
|
||
|
let code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null;
|
||
|
|
||
|
if (code) {
|
||
|
// This is broken: we return the code, and a path for the source map
|
||
|
// directly from the cache. But, nothing ensures the source map actually
|
||
|
// matches that source code. They could have gotten out-of-sync in case
|
||
|
// two separate processes write concurrently to the same cache files.
|
||
|
return {
|
||
|
code,
|
||
|
sourceMapPath };
|
||
|
|
||
|
}
|
||
|
|
||
|
let transformed = {
|
||
|
code: content,
|
||
|
map: null };
|
||
|
|
||
|
|
||
|
if (transform && shouldTransform(filename, this._config)) {
|
||
|
const processed = transform.process(content, filename, this._config, {
|
||
|
instrument });
|
||
|
|
||
|
|
||
|
if (typeof processed === 'string') {
|
||
|
transformed.code = processed;
|
||
|
} else if (processed != null && typeof processed.code === 'string') {
|
||
|
transformed = processed;
|
||
|
} else {
|
||
|
throw new TypeError(
|
||
|
"Jest: a transform's `process` function must return a string, " +
|
||
|
'or an object with `code` key containing this string.');
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mapCoverage) {
|
||
|
if (!transformed.map) {
|
||
|
const inlineSourceMap = (_convertSourceMap || _load_convertSourceMap()).default.fromSource(transformed.code);
|
||
|
if (inlineSourceMap) {
|
||
|
transformed.map = inlineSourceMap.toJSON();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// That means that the transform has a custom instrumentation
|
||
|
// logic and will handle it based on `config.collectCoverage` option
|
||
|
const transformDidInstrument = transform && transform.canInstrument;
|
||
|
|
||
|
if (!transformDidInstrument && instrument) {
|
||
|
code = this._instrumentFile(filename, transformed.code);
|
||
|
} else {
|
||
|
code = transformed.code;
|
||
|
}
|
||
|
|
||
|
if (instrument && mapCoverage && transformed.map) {
|
||
|
const sourceMapContent =
|
||
|
typeof transformed.map === 'string' ?
|
||
|
transformed.map :
|
||
|
JSON.stringify(transformed.map);
|
||
|
writeCacheFile(sourceMapPath, sourceMapContent);
|
||
|
} else {
|
||
|
sourceMapPath = null;
|
||
|
}
|
||
|
|
||
|
writeCodeCacheFile(cacheFilePath, code);
|
||
|
|
||
|
return {
|
||
|
code,
|
||
|
sourceMapPath };
|
||
|
|
||
|
}
|
||
|
|
||
|
_transformAndBuildScript(
|
||
|
filename,
|
||
|
options,
|
||
|
instrument,
|
||
|
fileSource)
|
||
|
{
|
||
|
const isInternalModule = !!(options && options.isInternalModule);
|
||
|
const isCoreModule = !!(options && options.isCoreModule);
|
||
|
const content = stripShebang(
|
||
|
fileSource || (_gracefulFs || _load_gracefulFs()).default.readFileSync(filename, 'utf8'));
|
||
|
|
||
|
|
||
|
let wrappedCode;
|
||
|
let sourceMapPath = null;
|
||
|
|
||
|
const willTransform =
|
||
|
!isInternalModule &&
|
||
|
!isCoreModule && (
|
||
|
shouldTransform(filename, this._config) || instrument);
|
||
|
|
||
|
try {
|
||
|
if (willTransform) {
|
||
|
const transformedSource = this.transformSource(
|
||
|
filename,
|
||
|
content,
|
||
|
instrument,
|
||
|
!!(options && options.mapCoverage));
|
||
|
|
||
|
|
||
|
wrappedCode = wrap(transformedSource.code);
|
||
|
sourceMapPath = transformedSource.sourceMapPath;
|
||
|
} else {
|
||
|
wrappedCode = wrap(content);
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
script: new (_vm || _load_vm()).default.Script(wrappedCode, {
|
||
|
displayErrors: true,
|
||
|
filename: isCoreModule ? 'jest-nodejs-core-' + filename : filename }),
|
||
|
|
||
|
sourceMapPath };
|
||
|
|
||
|
} catch (e) {
|
||
|
if (e.codeFrame) {
|
||
|
e.stack = e.codeFrame;
|
||
|
}
|
||
|
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
transform(
|
||
|
filename,
|
||
|
options,
|
||
|
fileSource)
|
||
|
{
|
||
|
let scriptCacheKey = null;
|
||
|
let instrument = false;
|
||
|
let result = '';
|
||
|
|
||
|
if (!options.isCoreModule) {
|
||
|
instrument = (0, (_should_instrument || _load_should_instrument()).default)(filename, options, this._config);
|
||
|
scriptCacheKey = getScriptCacheKey(filename, this._config, instrument);
|
||
|
result = cache.get(scriptCacheKey);
|
||
|
}
|
||
|
|
||
|
if (result) {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
result = this._transformAndBuildScript(
|
||
|
filename,
|
||
|
options,
|
||
|
instrument,
|
||
|
fileSource);
|
||
|
|
||
|
|
||
|
if (scriptCacheKey) {
|
||
|
cache.set(scriptCacheKey, result);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}}exports.default = ScriptTransformer;
|
||
|
|
||
|
|
||
|
const removeFile = path => {
|
||
|
try {
|
||
|
(_gracefulFs || _load_gracefulFs()).default.unlinkSync(path);
|
||
|
} catch (e) {}
|
||
|
};
|
||
|
|
||
|
const stripShebang = content => {
|
||
|
// If the file data starts with a shebang remove it. Leaves the empty line
|
||
|
// to keep stack trace line numbers correct.
|
||
|
if (content.startsWith('#!')) {
|
||
|
return content.replace(/^#!.*/, '');
|
||
|
} else {
|
||
|
return content;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This is like `writeCacheFile` but with an additional sanity checksum. We
|
||
|
* cannot use the same technique for source maps because we expose source map
|
||
|
* cache file paths directly to callsites, with the expectation they can read
|
||
|
* it right away. This is not a great system, because source map cache file
|
||
|
* could get corrupted, out-of-sync, etc.
|
||
|
*/
|
||
|
function writeCodeCacheFile(cachePath, code) {
|
||
|
const checksum = (_crypto || _load_crypto()).default.
|
||
|
createHash('md5').
|
||
|
update(code).
|
||
|
digest('hex');
|
||
|
writeCacheFile(cachePath, checksum + '\n' + code);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read counterpart of `writeCodeCacheFile`. We verify that the content of the
|
||
|
* file matches the checksum, in case some kind of corruption happened. This
|
||
|
* could happen if an older version of `jest-runtime` writes non-atomically to
|
||
|
* the same cache, for example.
|
||
|
*/
|
||
|
function readCodeCacheFile(cachePath) {
|
||
|
const content = readCacheFile(cachePath);
|
||
|
if (content == null) {
|
||
|
return null;
|
||
|
}
|
||
|
const code = content.substr(33);
|
||
|
const checksum = (_crypto || _load_crypto()).default.
|
||
|
createHash('md5').
|
||
|
update(code).
|
||
|
digest('hex');
|
||
|
if (checksum === content.substr(0, 32)) {
|
||
|
return code;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writing to the cache atomically relies on 'rename' being atomic on most
|
||
|
* file systems. Doing atomic write reduces the risk of corruption by avoiding
|
||
|
* 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, fileData) => {
|
||
|
try {
|
||
|
(_writeFileAtomic || _load_writeFileAtomic()).default.sync(cachePath, fileData, { encoding: 'utf8' });
|
||
|
} catch (e) {
|
||
|
if (cacheWriteErrorSafeToIgnore(e, cachePath)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
e.message =
|
||
|
'jest: failed to cache transform results in: ' +
|
||
|
cachePath +
|
||
|
'\nFailure message: ' +
|
||
|
e.message;
|
||
|
removeFile(cachePath);
|
||
|
throw e;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* On Windows, renames are not atomic, leading to EPERM exceptions when two
|
||
|
* processes attempt to rename to the same target file at the same time.
|
||
|
* 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, cachePath) => {
|
||
|
return (
|
||
|
process.platform === 'win32' &&
|
||
|
e.code === 'EPERM' &&
|
||
|
(_gracefulFs || _load_gracefulFs()).default.existsSync(cachePath));
|
||
|
|
||
|
};
|
||
|
|
||
|
const readCacheFile = cachePath => {
|
||
|
if (!(_gracefulFs || _load_gracefulFs()).default.existsSync(cachePath)) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
let fileData;
|
||
|
try {
|
||
|
fileData = (_gracefulFs || _load_gracefulFs()).default.readFileSync(cachePath, 'utf8');
|
||
|
} catch (e) {
|
||
|
e.message =
|
||
|
'jest: failed to read cache file: ' +
|
||
|
cachePath +
|
||
|
'\nFailure message: ' +
|
||
|
e.message;
|
||
|
removeFile(cachePath);
|
||
|
throw e;
|
||
|
}
|
||
|
|
||
|
if (fileData == null) {
|
||
|
// We must have somehow created the file but failed to write to it,
|
||
|
// let's delete it and retry.
|
||
|
removeFile(cachePath);
|
||
|
}
|
||
|
return fileData;
|
||
|
};
|
||
|
|
||
|
const getScriptCacheKey = (filename, config, instrument) => {
|
||
|
const mtime = (_gracefulFs || _load_gracefulFs()).default.statSync(filename).mtime;
|
||
|
return filename + '_' + mtime.getTime() + (instrument ? '_instrumented' : '');
|
||
|
};
|
||
|
|
||
|
const shouldTransform = (filename, config) => {
|
||
|
if (!ignoreCache.has(config)) {
|
||
|
if (
|
||
|
!config.transformIgnorePatterns ||
|
||
|
config.transformIgnorePatterns.length === 0)
|
||
|
{
|
||
|
ignoreCache.set(config, null);
|
||
|
} else {
|
||
|
ignoreCache.set(
|
||
|
config,
|
||
|
new RegExp(config.transformIgnorePatterns.join('|')));
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ignoreRegexp = ignoreCache.get(config);
|
||
|
const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
|
||
|
return !!config.transform && !!config.transform.length && !isIgnored;
|
||
|
};
|
||
|
|
||
|
const wrap = content =>
|
||
|
'({"' +
|
||
|
ScriptTransformer.EVAL_RESULT_VARIABLE +
|
||
|
'":function(module,exports,require,__dirname,__filename,global,jest){' +
|
||
|
content +
|
||
|
'\n}});';
|
||
|
|
||
|
ScriptTransformer.EVAL_RESULT_VARIABLE = 'Object.<anonymous>';
|