Skip to content

Commit

Permalink
Use separate Jest preprocessors based on file path
Browse files Browse the repository at this point in the history
Some Babel plugins are only meant to run on source files, some only on
tests, and so on. We were using a single preprocessor and some
metaprogramming to determine which ones to run per file; I changed it
to use separate preprocessors per category instead.

The practical reason I made this change was so the error code script
does not run on test files.
  • Loading branch information
acdlite committed Apr 9, 2019
1 parent bd1ae71 commit 8fad8da
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 188 deletions.
4 changes: 1 addition & 3 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it mig
export const enableSchedulerDebugging = false;

// Only used in www builds.
export function addUserTimingListener() {
throw new Error('Not implemented.');
}
export function addUserTimingListener() {}

// Disable javascript: URL strings in href for XSS protection.
export const disableJavaScriptURLs = false;
Expand Down
7 changes: 6 additions & 1 deletion scripts/jest/config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ module.exports = {
'<rootDir>/scripts/bench/',
],
transform: {
'.*': require.resolve('./preprocessor.js'),
'packages/((?!__tests__).)*\\.js$': require.resolve(
'./preprocessorForSourceFiles.js'
),
'__tests__.*\\.js$': require.resolve('./preprocessorForTestFiles.js'),
'\\.coffee$': require.resolve('./preprocessorForCoffeeScript.js'),
'\\.ts$': require.resolve('./preprocessorForTypeScript.js'),
},
setupFiles: [require.resolve('./setupEnvironment.js')],
setupTestFrameworkScriptFile: require.resolve('./setupTests.js'),
Expand Down
86 changes: 0 additions & 86 deletions scripts/jest/preprocessor.js

This file was deleted.

9 changes: 9 additions & 0 deletions scripts/jest/preprocessorForCoffeeScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

const coffee = require('coffee-script');

module.exports = {
process(src, filePath) {
return coffee.compile(src, {bare: true});
},
};
49 changes: 49 additions & 0 deletions scripts/jest/preprocessorForSourceFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const path = require('path');
const babel = require('babel-core');
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');

// Use require.resolve to be resilient to file moves, npm updates, etc
const pathToBabel = path.join(
require.resolve('babel-core'),
'..',
'package.json'
);
const pathToBabelPluginDevWithCode = require.resolve(
'../error-codes/minify-error-messages'
);
const pathToBabelPluginWrapWarning = require.resolve(
'../babel/wrap-warning-with-env-check'
);
const pathToBabelrc = path.join(__dirname, '..', '..', '.babelrc');
const pathToErrorCodes = require.resolve('../error-codes/codes.json');

module.exports = {
process(src, filePath) {
// for test files, we also apply the async-await transform, but we want to
// make sure we don't accidentally apply that transform to product code.
return babel.transform(src, {
filename: path.relative(process.cwd(), filePath),
plugins: [
// For Node environment only. For builds, Rollup takes care of ESM.
require.resolve('babel-plugin-transform-es2015-modules-commonjs'),

require.resolve('../babel/transform-prevent-infinite-loops'),

pathToBabelPluginDevWithCode,
pathToBabelPluginWrapWarning,
],
retainLines: true,
}).code;
},

getCacheKey: createCacheKeyFunction([
__filename,
pathToBabel,
pathToBabelrc,
pathToBabelPluginDevWithCode,
pathToBabelPluginWrapWarning,
pathToErrorCodes,
]),
};
36 changes: 36 additions & 0 deletions scripts/jest/preprocessorForTestFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const path = require('path');
const babel = require('babel-core');
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');
const pathToBabelPluginAsyncToGenerator = require.resolve(
'babel-plugin-transform-async-to-generator'
);

// Use require.resolve to be resilient to file moves, npm updates, etc
const pathToBabel = path.join(
require.resolve('babel-core'),
'..',
'package.json'
);
const pathToBabelrc = path.join(__dirname, '..', '..', '.babelrc');

module.exports = {
process(src, filePath) {
return babel.transform(src, {
filename: path.relative(process.cwd(), filePath),
plugins: [
require.resolve('babel-plugin-transform-react-jsx-source'),
require.resolve('../babel/transform-prevent-infinite-loops'),
require.resolve('babel-plugin-transform-async-to-generator'),
],
}).code;
},

getCacheKey: createCacheKeyFunction([
__filename,
pathToBabel,
pathToBabelrc,
pathToBabelPluginAsyncToGenerator,
]),
};
98 changes: 98 additions & 0 deletions scripts/jest/preprocessorForTypeScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict';

const fs = require('fs');
const path = require('path');
const ts = require('typescript');

const tsOptions = {
module: ts.ModuleKind.CommonJS,
jsx: ts.JsxEmit.React,
};

function formatErrorMessage(error) {
return (
error.file.filename +
'(' +
error.file.getLineAndCharacterOfPosition(error.start).line +
'): ' +
error.messageText
);
}

module.exports = {
process(content, contentFilename) {
let output = null;
const compilerHost = {
getSourceFile(filename, languageVersion) {
let source;
const jestRegex = /jest\.d\.ts/;
const reactRegex = /(?:React|ReactDOM|PropTypes)(?:\.d)?\.ts$/;

// `path.normalize` is used to turn forward slashes in
// the file path into backslashes on Windows.
filename = path.normalize(filename);
if (filename === 'lib.d.ts') {
source = fs
.readFileSync(require.resolve('typescript/lib/lib.d.ts'))
.toString();
} else if (filename.match(jestRegex)) {
source = fs
.readFileSync(path.join(__dirname, 'typescript', 'jest.d.ts'))
.toString();
} else if (filename === contentFilename) {
source = content;
} else if (reactRegex.test(filename)) {
// TypeScript will look for the .d.ts files in each ancestor directory,
// so there may not be a file at the referenced path as it climbs the
// hierarchy.
try {
source = fs.readFileSync(filename).toString();
} catch (e) {
if (e.code === 'ENOENT') {
return undefined;
}
throw e;
}
} else {
throw new Error('Unexpected filename ' + filename);
}
return ts.createSourceFile(filename, source, 'ES5', '0');
},
writeFile(name, text, writeByteOrderMark) {
if (output === null) {
output = text;
} else {
throw new Error('Expected only one dependency.');
}
},
getCanonicalFileName(filename) {
return filename;
},
getCurrentDirectory() {
return '';
},
getNewLine() {
return '\n';
},
fileExists(filename) {
return ts.sys.fileExists(filename);
},
useCaseSensitiveFileNames() {
return ts.sys.useCaseSensitiveFileNames;
},
};
const program = ts.createProgram(
['lib.d.ts', 'jest.d.ts', contentFilename],
tsOptions,
compilerHost
);
const emitResult = program.emit();
const errors = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
if (errors.length) {
throw new Error(errors.map(formatErrorMessage).join('\n'));
}
return output;
},
};
Loading

0 comments on commit 8fad8da

Please sign in to comment.