mirror of
https://github.com/facebook/lexical.git
synced 2025-05-17 15:18:47 +08:00
[lexical] Chore: Change $getTextNodeOffset invariant to warn in prod (error in __DEV__) (#7397)
This commit is contained in:
@ -53,10 +53,10 @@ function fmt(strings: TemplateStringsArray, ...keys: unknown[]) {
|
||||
.replace(/.use strict.;\n/g, '')
|
||||
.replace(/var _[^;]+;\n/g, '')
|
||||
.replace(/function _interopRequireDefault\([^)]*\) {[^;]+?;[\s\n]*}\n/g, '')
|
||||
.replace(/_format(Dev|Prod)ErrorMessage\d+/g, 'format$1ErrorMessage')
|
||||
.replace(/_format(Dev|Prod)(Error|Warning)Message\d+/g, 'format$1$2Message')
|
||||
.replace(
|
||||
/\(0,\s*format(Dev|Prod)ErrorMessage\.default\)/g,
|
||||
'format$1ErrorMessage',
|
||||
/\(0,\s*format(Dev|Prod)(Error|Warning)Message\.default\)/g,
|
||||
'format$1$2Message',
|
||||
)
|
||||
.trim();
|
||||
return prettier.format(before, {
|
||||
@ -98,77 +98,155 @@ async function expectTransform(opts) {
|
||||
}
|
||||
|
||||
describe('transform-error-messages', () => {
|
||||
describe('{extractCodes: true, noMinify: false}', () => {
|
||||
const opts = {extractCodes: true, noMinify: false};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
describe('invariant', () => {
|
||||
describe('{extractCodes: true, noMinify: false}', () => {
|
||||
const opts = {extractCodes: true, noMinify: false};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
invariant(condition, ${JSON.stringify(NEW_MSG)});
|
||||
invariant(condition, ${JSON.stringify(KNOWN_MSG)}, adj, noun);
|
||||
`,
|
||||
codeExpect: `
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatProdErrorMessage(1);
|
||||
}
|
||||
if (!condition) {
|
||||
formatProdErrorMessage(0, adj, noun);
|
||||
}`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('{extractCodes: true, noMinify: true}', () => {
|
||||
const opts = {extractCodes: true, noMinify: true};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
describe('{extractCodes: true, noMinify: true}', () => {
|
||||
const opts = {extractCodes: true, noMinify: true};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
invariant(condition, ${JSON.stringify(NEW_MSG)});
|
||||
invariant(condition, ${JSON.stringify(KNOWN_MSG)}, adj, noun);
|
||||
`,
|
||||
codeExpect: `
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A new invariant\`);
|
||||
}
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A \${adj} message that contains \${noun}\`);
|
||||
}`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('{extractCodes: false, noMinify: false}', () => {
|
||||
const opts = {extractCodes: false, noMinify: false};
|
||||
it('inserts known message', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `invariant(condition, ${JSON.stringify(
|
||||
KNOWN_MSG,
|
||||
)}, adj, noun)`,
|
||||
codeExpect: `
|
||||
describe('{extractCodes: false, noMinify: false}', () => {
|
||||
const opts = {extractCodes: false, noMinify: false};
|
||||
it('inserts known message', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `invariant(condition, ${JSON.stringify(
|
||||
KNOWN_MSG,
|
||||
)}, adj, noun)`,
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatProdErrorMessage(0, adj, noun);
|
||||
}
|
||||
`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
it('inserts warning comment for unknown messages', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `invariant(condition, ${JSON.stringify(NEW_MSG)})`,
|
||||
codeExpect: `
|
||||
it('inserts warning comment for unknown messages', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `invariant(condition, ${JSON.stringify(NEW_MSG)})`,
|
||||
codeExpect: `
|
||||
/*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A new invariant\`);
|
||||
}
|
||||
`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('devInvariant', () => {
|
||||
describe('{extractCodes: true, noMinify: false}', () => {
|
||||
const opts = {extractCodes: true, noMinify: false};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
devInvariant(condition, ${JSON.stringify(NEW_MSG)});
|
||||
devInvariant(condition, ${JSON.stringify(KNOWN_MSG)}, adj, noun);
|
||||
`,
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatProdWarningMessage(1);
|
||||
}
|
||||
if (!condition) {
|
||||
formatProdWarningMessage(0, adj, noun);
|
||||
}`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('{extractCodes: true, noMinify: true}', () => {
|
||||
const opts = {extractCodes: true, noMinify: true};
|
||||
it('inserts known and extracts unknown message codes', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `
|
||||
devInvariant(condition, ${JSON.stringify(NEW_MSG)});
|
||||
devInvariant(condition, ${JSON.stringify(KNOWN_MSG)}, adj, noun);
|
||||
`,
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A new invariant\`);
|
||||
}
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A \${adj} message that contains \${noun}\`);
|
||||
}`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: NEW_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('{extractCodes: false, noMinify: false}', () => {
|
||||
const opts = {extractCodes: false, noMinify: false};
|
||||
it('inserts known message', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `devInvariant(condition, ${JSON.stringify(
|
||||
KNOWN_MSG,
|
||||
)}, adj, noun)`,
|
||||
codeExpect: `
|
||||
if (!condition) {
|
||||
formatProdWarningMessage(0, adj, noun);
|
||||
}
|
||||
`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
it('inserts warning comment for unknown messages', async () => {
|
||||
await expectTransform({
|
||||
codeBefore: `devInvariant(condition, ${JSON.stringify(NEW_MSG)})`,
|
||||
codeExpect: `
|
||||
/*FIXME (minify-errors-in-prod): Unminified error message in production build!*/
|
||||
if (!condition) {
|
||||
formatDevErrorMessage(\`A new invariant\`);
|
||||
}
|
||||
`,
|
||||
messageMapBefore: KNOWN_MSG_MAP,
|
||||
messageMapExpect: KNOWN_MSG_MAP,
|
||||
opts,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -50,6 +50,19 @@ function getErrorMap(filepath) {
|
||||
* @property {boolean} noMinify
|
||||
*/
|
||||
|
||||
const invariantExpressions = [
|
||||
{
|
||||
dev: 'formatDevErrorMessage',
|
||||
name: 'invariant',
|
||||
prod: 'formatProdErrorMessage',
|
||||
},
|
||||
{
|
||||
dev: 'formatDevErrorMessage',
|
||||
name: 'devInvariant',
|
||||
prod: 'formatProdWarningMessage',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {import('@babel/core')} babel
|
||||
* @param {Partial<TransformErrorMessagesOptions>} opts
|
||||
@ -66,120 +79,117 @@ module.exports = function (babel, opts) {
|
||||
const node = path.node;
|
||||
const {extractCodes, noMinify} =
|
||||
/** @type Partial<TransformErrorMessagesOptions> */ (file.opts);
|
||||
if (path.get('callee').isIdentifier({name: 'invariant'})) {
|
||||
// Turns this code:
|
||||
//
|
||||
// invariant(condition, 'A %s message that contains %s', adj, noun);
|
||||
//
|
||||
// into something equivalent to this:
|
||||
//
|
||||
// if (!condition) {
|
||||
// if (__DEV__ || ERR_CODE === undefined) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// } else {
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun)
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// where ERR_CODE is an error code: a unique identifier (a number
|
||||
// string) that references a verbose error message. The mapping is
|
||||
// stored in `scripts/error-codes/codes.json`.
|
||||
const condition = node.arguments[0];
|
||||
const errorMsgLiteral = evalToString(node.arguments[1]);
|
||||
const errorMsgExpressions = Array.from(node.arguments.slice(2));
|
||||
const errorMsgQuasis = errorMsgLiteral
|
||||
.split('%s')
|
||||
.map((raw) => t.templateElement({cooked: String.raw({raw}), raw}));
|
||||
|
||||
// Outputs:
|
||||
// `A ${adj} message that contains ${noun}`;
|
||||
const devMessage = t.templateLiteral(
|
||||
errorMsgQuasis,
|
||||
errorMsgExpressions,
|
||||
);
|
||||
|
||||
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.',
|
||||
);
|
||||
}
|
||||
|
||||
// We extract the prodErrorId even if we are not using it
|
||||
// so we can extractCodes in a non-production build.
|
||||
let prodErrorId = errorMap.getOrAddToErrorMap(
|
||||
errorMsgLiteral,
|
||||
extractCodes,
|
||||
);
|
||||
|
||||
/** @type {babel.types.CallExpression} */
|
||||
let callExpression;
|
||||
if (noMinify || prodErrorId === undefined) {
|
||||
// Error minification is disabled for this build.
|
||||
for (const {name, dev, prod} of invariantExpressions) {
|
||||
if (path.get('callee').isIdentifier({name})) {
|
||||
// Turns this code:
|
||||
//
|
||||
// Outputs:
|
||||
// if (!condition) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// }
|
||||
const formatDevErrorMessageIdentifier =
|
||||
helperModuleImports.addDefault(
|
||||
path,
|
||||
'shared/formatDevErrorMessage',
|
||||
{
|
||||
nameHint: 'formatDevErrorMessage',
|
||||
},
|
||||
);
|
||||
callExpression = t.callExpression(formatDevErrorMessageIdentifier, [
|
||||
devMessage,
|
||||
]);
|
||||
} else {
|
||||
// Error minification enabled for this build.
|
||||
// invariant(condition, 'A %s message that contains %s', adj, noun);
|
||||
//
|
||||
// into something equivalent to this:
|
||||
//
|
||||
// Outputs:
|
||||
// if (!condition) {
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun)
|
||||
// if (__DEV__ || ERR_CODE === undefined) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// } else {
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun)
|
||||
// };
|
||||
// }
|
||||
|
||||
// Import ReactErrorProd
|
||||
const formatProdErrorMessageIdentifier =
|
||||
helperModuleImports.addDefault(
|
||||
path,
|
||||
'shared/formatProdErrorMessage',
|
||||
{
|
||||
nameHint: 'formatProdErrorMessage',
|
||||
},
|
||||
//
|
||||
// where ERR_CODE is an error code: a unique identifier (a number
|
||||
// string) that references a verbose error message. The mapping is
|
||||
// stored in `scripts/error-codes/codes.json`.
|
||||
const condition = node.arguments[0];
|
||||
const errorMsgLiteral = evalToString(node.arguments[1]);
|
||||
const errorMsgExpressions = Array.from(node.arguments.slice(2));
|
||||
const errorMsgQuasis = errorMsgLiteral
|
||||
.split('%s')
|
||||
.map((raw) =>
|
||||
t.templateElement({cooked: String.raw({raw}), raw}),
|
||||
);
|
||||
|
||||
// Outputs:
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun);
|
||||
callExpression = t.callExpression(
|
||||
formatProdErrorMessageIdentifier,
|
||||
[t.numericLiteral(prodErrorId), ...errorMsgExpressions],
|
||||
// `A ${adj} message that contains ${noun}`;
|
||||
const devMessage = t.templateLiteral(
|
||||
errorMsgQuasis,
|
||||
errorMsgExpressions,
|
||||
);
|
||||
}
|
||||
|
||||
parentStatementPath.replaceWith(
|
||||
t.ifStatement(
|
||||
t.unaryExpression('!', condition),
|
||||
t.blockStatement([t.expressionStatement(callExpression)]),
|
||||
),
|
||||
);
|
||||
const parentStatementPath = path.parentPath;
|
||||
if (parentStatementPath.type !== 'ExpressionStatement') {
|
||||
throw path.buildCodeFrameError(
|
||||
`${name}() cannot be called from expression context. Move the call to its own statement.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!noMinify && prodErrorId === undefined) {
|
||||
// There is no error code for this message. Add an inline comment
|
||||
// that flags this as an unminified error. This allows the build
|
||||
// to proceed, while also allowing a post-build linter to detect it.
|
||||
//
|
||||
// Outputs:
|
||||
// /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
|
||||
// if (!condition) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// }
|
||||
parentStatementPath.addComment(
|
||||
'leading',
|
||||
'FIXME (minify-errors-in-prod): Unminified error message in production build!',
|
||||
// We extract the prodErrorId even if we are not using it
|
||||
// so we can extractCodes in a non-production build.
|
||||
let prodErrorId = errorMap.getOrAddToErrorMap(
|
||||
errorMsgLiteral,
|
||||
extractCodes,
|
||||
);
|
||||
|
||||
/** @type {babel.types.CallExpression} */
|
||||
let callExpression;
|
||||
if (noMinify || prodErrorId === undefined) {
|
||||
// Error minification is disabled for this build.
|
||||
//
|
||||
// Outputs:
|
||||
// if (!condition) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// }
|
||||
const formatDevErrorMessageIdentifier =
|
||||
helperModuleImports.addDefault(path, `shared/${dev}`, {
|
||||
nameHint: dev,
|
||||
});
|
||||
callExpression = t.callExpression(
|
||||
formatDevErrorMessageIdentifier,
|
||||
[devMessage],
|
||||
);
|
||||
} else {
|
||||
// Error minification enabled for this build.
|
||||
//
|
||||
// Outputs:
|
||||
// if (!condition) {
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun)
|
||||
// }
|
||||
|
||||
// Import ReactErrorProd
|
||||
const formatProdErrorMessageIdentifier =
|
||||
helperModuleImports.addDefault(path, `shared/${prod}`, {
|
||||
nameHint: prod,
|
||||
});
|
||||
|
||||
// Outputs:
|
||||
// formatProdErrorMessage(ERR_CODE, adj, noun);
|
||||
callExpression = t.callExpression(
|
||||
formatProdErrorMessageIdentifier,
|
||||
[t.numericLiteral(prodErrorId), ...errorMsgExpressions],
|
||||
);
|
||||
}
|
||||
|
||||
parentStatementPath.replaceWith(
|
||||
t.ifStatement(
|
||||
t.unaryExpression('!', condition),
|
||||
t.blockStatement([t.expressionStatement(callExpression)]),
|
||||
),
|
||||
);
|
||||
|
||||
if (!noMinify && prodErrorId === undefined) {
|
||||
// There is no error code for this message. Add an inline comment
|
||||
// that flags this as an unminified error. This allows the build
|
||||
// to proceed, while also allowing a post-build linter to detect it.
|
||||
//
|
||||
// Outputs:
|
||||
// /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
|
||||
// if (!condition) {
|
||||
// formatDevErrorMessage(`A ${adj} message that contains ${noun}`);
|
||||
// }
|
||||
parentStatementPath.addComment(
|
||||
'leading',
|
||||
'FIXME (minify-errors-in-prod): Unminified error message in production build!',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Reference in New Issue
Block a user