eslint 1.9.0 → 1.10.3

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 (51) hide show
  1. package/README.md +1 -1
  2. package/bin/eslint.js +1 -2
  3. package/lib/ast-utils.js +22 -1
  4. package/lib/cli-engine.js +15 -7
  5. package/lib/cli.js +2 -1
  6. package/lib/config/config-file.js +440 -0
  7. package/lib/config/config-initializer.js +241 -0
  8. package/lib/config/config-ops.js +186 -0
  9. package/lib/{config-validator.js → config/config-validator.js} +10 -2
  10. package/lib/config.js +39 -183
  11. package/lib/eslint.js +49 -41
  12. package/lib/file-finder.js +34 -15
  13. package/lib/ignored-paths.js +1 -1
  14. package/lib/options.js +7 -1
  15. package/lib/rules/block-spacing.js +7 -2
  16. package/lib/rules/brace-style.js +3 -1
  17. package/lib/rules/comma-spacing.js +25 -66
  18. package/lib/rules/consistent-this.js +25 -29
  19. package/lib/rules/curly.js +37 -7
  20. package/lib/rules/eqeqeq.js +17 -2
  21. package/lib/rules/id-length.js +2 -2
  22. package/lib/rules/indent.js +7 -10
  23. package/lib/rules/lines-around-comment.js +32 -12
  24. package/lib/rules/new-cap.js +11 -1
  25. package/lib/rules/no-alert.js +2 -3
  26. package/lib/rules/no-catch-shadow.js +7 -13
  27. package/lib/rules/no-extend-native.js +1 -2
  28. package/lib/rules/no-fallthrough.js +1 -2
  29. package/lib/rules/no-implicit-coercion.js +19 -0
  30. package/lib/rules/no-label-var.js +9 -23
  31. package/lib/rules/no-multiple-empty-lines.js +1 -1
  32. package/lib/rules/no-sequences.js +2 -1
  33. package/lib/rules/no-shadow.js +31 -58
  34. package/lib/rules/no-spaced-func.js +16 -12
  35. package/lib/rules/no-undef-init.js +5 -3
  36. package/lib/rules/no-undef.js +10 -13
  37. package/lib/rules/no-use-before-define.js +7 -21
  38. package/lib/rules/operator-linebreak.js +3 -2
  39. package/lib/rules/quotes.js +34 -15
  40. package/lib/rules/require-jsdoc.js +61 -2
  41. package/lib/rules/space-after-keywords.js +2 -0
  42. package/lib/rules/space-before-function-paren.js +5 -26
  43. package/lib/rules/space-before-keywords.js +5 -2
  44. package/lib/rules/spaced-comment.js +1 -3
  45. package/lib/rules/valid-jsdoc.js +8 -4
  46. package/lib/rules/vars-on-top.js +2 -2
  47. package/lib/testers/rule-tester.js +48 -7
  48. package/lib/util/source-code.js +4 -0
  49. package/lib/util.js +0 -92
  50. package/package.json +4 -6
  51. package/lib/config-initializer.js +0 -146
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ var astUtils = require("../ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -18,32 +24,12 @@ module.exports = function(context) {
18
24
  /**
19
25
  * Check if the identifier is present inside current scope
20
26
  * @param {object} scope current scope
21
- * @param {ASTNode} identifier To evaluate
27
+ * @param {string} name To evaluate
22
28
  * @returns {boolean} True if its present
23
29
  * @private
24
30
  */
25
- function findIdentifier(scope, identifier) {
26
- var found = false;
27
-
28
- scope.variables.forEach(function(variable) {
29
- if (variable.name === identifier) {
30
- found = true;
31
- }
32
- });
33
-
34
- scope.references.forEach(function(reference) {
35
- if (reference.identifier.name === identifier) {
36
- found = true;
37
- }
38
- });
39
-
40
- // If we have not found the identifier in this scope, check the parent
41
- // scope.
42
- if (scope.upper && !found) {
43
- return findIdentifier(scope.upper, identifier);
44
- }
45
-
46
- return found;
31
+ function findIdentifier(scope, name) {
32
+ return astUtils.getVariableByName(scope, name) !== null;
47
33
  }
48
34
 
49
35
  //--------------------------------------------------------------------------
@@ -88,7 +88,7 @@ module.exports = function(context) {
88
88
  // within the file, not at the end
89
89
  if (blankCounter >= max) {
90
90
  context.report(node, location,
91
- "More than " + max + " blank lines not allowed.");
91
+ "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed.");
92
92
  }
93
93
  } else {
94
94
  // inside the last blank lines
@@ -85,7 +85,8 @@ module.exports = function(context) {
85
85
  }
86
86
  }
87
87
 
88
- context.report(node, "Unexpected use of comma operator.");
88
+ var child = context.getTokenAfter(node.expressions[0]);
89
+ context.report(node, child.loc.start, "Unexpected use of comma operator.");
89
90
  }
