Skip to content

Commit faa3ed8

Browse files
committed
feat: Types for lib/rules
1 parent b6157bf commit faa3ed8

23 files changed

+328
-208
lines changed

lib/eslint-utils.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
declare module "eslint-plugin-es-x" {
2+
export const rules: NonNullable<import('eslint').ESLint.Plugin["rules"]>;
3+
}
4+
15
declare module "@eslint-community/eslint-utils" {
26
import * as estree from 'estree';
37
import * as eslint from 'eslint';

lib/rules/exports-style.js

+80-42
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,52 @@
44
*/
55
"use strict"
66

7+
/**
8+
* @typedef {import('estree').Node & { parent?: Node }} Node
9+
*/
10+
711
/*istanbul ignore next */
812
/**
913
* This function is copied from https://github.com/eslint/eslint/blob/2355f8d0de1d6732605420d15ddd4f1eee3c37b6/lib/ast-utils.js#L648-L684
1014
*
11-
* @param {import('eslint').Rule.Node} node - The node to get.
15+
* @param {Node} node - The node to get.
1216
* @returns {string | null | undefined} The property name if static. Otherwise, null.
1317
* @private
1418
*/
1519
function getStaticPropertyName(node) {
20+
/** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */
1621
let prop = null
1722

1823
switch (node?.type) {
1924
case "Property":
2025
case "MethodDefinition":
21-
prop = /** @type {import('estree').Property} */ (node).key
26+
prop = node.key
2227
break
2328

2429
case "MemberExpression":
25-
prop = /** @type {import('estree').MemberExpression} */ (node)
26-
.property
30+
prop = node.property
2731
break
2832

2933
// no default
3034
}
3135

3236
switch (prop?.type) {
3337
case "Literal":
34-
return String(/** @type {import('estree').Literal} */ (prop).value)
38+
return String(prop.value)
3539

3640
case "TemplateLiteral":
37-
if (
38-
/** @type {import('estree').TemplateLiteral} */ (prop)
39-
.expressions.length === 0 &&
40-
/** @type {import('estree').TemplateLiteral} */ (prop).quasis
41-
.length === 1
42-
) {
43-
return /** @type {import('estree').TemplateLiteral} */ (prop)
44-
.quasis[0].value.cooked
41+
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
42+
return prop.quasis[0].value.cooked
4543
}
4644
break
4745

4846
case "Identifier":
49-
if (!node.computed) {
47+
if (
48+
!(
49+
/** @type {import('estree').MemberExpression} */ (node)
50+
.computed
51+
)
52+
) {
5053
return prop.name
5154
}
5255
break
@@ -60,12 +63,13 @@ function getStaticPropertyName(node) {
6063
/**
6164
* Checks whether the given node is assignee or not.
6265
*
63-
* @param {import('eslint').Rule.Node} node - The node to check.
66+
* @param {Node} node - The node to check.
6467
* @returns {boolean} `true` if the node is assignee.
6568
*/
6669
function isAssignee(node) {
6770
return (
68-
node.parent.type === "AssignmentExpression" && node.parent.left === node
71+
node.parent?.type === "AssignmentExpression" &&
72+
node.parent.left === node
6973
)
7074
}
7175

@@ -75,15 +79,15 @@ function isAssignee(node) {
7579
* This is used to distinguish 2 assignees belong to the same assignment.
7680
* If the node is not an assignee, this returns null.
7781
*
78-
* @param {import('eslint').Rule.Node} leafNode - The node to get.
79-
* @returns {import('eslint').Rule.Node|null} The top assignment expression node, or null.
82+
* @param {Node} leafNode - The node to get.
83+
* @returns {Node|null} The top assignment expression node, or null.
8084
*/
8185
function getTopAssignment(leafNode) {
8286
let node = leafNode
8387

8488
// Skip MemberExpressions.
8589
while (
86-
node.parent.type === "MemberExpression" &&
90+
node.parent?.type === "MemberExpression" &&
8791
node.parent.object === node
8892
) {
8993
node = node.parent
@@ -95,7 +99,7 @@ function getTopAssignment(leafNode) {
9599
}
96100

97101
// Find the top.
98-
while (node.parent.type === "AssignmentExpression") {
102+
while (node.parent?.type === "AssignmentExpression") {
99103
node = node.parent
100104
}
101105

@@ -105,31 +109,33 @@ function getTopAssignment(leafNode) {
105109
/**
106110
* Gets top assignment nodes of the given node list.
107111
*
108-
* @param {import('eslint').Rule.Node[]} nodes - The node list to get.
109-
* @returns {import('eslint').Rule.Node[]} Gotten top assignment nodes.
112+
* @param {Node[]} nodes - The node list to get.
113+
* @returns {Node[]} Gotten top assignment nodes.
110114
*/
111115
function createAssignmentList(nodes) {
112-
return /** @type {import('eslint').Rule.Node[]} */ (
113-
nodes.map(getTopAssignment).filter(Boolean)
114-
)
116+
return /** @type {Node[]} */ (nodes.map(getTopAssignment).filter(Boolean))
115117
}
116118

117119
/**
118120
* Gets the reference of `module.exports` from the given scope.
119121
*
120122
* @param {import('eslint').Scope.Scope} scope - The scope to get.
121-
* @returns {import('eslint').Rule.Node[]} Gotten MemberExpression node list.
123+
* @returns {Node[]} Gotten MemberExpression node list.
122124
*/
123125
function getModuleExportsNodes(scope) {
124126
const variable = scope.set.get("module")
125127
if (variable == null) {
126128
return []
127129
}
128130
return variable.references
129-
.map(reference => reference.identifier.parent)
131+
.map(
132+
reference =>
133+
/** @type {Node & { parent: Node }} */ (reference.identifier)
134+
.parent
135+
)
130136
.filter(
131137
node =>
132-
node.type === "MemberExpression" &&
138+
node?.type === "MemberExpression" &&
133139
getStaticPropertyName(node) === "exports"
134140
)
135141
}
@@ -149,6 +155,11 @@ function getExportsNodes(scope) {
149155
return variable.references.map(reference => reference.identifier)
150156
}
151157

158+
/**
159+
* @param {Node} property
160+
* @param {import('eslint').SourceCode} sourceCode
161+
* @returns {string | null}
162+
*/
152163
function getReplacementForProperty(property, sourceCode) {
153164
if (property.type !== "Property" || property.kind !== "init") {
154165
// We don't have a nice syntax for adding these directly on the exports object. Give up on fixing the whole thing:
@@ -162,7 +173,7 @@ function getReplacementForProperty(property, sourceCode) {
162173
}
163174

164175
let fixedValue = sourceCode.getText(property.value)
165-
if (property.method) {
176+
if (property.value.type === "FunctionExpression" && property.method) {
166177
fixedValue = `function${
167178
property.value.generator ? "*" : ""
168179
} ${fixedValue}`
@@ -172,6 +183,7 @@ function getReplacementForProperty(property, sourceCode) {
172183
}
173184
const lines = sourceCode
174185
.getCommentsBefore(property)
186+
// @ts-expect-error getText supports both BaseNode and BaseNodeWithoutComments
175187
.map(comment => sourceCode.getText(comment))
176188
if (property.key.type === "Literal" || property.computed) {
177189
// String or dynamic key:
@@ -190,28 +202,43 @@ function getReplacementForProperty(property, sourceCode) {
190202
lines.push(
191203
...sourceCode
192204
.getCommentsAfter(property)
205+
// @ts-expect-error getText supports both BaseNode and BaseNodeWithoutComments
193206
.map(comment => sourceCode.getText(comment))
194207
)
195208
return lines.join("\n")
196209
}
197210

198-
// Check for a top level module.exports = { ... }
211+
/**
212+
* Check for a top level module.exports = { ... }
213+
* @param {Node} node
214+
* @returns {node is {parent: import('estree').AssignmentExpression & {parent: import('estree').ExpressionStatement, right: import('estree').ObjectExpression}}}
215+
*/
199216
function isModuleExportsObjectAssignment(node) {
200217
return (
201-
node.parent.type === "AssignmentExpression" &&
202-
node.parent.parent.type === "ExpressionStatement" &&
203-
node.parent.parent.parent.type === "Program" &&
218+
node.parent?.type === "AssignmentExpression" &&
219+
node.parent?.parent?.type === "ExpressionStatement" &&
220+
node.parent.parent.parent?.type === "Program" &&
204221
node.parent.right.type === "ObjectExpression"
205222
)
206223
}
207224

208-
// Check for module.exports.foo or module.exports.bar reference or assignment
225+
/**
226+
* Check for module.exports.foo or module.exports.bar reference or assignment
227+
* @param {Node} node
228+
* @returns {node is import('estree').MemberExpression}
229+
*/
209230
function isModuleExportsReference(node) {
210231
return (
211-
node.parent.type === "MemberExpression" && node.parent.object === node
232+
node.parent?.type === "MemberExpression" && node.parent.object === node
212233
)
213234
}
214235

236+
/**
237+
* @param {Node} node
238+
* @param {import('eslint').SourceCode} sourceCode
239+
* @param {import('eslint').Rule.RuleFixer} fixer
240+
* @returns {import('eslint').Rule.Fix | null}
241+
*/
215242
function fixModuleExports(node, sourceCode, fixer) {
216243
if (isModuleExportsReference(node)) {
217244
return fixer.replaceText(node, "exports")
@@ -280,14 +307,16 @@ module.exports = {
280307
* module.exports = foo
281308
* ^^^^^^^^^^^^^^^^
282309
*
283-
* @param {import('eslint').Rule.Node} node - The node of `exports`/`module.exports`.
284-
* @returns {Location} The location info of reports.
310+
* @param {Node} node - The node of `exports`/`module.exports`.
311+
* @returns {import('estree').SourceLocation} The location info of reports.
285312
*/
286313
function getLocation(node) {
287314
const token = sourceCode.getTokenAfter(node)
288315
return {
289-
start: node.loc.start,
290-
end: token.loc.end,
316+
start: /** @type {import('estree').SourceLocation} */ (node.loc)
317+
.start,
318+
end: /** @type {import('estree').SourceLocation} */ (token?.loc)
319+
?.end,
291320
}
292321
}
293322

@@ -306,9 +335,11 @@ module.exports = {
306335

307336
for (const node of exportsNodes) {
308337
// Skip if it's a batch assignment.
338+
const topAssignment = getTopAssignment(node)
309339
if (
340+
topAssignment &&
310341
assignList.length > 0 &&
311-
assignList.indexOf(getTopAssignment(node)) !== -1
342+
assignList.indexOf(topAssignment) !== -1
312343
) {
313344
continue
314345
}
@@ -340,7 +371,10 @@ module.exports = {
340371
for (const node of moduleExportsNodes) {
341372
// Skip if it's a batch assignment.
342373
if (assignList.length > 0) {
343-
const found = assignList.indexOf(getTopAssignment(node))
374+
const topAssignment = getTopAssignment(node)
375+
const found = topAssignment
376+
? assignList.indexOf(topAssignment)
377+
: -1
344378
if (found !== -1) {
345379
batchAssignList.push(assignList[found])
346380
assignList.splice(found, 1)
@@ -366,8 +400,12 @@ module.exports = {
366400
continue
367401
}
368402

403+
const topAssignment = getTopAssignment(node)
369404
// Check if it's a batch assignment.
370-
if (batchAssignList.indexOf(getTopAssignment(node)) !== -1) {
405+
if (
406+
topAssignment &&
407+
batchAssignList.indexOf(topAssignment) !== -1
408+
) {
371409
continue
372410
}
373411

lib/rules/file-extension-in-import.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ module.exports = {
7070
*/
7171
function verify({ filePath, name, node, moduleType }) {
7272
// Ignore if it's not resolved to a file or it's a bare module.
73-
if (moduleType !== "relative" && moduleType !== "absolute") {
73+
if (
74+
(moduleType !== "relative" && moduleType !== "absolute") ||
75+
filePath == null
76+
) {
7477
return
7578
}
7679

lib/rules/global-require.js

+11-16
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,28 @@ const ACCEPTABLE_PARENTS = [
1717

1818
/**
1919
* Finds the eslint-scope reference in the given scope.
20-
* @param {Object} scope The scope to search.
21-
* @param {ASTNode} node The identifier node.
22-
* @returns {Reference|null} Returns the found reference or null if none were found.
20+
* @param {import('eslint').Scope.Scope} scope The scope to search.
21+
* @param {import('estree').Node} node The identifier node.
22+
* @returns {import('eslint').Scope.Reference|undefined} Returns the found reference or null if none were found.
2323
*/
2424
function findReference(scope, node) {
25-
const references = scope.references.filter(
25+
return scope.references.find(
2626
reference =>
27-
reference.identifier.range[0] === node.range[0] &&
28-
reference.identifier.range[1] === node.range[1]
27+
reference.identifier.range?.[0] === node.range?.[0] &&
28+
reference.identifier.range?.[1] === node.range?.[1]
2929
)
30-
31-
/* istanbul ignore else: correctly returns null */
32-
if (references.length === 1) {
33-
return references[0]
34-
}
35-
return null
3630
}
3731

3832
/**
3933
* Checks if the given identifier node is shadowed in the given scope.
40-
* @param {Object} scope The current scope.
41-
* @param {ASTNode} node The identifier node to check.
34+
* @param {import('eslint').Scope.Scope} scope The current scope.
35+
* @param {import('estree').Node} node The identifier node to check.
4236
* @returns {boolean} Whether or not the name is shadowed.
4337
*/
4438
function isShadowed(scope, node) {
4539
const reference = findReference(scope, node)
4640

47-
return reference && reference.resolved && reference.resolved.defs.length > 0
41+
return Boolean(reference?.resolved?.defs?.length)
4842
}
4943

5044
/** @type {import('eslint').Rule.RuleModule} */
@@ -73,7 +67,8 @@ module.exports = {
7367
sourceCode.getScope?.(node) ?? context.getScope() //TODO: remove context.getScope() when dropping support for ESLint < v9
7468

7569
if (
76-
node.callee.name === "require" &&
70+
/** @type {import('estree').Identifier} */ (node.callee)
71+
.name === "require" &&
7772
!isShadowed(currentScope, node.callee)
7873
) {
7974
const isGoodRequire = (

lib/rules/handle-callback-err.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ module.exports = {
5555

5656
/**
5757
* Get the parameters of a given function scope.
58-
* @param {Object} scope The function scope.
59-
* @returns {Array} All parameters of the given scope.
58+
* @param {import('eslint').Scope.Scope} scope The function scope.
59+
* @returns {import('eslint').Scope.Variable[]} All parameters of the given scope.
6060
*/
6161
function getParameters(scope) {
6262
return scope.variables.filter(
@@ -67,7 +67,7 @@ module.exports = {
6767

6868
/**
6969
* Check to see if we're handling the error object properly.
70-
* @param {ASTNode} node The AST node to check.
70+
* @param {import('estree').Node} node The AST node to check.
7171
* @returns {void}
7272
*/
7373
function checkForError(node) {

0 commit comments

Comments
 (0)