eslint 4.1.1 → 4.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/eslint.js +5 -4
  3. package/conf/category-list.json +2 -2
  4. package/conf/config-schema.js +3 -1
  5. package/conf/eslint-recommended.js +12 -14
  6. package/lib/cli-engine.js +4 -3
  7. package/lib/cli.js +12 -1
  8. package/lib/config/config-file.js +5 -5
  9. package/lib/config/config-initializer.js +123 -14
  10. package/lib/config/config-validator.js +43 -14
  11. package/lib/config/plugins.js +13 -1
  12. package/lib/linter.js +26 -15
  13. package/lib/rule-context.js +53 -41
  14. package/lib/rules/arrow-parens.js +5 -2
  15. package/lib/rules/comma-dangle.js +40 -40
  16. package/lib/rules/curly.js +1 -1
  17. package/lib/rules/dot-notation.js +9 -0
  18. package/lib/rules/getter-return.js +176 -0
  19. package/lib/rules/id-blacklist.js +7 -3
  20. package/lib/rules/id-match.js +8 -4
  21. package/lib/rules/indent-legacy.js +2 -2
  22. package/lib/rules/indent.js +354 -349
  23. package/lib/rules/key-spacing.js +2 -2
  24. package/lib/rules/multiline-ternary.js +8 -2
  25. package/lib/rules/no-cond-assign.js +7 -3
  26. package/lib/rules/no-constant-condition.js +62 -6
  27. package/lib/rules/no-debugger.js +6 -1
  28. package/lib/rules/no-else-return.js +1 -1
  29. package/lib/rules/no-extra-parens.js +24 -11
  30. package/lib/rules/no-inner-declarations.js +8 -4
  31. package/lib/rules/no-multi-spaces.js +53 -115
  32. package/lib/rules/no-regex-spaces.js +4 -4
  33. package/lib/rules/no-restricted-globals.js +50 -9
  34. package/lib/rules/no-restricted-properties.js +19 -11
  35. package/lib/rules/no-sync.js +15 -3
  36. package/lib/rules/no-tabs.js +8 -4
  37. package/lib/rules/no-underscore-dangle.js +28 -1
  38. package/lib/rules/object-curly-newline.js +18 -0
  39. package/lib/rules/object-curly-spacing.js +1 -1
  40. package/lib/rules/padded-blocks.js +2 -2
  41. package/lib/rules/padding-line-between-statements.js +1 -1
  42. package/lib/rules/prefer-destructuring.js +70 -32
  43. package/lib/rules/prefer-numeric-literals.js +36 -7
  44. package/lib/rules/prefer-reflect.js +8 -4
  45. package/lib/rules/prefer-template.js +2 -2
  46. package/lib/rules/space-infix-ops.js +1 -1
  47. package/lib/rules/spaced-comment.js +2 -2
  48. package/lib/rules/valid-jsdoc.js +15 -7
  49. package/lib/testers/rule-tester.js +23 -30
  50. package/lib/testers/test-parser.js +48 -0
  51. package/lib/util/ajv.js +29 -0
  52. package/lib/util/npm-util.js +9 -8
  53. package/lib/util/source-code-fixer.js +47 -19
  54. package/package.json +11 -7
  55. package/conf/json-schema-schema.json +0 -150
@@ -19,14 +19,26 @@ module.exports = {
19
19
  recommended: false
20
20
  },
21
21
 
22
- schema: []
22
+ schema: [
23
+ {
24
+ type: "object",
25
+ properties: {
26
+ allowAtRootLevel: {
27
+ type: "boolean"
28
+ }
29
+ },
30
+ additionalProperties: false
31
+ }
32
+ ]
23
33
  },
24
34
 
