Skip to content

Commit d7afbed

Browse files
authored
feat: implement prefer-import/assert-strict rule (#553)
1 parent d499fc1 commit d7afbed

5 files changed

Lines changed: 291 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ For [Shareable Configs](https://eslint.org/docs/latest/developer-guide/shareable
148148
| [prefer-global/timers](docs/rules/prefer-global/timers.md) | enforce either global timer functions or `require("timers")` | | | |
149149
| [prefer-global/url](docs/rules/prefer-global/url.md) | enforce either `URL` or `require("url").URL` | | | |
150150
| [prefer-global/url-search-params](docs/rules/prefer-global/url-search-params.md) | enforce either `URLSearchParams` or `require("url").URLSearchParams` | | | |
151+
| [prefer-import/assert-strict](docs/rules/prefer-import/assert-strict.md) | enforce using `node:assert/strict` instead of `node:assert`. | | | |
151152
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | enforce using the `node:` protocol when importing Node.js builtin modules. | | 🔧 | |
152153
| [prefer-process-get-builtin-module](docs/rules/prefer-process-get-builtin-module.md) | enforce using `process.getBuiltinModule()` to load Node.js built-in modules | | | |
153154
| [prefer-promises/dns](docs/rules/prefer-promises/dns.md) | enforce `require("dns").promises` | | | |
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# n/prefer-import/assert-strict
2+
3+
📝 Enforce using `node:assert/strict` instead of `node:assert`.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
## 📖 Rule Details
8+
9+
The `node:assert` module exposes [legacy](https://nodejs.org/api/assert.html#legacy-assertion-mode), non-strict assertion methods. The `node:assert/strict` module changes the legacy methods to use strict equality.
10+
11+
👍 Examples of **correct** code for this rule:
12+
13+
```js
14+
/*eslint n/prefer-import/assert-strict: error */
15+
16+
import assert from "node:assert/strict"
17+
import("node:assert/strict")
18+
const assert = require("node:assert/strict")
19+
20+
// These forms already select strict assertion mode.
21+
import { strict as strictAssert } from "node:assert"
22+
const requiredAssert = require("node:assert").strict
23+
```
24+
25+
👎 Examples of **incorrect** code for this rule:
26+
27+
```js
28+
/*eslint n/prefer-import/assert-strict: error */
29+
30+
import assert from "node:assert"
31+
import("node:assert")
32+
const assert = require("node:assert")
33+
```
34+
35+
## 🔎 Implementation
36+
37+
- [Rule source](../../../lib/rules/prefer-import/assert-strict.js)
38+
- [Test source](../../../tests/lib/rules/prefer-import/assert-strict.js)

lib/all-rules.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import preferGlobalTextEncoder from "./rules/prefer-global/text-encoder.js"
4040
import preferGlobalUrlSearchParams from "./rules/prefer-global/url-search-params.js"
4141
import preferGlobalUrl from "./rules/prefer-global/url.js"
4242
import preferGlobalTimers from "./rules/prefer-global/timers.js"
43+
import preferImportAssertStrict from "./rules/prefer-import/assert-strict.js"
4344
import preferNodeProtocol from "./rules/prefer-node-protocol.js"
4445
import preferProcessGetBuiltinModule from "./rules/prefer-process-get-builtin-module.js"
4546
import preferPromisesDns from "./rules/prefer-promises/dns.js"
@@ -87,6 +88,7 @@ const allRules = {
8788
"prefer-global/url-search-params": preferGlobalUrlSearchParams,
8889
"prefer-global/url": preferGlobalUrl,
8990
"prefer-global/timers": preferGlobalTimers,
91+
"prefer-import/assert-strict": preferImportAssertStrict,
9092
"prefer-node-protocol": preferNodeProtocol,
9193
"prefer-process-get-builtin-module": preferProcessGetBuiltinModule,
9294
"prefer-promises/dns": preferPromisesDns,
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* @author baevm
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
6+
const messageId = "preferAssertStrict"
7+
8+
/**
9+
* @param {import('estree').ImportDeclaration} node
10+
* @returns {boolean}
11+
*/
12+
function importsOnlyStrict(node) {
13+
return (
14+
node.specifiers.length > 0 &&
15+
node.specifiers.every(
16+
specifier =>
17+
specifier.type === "ImportSpecifier" &&
18+
(specifier.imported.type === "Identifier"
19+
? specifier.imported.name
20+
: specifier.imported.value) === "strict"
21+
)
22+
)
23+
}
24+
25+
/**
26+
* @param {import('estree').ExportNamedDeclaration} node
27+
* @returns {boolean}
28+
*/
29+
function exportsOnlyStrict(node) {
30+
return (
31+
node.specifiers.length > 0 &&
32+
node.specifiers.every(
33+
specifier =>
34+
specifier.type === "ExportSpecifier" &&
35+
(specifier.local.type === "Identifier"
36+
? specifier.local.name
37+
: specifier.local.value) === "strict"
38+
)
39+
)
40+
}
41+
42+
/**
43+
* @param {import('estree').Property | import('estree').RestElement} property
44+
* @returns {boolean}
45+
*/
46+
function isStrictProperty(property) {
47+
if (property.type !== "Property") {
48+
return false
49+
}
50+
51+
return property.computed
52+
? property.key.type === "Literal" && property.key.value === "strict"
53+
: property.key.type === "Identifier" && property.key.name === "strict"
54+
}
55+
56+
/**
57+
* @param {import('estree').Pattern | import('estree').MemberExpression} node
58+
* @returns {boolean}
59+
*/
60+
function accessesOnlyStrict(node) {
61+
if (node.type === "MemberExpression") {
62+
return node.computed
63+
? node.property.type === "Literal" &&
64+
node.property.value === "strict"
65+
: node.property.type === "Identifier" &&
66+
node.property.name === "strict"
67+
}
68+
69+
return (
70+
node.type === "ObjectPattern" &&
71+
node.properties.length > 0 &&
72+
node.properties.every(isStrictProperty)
73+
)
74+
}
75+
76+
/**
77+
* @param {import('estree').CallExpression & import('eslint').Rule.NodeParentExtension} node
78+
* @returns {boolean}
79+
*/
80+
function requiresOnlyStrict(node) {
81+
const { parent } = node
82+
83+
if (parent.type === "MemberExpression" && parent.object === node) {
84+
return accessesOnlyStrict(parent)
85+
}
86+
if (parent.type === "VariableDeclarator" && parent.init === node) {
87+
return accessesOnlyStrict(parent.id)
88+
}
89+
if (parent.type === "AssignmentExpression" && parent.right === node) {
90+
return accessesOnlyStrict(parent.left)
91+
}
92+
return false
93+
}
94+
95+
/**
96+
* @param {import('estree').Node | null | undefined} node
97+
* @param {import('eslint').Rule.RuleContext} context
98+
*/
99+
function checkSource(node, context) {
100+
if (
101+
node?.type !== "Literal" ||
102+
typeof node.value !== "string" ||
103+
node.value !== "node:assert"
104+
) {
105+
return
106+
}
107+
108+
context.report({
109+
node,
110+
messageId,
111+
})
112+
}
113+
114+
/** @type {import('../rule-module.js').RuleModule} */
115+
export default {
116+
meta: {
117+
docs: {
118+
description:
119+
"enforce using `node:assert/strict` instead of `node:assert`.",
120+
recommended: false,
121+
url: "https://github.com/eslint-community/eslint-plugin-n/blob/HEAD/docs/rules/prefer-import/assert-strict.md",
122+
},
123+
messages: {
124+
[messageId]: "Prefer `node:assert/strict` over `node:assert`.",
125+
},
126+
schema: [],
127+
type: "suggestion",
128+
},
129+
create(context) {
130+
return {
131+
CallExpression(node) {
132+
if (
133+
node.callee.type === "Identifier" &&
134+
node.callee.name === "require" &&
135+
!requiresOnlyStrict(node)
136+
) {
137+
checkSource(node.arguments[0], context)
138+
}
139+
},
140+
ExportAllDeclaration(node) {
141+
checkSource(node.source, context)
142+
},
143+
ExportNamedDeclaration(node) {
144+
if (!exportsOnlyStrict(node)) {
145+
checkSource(node.source, context)
146+
}
147+
},
148+
ImportDeclaration(node) {
149+
if (!importsOnlyStrict(node)) {
150+
checkSource(node.source, context)
151+
}
152+
},
153+
ImportExpression(node) {
154+
checkSource(node.source, context)
155+
},
156+
}
157+
},
158+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* @author baevm
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
6+
import { RuleTester } from "#test-helpers"
7+
import rule from "../../../../lib/rules/prefer-import/assert-strict.js"
8+
9+
new RuleTester({
10+
languageOptions: {
11+
ecmaVersion: 2020,
12+
sourceType: "module",
13+
},
14+
}).run("prefer-import/assert-strict", rule, {
15+
valid: [
16+
'import assert from "node:assert/strict";',
17+
'import assert from "assert";',
18+
'import assert from "node:assert/assert";',
19+
'import assert from "another-assert";',
20+
'import { strict } from "node:assert";',
21+
'import { strict as assert } from "node:assert";',
22+
'import { strict, strict as assert } from "node:assert";',
23+
'export { strict } from "node:assert";',
24+
'export { strict as assert } from "node:assert";',
25+
'export { strict, strict as assert } from "node:assert";',
26+
'import("node:assert/strict");',
27+
'require("node:assert/strict");',
28+
'const assert = require("node:assert").strict;',
29+
'const assert = require("node:assert")["strict"];',
30+
'const { strict } = require("node:assert");',
31+
'const { strict: assert } = require("node:assert");',
32+
'const { ["strict"]: assert } = require("node:assert");',
33+
'({ strict: assert } = require("node:assert"));',
34+
'notRequire("node:assert");',
35+
"require(`node:assert`);",
36+
"require(moduleName);",
37+
],
38+
invalid: [
39+
{
40+
code: 'import assert from "node:assert";',
41+
errors: [{ messageId: "preferAssertStrict" }],
42+
},
43+
{
44+
code: "import * as assert from 'node:assert';",
45+
errors: [{ messageId: "preferAssertStrict" }],
46+
},
47+
{
48+
code: 'import "node:assert";',
49+
errors: [{ messageId: "preferAssertStrict" }],
50+
},
51+
{
52+
code: 'import assert, { strict } from "node:assert";',
53+
errors: [{ messageId: "preferAssertStrict" }],
54+
},
55+
{
56+
code: 'import { strict, equal } from "node:assert";',
57+
errors: [{ messageId: "preferAssertStrict" }],
58+
},
59+
{
60+
code: 'export { default as assert } from "node:assert";',
61+
errors: [{ messageId: "preferAssertStrict" }],
62+
},
63+
{
64+
code: 'export { strict, equal } from "node:assert";',
65+
errors: [{ messageId: "preferAssertStrict" }],
66+
},
67+
{
68+
code: 'export * from "node:assert";',
69+
errors: [{ messageId: "preferAssertStrict" }],
70+
},
71+
{
72+
code: 'import("node:assert");',
73+
errors: [{ messageId: "preferAssertStrict" }],
74+
},
75+
{
76+
code: 'require("node:assert");',
77+
errors: [{ messageId: "preferAssertStrict" }],
78+
},
79+
{
80+
code: 'require("node:assert", extra);',
81+
errors: [{ messageId: "preferAssertStrict" }],
82+
},
83+
{
84+
code: 'const { strict, equal } = require("node:assert");',
85+
errors: [{ messageId: "preferAssertStrict" }],
86+
},
87+
{
88+
code: 'require("node:\\u0061ssert");',
89+
errors: [{ messageId: "preferAssertStrict" }],
90+
},
91+
],
92+
})

0 commit comments

Comments
 (0)