eslint 8.0.1 → 8.1.0

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
@@ -242,7 +242,7 @@ Toru Nagashima
242
242
  </td><td align="center" valign="top" width="11%">
243
243
  <a href="https://github.com/aladdin-add">
244
244
  <img src="https://github.com/aladdin-add.png?s=75" width="75" height="75"><br />
245
- 薛定谔的猫
245
+ 唯然
246
246
  </a>
247
247
  </td></tr></tbody></table>
248
248
 
@@ -570,8 +570,10 @@ class CLIEngine {
570
570
  /**
571
571
  * Creates a new instance of the core CLI engine.
572
572
  * @param {CLIEngineOptions} providedOptions The options for this instance.
573
+ * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions.
574
+ * @param {Record<string,Plugin>|null} [additionalData.preloadedPlugins] Preloaded plugins.
573
575
  */
574
- constructor(providedOptions) {
576
+ constructor(providedOptions, { preloadedPlugins } = {}) {
575
577
  const options = Object.assign(
576
578
  Object.create(null),
577
579
  defaultOptions,
@@ -584,6 +586,13 @@ class CLIEngine {
584
586
  }
585
587
 
586
588
  const additionalPluginPool = new Map();
589
+
590
+ if (preloadedPlugins) {
591
+ for (const [id, plugin] of Object.entries(preloadedPlugins)) {
592
+ additionalPluginPool.set(id, plugin);
593
+ }
594
+ }
595
+
587
596
  const cacheFilePath = getCacheFile(
588
597
  options.cacheLocation || options.cacheFile,
589
598
  options.cwd
@@ -698,26 +707,6 @@ class CLIEngine {
698
707
  });
699
708
  }
700
709
 
701
-
702
- /**
703
- * Add a plugin by passing its configuration
704
- * @param {string} name Name of the plugin.
705
- * @param {Plugin} pluginObject Plugin configuration object.
706
- * @returns {void}
707
- */
708
- addPlugin(name, pluginObject) {
709
- const {
710
- additionalPluginPool,
711
- configArrayFactory,
712
- lastConfigArrays
713
- } = internalSlotsMap.get(this);
714
-
715
- additionalPluginPool.set(name, pluginObject);
716
- configArrayFactory.clearCache();
717
- lastConfigArrays.length = 1;
718
- lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
719
- }
720
-
721
710
  /**
722
711
  * Resolves the patterns passed into executeOnFiles() into glob-based patterns
723
712
  * for easier handling.
@@ -54,7 +54,7 @@ const { version } = require("../../package.json");
54
54
  * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
55
55
  * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
56
56
  * @property {string} [overrideConfigFile] The configuration file to use.
57
- * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
57
+ * @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
58
58
  * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
59
59
  * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
60
60
  * @property {string[]} [rulePaths] An array of directories to load custom rules from.
@@ -433,26 +433,13 @@ class ESLint {
433
433
  */
434
434
  constructor(options = {}) {
435
435
  const processedOptions = processOptions(options);
436
- const cliEngine = new CLIEngine(processedOptions);
436
+ const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
437
437
  const {
438
- additionalPluginPool,
439
438
  configArrayFactory,
440
439
  lastConfigArrays
441
440
  } = getCLIEngineInternalSlots(cliEngine);
442
441
  let updated = false;
443
442
 
444
- /*
445
- * Address `plugins` to add plugin implementations.
446
- * Operate the `additionalPluginPool` internal slot directly to avoid
447
- * using `addPlugin(id, plugin)` method that resets cache everytime.
448
- */
449
- if (options.plugins) {
450
- for (const [id, plugin] of Object.entries(options.plugins)) {
451
- additionalPluginPool.set(id, plugin);
452
- updated = true;
453
- }
454
- }
455
-
456
443
  /*
457
444
  * Address `overrideConfig` to set override config.
458
445
  * Operate the `configArrayFactory` internal slot directly because this
@@ -98,6 +98,13 @@ function getPossibleTypes(parsedSelector) {
98
98
  case "adjacent":
99
99
  return getPossibleTypes(parsedSelector.right);
100
100
 
101
+ case "class":
102
+ if (parsedSelector.name === "function") {
103
+ return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"];
104
+ }
105
+
106
+ return null;
107
+
101
108
  default:
102
109
  return null;
103
110
 
@@ -67,6 +67,7 @@ const { SourceCode } = require("../source-code");
67
67
  /**
68
68
  * A test case that is expected to pass lint.
69
69
  * @typedef {Object} ValidTestCase
70
+ * @property {string} [name] Name for the test case.
70
71
  * @property {string} code Code for the test case.
71
72
  * @property {any[]} [options] Options for the test case.
72
73
  * @property {{ [name: string]: any }} [settings] Settings for the test case.
@@ -81,6 +82,7 @@ const { SourceCode } = require("../source-code");
81
82
  /**
82
83
  * A test case that is expected to fail lint.
83
84
  * @typedef {Object} InvalidTestCase
85
+ * @property {string} [name] Name for the test case.
84
86
  * @property {string} code Code for the test case.
85
87
  * @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
86
88
  * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
@@ -124,6 +126,7 @@ let defaultConfig = { rules: {} };
124
126
  * configuration
125
127
  */
126
128
  const RuleTesterParameters = [
129
+ "name",
127
130
  "code",
128
131
  "filename",
129
132
  "options",
@@ -964,7 +967,7 @@ class RuleTester {
964
967
  RuleTester.describe("valid", () => {
965
968
  test.valid.forEach(valid => {
966
969
  RuleTester[valid.only ? "itOnly" : "it"](
967
- sanitize(typeof valid === "object" ? valid.code : valid),
970
+ sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
968
971
  () => {
969
972
  testValidTemplate(valid);
970
973
  }
@@ -975,7 +978,7 @@ class RuleTester {
975
978
  RuleTester.describe("invalid", () => {
976
979
  test.invalid.forEach(invalid => {
977
980
  RuleTester[invalid.only ? "itOnly" : "it"](
978
- sanitize(invalid.code),
981
+ sanitize(invalid.name || invalid.code),
979
982
  () => {
980
983
  testInvalidTemplate(invalid);
981
984
  }
@@ -221,6 +221,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
221
221
  "no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"),
222
222
  "no-unused-expressions": () => require("./no-unused-expressions"),
223
223
  "no-unused-labels": () => require("./no-unused-labels"),
224
+ "no-unused-private-class-members": () => require("./no-unused-private-class-members"),
224
225
  "no-unused-vars": () => require("./no-unused-vars"),
225
226
  "no-use-before-define": () => require("./no-use-before-define"),
226
227
  "no-useless-backreference": () => require("./no-useless-backreference"),
@@ -109,6 +109,8 @@ module.exports = {
109
109
  create(context) {
110
110
  const sourceCode = context.getSourceCode();
111
111
 
112
+ const tokensToIgnore = new WeakSet();
113
+
112
114
  /**
113
115
  * Reports a given token if there are not space(s) before the token.
114
116
  * @param {Token} token A token to report.
@@ -121,6 +123,7 @@ module.exports = {
121
123
  if (prevToken &&
122
124
  (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
123
125
  !isOpenParenOfTemplate(prevToken) &&
126
+ !tokensToIgnore.has(prevToken) &&
124
127
  astUtils.isTokenOnSameLine(prevToken, token) &&
125
128
  !sourceCode.isSpaceBetweenTokens(prevToken, token)
126
129
  ) {
@@ -147,6 +150,7 @@ module.exports = {
147
150
  if (prevToken &&
148
151
  (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
149
152
  !isOpenParenOfTemplate(prevToken) &&
153
+ !tokensToIgnore.has(prevToken) &&
150
154
  astUtils.isTokenOnSameLine(prevToken, token) &&
151
155
  sourceCode.isSpaceBetweenTokens(prevToken, token)
152
156
  ) {
@@ -173,6 +177,7 @@ module.exports = {
173
177
  if (nextToken &&
174
178
  (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
175
179
  !isCloseParenOfTemplate(nextToken) &&
180
+ !tokensToIgnore.has(nextToken) &&
176
181
  astUtils.isTokenOnSameLine(token, nextToken) &&
177
182
  !sourceCode.isSpaceBetweenTokens(token, nextToken)
178
183
  ) {
@@ -199,6 +204,7 @@ module.exports = {
199
204
  if (nextToken &&
200
205
  (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
201
206
  !isCloseParenOfTemplate(nextToken) &&
207
+ !tokensToIgnore.has(nextToken) &&
202
208
  astUtils.isTokenOnSameLine(token, nextToken) &&
203
209
  sourceCode.isSpaceBetweenTokens(token, nextToken)
204
210
  ) {
@@ -584,7 +590,14 @@ module.exports = {
584
590
  ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
585
591
  MethodDefinition: checkSpacingForProperty,
586
592
  PropertyDefinition: checkSpacingForProperty,
587
- Property: checkSpacingForProperty
593
+ Property: checkSpacingForProperty,
594
+
595
+ // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b`
596
+ "BinaryExpression[operator='>']"(node) {
597
+ const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
598
+
599
+ tokensToIgnore.add(operatorToken);
600
+ }
588
601
  };
589
602
  }
590
603
  };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @fileoverview Rule to flag declared but unused private class members
3
+ * @author Tim van der Lippe
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+
16
+ docs: {
17
+ description: "disallow unused private class members",
18
+ recommended: false,
19
+ url: "https://eslint.org/docs/rules/no-unused-private-class-members"
20
+ },
21
+
22
+ schema: [],
23
+
24
+ messages: {
25
+ unusedPrivateClassMember: "'{{classMemberName}}' is defined but never used."
26
+ }
27
+ },
28
+
29
+ create(context) {
30
+ const trackedClasses = [];
31
+
32
+ /**
33
+ * Check whether the current node is in a write only assignment.
34
+ * @param {ASTNode} privateIdentifierNode Node referring to a private identifier
35
+ * @returns {boolean} Whether the node is in a write only assignment
36
+ * @private
37
+ */
38
+ function isWriteOnlyAssignment(privateIdentifierNode) {
39
+ const parentStatement = privateIdentifierNode.parent.parent;
40
+ const isAssignmentExpression = parentStatement.type === "AssignmentExpression";
41
+
42
+ if (!isAssignmentExpression &&
43
+ parentStatement.type !== "ForInStatement" &&
44
+ parentStatement.type !== "ForOfStatement" &&
45
+ parentStatement.type !== "AssignmentPattern") {
46
+ return false;
47
+ }
48
+
49
+ // It is a write-only usage, since we still allow usages on the right for reads
50
+ if (parentStatement.left !== privateIdentifierNode.parent) {
51
+ return false;
52
+ }
53
+
54
+ // For any other operator (such as '+=') we still consider it a read operation
55
+ if (isAssignmentExpression && parentStatement.operator !== "=") {
56
+
57
+ /*
58
+ * However, if the read operation is "discarded" in an empty statement, then
59
+ * we consider it write only.
60
+ */
61
+ return parentStatement.parent.type === "ExpressionStatement";
62
+ }
63
+
64
+ return true;
65
+ }
66
+
67
+ //--------------------------------------------------------------------------
68
+ // Public
69
+ //--------------------------------------------------------------------------
70
+
71
+ return {
72
+
73
+ // Collect all declared members up front and assume they are all unused
74
+ ClassBody(classBodyNode) {
75
+ const privateMembers = new Map();
76
+
77
+ trackedClasses.unshift(privateMembers);
78
+ for (const bodyMember of classBodyNode.body) {
79
+ if (bodyMember.type === "PropertyDefinition" || bodyMember.type === "MethodDefinition") {
80
+ if (bodyMember.key.type === "PrivateIdentifier") {
81
+ privateMembers.set(bodyMember.key.name, {
82
+ declaredNode: bodyMember,
83
+ isAccessor: bodyMember.type === "MethodDefinition" &&
84
+ (bodyMember.kind === "set" || bodyMember.kind === "get")
85
+ });
86
+ }
87
+ }
88
+ }
89
+ },
90
+
91
+ /*
92
+ * Process all usages of the private identifier and remove a member from
93
+ * `declaredAndUnusedPrivateMembers` if we deem it used.
94
+ */
95
+ PrivateIdentifier(privateIdentifierNode) {
96
+ const classBody = trackedClasses.find(classProperties => classProperties.has(privateIdentifierNode.name));
97
+
98
+ // Can't happen, as it is a parser to have a missing class body, but let's code defensively here.
99
+ if (!classBody) {
100
+ return;
101
+ }
102
+
103
+ // In case any other usage was already detected, we can short circuit the logic here.
104
+ const memberDefinition = classBody.get(privateIdentifierNode.name);
105
+
106
+ if (memberDefinition.isUsed) {
107
+ return;
108
+ }
109
+
110
+ // The definition of the class member itself
111
+ if (privateIdentifierNode.parent.type === "PropertyDefinition" ||
112
+ privateIdentifierNode.parent.type === "MethodDefinition") {
113
+ return;
114
+ }
115
+
116
+ /*
117
+ * Any usage of an accessor is considered a read, as the getter/setter can have
118
+ * side-effects in its definition.
119
+ */
120
+ if (memberDefinition.isAccessor) {
121
+ memberDefinition.isUsed = true;
122
+ return;
123
+ }
124
+
125
+ // Any assignments to this member, except for assignments that also read
126
+ if (isWriteOnlyAssignment(privateIdentifierNode)) {
127
+ return;
128
+ }
129
+
130
+ const wrappingExpressionType = privateIdentifierNode.parent.parent.type;
131
+ const parentOfWrappingExpressionType = privateIdentifierNode.parent.parent.parent.type;
132
+
133
+ // A statement which only increments (`this.#x++;`)
134
+ if (wrappingExpressionType === "UpdateExpression" &&
135
+ parentOfWrappingExpressionType === "ExpressionStatement") {
136
+ return;
137
+ }
138
+
139
+ /*
140
+ * ({ x: this.#usedInDestructuring } = bar);
141
+ *
142
+ * But should treat the following as a read:
143
+ * ({ [this.#x]: a } = foo);
144
+ */
145
+ if (wrappingExpressionType === "Property" &&
146
+ parentOfWrappingExpressionType === "ObjectPattern" &&
147
+ privateIdentifierNode.parent.parent.value === privateIdentifierNode.parent) {
148
+ return;
149
+ }
150
+
151
+ // [...this.#unusedInRestPattern] = bar;
152
+ if (wrappingExpressionType === "RestElement") {
153
+ return;
154
+ }
155
+
156
+ // [this.#unusedInAssignmentPattern] = bar;
157
+ if (wrappingExpressionType === "ArrayPattern") {
158
+ return;
159
+ }
160
+
161
+ /*
162
+ * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used.
163
+ * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete
164
+ * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class
165
+ * as used, which is incorrect.
166
+ */
167
+ memberDefinition.isUsed = true;
168
+ },
169
+
170
+ /*
171
+ * Post-process the class members and report any remaining members.
172
+ * Since private members can only be accessed in the current class context,
173
+ * we can safely assume that all usages are within the current class body.
174
+ */
175
+ "ClassBody:exit"() {
176
+ const unusedPrivateMembers = trackedClasses.shift();
177
+
178
+ for (const [classMemberName, { declaredNode, isUsed }] of unusedPrivateMembers.entries()) {
179
+ if (isUsed) {
180
+ continue;
181
+ }
182
+ context.report({
183
+ node: declaredNode,
184
+ loc: declaredNode.key.loc,
185
+ messageId: "unusedPrivateClassMember",
186
+ data: {
187
+ classMemberName: `#${classMemberName}`
188
+ }
189
+ });
190
+ }
191
+ }
192
+ };
193
+ }
194
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.0.1",
3
+ "version": "8.1.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -99,7 +99,7 @@
99
99
  "eslint": "file:.",
100
100
  "eslint-config-eslint": "file:packages/eslint-config-eslint",
101
101
  "eslint-plugin-eslint-comments": "^3.2.0",
102
- "eslint-plugin-eslint-plugin": "^3.5.3",
102
+ "eslint-plugin-eslint-plugin": "^4.0.1",
103
103
  "eslint-plugin-internal-rules": "file:tools/internal-rules",
104
104
  "eslint-plugin-jsdoc": "^36.0.6",
105
105
  "eslint-plugin-node": "^11.1.0",