25
35
  create(context) {
36
+ const selector = context.options[0] && context.options[0].allowAtRootLevel
37
+ ? ":function MemberExpression[property.name=/.*Sync$/]"
38
+ : "MemberExpression[property.name=/.*Sync$/]";
26
39
 
27
40
  return {
28
-
29
- "MemberExpression[property.name=/.*Sync$/]"(node) {
41
+ [selector](node) {
30
42
  context.report({
31
43
  node,
32
44
  message: "Unexpected sync method: '{{propertyName}}'.",
@@ -31,10 +31,14 @@ module.exports = {
31
31
  const match = regex.exec(line);
32
32
 
33
33
  if (match) {
34
- context.report({ node, loc: {
35
- line: index + 1,
36
- column: match.index + 1
37
- }, message: "Unexpected tab character." });
34
+ context.report({
35
+ node,
36
+ loc: {
37
+ line: index + 1,
38
+ column: match.index + 1
39
+ },
40
+ message: "Unexpected tab character."
41
+ });
38
42
  }
39
43
  });
40
44
  }
@@ -32,6 +32,9 @@ module.exports = {
32
32
  },
33
33
  allowAfterSuper: {
34
34
  type: "boolean"
35
+ },
36
+ enforceInMethodNames: {
37
+ type: "boolean"
35
38
  }
36
39
  },
37
40
  additionalProperties: false
@@ -45,6 +48,7 @@ module.exports = {
45
48
  const ALLOWED_VARIABLES = options.allow ? options.allow : [];
46
49
  const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
47
50
  const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
51
+ const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
48
52
 
49
53
  //-------------------------------------------------------------------------
50
54
  // Helpers
@@ -162,6 +166,27 @@ module.exports = {
162
166
  }
163
167
  }
164
168
 
169
+ /**
170
+ * Check if method declaration or method property has a underscore at the end
171
+ * @param {ASTNode} node node to evaluate
172
+ * @returns {void}
173
+ * @private
174
+ */
175
+ function checkForTrailingUnderscoreInMethod(node) {
176
+ const identifier = node.key.name;
177
+ const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
178
+
179
+ if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier)) {
180
+ context.report({
181
+ node,
182
+ message: "Unexpected dangling '_' in '{{identifier}}'.",
183
+ data: {
184
+ identifier
185
+ }
186
+ });
187
+ }
188
+ }
189
+
165
190
  //--------------------------------------------------------------------------
166
191
  // Public API
167
192
  //--------------------------------------------------------------------------
@@ -169,7 +194,9 @@ module.exports = {
169
194
  return {
170
195
  FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
171
196
  VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
172
- MemberExpression: checkForTrailingUnderscoreInMemberExpression
197
+ MemberExpression: checkForTrailingUnderscoreInMemberExpression,
198
+ MethodDefinition: checkForTrailingUnderscoreInMethod,
199
+ Property: checkForTrailingUnderscoreInMethod
173
200
  };
174
201
 
175
202
  }
@@ -143,6 +143,8 @@ module.exports = {
143
143
  first.loc.start.line !== last.loc.end.line
144
144
  )
145
145
  );
146
+ const hasCommentsFirstToken = astUtils.isCommentToken(first);
147
+ const hasCommentsLastToken = astUtils.isCommentToken(last);
146
148
 
