eslint-plugin-n 15.0.1 → 15.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,7 +93,7 @@ $ npm install --save-dev eslint eslint-plugin-n
93
93
  | Rule ID | Description | |
94
94
  |:--------|:------------|:--:|
95
95
  | [n/callback-return](./docs/rules/callback-return.md) | require `return` statements after callbacks | |
96
- | [n/exports-style](./docs/rules/exports-style.md) | enforce either `module.exports` or `exports` | |
96
+ | [n/exports-style](./docs/rules/exports-style.md) | enforce either `module.exports` or `exports` | ✒️ |
97
97
  | [n/file-extension-in-import](./docs/rules/file-extension-in-import.md) | enforce the style of file extensions in `import` declarations | ✒️ |
98
98
  | [n/global-require](./docs/rules/global-require.md) | require `require()` calls to be placed at top-level module scope | |
99
99
  | [n/no-mixed-requires](./docs/rules/no-mixed-requires.md) | disallow `require` calls to be mixed with regular variable declarations | |
@@ -167,7 +167,7 @@ Those preset config:
167
167
 
168
168
  ## 📰 Changelog
169
169
 
170
- - [GitHub Releases](https://github.com/weiran-zsd/eslint-plugin-n/releases)
170
+ - [GitHub Releases](https://github.com/weiran-zsd/eslint-plugin-node/releases)
171
171
 
172
172
  ## ❤️ Contributing
173
173
 
@@ -139,6 +139,90 @@ function getExportsNodes(scope) {
139
139
  return variable.references.map(reference => reference.identifier)
140
140
  }
141
141
 
142
+ function getReplacementForProperty(property, sourceCode) {
143
+ if (property.type !== "Property" || property.kind !== "init") {
144
+ // We don't have a nice syntax for adding these directly on the exports object. Give up on fixing the whole thing:
145
+ // property.kind === 'get':
146
+ // module.exports = { get foo() { ... } }
147
+ // property.kind === 'set':
148
+ // module.exports = { set foo() { ... } }
149
+ // property.type === 'SpreadElement':
150
+ // module.exports = { ...foo }
151
+ return null
152
+ }
153
+
154
+ let fixedValue = sourceCode.getText(property.value)
155
+ if (property.method) {
156
+ fixedValue = `function${
157
+ property.value.generator ? "*" : ""
158
+ } ${fixedValue}`
159
+ if (property.value.async) {
160
+ fixedValue = `async ${fixedValue}`
161
+ }
162
+ }
163
+ const lines = sourceCode
164
+ .getCommentsBefore(property)
165
+ .map(comment => sourceCode.getText(comment))
166
+ if (property.key.type === "Literal" || property.computed) {
167
+ // String or dynamic key:
168
+ // module.exports = { [ ... ]: ... } or { "foo": ... }
169
+ lines.push(
170
+ `exports[${sourceCode.getText(property.key)}] = ${fixedValue};`
171
+ )
172
+ } else if (property.key.type === "Identifier") {
173
+ // Regular identifier:
174
+ // module.exports = { foo: ... }
175
+ lines.push(`exports.${property.key.name} = ${fixedValue};`)
176
+ } else {
177
+ // Some other unknown property type. Conservatively give up on fixing the whole thing.
178
+ return null
179
+ }
180
+ lines.push(
181
+ ...sourceCode
182
+ .getCommentsAfter(property)
183
+ .map(comment => sourceCode.getText(comment))
184
+ )
185
+ return lines.join("\n")
186
+ }
187
+
188
+ // Check for a top level module.exports = { ... }
189
+ function isModuleExportsObjectAssignment(node) {
190
+ return (
191
+ node.parent.type === "AssignmentExpression" &&
192
+ node.parent.parent.type === "ExpressionStatement" &&
193
+ node.parent.parent.parent.type === "Program" &&
194
+ node.parent.right.type === "ObjectExpression"
195
+ )
196
+ }
197
+
198
+ // Check for module.exports.foo or module.exports.bar reference or assignment
199
+ function isModuleExportsReference(node) {
200
+ return (
201
+ node.parent.type === "MemberExpression" && node.parent.object === node
202
+ )
203
+ }
204
+
205
+ function fixModuleExports(node, sourceCode, fixer) {
206
+ if (isModuleExportsReference(node)) {
207
+ return fixer.replaceText(node, "exports")
208
+ }
209
+ if (!isModuleExportsObjectAssignment(node)) {
210
+ return null
211
+ }
212
+ const statements = []
213
+ const properties = node.parent.right.properties
214
+ for (const property of properties) {
215
+ const statement = getReplacementForProperty(property, sourceCode)
216
+ if (statement) {
217
+ statements.push(statement)
218
+ } else {
219
+ // No replacement available, give up on the whole thing
220
+ return null
221
+ }
222
+ }
223
+ return fixer.replaceText(node.parent, statements.join("\n\n"))
224
+ }
225
+
142
226
  module.exports = {
143
227
  meta: {
144
228
  docs: {
@@ -148,7 +232,7 @@ module.exports = {
148
232
  url: "https://github.com/weiran-zsd/eslint-plugin-node/blob/HEAD/docs/rules/exports-style.md",
149
233
  },
150
234
  type: "suggestion",
151
- fixable: null,
235
+ fixable: "code",
152
236
  schema: [
153
237
  {
154
238
  //
@@ -253,6 +337,9 @@ module.exports = {
253
337
  loc: getLocation(node),
254
338
  message:
255
339
  "Unexpected access to 'module.exports'. Use 'exports' instead.",
340
+ fix(fixer) {
341
+ return fixModuleExports(node, sourceCode, fixer)
342
+ },
256
343
  })
257
344
  }
258
345
 
@@ -10,6 +10,11 @@ const visitImport = require("../util/visit-import")
10
10
  const packageNamePattern = /^(?:@[^/\\]+[/\\])?[^/\\]+$/u
11
11
  const corePackageOverridePattern =
12
12
  /^(?:assert|async_hooks|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|http2|https|inspector|module|net|os|path|perf_hooks|process|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|trace_events|tty|url|util|v8|vm|worker_threads|zlib)[/\\]$/u
13
+ const typescriptFileExtensionsMapping = {
14
+ ".ts": ".js",
15
+ ".cts": ".cjs",
16
+ ".mts": ".mjs",
17
+ }
13
18
 
14
19
  /**
15
20
  * Get all file extensions of the files which have the same basename.
@@ -31,6 +36,27 @@ function getExistingExtensions(filePath) {
31
36
  }
32
37
  }
33
38
 
39
+ /**
40
+ * Get the file extension that should be added in an import statement,
41
+ * based on the given file extension of the referenced file.
42
+ *
43
+ * For example, in typescript, when referencing another typescript from a typescript file,
44
+ * a .js extension should be used instead of the original .ts extension of the referenced file.
45
+ * @param {string} referencedFileExt The original file extension of the referenced file.
46
+ * @param {string} referencingFileExt The original file extension of the file the contains the import statement.
47
+ * @returns {string} The file extension to append to the import statement.
48
+ */
49
+ function getFileExtensionToAdd(referencedFileExt, referencingFileExt) {
50
+ if (
51
+ referencingFileExt in typescriptFileExtensionsMapping &&
52
+ referencedFileExt in typescriptFileExtensionsMapping
53
+ ) {
54
+ return typescriptFileExtensionsMapping[referencedFileExt]
55
+ }
56
+
57
+ return referencedFileExt
58
+ }
59
+
34
60
  module.exports = {
35
61
  meta: {
36
62
  docs: {
@@ -85,16 +111,26 @@ module.exports = {
85
111
 
86
112
  // Verify.
87
113
  if (style === "always" && ext !== originalExt) {
114
+ const referencingFileExt = path.extname(
115
+ context.getPhysicalFilename()
116
+ )
117
+ const fileExtensionToAdd = getFileExtensionToAdd(
118
+ ext,
119
+ referencingFileExt
120
+ )
88
121
  context.report({
89
122
  node,
90
123
  messageId: "requireExt",
91
- data: { ext },
124
+ data: { ext: fileExtensionToAdd },
92
125
  fix(fixer) {
93
126
  if (existingExts.length !== 1) {
94
127
  return null
95
128
  }
96
129
  const index = node.range[1] - 1
97
- return fixer.insertTextBeforeRange([index, index], ext)
130
+ return fixer.insertTextBeforeRange(
131
+ [index, index],
132
+ fileExtensionToAdd
133
+ )
98
134
  },
99
135
  })
100
136
  } else if (style === "never" && ext === originalExt) {
@@ -6,7 +6,7 @@
6
6
 
7
7
  const { rules: esRules } = require("eslint-plugin-es")
8
8
  const { getInnermostScope } = require("eslint-utils")
9
- const { Range } = require("semver") //eslint-disable-line no-unused-vars
9
+ const { Range } = require("semver")
10
10
  const getConfiguredNodeVersion = require("../../util/get-configured-node-version")
11
11
  const getSemverRange = require("../../util/get-semver-range")
12
12
  const mergeVisitorsInPlace = require("../../util/merge-visitors-in-place")
@@ -378,7 +378,7 @@ const features = {
378
378
  ruleId: "no-dynamic-import",
379
379
  cases: [
380
380
  {
381
- supported: null,
381
+ supported: new Range(">=12.17 <13 || >=13.2"),
382
382
  messageId: "no-dynamic-import",
383
383
  },
384
384
  ],
@@ -453,10 +453,15 @@ function defineVisitor(context, options) {
453
453
  * @returns {boolean} `true` if it's supporting.
454
454
  */
455
455
  function isNotSupportingVersion(aCase) {
456
- return (
457
- !aCase.supported ||
458
- options.version.intersects(getSemverRange(`<${aCase.supported}`))
459
- )
456
+ if (!aCase.supported) {
457
+ return true
458
+ }
459
+
460
+ if (aCase.supported instanceof Range) {
461
+ return !options.version.intersects(aCase.supported)
462
+ }
463
+
464
+ return options.version.intersects(getSemverRange(`<${aCase.supported}`))
460
465
  }
461
466
 
462
467
  /**
@@ -645,7 +650,7 @@ module.exports = {
645
650
  "no-bigint-property-names":
646
651
  "Bigint literal property names are not supported yet.",
647
652
  "no-dynamic-import":
648
- "'import()' expressions are not supported yet.",
653
+ "'import()' expressions are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.",
649
654
  "no-optional-chaining":
650
655
  "Optional chainings are not supported until Node.js {{supported}}. The configured version range is '{{version}}'.",
651
656
  "no-nullish-coalescing-operators":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-n",
3
- "version": "15.0.1",
3
+ "version": "15.2.1",
4
4
  "description": "Additional ESLint's rules for Node.js",
5
5
  "engines": {
6
6
  "node": ">=12.22.0"
@@ -13,35 +13,34 @@
13
13
  "eslint": ">=7.0.0"
14
14
  },
15
15
  "dependencies": {
16
- "builtins": "^4.0.0",
16
+ "builtins": "^5.0.1",
17
17
  "eslint-plugin-es": "^4.1.0",
18
18
  "eslint-utils": "^3.0.0",
19
19
  "ignore": "^5.1.1",
20
- "is-core-module": "^2.3.0",
21
- "minimatch": "^3.0.4",
20
+ "is-core-module": "^2.9.0",
21
+ "minimatch": "^3.1.2",
22
22
  "resolve": "^1.10.1",
23
- "semver": "^6.3.0"
23
+ "semver": "^7.3.7"
24
24
  },
25
25
  "devDependencies": {
26
- "@mysticatea/eslint-plugin": "^13.0.0",
27
26
  "codecov": "^3.3.0",
28
- "esbuild": "^0.14.14",
29
- "eslint": "^8.3.0",
30
- "eslint-config-prettier": "^8.3.0",
27
+ "esbuild": "^0.14.39",
28
+ "eslint": "^8.15.0",
29
+ "eslint-config-prettier": "^8.5.0",
31
30
  "eslint-plugin-eslint-plugin": "^4.0.1",
32
31
  "eslint-plugin-n": "file:.",
33
- "fast-glob": "^2.2.6",
34
- "globals": "^11.12.0",
35
- "husky": "^7.0.4",
32
+ "fast-glob": "^3.2.11",
33
+ "globals": "^13.14.0",
34
+ "husky": "^8.0.1",
36
35
  "import-meta-resolve": "^1.1.1",
37
- "lint-staged": "^12.3.1",
38
- "mocha": "^6.1.4",
39
- "nyc": "^14.0.0",
36
+ "lint-staged": "^12.4.1",
37
+ "mocha": "^10.0.0",
38
+ "nyc": "^15.1.0",
40
39
  "opener": "^1.5.1",
41
- "prettier": "^2.5.1",
40
+ "prettier": "^2.6.2",
42
41
  "punycode": "^2.1.1",
43
- "release-it": "^14.11.6",
44
- "rimraf": "^2.6.3"
42
+ "release-it": "^15.0.0",
43
+ "rimraf": "^3.0.2"
45
44
  },
46
45
  "scripts": {
47
46
  "build": "node scripts/update",