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
@@ -433,9 +433,9 @@ module.exports = {
433
433
 
434
434
  // Remove whitespace
435
435
  if (isKeySide) {
436
- range = [tokenBeforeColon.end, tokenBeforeColon.end + diffAbs];
436
+ range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];
437
437
  } else {
438
- range = [tokenAfterColon.start - diffAbs, tokenAfterColon.start];
438
+ range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];
439
439
  }
440
440
  fix = function(fixer) {
441
441
  return fixer.removeRange(range);
@@ -20,13 +20,15 @@ module.exports = {
20
20
  },
21
21
  schema: [
22
22
  {
23
- enum: ["always", "never"]
23
+ enum: ["always", "always-multiline", "never"]
24
24
  }
25
25
  ]
26
26
  },
27
27
 
28
28
  create(context) {
29
- const multiline = context.options[0] !== "never";
29
+ const option = context.options[0];
30
+ const multiline = option !== "never";
31
+ const allowSingleLine = option === "always-multiline";
30
32
 
31
33
  //--------------------------------------------------------------------------
32
34
  // Helpers
@@ -69,6 +71,10 @@ module.exports = {
69
71
  reportError(node.consequent, node, false);
70
72
  }
71
73
  } else {
74
+ if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
75
+ return;
76
+ }
77
+
72
78
  if (areTestAndConsequentOnSameLine) {
73
79
  reportError(node.test, node, true);
74
80
  }
@@ -112,9 +112,13 @@ module.exports = {
112
112
  const ancestor = findConditionalAncestor(node);
113
113
 
114
114
  if (ancestor) {
115
- context.report({ node: ancestor, message: "Unexpected assignment within {{type}}.", data: {
116
- type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
117
- } });
115
+ context.report({
116
+ node: ancestor,
117
+ message: "Unexpected assignment within {{type}}.",
118
+ data: {
119
+ type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
120
+ }
121
+ });
118
122
  }
119
123
  }
120
124
 