90
91
  };
91
92
 
@@ -6,6 +6,12 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ var astUtils = require("../ast-utils");
14
+
9
15
  //------------------------------------------------------------------------------
10
16
  // Rule Definition
11
17
  //------------------------------------------------------------------------------
@@ -99,70 +105,37 @@ module.exports = function(context) {
99
105
  );
100
106
  }
101
107
 
102
- /**
103
- * Checks if a variable is contained in the list of given scope variables.
104
- * @param {Object} variable The variable to check.
105
- * @param {Array} scopeVars The scope variables to look for.
106
- * @returns {boolean} Whether or not the variable is contains in the list of scope variables.
107
- */
108
- function isContainedInScopeVars(variable, scopeVars) {
109
- return scopeVars.some(function(scopeVar) {
110
- return (
111
- (scopeVar.identifiers.length > 0 || (options.builtinGlobals && "writeable" in scopeVar)) &&
112
- variable.name === scopeVar.name &&
113
- !isDuplicatedClassNameVariable(scopeVar) &&
114
- !isOnInitializer(variable, scopeVar) &&
115
- !(options.hoist !== "all" && isInTdz(variable, scopeVar))
116
- );
117
- });
118
- }
119
-
120
- /**
121
- * Checks if the given variables are shadowed in the given scope.
122
- * @param {Array} variables The variables to look for
123
- * @param {Object} scope The scope to be checked.
124
- * @returns {Array} Variables which are not declared in the given scope.
125
- */
126
- function checkShadowsInScope(variables, scope) {
127
-
128
- var passedVars = [];
129
-
130
- variables.forEach(function(variable) {
131
- // "arguments" is a special case that has no identifiers (#1759)
132
- if (variable.identifiers.length > 0 && isContainedInScopeVars(variable, scope.variables)) {
133
- context.report(
134
- variable.identifiers[0],
135
- "\"{{name}}\" is already declared in the upper scope.",
136
- {name: variable.name});
137
- } else {
138
- passedVars.push(variable);
139
- }
140
- });
141
-
142
- return passedVars;
143
- }
144
-
145
108
  /**
146
109
  * Checks the current context for shadowed variables.
147
110
  * @param {Scope} scope - Fixme
148
111
  * @returns {void}
149
112
  */
