eslint 1.4.1 → 1.5.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 (38) hide show
  1. package/README.md +1 -1
  2. package/bin/eslint.js +32 -3
  3. package/lib/ast-utils.js +17 -11
  4. package/lib/cli-engine.js +72 -11
  5. package/lib/config.js +54 -55
  6. package/lib/eslint.js +7 -56
  7. package/lib/options.js +7 -0
  8. package/lib/rules/array-bracket-spacing.js +6 -5
  9. package/lib/rules/block-spacing.js +4 -3
  10. package/lib/rules/comma-dangle.js +25 -10
  11. package/lib/rules/comma-spacing.js +4 -29
  12. package/lib/rules/computed-property-spacing.js +5 -4
  13. package/lib/rules/eol-last.js +8 -1
  14. package/lib/rules/func-style.js +29 -9
  15. package/lib/rules/id-length.js +3 -3
  16. package/lib/rules/indent.js +100 -47
  17. package/lib/rules/jsx-quotes.js +1 -1
  18. package/lib/rules/key-spacing.js +7 -1
  19. package/lib/rules/no-dupe-args.js +30 -46
  20. package/lib/rules/no-extra-semi.js +7 -1
  21. package/lib/rules/no-inline-comments.js +3 -1
  22. package/lib/rules/no-spaced-func.js +18 -5
  23. package/lib/rules/no-trailing-spaces.js +34 -6
  24. package/lib/rules/no-unused-vars.js +4 -4
  25. package/lib/rules/no-warning-comments.js +7 -0
  26. package/lib/rules/object-curly-spacing.js +7 -9
  27. package/lib/rules/semi-spacing.js +29 -4
  28. package/lib/rules/space-after-keywords.js +27 -5
  29. package/lib/rules/space-before-blocks.js +64 -7
  30. package/lib/rules/space-before-function-paren.js +19 -13
  31. package/lib/rules/space-before-keywords.js +45 -17
  32. package/lib/rules/space-infix-ops.js +22 -1
  33. package/lib/rules/space-return-throw-case.js +7 -1
  34. package/lib/testers/event-generator-tester.js +63 -0
  35. package/lib/util/comment-event-generator.js +116 -0
  36. package/lib/util/node-event-generator.js +55 -0
  37. package/lib/util/source-code.js +14 -7
  38. package/package.json +2 -2
package/README.md CHANGED
@@ -104,7 +104,7 @@ The following projects are using ESLint to validate their JavaScript:
104
104
 
