-
Notifications
You must be signed in to change notification settings - Fork 0
/
global-require.ts
75 lines (65 loc) · 2.38 KB
/
global-require.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import type { TSESTree } from '@typescript-eslint/typescript-estree';
import type { TSESLint } from '@typescript-eslint/utils';
import { createRule } from '../util/create-rule';
const ACCEPTABLE_PARENTS = new Set([
'AssignmentExpression',
'VariableDeclarator',
'MemberExpression',
'ExpressionStatement',
'CallExpression',
'ConditionalExpression',
'Program',
'VariableDeclaration',
'TSAsExpression',
'TSTypeAssertion',
]);
/**
* Finds the eslint-scope reference in the given scope.
* @param {TSESLint.Scope.Scope} scope The scope to search.
* @param {TSESTree.Node} node The identifier node.
* @returns {TSESLint.Scope.Reference|null} Returns the found reference or null if none were found.
*/
const findReference = (scope: TSESLint.Scope.Scope, node: TSESTree.Identifier): TSESLint.Scope.Reference | null => {
const references = scope.references.filter(
(reference) => reference.identifier.range[0] === node.range[0] && reference.identifier.range[1] === node.range[1],
);
/* istanbul ignore else: correctly returns null */
if (references.length === 1) {
return references[0];
}
return null;
};
/**
* Checks if the given identifier node is shadowed in the given scope.
* @param {TSESLint.Scope.Scope} scope The current scope.
* @param {TSESTree.Node} node The identifier node to check.
* @returns {boolean} Whether or not the name is shadowed.
*/
const isShadowed = (scope: TSESLint.Scope.Scope, node: TSESTree.Identifier): boolean => {
const reference = findReference(scope, node);
return !!reference?.resolved?.defs.length;
};
export const category = 'Stylistic Issues';
export default createRule<[], 'unexpected'>({
name: 'global-require',
meta: {
type: 'suggestion',
docs: { description: 'require `require()` calls to be placed at top-level module scope', recommended: false },
schema: [],
messages: { unexpected: 'Unexpected require().' },
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const currentScope = context.getScope();
if ('name' in node.callee && node.callee.name === 'require' && !isShadowed(currentScope, node.callee)) {
const isGoodRequire = context.getAncestors().every((parent) => ACCEPTABLE_PARENTS.has(parent.type));
if (!isGoodRequire) {
context.report({ node, messageId: 'unexpected' });
}
}
},
};
},
});