147
149
  /*
148
150
  * Use tokens or comments to check multiline or not.
@@ -162,6 +164,10 @@ module.exports = {
162
164
  node,
163
165
  loc: openBrace.loc.start,
164
166
  fix(fixer) {
167
+ if (hasCommentsFirstToken) {
168
+ return null;
169
+ }
170
+
165
171
  return fixer.insertTextAfter(openBrace, "\n");
166
172
  }
167
173
  });
@@ -172,6 +178,10 @@ module.exports = {
172
178
  node,
173
179
  loc: closeBrace.loc.start,
174
180
  fix(fixer) {
181
+ if (hasCommentsLastToken) {
182
+ return null;
183
+ }
184
+
175
185
  return fixer.insertTextBefore(closeBrace, "\n");
176
186
  }
177
187
  });
@@ -190,6 +200,10 @@ module.exports = {
190
200
  node,
191
201
  loc: openBrace.loc.start,
192
202
  fix(fixer) {
203
+ if (hasCommentsFirstToken) {
204
+ return null;
205
+ }
206
+
193
207
  return fixer.removeRange([
194
208
  openBrace.range[1],
195
209
  first.range[0]
@@ -206,6 +220,10 @@ module.exports = {
206
220
  node,
207
221
  loc: closeBrace.loc.start,
208
222
  fix(fixer) {
223
+ if (hasCommentsLastToken) {
224
+ return null;
225
+ }
226
+
209
227
  return fixer.removeRange([
210
228
  last.range[1],
211
229
  closeBrace.range[0]
@@ -174,7 +174,7 @@ module.exports = {
174
174
  options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
175
175
  options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
176
176
  );
177
- const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.start).type;
177
+ const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type;
178
178
 
179
179
  const closingCurlyBraceMustBeSpaced = (
180
180
  options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
@@ -202,7 +202,7 @@ module.exports = {
202
202
  node,
203
203
  loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
204
204
  fix(fixer) {
205
- return fixer.replaceTextRange([tokenBeforeFirst.end, firstBlockToken.start - firstBlockToken.loc.start.column], "\n");
205
+ return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
206
206
  },
207
207
  message: NEVER_MESSAGE
208
208
  });
@@ -215,7 +215,7 @@ module.exports = {
215
215
  loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
216
216
  message: NEVER_MESSAGE,
217
217
  fix(fixer) {
218
- return fixer.replaceTextRange([lastBlockToken.end, tokenAfterLast.start - tokenAfterLast.loc.start.column], "\n");
218
+ return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
219
219
  }
220
220
  });
221
221
  }
@@ -93,7 +93,7 @@ function isBlockLikeStatement(sourceCode, node) {
93
93
 
94
94
  // Checks the last token is a closing brace of blocks.
95
95
  const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
96
- const belongingNode = astUtils.isClosingBraceToken(lastToken)
96
+ const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken)
97
97
  ? sourceCode.getNodeByRangeIndex(lastToken.range[0])
98
98
  : null;
99
99
 
@@ -15,19 +15,55 @@ module.exports = {
15
15
  category: "ECMAScript 6",
16
16
  recommended: false
17
17
  },
18
-
19
18
  schema: [
20
19
  {
21
- type: "object",
22
- properties: {
23
- array: {
24
- type: "boolean"
20
+
21
+ // old support {array: Boolean, object: Boolean}
22
+ // new support {VariableDeclarator: {}, AssignmentExpression: {}}
23
+ oneOf: [
24
+ {
25
+ type: "object",
26
+ properties: {
27
+ VariableDeclarator: {
28
+ type: "object",
29
+ properties: {
30
+ array: {
31
+ type: "boolean"
32
+ },
33
+ object: {
34
+ type: "boolean"
35
+ }
36
+ },
37
+ additionalProperties: false
38
+ },
39
+ AssignmentExpression: {
40
+ type: "object",
41
+ properties: {
42
+ array: {
43
+ type: "boolean"
44
+ },
45
+ object: {
46
+ type: "boolean"
47
+ }
48
+ },
49
+ additionalProperties: false
50
+ }
51
+ },
52
+ additionalProperties: false
25
53
  },
26
- object: {
27
- type: "boolean"
54
+ {
55
+ type: "object",
56
+ properties: {
57
+ array: {
58
+ type: "boolean"
59
+ },
60
+ object: {
61
+ type: "boolean"
62
+ }
63
+ },
64
+ additionalProperties: false
28
65
  }
29
- },
30
- additionalProperties: false
66
+ ]
31
67
  },
32
68
  {
33
69
  type: "object",
@@ -42,26 +78,17 @@ module.exports = {
42
78
  },
43
79
  create(context) {
44
80
 
45
- let checkArrays = true;
46
- let checkObjects = true;
47
- let enforceForRenamedProperties = false;
48
81
  const enabledTypes = context.options[0];
49
- const additionalOptions = context.options[1];
82
+ const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
83
+ let normalizedOptions = {
84
+ VariableDeclarator: { array: true, object: true },
85
+ AssignmentExpression: { array: true, object: true }
86
+ };
50
87
 
51
88
  if (enabledTypes) {
52
- if (typeof enabledTypes.array !== "undefined") {
53
- checkArrays = enabledTypes.array;
54
- }
55
-
56
- if (typeof enabledTypes.object !== "undefined") {
57
- checkObjects = enabledTypes.object;
58
- }
59
- }
60
-
61
- if (additionalOptions) {
62
- if (typeof additionalOptions.enforceForRenamedProperties !== "undefined") {
63
- enforceForRenamedProperties = additionalOptions.enforceForRenamedProperties;
64
- }
89
+ normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
90
+ ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
91
+ : enabledTypes;
65
92
  }
66
93
 
67
94
  //--------------------------------------------------------------------------
@@ -69,7 +96,18 @@ module.exports = {
69
96
  //--------------------------------------------------------------------------
70
97
 
71
98
  /**
72
- * Determines if the given node node is accessing an array index
99
+ * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
100
+ * @param {string} destructuringType "array" or "object"
101
+ * @returns {boolean} `true` if the destructuring type should be checked for the given node
102
+ */
103
+ function shouldCheck(nodeType, destructuringType) {
104
+ return normalizedOptions &&
105
+ normalizedOptions[nodeType] &&
106
+ normalizedOptions[nodeType][destructuringType];
107
+ }
108
+
109
+ /**
110
+ * Determines if the given node is accessing an array index
73
111
  *
74
112
  * This is used to differentiate array index access from object property
75
113
  * access.
@@ -110,22 +148,22 @@ module.exports = {
110
148
  }
111
149
 
112
150
  if (isArrayIndexAccess(rightNode)) {
113
- if (checkArrays) {
151
+ if (shouldCheck(reportNode.type, "array")) {
114
152
  report(reportNode, "array");
115
153
  }
116
154
  return;
117
155
  }
118
156
 
119
- if (checkObjects && enforceForRenamedProperties) {
157
+ if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
120
158
  report(reportNode, "object");
121
159
  return;
122
160
  }
123
161
 
124
- if (checkObjects) {
162
+ if (shouldCheck(reportNode.type, "object")) {
125
163
  const property = rightNode.property;
126
164
 
127
- if ((property.type === "Literal" && leftNode.name === property.value) ||
128
- (property.type === "Identifier" && leftNode.name === property.name)) {
165
+ if ((property.type === "Literal" && leftNode.name === property.value) || (property.type === "Identifier" &&
166
+ leftNode.name === property.name)) {
129
167
  report(reportNode, "object");
130
168
  }
131
169
  }
@@ -5,6 +5,33 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Helpers
10
+ //------------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Checks to see if a CallExpression's callee node is `parseInt` or
14
+ * `Number.parseInt`.
15
+ * @param {ASTNode} calleeNode The callee node to evaluate.
16
+ * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`,
17
+ * false otherwise.
18
+ */
19
+ function isParseInt(calleeNode) {
20
+ switch (calleeNode.type) {
21
+ case "Identifier":
22
+ return calleeNode.name === "parseInt";
23
+ case "MemberExpression":
24
+ return calleeNode.object.type === "Identifier" &&
25
+ calleeNode.object.name === "Number" &&
26
+ calleeNode.property.type === "Identifier" &&
27
+ calleeNode.property.name === "parseInt";
28
+
29
+ // no default
30
+ }
31
+
32
+ return false;
33
+ }
34
+
8
35
  //------------------------------------------------------------------------------
