data-provider-temporary
Version:
Library that helps with server-to-client synchronization of data
415 lines (331 loc) • 10.2 kB
JavaScript
'use strict'; /**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*
*/
const crypto = require('crypto');
const path = require('path');
const vm = require('vm');var _require =
require('jest-util');const createDirectory = _require.createDirectory;
const fs = require('graceful-fs');var _require2 =
require('jest-haste-map');const getCacheFilePath = _require2.getCacheFilePath;
const stableStringify = require('json-stable-stringify');
const slash = require('slash');
const VERSION = require('../package.json').version;
const shouldInstrument = require('./shouldInstrument');
const cache = new Map();
const configToJsonMap = new Map();
// Cache regular expressions to test whether the file needs to be preprocessed
const ignoreCache = new WeakMap();
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, stableStringify(this._config));
}
const configString = configToJsonMap.get(this._config) || '';
const transformer = this._getTransformer(filename, this._config);
if (transformer && typeof transformer.getCacheKey === 'function') {
return transformer.getCacheKey(fileData, filename, configString, {
instrument });
} else {
return crypto.
createHash('md5').
update(fileData).
update(configString).
update(instrument ? 'instrument' : '').
update(mapCoverage ? 'mapCoverage' : '').
digest('hex');
}
}
_getFileCachePath(
filename,
content,
instrument,
mapCoverage)
{
const baseCacheDir = getCacheFilePath(
this._config.cacheDirectory,
'jest-transform-cache-' + this._config.name,
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.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
const cachePath = slash(
path.join(
cacheDir,
path.basename(filename, path.extname(filename)) + '_' + cacheKey));
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) {
// Keeping these requires inside this function reduces a single run
// time by 2sec if not running in `--coverage` mode
const babel = require('babel-core');
const babelPluginIstanbul = require('babel-plugin-istanbul').default;
return babel.transform(content, {
auxiliaryCommentBefore: ' istanbul ignore next ',
babelrc: false,
filename,
plugins: [
[
babelPluginIstanbul,
{
// files outside `cwd` will not be instrumented
cwd: this._config.rootDir,
exclude: [],
useInlineSourceMaps: false }]],
retainLines: true }).
code;
}
transformSource(
filename,
content,
instrument,
mapCoverage)
{
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 ?
readCacheFile(filename, cacheFilePath) :
null;
if (code) {
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 {
transformed = processed;
}
}
if (mapCoverage) {
if (!transformed.map) {
const convert = require('convert-source-map');
const inlineSourceMap = convert.fromSource(transformed.code);
if (inlineSourceMap) {
transformed.map = inlineSourceMap.toJSON();
}
}
} else {
transformed.map = null;
}
// 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 && transformed.map && mapCoverage) {
const sourceMapContent = typeof transformed.map === 'string' ?
transformed.map :
JSON.stringify(transformed.map);
writeCacheFile(sourceMapPath, sourceMapContent);
} else {
sourceMapPath = null;
}
writeCacheFile(cacheFilePath, code);
return {
code,
sourceMapPath };
}
_transformAndBuildScript(
filename,
options,
instrument,
fileSource)
{
const isInternalModule = !!(options && options.isInternalModule);
const content = stripShebang(
fileSource || fs.readFileSync(filename, 'utf8'));
let wrappedCode;
let sourceMapPath = null;
const willTransform =
!isInternalModule && (
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.Script(wrappedCode, { displayErrors: true, filename }),
sourceMapPath };
} catch (e) {
if (e.codeFrame) {
e.stack = e.codeFrame;
}
throw e;
}
}
transform(
filename,
options,
fileSource)
{
const instrument = shouldInstrument(filename, options, this._config);
const scriptCacheKey = getScriptCacheKey(
filename,
this._config,
instrument);
let result = cache.get(scriptCacheKey);
if (result) {
return result;
} else {
result = this._transformAndBuildScript(
filename,
options,
instrument,
fileSource);
cache.set(scriptCacheKey, result);
return result;
}
}}
const removeFile = path => {
try {
fs.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;
}
};
const writeCacheFile = (cachePath, fileData) => {
try {
fs.writeFileSync(cachePath, fileData, 'utf8');
} catch (e) {
e.message =
'jest: failed to cache transform results in: ' +
cachePath +
'\nFailure message: ' +
e.message;
removeFile(cachePath);
throw e;
}
};
const readCacheFile = (filename, cachePath) => {
if (!fs.existsSync(cachePath)) {
return null;
}
let fileData;
try {
fileData = fs.readFileSync(cachePath, 'utf8');
} catch (e) {
e.message = 'jest: failed to read cache file: ' + cachePath;
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 = fs.statSync(filename).mtime;
return filename + '_' + mtime.getTime() + (instrument ? '_instrumented' : '');
};
const shouldTransform = (filename, config) => {
if (!ignoreCache.has(config)) {
if (!config.transformIgnorePatterns) {
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 && (
!config.transformIgnorePatterns.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>';
module.exports = ScriptTransformer;