Handle synchronous errors in `worker.transformCode`

Summary: `worker.transformCode` is a callback-taking function, but did not properly guard against errors thrown in its body.

Reviewed By: cpojer

Differential Revision: D5245253

fbshipit-source-id: 3fd08b68dd8605f664b316652ebd1f9497b2dac9
This commit is contained in:
David Aurelio 2017-06-14 05:00:57 -07:00 committed by Facebook Github Bot
parent fdc8f37a5b
commit e87a8205d8
2 changed files with 92 additions and 68 deletions

View File

@ -213,6 +213,24 @@ describe('code transformation worker:', () => {
}, },
); );
}); });
it('calls back with every error thrown by `extractDependencies`', done => {
const error = new Error('arbitrary');
extractDependencies.mockImplementation(() => {
throw error;
});
transformCode(
transformer,
'arbitrary.js',
'local/arbitrary.js',
'code',
{},
(e, data) => {
expect(e).toBe(error);
done();
},
);
});
}); });
describe('Minifications:', () => { describe('Minifications:', () => {

View File

@ -12,6 +12,7 @@
'use strict'; 'use strict';
const asyncify = require('async/asyncify');
const constantFolding = require('./constant-folding'); const constantFolding = require('./constant-folding');
const extractDependencies = require('./extract-dependencies'); const extractDependencies = require('./extract-dependencies');
const inline = require('./inline'); const inline = require('./inline');
@ -73,89 +74,94 @@ export type Data = {
}; };
type Callback<T> = (error: ?Error, data: ?T) => mixed; type Callback<T> = (error: ?Error, data: ?T) => mixed;
type TransformCode = (
Transformer<*>,
string,
LocalPath,
string,
Options,
Callback<Data>,
) => void;
function transformCode( const transformCode: TransformCode = asyncify(
transformer: Transformer<*>, (
filename: string, transformer: Transformer<*>,
localPath: LocalPath, filename: string,
sourceCode: string, localPath: LocalPath,
options: Options, sourceCode: string,
callback: Callback<Data>, options: Options,
) { ): Data => {
invariant( invariant(
!options.minify || options.transform.generateSourceMaps, !options.minify || options.transform.generateSourceMaps,
'Minifying source code requires the `generateSourceMaps` option to be `true`', 'Minifying source code requires the `generateSourceMaps` option to be `true`',
); );
const isJson = filename.endsWith('.json'); const isJson = filename.endsWith('.json');
if (isJson) { if (isJson) {
sourceCode = 'module.exports=' + sourceCode; sourceCode = 'module.exports=' + sourceCode;
} }
const transformFileStartLogEntry = { const transformFileStartLogEntry = {
action_name: 'Transforming file', action_name: 'Transforming file',
action_phase: 'start', action_phase: 'start',
file_name: filename, file_name: filename,
log_entry_label: 'Transforming file', log_entry_label: 'Transforming file',
start_timestamp: process.hrtime(), start_timestamp: process.hrtime(),
}; };
let transformed; const transformed = transformer.transform({
try {
transformed = transformer.transform({
filename, filename,
localPath, localPath,
options: options.transform, options: options.transform,
src: sourceCode, src: sourceCode,
}); });
} catch (error) {
callback(error);
return;
}
invariant( invariant(
transformed != null, transformed != null,
'Missing transform results despite having no error.', 'Missing transform results despite having no error.',
); );
var code, map; var code, map;
if (options.minify) { if (options.minify) {
({code, map} = constantFolding( ({code, map} = constantFolding(
filename, filename,
inline(filename, transformed, options), inline(filename, transformed, options),
)); ));
invariant(code != null, 'Missing code from constant-folding transform.'); invariant(code != null, 'Missing code from constant-folding transform.');
} else { } else {
({code, map} = transformed); ({code, map} = transformed);
} }
if (isJson) { if (isJson) {
code = code.replace(/^\w+\.exports=/, ''); code = code.replace(/^\w+\.exports=/, '');
} else { } else {
// Remove shebang // Remove shebang
code = code.replace(/^#!.*/, ''); code = code.replace(/^#!.*/, '');
} }
const depsResult = isJson const depsResult = isJson
? {dependencies: [], dependencyOffsets: []} ? {dependencies: [], dependencyOffsets: []}
: extractDependencies(code); : extractDependencies(code);
const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp); const timeDelta = process.hrtime(
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6); transformFileStartLogEntry.start_timestamp,
const transformFileEndLogEntry = { );
action_name: 'Transforming file', const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
action_phase: 'end', const transformFileEndLogEntry = {
file_name: filename, action_name: 'Transforming file',
duration_ms, action_phase: 'end',
log_entry_label: 'Transforming file', file_name: filename,
}; duration_ms,
log_entry_label: 'Transforming file',
};
callback(null, { return {
result: {...depsResult, code, map}, result: {...depsResult, code, map},
transformFileStartLogEntry, transformFileStartLogEntry,
transformFileEndLogEntry, transformFileEndLogEntry,
}); };
} },
);
exports.transformAndExtractDependencies = ( exports.transformAndExtractDependencies = (
transform: string, transform: string,