150
113
  function checkForShadows(scope) {
151
- var variables = scope.variables.filter(function(variable) {
152
- return (
153
- // Skip "arguments".
154
- variable.identifiers.length > 0 &&
155
- // Skip variables of a class name in the class scope of ClassDeclaration.
156
- !isDuplicatedClassNameVariable(variable) &&
157
- !isAllowed(variable)
158
- );
159
- });
160
-
161
- // iterate through the array of variables and find duplicates with the upper scope
162
- var upper = scope.upper;
163
- while (upper && variables.length) {
164
- variables = checkShadowsInScope(variables, upper);
165
- upper = upper.upper;
114
+ var variables = scope.variables;
115
+ for (var i = 0; i < variables.length; ++i) {
116
+ var variable = variables[i];
117
+
118
+ // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration.
119
+ if (variable.identifiers.length === 0 ||
120
+ isDuplicatedClassNameVariable(variable) ||
121
+ isAllowed(variable)
122
+ ) {
123
+ continue;
124
+ }
125
+
126
+ // Gets shadowed variable.
127
+ var shadowed = astUtils.getVariableByName(scope.upper, variable.name);
128
+ if (shadowed &&
129
+ (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) &&
130
+ !isOnInitializer(variable, shadowed) &&
131
+ !(options.hoist !== "all" && isInTdz(variable, shadowed))
132
+ ) {
133
+ context.report({
134
+ node: variable.identifiers[0],
135
+ message: "\"{{name}}\" is already declared in the upper scope.",
136
+ data: variable
137
+ });
138
+ }
166
139
  }
167
140
  }
168
141
 
@@ -21,26 +21,30 @@ module.exports = function(context) {
21
21
  */
22
22
  function detectOpenSpaces(node) {
23
23
  var lastCalleeToken = sourceCode.getLastToken(node.callee),
24
- tokens = sourceCode.getTokens(node),
25
- i = tokens.indexOf(lastCalleeToken),
26
- l = tokens.length;
27
-
28
- while (i < l && tokens[i].value !== "(") {
29
- ++i;
30
- }
31
-
32
- if (i >= l) {
33
- return;
24
+ prevToken = lastCalleeToken,
25
+ parenToken = sourceCode.getTokenAfter(lastCalleeToken);
26
+
27
+ // advances to an open parenthesis.
28
+ while (
29
+ parenToken &&
30
+ parenToken.range[1] < node.range[1] &&
31
+ parenToken.value !== "("
32
+ ) {
33
+ prevToken = parenToken;
34
+ parenToken = sourceCode.getTokenAfter(parenToken);
34
35
  }
35
36
 
36
37
  // look for a space between the callee and the open paren
37
- if (sourceCode.isSpaceBetweenTokens(tokens[i - 1], tokens[i])) {
38
+ if (parenToken &&
39
+ parenToken.range[1] < node.range[1] &&
40
+ sourceCode.isSpaceBetweenTokens(prevToken, parenToken)
41
+ ) {
38
42
  context.report({
39
43
  node: node,
40
44
  loc: lastCalleeToken.loc.start,
41
45
  message: "Unexpected space between function name and paren.",
42
46
  fix: function(fixer) {
43
- return fixer.removeRange([tokens[i - 1].range[1], tokens[i].range[0]]);
47
+ return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
44
48
  }
45
49
  });
46
50
  }
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * @fileoverview Rule to flag when initializing to undefined
3
3
  * @author Ilya Volodin
4
+ * @copyright 2013 Ilya Volodin. All rights reserved.
5
+ * See LICENSE in root directory for full license.
4
6
  */
5
7
 
6
8
  "use strict";
@@ -14,10 +16,10 @@ module.exports = function(context) {
14
16
  return {
15
17
 
16
18
  "VariableDeclarator": function(node) {
17
- var name = node.id.name;
18
- var init = node.init && node.init.name;
19
+ var name = node.id.name,
20
+ init = node.init && node.init.name;
19
21
 
20
- if (init === "undefined") {
22
+ if (init === "undefined" && node.parent.kind !== "const") {
21
23
  context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name });
22
24
  }
23
25
  }
@@ -10,12 +10,14 @@
10
10
  // Requirements
11
11
  //------------------------------------------------------------------------------
12
12
 
13
- // none!
13
+ var astUtils = require("../ast-utils");
14
14
 
15
15
  //------------------------------------------------------------------------------
16
16
  // Helpers
17
17
  //------------------------------------------------------------------------------
18
18
 
19
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
20
+
19
21
  /**
20
22
  * Check if a variable is an implicit declaration
21
23
  * @param {ASTNode} variable node to evaluate
@@ -35,18 +37,13 @@ function isImplicitGlobal(variable) {
35
37
  * @returns {Variable} The variable, or null if ref refers to an undeclared variable.
36
38
  */
37
39
  function getDeclaredGlobalVariable(scope, ref) {
38
- var declaredGlobal = null;
39
- scope.variables.some(function(variable) {
40
- if (variable.name === ref.identifier.name) {
41
- // If it's an implicit global, it must have a `writeable` field (indicating it was declared)
42
- if (!isImplicitGlobal(variable) || {}.hasOwnProperty.call(variable, "writeable")) {
43
- declaredGlobal = variable;
44
- return true;
45
- }
46
- }
47
- return false;
48
- });
49
- return declaredGlobal;
40
+ var variable = astUtils.getVariableByName(scope, ref.identifier.name);
41
+
42
+ // If it's an implicit global, it must have a `writeable` field (indicating it was declared)
43
+ if (variable && (!isImplicitGlobal(variable) || hasOwnProperty.call(variable, "writeable"))) {
44
+ return variable;
45
+ }
46
+ return null;
50
47
  }
51
48
 
52
49
  /**
@@ -6,6 +6,12 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ var astUtils = require("../ast-utils");
14
+
9
15
  //------------------------------------------------------------------------------
10
16
  // Constants
11
17
  //------------------------------------------------------------------------------
@@ -18,26 +24,6 @@ var NO_FUNC = "nofunc";
18
24
 
19
25
  module.exports = function(context) {
20
26
 
21
- /**
22
- * Finds variable declarations in a given scope.
23
- * @param {string} name The variable name to find.
24
- * @param {Scope} scope The scope to search in.
25
- * @returns {Object} The variable declaration object.
26
- * @private
27
- */
28
- function findDeclaration(name, scope) {
29
- // try searching in the current scope first
30
- for (var i = 0, l = scope.variables.length; i < l; i++) {
31
- if (scope.variables[i].name === name) {
32
- return scope.variables[i];
33
- }
34
- }
35
- // check if there's upper scope and call recursivly till we find the variable
36
- if (scope.upper) {
37
- return findDeclaration(name, scope.upper);
38
- }
39
- }
40
-
41
27
  /**
42
28
  * Finds and validates all variables in a given scope.
43
29
  * @param {Scope} scope The scope object.
@@ -68,7 +54,7 @@ module.exports = function(context) {
68
54
  if (reference.resolved && reference.resolved.identifiers.length > 0) {
69
55
  checkLocationAndReport(reference, reference.resolved);
70
56
  } else {
71
- var declaration = findDeclaration(reference.identifier.name, scope);
57
+ var declaration = astUtils.getVariableByName(scope, reference.identifier.name);
72
58
  // if there're no identifiers, this is a global environment variable
73
59
  if (declaration && declaration.identifiers.length !== 0) {
74
60
  checkLocationAndReport(reference, declaration);
@@ -6,7 +6,8 @@
6
6
 
7
7
  "use strict";
8
8
 
9
- var astUtils = require("../ast-utils");
9
+ var assign = require("object-assign"),
10
+ astUtils = require("../ast-utils");
10
11
 
11
12
  //------------------------------------------------------------------------------
12
13
  // Rule Definition
@@ -17,7 +18,7 @@ module.exports = function(context) {
17
18
  var usedDefaultGlobal = !context.options[0];
18
19
  var globalStyle = context.options[0] || "after";
19
20
  var options = context.options[1] || {};
20
- var styleOverrides = options.overrides || {};
21
+ var styleOverrides = options.overrides ? assign({}, options.overrides) : {};
21
22
 
22
23
  if (usedDefaultGlobal && !styleOverrides["?"]) {
23
24
  styleOverrides["?"] = "before";
@@ -11,9 +11,7 @@
11
11
  // Requirements
12
12
  //------------------------------------------------------------------------------
13
13
 
14
- var astUtils = require("../ast-utils"),
15
- toSingleQuotes = require("to-single-quotes"),
16
- toDoubleQuotes = require("to-double-quotes");
14
+ var astUtils = require("../ast-utils");
17
15
 
18
16
  //------------------------------------------------------------------------------
19
17
  // Constants
@@ -23,27 +21,48 @@ var QUOTE_SETTINGS = {
23
21
  "double": {
24
22
  quote: "\"",
25
23
  alternateQuote: "'",
26
- description: "doublequote",
27
- convert: function(str) {
28
- return toDoubleQuotes(str);
29
- }
24
+ description: "doublequote"
30
25
  },
31
26
  "single": {
32
27
  quote: "'",
33
28
  alternateQuote: "\"",
34
- description: "singlequote",
35
- convert: function(str) {
36
- return toSingleQuotes(str);
37
- }
29
+ description: "singlequote"
38
30
  },
39
31
  "backtick": {
40
32
  quote: "`",
41
33
  alternateQuote: "\"",
42
- description: "backtick",
43
- convert: function(str) {
44
- return str.replace(/`/g, "\`").replace(/^(?:\\*)["']|(?:\\*)["']$/g, "`");
45
- }
34
+ description: "backtick"
35
+ }
36
+ };
37
+ /**
38
+ * Switches quoting of javascript string between ' " and `
39
+ * escaping and unescaping as necessary.
40
+ * Only escaping of the minimal set of characters is changed.
41
+ * Note: escaping of newlines when switching from backtick to other quotes is not handled.
42
+ * @param {string} str - A string to convert.
43
+ * @returns {string} The string with changed quotes.
44
+ * @private
45
+ */
46
+ QUOTE_SETTINGS.double.convert =
47
+ QUOTE_SETTINGS.single.convert =
48
+ QUOTE_SETTINGS.backtick.convert = function(str) {
49
+ var newQuote = this.quote;
50
+ var oldQuote = str[0];
51
+ if (newQuote === oldQuote) {
52
+ return str;
46
53
  }
54
+ return newQuote + str.slice(1, -1).replace(/\\(\${|\r\n?|\n|.)|["'`]|\${|(\r\n?|\n)/g, function(match, escaped, newline) {
55
+ if (escaped === oldQuote || oldQuote === "`" && escaped === "${") {
56
+ return escaped; // unescape
57
+ }
58
+ if (match === newQuote || newQuote === "`" && match === "${") {
59
+ return "\\" + match; // escape
60
+ }
61
+ if (newline && oldQuote === "`") {
62
+ return "\\n"; // escape newlines
63
+ }
64
+ return match;
65
+ }) + newQuote;
47
66
  };
48
67
 
49
68
  var AVOID_ESCAPE = "avoid-escape",
@@ -5,8 +5,16 @@
5
5
  */
6
6
  "use strict";
7
7
 
8
+ var assign = require("object-assign");
9
+
8
10
  module.exports = function(context) {
9
11
  var source = context.getSourceCode();
12
+ var DEFAULT_OPTIONS = {
13
+ "FunctionDeclaration": true,
14
+ "MethodDefinition": false,
15
+ "ClassDeclaration": false
16
+ };
17
+ var options = assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {});
10
18
 
11
19
  /**
12
20
  * Report the error message
@@ -17,6 +25,21 @@ module.exports = function(context) {
17
25
  context.report(node, "Missing JSDoc comment.");
18
26
  }
19
27
 
28
+ /**
29
+ * Check if the jsdoc comment is present for class methods
30
+ * @param {ASTNode} node node to examine
31
+ * @returns {void}
32
+ */
33
+ function checkClassMethodJsDoc(node) {
34
+ if (node.parent.type === "MethodDefinition") {
35
+ var jsdocComment = source.getJSDocComment(node);
36
+
37
+ if (!jsdocComment) {
38
+ report(node);
39
+ }
40
+ }
41
+ }
42
+
20
43
  /**
21
44
  * Check if the jsdoc comment is present or not.
22
45
  * @param {ASTNode} node node to examine
@@ -31,8 +54,44 @@ module.exports = function(context) {
31
54
  }
32
55
 
33
56
  return {
34
- "FunctionDeclaration": checkJsDoc
57
+ "FunctionDeclaration": function(node) {
58
+ if (options.FunctionDeclaration) {
59
+ checkJsDoc(node);
60
+ }
61
+ },
62
+ "FunctionExpression": function(node) {
63
+ if (options.MethodDefinition) {
64
+ checkClassMethodJsDoc(node);
65
+ }
66
+ },
67
+ "ClassDeclaration": function(node) {
68
+ if (options.ClassDeclaration) {
69
+ checkJsDoc(node);
70
+ }
71
+ }
35
72
  };
36
73
  };
37
74
 
38
- module.exports.schema = [];
75
+ module.exports.schema = [
76
+ {
77
+ "type": "object",
78
+ "properties": {
79
+ "require": {
80
+ "type": "object",
81
+ "properties": {
82
+ "ClassDeclaration": {
83
+ "type": "boolean"
84
+ },
85
+ "MethodDefinition": {
86
+ "type": "boolean"
87
+ },
88
+ "FunctionDeclaration": {
89
+ "type": "boolean"
90
+ }
91
+ },
92
+ "additionalProperties": false
93
+ }
94
+ },
95
+ "additionalProperties": false
96
+ }
97
+ ];
@@ -33,6 +33,7 @@ module.exports = function(context) {
33
33
  if (hasSpace !== requiresSpace) {
34
34
  context.report({
35
35
  node: node,
36
+ loc: left.loc.end,
36
37
  message: "Keyword \"{{value}}\" must {{not}}be followed by whitespace.",
37
38
  data: {
38
39
  value: value,
@@ -49,6 +50,7 @@ module.exports = function(context) {
49
50
  } else if (left.loc.end.line !== right.loc.start.line) {
50
51
  context.report({
51
52
  node: node,
53
+ loc: left.loc.end,
52
54
  message: "Keyword \"{{value}}\" must not be followed by a newline.",
53
55
  data: {
54
56
  value: value
@@ -37,7 +37,7 @@ module.exports = function(context) {
37
37
  return true;
38
38
  }
39
39
 
40
- parent = context.getAncestors().pop();
40
+ parent = node.parent;
41
41
  return parent.type === "MethodDefinition" ||
42
42
  (parent.type === "Property" &&
43
43
  (
@@ -55,7 +55,6 @@ module.exports = function(context) {
55
55
  */
56
56
  function validateSpacingBeforeParentheses(node) {
57
57
  var isNamed = isNamedFunction(node),
58
- tokens,
59
58
  leftToken,
60
59
  rightToken,
61
60
  location;
@@ -64,31 +63,11 @@ module.exports = function(context) {
64
63
  return;
65
64
  }
66
65
 
67
- tokens = context.getTokens(node);
68
-
69
- if (node.generator) {
70
- if (node.id) {
71
- leftToken = tokens[2];
72
- rightToken = tokens[3];
73
- } else {
74
- // Object methods are named but don't have an id
75
- leftToken = context.getTokenBefore(node);
76
- rightToken = tokens[0];
77
- }
78
- } else if (isNamed) {
79
- if (node.id) {
80
- leftToken = tokens[1];
81
- rightToken = tokens[2];
82
- } else {
83
- // Object methods are named but don't have an id
84
- leftToken = context.getTokenBefore(node);
85
- rightToken = tokens[0];
86
- }
87
- } else {
88
- leftToken = tokens[0];
89
- rightToken = tokens[1];
66
+ rightToken = sourceCode.getFirstToken(node);
67
+ while (rightToken.value !== "(") {
68
+ rightToken = sourceCode.getTokenAfter(rightToken);
90
69
  }
91
-
70
+ leftToken = context.getTokenBefore(rightToken);
92
71
  location = leftToken.loc.end;
93
72
 
94
73
  if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {
@@ -134,7 +134,10 @@ module.exports = function(context) {
134
134
  check(node);
135
135
  // else
136
136
  if (node.alternate) {
137
- check(context.getTokenBefore(node.alternate), { requireSpace: SPACE_REQUIRED });
137
+ var tokens = context.getTokensBefore(node.alternate, 2);
138
+ if (tokens[0].value === "}") {
139
+ check(tokens[1], { requireSpace: SPACE_REQUIRED });
140
+ }
138
141
  }
139
142
  },
140
143
  "ForStatement": check,
@@ -185,7 +188,7 @@ module.exports = function(context) {
185
188
  return;
186
189
  }
187
190
 
188
- checkTokens(node, left, right, { allowedPrecedingChars: [ "(", "{" ] });
191
+ checkTokens(node, left, right, { allowedPrecedingChars: [ "(", "{", "[" ] });
189
192
  },
190
193
  "YieldExpression": function(node) {
191
194
  check(node, { allowedPrecedingChars: [ "(", "{" ] });
@@ -41,9 +41,7 @@ function escapeAndRepeat(s) {
41
41
  * @returns {string[]} A marker list.
42
42
  */
43
43
  function parseMarkersOption(markers) {
44
- if (!markers) {
45
- markers = [];
46
- }
44
+ markers = markers ? markers.slice(0) : [];
47
45
 
48
46
  // `*` is a marker for JSDoc comments.
49
47
  if (markers.indexOf("*") === -1) {
@@ -24,7 +24,8 @@ module.exports = function(context) {
24
24
  // these both default to true, so you have to explicitly make them false
25
25
  requireReturn = options.requireReturn !== false,
26
26
  requireParamDescription = options.requireParamDescription !== false,
27
- requireReturnDescription = options.requireReturnDescription !== false;
27
+ requireReturnDescription = options.requireReturnDescription !== false,
28
+ requireReturnType = options.requireReturnType !== false;
28
29
 
29
30
  //--------------------------------------------------------------------------
30
31
  // Helpers
@@ -77,7 +78,7 @@ module.exports = function(context) {
77
78
  * @private
78
79
  */
79
80
  function isValidReturnType(tag) {
80
- return tag.type.name === "void" || tag.type.type === "UndefinedLiteral";
81
+ return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral";
81
82
  }
82
83
 
83
84
  /**
@@ -141,10 +142,10 @@ module.exports = function(context) {
141
142
  case "returns":
142
143
  hasReturns = true;
143
144
 
144
- if (!requireReturn && !functionData.returnPresent && tag.type.name !== "void" && tag.type.name !== "undefined") {
145
+ if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag))) {
145
146
  context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement.");
146
147
  } else {
147
- if (!tag.type) {
148
+ if (requireReturnType && !tag.type) {
148
149
  context.report(jsdocNode, "Missing JSDoc return type.");
149
150
  }
150
151
 
@@ -258,6 +259,9 @@ module.exports.schema = [
258
259
  },
259
260
  "matchDescription": {
260
261
  "type": "string"
262
+ },
263
+ "requireReturnType": {
264
+ "type": "boolean"
261
265
  }
262
266
  },
263
267
  "additionalProperties": false