Skip to content

Commit

Permalink
Remove IIFE wrappers from dev invariant checks (#16963)
Browse files Browse the repository at this point in the history
The error transform works by replacing calls to `invariant` with
an `if` statement.

Since we're replacing a call expression with a statement, Babel wraps
the new statement in an immediately-invoked function expression (IIFE).
This wrapper is unnecessary in practice because our `invariant` calls
are always part of their own expression statement.

In the production bundle, the function wrappers are removed by Closure.
But they remain in the development bundles.

This commit updates the transform to confirm that an `invariant` call
expression's parent node is an expression statement. (If not, it throws
a transform error.)

Then, it replaces the expression statement instead of the expression
itself, effectively removing the extraneous IIFE wrapper.
  • Loading branch information
acdlite committed Sep 30, 2019
1 parent 2c88320 commit 05dc814
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 31 deletions.
Expand Up @@ -29,56 +29,48 @@ exports[`error transform should replace simple invariant calls 1`] = `
import _ReactError from \\"shared/ReactError\\";
import invariant from 'shared/invariant';
(function () {
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Do not override existing functions.\\"));
} else {
throw _ReactErrorProd(Error(16));
}
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Do not override existing functions.\\"));
} else {
throw _ReactErrorProd(Error(16));
}
})();"
}"
`;

exports[`error transform should support invariant calls with a concatenated template string and args 1`] = `
"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
import _ReactError from \\"shared/ReactError\\";
import invariant from 'shared/invariant';
(function () {
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Expected a component class, got \\" + Foo + \\".\\" + Bar));
} else {
throw _ReactErrorProd(Error(18), Foo, Bar);
}
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Expected a component class, got \\" + Foo + \\".\\" + Bar));
} else {
throw _ReactErrorProd(Error(18), Foo, Bar);
}
})();"
}"
`;

exports[`error transform should support invariant calls with args 1`] = `
"import _ReactErrorProd from \\"shared/ReactErrorProd\\";
import _ReactError from \\"shared/ReactError\\";
import invariant from 'shared/invariant';
(function () {
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Expected \\" + foo + \\" target to be an array; got \\" + bar));
} else {
throw _ReactErrorProd(Error(7), foo, bar);
}
if (!condition) {
if (__DEV__) {
throw _ReactError(Error(\\"Expected \\" + foo + \\" target to be an array; got \\" + bar));
} else {
throw _ReactErrorProd(Error(7), foo, bar);
}
})();"
}"
`;

exports[`error transform should support noMinify option 1`] = `
"import _ReactError from \\"shared/ReactError\\";
import invariant from 'shared/invariant';
(function () {
if (!condition) {
throw _ReactError(Error(\\"Do not override existing functions.\\"));
}
})();"
if (!condition) {
throw _ReactError(Error(\\"Do not override existing functions.\\"));
}"
`;
9 changes: 9 additions & 0 deletions scripts/error-codes/__tests__/transform-error-messages.js
Expand Up @@ -37,6 +37,15 @@ invariant(condition, 'Do not override existing functions.');
).toMatchSnapshot();
});

it('should throw if invariant is not in an expression statement', () => {
expect(() => {
transform(`
import invariant from 'shared/invariant';
cond && invariant(condition, 'Do not override existing functions.');
`);
}).toThrow('invariant() cannot be called from expression context');
});

it('should support invariant calls with args', () => {
expect(
transform(`
Expand Down
12 changes: 10 additions & 2 deletions scripts/error-codes/transform-error-messages.js
Expand Up @@ -64,14 +64,22 @@ module.exports = function(babel) {
])
);

const parentStatementPath = path.parentPath;
if (parentStatementPath.type !== 'ExpressionStatement') {
throw path.buildCodeFrameError(
'invariant() cannot be called from expression context. Move ' +
'the call to its own statement.'
);
}

if (noMinify) {
// Error minification is disabled for this build.
//
// Outputs:
// if (!condition) {
// throw ReactError(Error(`A ${adj} message that contains ${noun}`));
// }
path.replaceWith(
parentStatementPath.replaceWith(
t.ifStatement(
t.unaryExpression('!', condition),
t.blockStatement([devThrow])
Expand Down Expand Up @@ -138,7 +146,7 @@ module.exports = function(babel) {
// throw ReactErrorProd(Error(ERR_CODE), adj, noun);
// }
// }
path.replaceWith(
parentStatementPath.replaceWith(
t.ifStatement(
t.unaryExpression('!', condition),
t.blockStatement([
Expand Down

0 comments on commit 05dc814

Please sign in to comment.