eslint 1.7.3 → 1.8.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.
@@ -13,13 +13,15 @@
13
13
  module.exports = function(context) {
14
14
 
15
15
  // Use options.max or 2 as default
16
- var numLines = 2;
16
+ var max = 2,
17
+ maxEOF;
17
18
 
18
19
  // store lines that appear empty but really aren't
19
20
  var notEmpty = [];
20
21
 
21
22
  if (context.options.length) {
22
- numLines = context.options[0].max;
23
+ max = context.options[0].max;
24
+ maxEOF = context.options[0].maxEOF;
23
25
  }
24
26
 
25
27
  //--------------------------------------------------------------------------
@@ -45,17 +47,28 @@ module.exports = function(context) {
45
47
  location,
46
48
  trimmedLines = lines.map(function(str) {
47
49
  return str.trim();
48
- });
50
+ }),
51
+ firstOfEndingBlankLines;
49
52
 
50
53
  // add the notEmpty lines in there with a placeholder
51
54
  notEmpty.forEach(function(x, i) {
52
55
  trimmedLines[i] = x;
53
56
  });
54
57
 
55
- // a single empty line at the end is a valid case regardless of the value of "max" option
56
- if (trimmedLines[trimmedLines.length - 2] !== "" &&
57
- trimmedLines[trimmedLines.length - 1] === "") {
58
- trimmedLines = trimmedLines.slice(0, -1);
58
+ if (typeof maxEOF === "undefined") {
59
+ // swallow the final newline, as some editors add it
60
+ // automatically and we don't want it to cause an issue
61
+ if (trimmedLines[trimmedLines.length - 1] === "") {
62
+ trimmedLines = trimmedLines.slice(0, -1);
63
+ }
64
+ firstOfEndingBlankLines = trimmedLines.length;
65
+ } else {
66
+ // save the number of the first of the last blank lines
67
+ firstOfEndingBlankLines = trimmedLines.length;
68
+ while (trimmedLines[firstOfEndingBlankLines - 1] === ""
69
+ && firstOfEndingBlankLines > 0) {
70
+ firstOfEndingBlankLines--;
71
+ }
59
72
  }
60
73
 
61
74
  // Aggregate and count blank lines
@@ -67,12 +80,22 @@ module.exports = function(context) {
67
80
  if (lastLocation === currentLocation - 1) {
68
81
  blankCounter++;
69
82
  } else {
70
- if (blankCounter >= numLines) {
71
- location = {
72
- line: lastLocation + 1,
73
- column: lines[lastLocation].length
74
- };
75
- context.report(node, location, "Multiple blank lines not allowed.");
83
+ location = {
84
+ line: lastLocation + 1,
85
+ column: 1
86
+ };
87
+ if (lastLocation < firstOfEndingBlankLines) {
88
+ // within the file, not at the end
89
+ if (blankCounter >= max) {
90
+ context.report(node, location,
91
+ "Multiple blank lines not allowed.");
92
+ }
93
+ } else {
94
+ // inside the last blank lines
95
+ if (blankCounter >= maxEOF) {
96
+ context.report(node, location,
97
+ "Too many blank lines at the end of file.");
98
+ }
76
99
  }
77
100
 
78
101
  // Finally, reset the blank counter
@@ -90,6 +113,9 @@ module.exports.schema = [
90
113
  "properties": {
91
114
  "max": {
92
115
  "type": "integer"
116
+ },
117
+ "maxEOF": {
118
+ "type": "integer"
93
119
  }
94
120
  },
95
121
  "required": ["max"],
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Rule to flag use of unary increment and decrement operators.
3
3
  * @author Ian Christian Myers
4
+ * @author Brody McKee (github.com/mrmckeb)
4
5
  */
5
6
 
6
7
  "use strict";
@@ -11,9 +12,19 @@
11
12
 
12
13
  module.exports = function(context) {
13
14
 
15
+ var config = context.options[0],
16
+ allowInForAfterthought = false;
17
+
18
+ if (typeof config === "object") {
19
+ allowInForAfterthought = config.allowForLoopAfterthoughts === true;
20
+ }
21
+
14
22
  return {
15
23
 
16
24
  "UpdateExpression": function(node) {
25
+ if (allowInForAfterthought && node.parent.type === "ForStatement") {
26
+ return;
27
+ }
17
28
  context.report(node, "Unary operator '" + node.operator + "' used.");
18
29
  }
19
30
 
@@ -21,4 +32,14 @@ module.exports = function(context) {
21
32
 
22
33
  };
23
34
 
24
- module.exports.schema = [];
35
+ module.exports.schema = [
36
+ {
37
+ "type": "object",
38
+ "properties": {
39
+ "allowForLoopAfterthoughts": {
40
+ "type": "boolean"
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }
45
+ ];
@@ -14,9 +14,20 @@ module.exports = function(context) {
14
14
 
15
15
  var options = {
16
16
  builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals),
17
- hoist: (context.options[0] && context.options[0].hoist) || "functions"
17
+ hoist: (context.options[0] && context.options[0].hoist) || "functions",
18
+ allow: (context.options[0] && context.options[0].allow) || []
18
19
  };
19
20
 
21
+ /**
22
+ * Check if variable name is allowed.
23
+ *
24
+ * @param {ASTNode} variable The variable to check.
25
+ * @returns {boolean} Whether or not the variable name is allowed.
26
+ */
27
+ function isAllowed(variable) {
28
+ return options.allow.indexOf(variable.name) !== -1;
29
+ }
30
+
20
31
  /**
21
32
  * Checks if a variable of the class name in the class scope of ClassDeclaration.
22
33
  *
@@ -142,7 +153,8 @@ module.exports = function(context) {
142
153
  // Skip "arguments".
143
154
  variable.identifiers.length > 0 &&
144
155
  // Skip variables of a class name in the class scope of ClassDeclaration.
145
- !isDuplicatedClassNameVariable(variable)
156
+ !isDuplicatedClassNameVariable(variable) &&
157
+ !isAllowed(variable)
146
158
  );
147
159
  });
148
160
 
@@ -175,7 +187,13 @@ module.exports.schema = [
175
187
  "type": "object",
176
188
  "properties": {
177
189
  "builtinGlobals": {"type": "boolean"},
178
- "hoist": {"enum": ["all", "functions", "never"]}
190
+ "hoist": {"enum": ["all", "functions", "never"]},
191
+ "allow": {
192
+ "type": "array",
193
+ "items": {
194
+ "type": "string"
195
+ }
196
+ }
179
197
  },
180
198
  "additionalProperties": false
181
199
  }
@@ -6,6 +6,8 @@
6
6
  */
7
7
  "use strict";
8
8
 
9
+ var astUtils = require("../ast-utils");
10
+
9
11
  //------------------------------------------------------------------------------
10
12
  // Rule Definition
11
13
  //------------------------------------------------------------------------------
@@ -14,258 +16,201 @@ module.exports = function(context) {
14
16
 
15
17
  var MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",
16
18
  REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",
17
- exceptionsArray = (context.options.length === 2) ? context.options[1].exceptions : [],
19
+ ALWAYS = context.options[0] === "always",
20
+
21
+ exceptionsArrayOptions = (context.options.length === 2) ? context.options[1].exceptions : [],
18
22
  options = {},
19
- rejectedSpaceRegExp,
20
- missingSpaceRegExp,
21
- spaceChecks;
22
-
23
- if (exceptionsArray && exceptionsArray.length) {
24
- options.braceException = exceptionsArray.indexOf("{}") !== -1 || false;
25
- options.bracketException = exceptionsArray.indexOf("[]") !== -1 || false;
26
- options.parenException = exceptionsArray.indexOf("()") !== -1 || false;
27
- options.empty = exceptionsArray.indexOf("empty") !== -1 || false;
23
+ exceptions;
24
+
25
+ if (exceptionsArrayOptions.length) {
26
+ options.braceException = exceptionsArrayOptions.indexOf("{}") !== -1;
27
+ options.bracketException = exceptionsArrayOptions.indexOf("[]") !== -1;
28
+ options.parenException = exceptionsArrayOptions.indexOf("()") !== -1;
29
+ options.empty = exceptionsArrayOptions.indexOf("empty") !== -1;
28
30
  }
29
31
 
30
32
  /**
31
- * Used with the `never` option to produce, given the exception options,
32
- * two regular expressions to check for missing and rejected spaces.
33
+ * Produces an object with the opener and closer exception values
33
34
  * @param {Object} opts The exception options
34
- * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
35
+ * @returns {Object} `openers` and `closers` exception values
35
36
  * @private
36
37
  */
37
- function getNeverChecks(opts) {
38
- var missingSpaceOpeners = [],
39
- missingSpaceClosers = [],
40
- rejectedSpaceOpeners = ["\\s"],
41
- rejectedSpaceClosers = ["\\s"],
42
- missingSpaceCheck,
43
- rejectedSpaceCheck;
44
-
45
- // Populate openers and closers
46
- if (opts.braceException) {
47
- missingSpaceOpeners.push("\\{");
48
- missingSpaceClosers.push("\\}");
49
- rejectedSpaceOpeners.push("\\{");
50
- rejectedSpaceClosers.push("\\}");
51
- }
52
- if (opts.bracketException) {
53
- missingSpaceOpeners.push("\\[");
54
- missingSpaceClosers.push("\\]");
55
- rejectedSpaceOpeners.push("\\[");
56
- rejectedSpaceClosers.push("\\]");
57
- }
58
- if (opts.parenException) {
59
- missingSpaceOpeners.push("\\(");
60
- missingSpaceClosers.push("\\)");
61
- rejectedSpaceOpeners.push("\\(");
62
- rejectedSpaceClosers.push("\\)");
63
- }
64
- if (opts.empty) {
65
- missingSpaceOpeners.push("\\)");
66
- missingSpaceClosers.push("\\(");
67
- rejectedSpaceOpeners.push("\\)");
68
- rejectedSpaceClosers.push("\\(");
38
+ function getExceptions() {
39
+ var openers = [],
40
+ closers = [];
41
+ if (options.braceException) {
42
+ openers.push("{");
43
+ closers.push("}");
69
44
  }
70
45
 
71
- if (missingSpaceOpeners.length) {
72
- missingSpaceCheck = "\\((" + missingSpaceOpeners.join("|") + ")";
73
- if (missingSpaceClosers.length) {
74
- missingSpaceCheck += "|";
75
- }
46
+ if (options.bracketException) {
47
+ openers.push("[");
48
+ closers.push("]");
76
49
  }
77
- if (missingSpaceClosers.length) {
78
- missingSpaceCheck += "(" + missingSpaceClosers.join("|") + ")\\)";
79
- }
80
-
81
- // compose the rejected regexp
82
- rejectedSpaceCheck = "\\( +[^" + rejectedSpaceOpeners.join("") + "]";
83
- rejectedSpaceCheck += "|[^" + rejectedSpaceClosers.join("") + "] +\\)";
84
50
 
85
- return {
86
- // e.g. \((\{)|(\})\) --- where {} is an exception
87
- missingSpace: missingSpaceCheck || ".^",
88
- // e.g. \( +[^ \n\r\{]|[^ \n\r\}] +\) --- where {} is an exception
89
- rejectedSpace: rejectedSpaceCheck
90
- };
91
- }
92
-
93
- /**
94
- * Used with the `always` option to produce, given the exception options,
95
- * two regular expressions to check for missing and rejected spaces.
96
- * @param {Object} opts The exception options
97
- * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
98
- * @private
99
- */
100
- function getAlwaysChecks(opts) {
101
- var missingSpaceOpeners = ["\\s", "\\)"],
102
- missingSpaceClosers = ["\\s", "\\("],
103
- rejectedSpaceOpeners = [],
104
- rejectedSpaceClosers = [],
105
- missingSpaceCheck,
106
- rejectedSpaceCheck;
107
-
108
- // Populate openers and closers
109
- if (opts.braceException) {
110
- missingSpaceOpeners.push("\\{");
111
- missingSpaceClosers.push("\\}");
112
- rejectedSpaceOpeners.push(" \\{");
113
- rejectedSpaceClosers.push("\\} ");
114
- }
115
- if (opts.bracketException) {
116
- missingSpaceOpeners.push("\\[");
117
- missingSpaceClosers.push("\\]");
118
- rejectedSpaceOpeners.push(" \\[");
119
- rejectedSpaceClosers.push("\\] ");
51
+ if (options.parenException) {
52
+ openers.push("(");
53
+ closers.push(")");
120
54
  }
121
- if (opts.parenException) {
122
- missingSpaceOpeners.push("\\(");
123
- missingSpaceClosers.push("\\)");
124
- rejectedSpaceOpeners.push(" \\(");
125
- rejectedSpaceClosers.push("\\) ");
126
- }
127
- if (opts.empty) {
128
- rejectedSpaceOpeners.push(" \\)");
129
- rejectedSpaceClosers.push("\\( ");
130
- }
131
-
132
- // compose the allowed regexp
133
- missingSpaceCheck = "\\([^" + missingSpaceOpeners.join("") + "]";
134
- missingSpaceCheck += "|[^" + missingSpaceClosers.join("") + "]\\)";
135
55
 
136
- // compose the rejected regexp
137
- if (rejectedSpaceOpeners.length) {
138
- rejectedSpaceCheck = "\\((" + rejectedSpaceOpeners.join("|") + ")";
139
- if (rejectedSpaceClosers.length) {
140
- rejectedSpaceCheck += "|";
141
- }
142
- }
143
- if (rejectedSpaceClosers.length) {
144
- rejectedSpaceCheck += "(" + rejectedSpaceClosers.join("|") + ")\\)";
56
+ if (options.empty) {
57
+ openers.push(")");
58
+ closers.push("(");
145
59
  }
146
60
 
147
61
  return {
148
- // e.g. \([^ \)\r\n\{]|[^ \(\r\n\}]\) --- where {} is an exception
149
- missingSpace: missingSpaceCheck,
150
- // e.g. \(( \{})|(\} )\) --- where {} is an excpetion
151
- rejectedSpace: rejectedSpaceCheck || ".^"
62
+ openers: openers,
63
+ closers: closers
152
64
  };
153
65
  }
154
66
 
155
- spaceChecks = (context.options[0] === "always") ? getAlwaysChecks(options) : getNeverChecks(options);
156
- missingSpaceRegExp = new RegExp(spaceChecks.missingSpace, "mg");
157
- rejectedSpaceRegExp = new RegExp(spaceChecks.rejectedSpace, "mg");
158
-
159
-
160
67
  //--------------------------------------------------------------------------
161
68
  // Helpers
162
69
  //--------------------------------------------------------------------------
163
-
164
- var skipRanges = [];
70
+ var sourceCode = context.getSourceCode();
165
71
 
166
72
  /**
167
- * Adds the range of a node to the set to be skipped when checking parens
168
- * @param {ASTNode} node The node to skip
169
- * @returns {void}
170
- * @private
73
+ * Determines if a token is one of the exceptions for the opener paren
74
+ * @param {Object} token The token to check
75
+ * @returns {boolean} True if the token is one of the exceptions for the opener paren
171
76
  */
172
- function addSkipRange(node) {
173
- skipRanges.push(node.range);
77
+ function isOpenerException(token) {
78
+ return token.type === "Punctuator" && exceptions.openers.indexOf(token.value) >= 0;
174
79
  }
175
80
 
176
81
  /**
177
- * Sorts the skipRanges array. Must be called before shouldSkip
178
- * @returns {void}
179
- * @private
82
+ * Determines if a token is one of the exceptions for the closer paren
83
+ * @param {Object} token The token to check
84
+ * @returns {boolean} True if the token is one of the exceptions for the closer paren
180
85
  */
181
- function sortSkipRanges() {
182
- skipRanges.sort(function(a, b) {
183
- return a[0] - b[0];
184
- });
86
+ function isCloserException(token) {
87
+ return token.type === "Punctuator" && exceptions.closers.indexOf(token.value) >= 0;
185
88
  }
186
89
 
187
90
  /**
188
- * Checks if a certain position in the source should be skipped
189
- * @param {Number} pos The 0-based index in the source
190
- * @returns {boolean} whether the position should be skipped
191
- * @private
91
+ * Determines if an opener paren should have a missing space after it
92
+ * @param {Object} left The paren token
93
+ * @param {Object} right The token after it
94
+ * @returns {boolean} True if the paren should have a space
192
95
  */
193
- function shouldSkip(pos) {
194
- var i, len, range;
195
- for (i = 0, len = skipRanges.length; i < len; i += 1) {
196
- range = skipRanges[i];
197
- if (pos < range[0]) {
198
- break;
199
- } else if (pos < range[1]) {
200
- return true;
96
+ function shouldOpenerHaveSpace(left, right) {
97
+ if (sourceCode.isSpaceBetweenTokens(left, right)) {
98
+ return false;
99
+ }
100
+
101
+ if (ALWAYS) {
102
+ if (right.type === "Punctuator" && right.value === ")") {
103
+ return false;
201
104
  }
105
+ return !isOpenerException(right);
106
+ } else {
107
+ return isOpenerException(right);
202
108
  }
203
- return false;
204
109
  }
205
110
 
111
+ /**
112
+ * Determines if an closer paren should have a missing space after it
113
+ * @param {Object} left The token before the paren
114
+ * @param {Object} right The paren token
115
+ * @returns {boolean} True if the paren should have a space
116
+ */
117
+ function shouldCloserHaveSpace(left, right) {
118
+ if (left.type === "Punctuator" && left.value === "(") {
119
+ return false;
120
+ }
206
121
 
207
- //--------------------------------------------------------------------------
208
- // Public
209
- //--------------------------------------------------------------------------
210
-
211
- return {
122
+ if (sourceCode.isSpaceBetweenTokens(left, right)) {
123
+ return false;
124
+ }
212
125
 
213
- "Program:exit": function checkParenSpaces(node) {
214
-
215
- var nextMatch,
216
- nextLine,
217
- column,
218
- line = 1,
219
- source = context.getSource(),
220
- pos = 0;
221
-
222
- /**
223
- * Check the match
224
- * @param {object} match Object to match
225
- * @param {string} message Message to report
226
- * @returns {void}
227
- * @private
228
- */
229
- function checkMatch(match, message) {
230
- if (source.charAt(match.index) !== "(") {
231
- // Matched a closing paren pattern
232
- match.index += 1;
233
- }
126
+ if (ALWAYS) {
127
+ return !isCloserException(left);
128
+ } else {
129
+ return isCloserException(left);
130
+ }
131
+ }
234
132
 
235
- if (!shouldSkip(match.index)) {
236
- while ((nextLine = source.indexOf("\n", pos)) !== -1 && nextLine < match.index) {
237
- pos = nextLine + 1;
238
- line += 1;
239
- }
240
- column = match.index - pos;
133
+ /**
134
+ * Determines if an opener paren should not have an existing space after it
135
+ * @param {Object} left The paren token
136
+ * @param {Object} right The token after it
137
+ * @returns {boolean} True if the paren should reject the space
138
+ */
139
+ function shouldOpenerRejectSpace(left, right) {
140
+ if (!astUtils.isTokenOnSameLine(left, right)) {
141
+ return false;
142
+ }
241
143
 
242
- context.report(node, { line: line, column: column }, message);
243
- }
244
- }
144
+ if (!sourceCode.isSpaceBetweenTokens(left, right)) {
145
+ return false;
146
+ }
245
147
 
246
- sortSkipRanges();
148
+ if (ALWAYS) {
149
+ return isOpenerException(right);
150
+ } else {
151
+ return !isOpenerException(right);
152
+ }
153
+ }
247
154
 
248
- while ((nextMatch = rejectedSpaceRegExp.exec(source)) !== null) {
249
- checkMatch(nextMatch, REJECTED_SPACE_MESSAGE);
250
- }
155
+ /**
156
+ * Determines if an closer paren should not have an existing space after it
157
+ * @param {Object} left The token before the paren
158
+ * @param {Object} right The paren token
159
+ * @returns {boolean} True if the paren should reject the space
160
+ */
161
+ function shouldCloserRejectSpace(left, right) {
162
+ if (left.type === "Punctuator" && left.value === "(") {
163
+ return false;
164
+ }
251
165
 
252
- while ((nextMatch = missingSpaceRegExp.exec(source)) !== null) {
253
- checkMatch(nextMatch, MISSING_SPACE_MESSAGE);
254
- }
166
+ if (!astUtils.isTokenOnSameLine(left, right)) {
167
+ return false;
168
+ }
255
169
 
256
- },
170
+ if (!sourceCode.isSpaceBetweenTokens(left, right)) {
171
+ return false;
172
+ }
257
173
 
174
+ if (ALWAYS) {
175
+ return isCloserException(left);
176
+ } else {
177
+ return !isCloserException(left);
178
+ }
179
+ }
258
180
 
259
- // These nodes can contain parentheses that this rule doesn't care about
181
+ //--------------------------------------------------------------------------
182
+ // Public
183
+ //--------------------------------------------------------------------------
260
184
 
261
- LineComment: addSkipRange,
185
+ return {
186
+ "Program": function checkParenSpaces(node) {
187
+ var tokens = node.tokens;
188
+ var prevToken, nextToken;
189
+ exceptions = getExceptions();
262
190
 
263
- BlockComment: addSkipRange,
191
+ tokens.forEach(function(token, i) {
192
+ prevToken = tokens[i - 1];
193
+ nextToken = tokens[i + 1];
264
194
 
265
- Literal: addSkipRange,
195
+ if (token.type !== "Punctuator") {
196
+ return;
197
+ }
266
198
 
267
- TemplateElement: addSkipRange
199
+ if (token.value !== "(" && token.value !== ")") {
200
+ return;
201
+ }
268
202
 
203
+ if (token.value === "(" && shouldOpenerHaveSpace(token, nextToken)) {
204
+ context.report(node, token.loc.end, MISSING_SPACE_MESSAGE);
205
+ } else if (token.value === "(" && shouldOpenerRejectSpace(token, nextToken)) {
206
+ context.report(node, token.loc.end, REJECTED_SPACE_MESSAGE);
207
+ } else if (token.value === ")" && shouldCloserHaveSpace(prevToken, token)) {
208
+ context.report(node, token.loc.end, MISSING_SPACE_MESSAGE);
209
+ } else if (token.value === ")" && shouldCloserRejectSpace(prevToken, token)) {
210
+ context.report(node, token.loc.end, REJECTED_SPACE_MESSAGE);
211
+ }
212
+ });
213
+ }
269
214
  };
270
215
 
271
216
  };