UNPKG

data-provider-temporary

Version:

Library that helps with server-to-client synchronization of data

415 lines (331 loc) 10.2 kB
'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;