@@ -33,7 +33,10 @@ module.exports = {
33
33
 
34
34
  create(context) {
35
35
  const options = context.options[0] || {},
36
- checkLoops = options.checkLoops !== false;
36
+ checkLoops = options.checkLoops !== false,
37
+ loopSetStack = [];
38
+
39
+ let loopsInCurrentScope = new Set();
37
40
 
38
41
  //--------------------------------------------------------------------------
39
42
  // Helpers
@@ -114,18 +117,64 @@ module.exports = {
114
117
  return false;
115
118
  }
116
119
 
120
+ /**
121
+ * Tracks when the given node contains a constant condition.
122
+ * @param {ASTNode} node The AST node to check.
123
+ * @returns {void}
124
+ * @private
125
+ */
126
+ function trackConstantConditionLoop(node) {
127
+ if (node.test && isConstant(node.test, true)) {
128
+ loopsInCurrentScope.add(node);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Reports when the set contains the given constant condition node
134
+ * @param {ASTNode} node The AST node to check.
135
+ * @returns {void}
136
+ * @private
137
+ */
138
+ function checkConstantConditionLoopInSet(node) {
139
+ if (loopsInCurrentScope.has(node)) {
140
+ loopsInCurrentScope.delete(node);
141
+ context.report({ node, message: "Unexpected constant condition." });
142
+ }
143
+ }
144
+
117
145
  /**
118
146
  * Reports when the given node contains a constant condition.
119
147
  * @param {ASTNode} node The AST node to check.
120
148
  * @returns {void}
121
149
  * @private
122
150
  */
123
- function checkConstantCondition(node) {
151
+ function reportIfConstant(node) {
124
152
  if (node.test && isConstant(node.test, true)) {
125
153
  context.report({ node, message: "Unexpected constant condition." });
126
154
  }
127
155
  }
128
156
 
157
+ /**
158
+ * Stores current set of constant loops in loopSetStack temporarily
159
+ * and uses a new set to track constant loops
160
+ * @returns {void}
161
+ * @private
162
+ */
163
+ function enterFunction() {
164
+ loopSetStack.push(loopsInCurrentScope);
165
+ loopsInCurrentScope = new Set();
166
+ }
167
+
168
+ /**
169
+ * Reports when the set still contains stored constant conditions
170
+ * @param {ASTNode} node The AST node to check.
171
+ * @returns {void}
172
+ * @private
173
+ */
174
+ function exitFunction() {
175
+ loopsInCurrentScope = loopSetStack.pop();
176
+ }
177
+
129
178
  /**
130
179
  * Checks node when checkLoops option is enabled
131
180
  * @param {ASTNode} node The AST node to check.
@@ -134,7 +183,7 @@ module.exports = {
134
183
  */
135
184
  function checkLoop(node) {
136
185
  if (checkLoops) {
137
- checkConstantCondition(node);
186
+ trackConstantConditionLoop(node);
138
187
  }
139
188
  }
140
189
 
@@ -143,11 +192,18 @@ module.exports = {
143
192
  //--------------------------------------------------------------------------
144
193
 
145
194
  return {
146
- ConditionalExpression: checkConstantCondition,
147
- IfStatement: checkConstantCondition,
195
+ ConditionalExpression: reportIfConstant,
196
+ IfStatement: reportIfConstant,
148
197
  WhileStatement: checkLoop,
198
+ "WhileStatement:exit": checkConstantConditionLoopInSet,
149
199
  DoWhileStatement: checkLoop,
150
- ForStatement: checkLoop
200
+ "DoWhileStatement:exit": checkConstantConditionLoopInSet,
201
+ ForStatement: checkLoop,
202
+ "ForStatement > .test": node => checkLoop(node.parent),
203
+ "ForStatement:exit": checkConstantConditionLoopInSet,
204
+ FunctionDeclaration: enterFunction,
205
+ "FunctionDeclaration:exit": exitFunction,
206
+ YieldExpression: () => loopsInCurrentScope.clear()
151
207
  };
152
208
 
153
209
  }
@@ -5,6 +5,8 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const astUtils = require("../ast-utils");
9
+
8
10
  //------------------------------------------------------------------------------
9
11
  // Rule Definition
10
12
  //------------------------------------------------------------------------------
@@ -28,7 +30,10 @@ module.exports = {
28
30
  node,
29
31
  message: "Unexpected 'debugger' statement.",
30
32
  fix(fixer) {
31
- return fixer.remove(node);
33
+ if (astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
34
+ return fixer.remove(node);
35
+ }
36
+ return null;
32
37
  }
33
38
  });
34
39
  }
@@ -99,7 +99,7 @@ module.exports = {
99
99
  // https://github.com/eslint/eslint/issues/8026
100
100
  return new FixTracker(fixer, sourceCode)
101
101
  .retainEnclosingFunction(node)
102
- .replaceTextRange([elseToken.start, node.end], fixedSource);
102
+ .replaceTextRange([elseToken.range[0], node.range[1]], fixedSource);
103
103
  }
104
104
  });
105
105
  }
@@ -418,6 +418,7 @@ module.exports = {
418
418
  function checkExpressionOrExportStatement(node) {
419
419
  const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
420
420
  const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
421
+ const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
421
422
 
422
423
  if (
423
424
  astUtils.isOpeningParenToken(firstToken) &&
@@ -426,8 +427,9 @@ module.exports = {
426
427
  secondToken.type === "Keyword" && (
427
428
  secondToken.value === "function" ||
428
429
  secondToken.value === "class" ||
429
- secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken))
430
- )
430
+ secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))
431
+ ) ||
432
+ secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
431
433
  )
