Skip to content

Commit 18cdd53

Browse files
authored
refactor: Improve typescript types and strictness (#367)
1 parent 06d60ae commit 18cdd53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+664
-384
lines changed

lib/configs/_commons.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"use strict"
22

3-
module.exports.commonRules = /** @type {const} */ ({
3+
/**
4+
* @type {import('eslint').Linter.RulesRecord}
5+
*/
6+
module.exports.commonRules = {
47
"n/no-deprecated-api": "error",
58
"n/no-extraneous-import": "error",
69
"n/no-extraneous-require": "error",
@@ -16,4 +19,4 @@ module.exports.commonRules = /** @type {const} */ ({
1619
"n/no-unsupported-features/node-builtins": "error",
1720
"n/process-exit-as-throw": "error",
1821
"n/hashbang": "error",
19-
})
22+
}

lib/eslint-utils.d.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
declare module "eslint-plugin-es-x" {
2-
// @ts-ignore
32
export const rules: NonNullable<import('eslint').ESLint.Plugin["rules"]>;
43
}
54

65
declare module "@eslint-community/eslint-utils" {
7-
// @ts-ignore
86
import * as estree from 'estree';
9-
// @ts-ignore
107
import * as eslint from 'eslint';
118

129
type Node = estree.Node | estree.Expression;
@@ -39,7 +36,7 @@ declare module "@eslint-community/eslint-utils" {
3936
[READ]?: Info;
4037
[CALL]?: Info;
4138
[CONSTRUCT]?: Info;
42-
[key: string]: TraceMap<Info>;
39+
[key: string]: TraceMap<Info> | undefined;
4340
}
4441
type RichNode = eslint.Rule.Node | Node;
4542
type Reference<Info extends unknown> = {

lib/index.js

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,15 @@ const esmConfig = require("./configs/recommended-module")
55
const cjsConfig = require("./configs/recommended-script")
66
const recommendedConfig = require("./configs/recommended")
77

8-
/**
9-
* @typedef {{
10-
'recommended-module': import('eslint').ESLint.ConfigData;
11-
'recommended-script': import('eslint').ESLint.ConfigData;
12-
'recommended': import('eslint').ESLint.ConfigData;
13-
'flat/recommended-module': import('eslint').Linter.FlatConfig;
14-
'flat/recommended-script': import('eslint').Linter.FlatConfig;
15-
'flat/recommended': import('eslint').Linter.FlatConfig;
16-
'flat/mixed-esm-and-cjs': import('eslint').Linter.FlatConfig[];
17-
}} Configs
18-
*/
8+
/** @import { ESLint, Linter } from 'eslint' */
199

20-
/** @type {import('eslint').ESLint.Plugin & { configs: Configs }} */
21-
const plugin = {
10+
/** @type {ESLint.Plugin} */
11+
const base = {
2212
meta: {
2313
name: pkg.name,
2414
version: pkg.version,
2515
},
26-
rules: /** @type {Record<string, import('eslint').Rule.RuleModule>} */ ({
16+
rules: {
2717
"callback-return": require("./rules/callback-return"),
2818
"exports-style": require("./rules/exports-style"),
2919
"file-extension-in-import": require("./rules/file-extension-in-import"),
@@ -66,28 +56,38 @@ const plugin = {
6656
// Deprecated rules.
6757
"no-hide-core-modules": require("./rules/no-hide-core-modules"),
6858
shebang: require("./rules/shebang"),
69-
}),
70-
configs: {
71-
"recommended-module": { plugins: ["n"], ...esmConfig.eslintrc },
72-
"recommended-script": { plugins: ["n"], ...cjsConfig.eslintrc },
73-
recommended: { plugins: ["n"], ...recommendedConfig.eslintrc },
74-
"flat/recommended-module": { ...esmConfig.flat },
75-
"flat/recommended-script": { ...cjsConfig.flat },
76-
"flat/recommended": { ...recommendedConfig.flat },
77-
"flat/mixed-esm-and-cjs": [
78-
{ files: ["**/*.js"], ...recommendedConfig.flat },
79-
{ files: ["**/*.mjs"], ...esmConfig.flat },
80-
{ files: ["**/*.cjs"], ...cjsConfig.flat },
81-
],
8259
},
8360
}
61+
/**
62+
* @typedef {{
63+
* 'recommended-module': ESLint.ConfigData;
64+
* 'recommended-script': ESLint.ConfigData;
65+
* 'recommended': ESLint.ConfigData;
66+
* 'flat/recommended-module': Linter.Config;
67+
* 'flat/recommended-script': Linter.Config;
68+
* 'flat/recommended': Linter.Config;
69+
* 'flat/mixed-esm-and-cjs': Linter.Config[];
70+
* }} Configs
71+
*/
8472

85-
plugin.configs["flat/recommended-module"].plugins = { n: plugin }
86-
plugin.configs["flat/recommended-script"].plugins = { n: plugin }
87-
plugin.configs["flat/recommended"].plugins = { n: plugin }
88-
89-
for (const config of plugin.configs["flat/mixed-esm-and-cjs"]) {
90-
config.plugins = { n: plugin }
73+
/** @type {Configs} */
74+
const configs = {
75+
"recommended-module": { plugins: ["n"], ...esmConfig.eslintrc },
76+
"recommended-script": { plugins: ["n"], ...cjsConfig.eslintrc },
77+
recommended: { plugins: ["n"], ...recommendedConfig.eslintrc },
78+
"flat/recommended-module": { plugins: { n: base }, ...esmConfig.flat },
79+
"flat/recommended-script": { plugins: { n: base }, ...cjsConfig.flat },
80+
"flat/recommended": { plugins: { n: base }, ...recommendedConfig.flat },
81+
"flat/mixed-esm-and-cjs": [
82+
{ files: ["**/*.js"], plugins: { n: base }, ...recommendedConfig.flat },
83+
{ files: ["**/*.mjs"], plugins: { n: base }, ...esmConfig.flat },
84+
{ files: ["**/*.cjs"], plugins: { n: base }, ...cjsConfig.flat },
85+
],
9186
}
9287

93-
module.exports = plugin
88+
/** @type {ESLint.Plugin & { configs: Configs }} */
89+
module.exports = {
90+
meta: base.meta,
91+
rules: base.rules,
92+
configs: configs,
93+
}

lib/rules/callback-return.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ module.exports = {
8282
/**
8383
* Determines whether or not the callback is part of a callback expression.
8484
* @param {import('eslint').Rule.Node} node The callback node
85-
* @param {import('estree').Statement} parentNode The expression node
85+
* @param {import('estree').Statement} [parentNode] The expression node
8686
* @returns {boolean} Whether or not this is part of a callback expression
8787
*/
8888
function isCallbackExpression(node, parentNode) {
@@ -136,8 +136,7 @@ module.exports = {
136136
// block statements are part of functions and most if statements
137137
if (closestBlock?.type === "BlockStatement") {
138138
// find the last item in the block
139-
const lastItem =
140-
closestBlock.body[closestBlock.body.length - 1]
139+
const lastItem = closestBlock.body.at(-1)
141140

142141
// if the callback is the last thing in a block that might be ok
143142
if (isCallbackExpression(node, lastItem)) {
@@ -154,13 +153,10 @@ module.exports = {
154153
}
155154

156155
// ending a block with a return is also ok
157-
if (lastItem.type === "ReturnStatement") {
156+
if (lastItem?.type === "ReturnStatement") {
158157
// but only if the callback is immediately before
159158
if (
160-
isCallbackExpression(
161-
node,
162-
closestBlock.body[closestBlock.body.length - 2]
163-
)
159+
isCallbackExpression(node, closestBlock.body.at(-2))
164160
) {
165161
return
166162
}

lib/rules/exports-style.js

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
*/
55
"use strict"
66

7-
/**
8-
* @typedef {import('estree').Node & { parent?: Node }} Node
9-
*/
7+
const { hasParentNode } = require("../util/has-parent-node.js")
108

119
/*istanbul ignore next */
1210
/**
1311
* This function is copied from https://github.com/eslint/eslint/blob/2355f8d0de1d6732605420d15ddd4f1eee3c37b6/lib/ast-utils.js#L648-L684
1412
*
15-
* @param {Node} node - The node to get.
13+
* @param {import('estree').Node} node - The node to get.
1614
* @returns {string | null | undefined} The property name if static. Otherwise, null.
1715
* @private
1816
*/
@@ -39,17 +37,12 @@ function getStaticPropertyName(node) {
3937

4038
case "TemplateLiteral":
4139
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
42-
return prop.quasis[0].value.cooked
40+
return prop.quasis[0]?.value.cooked
4341
}
4442
break
4543

4644
case "Identifier":
47-
if (
48-
!(
49-
/** @type {import('estree').MemberExpression} */ (node)
50-
.computed
51-
)
52-
) {
45+
if (node.type === "MemberExpression" && node.computed === false) {
5346
return prop.name
5447
}
5548
break
@@ -63,11 +56,12 @@ function getStaticPropertyName(node) {
6356
/**
6457
* Checks whether the given node is assignee or not.
6558
*
66-
* @param {Node} node - The node to check.
59+
* @param {import('estree').Node} node - The node to check.
6760
* @returns {boolean} `true` if the node is assignee.
6861
*/
6962
function isAssignee(node) {
7063
return (
64+
hasParentNode(node) &&
7165
node.parent?.type === "AssignmentExpression" &&
7266
node.parent.left === node
7367
)
@@ -79,15 +73,16 @@ function isAssignee(node) {
7973
* This is used to distinguish 2 assignees belong to the same assignment.
8074
* If the node is not an assignee, this returns null.
8175
*
82-
* @param {Node} leafNode - The node to get.
83-
* @returns {Node|null} The top assignment expression node, or null.
76+
* @param {import('estree').Node} leafNode - The node to get.
77+
* @returns {import('estree').Node | null} The top assignment expression node, or null.
8478
*/
8579
function getTopAssignment(leafNode) {
8680
let node = leafNode
8781

8882
// Skip MemberExpressions.
8983
while (
90-
node.parent?.type === "MemberExpression" &&
84+
hasParentNode(node) &&
85+
node.parent.type === "MemberExpression" &&
9186
node.parent.object === node
9287
) {
9388
node = node.parent
@@ -99,7 +94,7 @@ function getTopAssignment(leafNode) {
9994
}
10095

10196
// Find the top.
102-
while (node.parent?.type === "AssignmentExpression") {
97+
while (hasParentNode(node) && node.parent.type === "AssignmentExpression") {
10398
node = node.parent
10499
}
105100

@@ -109,35 +104,41 @@ function getTopAssignment(leafNode) {
109104
/**
110105
* Gets top assignment nodes of the given node list.
111106
*
112-
* @param {Node[]} nodes - The node list to get.
113-
* @returns {Node[]} Gotten top assignment nodes.
107+
* @param {import('estree').Node[]} nodes - The node list to get.
108+
* @returns {import('estree').Node[]} Gotten top assignment nodes.
114109
*/
115110
function createAssignmentList(nodes) {
116-
return /** @type {Node[]} */ (nodes.map(getTopAssignment).filter(Boolean))
111+
return nodes.map(getTopAssignment).filter(input => input != null)
117112
}
118113

119114
/**
120115
* Gets the reference of `module.exports` from the given scope.
121116
*
122117
* @param {import('eslint').Scope.Scope} scope - The scope to get.
123-
* @returns {Node[]} Gotten MemberExpression node list.
118+
* @returns {import('estree').Node[]} Gotten MemberExpression node list.
124119
*/
125120
function getModuleExportsNodes(scope) {
126121
const variable = scope.set.get("module")
127122
if (variable == null) {
128123
return []
129124
}
130-
return variable.references
131-
.map(
132-
reference =>
133-
/** @type {Node & { parent: Node }} */ (reference.identifier)
134-
.parent
135-
)
136-
.filter(
137-
node =>
138-
node?.type === "MemberExpression" &&
139-
getStaticPropertyName(node) === "exports"
140-
)
125+
126+
/** @type {import('estree').Node[]} */
127+
const nodes = []
128+
129+
for (const reference of variable.references) {
130+
if (hasParentNode(reference.identifier) === false) {
131+
continue
132+
}
133+
const node = reference.identifier.parent
134+
if (
135+
node.type === "MemberExpression" &&
136+
getStaticPropertyName(node) === "exports"
137+
) {
138+
nodes.push(node)
139+
}
140+
}
141+
return nodes
141142
}
142143

143144
/**
@@ -156,7 +157,7 @@ function getExportsNodes(scope) {
156157
}
157158

158159
/**
159-
* @param {Node} property
160+
* @param {import('estree').Node} property
160161
* @param {import('eslint').SourceCode} sourceCode
161162
* @returns {string | null}
162163
*/
@@ -210,31 +211,36 @@ function getReplacementForProperty(property, sourceCode) {
210211

211212
/**
212213
* Check for a top level module.exports = { ... }
213-
* @param {Node} node
214+
* @param {import('estree').Node} node
214215
* @returns {node is {parent: import('estree').AssignmentExpression & {parent: import('estree').ExpressionStatement, right: import('estree').ObjectExpression}}}
215216
*/
216217
function isModuleExportsObjectAssignment(node) {
217218
return (
219+
hasParentNode(node) &&
218220
node.parent?.type === "AssignmentExpression" &&
221+
hasParentNode(node.parent) &&
219222
node.parent?.parent?.type === "ExpressionStatement" &&
223+
hasParentNode(node.parent.parent) &&
220224
node.parent.parent.parent?.type === "Program" &&
221225
node.parent.right.type === "ObjectExpression"
222226
)
223227
}
224228

225229
/**
226230
* Check for module.exports.foo or module.exports.bar reference or assignment
227-
* @param {Node} node
231+
* @param {import('estree').Node} node
228232
* @returns {node is import('estree').MemberExpression}
229233
*/
230234
function isModuleExportsReference(node) {
231235
return (
232-
node.parent?.type === "MemberExpression" && node.parent.object === node
236+
hasParentNode(node) &&
237+
node.parent?.type === "MemberExpression" &&
238+
node.parent.object === node
233239
)
234240
}
235241

236242
/**
237-
* @param {Node} node
243+
* @param {import('estree').Node} node
238244
* @param {import('eslint').SourceCode} sourceCode
239245
* @param {import('eslint').Rule.RuleFixer} fixer
240246
* @returns {import('eslint').Rule.Fix | null}
@@ -307,16 +313,17 @@ module.exports = {
307313
* module.exports = foo
308314
* ^^^^^^^^^^^^^^^^
309315
*
310-
* @param {Node} node - The node of `exports`/`module.exports`.
311-
* @returns {import('estree').SourceLocation} The location info of reports.
316+
* @param {import('estree').Node} node - The node of `exports`/`module.exports`.
317+
* @returns {import('estree').SourceLocation | undefined} The location info of reports.
312318
*/
313319
function getLocation(node) {
314320
const token = sourceCode.getTokenAfter(node)
321+
if (node.loc?.start == null || token?.loc?.end == null) {
322+
return
323+
}
315324
return {
316-
start: /** @type {import('estree').SourceLocation} */ (node.loc)
317-
.start,
318-
end: /** @type {import('estree').SourceLocation} */ (token?.loc)
319-
?.end,
325+
start: node.loc?.start,
326+
end: token?.loc?.end,
320327
}
321328
}
322329

0 commit comments

Comments
 (0)