eslint 1.7.1 → 1.9.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.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/conf/eslint.json +3 -0
  3. package/lib/cli-engine.js +74 -74
  4. package/lib/cli.js +12 -10
  5. package/lib/eslint.js +15 -26
  6. package/lib/logging.js +25 -0
  7. package/lib/options.js +7 -2
  8. package/lib/rules/array-bracket-spacing.js +2 -2
  9. package/lib/rules/arrow-body-style.js +71 -0
  10. package/lib/rules/comma-dangle.js +26 -10
  11. package/lib/rules/comma-spacing.js +72 -36
  12. package/lib/rules/eol-last.js +10 -4
  13. package/lib/rules/indent.js +8 -7
  14. package/lib/rules/key-spacing.js +13 -25
  15. package/lib/rules/linebreak-style.js +45 -10
  16. package/lib/rules/max-nested-callbacks.js +1 -1
  17. package/lib/rules/no-arrow-condition.js +88 -0
  18. package/lib/rules/no-case-declarations.js +47 -0
  19. package/lib/rules/no-extend-native.js +3 -3
  20. package/lib/rules/no-magic-numbers.js +22 -5
  21. package/lib/rules/no-mixed-spaces-and-tabs.js +23 -19
  22. package/lib/rules/no-multiple-empty-lines.js +39 -13
  23. package/lib/rules/no-plusplus.js +22 -1
  24. package/lib/rules/no-shadow.js +22 -4
  25. package/lib/rules/no-use-before-define.js +1 -1
  26. package/lib/rules/no-warning-comments.js +1 -1
  27. package/lib/rules/radix.js +36 -6
  28. package/lib/rules/space-in-parens.js +148 -199
  29. package/lib/rules/spaced-comment.js +3 -3
  30. package/lib/rules/valid-jsdoc.js +36 -19
  31. package/lib/rules.js +13 -9
  32. package/lib/testers/rule-tester.js +62 -7
  33. package/lib/util/estraverse.js +54 -0
  34. package/lib/util/glob-util.js +149 -0
  35. package/lib/util/source-code-fixer.js +1 -1
  36. package/lib/util/source-code.js +11 -1
  37. package/lib/util.js +15 -9
  38. package/package.json +21 -21
package/README.md CHANGED
@@ -69,6 +69,7 @@ These folks keep the project moving and are resources for help:
69
69
  * Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
70
70
  * Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - committer
71
71
  * Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
72
+ * Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer
72
73
 
73
74
  ## Releases
74
75
 
