eslint 6.1.0 → 6.2.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.
@@ -5,10 +5,87 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Typedefs
16
+ //------------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
20
+ * @typedef {string|Token[]} Key
21
+ */
22
+
23
+ /**
24
+ * Accessor nodes with the same key.
25
+ * @typedef {Object} AccessorData
26
+ * @property {Key} key Accessor's key
27
+ * @property {ASTNode[]} getters List of getter nodes.
28
+ * @property {ASTNode[]} setters List of setter nodes.
29
+ */
30
+
8
31
  //------------------------------------------------------------------------------
9
32
  // Helpers
10
33
  //------------------------------------------------------------------------------
11
34
 
35
+ /**
36
+ * Checks whether or not the given lists represent the equal tokens in the same order.
37
+ * Tokens are compared by their properties, not by instance.
38
+ * @param {Token[]} left First list of tokens.
39
+ * @param {Token[]} right Second list of tokens.
40
+ * @returns {boolean} `true` if the lists have same tokens.
41
+ */
42
+ function areEqualTokenLists(left, right) {
43
+ if (left.length !== right.length) {
44
+ return false;
45
+ }
46
+
47
+ for (let i = 0; i < left.length; i++) {
48
+ const leftToken = left[i],
49
+ rightToken = right[i];
50
+
51
+ if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ /**
60
+ * Checks whether or not the given keys are equal.
61
+ * @param {Key} left First key.
62
+ * @param {Key} right Second key.
63
+ * @returns {boolean} `true` if the keys are equal.
64
+ */
65
+ function areEqualKeys(left, right) {
66
+ if (typeof left === "string" && typeof right === "string") {
67
+
68
+ // Statically computed names.
69
+ return left === right;
70
+ }
71
+ if (Array.isArray(left) && Array.isArray(right)) {
72
+
73
+ // Token lists.
74
+ return areEqualTokenLists(left, right);
75
+ }
76
+
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * Checks whether or not a given node is of an accessor kind ('get' or 'set').
82
+ * @param {ASTNode} node - A node to check.
83
+ * @returns {boolean} `true` if the node is of an accessor kind.
84
+ */
85
+ function isAccessorKind(node) {
86
+ return node.kind === "get" || node.kind === "set";
87
+ }
88
+
12
89
  /**
13
90
  * Checks whether or not a given node is an `Identifier` node which was named a given name.
14
91
  * @param {ASTNode} node - A node to check.
@@ -97,69 +174,152 @@ module.exports = {
97
174
  }],
98
175
 
99
176
  messages: {
100
- getter: "Getter is not present.",
101
- setter: "Setter is not present."
177
+ missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
178
+ missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
179
+ missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
180
+ missingSetterInObjectLiteral: "Setter is not present for {{ name }}."
102
181
  }
103
182
  },
104
183
  create(context) {
105
184
  const config = context.options[0] || {};
106
185
  const checkGetWithoutSet = config.getWithoutSet === true;
107
186
  const checkSetWithoutGet = config.setWithoutGet !== false;
187
+ const sourceCode = context.getSourceCode();
108
188
 
109
189
  /**
110
- * Checks a object expression to see if it has setter and getter both present or none.
111
- * @param {ASTNode} node The node to check.
190
+ * Reports the given node.
191
+ * @param {ASTNode} node The node to report.
192
+ * @param {string} messageKind "missingGetter" or "missingSetter".
112
193
  * @returns {void}
113
194
  * @private
114
195
  */
115
- function checkLonelySetGet(node) {
116
- let isSetPresent = false;
117
- let isGetPresent = false;
118
- const isDescriptor = isPropertyDescriptor(node);
196
+ function report(node, messageKind) {
197
+ if (node.type === "Property") {
198
+ context.report({
199
+ node,
200
+ messageId: `${messageKind}InObjectLiteral`,
201
+ loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
202
+ data: { name: astUtils.getFunctionNameWithKind(node.value) }
203
+ });
204
+ } else {
205
+ context.report({
206
+ node,
207
+ messageId: `${messageKind}InPropertyDescriptor`
208
+ });
209
+ }
210
+ }
119
211
 
120
- for (let i = 0, end = node.properties.length; i < end; i++) {
121
- const property = node.properties[i];
212
+ /**
213
+ * Reports each of the nodes in the given list using the same messageId.
214
+ * @param {ASTNode[]} nodes Nodes to report.
215
+ * @param {string} messageKind "missingGetter" or "missingSetter".
216
+ * @returns {void}
217
+ * @private
218
+ */
219
+ function reportList(nodes, messageKind) {
220
+ for (const node of nodes) {
221
+ report(node, messageKind);
222
+ }
223
+ }
122
224
 
123
- let propToCheck = "";
225
+ /**
226
+ * Creates a new `AccessorData` object for the given getter or setter node.
227
+ * @param {ASTNode} node A getter or setter node.
228
+ * @returns {AccessorData} New `AccessorData` object that contains the given node.
229
+ * @private
230
+ */
231
+ function createAccessorData(node) {
232
+ const name = astUtils.getStaticPropertyName(node);
233
+ const key = (name !== null) ? name : sourceCode.getTokens(node.key);
124
234
 
125
- if (property.kind === "init") {
126
- if (isDescriptor && !property.computed) {
127
- propToCheck = property.key.name;
128
- }
129
- } else {
130
- propToCheck = property.kind;
131
- }
235
+ return {
236
+ key,
237
+ getters: node.kind === "get" ? [node] : [],
238
+ setters: node.kind === "set" ? [node] : []
239
+ };
240
+ }
132
241
 
133
- switch (propToCheck) {
134
- case "set":
135
- isSetPresent = true;
136
- break;
242
+ /**
243
+ * Merges the given `AccessorData` object into the given accessors list.
244
+ * @param {AccessorData[]} accessors The list to merge into.
245
+ * @param {AccessorData} accessorData The object to merge.
246
+ * @returns {AccessorData[]} The same instance with the merged object.
247
+ * @private
248
+ */
249
+ function mergeAccessorData(accessors, accessorData) {
250
+ const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
137
251
 
138
- case "get":
139
- isGetPresent = true;
140
- break;
252
+ if (equalKeyElement) {
253
+ equalKeyElement.getters.push(...accessorData.getters);
254
+ equalKeyElement.setters.push(...accessorData.setters);
255
+ } else {
256
+ accessors.push(accessorData);
257
+ }
141
258
 
142
- default:
259
+ return accessors;
260
+ }
143
261
 
144
- // Do nothing
145
- }
262
+ /**
263
+ * Checks accessor pairs in the given list of nodes.
264
+ * @param {ASTNode[]} nodes The list to check.
265
+ * @returns {void}
266
+ * @private
267
+ */
268
+ function checkList(nodes) {
269
+ const accessors = nodes
270
+ .filter(isAccessorKind)
271
+ .map(createAccessorData)
272
+ .reduce(mergeAccessorData, []);
146
273
 
147
- if (isSetPresent && isGetPresent) {
148
- break;
274
+ for (const { getters, setters } of accessors) {
275
+ if (checkSetWithoutGet && setters.length && !getters.length) {
276
+ reportList(setters, "missingGetter");
277
+ }
278
+ if (checkGetWithoutSet && getters.length && !setters.length) {
279
+ reportList(getters, "missingSetter");
149
280
  }
150
281
  }
282
+ }
151
283
 
152
- if (checkSetWithoutGet && isSetPresent && !isGetPresent) {
153
- context.report({ node, messageId: "getter" });
154
- } else if (checkGetWithoutSet && isGetPresent && !isSetPresent) {
155
- context.report({ node, messageId: "setter" });
284
+ /**
285
+ * Checks accessor pairs in an object literal.
286
+ * @param {ASTNode} node `ObjectExpression` node to check.
287
+ * @returns {void}
288
+ * @private
289
+ */
290
+ function checkObjectLiteral(node) {
291
+ checkList(node.properties.filter(p => p.type === "Property"));
292
+ }
293
+
294
+ /**
295
+ * Checks accessor pairs in a property descriptor.
296
+ * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
297
+ * @returns {void}
298
+ * @private
299
+ */
300
+ function checkPropertyDescriptor(node) {
301
+ const namesToCheck = node.properties
302
+ .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
303
+ .map(({ key }) => key.name);
304
+
305
+ const hasGetter = namesToCheck.includes("get");
306
+ const hasSetter = namesToCheck.includes("set");
307
+
308
+ if (checkSetWithoutGet && hasSetter && !hasGetter) {
309
+ report(node, "missingGetter");
310
+ }
311
+ if (checkGetWithoutSet && hasGetter && !hasSetter) {
312
+ report(node, "missingSetter");
156
313
  }
157
314
  }
158
315
 
159
316
  return {
160
317
  ObjectExpression(node) {
161
318
  if (checkSetWithoutGet || checkGetWithoutSet) {
162
- checkLonelySetGet(node);
319
+ checkObjectLiteral(node);
320
+ if (isPropertyDescriptor(node)) {
321
+ checkPropertyDescriptor(node);
322
+ }
163
323
  }
164
324
  }
165
325
  };
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -34,7 +40,7 @@ module.exports = {
34
40
  }],
35
41
 
36
42
  messages: {
37
- missingThis: "Expected 'this' to be used by class method '{{name}}'."
43
+ missingThis: "Expected 'this' to be used by class {{name}}."
38
44
  }
39
45
  },
40
46
  create(context) {
@@ -70,7 +76,8 @@ module.exports = {
70
76
  * @private
71
77
  */
72
78
  function isIncludedInstanceMethod(node) {
73
- return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
79
+ return isInstanceMethod(node) &&
80
+ (node.computed || !exceptMethods.has(node.key.name));
74
81
  }
75
82
 
76
83
  /**
@@ -89,7 +96,7 @@ module.exports = {
89
96
  node,
90
97
  messageId: "missingThis",
91
98
  data: {
92
- name: node.parent.key.name
99
+ name: astUtils.getFunctionNameWithKind(node)
93
100
  }
94
101
  });
95
102
  }
@@ -9,13 +9,16 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
+ const keywords = require("./utils/keywords");
12
13
 
13
14
  //------------------------------------------------------------------------------
14
15
  // Rule Definition
15
16
  //------------------------------------------------------------------------------
16
17
 
17
18
  const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
18
- const keywords = require("./utils/keywords");
19
+
20
+ // `null` literal must be handled separately.
21
+ const literalTypesToCheck = new Set(["string", "boolean"]);
19
22
 
20
23
  module.exports = {
21
24
  meta: {
@@ -115,7 +118,8 @@ module.exports = {
115
118
  MemberExpression(node) {
116
119
  if (
117
120
  node.computed &&
118
- node.property.type === "Literal"
121
+ node.property.type === "Literal" &&
122
+ (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
119
123
  ) {
120
124
  checkComputedProperty(node, node.property.value);
121
125
  }
@@ -78,21 +78,13 @@ module.exports = {
78
78
  /**
79
79
  * Check if open space is present in a function name
80
80
  * @param {ASTNode} node node to evaluate
81
+ * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee.
82
+ * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments.
81
83
  * @returns {void}
82
84
  * @private
83
85
  */
84
- function checkSpacing(node) {
85
- const lastToken = sourceCode.getLastToken(node);
86
- const lastCalleeToken = sourceCode.getLastToken(node.callee);
87
- const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
88
- const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
89
-
90
- // Parens in NewExpression are optional
91
- if (!(parenToken && parenToken.range[1] < node.range[1])) {
92
- return;
93
- }
94
-
95
- const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//gu, "");
86
+ function checkSpacing(node, leftToken, rightToken) {
87
+ const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, "");
96
88
  const hasWhitespace = /\s/u.test(textBetweenTokens);
97
89
  const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
98
90
 
@@ -123,7 +115,7 @@ module.exports = {
123
115
  if (never && hasWhitespace) {
124
116
  context.report({
125
117
  node,
126
- loc: lastCalleeToken.loc.start,
118
+ loc: leftToken.loc.start,
127
119
  messageId: "unexpected",
128
120
  fix(fixer) {
129
121
 
@@ -132,7 +124,7 @@ module.exports = {
132
124
  * https://github.com/eslint/eslint/issues/7787
133
125
  */
134
126
  if (!hasNewline) {
135
- return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
127
+ return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
136
128
  }
137
129
 
138
130
  return null;
@@ -141,27 +133,45 @@ module.exports = {
141
133
  } else if (!never && !hasWhitespace) {
142
134
  context.report({
143
135
  node,
144
- loc: lastCalleeToken.loc.start,
136
+ loc: leftToken.loc.start,
145
137
  messageId: "missing",
146
138
  fix(fixer) {
147
- return fixer.insertTextBefore(parenToken, " ");
139
+ return fixer.insertTextBefore(rightToken, " ");
148
140
  }
149
141
  });
150
142
  } else if (!never && !allowNewlines && hasNewline) {
151
143
  context.report({
152
144
  node,
153
- loc: lastCalleeToken.loc.start,
145
+ loc: leftToken.loc.start,
154
146
  messageId: "unexpected",
155
147
  fix(fixer) {
156
- return fixer.replaceTextRange([prevToken.range[1], parenToken.range[0]], " ");
148
+ return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
157
149
  }
158
150
  });
159
151
  }
160
152
  }
161
153
 
162
154
  return {
163
- CallExpression: checkSpacing,
164
- NewExpression: checkSpacing
155
+ "CallExpression, NewExpression"(node) {
156
+ const lastToken = sourceCode.getLastToken(node);
157
+ const lastCalleeToken = sourceCode.getLastToken(node.callee);
158
+ const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
159
+ const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
160
+
161
+ // Parens in NewExpression are optional
162
+ if (!(parenToken && parenToken.range[1] < node.range[1])) {
163
+ return;
164
+ }
165
+
166
+ checkSpacing(node, prevToken, parenToken);
167
+ },
168
+
169
+ ImportExpression(node) {
170
+ const leftToken = sourceCode.getFirstToken(node);
171
+ const rightToken = sourceCode.getTokenAfter(leftToken);
172
+
173
+ checkSpacing(node, leftToken, rightToken);
174
+ }
165
175
  };
166
176
 
167
177
  }
@@ -69,6 +69,8 @@ module.exports = {
69
69
 
70
70
  create(context) {
71
71
 
72
+ const sourceCode = context.getSourceCode();
73
+
72
74
  /**
73
75
  * Returns the config option for the given node.
74
76
  * @param {ASTNode} node - A node to get the config for.
@@ -130,6 +132,7 @@ module.exports = {
130
132
  context.report({
131
133
  node,
132
134
  messageId: "unnamed",
135
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
133
136
  data: { name: astUtils.getFunctionNameWithKind(node) }
134
137
  });
135
138
  }
@@ -143,6 +146,7 @@ module.exports = {
143
146
  context.report({
144
147
  node,
145
148
  messageId: "named",
149
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
146
150
  data: { name: astUtils.getFunctionNameWithKind(node) }
147
151
  });
148
152
  }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @fileoverview Rule to enforce line breaks between arguments of a function call
3
+ * @author Alexey Gonchar <https://github.com/finico>
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "layout",
15
+
16
+ docs: {
17
+ description: "enforce line breaks between arguments of a function call",
18
+ category: "Stylistic Issues",
19
+ recommended: false,
20
+ url: "https://eslint.org/docs/rules/function-call-argument-newline"
21
+ },
22
+
23
+ fixable: "whitespace",
24
+
25
+ schema: [
26
+ {
27
+ enum: ["always", "never", "consistent"]
28
+ }
29
+ ],
30
+
31
+ messages: {
32
+ unexpectedLineBreak: "There should be no line break here.",
33
+ missingLineBreak: "There should be a line break after this argument."
34
+ }
35
+ },
36
+
37
+ create(context) {
38
+ const sourceCode = context.getSourceCode();
39
+
40
+ const checkers = {
41
+ unexpected: {
42
+ messageId: "unexpectedLineBreak",
43
+ check: (prevToken, currentToken) => prevToken.loc.start.line !== currentToken.loc.start.line,
44
+ createFix: (token, tokenBefore) => fixer =>
45
+ fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
46
+ },
47
+ missing: {
48
+ messageId: "missingLineBreak",
49
+ check: (prevToken, currentToken) => prevToken.loc.start.line === currentToken.loc.start.line,
50
+ createFix: (token, tokenBefore) => fixer =>
51
+ fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Check all arguments for line breaks in the CallExpression
57
+ * @param {CallExpression} node node to evaluate
58
+ * @param {{ messageId: string, check: Function }} checker selected checker
59
+ * @returns {void}
60
+ * @private
61
+ */
62
+ function checkArguments(node, checker) {
63
+ for (let i = 1; i < node.arguments.length; i++) {
64
+ const prevArgToken = sourceCode.getFirstToken(node.arguments[i - 1]);
65
+ const currentArgToken = sourceCode.getFirstToken(node.arguments[i]);
66
+
67
+ if (checker.check(prevArgToken, currentArgToken)) {
68
+ const tokenBefore = sourceCode.getTokenBefore(
69
+ currentArgToken,
70
+ { includeComments: true }
71
+ );
72
+
73
+ context.report({
74
+ node,
75
+ loc: {
76
+ start: tokenBefore.loc.end,
77
+ end: currentArgToken.loc.start
78
+ },
79
+ messageId: checker.messageId,
80
+ fix: checker.createFix(currentArgToken, tokenBefore)
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Check if open space is present in a function name
88
+ * @param {CallExpression} node node to evaluate
89
+ * @returns {void}
90
+ * @private
91
+ */
92
+ function check(node) {
93
+ if (node.arguments.length < 2) {
94
+ return;
95
+ }
96
+
97
+ const option = context.options[0] || "always";
98
+
99
+ if (option === "never") {
100
+ checkArguments(node, checkers.unexpected);
101
+ } else if (option === "always") {
102
+ checkArguments(node, checkers.missing);
103
+ } else if (option === "consistent") {
104
+ const firstArgToken = sourceCode.getFirstToken(node.arguments[0]);
105
+ const secondArgToken = sourceCode.getFirstToken(node.arguments[1]);
106
+
107
+ if (firstArgToken.loc.start.line === secondArgToken.loc.start.line) {
108
+ checkArguments(node, checkers.unexpected);
109
+ } else {
110
+ checkArguments(node, checkers.missing);
111
+ }
112
+ }
113
+ }
114
+
115
+ return {
116
+ CallExpression: check,
117
+ NewExpression: check
118
+ };
119
+ }
120
+ };
@@ -232,25 +232,15 @@ module.exports = {
232
232
  };
233
233
  }
234
234
 
235
- default:
236
- throw new TypeError(`unexpected node with type ${node.type}`);
237
- }
238
- }
239
-
240
- /**
241
- * Validates the parentheses for a node
242
- * @param {ASTNode} node The node with parens
243
- * @returns {void}
244
- */
245
- function validateNode(node) {
246
- const parens = getParenTokens(node);
247
-
248
- if (parens) {
249
- validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
235
+ case "ImportExpression": {
236
+ const leftParen = sourceCode.getFirstToken(node, 1);
237
+ const rightParen = sourceCode.getLastToken(node);
250
238
 
251
- if (multilineArgumentsOption) {
252
- validateArguments(parens, astUtils.isFunction(node) ? node.params : node.arguments);
239
+ return { leftParen, rightParen };
253
240
  }
241
+
242
+ default:
243
+ throw new TypeError(`unexpected node with type ${node.type}`);
254
244
  }
255
245
  }
256
246
 
@@ -259,11 +249,33 @@ module.exports = {
259
249
  //----------------------------------------------------------------------
260
250
 
261
251
  return {
262
- ArrowFunctionExpression: validateNode,
263
- CallExpression: validateNode,
264
- FunctionDeclaration: validateNode,
265
- FunctionExpression: validateNode,
266
- NewExpression: validateNode
252
+ [[
253
+ "ArrowFunctionExpression",
254
+ "CallExpression",
255
+ "FunctionDeclaration",
256
+ "FunctionExpression",
257
+ "ImportExpression",
258
+ "NewExpression"
259
+ ]](node) {
260
+ const parens = getParenTokens(node);
261
+ let params;
262
+
263
+ if (node.type === "ImportExpression") {
264
+ params = [node.source];
265
+ } else if (astUtils.isFunction(node)) {
266
+ params = node.params;
267
+ } else {
268
+ params = node.arguments;
269
+ }
270
+
271
+ if (parens) {
272
+ validateParens(parens, params);
273
+
274
+ if (multilineArgumentsOption) {
275
+ validateArguments(parens, params);
276
+ }
277
+ }
278
+ }
267
279
  };
268
280
  }
269
281
  };