9
36
  // Rule Definition
10
37
  //------------------------------------------------------------------------------
@@ -12,7 +39,7 @@
12
39
  module.exports = {
13
40
  meta: {
14
41
  docs: {
15
- description: "disallow `parseInt()` in favor of binary, octal, and hexadecimal literals",
42
+ description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals",
16
43
  category: "ECMAScript 6",
17
44
  recommended: false
18
45
  },
@@ -23,6 +50,8 @@ module.exports = {
23
50
  },
24
51
 
25
52
  create(context) {
53
+ const sourceCode = context.getSourceCode();
54
+
26
55
  const radixMap = {
27
56
  2: "binary",
28
57
  8: "octal",
@@ -35,9 +64,9 @@ module.exports = {
35
64
  16: "0x"
36
65
  };
37
66
 
38
- //--------------------------------------------------------------------------
67
+ //----------------------------------------------------------------------
39
68
  // Public
40
- //--------------------------------------------------------------------------
69
+ //----------------------------------------------------------------------
41
70
 
42
71
  return {
43
72
 
@@ -51,16 +80,16 @@ module.exports = {
51
80
  // only error if the radix is 2, 8, or 16
52
81
  const radixName = radixMap[node.arguments[1].value];
53
82
 
54
- if (node.callee.type === "Identifier" &&
55
- node.callee.name === "parseInt" &&
83
+ if (isParseInt(node.callee) &&
56
84
  radixName &&
57
85
  node.arguments[0].type === "Literal"
58
86
  ) {
59
87
  context.report({
60
88
  node,
61
- message: "Use {{radixName}} literals instead of parseInt().",
89
+ message: "Use {{radixName}} literals instead of {{functionName}}().",
62
90
  data: {
63
- radixName
91
+ radixName,
92
+ functionName: sourceCode.getText(node.callee)
64
93
  },
65
94
  fix(fixer) {
66
95
  const newPrefix = prefixMap[node.arguments[1].value];
@@ -83,10 +83,14 @@ module.exports = {
83
83
  * @returns {void}
84
84
  */
85
85
  function report(node, existing, substitute) {
86
- context.report({ node, message: "Avoid using {{existing}}, instead use {{substitute}}.", data: {
87
- existing,
88
- substitute
89
- } });
86
+ context.report({
87
+ node,
88
+ message: "Avoid using {{existing}}, instead use {{substitute}}.",
89
+ data: {
90
+ existing,
91
+ substitute
92
+ }
93
+ });
90
94
  }
91
95
 
92
96
  return {
@@ -74,7 +74,7 @@ function startsWithTemplateCurly(node) {
74
74
  return startsWithTemplateCurly(node.left);
75
75
  }
76
76
  if (node.type === "TemplateLiteral") {
77
- return node.expressions.length && node.quasis.length && node.quasis[0].start === node.quasis[0].end;
77
+ return node.expressions.length && node.quasis.length && node.quasis[0].range[0] === node.quasis[0].range[1];
78
78
  }
79
79
  return node.type !== "Literal" || typeof node.value !== "string";
80
80
  }
@@ -89,7 +89,7 @@ function endsWithTemplateCurly(node) {
89
89
  return startsWithTemplateCurly(node.right);
90
90
  }
91
91
  if (node.type === "TemplateLiteral") {
92
- return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].start === node.quasis[node.quasis.length - 1].end;
92
+ return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1];
93
93
  }
94
94
  return node.type !== "Literal" || typeof node.value !== "string";
95
95
  }
@@ -112,7 +112,7 @@ module.exports = {
112
112
  const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
113
113
 
114
114
  if (nonSpacedNode) {
115
- if (!(int32Hint && sourceCode.getText(node).substr(-2) === "|0")) {
115
+ if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
116
116
  report(node, nonSpacedNode);
117
117
  }
118
118
  }
@@ -303,9 +303,9 @@ module.exports = {
303
303
  node,
304
304
  fix(fixer) {
305
305
  if (requireSpace) {
306
- return fixer.insertTextAfterRange([node.start, node.end - 2], " ");
306
+ return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " ");
307
307
  }
308
- const end = node.end - 2,
308
+ const end = node.range[1] - 2,
309
309
  start = end - match[0].length;
310
310
 
311
311
  return fixer.replaceTextRange([start, end], "");
@@ -362,14 +362,22 @@ module.exports = {
362
362
  // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
363
363
  if (param.type === "Identifier") {
364
364
  if (jsdocParams[i] && (name !== jsdocParams[i])) {
365
- context.report({ node: jsdocNode, message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", data: {
366
- name,
367
- jsdocName: jsdocParams[i]
368
- } });
365
+ context.report({
366
+ node: jsdocNode,
367
+ message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
368
+ data: {
369
+ name,
370
+ jsdocName: jsdocParams[i]
371
+ }
372
+ });
369
373
  } else if (!params[name] && !isOverride) {
370
- context.report({ node: jsdocNode, message: "Missing JSDoc for parameter '{{name}}'.", data: {
371
- name
372
- } });
374
+ context.report({
375
+ node: jsdocNode,
376
+ message: "Missing JSDoc for parameter '{{name}}'.",
377
+ data: {
378
+ name
379
+ }
380
+ });
373
381
  }
374
382
  }
375
383
  });