package/conf/eslint.json CHANGED
@@ -4,8 +4,10 @@
4
4
  "rules": {
5
5
  "no-alert": 0,
6
6
  "no-array-constructor": 0,
7
+ "no-arrow-condition": 0,
7
8
  "no-bitwise": 0,
8
9
  "no-caller": 0,
10
+ "no-case-declarations": 0,
9
11
  "no-catch-shadow": 0,
10
12
  "no-class-assign": 0,
11
13
  "no-cond-assign": 2,
@@ -110,6 +112,7 @@
110
112
  "no-magic-numbers": 0,
111
113
 
112
114
  "array-bracket-spacing": [0, "never"],
115
+ "arrow-body-style": [0, "as-needed"],
113
116
  "arrow-parens": 0,
114
117
  "arrow-spacing": 0,
115
118
  "accessor-pairs": 0,
package/lib/cli-engine.js CHANGED
@@ -22,7 +22,6 @@ var fs = require("fs"),
22
22
 
23
23
  assign = require("object-assign"),
24
24
  debug = require("debug"),
25
- glob = require("glob"),
26
25
  shell = require("shelljs"),
27
26
 
28
27
  rules = require("./rules"),
@@ -31,6 +30,7 @@ var fs = require("fs"),
31
30
  Config = require("./config"),
32
31
  util = require("./util"),
33
32
  fileEntryCache = require("file-entry-cache"),
33
+ globUtil = require("./util/glob-util"),
34
34
  SourceCodeFixer = require("./util/source-code-fixer"),
35
35
  validator = require("./config-validator"),
36
36
  stringify = require("json-stable-stringify"),
@@ -90,6 +90,10 @@ var defaultOptions = {
90
90
  ignorePath: null,
91
91
  parser: DEFAULT_PARSER,
92
92
  cache: false,
93
+ // in order to honor the cacheFile option if specified
94
+ // this option should not have a default value otherwise
95
+ // it will always be used
96
+ cacheLocation: "",
93
97
  cacheFile: ".eslintcache",
94
98
  fix: false
95
99
  },
@@ -293,51 +297,78 @@ function isErrorMessage(message) {
293
297
  return message.severity === 2;
294
298
  }
295
299
 
300
+ /**
301
+ * create a md5Hash of a given string
302
+ * @param {string} str the string to calculate the hash for
303
+ * @returns {string} the calculated hash
304
+ */
305
+ function md5Hash(str) {
306
+ return crypto
307
+ .createHash("md5")
308
+ .update(str, "utf8")
309
+ .digest("hex");
310
+ }
296
311
 
297
312
  /**
298
- * Checks if a provided path is a directory and returns a glob string matching
299
- * all files under that directory if so, the path itself otherwise.
313
+ * return the cacheFile to be used by eslint, based on whether the provided parameter is
314
+ * a directory or looks like a directory (ends in `path.sep`), in which case the file
315
+ * name will be the `cacheFile/.cache_hashOfCWD`
300
316
  *
301
- * Reason for this is that `glob` needs `/**` to collect all the files under a
302
- * directory where as our previous implementation without `glob` simply walked
303
- * a directory that is passed. So this is to maintain backwards compatibility.
317
+ * if cacheFile points to a file or looks like a file then in will just use that file
304
318
  *
305
- * Also makes sure all path separators are POSIX style for `glob` compatibility.
306
- *
307
- * @param {string[]} [extensions] An array of accepted extensions
308
- * @returns {Function} A function that takes a pathname and returns a glob that
309
- * matches all files with the provided extensions if
310
- * pathname is a directory.
319
+ * @param {string} cacheFile The name of file to be used to store the cache
320
+ * @returns {string} the resolved path to the cache file
311
321
  */
312
- function processPath(extensions) {
313
- var suffix = "/**";
322
+ function getCacheFile(cacheFile) {
323
+ // make sure the path separators are normalized for the environment/os
324
+ // keeping the trailing path separator if present
325
+ cacheFile = path.normalize(cacheFile);
314
326
 
315
- if (extensions) {
316
- if (extensions.length === 1) {
317
- suffix += "/*." + extensions[0];
318
- } else {
319
- suffix += "/*.{" + extensions.join(",") + "}";
320
- }
321
- }
327
+ var resolvedCacheFile = path.resolve(cacheFile);
328
+ var looksLikeADirectory = cacheFile[cacheFile.length - 1 ] === path.sep;
322
329
 
323
330
  /**
324
- * A function that converts a directory name to a glob pattern
325
- *
326
- * @param {string} pathname The directory path to be modified
327
- * @returns {string} The glob path or the file path itself
328
- * @private
331
+ * return the name for the cache file in case the provided parameter is a directory
332
+ * @returns {string} the resolved path to the cacheFile
329
333
  */
330
- return function(pathname) {
331
- var newPath = pathname;
334
+ function getCacheFileForDirectory() {
335
+ return path.join(resolvedCacheFile, ".cache_" + md5Hash(process.cwd()));
336
+ }
332
337
 
333
- if (shell.test("-d", pathname)) {
334
- newPath = pathname.replace(/[\/\\]$/, "") + suffix;
338
+ var fileStats;
339
+
340
+ try {
341
+ fileStats = fs.lstatSync(resolvedCacheFile);
342
+ } catch (ex) {
343
+ fileStats = null;
344
+ }
345
+
346
+
347
+ // in case the file exists we need to verify if the provided path
348
+ // is a directory or a file. If it is a directory we want to create a file
349
+ // inside that directory
350
+ if (fileStats) {
351
+ // is a directory or is a file, but the original file the user provided
352
+ // looks like a directory but `path.resolve` removed the `last path.sep`
353
+ // so we need to still treat this like a directory
354
+ if (fileStats.isDirectory() || looksLikeADirectory) {
355
+ return getCacheFileForDirectory();
335
356
  }
357
+ // is file so just use that file
358
+ return resolvedCacheFile;
359
+ }
336
360
 
337
- return newPath.replace(/\\/g, "/").replace(/^\.\//, "");
338
- };
339
- }
361
+ // here we known the file or directory doesn't exist,
362
+ // so we will try to infer if its a directory if it looks like a directory
363
+ // for the current operating system.
340
364
 
365
+ // if the last character passed is a path separator we assume is a directory
366
+ if (looksLikeADirectory) {
367
+ return getCacheFileForDirectory();
368
+ }
369
+
370
+ return resolvedCacheFile;
371
+ }
341
372
 
342
373
  //------------------------------------------------------------------------------
343
374
  // Public Interface
@@ -356,12 +387,15 @@ function CLIEngine(options) {
356
387
  */
357
388
  this.options = assign(Object.create(defaultOptions), options || {});
358
389
 
390
+
391
+ var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile);
392
+
359
393
  /**
360
394
  * cache used to not operate on files that haven't changed since last successful
361
395
  * execution (e.g. file passed with no errors and no warnings
362
396
  * @type {Object}
363
397
  */
364
- this._fileCache = fileEntryCache.create(path.resolve(this.options.cacheFile)); // eslint-disable-line no-underscore-dangle
398
+ this._fileCache = fileEntryCache.create(cacheFile); // eslint-disable-line no-underscore-dangle
365
399
 
366
400
  if (!this.options.cache) {
367
401
  this._fileCache.destroy(); // eslint-disable-line no-underscore-dangle
@@ -478,11 +512,7 @@ CLIEngine.prototype = {
478
512
  * @returns {string[]} The equivalent glob patterns.
479
513
  */
480
514
  resolveFileGlobPatterns: function(patterns) {
481
- var extensions = this.options.extensions.map(function(ext) {
482
- return ext.charAt(0) === "." ? ext.substr(1) : ext;
483
- });
484
-
485
- return patterns.map(processPath(extensions));
515
+ return globUtil.resolveFileGlobPatterns(patterns, this.options.extensions);
486
516
  },
487
517
 
488
518
  /**
@@ -496,32 +526,13 @@ CLIEngine.prototype = {
496
526
  options = this.options,
497
527
  fileCache = this._fileCache, // eslint-disable-line no-underscore-dangle
498
528
  configHelper = new Config(options),
499
- ignoredPaths = IgnoredPaths.load(options),
500
- ignoredPathsList = ignoredPaths.patterns || [],
501
- globOptions,
502
529
  stats,
503
530
  startTime,
504
531
  prevConfig; // the previous configuration used
505
532
 
506
533
  startTime = Date.now();
507
- patterns = this.resolveFileGlobPatterns(patterns);
508
534
 
509
- globOptions = {
510
- nodir: true,
511
- ignore: ignoredPathsList
512
- };
513
-
514
- /**
515
- * create a md5Hash of a given string
516
- * @param {string} str the string to calculate the hash for
517
- * @returns {string} the calculated hash
518
- */
519
- function md5Hash(str) {
520
- return crypto
521
- .createHash("md5")
522
- .update(str)
523
- .digest("hex");
524
- }
535
+ patterns = this.resolveFileGlobPatterns(patterns);
525
536
 
526
537
  /**
527
538
  * Calculates the hash of the config file used to validate a given file
@@ -552,22 +563,16 @@ CLIEngine.prototype = {
552
563
  /**
553
564
  * Executes the linter on a file defined by the `filename`. Skips
554
565
  * unsupported file extensions and any files that are already linted.
555
- * @param {string} filename The file to be linted
566
+ * @param {string} filename The resolved filename of the file to be linted
556
567
  * @returns {void}
557
568
  */
558
569
  function executeOnFile(filename) {
559
- var shouldIgnore = ignoredPaths.contains(filename);
560
- if (shouldIgnore) {
561
- return;
562
- }
570
+ var hashOfConfig;
563
571
 
564
- filename = fs.realpathSync(filename);
565
572
  if (processed[filename]) {
566
573
  return;
567
574
  }
568
575
 
569
- var hashOfConfig;
570
-
571
576
  if (options.cache) {
572
577
  // get the descriptor for this file
573
578
  // with the metadata and the flag that determines if
@@ -623,13 +628,8 @@ CLIEngine.prototype = {
623
628
  results.push(res);
624
629
  }
625
630
 
626
- patterns.forEach(function(pattern) {
627
- if (shell.test("-f", pattern) && !ignoredPaths.contains(pattern)) {
628
- executeOnFile(pattern);
629
- } else {
630
- glob.sync(pattern, globOptions).forEach(executeOnFile);
631
- }
632
- });
631
+ // Lint each desired file
632
+ globUtil.listFilesToProcess(patterns, options).forEach(executeOnFile);
633
633
 
634
634
  // only warn for files explicitly passed on the command line
635
635
  if (options.ignore) {
package/lib/cli.js CHANGED
@@ -22,7 +22,8 @@ var fs = require("fs"),
22
22
 
23
23
  options = require("./options"),
24
24
  CLIEngine = require("./cli-engine"),
25
- mkdirp = require("mkdirp");
25
+ mkdirp = require("mkdirp"),
26
+ log = require("./logging");
26
27
 
27
28
  //------------------------------------------------------------------------------
28
29
  // Helpers
@@ -52,6 +53,7 @@ function translateOptions(cliOptions) {
52
53
  parser: cliOptions.parser,
53
54
  cache: cliOptions.cache,
54
55
  cacheFile: cliOptions.cacheFile,
56
+ cacheLocation: cliOptions.cacheLocation,
55
57
  fix: cliOptions.fix
56
58
  };
57
59
  }
@@ -72,7 +74,7 @@ function printResults(engine, results, format, outputFile) {
72
74
 
73
75
  formatter = engine.getFormatter(format);
74
76
  if (!formatter) {
75
- console.error("Could not find formatter '%s'.", format);
77
+ log.error("Could not find formatter '%s'.", format);
76
78
  return false;
77
79
  }
78
80
 
@@ -83,7 +85,7 @@ function printResults(engine, results, format, outputFile) {
83
85
  filePath = path.resolve(process.cwd(), outputFile);
84
86
 
85
87
  if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
86
- console.error("Cannot write to output file path, it is a directory: %s", outputFile);
88
+ log.error("Cannot write to output file path, it is a directory: %s", outputFile);
87
89
  return false;
88
90
  }
89
91
 
@@ -91,11 +93,11 @@ function printResults(engine, results, format, outputFile) {
91
93
  mkdirp.sync(path.dirname(filePath));
92
94
  fs.writeFileSync(filePath, output);
93
95
  } catch (ex) {
94
- console.error("There was a problem writing the output file:\n%s", ex);
96
+ log.error("There was a problem writing the output file:\n%s", ex);
95
97
  return false;
96
98
  }
97
99
  } else {
98
- console.log(output);
100
+ log.info(output);
99
101
  }
100
102
  }
101
103
 
@@ -130,7 +132,7 @@ var cli = {
130
132
  try {
131
133
  currentOptions = options.parse(args);
132
134
  } catch (error) {
133
- console.error(error.message);
135
+ log.error(error.message);
134
136
  return 1;
135
137
  }
136
138
 
@@ -138,11 +140,11 @@ var cli = {
138
140
 
139
141
  if (currentOptions.version) { // version from package.json
140
142
 
141
- console.log("v" + require("../package.json").version);
143
+ log.info("v" + require("../package.json").version);
142
144
 
143
145
  } else if (currentOptions.help || (!files.length && !text)) {
144
146
 
145
- console.log(options.generateHelp());
147
+ log.info(options.generateHelp());
146
148
 
147
149
  } else {
148
150
 
@@ -150,7 +152,7 @@ var cli = {
150
152
 
151
153
  // disable --fix for piped-in code until we know how to do it correctly
152
154
  if (text && currentOptions.fix) {
153
- console.error("The --fix option is not available for piped-in code.");
155
+ log.error("The --fix option is not available for piped-in code.");
154
156
  return 1;
155
157
  }
156
158
 
@@ -171,7 +173,7 @@ var cli = {
171
173
  tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings;
172
174
 
173
175
  if (!report.errorCount && tooManyWarnings) {
174
- console.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings);
176
+ log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings);
175
177
  }
176
178
 
177
179
  return (report.errorCount || tooManyWarnings) ? 1 : 0;
package/lib/eslint.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // Requirements
11
11
  //------------------------------------------------------------------------------
12
12
 
13
- var estraverse = require("estraverse-fb"),
13
+ var estraverse = require("./util/estraverse"),
14
14
  escope = require("escope"),
15
15
  environments = require("../conf/environments"),
16
16
  blankScriptAST = require("../conf/blank-script.json"),
@@ -32,17 +32,6 @@ var DEFAULT_PARSER = require("../conf/eslint.json").parser;
32
32
  // Helpers
33
33
  //------------------------------------------------------------------------------
34
34
 
35
- // additional changes to make estraverse happy
36
- estraverse.Syntax.ExperimentalSpreadProperty = "ExperimentalSpreadProperty";
37
- estraverse.Syntax.ExperimentalRestProperty = "ExperimentalRestProperty";
38
-
39
- estraverse.VisitorKeys.ExperimentalSpreadProperty = ["argument"];
40
- estraverse.VisitorKeys.ExperimentalRestProperty = ["argument"];
41
-
42
- // All nodes in ObjectExpression.properties and ObjectPattern.properties are visited as `Property`.
43
- // See Also: https://github.com/estools/estraverse/blob/master/estraverse.js#L687-L688
44
- estraverse.VisitorKeys.Property.push("argument");
45
-
46
35
  /**
47
36
  * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
48
37
  * whitespace.
@@ -637,6 +626,20 @@ module.exports = (function() {
637
626
  this.reset();
638
627
  }
639
628
 
629
+ // search and apply "eslint-env *".
630
+ var envInFile = findEslintEnv(text || textOrSourceCode.text);
631
+ if (envInFile) {
632
+ if (!config || !config.env) {
633
+ config = assign({}, config || {}, {env: envInFile});
634
+ } else {
635
+ config = assign({}, config);
636
+ config.env = assign({}, config.env, envInFile);
637
+ }
638
+ }
639
+
640
+ // process initial config to make it safe to extend
641
+ config = prepareConfig(config || {});
642
+
640
643
  // only do this for text
641
644
  if (text !== null) {
642
645
 
@@ -646,20 +649,6 @@ module.exports = (function() {
646
649
  return messages;
647
650
  }
648
651
 
649
- // search and apply "eslint-env *".
650
- var envInFile = findEslintEnv(text);
651
- if (envInFile) {
652
- if (!config || !config.env) {
653
- config = assign({}, config || {}, {env: envInFile});
654
- } else {
655
- config = assign({}, config);
656
- config.env = assign({}, config.env, envInFile);
657
- }
658
- }
659
-
660
- // process initial config to make it safe to extend
661
- config = prepareConfig(config || {});
662
-
663
652
  ast = parse(text.replace(/^#!([^\r\n]+)/, function(match, captured) {
664
653
  shebang = captured;
665
654
  return "//" + captured;
package/lib/logging.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @fileoverview Handle logging for Eslint
3
+ * @author Gyandeep Singh
4
+ * @copyright 2015 Gyandeep Singh. All rights reserved.
5
+ */
6
+ "use strict";
7
+
8
+ /* istanbul ignore next */
9
+ module.exports = {
10
+ /**
11
+ * Cover for console.log
12
+ * @returns {void}
13
+ */
14
+ info: function() {
15
+ console.log.apply(console, Array.prototype.slice.call(arguments));
16
+ },
17
+
18
+ /**
19
+ * Cover for console.error
20
+ * @returns {void}
21
+ */
22
+ error: function() {
23
+ console.error.apply(console, Array.prototype.slice.call(arguments));
24
+ }
25
+ };
package/lib/options.js CHANGED
@@ -69,9 +69,14 @@ module.exports = optionator({
69
69
  },
70
70
  {
71
71
  option: "cache-file",
72
- type: "String",
72
+ type: "path::String",
73
73
  default: ".eslintcache",
74
- description: "Path to the cache file"
74
+ description: "Path to the cache file. Deprecated: use --cache-location"
75
+ },
76
+ {
77
+ option: "cache-location",
78
+ type: "path::String",
79
+ description: "Path to the cache file or directory"
75
80
  },
76
81
  {
77
82
  heading: "Specifying rules and plugins"
@@ -116,7 +116,7 @@ module.exports = function(context) {
116
116
  * @returns {boolean} Whether or not the node is an object type.
117
117
  */
118
118
  function isObjectType(node) {
119
- return node.type === "ObjectExpression" || node.type === "ObjectPattern";
119
+ return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
120
120
  }
121
121
 
122
122
  /**
@@ -125,7 +125,7 @@ module.exports = function(context) {
125
125
  * @returns {boolean} Whether or not the node is an array type.
126
126
  */
127
127
  function isArrayType(node) {
128
- return node.type === "ArrayExpression" || node.type === "ArrayPattern";
128
+ return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
129
129
  }
130
130
 
131
131
  /**
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Rule to require braces in arrow function body.
3
+ * @author Alberto Rodríguez
4
+ * @copyright 2015 Alberto Rodríguez. All rights reserved.
5
+ * See LICENSE file in root directory for full license.
6
+ */
7
+ "use strict";
8
+
9
+ //------------------------------------------------------------------------------
10
+ // Rule Definition
11
+ //------------------------------------------------------------------------------
12
+
13
+ module.exports = function(context) {
14
+ var always = context.options[0] === "always";
15
+ var asNeeded = !context.options[0] || context.options[0] === "as-needed";
16
+
17
+ /**
18
+ * Determines whether a arrow function body needs braces
19
+ * @param {ASTNode} node The arrow function node.
20
+ * @returns {void}
21
+ */
22
+ function validate(node) {
23
+ var arrowBody = node.body;
24
+ if (arrowBody.type === "BlockStatement") {
25
+ var blockBody = arrowBody.body;
26
+
27
+ if (blockBody.length > 1) {
28
+ return;
29
+ }
30
+
31
+ if (blockBody.length === 0) {
32
+ var hasComments = context.getComments(arrowBody).trailing.length > 0;
33
+ if (hasComments) {
34
+ return;
35
+ }
36
+
37
+ context.report({
38
+ node: node,
39
+ loc: arrowBody.loc.start,
40
+ message: "Unexpected empty block in arrow body."
41
+ });
42
+ } else {
43
+ if (asNeeded && blockBody[0].type === "ReturnStatement") {
44
+ context.report({
45
+ node: node,
46
+ loc: arrowBody.loc.start,
47
+ message: "Unexpected block statement surrounding arrow body."
48
+ });
49
+ }
50
+ }
51
+ } else {
52
+ if (always) {
53
+ context.report({
54
+ node: node,
55
+ loc: arrowBody.loc.start,
56
+ message: "Expected block statement surrounding arrow body."
57
+ });
58
+ }
59
+ }
60
+ }
61
+
62
+ return {
63
+ "ArrowFunctionExpression": validate
64
+ };
65
+ };
66
+
67
+ module.exports.schema = [
68
+ {
69
+ "enum": ["always", "as-needed"]
70
+ }
71
+ ];
@@ -16,12 +16,12 @@
16
16
  /**
17
17
  * Gets the last element of a given array.
18
18
  *
19
- * @param {any[]} xs - An array to get.
20
- * @returns {any} The last element, or undefined.
19
+ * @param {*[]} xs - An array to get.
20
+ * @returns {*} The last element, or undefined.
21
21
  */
22
22
  function getLast(xs) {
23
23
  if (xs.length === 0) {
24
- return void 0;
24
+ return null;
25
25
  }
26
26
  return xs[xs.length - 1];
27
27
  }
@@ -79,9 +79,9 @@ module.exports = function(context) {
79
79
  return false;
80
80
  }
81
81
 
82
- var sourceCode = context.getSourceCode();
83
- var penultimateToken = sourceCode.getLastToken(lastItem);
84
- var lastToken = sourceCode.getTokenAfter(penultimateToken);
82
+ var sourceCode = context.getSourceCode(),
83
+ penultimateToken = sourceCode.getLastToken(lastItem),
84
+ lastToken = sourceCode.getLastToken(node);
85
85
 
86
86
  if (lastToken.value === ",") {
87
87
  penultimateToken = lastToken;
@@ -105,8 +105,16 @@ module.exports = function(context) {
105
105
  return;
106
106
  }
107
107
 
108
- var sourceCode = context.getSourceCode();
109
- var trailingToken = sourceCode.getTokenAfter(lastItem);
108
+ var sourceCode = context.getSourceCode(),
109
+ trailingToken;
110
+
111
+ // last item can be surrounded by parentheses for object and array literals
112
+ if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
113
+ trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
114
+ } else {
115
+ trailingToken = sourceCode.getTokenAfter(lastItem);
116
+ }
117
+
110
118
  if (trailingToken.value === ",") {
111
119
  context.report(
112
120
  lastItem,
@@ -137,8 +145,16 @@ module.exports = function(context) {
137
145
  return;
138
146
  }
139
147
 
140
- var sourceCode = context.getSourceCode();
141
- var trailingToken = sourceCode.getTokenAfter(lastItem);
148
+ var sourceCode = context.getSourceCode(),
149
+ trailingToken;
150
+
151
+ // last item can be surrounded by parentheses for object and array literals
152
+ if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
153
+ trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
154
+ } else {
155
+ trailingToken = sourceCode.getTokenAfter(lastItem);
156
+ }
157
+
142
158
  if (trailingToken.value !== ",") {
143
159
  context.report(
144
160
  lastItem,