432
434
  ) {
433
435
  tokensToIgnore.add(secondToken);
@@ -512,16 +514,27 @@ module.exports = {
512
514
  ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
513
515
  ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
514
516
 
515
- ForInStatement(node) {
516
- if (hasExcessParens(node.right)) {
517
- report(node.right);
518
- }
519
- if (hasExcessParens(node.left)) {
520
- report(node.left);
521
- }
522
- },
517
+ "ForInStatement, ForOfStatement"(node) {
518
+ if (node.left.type !== "VariableDeclarator") {
519
+ const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
520
+
521
+ if (
522
+ firstLeftToken.value === "let" && (
523
+
524
+ // If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
525
+ // Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
526
+ firstLeftToken.range[1] === node.left.range[1] ||
523
527
 
524
- ForOfStatement(node) {
528
+ // If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
529
+ // Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
530
+ astUtils.isOpeningBracketToken(
531
+ sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
532
+ )
533
+ )
534
+ ) {
535
+ tokensToIgnore.add(firstLeftToken);
536
+ }
537
+ }
525
538
  if (hasExcessParens(node.right)) {
526
539
  report(node.right);
527
540
  }
@@ -63,10 +63,14 @@ module.exports = {
63
63
  body.distance === 2);
64
64
 
65
65
  if (!valid) {
66
- context.report({ node, message: "Move {{type}} declaration to {{body}} root.", data: {
67
- type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
68
- body: (body.type === "Program" ? "program" : "function body")
69
- } });
66
+ context.report({
67
+ node,
68
+ message: "Move {{type}} declaration to {{body}} root.",
69
+ data: {
70
+ type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
71
+ body: (body.type === "Program" ? "program" : "function body")
72
+ }
73
+ });
70
74
  }
71
75
  }
72
76
 
@@ -44,68 +44,11 @@ module.exports = {
44
44
  },
45
45
 
46
46
  create(context) {
47
-
48
- // the index of the last comment that was checked
49
- const sourceCode = context.getSourceCode(),
50
- exceptions = { Property: true },
51
- options = context.options[0] || {},
52
- ignoreEOLComments = options.ignoreEOLComments;
53
- let hasExceptions = true,
54
- lastCommentIndex = 0;
55
-
56
- if (options && options.exceptions) {
57
- Object.keys(options.exceptions).forEach(key => {
58
- if (options.exceptions[key]) {
59
- exceptions[key] = true;
60
- } else {
61
- delete exceptions[key];
62
- }
63
- });
64
- hasExceptions = Object.keys(exceptions).length > 0;
65
- }
66
-
67
- /**
68
- * Checks if a given token is the last token of the line or not.
69
- * @param {Token} token The token to check.
70
- * @returns {boolean} Whether or not a token is at the end of the line it occurs in.
71
- * @private
72
- */
73
- function isLastTokenOfLine(token) {
74
- const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
75
-
76
- // nextToken is null if the comment is the last token in the program.
77
- if (!nextToken) {
78
- return true;
79
- }
80
-
81
- return !astUtils.isTokenOnSameLine(token, nextToken);
82
- }
83
-
84
- /**
85
- * Determines if a given source index is in a comment or not by checking
86
- * the index against the comment range. Since the check goes straight
87
- * through the file, once an index is passed a certain comment, we can
88
- * go to the next comment to check that.
89
- * @param {int} index The source index to check.
90
- * @param {ASTNode[]} comments An array of comment nodes.
91
- * @returns {boolean} True if the index is within a comment, false if not.
92
- * @private
93
- */
94
- function isIndexInComment(index, comments) {
95
- while (lastCommentIndex < comments.length) {
96
- const comment = comments[lastCommentIndex];
97
-
98
- if (comment.range[0] < index && index < comment.range[1]) {
99
- return true;
100
- } else if (index > comment.range[1]) {
101
- lastCommentIndex++;
102
- } else {
103
- break;
104
- }
105
- }
106
-
107
- return false;
108
- }
47
+ const sourceCode = context.getSourceCode();
48
+ const options = context.options[0] || {};
49
+ const ignoreEOLComments = options.ignoreEOLComments;
50
+ const exceptions = Object.assign({ Property: true }, options.exceptions);
51
+ const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0;
109
52
 
110
53
  /**
111
54
  * Formats value of given comment token for error message by truncating its length.
@@ -116,75 +59,70 @@ module.exports = {
116
59
  function formatReportedCommentValue(token) {
117
60
  const valueLines = token.value.split("\n");
118
61
  const value = valueLines[0];
119
- const formattedValue = `${value.substring(0, 12)}...`;
62
+ const formattedValue = `${value.slice(0, 12)}...`;
120
63
 
121
64
  return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
122
65
  }
123
66
 
124
- /**
125
- * Creates a fix function that removes the multiple spaces between the two tokens
126
- * @param {Token} leftToken left token
127
- * @param {Token} rightToken right token
128
- * @returns {Function} fix function
129
- * @private
130
- */
131
- function createFix(leftToken, rightToken) {
132
- return function(fixer) {
133
- return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
134
- };
135
- }
136
-
137
67
  //--------------------------------------------------------------------------
138
68
  // Public
139
69
  //--------------------------------------------------------------------------
140
70
 
141
71
  return {
142
72
  Program() {
73
+ sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
74
+ if (leftIndex === tokensAndComments.length - 1) {
75
+ return;
76
+ }
77
+ const rightToken = tokensAndComments[leftIndex + 1];
78
+
79
+ // Ignore tokens that don't have 2 spaces between them or are on different lines
80
+ if (
81
+ !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") ||
82
+ leftToken.loc.end.line < rightToken.loc.start.line
83
+ ) {
84
+ return;
85
+ }
143
86
 
144
- const source = sourceCode.getText(),
145
- allComments = sourceCode.getAllComments(),
146
- pattern = /[^\s].*? {2,}/g;
147
- let parent;
148
-
149
- while (pattern.test(source)) {
150
-
151
- // do not flag anything inside of comments
152
- if (!isIndexInComment(pattern.lastIndex, allComments)) {
153
-
154
- const token = sourceCode.getTokenByRangeStart(pattern.lastIndex, { includeComments: true });
155
-
156
- if (token) {
157
- if (ignoreEOLComments && astUtils.isCommentToken(token) && isLastTokenOfLine(token)) {
158
- return;
159
- }
160
-
161
- const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
87
+ // Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
88
+ if (
89
+ ignoreEOLComments &&
90
+ astUtils.isCommentToken(rightToken) &&
91
+ (
92
+ leftIndex === tokensAndComments.length - 2 ||
93
+ rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line
94
+ )
95
+ ) {
96
+ return;
97
+ }
162
98
 
163
- if (hasExceptions) {
164
- parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
165
- }
99
+ // Ignore tokens that are in a node in the "exceptions" object
100
+ if (hasExceptions) {
101
+ const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);
166
102
 
167
- if (!parent || !exceptions[parent.type]) {
168
- let value = token.value;
169
-
170
- if (token.type === "Block") {
171
- value = `/*${formatReportedCommentValue(token)}*/`;
172
- } else if (token.type === "Line") {
173
- value = `//${formatReportedCommentValue(token)}`;
174
- }
175
-
176
- context.report({
177
- node: token,
178
- loc: token.loc.start,
179
- message: "Multiple spaces found before '{{value}}'.",
180
- data: { value },
181
- fix: createFix(previousToken, token)
182
- });
183
- }
103
+ if (parentNode && exceptions[parentNode.type]) {
104
+ return;
184
105
  }
106
+ }
185
107
 
108
+ let displayValue;
109
+
110
+ if (rightToken.type === "Block") {
111
+ displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
112
+ } else if (rightToken.type === "Line") {
113
+ displayValue = `//${formatReportedCommentValue(rightToken)}`;
114
+ } else {
115
+ displayValue = rightToken.value;
186
116
  }
187
- }
117
+
118
+ context.report({
119
+ node: rightToken,
120
+ loc: rightToken.loc.start,
121
+ message: "Multiple spaces found before '{{displayValue}}'.",
122
+ data: { displayValue },
123
+ fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
124
+ });
125
+ });
188
126
  }
189
127
  };
190
128
 
@@ -37,11 +37,11 @@ module.exports = {
37
37
  * @private
38
38
  */
39
39
  function checkRegex(node, value, valueStart) {
40
- const multipleSpacesRegex = /( {2,})+?/,
40
+ const multipleSpacesRegex = /( {2,})( [+*{?]|[^+*{?]|$)/,
41
41
  regexResults = multipleSpacesRegex.exec(value);
42
42
 
43
43
  if (regexResults !== null) {
44
- const count = regexResults[0].length;
44
+ const count = regexResults[1].length;
45
45
 
46
46
  context.report({
47
47
  node,
@@ -74,7 +74,7 @@ module.exports = {
74
74
  nodeValue = token.value;
75
75
 
76
76
  if (nodeType === "RegularExpression") {
77
- checkRegex(node, nodeValue, token.start);
77
+ checkRegex(node, nodeValue, token.range[0]);
78
78
  }
79
79
  }
80
80
 
@@ -100,7 +100,7 @@ module.exports = {
100
100
  const shadowed = regExpVar && regExpVar.defs.length > 0;
101
101
 
102
102
  if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0]) && !shadowed) {
103
- checkRegex(node, node.arguments[0].value, node.arguments[0].start + 1);
103
+ checkRegex(node, node.arguments[0].value, node.arguments[0].range[0] + 1);
104
104
  }
105
105
  }
106
106
 
@@ -4,6 +4,13 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Helpers
9
+ //------------------------------------------------------------------------------
10
+
11
+ const DEFAULT_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'.",
12
+ CUSTOM_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'. {{customMessage}}";
13
+
7
14
  //------------------------------------------------------------------------------
8
15
  // Rule Definition
9
16
  //------------------------------------------------------------------------------
@@ -19,20 +26,43 @@ module.exports = {
19
26
  schema: {
20
27
  type: "array",
21
28
  items: {
22
- type: "string"
29
+ oneOf: [
30
+ {
31
+ type: "string"
32
+ },
33
+ {
34
+ type: "object",
35
+ properties: {
36
+ name: { type: "string" },
37
+ message: { type: "string" }
38
+ },
39
+ required: ["name"],
40
+ additionalProperties: false
41
+ }
42
+ ]
23
43
  },
24
- uniqueItems: true
44
+ uniqueItems: true,
45
+ minItems: 0
25
46
  }
26
47
  },
27
48
 
28
49
  create(context) {
29
- const restrictedGlobals = context.options;
30
50
 
31
- // if no globals are restricted we don't need to check
32
- if (restrictedGlobals.length === 0) {
51
+ // If no globals are restricted, we don't need to do anything
52
+ if (context.options.length === 0) {
33
53
  return {};
34
54
  }
35
55
 
56
+ const restrictedGlobalMessages = context.options.reduce((memo, option) => {
57
+ if (typeof option === "string") {
58
+ memo[option] = null;
59
+ } else {
60
+ memo[option.name] = option.message;
61
+ }
62
+
63
+ return memo;
64
+ }, {});
65
+
36
66
  /**
37
67
  * Report a variable to be used as a restricted global.
38
68
  * @param {Reference} reference the variable reference
@@ -40,9 +70,20 @@ module.exports = {
40
70
  * @private
41
71
  */
42
72
  function reportReference(reference) {
43
- context.report({ node: reference.identifier, message: "Unexpected use of '{{name}}'.", data: {
44
- name: reference.identifier.name
45
- } });
73
+ const name = reference.identifier.name,
74
+ customMessage = restrictedGlobalMessages[name],
75
+ message = customMessage
76
+ ? CUSTOM_MESSAGE_TEMPLATE
77
+ : DEFAULT_MESSAGE_TEMPLATE;
78
+
79
+ context.report({
80
+ node: reference.identifier,
81
+ message,
82
+ data: {
83
+ name,
84
+ customMessage
85
+ }
86
+ });
46
87
  }
47
88
 
48
89
  /**
@@ -52,7 +93,7 @@ module.exports = {
52
93
  * @private
53
94
  */
54
95
  function isRestricted(name) {
55
- return restrictedGlobals.indexOf(name) >= 0;
96
+ return restrictedGlobalMessages.hasOwnProperty(name);
56
97
  }
57
98
 
58
99
  return {
@@ -109,20 +109,28 @@ module.exports = {
109
109
  if (matchedObjectProperty) {
110
110
  const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
111
111
 
112
- // eslint-disable-next-line eslint-plugin/report-message-format
113
- context.report({ node, message: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", data: {
114
- objectName,
115
- propertyName,
116
- message
117
- } });
112
+ context.report({
113
+ node,
114
+ // eslint-disable-next-line eslint-plugin/report-message-format
115
+ message: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}",
116
+ data: {
117
+ objectName,
118
+ propertyName,
119
+ message
120
+ }
121
+ });
118
122
  } else if (globalMatchedProperty) {
119
123
  const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
120
124
 
121
- // eslint-disable-next-line eslint-plugin/report-message-format
122
- context.report({ node, message: "'{{propertyName}}' is restricted from being used.{{message}}", data: {
123
- propertyName,
124
- message
125
- } });
125
+ context.report({
126
+ node,
127
+ // eslint-disable-next-line eslint-plugin/report-message-format
128
+ message: "'{{propertyName}}' is restricted from being used.{{message}}",
129
+ data: {
130
+ propertyName,
131
+ message
132
+ }
133
+ });
126
134
  }
127
135
  }
128
136