105
105
  * [Drupal](https://www.drupal.org/node/2274223)
106
106
  * [Esprima](https://github.com/ariya/esprima)
107
- * [io.js](https://github.com/iojs/io.js/commit/f9dd34d301ab385ae316769b85ef916f9b70b6f6)
107
+ * [Node.js](https://github.com/nodejs/node/commit/f9dd34d301ab385ae316769b85ef916f9b70b6f6)
108
108
  * [WebKit](https://bugs.webkit.org/show_bug.cgi?id=125048)
109
109
 
110
110
  In addition, the following companies are using ESLint internally to validate their JavaScript:
package/bin/eslint.js CHANGED
@@ -1,11 +1,40 @@
1
1
  #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Main CLI that is run via the eslint command.
5
+ * @author Nicholas C. Zakas
6
+ * @copyright 2013 Nicholas C. Zakas. All rights reserved.
7
+ * See LICENSE file in root directory for full license.
8
+ */
9
+
10
+ "use strict";
11
+
12
+ //------------------------------------------------------------------------------
13
+ // Helpers
14
+ //------------------------------------------------------------------------------
15
+
16
+ var exitCode = 0,
17
+ useStdIn = (process.argv.indexOf("--stdin") > -1),
18
+ init = (process.argv.indexOf("--init") > -1),
19
+ debug = (process.argv.indexOf("--debug") > -1);
20
+
21
+ // must do this initialization *before* other requires in order to work
22
+ if (debug) {
23
+ require("debug").enable("eslint:*");
24
+ }
25
+
26
+ //------------------------------------------------------------------------------
27
+ // Requirements
28
+ //------------------------------------------------------------------------------
29
+
30
+ // now we can safely include the other modules that use debug
2
31
  var concat = require("concat-stream"),
3
32
  configInit = require("../lib/config-initializer"),
4
33
  cli = require("../lib/cli");
5
34
 
6
- var exitCode = 0,
7
- useStdIn = (process.argv.indexOf("--stdin") > -1),
8
- init = (process.argv.indexOf("--init") > -1);
35
+ //------------------------------------------------------------------------------
36
+ // Execution
37
+ //------------------------------------------------------------------------------
9
38
 
10
39
  if (useStdIn) {
11
40
  process.stdin.pipe(concat({ encoding: "string" }, function(text) {
package/lib/ast-utils.js CHANGED
@@ -37,17 +37,6 @@ function isModifyingReference(reference, index, references) {
37
37
 
38
38
  module.exports = {
39
39
 
40
- /**
41
- * Determines whether two adjacent tokens are have whitespace between them.
42
- * @param {Object} left - The left token object.
43
- * @param {Object} right - The right token object.
44
- * @returns {boolean} Whether or not there is space between the tokens.
45
- * @public
46
- */
47
- isTokenSpaced: function(left, right) {
48
- return left.range[1] < right.range[0];
49
- },
50
-
51
40
  /**
52
41
  * Determines whether two adjacent tokens are on the same line.
53
42
  * @param {Object} left - The left token object.
@@ -104,5 +93,22 @@ module.exports = {
104
93
  */
105
94
  isSurroundedBy: function(val, character) {
106
95
  return val[0] === character && val[val.length - 1] === character;
96
+ },
97
+
98
+ /**
99
+ * Returns whether the provided node is an ESLint directive comment or not
100
+ * @param {LineComment|BlockComment} node The node to be checked
101
+ * @returns {boolean} `true` if the node is an ESLint directive comment
102
+ */
103
+ isDirectiveComment: function(node) {
104
+ var comment = node.value.trim();
105
+ return (
106
+ node.type === "Line" && comment.indexOf("eslint-") === 0 ||
107
+ node.type === "Block" && (
108
+ comment.indexOf("global ") === 0 ||
109
+ comment.indexOf("eslint ") === 0 ||
110
+ comment.indexOf("eslint-") === 0
111
+ )
112
+ );
107
113
  }
108
114
  };
package/lib/cli-engine.js CHANGED
@@ -32,7 +32,10 @@ var fs = require("fs"),
32
32
  util = require("./util"),
33
33
  fileEntryCache = require("file-entry-cache"),
34
34
  SourceCodeFixer = require("./util/source-code-fixer"),
35
- validator = require("./config-validator");
35
+ validator = require("./config-validator"),
36
+
37
+ crypto = require( "crypto" ),
38
+ pkg = require("../package.json");
36
39
 
37
40
  var DEFAULT_PARSER = require("../conf/eslint.json").parser;
38
41
 
@@ -321,12 +324,13 @@ function processPath(extensions) {
321
324
  *
322
325
  * @param {string} pathname The directory path to be modified
323
326
  * @returns {string} The glob path or the file path itself
327
+ * @private
324
328
  */
325
329
  return function(pathname) {
326
330
  var newPath = pathname;
327
331
 
328
332
  if (shell.test("-d", pathname)) {
329
- newPath = pathname + suffix;
333
+ newPath = pathname.replace(/[\/\\]$/, "") + suffix;
330
334
  }
331
335
 
332
336
  return newPath.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -466,6 +470,20 @@ CLIEngine.prototype = {
466
470
  loadedPlugins[pluginNameWithoutPrefix] = pluginobject;
467
471
  },
468
472
 
473
+ /**
474
+ * Resolves the patterns passed into executeOnFiles() into glob-based patterns
475
+ * for easier handling.
476
+ * @param {string[]} patterns The file patterns passed on the command line.
477
+ * @returns {string[]} The equivalent glob patterns.
478
+ */
479
+ resolveFileGlobPatterns: function(patterns) {
480
+ var extensions = this.options.extensions.map(function(ext) {
481
+ return ext.charAt(0) === "." ? ext.substr(1) : ext;
482
+ });
483
+
484
+ return patterns.map(processPath(extensions));
485
+ },
486
+
469
487
  /**
470
488
  * Executes the current configuration on an array of file and directory names.
471
489
  * @param {string[]} patterns An array of file and directory names.
@@ -479,23 +497,57 @@ CLIEngine.prototype = {
479
497
  configHelper = new Config(options),
480
498
  ignoredPaths = IgnoredPaths.load(options),
481
499
  ignoredPathsList = ignoredPaths.patterns || [],
482
- extensions = options.extensions.map(function(ext) {
483
- return ext.charAt(0) === "." ? ext.substr(1) : ext;
484
- }),
485
500
  globOptions,
486
501
  stats,
487
- startTime;
502
+ startTime,
503
+ prevConfig; // the previous configuration used
488
504
 
489
505
  startTime = Date.now();
490
-
491
- patterns = patterns.map(processPath(extensions));
492
- ignoredPathsList = ignoredPathsList.map(processPath());
506
+ patterns = this.resolveFileGlobPatterns(patterns);
493
507
 
494
508
  globOptions = {
495
509
  nodir: true,
496
510
  ignore: ignoredPathsList
497
511
  };
498
512
 
513
+ /**
514
+ * create a md5Hash of a given string
515
+ * @param {string} str the string to calculate the hash for
516
+ * @returns {string} the calculated hash
517
+ */
518
+ function md5Hash(str) {
519
+ return crypto
520
+ .createHash("md5")
521
+ .update(str)
522
+ .digest("hex");
523
+ }
524
+
525
+ /**
526
+ * Calculates the hash of the config file used to validate a given file
527
+ * @param {string} filename The path of the file to retrieve a config object for to calculate the hash
528
+ * @returns {string} the hash of the config
529
+ */
530
+ function hashOfConfigFor(filename) {
531
+ var config = configHelper.getConfig(filename);
532
+
533
+ if (!prevConfig) {
534
+ prevConfig = {};
535
+ }
536
+
537
+ // reuse the previously hashed config if the config hasn't changed
538
+ if (prevConfig.config !== config) {
539
+ // config changed so we need to calculate the hash of the config
540
+ // and the hash of the plugins being used
541
+ prevConfig.config = config;
542
+
543
+ var eslintVersion = pkg.version;
544
+
545
+ prevConfig.hash = md5Hash(eslintVersion + "_" + JSON.stringify(config));
546
+ }
547
+
548
+ return prevConfig.hash;
549
+ }
550
+
499
551
  /**
500
552
  * Executes the linter on a file defined by the `filename`. Skips
501
553
  * unsupported file extensions and any files that are already linted.
@@ -503,18 +555,26 @@ CLIEngine.prototype = {
503
555
  * @returns {void}
504
556
  */
505
557
  function executeOnFile(filename) {
558
+ var shouldIgnore = ignoredPaths.contains(filename);
506
559
  filename = fs.realpathSync(filename);
507
560
 
508
- if (processed[filename]) {
561
+ if (processed[filename] || shouldIgnore) {
509
562
  return;
510
563
  }
511
564
 
565
+ var hashOfConfig;
566
+
512
567
  if (options.cache) {
513
568
  // get the descriptor for this file
514
569
  // with the metadata and the flag that determines if
515
570
  // the file has changed
516
571
  var descriptor = fileCache.getFileDescriptor(filename);
517
- if (!descriptor.changed) {
572
+ var meta = descriptor.meta || {};
573
+
574
+ hashOfConfig = hashOfConfigFor(filename);
575
+
576
+ var changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
577
+ if (!changed) {
518
578
  debug("Skipping file since hasn't changed: " + filename);
519
579
 
520
580
  // Adding the filename to the processed hashmap
@@ -551,6 +611,7 @@ CLIEngine.prototype = {
551
611
  // since the file passed we store the result here
552
612
  // TODO: check this as we might not need to store the
553
613
  // successful runs as it will always should be 0 error 0 warnings
614
+ descriptor.meta.hashOfConfig = hashOfConfig;
554
615
  descriptor.meta.results = res;
555
616
  }
556
617
  }
package/lib/config.js CHANGED
@@ -69,6 +69,44 @@ function isObject(item) {
69
69
  return typeof item === "object" && !Array.isArray(item) && item !== null;
70
70
  }
71
71
 
72
+ /**
73
+ * Creates an environment config based on the specified environments.
74
+ * @param {Object<string,boolean>} envs The environment settings.
75
+ * @returns {Object} A configuration object with the appropriate rules and globals
76
+ * set.
77
+ * @private
78
+ */
79
+ function createEnvironmentConfig(envs) {
80
+
81
+ var envConfig = {
82
+ globals: {},
83
+ env: envs || {},
84
+ rules: {},
85
+ ecmaFeatures: {}
86
+ };
87
+
88
+ if (envs) {
89
+ Object.keys(envs).filter(function(name) {
90
+ return envs[name];
91
+ }).forEach(function(name) {
92
+ var environment = environments[name];
93
+
94
+ if (environment) {
95
+
96
+ if (environment.globals) {
97
+ assign(envConfig.globals, environment.globals);
98
+ }
99
+
100
+ if (environment.ecmaFeatures) {
101
+ assign(envConfig.ecmaFeatures, environment.ecmaFeatures);
102
+ }
103
+ }
104
+ });
105
+ }
106
+
107
+ return envConfig;
108
+ }
109
+
72
110
  /**
73
111
  * Read the config from the config JSON file
74
112
  * @param {string} filePath the path to the JSON config file
@@ -175,6 +213,10 @@ function loadConfig(configToLoad) {
175
213
 
176
214
  }
177
215
 
216
+ if (config.env) {
217
+ // Merge in environment-specific globals and ecmaFeatures.
218
+ config = util.mergeConfigs(createEnvironmentConfig(config.env), config);
219
+ }
178
220
 
179
221
  }
180
222
 
@@ -253,14 +295,16 @@ function getLocalConfig(thisConfig, directory) {
253
295
  config = {},
254
296
  localConfigFiles = thisConfig.findLocalConfigFiles(directory),
255
297
  numFiles = localConfigFiles.length,
256
- rootPath;
298
+ rootPath,
299
+ projectConfigPath = path.join(process.cwd(), LOCAL_CONFIG_FILENAME);
257
300
 
258
301
  for (i = 0; i < numFiles; i++) {
259
302
 
260
303
  localConfigFile = localConfigFiles[i];
261
304
 
262
- // Don't consider the personal config file in the home directory.
263
- if (localConfigFile === PERSONAL_CONFIG_PATH) {
305
+ // Don't consider the personal config file in the home directory,
306
+ // except if the home directory is the same as the current working directory
307
+ if (localConfigFile === PERSONAL_CONFIG_PATH && localConfigFile !== projectConfigPath) {
264
308
  continue;
265
309
  }
266
310
 
@@ -291,44 +335,6 @@ function getLocalConfig(thisConfig, directory) {
291
335
  return found ? config : util.mergeConfigs(config, getPersonalConfig());
292
336
  }
293
337
 
294
- /**
295
- * Creates an environment config based on the specified environments.
296
- * @param {Object<string,boolean>} envs The environment settings.
297
- * @returns {Object} A configuration object with the appropriate rules and globals
298
- * set.
299
- * @private
300
- */
301
- function createEnvironmentConfig(envs) {
302
-
303
- var envConfig = {
304
- globals: {},
305
- env: envs || {},
306
- rules: {},
307
- ecmaFeatures: {}
308
- };
309
-
310
- if (envs) {
311
- Object.keys(envs).filter(function(name) {
312
- return envs[name];
313
- }).forEach(function(name) {
314
- var environment = environments[name];
315
-
316
- if (environment) {
317
-
318
- if (environment.globals) {
319
- assign(envConfig.globals, environment.globals);
320
- }
321
-
322
- if (environment.ecmaFeatures) {
323
- assign(envConfig.ecmaFeatures, environment.ecmaFeatures);
324
- }
325
- }
326
- });
327
- }
328
-
329
- return envConfig;
330
- }
331
-
332
338
  //------------------------------------------------------------------------------
333
339
  // API
334
340
  //------------------------------------------------------------------------------
@@ -412,44 +418,37 @@ Config.prototype.getConfig = function(filePath) {
412
418
  // Step 2: Create a copy of the baseConfig
413
419
  config = util.mergeConfigs({parser: this.parser}, this.baseConfig);
414
420
 
415
- // Step 3: Merge in environment-specific globals and rules from .eslintrc files
416
- config = util.mergeConfigs(config, createEnvironmentConfig(userConfig.env));
417
-
418
- // Step 4: Merge in the user-specified configuration from .eslintrc and package.json
421
+ // Step 3: Merge in the user-specified configuration from .eslintrc and package.json
419
422
  config = util.mergeConfigs(config, userConfig);
420
423
 
421
- // Step 5: Merge in command line config file
424
+ // Step 4: Merge in command line config file
422
425
  if (this.useSpecificConfig) {
423
426
  debug("Merging command line config file");
424
427
 
425
- if (this.useSpecificConfig.env) {
426
- config = util.mergeConfigs(config, createEnvironmentConfig(this.useSpecificConfig.env));
427
- }
428
-
429
428
  config = util.mergeConfigs(config, this.useSpecificConfig);
430
429
  }
431
430
 
432
- // Step 6: Merge in command line environments
431
+ // Step 5: Merge in command line environments
433
432
  debug("Merging command line environment settings");
434
433
  config = util.mergeConfigs(config, createEnvironmentConfig(this.env));
435
434
 
436
- // Step 7: Merge in command line rules
435
+ // Step 6: Merge in command line rules
437
436
  if (this.options.rules) {
438
437
  debug("Merging command line rules");
439
438
  config = util.mergeConfigs(config, { rules: this.options.rules });
440
439
  }
441
440
 
442
- // Step 8: Merge in command line globals
441
+ // Step 7: Merge in command line globals
443
442
  config = util.mergeConfigs(config, { globals: this.globals });
444
443
 
445
- // Step 9: Merge in command line plugins
444
+ // Step 8: Merge in command line plugins
446
445
  if (this.options.plugins) {
447
446
  debug("Merging command line plugins");
448
447
  pluginConfig = getPluginsConfig(this.options.plugins);
449
448
  config = util.mergeConfigs(config, { plugins: this.options.plugins });
450
449
  }
451
450
 
452
- // Step 10: Merge in plugin specific rules in reverse
451
+ // Step 9: Merge in plugin specific rules in reverse
453
452
  if (config.plugins) {
454
453
  pluginConfig = getPluginsConfig(config.plugins);
455
454
  config = util.mergeConfigs(pluginConfig, config);
package/lib/eslint.js CHANGED
@@ -20,6 +20,8 @@ var estraverse = require("estraverse-fb"),
20
20
  RuleContext = require("./rule-context"),
21
21
  timing = require("./timing"),
22
22
  SourceCode = require("./util/source-code"),
23
+ NodeEventGenerator = require("./util/node-event-generator"),
24
+ CommentEventGenerator = require("./util/comment-event-generator"),
23
25
  EventEmitter = require("events").EventEmitter,
24
26
  validator = require("./config-validator"),
25
27
  replacements = require("../conf/replacements.json");
@@ -498,8 +500,6 @@ module.exports = (function() {
498
500
  currentFilename = null,
499
501
  controller = null,
500
502
  reportingConfig = [],
501
- commentLocsEnter = [],
502
- commentLocsExit = [],
503
503
  sourceCode = null;
504
504
 
505
505
  /**
@@ -564,46 +564,6 @@ module.exports = (function() {
564
564
  }
565
565
  }
566
566
 
567
- /**
568
- * Check collection of comments to prevent double event for comment as
569
- * leading and trailing, then emit event if passing
570
- * @param {ASTNode[]} comments Collection of comment nodes
571
- * @param {Object[]} locs List of locations of previous comment nodes
572
- * @param {string} eventName Event name postfix
573
- * @returns {void}
574
- */
575
- function emitComments(comments, locs, eventName) {
576
-
577
- if (comments.length) {
578
- comments.forEach(function(node) {
579
- if (locs.indexOf(node.loc) >= 0) {
580
- locs.splice(locs.indexOf(node.loc), 1);
581
- } else {
582
- locs.push(node.loc);
583
- api.emit(node.type + eventName, node);
584
- }
585
- });
586
- }
587
- }
588
-
589
- /**
590
- * Shortcut to check and emit enter of comment nodes
591
- * @param {ASTNode[]} comments Collection of comment nodes
592
- * @returns {void}
593
- */
594
- function emitCommentsEnter(comments) {
595
- emitComments(comments, commentLocsEnter, "Comment");
596
- }
597
-
598
- /**
599
- * Shortcut to check and emit exit of comment nodes
600
- * @param {ASTNode[]} comments Collection of comment nodes
601
- * @returns {void}
602
- */
603
- function emitCommentsExit(comments) {
604
- emitComments(comments, commentLocsExit, "Comment:exit");
605
- }
606
-
607
567
  /**
608
568
  * Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
609
569
  * Returns 0 if the rule config is not valid (an Array or a number)
@@ -649,8 +609,6 @@ module.exports = (function() {
649
609
  scopeManager = null;
650
610
  controller = null;
651
611
  reportingConfig = [];
652
- commentLocsEnter = [];
653
- commentLocsExit = [];
654
612
  sourceCode = null;
655
613
  };
656
614
 
@@ -811,6 +769,9 @@ module.exports = (function() {
811
769
  }
812
770
  }
813
771
 
772
+ var eventGenerator = new NodeEventGenerator(api);
773
+ eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
774
+
814
775
  /*
815
776
  * Each node has a type property. Whenever a particular type of node is found,
816
777
  * an event is fired. This allows any listeners to automatically be informed
@@ -818,21 +779,11 @@ module.exports = (function() {
818
779
  */
819
780
  controller.traverse(ast, {
820
781
  enter: function(node, parent) {
821
-
822
- var comments = api.getComments(node);
823
-
824
- emitCommentsEnter(comments.leading);
825
782
  node.parent = parent;
826
- api.emit(node.type, node);
827
- emitCommentsEnter(comments.trailing);
783
+ eventGenerator.enterNode(node);
828
784
  },
829
785
  leave: function(node) {
830
-
831
- var comments = api.getComments(node);
832
-
833
- emitCommentsExit(comments.trailing);
834
- api.emit(node.type + ":exit", node);
835
- emitCommentsExit(comments.leading);
786
+ eventGenerator.leaveNode(node);
836
787
  }
837
788
  });
838
789
 
package/lib/options.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Options configuration for optionator.
3
3
  * @author George Zahariev
4
+ * See LICENSE in root directory for full license.
4
5
  */
5
6
  "use strict";
6
7
 
@@ -175,6 +176,12 @@ module.exports = optionator({
175
176
  default: false,
176
177
  description: "Automatically fix problems"
177
178
  },
179
+ {
180
+ option: "debug",
181
+ type: "Boolean",
182
+ default: false,
183
+ description: "Output debugging information"
184
+ },
178
185
  {
179
186
  option: "help",
180
187
  alias: "h",
@@ -15,7 +15,8 @@ var astUtils = require("../ast-utils");
15
15
  //------------------------------------------------------------------------------
16
16
 
17
17
  module.exports = function(context) {
18
- var spaced = context.options[0] === "always";
18
+ var spaced = context.options[0] === "always",
19
+ sourceCode = context.getSourceCode();
19
20
 
20
21
  /**
21
22
  * Determines whether an option is set, relative to the spacing option.
@@ -111,19 +112,19 @@ module.exports = function(context) {
111
112
  ? !options.spaced : options.spaced;
112
113
 
113
114
  if (astUtils.isTokenOnSameLine(first, second)) {
114
- if (openingBracketMustBeSpaced && !astUtils.isTokenSpaced(first, second)) {
115
+ if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
115
116
  reportRequiredBeginningSpace(node, first);
116
117
  }
117
- if (!openingBracketMustBeSpaced && astUtils.isTokenSpaced(first, second)) {
118
+ if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
118
119
  reportNoBeginningSpace(node, first);
119
120
  }
120
121
  }
121
122
 
122
123
  if (astUtils.isTokenOnSameLine(penultimate, last)) {
123
- if (closingBracketMustBeSpaced && !astUtils.isTokenSpaced(penultimate, last)) {
124
+ if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
124
125
  reportRequiredEndingSpace(node, last);
125
126
  }
126
- if (!closingBracketMustBeSpaced && astUtils.isTokenSpaced(penultimate, last)) {
127
+ if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
127
128
  reportNoEndingSpace(node, last);
128
129
  }
129
130
  }
@@ -13,8 +13,9 @@ var util = require("../ast-utils");
13
13
  //------------------------------------------------------------------------------
14
14
 
15
15
  module.exports = function(context) {
16
- var always = (context.options[0] !== "never");
17
- var message = always ? "Requires a space" : "Unexpected space(s)";
16
+ var always = (context.options[0] !== "never"),
17
+ message = always ? "Requires a space" : "Unexpected space(s)",
18
+ sourceCode = context.getSourceCode();
18
19
 
19
20
  /**
20
21
  * Gets the open brace token from a given node.
@@ -45,7 +46,7 @@ module.exports = function(context) {
45
46
  function isValid(left, right) {
46
47
  return (
47
48
  !util.isTokenOnSameLine(left, right) ||
48
- util.isTokenSpaced(left, right) === always
49
+ sourceCode.isSpaceBetweenTokens(left, right) === always
49
50
  );
50
51
  }
51
52
 
@@ -74,9 +74,19 @@ module.exports = function(context) {
74
74
  * @returns {boolean} `true` if the node is multiline.
75
75
  */
76
76
  function isMultiline(node) {
77
+ var lastItem = getLast(node.properties || node.elements || node.specifiers);
78
+ if (!lastItem) {
79
+ return false;
80
+ }
81
+
77
82
  var sourceCode = context.getSourceCode();
78
- var lastToken = sourceCode.getLastToken(node);
79
- var penultimateToken = sourceCode.getLastToken(node, 1);
83
+ var penultimateToken = sourceCode.getLastToken(lastItem);
84
+ var lastToken = sourceCode.getTokenAfter(penultimateToken);
85
+
86
+ if (lastToken.value === ",") {
87
+ penultimateToken = lastToken;
88
+ lastToken = sourceCode.getTokenAfter(lastToken);
89
+ }
80
90
 
81
91
  return lastToken.loc.end.line !== penultimateToken.loc.end.line;
82
92
  }
@@ -85,12 +95,13 @@ module.exports = function(context) {
85
95
  * Reports a trailing comma if it exists.
86
96
  *
87
97
  * @param {ASTNode} node - A node to check. Its type is one of
88
- * ObjectExpression, ObjectPattern, ArrayExpression, and ArrayPattern.
98
+ * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
99
+ * ImportDeclaration, and ExportNamedDeclaration.
89
100
  * @returns {void}
90
101
  */
91
102
  function forbidTrailingComma(node) {
92
- var lastItem = getLast(node.properties || node.elements);
93
- if (!lastItem) {
103
+ var lastItem = getLast(node.properties || node.elements || node.specifiers);
104
+ if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
94
105
  return;
95
106
  }
96
107
 
@@ -112,12 +123,13 @@ module.exports = function(context) {
112
123
  * comma is disallowed, so report if it exists.
113
124
  *
114
125
  * @param {ASTNode} node - A node to check. Its type is one of
115
- * ObjectExpression, ObjectPattern, ArrayExpression, and ArrayPattern.
126
+ * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
127
+ * ImportDeclaration, and ExportNamedDeclaration.
116
128
  * @returns {void}
117
129
  */
118
130
  function forceTrailingComma(node) {
119
- var lastItem = getLast(node.properties || node.elements);
120
- if (!lastItem) {
131
+ var lastItem = getLast(node.properties || node.elements || node.specifiers);
132
+ if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
121
133
  return;
122
134
  }
123
135
  if (!isTrailingCommaAllowed(node, lastItem)) {
@@ -141,7 +153,8 @@ module.exports = function(context) {
141
153
  * Otherwise, reports a trailing comma if it exists.
142
154
  *
143
155
  * @param {ASTNode} node - A node to check. Its type is one of
144
- * ObjectExpression, ObjectPattern, ArrayExpression, and ArrayPattern.
156
+ * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
157
+ * ImportDeclaration, and ExportNamedDeclaration.
145
158
  * @returns {void}
146
159
  */
147
160
  function forceTrailingCommaIfMultiline(node) {
@@ -166,7 +179,9 @@ module.exports = function(context) {
166
179
  "ObjectExpression": checkForTrailingComma,
167
180
  "ObjectPattern": checkForTrailingComma,
168
181
  "ArrayExpression": checkForTrailingComma,
169
- "ArrayPattern": checkForTrailingComma
182
+ "ArrayPattern": checkForTrailingComma,
183
+ "ImportDeclaration": checkForTrailingComma,
184
+ "ExportNamedDeclaration": checkForTrailingComma
170
185
  };
171
186
  };
172
187