Skip to content

Commit a4423eb

Browse files
committed
tools: add require-order rule
This rule enforces `{a, b, c} = require()` is in alphabetical order.
1 parent 8c35a4e commit a4423eb

File tree

5 files changed

+152
-17
lines changed

5 files changed

+152
-17
lines changed

lib/.eslintrc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ rules:
166166
node-core/lowercase-name-for-primitive: error
167167
node-core/non-ascii-character: error
168168
node-core/no-array-destructuring: error
169+
node-core/require-order: error
169170
node-core/prefer-primordials:
170171
- error
171172
- name: AggregateError
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if ((!common.hasCrypto) || (!common.hasIntl)) {
5+
common.skip('ESLint tests require crypto and Intl');
6+
}
7+
8+
common.skipIfEslintMissing();
9+
10+
const RuleTester = require('../../tools/node_modules/eslint').RuleTester;
11+
const rule = require('../../tools/eslint-rules/require-order');
12+
13+
new RuleTester({
14+
parserOptions: { ecmaVersion: 6 },
15+
})
16+
.run('require-order', rule, {
17+
valid: [
18+
"const A = require('path')",
19+
'const A = primordials',
20+
"const { A } = require('path')",
21+
'const { A } = primordials',
22+
"const { A, B } = require('path')",
23+
'const { A, B } = primordials',
24+
"const { B: a, A: b } = require('path')",
25+
'const { B: a, A: b } = primordials',
26+
],
27+
invalid: [
28+
{
29+
code: "const { B, A } = require('path')",
30+
errors: [{ message: 'A should occur before B' }]
31+
},
32+
{
33+
code: 'const { B, A } = primordials',
34+
errors: [{ message: 'A should occur before B' }]
35+
},
36+
{
37+
code: "const { A: b, A: a } = require('path')",
38+
errors: [{ message: 'a should occur before b' }]
39+
},
40+
{
41+
code: 'const { A: b, A: a } = primordials',
42+
errors: [{ message: 'a should occur before b' }]
43+
},
44+
{
45+
code: "const { C, B, A } = require('path')",
46+
errors: [{ message: 'B should occur before C' }]
47+
},
48+
{
49+
code: 'const { C, B, A } = primordials',
50+
errors: [{ message: 'B should occur before C' }]
51+
},
52+
{
53+
code: "const { C, A, B } = require('path')",
54+
errors: [{ message: 'A should occur before C' }]
55+
},
56+
{
57+
code: 'const { C, A, B } = primordials',
58+
errors: [{ message: 'A should occur before C' }]
59+
},
60+
]
61+
});

tools/eslint-rules/no-duplicate-requires.js

+2-17
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,12 @@
44
*/
55
'use strict';
66

7-
const { isRequireCall, isString } = require('./rules-utils.js');
7+
const { isTopLevelRequireCall, isString } = require('./rules-utils.js');
88

99
//------------------------------------------------------------------------------
1010
// Rule Definition
1111
//------------------------------------------------------------------------------
1212

13-
const secondLevelTypes = [
14-
'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression',
15-
'ClassBody', 'MethodDefinition',
16-
];
17-
18-
function isTopLevel(node) {
19-
while (!secondLevelTypes.includes(node.type)) {
20-
node = node.parent;
21-
if (!node) {
22-
return true;
23-
}
24-
}
25-
return false;
26-
}
27-
2813
module.exports = (context) => {
2914
if (context.parserOptions.sourceType === 'module') {
3015
return {};
@@ -43,7 +28,7 @@ module.exports = (context) => {
4328

4429
const rules = {
4530
CallExpression: (node) => {
46-
if (isRequireCall(node) && isTopLevel(node)) {
31+
if (isTopLevelRequireCall(node)) {
4732
const moduleName = getRequiredModuleNameFromCall(node);
4833
if (moduleName === undefined) {
4934
return;

tools/eslint-rules/require-order.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
3+
const { isTopLevelRequireCall } = require('./rules-utils.js');
4+
5+
const getRequiredNodesFromRequireCallExpression = (node) => {
6+
if (
7+
node &&
8+
node.parent &&
9+
node.parent.id &&
10+
node.parent.id.type === 'ObjectPattern'
11+
) {
12+
return node.parent.id.properties.map((p) => p.value);
13+
}
14+
};
15+
16+
const isPrimordialsDeclarator = (node) => {
17+
return (
18+
node &&
19+
node.type === 'VariableDeclarator' &&
20+
node.init?.name === 'primordials'
21+
);
22+
};
23+
24+
const getRequiredNodesFromPrimordialsDeclarator = (node) => {
25+
if (
26+
node &&
27+
node.id &&
28+
node.id.type === 'ObjectPattern'
29+
) {
30+
return node.id.properties.map((p) => p.value);
31+
}
32+
};
33+
34+
const findOutOfOrderIndex = (nodes) => {
35+
for (let i = 0; i < nodes.length - 1; i++) {
36+
if (nodes[i].name > nodes[i + 1].name) return i;
37+
}
38+
return -1;
39+
};
40+
41+
module.exports = {
42+
create(context) {
43+
const reportOutOfOrder = (target, requiredNodes, outOfOrderIndex) => {
44+
const before = requiredNodes[outOfOrderIndex];
45+
const after = requiredNodes[outOfOrderIndex + 1];
46+
context.report(
47+
target,
48+
`${after.name} should occur before ${before.name}`
49+
);
50+
};
51+
52+
return {
53+
CallExpression(node) {
54+
if (!isTopLevelRequireCall(node)) return;
55+
const requiredNodes = getRequiredNodesFromRequireCallExpression(node);
56+
if (!requiredNodes) return;
57+
const outOfOrderIndex = findOutOfOrderIndex(requiredNodes);
58+
if (outOfOrderIndex === -1) return;
59+
reportOutOfOrder(node, requiredNodes, outOfOrderIndex);
60+
},
61+
VariableDeclarator(node) {
62+
if (!isPrimordialsDeclarator(node)) return;
63+
const requiredNodes = getRequiredNodesFromPrimordialsDeclarator(node);
64+
if (!requiredNodes) return;
65+
const outOfOrderIndex = findOutOfOrderIndex(requiredNodes);
66+
if (outOfOrderIndex === -1) return;
67+
reportOutOfOrder(node, requiredNodes, outOfOrderIndex);
68+
},
69+
};
70+
},
71+
};

tools/eslint-rules/rules-utils.js

+17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ function isRequireCall(node) {
88
}
99
module.exports.isRequireCall = isRequireCall;
1010

11+
module.exports.isTopLevelRequireCall = function(node) {
12+
const secondLevelTypes = [
13+
'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression',
14+
'ClassBody', 'MethodDefinition',
15+
];
16+
const isTopLevel = (node) => {
17+
while (!secondLevelTypes.includes(node.type)) {
18+
node = node.parent;
19+
if (!node) {
20+
return true;
21+
}
22+
}
23+
return false;
24+
};
25+
return isRequireCall(node) && isTopLevel(node);
26+
};
27+
1128
module.exports.isString = function(node) {
1229
return node && node.type === 'Literal' && typeof node.value === 'string';
1330
};

0 commit comments

Comments
 (0)