mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
PR-URL: https://github.com/nodejs/node/pull/60150 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
104 lines
3.5 KiB
JavaScript
104 lines
3.5 KiB
JavaScript
'use strict';
|
|
|
|
const message =
|
|
'Assertions must be wrapped into `common.mustCall` or `common.mustCallAtLeast`';
|
|
|
|
|
|
const requireCall = 'CallExpression[callee.name="require"]';
|
|
const assertModuleSpecifier = '/^(node:)?assert(.strict)?$/';
|
|
|
|
function findEnclosingFunction(node) {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node) break;
|
|
|
|
if (node.type !== 'ArrowFunctionExpression' && node.type !== 'FunctionExpression') continue;
|
|
|
|
if (node.parent?.type === 'CallExpression') {
|
|
if (node.parent.callee === node) continue; // IIFE
|
|
|
|
if (
|
|
node.parent.callee.type === 'MemberExpression' &&
|
|
(node.parent.callee.object.type === 'ArrayExpression' || node.parent.callee.object.type === 'Identifier') &&
|
|
node.parent.callee.property.name === 'forEach'
|
|
) continue; // `[].forEach()` call
|
|
} else if (node.parent?.type === 'NewExpression') {
|
|
if (node.parent.callee.type === 'Identifier' && node.parent.callee.name === 'Promise') continue;
|
|
}
|
|
break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function isMustCallOrMustCallAtLeast(str) {
|
|
return str === 'mustCall' || str === 'mustCallAtLeast';
|
|
}
|
|
|
|
function isMustCallOrTest(str) {
|
|
return str === 'test' || str === 'it' || isMustCallOrMustCallAtLeast(str);
|
|
}
|
|
|
|
module.exports = {
|
|
meta: {
|
|
fixable: 'code',
|
|
},
|
|
create: function(context) {
|
|
return {
|
|
[`:function CallExpression:matches(${[
|
|
'[callee.type="Identifier"][callee.value=/^mustCall(AtLeast)?$/]',
|
|
'[callee.object.name="assert"][callee.property.name!="fail"]',
|
|
'[callee.object.name="common"][callee.property.name=/^mustCall(AtLeast)?$/]',
|
|
].join(',')})`]: (node) => {
|
|
const enclosingFn = findEnclosingFunction(node);
|
|
const parent = enclosingFn?.parent;
|
|
if (!parent) return; // Top-level
|
|
if (parent.type === 'CallExpression') {
|
|
switch (parent.callee.type) {
|
|
case 'MemberExpression':
|
|
if (
|
|
parent.callee.property.name === 'then' ||
|
|
{
|
|
assert: (name) => name === 'rejects' || name === 'throws', // assert.throws or assert.rejects
|
|
common: isMustCallOrMustCallAtLeast, // common.mustCall or common.mustCallAtLeast
|
|
process: (name) =>
|
|
(name === 'nextTick' && enclosingFn === parent.arguments[0]) || // process.nextTick
|
|
( // process.on('exit', …)
|
|
(name === 'on' || name === 'once') &&
|
|
enclosingFn === parent.arguments[1] &&
|
|
parent.arguments[0].type === 'Literal' &&
|
|
parent.arguments[0].value === 'exit'
|
|
),
|
|
}[parent.callee.object.name]?.(parent.callee.property.name)
|
|
) {
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'Identifier':
|
|
if (isMustCallOrTest(parent.callee.name)) return;
|
|
break;
|
|
}
|
|
}
|
|
context.report({
|
|
node,
|
|
message,
|
|
});
|
|
},
|
|
|
|
[[
|
|
`ImportDeclaration[source.value=${assertModuleSpecifier}]:not(${[
|
|
'length=1',
|
|
'0.type=/^Import(Default|Namespace)Specifier$/',
|
|
'0.local.name="assert"',
|
|
].map((selector) => `[specifiers.${selector}]`).join('')})`,
|
|
`:not(VariableDeclarator[id.name="assert"])>${requireCall}[arguments.0.value=${assertModuleSpecifier}]`,
|
|
].join(',')]: (node) => {
|
|
context.report({
|
|
node,
|
|
message: 'Only assign `node:assert` to `assert`',
|
|
});
|
|
},
|
|
};
|
|
},
|
|
};
|