2015-06-04 06:09:50 +08:00
|
|
|
/**
|
2022-10-18 23:19:24 +08:00
|
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
2015-06-04 06:09:50 +08:00
|
|
|
*
|
2017-09-25 04:48:13 +08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2015-06-04 06:09:50 +08:00
|
|
|
*
|
|
|
|
|
* @emails react-core
|
2017-11-08 02:09:33 +08:00
|
|
|
*/
|
2015-06-04 06:09:50 +08:00
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
Lint rule for unminified errors (#15757)
* Lint rule for unminified errors
Add a lint rule that fails if an invariant message is not part of the
error code map.
The goal is to be more disciplined about adding and modifiying
production error codes. Error codes should be consistent across releases
even if their wording changes, for continuity in logs.
Currently, error codes are added to the error code map via an automated
script that runs right before release. The problem with this approach is
that if someone modifies an error message in the source, but neglects to
modify the corresponding message in the error code map, then the message
will be assigned a new error code, instead of reusing the existing one.
Because the error extraction script only runs before a release, people
rarely modify the error code map in practice. By moving the extraction
step to the PR stage, it forces the author to consider whether the
message should be assigned a new error code. It also allows the reviewer
to review the changes.
The trade off is that it requires more effort and context to land new
error messages, or to modify existing ones, particular for new
contributors who are not familiar with our processes.
Since we already expect users to lint their code, I would argue the
additional burden is marginal. Even if they forget to run the lint
command locally, they will get quick feedback from the CI lint job,
which typically finishes within 2-3 minutes.
* Add unreleased error messages to map
2019-05-30 02:29:04 +08:00
|
|
|
const fs = require('fs');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const existingErrorMap = JSON.parse(
|
|
|
|
|
fs.readFileSync(path.resolve(__dirname, '../error-codes/codes.json'))
|
|
|
|
|
);
|
|
|
|
|
const messages = new Set();
|
|
|
|
|
Object.keys(existingErrorMap).forEach(key =>
|
|
|
|
|
messages.add(existingErrorMap[key])
|
|
|
|
|
);
|
|
|
|
|
|
2015-06-04 06:09:50 +08:00
|
|
|
/**
|
2019-12-11 11:28:14 +08:00
|
|
|
* The warning() function takes format strings as its second
|
2015-06-04 06:09:50 +08:00
|
|
|
* argument.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-09-07 03:42:40 +08:00
|
|
|
module.exports = {
|
|
|
|
|
meta: {
|
|
|
|
|
schema: [],
|
|
|
|
|
},
|
|
|
|
|
create(context) {
|
|
|
|
|
// we also allow literal strings and concatenated literal strings
|
|
|
|
|
function getLiteralString(node) {
|
|
|
|
|
if (node.type === 'Literal' && typeof node.value === 'string') {
|
|
|
|
|
return node.value;
|
|
|
|
|
} else if (node.type === 'BinaryExpression' && node.operator === '+') {
|
|
|
|
|
const l = getLiteralString(node.left);
|
|
|
|
|
const r = getLiteralString(node.right);
|
|
|
|
|
if (l !== null && r !== null) {
|
|
|
|
|
return l + r;
|
|
|
|
|
}
|
2015-06-04 06:09:50 +08:00
|
|
|
}
|
2021-09-07 03:42:40 +08:00
|
|
|
return null;
|
2015-06-04 06:09:50 +08:00
|
|
|
}
|
|
|
|
|
|
2021-09-07 03:42:40 +08:00
|
|
|
return {
|
|
|
|
|
CallExpression: function(node) {
|
|
|
|
|
// This could be a little smarter by checking context.getScope() to see
|
|
|
|
|
// how warning/invariant was defined.
|
|
|
|
|
const isWarning =
|
|
|
|
|
node.callee.type === 'MemberExpression' &&
|
|
|
|
|
node.callee.object.type === 'Identifier' &&
|
|
|
|
|
node.callee.object.name === 'console' &&
|
|
|
|
|
node.callee.property.type === 'Identifier' &&
|
|
|
|
|
(node.callee.property.name === 'error' ||
|
|
|
|
|
node.callee.property.name === 'warn');
|
|
|
|
|
if (!isWarning) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const name = 'console.' + node.callee.property.name;
|
|
|
|
|
if (node.arguments.length < 1) {
|
|
|
|
|
context.report(node, '{{name}} takes at least one argument', {
|
2019-12-15 02:09:25 +08:00
|
|
|
name,
|
2021-09-07 03:42:40 +08:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const format = getLiteralString(node.arguments[0]);
|
|
|
|
|
if (format === null) {
|
|
|
|
|
context.report(
|
|
|
|
|
node,
|
|
|
|
|
'The first argument to {{name}} must be a string literal',
|
|
|
|
|
{name}
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (format.length < 10 || /^[s\W]*$/.test(format)) {
|
|
|
|
|
context.report(
|
|
|
|
|
node,
|
|
|
|
|
'The {{name}} format should be able to uniquely identify this ' +
|
|
|
|
|
'warning. Please, use a more descriptive format than: {{format}}',
|
|
|
|
|
{name, format}
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// count the number of formatting substitutions, plus the first two args
|
|
|
|
|
const expectedNArgs = (format.match(/%s/g) || []).length + 1;
|
|
|
|
|
if (node.arguments.length !== expectedNArgs) {
|
|
|
|
|
context.report(
|
|
|
|
|
node,
|
|
|
|
|
'Expected {{expectedNArgs}} arguments in call to {{name}} based on ' +
|
|
|
|
|
'the number of "%s" substitutions, but got {{length}}',
|
|
|
|
|
{
|
|
|
|
|
expectedNArgs: expectedNArgs,
|
|
|
|
|
name,
|
|
|
|
|
length: node.arguments.length,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
2015-06-04 06:09:50 +08:00
|
|
|
};
|