Browse Source

tools: update eslint to v1.10.3

PR-URL: https://github.com/nodejs/io.js/pull/2286
Reviewed-By: Roman Reiss <me@silverwind.io>
process-exit-stdio-flushing
Michaël Zasso 9 years ago
committed by Roman Reiss
parent
commit
2d441493a4
  1. 57
      tools/eslint/README.md
  2. 38
      tools/eslint/bin/eslint.js
  3. 21
      tools/eslint/conf/blank-script.json
  4. 51
      tools/eslint/conf/environments.js
  5. 164
      tools/eslint/conf/eslint.json
  6. 150
      tools/eslint/conf/json-schema-schema.json
  7. 17
      tools/eslint/conf/replacements.json
  8. 4
      tools/eslint/lib/api.js
  9. 154
      tools/eslint/lib/ast-utils.js
  10. 435
      tools/eslint/lib/cli-engine.js
  11. 91
      tools/eslint/lib/cli.js
  12. 145
      tools/eslint/lib/config-initializer.js
  13. 110
      tools/eslint/lib/config-validator.js
  14. 266
      tools/eslint/lib/config.js
  15. 440
      tools/eslint/lib/config/config-file.js
  16. 241
      tools/eslint/lib/config/config-initializer.js
  17. 186
      tools/eslint/lib/config/config-ops.js
  18. 163
      tools/eslint/lib/config/config-validator.js
  19. 633
      tools/eslint/lib/eslint.js
  20. 53
      tools/eslint/lib/file-finder.js
  21. 15
      tools/eslint/lib/formatters/checkstyle.js
  22. 6
      tools/eslint/lib/formatters/compact.js
  23. 130
      tools/eslint/lib/formatters/html-template.html
  24. 88
      tools/eslint/lib/formatters/html.js
  25. 14
      tools/eslint/lib/formatters/json.js
  26. 6
      tools/eslint/lib/formatters/junit.js
  27. 59
      tools/eslint/lib/formatters/unix.js
  28. 12
      tools/eslint/lib/ignored-paths.js
  29. 2
      tools/eslint/lib/load-rules.js
  30. 25
      tools/eslint/lib/logging.js
  31. 297
      tools/eslint/lib/options.js
  32. 108
      tools/eslint/lib/rule-context.js
  33. 30
      tools/eslint/lib/rules.js
  34. 88
      tools/eslint/lib/rules/accessor-pairs.js
  35. 113
      tools/eslint/lib/rules/array-bracket-spacing.js
  36. 71
      tools/eslint/lib/rules/arrow-body-style.js
  37. 52
      tools/eslint/lib/rules/arrow-parens.js
  38. 124
      tools/eslint/lib/rules/arrow-spacing.js
  39. 356
      tools/eslint/lib/rules/block-scoped-var.js
  40. 119
      tools/eslint/lib/rules/block-spacing.js
  41. 73
      tools/eslint/lib/rules/brace-style.js
  42. 142
      tools/eslint/lib/rules/callback-return.js
  43. 209
      tools/eslint/lib/rules/comma-dangle.js
  44. 182
      tools/eslint/lib/rules/comma-spacing.js
  45. 25
      tools/eslint/lib/rules/comma-style.js
  46. 31
      tools/eslint/lib/rules/complexity.js
  47. 85
      tools/eslint/lib/rules/computed-property-spacing.js
  48. 58
      tools/eslint/lib/rules/consistent-this.js
  49. 8
      tools/eslint/lib/rules/constructor-super.js
  50. 267
      tools/eslint/lib/rules/curly.js
  51. 25
      tools/eslint/lib/rules/dot-location.js
  52. 62
      tools/eslint/lib/rules/dot-notation.js
  53. 21
      tools/eslint/lib/rules/eol-last.js
  54. 36
      tools/eslint/lib/rules/eqeqeq.js
  55. 59
      tools/eslint/lib/rules/func-style.js
  56. 26
      tools/eslint/lib/rules/generator-star-spacing.js
  57. 76
      tools/eslint/lib/rules/generator-star.js
  58. 35
      tools/eslint/lib/rules/global-require.js
  59. 49
      tools/eslint/lib/rules/global-strict.js
  60. 2
      tools/eslint/lib/rules/handle-callback-err.js
  61. 106
      tools/eslint/lib/rules/id-length.js
  62. 129
      tools/eslint/lib/rules/id-match.js
  63. 868
      tools/eslint/lib/rules/indent.js
  64. 120
      tools/eslint/lib/rules/init-declarations.js
  65. 63
      tools/eslint/lib/rules/jsx-quotes.js
  66. 50
      tools/eslint/lib/rules/key-spacing.js
  67. 57
      tools/eslint/lib/rules/linebreak-style.js
  68. 156
      tools/eslint/lib/rules/lines-around-comment.js
  69. 21
      tools/eslint/lib/rules/max-depth.js
  70. 103
      tools/eslint/lib/rules/max-len.js
  71. 2
      tools/eslint/lib/rules/max-nested-callbacks.js
  72. 17
      tools/eslint/lib/rules/max-statements.js
  73. 23
      tools/eslint/lib/rules/new-cap.js
  74. 61
      tools/eslint/lib/rules/newline-after-var.js
  75. 9
      tools/eslint/lib/rules/no-alert.js
  76. 6
      tools/eslint/lib/rules/no-array-constructor.js
  77. 88
      tools/eslint/lib/rules/no-arrow-condition.js
  78. 47
      tools/eslint/lib/rules/no-case-declarations.js
  79. 32
      tools/eslint/lib/rules/no-catch-shadow.js
  80. 48
      tools/eslint/lib/rules/no-class-assign.js
  81. 45
      tools/eslint/lib/rules/no-comma-dangle.js
  82. 18
      tools/eslint/lib/rules/no-cond-assign.js
  83. 41
      tools/eslint/lib/rules/no-const-assign.js
  84. 6
      tools/eslint/lib/rules/no-control-regex.js
  85. 76
      tools/eslint/lib/rules/no-dupe-args.js
  86. 82
      tools/eslint/lib/rules/no-dupe-class-members.js
  87. 7
      tools/eslint/lib/rules/no-dupe-keys.js
  88. 56
      tools/eslint/lib/rules/no-duplicate-case.js
  89. 61
      tools/eslint/lib/rules/no-else-return.js
  90. 2
      tools/eslint/lib/rules/no-empty-character-class.js
  91. 45
      tools/eslint/lib/rules/no-empty-class.js
  92. 2
      tools/eslint/lib/rules/no-empty-label.js
  93. 28
      tools/eslint/lib/rules/no-empty-pattern.js
  94. 35
      tools/eslint/lib/rules/no-ex-assign.js
  95. 21
      tools/eslint/lib/rules/no-extend-native.js
  96. 7
      tools/eslint/lib/rules/no-extra-bind.js
  97. 2
      tools/eslint/lib/rules/no-extra-boolean-cast.js
  98. 117
      tools/eslint/lib/rules/no-extra-parens.js
  99. 19
      tools/eslint/lib/rules/no-extra-semi.js
  100. 86
      tools/eslint/lib/rules/no-extra-strict.js

57
tools/eslint/README.md

@ -31,6 +31,31 @@ After that, you can run ESLint on any JavaScript file:
eslint test.js test2.js
## Configuration
After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
```json
{
"rules": {
"semi": [2, "always"],
"quotes": [2, "double"]
}
}
```
The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The number is the error level of the rule and can be one of the three values:
* `0` - turn the rule off
* `1` - turn the rule on as a warning (doesn't affect exit code)
* `2` - turn the rule on as an error (exit code will be 1)
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)).
## Sponsors
* Development is sponsored by [Box](https://box.com)
## Team
These folks keep the project moving and are resources for help:
@ -38,10 +63,17 @@ These folks keep the project moving and are resources for help:
* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas)) - project lead
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - committer
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
* Ian VanSchooten ([@ianvs](https://github.com/ianvs)) - committer
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - committer
* Burak Yiğit Kaya ([@byk](https://github.com/byk)) - committer
* Alberto Rodríguez ([@alberto](https://github.com/alberto)) - committer
## Releases
We have scheduled releases every two weeks on Friday or Saturday.
## Frequently Asked Questions
@ -67,23 +99,6 @@ If you are using both JSHint and JSCS on your files, then using just ESLint will
ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both.
### Who is using ESLint?
The following projects are using ESLint to validate their JavaScript:
* [Drupal](https://www.drupal.org/node/2274223)
* [Esprima](https://github.com/ariya/esprima)
* [io.js](https://github.com/iojs/io.js/commit/f9dd34d301ab385ae316769b85ef916f9b70b6f6)
* [WebKit](https://bugs.webkit.org/show_bug.cgi?id=125048)
In addition, the following companies are using ESLint internally to validate their JavaScript:
* [Box](https://box.com)
* [CustomInk](https://customink.com)
* [Fitbit](http://www.fitbit.com)
* [HolidayCheck](http://holidaycheck.de)
* [the native web](http://www.thenativeweb.io)
### What about ECMAScript 6 support?
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring).
@ -102,10 +117,10 @@ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](ht
[npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square
[npm-url]: https://npmjs.org/package/eslint
[npm-url]: https://www.npmjs.com/package/eslint
[travis-image]: https://img.shields.io/travis/eslint/eslint/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/eslint/eslint
[coveralls-image]: https://img.shields.io/coveralls/eslint/eslint/master.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/eslint/eslint?branch=master
[downloads-image]: http://img.shields.io/npm/dm/eslint.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/eslint
[downloads-image]: https://img.shields.io/npm/dm/eslint.svg?style=flat-square
[downloads-url]: https://www.npmjs.com/package/eslint

38
tools/eslint/bin/eslint.js

@ -1,11 +1,39 @@
#!/usr/bin/env node
var concat = require("concat-stream"),
configInit = require("../lib/config-initializer"),
cli = require("../lib/cli");
/**
* @fileoverview Main CLI that is run via the eslint command.
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
var exitCode = 0,
useStdIn = (process.argv.indexOf("--stdin") > -1),
init = (process.argv.indexOf("--init") > -1);
init = (process.argv.indexOf("--init") > -1),
debug = (process.argv.indexOf("--debug") > -1);
// must do this initialization *before* other requires in order to work
if (debug) {
require("debug").enable("eslint:*");
}
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
// now we can safely include the other modules that use debug
var concat = require("concat-stream"),
cli = require("../lib/cli");
//------------------------------------------------------------------------------
// Execution
//------------------------------------------------------------------------------
if (useStdIn) {
process.stdin.pipe(concat({ encoding: "string" }, function(text) {
@ -18,13 +46,13 @@ if (useStdIn) {
}
}));
} else if (init) {
var configInit = require("../lib/config/config-initializer");
configInit.initializeConfig(function(err) {
if (err) {
exitCode = 1;
console.error(err.message);
console.error(err.stack);
} else {
console.log("Successfully created .eslintrc file in " + process.cwd());
exitCode = 0;
}
});

21
tools/eslint/conf/blank-script.json

@ -0,0 +1,21 @@
{
"type": "Program",
"body": [],
"sourceType": "script",
"range": [
0,
0
],
"loc": {
"start": {
"line": 0,
"column": 0
},
"end": {
"line": 0,
"column": 0
}
},
"comments": [],
"tokens": []
}

51
tools/eslint/conf/environments.js

@ -5,8 +5,16 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var globals = require("globals");
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
builtin: globals.builtin,
browser: {
@ -16,16 +24,12 @@ module.exports = {
globals: globals.node,
ecmaFeatures: {
globalReturn: true
},
rules: {
"no-catch-shadow": 0,
"no-console": 0,
"no-mixed-requires": 2,
"no-new-require": 2,
"no-path-concat": 2,
"no-process-exit": 2,
"global-strict": [0, "always"],
"handle-callback-err": [2, "err"]
}
},
commonjs: {
globals: globals.commonjs,
ecmaFeatures: {
globalReturn: true
}
},
worker: {
@ -40,12 +44,18 @@ module.exports = {
jasmine: {
globals: globals.jasmine
},
jest: {
globals: globals.jest
},
phantomjs: {
globals: globals.phantom
globals: globals.phantomjs
},
jquery: {
globals: globals.jquery
},
qunit: {
globals: globals.qunit
},
prototypejs: {
globals: globals.prototypejs
},
@ -58,9 +68,24 @@ module.exports = {
mongo: {
globals: globals.mongo
},
protractor: {
globals: globals.protractor
},
applescript: {
globals: globals.applescript
},
nashorn: {
globals: globals.nashorn
},
serviceworker: {
globals: globals.serviceworker
},
embertest: {
globals: globals.embertest
},
webextensions: {
globals: globals.webextensions
},
es6: {
ecmaFeatures: {
arrowFunctions: true,
@ -81,7 +106,9 @@ module.exports = {
objectLiteralDuplicateProperties: true,
generators: true,
destructuring: true,
classes: true
classes: true,
spread: true,
newTarget: true
}
}
};

164
tools/eslint/conf/eslint.json

@ -1,186 +1,196 @@
{
"ecmaFeatures": {},
"parser": "espree",
"env": {
"browser": false,
"node": false,
"amd": false,
"mocha": false,
"jasmine": false
},
"ecmaFeatures": {},
"rules": {
"no-alert": 2,
"no-array-constructor": 2,
"no-alert": 0,
"no-array-constructor": 0,
"no-arrow-condition": 0,
"no-bitwise": 0,
"no-caller": 2,
"no-catch-shadow": 2,
"no-comma-dangle": 0,
"no-caller": 0,
"no-case-declarations": 0,
"no-catch-shadow": 0,
"no-class-assign": 0,
"no-cond-assign": 2,
"no-console": 2,
"no-const-assign": 0,
"no-constant-condition": 2,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-class-members": 0,
"no-dupe-keys": 2,
"no-dupe-args": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 2,
"no-empty-class": 0,
"no-empty-character-class": 2,
"no-empty-label": 2,
"no-empty-label": 0,
"no-empty-pattern": 0,
"no-eq-null": 0,
"no-eval": 2,
"no-eval": 0,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extend-native": 0,
"no-extra-bind": 0,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-extra-strict": 2,
"no-fallthrough": 2,
"no-floating-decimal": 0,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-implicit-coercion": 0,
"no-implied-eval": 0,
"no-inline-comments": 0,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-invalid-this": 0,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-iterator": 0,
"no-label-var": 0,
"no-labels": 0,
"no-lone-blocks": 0,
"no-lonely-if": 0,
"no-loop-func": 2,
"no-loop-func": 0,
"no-mixed-requires": [0, false],
"no-mixed-spaces-and-tabs": [2, false],
"linebreak-style": [0, "unix"],
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multi-spaces": 0,
"no-multi-str": 0,
"no-multiple-empty-lines": [0, {"max": 2}],
"no-native-reassign": 2,
"no-native-reassign": 0,
"no-negated-condition": 0,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new": 0,
"no-new-func": 0,
"no-new-object": 0,
"no-new-require": 0,
"no-new-wrappers": 2,
"no-new-wrappers": 0,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-octal-escape": 0,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-plusplus": 0,
"no-process-env": 0,
"no-process-exit": 2,
"no-proto": 2,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-reserved-keys": 0,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 2,
"no-restricted-syntax": 0,
"no-return-assign": 0,
"no-script-url": 0,
"no-self-compare": 0,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-space-before-semi": 0,
"no-spaced-func": 2,
"no-sequences": 0,
"no-shadow": 0,
"no-shadow-restricted-names": 0,
"no-spaced-func": 0,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-trailing-spaces": 2,
"no-trailing-spaces": 0,
"no-this-before-super": 0,
"no-throw-literal": 0,
"no-undef": 2,
"no-undef-init": 2,
"no-undef-init": 0,
"no-undefined": 0,
"no-unexpected-multiline": 0,
"no-underscore-dangle": 2,
"no-underscore-dangle": 0,
"no-unneeded-ternary": 0,
"no-unreachable": 2,
"no-unused-expressions": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-use-before-define": 2,
"no-use-before-define": 0,
"no-useless-call": 0,
"no-useless-concat": 0,
"no-void": 0,
"no-var": 0,
"prefer-const": 0,
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": 2,
"no-wrap-func": 2,
"no-with": 0,
"no-magic-numbers": 0,
"array-bracket-spacing": [0, "never"],
"arrow-body-style": [0, "as-needed"],
"arrow-parens": 0,
"arrow-spacing": 0,
"accessor-pairs": 0,
"block-scoped-var": 0,
"block-spacing": 0,
"brace-style": [0, "1tbs"],
"camelcase": 2,
"callback-return": 0,
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"comma-spacing": 0,
"comma-style": 0,
"complexity": [0, 11],
"computed-property-spacing": [0, "never"],
"consistent-return": 2,
"consistent-return": 0,
"consistent-this": [0, "that"],
"constructor-super": 0,
"curly": [2, "all"],
"curly": [0, "all"],
"default-case": 0,
"dot-location": 0,
"dot-notation": [2, { "allowKeywords": true }],
"eol-last": 2,
"eqeqeq": 2,
"dot-notation": [0, { "allowKeywords": true }],
"eol-last": 0,
"eqeqeq": 0,
"func-names": 0,
"func-style": [0, "declaration"],
"generator-star": 0,
"generator-star-spacing": 0,
"global-strict": [2, "never"],
"global-require": 0,
"guard-for-in": 0,
"handle-callback-err": 0,
"id-length": 0,
"indent": 0,
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"init-declarations": 0,
"jsx-quotes": [0, "prefer-double"],
"key-spacing": [0, { "beforeColon": false, "afterColon": true }],
"lines-around-comment": 0,
"max-depth": [0, 4],
"max-len": [0, 80, 4],
"max-nested-callbacks": [0, 2],
"max-params": [0, 3],
"max-statements": [0, 10],
"new-cap": 2,
"new-parens": 2,
"new-cap": 0,
"new-parens": 0,
"newline-after-var": 0,
"object-curly-spacing": [0, "never"],
"object-shorthand": 0,
"one-var": 0,
"one-var": [0, "always"],
"operator-assignment": [0, "always"],
"operator-linebreak": 0,
"padded-blocks": 0,
"prefer-arrow-callback": 0,
"prefer-const": 0,
"prefer-spread": 0,
"prefer-reflect": 0,
"prefer-template": 0,
"quote-props": 0,
"quotes": [2, "double"],
"quotes": [0, "double"],
"radix": 0,
"semi": 2,
"semi-spacing": [2, {"before": false, "after": true}],
"id-match": 0,
"require-jsdoc": 0,
"require-yield": 0,
"semi": 0,
"semi-spacing": [0, {"before": false, "after": true}],
"sort-vars": 0,
"space-after-function-name": [0, "never"],
"space-after-keywords": [0, "always"],
"space-before-keywords": [0, "always"],
"space-before-blocks": [0, "always"],
"space-before-function-paren": [0, "always"],
"space-before-function-parentheses": [0, "always"],
"space-in-brackets": [0, "never"],
"space-in-parens": [0, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"space-infix-ops": 0,
"space-return-throw-case": 0,
"space-unary-ops": [0, { "words": true, "nonwords": false }],
"spaced-comment": 0,
"spaced-line-comment": [0, "always"],
"strict": 2,
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": 0,
"wrap-regex": 0,
"yoda": [2, "never"]
"yoda": [0, "never"]
}
}

150
tools/eslint/conf/json-schema-schema.json

@ -0,0 +1,150 @@
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}

17
tools/eslint/conf/replacements.json

@ -0,0 +1,17 @@
{
"rules": {
"generator-star": ["generator-star-spacing"],
"global-strict": ["strict"],
"no-comma-dangle": ["comma-dangle"],
"no-empty-class": ["no-empty-character-class"],
"no-extra-strict": ["strict"],
"no-reserved-keys": ["quote-props"],
"no-space-before-semi": ["semi-spacing"],
"no-wrap-func": ["no-extra-parens"],
"space-after-function-name": ["space-before-function-paren"],
"space-before-function-parentheses": ["space-before-function-paren"],
"space-in-brackets": ["object-curly-spacing", "array-bracket-spacing", "computed-property-spacing"],
"space-unary-word-ops": ["space-unary-ops"],
"spaced-line-comment": ["spaced-comment"]
}
}

4
tools/eslint/lib/api.js

@ -7,7 +7,7 @@
module.exports = {
linter: require("./eslint"),
cli: require("./cli"),
CLIEngine: require("./cli-engine"),
validator: require("./config-validator")
RuleTester: require("./testers/rule-tester"),
SourceCode: require("./util/source-code")
};

154
tools/eslint/lib/ast-utils.js

@ -0,0 +1,154 @@
/**
* @fileoverview Common utils for AST.
* @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var esutils = require("esutils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks reference if is non initializer and writable.
* @param {Reference} reference - A reference to check.
* @param {int} index - The index of the reference in the references.
* @param {Reference[]} references - The array that the reference belongs to.
* @returns {boolean} Success/Failure
* @private
*/
function isModifyingReference(reference, index, references) {
var identifier = reference.identifier;
return (identifier &&
reference.init === false &&
reference.isWrite() &&
// Destructuring assignments can have multiple default value,
// so possibly there are multiple writeable references for the same identifier.
(index === 0 || references[index - 1].identifier !== identifier)
);
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
* @public
*/
isTokenOnSameLine: function(left, right) {
return left.loc.end.line === right.loc.start.line;
},
/**
* Checks whether or not a node is `null` or `undefined`.
* @param {ASTNode} node - A node to check.
* @returns {boolean} Whether or not the node is a `null` or `undefined`.
* @public
*/
isNullOrUndefined: function(node) {
return (
(node.type === "Literal" && node.value === null) ||
(node.type === "Identifier" && node.name === "undefined") ||
(node.type === "UnaryExpression" && node.operator === "void")
);
},
/**
* Checks whether or not a given node is a string literal.
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is a string literal.
*/
isStringLiteral: function(node) {
return (
(node.type === "Literal" && typeof node.value === "string") ||
node.type === "TemplateLiteral"
);
},
/**
* Gets references which are non initializer and writable.
* @param {Reference[]} references - An array of references.
* @returns {Reference[]} An array of only references which are non initializer and writable.
* @public
*/
getModifyingReferences: function(references) {
return references.filter(isModifyingReference);
},
/**
* Validate that a string passed in is surrounded by the specified character
* @param {string} val The text to check.
* @param {string} character The character to see if it's surrounded by.
* @returns {boolean} True if the text is surrounded by the character, false if not.
* @private
*/
isSurroundedBy: function(val, character) {
return val[0] === character && val[val.length - 1] === character;
},
/**
* Returns whether the provided node is an ESLint directive comment or not
* @param {LineComment|BlockComment} node The node to be checked
* @returns {boolean} `true` if the node is an ESLint directive comment
*/
isDirectiveComment: function(node) {
var comment = node.value.trim();
return (
node.type === "Line" && comment.indexOf("eslint-") === 0 ||
node.type === "Block" && (
comment.indexOf("global ") === 0 ||
comment.indexOf("eslint ") === 0 ||
comment.indexOf("eslint-") === 0
)
);
},
/**
* Gets the trailing statement of a given node.
*
* if (code)
* consequent;
*
* When taking this `IfStatement`, returns `consequent;` statement.
*
* @param {ASTNode} A node to get.
* @returns {ASTNode|null} The trailing statement's node.
*/
getTrailingStatement: esutils.ast.trailingStatement,
/**
* Finds the variable by a given name in a given scope and its upper scopes.
*
* @param {escope.Scope} initScope - A scope to start find.
* @param {string} name - A variable name to find.
* @returns {escope.Variable|null} A found variable or `null`.
*/
getVariableByName: function(initScope, name) {
var scope = initScope;
while (scope) {
var variable = scope.set.get(name);
if (variable) {
return variable;
}
scope = scope.upper;
}
return null;
}
};

435
tools/eslint/lib/cli-engine.js

@ -2,6 +2,7 @@
* @fileoverview Main CLI object.
* @author Nicholas C. Zakas
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -21,14 +22,23 @@ var fs = require("fs"),
assign = require("object-assign"),
debug = require("debug"),
shell = require("shelljs"),
rules = require("./rules"),
eslint = require("./eslint"),
traverse = require("./util/traverse"),
IgnoredPaths = require("./ignored-paths"),
Config = require("./config"),
util = require("./util"),
validator = require("./config-validator");
fileEntryCache = require("file-entry-cache"),
globUtil = require("./util/glob-util"),
SourceCodeFixer = require("./util/source-code-fixer"),
validator = require("./config/config-validator"),
stringify = require("json-stable-stringify"),
crypto = require( "crypto" ),
pkg = require("../package.json");
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
//------------------------------------------------------------------------------
// Typedefs
@ -38,8 +48,7 @@ var fs = require("fs"),
* The options to configure a CLI engine with.
* @typedef {Object} CLIEngineOptions
* @property {string} configFile The configuration file to use.
* @property {boolean} reset True disables all default rules and environments.
* @property {boolean|object} baseConfig Base config object. False disables all default rules and environments.
* @property {boolean|object} baseConfig Base config object. True enables recommend rules and environments.
* @property {boolean} ignore False disables use of .eslintignore.
* @property {string[]} rulePaths An array of directories to load custom rules from.
* @property {boolean} useEslintrc False disables looking for .eslintrc
@ -70,8 +79,7 @@ var fs = require("fs"),
var defaultOptions = {
configFile: null,
reset: false,
baseConfig: require(path.resolve(__dirname, "..", "conf", "eslint.json")),
baseConfig: false,
rulePaths: [],
useEslintrc: true,
envs: [],
@ -79,7 +87,16 @@ var defaultOptions = {
rules: {},
extensions: [".js"],
ignore: true,
ignorePath: null
ignorePath: null,
parser: DEFAULT_PARSER,
cache: false,
// in order to honor the cacheFile option if specified
// this option should not have a default value otherwise
// it will always be used
cacheLocation: "",
cacheFile: ".eslintcache",
fix: false,
allowInlineConfig: true
},
loadedPlugins = Object.create(null);
@ -96,7 +113,7 @@ debug = debug("eslint:cli-engine");
*/
function loadPlugins(pluginNames) {
if (pluginNames) {
pluginNames.forEach(function (pluginName) {
pluginNames.forEach(function(pluginName) {
var pluginNamespace = util.getNamespace(pluginName),
pluginNameWithoutNamespace = util.removeNameSpace(pluginName),
pluginNameWithoutPrefix = util.removePluginPrefix(pluginNameWithoutNamespace),
@ -159,10 +176,12 @@ function calculateStatsPerRun(results) {
* @param {string} text The source code to check.
* @param {Object} configHelper The configuration options for ESLint.
* @param {string} filename An optional string representing the texts filename.
* @param {boolean} fix Indicates if fixes should be processed.
* @param {boolean} allowInlineConfig Allow/ignore comments that change config.
* @returns {Result} The results for linting on this text.
* @private
*/
function processText(text, configHelper, filename) {
function processText(text, configHelper, filename, fix, allowInlineConfig) {
// clear all existing settings for a new file
eslint.reset();
@ -172,7 +191,8 @@ function processText(text, configHelper, filename) {
messages,
stats,
fileExtension = path.extname(filename),
processor;
processor,
fixedResult;
if (filename) {
filePath = path.resolve(filename);
@ -191,24 +211,48 @@ function processText(text, configHelper, filename) {
}
if (processor) {
debug("Using processor");
var parsedBlocks = processor.preprocess(text, filename);
var unprocessedMessages = [];
parsedBlocks.forEach(function(block) {
unprocessedMessages.push(eslint.verify(block, config, filename));
unprocessedMessages.push(eslint.verify(block, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
}));
});
// TODO(nzakas): Figure out how fixes might work for processors
messages = processor.postprocess(unprocessedMessages, filename);
} else {
messages = eslint.verify(text, config, filename);
messages = eslint.verify(text, config, {
filename: filename,
allowInlineConfig: allowInlineConfig
});
if (fix) {
debug("Generating fixed text for " + filename);
fixedResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
messages = fixedResult.messages;
}
}
stats = calculateStatsPerFile(messages);
return {
var result = {
filePath: filename,
messages: messages,
errorCount: stats.errorCount,
warningCount: stats.warningCount
};
if (fixedResult && fixedResult.fixed) {
result.output = fixedResult.output;
}
return result;
}
/**
@ -216,14 +260,17 @@ function processText(text, configHelper, filename) {
* exist, so no need to check that here.
* @param {string} filename The filename of the file being checked.
* @param {Object} configHelper The configuration options for ESLint.
* @param {Object} options The CLIEngine options object.
* @returns {Result} The results for linting on this file.
* @private
*/
function processFile(filename, configHelper) {
function processFile(filename, configHelper, options) {
var text = fs.readFileSync(path.resolve(filename), "utf8"),
result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig);
var text = fs.readFileSync(path.resolve(filename), "utf8");
return result;
return processText(text, configHelper, filename);
}
/**
@ -234,7 +281,7 @@ function processFile(filename, configHelper) {
*/
function createIgnoreResult(filePath) {
return {
filePath: filePath,
filePath: path.resolve(filePath),
messages: [
{
fatal: false,
@ -247,6 +294,90 @@ function createIgnoreResult(filePath) {
};
}
/**
* Checks if the given message is an error message.
* @param {object} message The message to check.
* @returns {boolean} Whether or not the message is an error message.
* @private
*/
function isErrorMessage(message) {
return message.severity === 2;
}
/**
* create a md5Hash of a given string
* @param {string} str the string to calculate the hash for
* @returns {string} the calculated hash
*/
function md5Hash(str) {
return crypto
.createHash("md5")
.update(str, "utf8")
.digest("hex");
}
/**
* return the cacheFile to be used by eslint, based on whether the provided parameter is
* a directory or looks like a directory (ends in `path.sep`), in which case the file
* name will be the `cacheFile/.cache_hashOfCWD`
*
* if cacheFile points to a file or looks like a file then in will just use that file
*
* @param {string} cacheFile The name of file to be used to store the cache
* @returns {string} the resolved path to the cache file
*/
function getCacheFile(cacheFile) {
// make sure the path separators are normalized for the environment/os
// keeping the trailing path separator if present
cacheFile = path.normalize(cacheFile);
var resolvedCacheFile = path.resolve(cacheFile);
var looksLikeADirectory = cacheFile[cacheFile.length - 1 ] === path.sep;
/**
* return the name for the cache file in case the provided parameter is a directory
* @returns {string} the resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, ".cache_" + md5Hash(process.cwd()));
}
var fileStats;
try {
fileStats = fs.lstatSync(resolvedCacheFile);
} catch (ex) {
fileStats = null;
}
// in case the file exists we need to verify if the provided path
// is a directory or a file. If it is a directory we want to create a file
// inside that directory
if (fileStats) {
// is a directory or is a file, but the original file the user provided
// looks like a directory but `path.resolve` removed the `last path.sep`
// so we need to still treat this like a directory
if (fileStats.isDirectory() || looksLikeADirectory) {
return getCacheFileForDirectory();
}
// is file so just use that file
return resolvedCacheFile;
}
// here we known the file or directory doesn't exist,
// so we will try to infer if its a directory if it looks like a directory
// for the current operating system.
// if the last character passed is a path separator we assume is a directory
if (looksLikeADirectory) {
return getCacheFileForDirectory();
}
return resolvedCacheFile;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -264,6 +395,20 @@ function CLIEngine(options) {
*/
this.options = assign(Object.create(defaultOptions), options || {});
var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile);
/**
* cache used to not operate on files that haven't changed since last successful
* execution (e.g. file passed with no errors and no warnings
* @type {Object}
*/
this._fileCache = fileEntryCache.create(cacheFile); // eslint-disable-line no-underscore-dangle
if (!this.options.cache) {
this._fileCache.destroy(); // eslint-disable-line no-underscore-dangle
}
// load in additional rules
if (this.options.rulePaths) {
this.options.rulePaths.forEach(function(rulesdir) {
@ -277,6 +422,79 @@ function CLIEngine(options) {
}.bind(this));
}
/**
* Returns the formatter representing the given format or null if no formatter
* with the given name can be found.
* @param {string} [format] The name of the format to load or the path to a
* custom formatter.
* @returns {Function} The formatter function or null if not found.
*/
CLIEngine.getFormatter = function(format) {
var formatterPath;
// default is stylish
format = format || "stylish";
// only strings are valid formatters
if (typeof format === "string") {
// replace \ with / for Windows compatibility
format = format.replace(/\\/g, "/");
// if there's a slash, then it's a file
if (format.indexOf("/") > -1) {
formatterPath = path.resolve(process.cwd(), format);
} else {
formatterPath = "./formatters/" + format;
}
try {
return require(formatterPath);
} catch (ex) {
return null;
}
} else {
return null;
}
};
/**
* Returns results that only contains errors.
* @param {LintResult[]} results The results to filter.
* @returns {LintResult[]} The filtered results.
*/
CLIEngine.getErrorResults = function(results) {
var filtered = [];
results.forEach(function(result) {
var filteredMessages = result.messages.filter(isErrorMessage);
if (filteredMessages.length > 0) {
filtered.push({
filePath: result.filePath,
messages: filteredMessages
});
}
});
return filtered;
};
/**
* Outputs fixes from the given results to files.
* @param {Object} report The report object created by CLIEngine.
* @returns {void}
*/
CLIEngine.outputFixes = function(report) {
report.results.filter(function(result) {
return result.hasOwnProperty("output");
}).forEach(function(result) {
fs.writeFileSync(result.filePath, result.output);
});
};
CLIEngine.prototype = {
constructor: CLIEngine,
@ -295,37 +513,137 @@ CLIEngine.prototype = {
loadedPlugins[pluginNameWithoutPrefix] = pluginobject;
},
/**
* Resolves the patterns passed into executeOnFiles() into glob-based patterns
* for easier handling.
* @param {string[]} patterns The file patterns passed on the command line.
* @returns {string[]} The equivalent glob patterns.
*/
resolveFileGlobPatterns: function(patterns) {
return globUtil.resolveFileGlobPatterns(patterns, this.options.extensions);
},
/**
* Executes the current configuration on an array of file and directory names.
* @param {string[]} files An array of file and directory names.
* @param {string[]} patterns An array of file and directory names.
* @returns {Object} The results for all files that were linted.
*/
executeOnFiles: function(files) {
executeOnFiles: function(patterns) {
var results = [],
processed = [],
processed = {},
options = this.options,
fileCache = this._fileCache, // eslint-disable-line no-underscore-dangle
configHelper = new Config(options),
ignoredPaths = IgnoredPaths.load(options),
exclude = ignoredPaths.contains.bind(ignoredPaths),
stats;
stats,
startTime,
prevConfig; // the previous configuration used
startTime = Date.now();
patterns = this.resolveFileGlobPatterns(patterns);
/**
* Calculates the hash of the config file used to validate a given file
* @param {string} filename The path of the file to retrieve a config object for to calculate the hash
* @returns {string} the hash of the config
*/
function hashOfConfigFor(filename) {
var config = configHelper.getConfig(filename);
if (!prevConfig) {
prevConfig = {};
}
// reuse the previously hashed config if the config hasn't changed
if (prevConfig.config !== config) {
// config changed so we need to calculate the hash of the config
// and the hash of the plugins being used
prevConfig.config = config;
var eslintVersion = pkg.version;
prevConfig.hash = md5Hash(eslintVersion + "_" + stringify(config));
}
return prevConfig.hash;
}
traverse({
files: files,
extensions: options.extensions,
exclude: options.ignore ? exclude : false
}, function(filename) {
/**
* Executes the linter on a file defined by the `filename`. Skips
* unsupported file extensions and any files that are already linted.
* @param {string} filename The resolved filename of the file to be linted
* @returns {void}
*/
function executeOnFile(filename) {
var hashOfConfig;
if (processed[filename]) {
return;
}
if (options.cache) {
// get the descriptor for this file
// with the metadata and the flag that determines if
// the file has changed
var descriptor = fileCache.getFileDescriptor(filename);
var meta = descriptor.meta || {};
hashOfConfig = hashOfConfigFor(filename);
var changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
if (!changed) {
debug("Skipping file since hasn't changed: " + filename);
// Adding the filename to the processed hashmap
// so the reporting is not affected (showing a warning about .eslintignore being used
// when it is not really used)
processed[filename] = true;
// Add the the cached results (always will be 0 error and 0 warnings)
// cause we don't save to cache files that failed
// to guarantee that next execution will process those files as well
results.push(descriptor.meta.results);
// move to the next file
return;
}
}
debug("Processing " + filename);
processed.push(filename);
results.push(processFile(filename, configHelper));
});
processed[filename] = true;
var res = processFile(filename, configHelper, options);
if (options.cache) {
// if a file contains errors or warnings we don't want to
// store the file in the cache so we can guarantee that
// next execution will also operate on this file
if ( res.errorCount > 0 || res.warningCount > 0 ) {
debug("File has problems, skipping it: " + filename);
// remove the entry from the cache
fileCache.removeEntry( filename );
} else {
// since the file passed we store the result here
// TODO: check this as we might not need to store the
// successful runs as it will always should be 0 error 0 warnings
descriptor.meta.hashOfConfig = hashOfConfig;
descriptor.meta.results = res;
}
}
results.push(res);
}
// Lint each desired file
globUtil.listFilesToProcess(patterns, options).forEach(executeOnFile);
// only warn for files explicitly passed on the command line
if (options.ignore) {
files.forEach(function(file) {
if (fs.statSync(path.resolve(file)).isFile() && processed.indexOf(file) === -1) {
patterns.forEach(function(file) {
var fullPath = path.resolve(file);
if (shell.test("-f", fullPath) && !processed[fullPath]) {
results.push(createIgnoreResult(file));
}
});
@ -333,6 +651,13 @@ CLIEngine.prototype = {
stats = calculateStatsPerRun(results);
if (options.cache) {
// persist the cache to disk
fileCache.reconcile();
}
debug("Linting complete in: " + (Date.now() - startTime) + "ms");
return {
results: results,
errorCount: stats.errorCount,
@ -358,7 +683,7 @@ CLIEngine.prototype = {
if (filename && options.ignore && exclude(filename)) {
results.push(createIgnoreResult(filename));
} else {
results.push(processText(text, configHelper, filename));
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
}
stats = calculateStatsPerRun(results);
@ -387,7 +712,7 @@ CLIEngine.prototype = {
* @param {string} filePath The path of the file to check.
* @returns {boolean} Whether or not the given path is ignored.
*/
isPathIgnored: function (filePath) {
isPathIgnored: function(filePath) {
var ignoredPaths;
if (this.options.ignore) {
@ -398,43 +723,7 @@ CLIEngine.prototype = {
return false;
},
/**
* Returns the formatter representing the given format or null if no formatter
* with the given name can be found.
* @param {string} [format] The name of the format to load or the path to a
* custom formatter.
* @returns {Function} The formatter function or null if not found.
*/
getFormatter: function(format) {
var formatterPath;
// default is stylish
format = format || "stylish";
// only strings are valid formatters
if (typeof format === "string") {
// replace \ with / for Windows compatibility
format = format.replace(/\\/g, "/");
// if there's a slash, then it's a file
if (format.indexOf("/") > -1) {
formatterPath = path.resolve(process.cwd(), format);
} else {
formatterPath = "./formatters/" + format;
}
try {
return require(formatterPath);
} catch (ex) {
return null;
}
} else {
return null;
}
}
getFormatter: CLIEngine.getFormatter
};

91
tools/eslint/lib/cli.js

@ -22,7 +22,8 @@ var fs = require("fs"),
options = require("./options"),
CLIEngine = require("./cli-engine"),
mkdirp = require("mkdirp");
mkdirp = require("mkdirp"),
log = require("./logging");
//------------------------------------------------------------------------------
// Helpers
@ -48,8 +49,13 @@ function translateOptions(cliOptions) {
ignorePattern: cliOptions.ignorePattern,
configFile: cliOptions.config,
rulePaths: cliOptions.rulesdir,
reset: cliOptions.reset,
useEslintrc: cliOptions.eslintrc
useEslintrc: cliOptions.eslintrc,
parser: cliOptions.parser,
cache: cliOptions.cache,
cacheFile: cliOptions.cacheFile,
cacheLocation: cliOptions.cacheLocation,
fix: cliOptions.fix,
allowInlineConfig: cliOptions.inlineConfig
};
}
@ -69,7 +75,7 @@ function printResults(engine, results, format, outputFile) {
formatter = engine.getFormatter(format);
if (!formatter) {
console.error("Could not find formatter '%s'.", format);
log.error("Could not find formatter '%s'.", format);
return false;
}
@ -80,7 +86,7 @@ function printResults(engine, results, format, outputFile) {
filePath = path.resolve(process.cwd(), outputFile);
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
console.error("Cannot write to output file path, it is a directory: %s", outputFile);
log.error("Cannot write to output file path, it is a directory: %s", outputFile);
return false;
}
@ -88,11 +94,11 @@ function printResults(engine, results, format, outputFile) {
mkdirp.sync(path.dirname(filePath));
fs.writeFileSync(filePath, output);
} catch (ex) {
console.error("There was a problem writing the output file:\n%s", ex);
log.error("There was a problem writing the output file:\n%s", ex);
return false;
}
} else {
console.log(output);
log.info(output);
}
}
@ -100,37 +106,6 @@ function printResults(engine, results, format, outputFile) {
}
/**
* Checks if the given message is an error message.
* @param {object} message The message to check.
* @returns {boolean} Whether or not the message is an error message.
*/
function isErrorMessage(message) {
return message.severity === 2;
}
/**
* Returns results that only contains errors.
* @param {LintResult[]} results The results to filter.
* @returns {LintResult[]} The filtered results.
*/
function getErrorResults(results) {
var filtered = [];
results.forEach(function (result) {
var filteredMessages = result.messages.filter(isErrorMessage);
if (filteredMessages.length > 0) {
filtered.push({
filePath: result.filePath,
messages: filteredMessages
});
}
});
return filtered;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -151,13 +126,14 @@ var cli = {
var currentOptions,
files,
result,
engine;
report,
engine,
tooManyWarnings;
try {
currentOptions = options.parse(args);
} catch (error) {
console.error(error.message);
log.error(error.message);
return 1;
}
@ -165,24 +141,43 @@ var cli = {
if (currentOptions.version) { // version from package.json
console.log("v" + require("../package.json").version);
log.info("v" + require("../package.json").version);
} else if (currentOptions.help || (!files.length && !text)) {
console.log(options.generateHelp());
log.info(options.generateHelp());
} else {
engine = new CLIEngine(translateOptions(currentOptions));
debug("Running on " + (text ? "text" : "files"));
result = text ? engine.executeOnText(text, currentOptions.stdinFilename) : engine.executeOnFiles(files);
// disable --fix for piped-in code until we know how to do it correctly
if (text && currentOptions.fix) {
log.error("The --fix option is not available for piped-in code.");
return 1;
}
engine = new CLIEngine(translateOptions(currentOptions));
report = text ? engine.executeOnText(text, currentOptions.stdinFilename) : engine.executeOnFiles(files);
if (currentOptions.fix) {
debug("Fix mode enabled - applying fixes");
CLIEngine.outputFixes(report);
}
if (currentOptions.quiet) {
result.results = getErrorResults(result.results);
debug("Quiet mode enabled - filtering out warnings");
report.results = CLIEngine.getErrorResults(report.results);
}
if (printResults(engine, result.results, currentOptions.format, currentOptions.outputFile)) {
return result.errorCount ? 1 : 0;
if (printResults(engine, report.results, currentOptions.format, currentOptions.outputFile)) {
tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings;
if (!report.errorCount && tooManyWarnings) {
log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings);
}
return (report.errorCount || tooManyWarnings) ? 1 : 0;
} else {
return 1;
}

145
tools/eslint/lib/config-initializer.js

@ -1,145 +0,0 @@
/**
* @fileoverview Config initialization wizard.
* @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/
"use strict";
var exec = require("child_process").exec,
fs = require("fs"),
inquirer = require("inquirer"),
yaml = require("js-yaml");
/* istanbul ignore next: hard to test fs function */
/**
* Create .eslintrc file in the current working directory
* @param {object} config object that contains user's answers
* @param {bool} isJson should config file be json or yaml
* @param {function} callback function to call once the file is written.
* @returns {void}
*/
function writeFile(config, isJson, callback) {
try {
fs.writeFileSync("./.eslintrc", isJson ? JSON.stringify(config, null, 4) : yaml.safeDump(config));
} catch (e) {
callback(e);
return;
}
if (config.plugins && config.plugins.indexOf("react") >= 0) {
exec("npm i eslint-plugin-react --save-dev", callback);
} else {
callback();
}
}
/**
* process user's answers and create config object
* @param {object} answers answers received from inquirer
* @returns {object} config object
*/
function processAnswers(answers) {
var config = {rules: {}, env: {}};
config.rules.indent = [2, answers.indent];
config.rules.quotes = [2, answers.quotes];
config.rules["linebreak-style"] = [2, answers.linebreak];
config.rules.semi = [2, answers.semi ? "always" : "never"];
if (answers.es6) {
config.env.es6 = true;
}
answers.env.forEach(function(env) {
config.env[env] = true;
});
if (answers.jsx) {
config.ecmaFeatures = {jsx: true};
if (answers.react) {
config.plugins = ["react"];
}
}
return config;
}
/* istanbul ignore next: no need to test inquirer*/
/**
* Ask use a few questions on command prompt
* @param {function} callback callback function when file has been written
* @returns {void}
*/
function promptUser(callback) {
inquirer.prompt([
{
type: "list",
name: "indent",
message: "What style of indentation do you use?",
default: "tabs",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
},
{
type: "list",
name: "quotes",
message: "What quotes do you use for strings?",
default: "double",
choices: [{name: "Double", value: "double"}, {name: "Single", value: "single"}]
},
{
type: "list",
name: "linebreak",
message: "What line endings do you use?",
default: "unix",
choices: [{name: "Unix", value: "unix"}, {name: "Windows", value: "windows"}]
},
{
type: "confirm",
name: "semi",
message: "Do you require semicolons?",
default: true
},
{
type: "confirm",
name: "es6",
message: "Are you using ECMAScript 6 features?",
default: false
},
{
type: "checkbox",
name: "env",
message: "Where will your code run?",
default: ["browser"],
choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}]
},
{
type: "confirm",
name: "jsx",
message: "Do you use JSX?",
default: false
},
{
type: "confirm",
name: "react",
message: "Do you use React",
default: false,
when: function (answers) {
return answers.jsx;
}
},
{
type: "list",
name: "format",
message: "What format do you want your config file to be in?",
default: "JSON",
choices: ["JSON", "YAML"]
}
], function(answers) {
var config = processAnswers(answers);
writeFile(config, answers.format === "JSON", callback);
});
}
var init = {
processAnswers: processAnswers,
initializeConfig: /* istanbul ignore next */ function(callback) {
promptUser(callback);
}
};
module.exports = init;

110
tools/eslint/lib/config-validator.js

@ -1,110 +0,0 @@
/**
* @fileoverview Validates configs.
* @author Brandon Mills
* @copyright 2015 Brandon Mills
*/
"use strict";
var rules = require("./rules"),
schemaValidator = require("is-my-json-valid");
var validators = {
rules: Object.create(null)
};
/**
* Gets a complete options schema for a rule.
* @param {string} id The rule's unique name.
* @returns {object} JSON Schema for the rule's options.
*/
function getRuleOptionsSchema(id) {
var rule = rules.get(id),
schema = rule && rule.schema;
if (!schema) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
],
"minItems": 1
};
}
// Given a tuple of schemas, insert warning level at the beginning
if (Array.isArray(schema)) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
].concat(schema),
"minItems": 1,
"maxItems": schema.length + 1
};
}
// Given a full schema, leave it alone
return schema;
}
/**
* Validates a rule's options against its schema.
* @param {string} id The rule's unique name.
* @param {array|number} options The given options for the rule.
* @param {string} source The name of the configuration source.
* @returns {void}
*/
function validateRuleOptions(id, options, source) {
var validateRule = validators.rules[id],
message;
if (!validateRule) {
validateRule = schemaValidator(getRuleOptionsSchema(id), { verbose: true });
validators.rules[id] = validateRule;
}
if (typeof options === "number") {
options = [options];
}
validateRule(options);
if (validateRule.errors) {
message = [
source, ":\n",
"\tConfiguration for rule \"", id, "\" is invalid:\n"
];
validateRule.errors.forEach(function (error) {
message.push(
"\tValue \"", error.value, "\" ", error.message, ".\n"
);
});
throw new Error(message.join(""));
}
}
/**
* Validates an entire config object.
* @param {object} config The config object to validate.
* @param {string} source The location to report with any errors.
* @returns {void}
*/
function validate(config, source) {
if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function (id) {
validateRuleOptions(id, config.rules[id], source);
});
}
}
module.exports = {
getRuleOptionsSchema: getRuleOptionsSchema,
validate: validate,
validateRuleOptions: validateRuleOptions
};

266
tools/eslint/lib/config.js

@ -2,8 +2,9 @@
* @fileoverview Responsible for loading config files
* @author Seth McLaughlin
* @copyright 2014 Nicholas C. Zakas. All rights reserved.
* @copyright 2013 Seth McLaughlin. All rights reserved.
* @copyright 2014 Michael McLaughlin. All rights reserved.
* @copyright 2013 Seth McLaughlin. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -11,27 +12,22 @@
// Requirements
//------------------------------------------------------------------------------
var fs = require("fs"),
path = require("path"),
environments = require("../conf/environments"),
var path = require("path"),
ConfigOps = require("./config/config-ops"),
ConfigFile = require("./config/config-file"),
util = require("./util"),
FileFinder = require("./file-finder"),
stripComments = require("strip-json-comments"),
assign = require("object-assign"),
debug = require("debug"),
yaml = require("js-yaml"),
userHome = require("user-home"),
isAbsolutePath = require("path-is-absolute"),
validator = require("./config-validator");
isResolvable = require("is-resolvable"),
pathIsInside = require("path-is-inside");
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
var LOCAL_CONFIG_FILENAME = ".eslintrc",
PACKAGE_CONFIG_FILENAME = "package.json",
PACKAGE_CONFIG_FIELD_NAME = "eslintConfig",
PERSONAL_CONFIG_PATH = userHome ? path.join(userHome, LOCAL_CONFIG_FILENAME) : null;
var PACKAGE_CONFIG_FILENAME = "package.json",
PERSONAL_CONFIG_DIR = userHome || null;
//------------------------------------------------------------------------------
// Private
@ -46,97 +42,40 @@ var loadedPlugins = Object.create(null);
debug = debug("eslint:config");
/**
* Determines if a given string represents a filepath or not using the same
* conventions as require(), meaning that the first character must be nonalphanumeric
* and not the @ sign which is used for scoped packages to be considered a file path.
* @param {string} filePath The string to check.
* @returns {boolean} True if it's a filepath, false if not.
* Check if item is an javascript object
* @param {*} item object to check for
* @returns {boolean} True if its an object
* @private
*/
function isFilePath(filePath) {
return isAbsolutePath(filePath) || !/\w|@/.test(filePath[0]);
function isObject(item) {
return typeof item === "object" && !Array.isArray(item) && item !== null;
}
/**
* Load and parse a JSON config object from a file.
* @param {string} filePath the path to the JSON config file
* @param {string|Object} configToLoad the path to the JSON config file or the config object itself.
* @returns {Object} the parsed config object (empty object if there was a parse error)
* @private
*/
function loadConfig(filePath) {
var config = {};
function loadConfig(configToLoad) {
var config = {},
filePath = "";
if (filePath) {
if (configToLoad) {
if (isFilePath(filePath)) {
try {
config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {};
} catch (e) {
debug("Error reading YAML file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
if (isObject(configToLoad)) {
config = configToLoad;
if (path.basename(filePath) === PACKAGE_CONFIG_FILENAME) {
config = config[PACKAGE_CONFIG_FIELD_NAME] || {};
if (config.extends) {
config = ConfigFile.applyExtends(config, filePath);
}
} else {
// it's a package
if (filePath.indexOf("eslint-config-") === -1) {
if (filePath.indexOf("@") === 0) {
// for scoped packages, insert the eslint-config after the first /
filePath = filePath.replace(/^([^\/]+\/)(.*)$/, "$1eslint-config-$2");
} else {
filePath = "eslint-config-" + filePath;
}
}
config = util.mergeConfigs(config, require(filePath));
filePath = configToLoad;
config = ConfigFile.load(filePath);
}
validator.validate(config, filePath);
// If an `extends` property is defined, it represents a configuration file to use as
// a "parent". Load the referenced file and merge the configuration recursively.
if (config.extends) {
var configExtends = config.extends;
if (!Array.isArray(config.extends)) {
configExtends = [config.extends];
}
// Make the last element in an array take the highest precedence
config = configExtends.reduceRight(function (previousValue, parentPath) {
if (isFilePath(parentPath)) {
// If the `extends` path is relative, use the directory of the current configuration
// file as the reference point. Otherwise, use as-is.
parentPath = (!isAbsolutePath(parentPath) ?
path.join(path.dirname(filePath), parentPath) :
parentPath
);
}
try {
return util.mergeConfigs(loadConfig(parentPath), previousValue);
} catch (e) {
// If the file referenced by `extends` failed to load, add the path to the
// configuration file that referenced it to the error message so the user is
// able to see where it was referenced from, then re-throw
e.message += "\nReferenced from: " + filePath;
throw e;
}
}, config);
}
}
return config;
}
@ -148,7 +87,7 @@ function loadConfig(filePath) {
function getPluginsConfig(pluginNames) {
var pluginConfig = {};
pluginNames.forEach(function (pluginName) {
pluginNames.forEach(function(pluginName) {
var pluginNamespace = util.getNamespace(pluginName),
pluginNameWithoutNamespace = util.removeNameSpace(pluginName),
pluginNameWithoutPrefix = util.removePluginPrefix(pluginNameWithoutNamespace),
@ -159,7 +98,7 @@ function getPluginsConfig(pluginNames) {
try {
plugin = require(pluginNamespace + util.PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix);
loadedPlugins[pluginNameWithoutPrefix] = plugin;
} catch(err) {
} catch (err) {
debug("Failed to load plugin configuration for " + pluginNameWithoutPrefix + ". Proceeding without it.");
plugin = { rulesConfig: {}};
}
@ -175,7 +114,7 @@ function getPluginsConfig(pluginNames) {
rules[pluginNameWithoutPrefix + "/" + item] = plugin.rulesConfig[item];
});
pluginConfig = util.mergeConfigs(pluginConfig, rules);
pluginConfig = ConfigOps.merge(pluginConfig, rules);
});
return {rules: pluginConfig};
@ -187,11 +126,16 @@ function getPluginsConfig(pluginNames) {
* @private
*/
function getPersonalConfig() {
var config = {};
var config = {},
filename;
if (PERSONAL_CONFIG_DIR) {
filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
if (PERSONAL_CONFIG_PATH && fs.existsSync(PERSONAL_CONFIG_PATH)) {
debug("Using personal config");
config = loadConfig(PERSONAL_CONFIG_PATH);
if (filename) {
debug("Using personal config");
config = loadConfig(filename);
}
}
return config;
@ -210,76 +154,45 @@ function getLocalConfig(thisConfig, directory) {
localConfigFile,
config = {},
localConfigFiles = thisConfig.findLocalConfigFiles(directory),
numFiles = localConfigFiles.length;
numFiles = localConfigFiles.length,
rootPath,
projectConfigPath = ConfigFile.getFilenameForDirectory(process.cwd());
for (i = 0; i < numFiles; i++) {
localConfigFile = localConfigFiles[i];
// Don't consider the personal config file in the home directory.
if (localConfigFile === PERSONAL_CONFIG_PATH) {
// Don't consider the personal config file in the home directory,
// except if the home directory is the same as the current working directory
if (path.dirname(localConfigFile) === PERSONAL_CONFIG_DIR && localConfigFile !== projectConfigPath) {
continue;
}
// If root flag is set, don't consider file if it is above root
if (rootPath && !pathIsInside(path.dirname(localConfigFile), rootPath)) {
continue;
}
debug("Loading " + localConfigFile);
localConfig = loadConfig(localConfigFile);
// Don't consider a local config file found if the config is empty.
if (!Object.keys(localConfig).length) {
// Don't consider a local config file found if the config is null
if (!localConfig) {
continue;
}
// Check for root flag
if (localConfig.root === true) {
rootPath = path.dirname(localConfigFile);
}
found = true;
debug("Using " + localConfigFile);
config = util.mergeConfigs(localConfig, config);
config = ConfigOps.merge(localConfig, config);
}
// Use the personal config file if there are no other local config files found.
return found ? config : util.mergeConfigs(config, getPersonalConfig());
}
/**
* Creates an environment config based on the specified environments.
* @param {Object<string,boolean>} envs The environment settings.
* @param {boolean} reset The value of the command line reset option. If true,
* rules are not automatically merged into the config.
* @returns {Object} A configuration object with the appropriate rules and globals
* set.
* @private
*/
function createEnvironmentConfig(envs, reset) {
var envConfig = {
globals: {},
env: envs || {},
rules: {},
ecmaFeatures: {}
};
if (envs) {
Object.keys(envs).filter(function (name) {
return envs[name];
}).forEach(function(name) {
var environment = environments[name];
if (environment) {
if (!reset && environment.rules) {
assign(envConfig.rules, environment.rules);
}
if (environment.globals) {
assign(envConfig.globals, environment.globals);
}
if (environment.ecmaFeatures) {
assign(envConfig.ecmaFeatures, environment.ecmaFeatures);
}
}
});
}
return envConfig;
return found ? config : ConfigOps.merge(config, getPersonalConfig());
}
//------------------------------------------------------------------------------
@ -301,26 +214,18 @@ function Config(options) {
this.ignore = options.ignore;
this.ignorePath = options.ignorePath;
this.cache = {};
this.parser = options.parser;
if (options.reset || options.baseConfig === false) {
// If `options.reset` is truthy or `options.baseConfig` is set to `false`,
// disable all default rules and environments
this.baseConfig = { rules: {} };
} else {
// If `options.baseConfig` is an object, just use it,
// otherwise use default base config from `conf/eslint.json`
this.baseConfig = options.baseConfig ||
require(path.resolve(__dirname, "..", "conf", "eslint.json"));
}
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} };
this.useEslintrc = (options.useEslintrc !== false);
this.env = (options.envs || []).reduce(function (envs, name) {
this.env = (options.envs || []).reduce(function(envs, name) {
envs[name] = true;
return envs;
}, {});
this.globals = (options.globals || []).reduce(function (globals, def) {
this.globals = (options.globals || []).reduce(function(globals, def) {
// Default "foo" to false and handle "foo:false" and "foo:true"
var parts = def.split(":");
globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
@ -332,7 +237,11 @@ function Config(options) {
if (useConfig) {
debug("Using command line config " + useConfig);
this.useSpecificConfig = loadConfig(path.resolve(process.cwd(), useConfig));
if (isResolvable(useConfig) || isResolvable("eslint-config-" + useConfig) || useConfig.charAt(0) === "@") {
this.useSpecificConfig = loadConfig(useConfig);
} else {
this.useSpecificConfig = loadConfig(path.resolve(process.cwd(), useConfig));
}
}
}
@ -342,7 +251,7 @@ function Config(options) {
* @param {string} filePath a file in whose directory we start looking for a local config
* @returns {Object} config object
*/
Config.prototype.getConfig = function (filePath) {
Config.prototype.getConfig = function(filePath) {
var config,
userConfig,
directory = filePath ? path.dirname(filePath) : process.cwd(),
@ -367,43 +276,42 @@ Config.prototype.getConfig = function (filePath) {
}
// Step 2: Create a copy of the baseConfig
config = util.mergeConfigs({}, this.baseConfig);
// Step 3: Merge in environment-specific globals and rules from .eslintrc files
config = util.mergeConfigs(config, createEnvironmentConfig(userConfig.env, this.options.reset));
config = ConfigOps.merge({parser: this.parser}, this.baseConfig);
// Step 4: Merge in the user-specified configuration from .eslintrc and package.json
config = util.mergeConfigs(config, userConfig);
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json
config = ConfigOps.merge(config, userConfig);
// Step 5: Merge in command line config file
// Step 4: Merge in command line config file
if (this.useSpecificConfig) {
debug("Merging command line config file");
if (this.useSpecificConfig.env) {
config = util.mergeConfigs(config, createEnvironmentConfig(this.useSpecificConfig.env, this.options.reset));
}
config = util.mergeConfigs(config, this.useSpecificConfig);
config = ConfigOps.merge(config, this.useSpecificConfig);
}
// Step 6: Merge in command line environments
// Step 5: Merge in command line environments
debug("Merging command line environment settings");
config = util.mergeConfigs(config, createEnvironmentConfig(this.env, this.options.reset));
config = ConfigOps.merge(config, ConfigOps.createEnvironmentConfig(this.env));
// Step 7: Merge in command line rules
// Step 6: Merge in command line rules
if (this.options.rules) {
debug("Merging command line rules");
config = util.mergeConfigs(config, { rules: this.options.rules });
config = ConfigOps.merge(config, { rules: this.options.rules });
}
// Step 8: Merge in command line globals
config = util.mergeConfigs(config, { globals: this.globals });
// Step 7: Merge in command line globals
config = ConfigOps.merge(config, { globals: this.globals });
// Step 8: Merge in command line plugins
if (this.options.plugins) {
debug("Merging command line plugins");
pluginConfig = getPluginsConfig(this.options.plugins);
config = ConfigOps.merge(config, { plugins: this.options.plugins });
}
// Step 9: Merge in plugin specific rules in reverse
if (config.plugins) {
pluginConfig = getPluginsConfig(config.plugins);
config = util.mergeConfigs(pluginConfig, config);
config = ConfigOps.merge(pluginConfig, config);
}
this.cache[directory] = config;
@ -416,10 +324,10 @@ Config.prototype.getConfig = function (filePath) {
* @param {string} directory The directory to start searching from.
* @returns {string[]} The paths of local config files found.
*/
Config.prototype.findLocalConfigFiles = function (directory) {
Config.prototype.findLocalConfigFiles = function(directory) {
if (!this.localConfigFinder) {
this.localConfigFinder = new FileFinder(LOCAL_CONFIG_FILENAME, PACKAGE_CONFIG_FILENAME);
this.localConfigFinder = new FileFinder(ConfigFile.CONFIG_FILES, PACKAGE_CONFIG_FILENAME);
}
return this.localConfigFinder.findAllInDirectoryAndParents(directory);

440
tools/eslint/lib/config/config-file.js

@ -0,0 +1,440 @@
/**
* @fileoverview Helper to locate and load configuration files.
* @author Nicholas C. Zakas
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
/* eslint no-use-before-define: 0 */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var debug = require("debug"),
fs = require("fs"),
path = require("path"),
ConfigOps = require("./config-ops"),
validator = require("./config-validator"),
stripComments = require("strip-json-comments"),
isAbsolutePath = require("path-is-absolute");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
var CONFIG_FILES = [
".eslintrc.js",
".eslintrc.yaml",
".eslintrc.yml",
".eslintrc.json",
".eslintrc"
];
debug = debug("eslint:config-file");
/**
* Convenience wrapper for synchronously reading file contents.
* @param {string} filePath The filename to read.
* @returns {string} The file contents.
* @private
*/
function readFile(filePath) {
return fs.readFileSync(filePath, "utf8");
}
/**
* Determines if a given string represents a filepath or not using the same
* conventions as require(), meaning that the first character must be nonalphanumeric
* and not the @ sign which is used for scoped packages to be considered a file path.
* @param {string} filePath The string to check.
* @returns {boolean} True if it's a filepath, false if not.
* @private
*/
function isFilePath(filePath) {
return isAbsolutePath(filePath) || !/\w|@/.test(filePath.charAt(0));
}
/**
* Loads a YAML configuration from a file.
* @param {string} filePath The filename to load.
* @returns {Object} The configuration object from the file.
* @throws {Error} If the file cannot be read.
* @private
*/
function loadYAMLConfigFile(filePath) {
debug("Loading YAML config file: " + filePath);
// lazy load YAML to improve performance when not used
var yaml = require("js-yaml");
try {
// empty YAML file can be null, so always use
return yaml.safeLoad(readFile(filePath)) || {};
} catch (e) {
debug("Error reading YAML file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a JSON configuration from a file.
* @param {string} filePath The filename to load.
* @returns {Object} The configuration object from the file.
* @throws {Error} If the file cannot be read.
* @private
*/
function loadJSONConfigFile(filePath) {
debug("Loading JSON config file: " + filePath);
try {
return JSON.parse(stripComments(readFile(filePath)));
} catch (e) {
debug("Error reading JSON file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a legacy (.eslintrc) configuration from a file.
* @param {string} filePath The filename to load.
* @returns {Object} The configuration object from the file.
* @throws {Error} If the file cannot be read.
* @private
*/
function loadLegacyConfigFile(filePath) {
debug("Loading config file: " + filePath);
// lazy load YAML to improve performance when not used
var yaml = require("js-yaml");
try {
return yaml.safeLoad(stripComments(readFile(filePath))) || {};
} catch (e) {
debug("Error reading YAML file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a JavaScript configuration from a file.
* @param {string} filePath The filename to load.
* @returns {Object} The configuration object from the file.
* @throws {Error} If the file cannot be read.
* @private
*/
function loadJSConfigFile(filePath) {
debug("Loading JS config file: " + filePath);
try {
return require(filePath);
} catch (e) {
debug("Error reading JavaScript file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a configuration from a package.json file.
* @param {string} filePath The filename to load.
* @returns {Object} The configuration object from the file.
* @throws {Error} If the file cannot be read.
* @private
*/
function loadPackageJSONConfigFile(filePath) {
debug("Loading package.json config file: " + filePath);
try {
return require(filePath).eslintConfig || null;
} catch (e) {
debug("Error reading package.json file: " + filePath);
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a JavaScript configuration from a package.
* @param {string} filePath The package name to load.
* @returns {Object} The configuration object from the package.
* @throws {Error} If the package cannot be read.
* @private
*/
function loadPackage(filePath) {
debug("Loading config package: " + filePath);
try {
return require(filePath);
} catch (e) {
debug("Error reading package: " + filePath);
e.message = "Cannot read config package: " + filePath + "\nError: " + e.message;
throw e;
}
}
/**
* Loads a configuration file regardless of the source. Inspects the file path
* to determine the correctly way to load the config file.
* @param {string} filePath The path to the configuration.
* @returns {Object} The configuration information.
* @private
*/
function loadConfigFile(filePath) {
var config;
if (isFilePath(filePath)) {
switch (path.extname(filePath)) {
case ".js":
config = loadJSConfigFile(filePath);
break;
case ".json":
if (path.basename(filePath) === "package.json") {
config = loadPackageJSONConfigFile(filePath);
if (config === null) {
return null;
}
} else {
config = loadJSONConfigFile(filePath);
}
break;
case ".yaml":
case ".yml":
config = loadYAMLConfigFile(filePath);
break;
default:
config = loadLegacyConfigFile(filePath);
}
} else {
config = loadPackage(filePath);
}
return ConfigOps.merge(ConfigOps.createEmptyConfig(), config);
}
/**
* Writes a configuration file in JSON format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @private
*/
function writeJSONConfigFile(config, filePath) {
debug("Writing JSON config file: " + filePath);
var content = JSON.stringify(config, null, 4);
fs.writeFileSync(filePath, content, "utf8");
}
/**
* Writes a configuration file in YAML format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @private
*/
function writeYAMLConfigFile(config, filePath) {
debug("Writing YAML config file: " + filePath);
// lazy load YAML to improve performance when not used
var yaml = require("js-yaml");
var content = yaml.safeDump(config);
fs.writeFileSync(filePath, content, "utf8");
}
/**
* Writes a configuration file in JavaScript format.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @private
*/
function writeJSConfigFile(config, filePath) {
debug("Writing JS config file: " + filePath);
var content = "module.exports = " + JSON.stringify(config, null, 4) + ";";
fs.writeFileSync(filePath, content, "utf8");
}
/**
* Writes a configuration file.
* @param {Object} config The configuration object to write.
* @param {string} filePath The filename to write to.
* @returns {void}
* @throws {Error} When an unknown file type is specified.
* @private
*/
function write(config, filePath) {
switch (path.extname(filePath)) {
case ".js":
writeJSConfigFile(config, filePath);
break;
case ".json":
writeJSONConfigFile(config, filePath);
break;
case ".yaml":
case ".yml":
writeYAMLConfigFile(config, filePath);
break;
default:
throw new Error("Can't write to unknown file type.");
}
}
/**
* Applies values from the "extends" field in a configuration file.
* @param {Object} config The configuration information.
* @param {string} filePath The file path from which the configuration information
* was loaded.
* @returns {Object} A new configuration object with all of the "extends" fields
* loaded and merged.
* @private
*/
function applyExtends(config, filePath) {
var configExtends = config.extends;
// normalize into an array for easier handling
if (!Array.isArray(config.extends)) {
configExtends = [config.extends];
}
// Make the last element in an array take the highest precedence
config = configExtends.reduceRight(function(previousValue, parentPath) {
if (parentPath === "eslint:recommended") {
// Add an explicit substitution for eslint:recommended to conf/eslint.json
// this lets us use the eslint.json file as the recommended rules
parentPath = path.resolve(__dirname, "../../conf/eslint.json");
} else if (isFilePath(parentPath)) {
// If the `extends` path is relative, use the directory of the current configuration
// file as the reference point. Otherwise, use as-is.
parentPath = (!isAbsolutePath(parentPath) ?
path.join(path.dirname(filePath), parentPath) :
parentPath
);
}
try {
debug("Loading " + parentPath);
return ConfigOps.merge(load(parentPath), previousValue);
} catch (e) {
// If the file referenced by `extends` failed to load, add the path to the
// configuration file that referenced it to the error message so the user is
// able to see where it was referenced from, then re-throw
e.message += "\nReferenced from: " + filePath;
throw e;
}
}, config);
return config;
}
/**
* Resolves a configuration file path into the fully-formed path, whether filename
* or package name.
* @param {string} filePath The filepath to resolve.
* @returns {string} A path that can be used directly to load the configuration.
* @private
*/
function resolve(filePath) {
if (isFilePath(filePath)) {
return path.resolve(filePath);
} else {
// it's a package
if (filePath.charAt(0) === "@") {
// it's a scoped package
// package name is "eslint-config", or just a username
var scopedPackageShortcutRegex = /^(@[^\/]+)(?:\/(?:eslint-config)?)?$/;
if (scopedPackageShortcutRegex.test(filePath)) {
filePath = filePath.replace(scopedPackageShortcutRegex, "$1/eslint-config");
} else if (filePath.split("/")[1].indexOf("eslint-config-") !== 0) {
// for scoped packages, insert the eslint-config after the first /
filePath = filePath.replace(/^@([^\/]+)\/(.*)$/, "@$1/eslint-config-$2");
}
} else if (filePath.indexOf("eslint-config-") !== 0) {
filePath = "eslint-config-" + filePath;
}
return filePath;
}
}
/**
* Loads a configuration file from the given file path.
* @param {string} filePath The filename or package name to load the configuration
* information from.
* @returns {Object} The configuration information.
* @private
*/
function load(filePath) {
var resolvedPath = resolve(filePath),
config = loadConfigFile(resolvedPath);
if (config) {
// validate the configuration before continuing
validator.validate(config, filePath);
// If an `extends` property is defined, it represents a configuration file to use as
// a "parent". Load the referenced file and merge the configuration recursively.
if (config.extends) {
config = applyExtends(config, filePath);
}
if (config.env) {
// Merge in environment-specific globals and ecmaFeatures.
config = ConfigOps.applyEnvironments(config);
}
}
return config;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
load: load,
resolve: resolve,
write: write,
applyExtends: applyExtends,
CONFIG_FILES: CONFIG_FILES,
/**
* Retrieves the configuration filename for a given directory. It loops over all
* of the valid configuration filenames in order to find the first one that exists.
* @param {string} directory The directory to check for a config file.
* @returns {?string} The filename of the configuration file for the directory
* or null if there is no configuration file in the directory.
*/
getFilenameForDirectory: function(directory) {
var filename;
for (var i = 0, len = CONFIG_FILES.length; i < len; i++) {
filename = path.join(directory, CONFIG_FILES[i]);
if (fs.existsSync(filename)) {
return filename;
}
}
return null;
}
};

241
tools/eslint/lib/config/config-initializer.js

@ -0,0 +1,241 @@
/**
* @fileoverview Config initialization wizard.
* @author Ilya Volodin
* @copyright 2015 Ilya Volodin. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var exec = require("child_process").exec,
inquirer = require("inquirer"),
ConfigFile = require("./config-file");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/* istanbul ignore next: hard to test fs function */
/**
* Create .eslintrc file in the current working directory
* @param {object} config object that contains user's answers
* @param {string} format The file format to write to.
* @param {function} callback function to call once the file is written.
* @returns {void}
*/
function writeFile(config, format, callback) {
// default is .js
var extname = ".js";
if (format === "YAML") {
extname = ".yml";
} else if (format === "JSON") {
extname = ".json";
}
try {
ConfigFile.write(config, "./.eslintrc" + extname);
console.log("Successfully created .eslintrc" + extname + " file in " + process.cwd());
} catch (e) {
callback(e);
return;
}
// install any external configs as well as any included plugins
if (config.extends && config.extends.indexOf("eslint") === -1) {
console.log("Installing additional dependencies");
exec("npm i eslint-config-" + config.extends + " --save-dev", function(err) {
if (err) {
return callback(err);
}
// TODO: consider supporting more than 1 plugin though it's required yet.
exec("npm i eslint-plugin-" + config.plugins[0] + " --save-dev", callback);
});
return;
}
// install the react plugin if it was explictly chosen
if (config.plugins && config.plugins.indexOf("react") >= 0) {
console.log("Installing React plugin");
exec("npm i eslint-plugin-react --save-dev", callback);
return;
}
callback();
}
/**
* process user's answers and create config object
* @param {object} answers answers received from inquirer
* @returns {object} config object
*/
function processAnswers(answers) {
var config = {rules: {}, env: {}, extends: "eslint:recommended"};
config.rules.indent = [2, answers.indent];
config.rules.quotes = [2, answers.quotes];
config.rules["linebreak-style"] = [2, answers.linebreak];
config.rules.semi = [2, answers.semi ? "always" : "never"];
if (answers.es6) {
config.env.es6 = true;
}
answers.env.forEach(function(env) {
config.env[env] = true;
});
if (answers.jsx) {
config.ecmaFeatures = {jsx: true};
if (answers.react) {
config.plugins = ["react"];
config.ecmaFeatures.experimentalObjectRestSpread = true;
}
}
return config;
}
/**
* process user's style guide of choice and return an appropriate config object.
* @param {string} guide name of the chosen style guide
* @returns {object} config object
*/
function getConfigForStyleGuide(guide) {
var guides = {
google: {extends: "google"},
airbnb: {extends: "airbnb", plugins: ["react"]},
standard: {extends: "standard", plugins: ["standard"]}
};
if (!guides[guide]) {
throw new Error("You referenced an unsupported guide.");
}
return guides[guide];
}
/* istanbul ignore next: no need to test inquirer*/
/**
* Ask use a few questions on command prompt
* @param {function} callback callback function when file has been written
* @returns {void}
*/
function promptUser(callback) {
inquirer.prompt([
{
type: "list",
name: "source",
message: "How would you like to configure ESLint?",
default: "prompt",
choices: [{name: "Answer questions about your style", value: "prompt"}, {name: "Use a popular style guide", value: "guide"}]
},
{
type: "list",
name: "styleguide",
message: "Which style guide do you want to follow?",
choices: [{name: "Google", value: "google"}, {name: "AirBnB", value: "airbnb"}, {name: "Standard", value: "standard"}],
when: function(answers) {
return answers.source === "guide";
}
},
{
type: "list",
name: "format",
message: "What format do you want your config file to be in?",
default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"],
when: function(answers) {
return answers.source === "guide";
}
}
], function(earlyAnswers) {
// early exit if you are using a style guide
if (earlyAnswers.source === "guide") {
writeFile(getConfigForStyleGuide(earlyAnswers.styleguide), earlyAnswers.format, callback);
return;
}
// continue with the style questions otherwise...
inquirer.prompt([
{
type: "list",
name: "indent",
message: "What style of indentation do you use?",
default: "tabs",
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
},
{
type: "list",
name: "quotes",
message: "What quotes do you use for strings?",
default: "double",
choices: [{name: "Double", value: "double"}, {name: "Single", value: "single"}]
},
{
type: "list",
name: "linebreak",
message: "What line endings do you use?",
default: "unix",
choices: [{name: "Unix", value: "unix"}, {name: "Windows", value: "windows"}]
},
{
type: "confirm",
name: "semi",
message: "Do you require semicolons?",
default: true
},
{
type: "confirm",
name: "es6",
message: "Are you using ECMAScript 6 features?",
default: false
},
{
type: "checkbox",
name: "env",
message: "Where will your code run?",
default: ["browser"],
choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}]
},
{
type: "confirm",
name: "jsx",
message: "Do you use JSX?",
default: false
},
{
type: "confirm",
name: "react",
message: "Do you use React",
default: false,
when: function(answers) {
return answers.jsx;
}
},
{
type: "list",
name: "format",
message: "What format do you want your config file to be in?",
default: "JavaScript",
choices: ["JavaScript", "YAML", "JSON"]
}
], function(answers) {
var config = processAnswers(answers);
writeFile(config, answers.format, callback);
});
});
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
var init = {
getConfigForStyleGuide: getConfigForStyleGuide,
processAnswers: processAnswers,
initializeConfig: /* istanbul ignore next */ function(callback) {
promptUser(callback);
}
};
module.exports = init;

186
tools/eslint/lib/config/config-ops.js

@ -0,0 +1,186 @@
/**
* @fileoverview Config file operations. This file must be usable in the browser,
* so no Node-specific code can be here.
* @author Nicholas C. Zakas
* @copyright 2015 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var debug = require("debug"),
environments = require("../../conf/environments"),
assign = require("object-assign");
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
debug = debug("eslint:config-ops");
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
/**
* Creates an empty configuration object suitable for merging as a base.
* @returns {Object} A configuration object.
*/
createEmptyConfig: function() {
return {
globals: {},
env: {},
rules: {},
ecmaFeatures: {}
};
},
/**
* Creates an environment config based on the specified environments.
* @param {Object<string,boolean>} env The environment settings.
* @returns {Object} A configuration object with the appropriate rules and globals
* set.
*/
createEnvironmentConfig: function(env) {
var envConfig = this.createEmptyConfig();
if (env) {
envConfig.env = env;
Object.keys(env).filter(function(name) {
return env[name];
}).forEach(function(name) {
var environment = environments[name];
if (environment) {
debug("Creating config for environment " + name);
if (environment.globals) {
assign(envConfig.globals, environment.globals);
}
if (environment.ecmaFeatures) {
assign(envConfig.ecmaFeatures, environment.ecmaFeatures);
}
}
});
}
return envConfig;
},
/**
* Given a config with environment settings, applies the globals and
* ecmaFeatures to the configuration and returns the result.
* @param {Object} config The configuration information.
* @returns {Object} The updated configuration information.
*/
applyEnvironments: function(config) {
if (config.env && typeof config.env === "object") {
debug("Apply environment settings to config");
return this.merge(this.createEnvironmentConfig(config.env), config);
}
return config;
},
/**
* Merges two config objects. This will not only add missing keys, but will also modify values to match.
* @param {Object} target config object
* @param {Object} src config object. Overrides in this config object will take priority over base.
* @param {boolean} [combine] Whether to combine arrays or not
* @param {boolean} [isRule] Whether its a rule
* @returns {Object} merged config object.
*/
merge: function deepmerge(target, src, combine, isRule) {
/*
The MIT License (MIT)
Copyright (c) 2012 Nicholas Fisher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// This code is taken from deepmerge repo (https://github.com/KyleAMathews/deepmerge) and modified to meet our needs.
var array = Array.isArray(src) || Array.isArray(target);
var dst = array && [] || {};
combine = !!combine;
isRule = !!isRule;
if (array) {
target = target || [];
if (isRule && src.length > 1) {
dst = dst.concat(src);
} else {
dst = dst.concat(target);
}
if (typeof src !== "object" && !Array.isArray(src)) {
src = [src];
}
Object.keys(src).forEach(function(e, i) {
e = src[i];
if (typeof dst[i] === "undefined") {
dst[i] = e;
} else if (typeof e === "object") {
if (isRule) {
dst[i] = e;
} else {
dst[i] = deepmerge(target[i], e, combine, isRule);
}
} else {
if (!combine) {
dst[i] = e;
} else {
if (dst.indexOf(e) === -1) {
dst.push(e);
}
}
}
});
} else {
if (target && typeof target === "object") {
Object.keys(target).forEach(function(key) {
dst[key] = target[key];
});
}
Object.keys(src).forEach(function(key) {
if (Array.isArray(src[key]) || Array.isArray(target[key])) {
dst[key] = deepmerge(target[key], src[key], key === "plugins", isRule);
} else if (typeof src[key] !== "object" || !src[key]) {
dst[key] = src[key];
} else {
if (!target[key]) {
dst[key] = src[key];
} else {
dst[key] = deepmerge(target[key], src[key], combine, key === "rules");
}
}
});
}
return dst;
}
};

163
tools/eslint/lib/config/config-validator.js

@ -0,0 +1,163 @@
/**
* @fileoverview Validates configs.
* @author Brandon Mills
* @copyright 2015 Brandon Mills
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var rules = require("../rules"),
environments = require("../../conf/environments"),
schemaValidator = require("is-my-json-valid");
var validators = {
rules: Object.create(null)
};
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Gets a complete options schema for a rule.
* @param {string} id The rule's unique name.
* @returns {object} JSON Schema for the rule's options.
*/
function getRuleOptionsSchema(id) {
var rule = rules.get(id),
schema = rule && rule.schema;
if (!schema) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
],
"minItems": 1
};
}
// Given a tuple of schemas, insert warning level at the beginning
if (Array.isArray(schema)) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
].concat(schema),
"minItems": 1,
"maxItems": schema.length + 1
};
}
// Given a full schema, leave it alone
return schema;
}
/**
* Validates a rule's options against its schema.
* @param {string} id The rule's unique name.
* @param {array|number} options The given options for the rule.
* @param {string} source The name of the configuration source.
* @returns {void}
*/
function validateRuleOptions(id, options, source) {
var validateRule = validators.rules[id],
message;
if (!validateRule) {
validateRule = schemaValidator(getRuleOptionsSchema(id), { verbose: true });
validators.rules[id] = validateRule;
}
if (typeof options === "number") {
options = [options];
}
validateRule(options);
if (validateRule.errors) {
message = [
source, ":\n",
"\tConfiguration for rule \"", id, "\" is invalid:\n"
];
validateRule.errors.forEach(function(error) {
if (error.field === "data[\"0\"]") { // better error for severity
message.push(
"\tSeverity should be one of the following: 0 = off, 1 = warning, 2 = error (you passed \"", error.value, "\").\n");
} else {
message.push(
"\tValue \"", error.value, "\" ", error.message, ".\n"
);
}
});
throw new Error(message.join(""));
}
}
/**
* Validates an environment object
* @param {object} environment The environment config object to validate.
* @param {string} source The location to report with any errors.
* @returns {void}
*/
function validateEnvironment(environment, source) {
// not having an environment is ok
if (!environment) {
return;
}
if (Array.isArray(environment)) {
throw new Error("Environment must not be an array");
}
if (typeof environment === "object") {
Object.keys(environment).forEach(function(env) {
if (!environments[env]) {
var message = [
source, ":\n",
"\tEnvironment key \"", env, "\" is unknown\n"
];
throw new Error(message.join(""));
}
});
} else {
throw new Error("Environment must be an object");
}
}
/**
* Validates an entire config object.
* @param {object} config The config object to validate.
* @param {string} source The location to report with any errors.
* @returns {void}
*/
function validate(config, source) {
if (typeof config.rules === "object") {
Object.keys(config.rules).forEach(function(id) {
validateRuleOptions(id, config.rules[id], source);
});
}
validateEnvironment(config.env, source);
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
getRuleOptionsSchema: getRuleOptionsSchema,
validate: validate,
validateRuleOptions: validateRuleOptions
};

633
tools/eslint/lib/eslint.js

@ -1,6 +1,8 @@
/**
* @fileoverview Main ESLint object.
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -8,18 +10,24 @@
// Requirements
//------------------------------------------------------------------------------
var estraverse = require("estraverse-fb"),
var estraverse = require("./util/estraverse"),
escope = require("escope"),
environments = require("../conf/environments"),
blankScriptAST = require("../conf/blank-script.json"),
assign = require("object-assign"),
rules = require("./rules"),
util = require("./util"),
RuleContext = require("./rule-context"),
timing = require("./timing"),
createTokenStore = require("./token-store.js"),
SourceCode = require("./util/source-code"),
NodeEventGenerator = require("./util/node-event-generator"),
CommentEventGenerator = require("./util/comment-event-generator"),
EventEmitter = require("events").EventEmitter,
escapeRegExp = require("escape-string-regexp"),
validator = require("./config-validator");
ConfigOps = require("./config/config-ops"),
validator = require("./config/config-validator"),
replacements = require("../conf/replacements.json"),
assert = require("assert");
var DEFAULT_PARSER = require("../conf/eslint.json").parser;
//------------------------------------------------------------------------------
// Helpers
@ -29,9 +37,10 @@ var estraverse = require("estraverse-fb"),
* Parses a list of "name:boolean_value" or/and "name" options divided by comma or
* whitespace.
* @param {string} string The string to parse.
* @param {Comment} comment The comment node which has the string.
* @returns {Object} Result map object of names and boolean values
*/
function parseBooleanConfig(string) {
function parseBooleanConfig(string, comment) {
var items = {};
// Collapse whitespace around : to make parsing easier
string = string.replace(/\s*:\s*/g, ":");
@ -48,7 +57,10 @@ function parseBooleanConfig(string) {
name = name.substring(0, pos);
}
items[name] = (value === "true");
items[name] = {
value: (value === "true"),
comment: comment
};
});
return items;
@ -66,14 +78,14 @@ function parseJsonConfig(string, location, messages) {
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
try {
items = JSON.parse("{" + string + "}");
} catch(ex) {
} catch (ex) {
messages.push({
fatal: true,
severity: 2,
message: "Failed to parse JSON from '" + string + "': " + ex.message,
line: location.start.line,
column: location.start.column
column: location.start.column + 1
});
}
@ -100,25 +112,6 @@ function parseListConfig(string) {
return items;
}
/**
* @param {Scope} scope The scope object to check.
* @param {string} name The name of the variable to look up.
* @returns {Variable} The variable object if found or null if not.
*/
function getVariable(scope, name) {
var variable = null;
scope.variables.some(function(v) {
if (v.name === name) {
variable = v;
return true;
} else {
return false;
}
});
return variable;
}
/**
* Ensures that variables representing built-in properties of the Global Object,
* and any globals declared by special block comments, are present in the global
@ -130,12 +123,13 @@ function getVariable(scope, name) {
*/
function addDeclaredGlobals(program, globalScope, config) {
var declaredGlobals = {},
exportedGlobals = {},
explicitGlobals = {},
builtin = environments.builtin;
assign(declaredGlobals, builtin);
Object.keys(config.env).forEach(function (name) {
Object.keys(config.env).forEach(function(name) {
if (config.env[name]) {
var environmentGlobals = environments[name] && environments[name].globals;
if (environmentGlobals) {
@ -144,27 +138,39 @@ function addDeclaredGlobals(program, globalScope, config) {
}
});
assign(exportedGlobals, config.exported);
assign(declaredGlobals, config.globals);
assign(explicitGlobals, config.astGlobals);
Object.keys(declaredGlobals).forEach(function(name) {
var variable = getVariable(globalScope, name);
var variable = globalScope.set.get(name);
if (!variable) {
variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = false;
globalScope.variables.push(variable);
globalScope.set.set(name, variable);
}
variable.writeable = declaredGlobals[name];
});
Object.keys(explicitGlobals).forEach(function(name) {
var variable = getVariable(globalScope, name);
var variable = globalScope.set.get(name);
if (!variable) {
variable = new escope.Variable(name, globalScope);
variable.eslintExplicitGlobal = true;
variable.eslintExplicitGlobalComment = explicitGlobals[name].comment;
globalScope.variables.push(variable);
globalScope.set.set(name, variable);
}
variable.writeable = explicitGlobals[name].value;
});
// mark all exported variables as such
Object.keys(exportedGlobals).forEach(function(name) {
var variable = globalScope.set.get(name);
if (variable) {
variable.eslintUsed = true;
}
variable.writeable = explicitGlobals[name];
});
}
@ -240,11 +246,12 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
* @param {Object} config The existing configuration data.
* @param {Object[]} reportingConfig The existing reporting configuration data.
* @param {Object[]} messages The messages queue.
* @returns {void}
* @returns {object} Modified config object
*/
function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) {
var commentConfig = {
exported: {},
astGlobals: {},
rules: {},
env: {}
@ -254,16 +261,20 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
ast.comments.forEach(function(comment) {
var value = comment.value.trim();
var match = /^(eslint-\w+|eslint-\w+-\w+|eslint|globals?)(\s|$)/.exec(value);
var match = /^(eslint-\w+|eslint-\w+-\w+|eslint|exported|globals?)(\s|$)/.exec(value);
if (match) {
value = value.substring(match.index + match[1].length);
if (comment.type === "Block") {
switch (match[1]) {
case "exported":
assign(commentConfig.exported, parseBooleanConfig(value, comment));
break;
case "globals":
case "global":
assign(commentConfig.astGlobals, parseBooleanConfig(value));
assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
break;
case "eslint-env":
@ -300,14 +311,14 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
});
// apply environment configs
Object.keys(commentConfig.env).forEach(function (name) {
Object.keys(commentConfig.env).forEach(function(name) {
if (environments[name]) {
util.mergeConfigs(commentConfig, environments[name]);
commentConfig = ConfigOps.merge(commentConfig, environments[name]);
}
});
assign(commentConfig.rules, commentRules);
util.mergeConfigs(config, commentConfig);
return ConfigOps.merge(config, commentConfig);
}
/**
@ -363,7 +374,7 @@ function prepareConfig(config) {
// merge in environment ecmaFeatures
if (typeof config.env === "object") {
Object.keys(config.env).forEach(function(env) {
if (config.env[env] && environments[env].ecmaFeatures) {
if (config.env[env] && environments[env] && environments[env].ecmaFeatures) {
assign(ecmaFeatures, environments[env].ecmaFeatures);
}
});
@ -371,11 +382,11 @@ function prepareConfig(config) {
preparedConfig = {
rules: copiedRules,
parser: config.parser || "espree",
globals: util.mergeConfigs({}, config.globals),
env: util.mergeConfigs({}, config.env || {}),
settings: util.mergeConfigs({}, config.settings || {}),
ecmaFeatures: util.mergeConfigs(ecmaFeatures, config.ecmaFeatures || {})
parser: config.parser || DEFAULT_PARSER,
globals: ConfigOps.merge({}, config.globals),
env: ConfigOps.merge({}, config.env || {}),
settings: ConfigOps.merge({}, config.settings || {}),
ecmaFeatures: ConfigOps.merge(ecmaFeatures, config.ecmaFeatures || {})
};
// can't have global return inside of modules
@ -386,6 +397,63 @@ function prepareConfig(config) {
return preparedConfig;
}
/**
* Provide a stub rule with a given message
* @param {string} message The message to be displayed for the rule
* @returns {Function} Stub rule function
*/
function createStubRule(message) {
/**
* Creates a fake rule object
* @param {object} context context object for each rule
* @returns {object} collection of node to listen on
*/
function createRuleModule(context) {
return {
Program: function(node) {
context.report(node, message);
}
};
}
if (message) {
return createRuleModule;
} else {
throw new Error("No message passed to stub rule");
}
}
/**
* Provide a rule replacement message
* @param {string} ruleId Name of the rule
* @returns {string} Message detailing rule replacement
*/
function getRuleReplacementMessage(ruleId) {
if (ruleId in replacements.rules) {
var newRules = replacements.rules[ruleId];
return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", ");
}
}
var eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
/**
* Checks whether or not there is a comment which has "eslint-env *" in a given text.
* @param {string} text - A source code text to check.
* @returns {object|null} A result of parseListConfig() with "eslint-env *" comment.
*/
function findEslintEnv(text) {
var match, retv;
eslintEnvPattern.lastIndex = 0;
while ((match = eslintEnvPattern.exec(text))) {
retv = assign(retv || {}, parseListConfig(match[1]));
}
return retv;
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -398,19 +466,14 @@ module.exports = (function() {
var api = Object.create(new EventEmitter()),
messages = [],
currentText = null,
currentTextLines = [],
currentConfig = null,
currentTokens = null,
currentScopes = null,
scopeMap = null,
scopeManager = null,
currentFilename = null,
controller = null,
reportingConfig = [],
commentLocsEnter = [],
commentLocsExit = [],
currentAST = null;
sourceCode = null;
/**
* Parses text into an AST. Moved out here because the try-catch prevents
@ -464,56 +527,16 @@ module.exports = (function() {
fatal: true,
severity: 2,
message: message,
message: "Parsing error: " + message,
line: ex.lineNumber,
column: ex.column
column: ex.column + 1
});
return null;
}
}
/**
* Check collection of comments to prevent double event for comment as
* leading and trailing, then emit event if passing
* @param {ASTNode[]} comments Collection of comment nodes
* @param {Object[]} locs List of locations of previous comment nodes
* @param {string} eventName Event name postfix
* @returns {void}
*/
function emitComments(comments, locs, eventName) {
if (comments.length) {
comments.forEach(function(node) {
if (locs.indexOf(node.loc) >= 0) {
locs.splice(locs.indexOf(node.loc), 1);
} else {
locs.push(node.loc);
api.emit(node.type + eventName, node);
}
});
}
}
/**
* Shortcut to check and emit enter of comment nodes
* @param {ASTNode[]} comments Collection of comment nodes
* @returns {void}
*/
function emitCommentsEnter(comments) {
emitComments(comments, commentLocsEnter, "Comment");
}
/**
* Shortcut to check and emit exit of comment nodes
* @param {ASTNode[]} comments Collection of comment nodes
* @returns {void}
*/
function emitCommentsExit(comments) {
emitComments(comments, commentLocsExit, "Comment:exit");
}
/**
* Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
* Returns 0 if the rule config is not valid (an Array or a number)
@ -553,103 +576,139 @@ module.exports = (function() {
api.reset = function() {
this.removeAllListeners();
messages = [];
currentAST = null;
currentConfig = null;
currentText = null;
currentTextLines = [];
currentTokens = null;
currentScopes = null;
scopeMap = null;
scopeManager = null;
controller = null;
reportingConfig = [];
commentLocsEnter = [];
commentLocsExit = [];
sourceCode = null;
};
/**
* Verifies the text against the rules specified by the second argument.
* @param {string} text The JavaScript text to verify.
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
* @param {Object} config An object whose keys specify the rules to use.
* @param {string=} filename The optional filename of the file being checked.
* If this is not set, the filename will default to '<input>' in the rule context.
* @param {boolean=} saveState Indicates if the state from the last run should be saved.
* @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
* If this is not set, the filename will default to '<input>' in the rule context. If
* an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
* @param {boolean} [saveState] Indicates if the state from the last run should be saved.
* Mostly useful for testing purposes.
* @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
* Useful if you want to validate JS without comments overriding rules.
* @returns {Object[]} The results as an array of messages or null if no messages.
*/
api.verify = function(text, config, filename, saveState) {
api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) {
var ast,
shebang,
ecmaFeatures,
ecmaVersion;
// set the current parsed filename
currentFilename = filename;
ecmaVersion,
allowInlineConfig,
text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
// evaluate arguments
if (typeof filenameOrOptions === "object") {
currentFilename = filenameOrOptions.filename;
allowInlineConfig = filenameOrOptions.allowInlineConfig;
saveState = filenameOrOptions.saveState;
} else {
currentFilename = filenameOrOptions;
}
if (!saveState) {
this.reset();
}
// there's no input, just exit here
if (text.trim().length === 0) {
currentText = text;
return messages;
// search and apply "eslint-env *".
var envInFile = findEslintEnv(text || textOrSourceCode.text);
if (envInFile) {
if (!config || !config.env) {
config = assign({}, config || {}, {env: envInFile});
} else {
config = assign({}, config);
config.env = assign({}, config.env, envInFile);
}
}
// process initial config to make it safe to extend
config = prepareConfig(config || {});
ast = parse(text.replace(/^#!([^\r\n]+)/, function(match, captured) {
shebang = captured;
return "//" + captured;
}), config);
// only do this for text
if (text !== null) {
// there's no input, just exit here
if (text.trim().length === 0) {
sourceCode = new SourceCode(text, blankScriptAST);
return messages;
}
ast = parse(text.replace(/^#!([^\r\n]+)/, function(match, captured) {
shebang = captured;
return "//" + captured;
}), config);
if (ast) {
sourceCode = new SourceCode(text, ast);
}
} else {
sourceCode = textOrSourceCode;
ast = sourceCode.ast;
}
// if espree failed to parse the file, there's no sense in setting up rules
if (ast) {
currentAST = ast;
// parse global comments and modify config
modifyConfigsFromComments(filename, ast, config, reportingConfig, messages);
if (allowInlineConfig !== false) {
config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
}
// enable appropriate rules
Object.keys(config.rules).filter(function(key) {
return getRuleSeverity(config.rules[key]) > 0;
}).forEach(function(key) {
var ruleCreator = rules.get(key),
severity = getRuleSeverity(config.rules[key]),
options = getRuleOptions(config.rules[key]),
var ruleCreator,
severity,
options,
rule;
if (ruleCreator) {
try {
rule = ruleCreator(new RuleContext(
key, api, severity, options,
config.settings, config.ecmaFeatures
));
// add all the node types as listeners
Object.keys(rule).forEach(function(nodeType) {
api.on(nodeType, timing.enabled
? timing.time(key, rule[nodeType])
: rule[nodeType]
);
});
} catch(ex) {
ex.message = "Error while loading rule '" + key + "': " + ex.message;
throw ex;
ruleCreator = rules.get(key);
if (!ruleCreator) {
var replacementMsg = getRuleReplacementMessage(key);
if (replacementMsg) {
ruleCreator = createStubRule(replacementMsg);
} else {
ruleCreator = createStubRule("Definition for rule '" + key + "' was not found");
}
rules.define(key, ruleCreator);
}
} else {
throw new Error("Definition for rule '" + key + "' was not found.");
severity = getRuleSeverity(config.rules[key]);
options = getRuleOptions(config.rules[key]);
try {
rule = ruleCreator(new RuleContext(
key, api, severity, options,
config.settings, config.ecmaFeatures
));
// add all the node types as listeners
Object.keys(rule).forEach(function(nodeType) {
api.on(nodeType, timing.enabled
? timing.time(key, rule[nodeType])
: rule[nodeType]
);
});
} catch (ex) {
ex.message = "Error while loading rule '" + key + "': " + ex.message;
throw ex;
}
});
// save config so rules can access as necessary
currentConfig = config;
currentText = text;
controller = new estraverse.Controller();
ecmaFeatures = currentConfig.ecmaFeatures;
@ -672,7 +731,7 @@ module.exports = (function() {
* lookup in getScope.
*/
scopeMap = [];
currentScopes.forEach(function (scope, index) {
currentScopes.forEach(function(scope, index) {
var range = scope.block.range[0];
// Sometimes two scopes are returned for a given node. This is
@ -682,21 +741,6 @@ module.exports = (function() {
}
});
/*
* Split text here into array of lines so
* it's not being done repeatedly
* by individual rules.
*/
currentTextLines = currentText.split(/\r\n|\r|\n|\u2028|\u2029/g);
// Freezing so array isn't accidentally changed by a rule.
Object.freeze(currentTextLines);
currentTokens = createTokenStore(ast.tokens);
Object.keys(currentTokens).forEach(function(method) {
api[method] = currentTokens[method];
});
// augment global scope with declared global variables
addDeclaredGlobals(ast, currentScopes[0], currentConfig);
@ -709,6 +753,9 @@ module.exports = (function() {
}
}
var eventGenerator = new NodeEventGenerator(api);
eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
/*
* Each node has a type property. Whenever a particular type of node is found,
* an event is fired. This allows any listeners to automatically be informed
@ -716,21 +763,11 @@ module.exports = (function() {
*/
controller.traverse(ast, {
enter: function(node, parent) {
var comments = api.getComments(node);
emitCommentsEnter(comments.leading);
node.parent = parent;
api.emit(node.type, node);
emitCommentsEnter(comments.trailing);
eventGenerator.enterNode(node);
},
leave: function(node) {
var comments = api.getComments(node);
emitCommentsExit(comments.trailing);
api.emit(node.type + ":exit", node);
emitCommentsExit(comments.leading);
eventGenerator.leaveNode(node);
}
});
@ -761,167 +798,98 @@ module.exports = (function() {
* @param {string} message The actual message.
* @param {Object} opts Optional template data which produces a formatted message
* with symbols being replaced by this object's values.
* @param {Object} fix A fix command description.
* @returns {void}
*/
api.report = function(ruleId, severity, node, location, message, opts) {
api.report = function(ruleId, severity, node, location, message, opts, fix) {
if (node) {
assert.strictEqual(typeof node, "object", "Node must be an object");
}
if (typeof location === "string") {
assert.ok(node, "Node must be provided when reporting error if location is not provided");
fix = opts;
opts = message;
message = location;
location = node.loc.start;
}
Object.keys(opts || {}).forEach(function (key) {
var rx = new RegExp(escapeRegExp("{{" + key + "}}"), "g");
message = message.replace(rx, opts[key]);
});
// else, assume location was provided, so node may be omitted
if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
return;
}
messages.push({
if (opts) {
message = message.replace(/\{\{\s*(.+?)\s*\}\}/g, function(fullMatch, term) {
if (term in opts) {
return opts[term];
}
// Preserve old behavior: If parameter name not provided, don't replace it.
return fullMatch;
});
}
var problem = {
ruleId: ruleId,
severity: severity,
message: message,
line: location.line,
column: location.column,
nodeType: node.type,
source: currentTextLines[location.line - 1] || ""
});
};
column: location.column + 1, // switch to 1-base instead of 0-base
nodeType: node && node.type,
source: sourceCode.lines[location.line - 1] || ""
};
/**
* Gets the source code for the given node.
* @param {ASTNode=} node The AST node to get the text for.
* @param {int=} beforeCount The number of characters before the node to retrieve.
* @param {int=} afterCount The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
*/
api.getSource = function(node, beforeCount, afterCount) {
if (node) {
return (currentText !== null) ? currentText.slice(Math.max(node.range[0] - (beforeCount || 0), 0),
node.range[1] + (afterCount || 0)) : null;
} else {
return currentText;
// ensure there's range and text properties, otherwise it's not a valid fix
if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {
problem.fix = fix;
}
messages.push(problem);
};
/**
* Gets the entire source text split into an array of lines.
* @returns {Array} The source text as an array of lines.
* Gets the SourceCode object representing the parsed source.
* @returns {SourceCode} The SourceCode object.
*/
api.getSourceLines = function() {
return currentTextLines;
api.getSourceCode = function() {
return sourceCode;
};
/**
* Retrieves an array containing all comments in the source code.
* @returns {ASTNode[]} An array of comment nodes.
*/
api.getAllComments = function() {
return currentAST.comments;
// methods that exist on SourceCode object
var externalMethods = {
getSource: "getText",
getSourceLines: "getLines",
getAllComments: "getAllComments",
getNodeByRangeIndex: "getNodeByRangeIndex",
getComments: "getComments",
getJSDocComment: "getJSDocComment",
getFirstToken: "getFirstToken",
getFirstTokens: "getFirstTokens",
getLastToken: "getLastToken",
getLastTokens: "getLastTokens",
getTokenAfter: "getTokenAfter",
getTokenBefore: "getTokenBefore",
getTokenByRangeStart: "getTokenByRangeStart",
getTokens: "getTokens",
getTokensAfter: "getTokensAfter",
getTokensBefore: "getTokensBefore",
getTokensBetween: "getTokensBetween"
};
/**
* Gets all comments for the given node.
* @param {ASTNode} node The AST node to get the comments for.
* @returns {Object} The list of comments indexed by their position.
*/
api.getComments = function(node) {
var leadingComments = node.leadingComments || [],
trailingComments = node.trailingComments || [];
/*
* espree adds a "comments" array on Program nodes rather than
* leadingComments/trailingComments. Comments are only left in the
* Program node comments array if there is no executable code.
*/
if (node.type === "Program") {
if (node.body.length === 0) {
leadingComments = node.comments;
}
}
return {
leading: leadingComments,
trailing: trailingComments
};
};
/**
* Retrieves the JSDoc comment for a given node.
* @param {ASTNode} node The AST node to get the comment for.
* @returns {ASTNode} The BlockComment node containing the JSDoc for the
* given node or null if not found.
*/
api.getJSDocComment = function(node) {
var parent = node.parent,
line = node.loc.start.line;
/**
* Finds a JSDoc comment node in an array of comment nodes.
* @param {ASTNode[]} comments The array of comment nodes to search.
* @returns {ASTNode} The node if found, null if not.
* @private
*/
function findJSDocComment(comments) {
if (comments) {
for (var i = comments.length - 1; i >= 0; i--) {
if (comments[i].type === "Block" && comments[i].value.charAt(0) === "*") {
// copy over methods
Object.keys(externalMethods).forEach(function(methodName) {
var exMethodName = externalMethods[methodName];
if (line - comments[i].loc.end.line <= 1) {
return comments[i];
} else {
break;
}
}
}
// All functions expected to have less arguments than 5.
api[methodName] = function(a, b, c, d, e) {
if (sourceCode) {
return sourceCode[exMethodName](a, b, c, d, e);
}
return null;
}
/**
* Check to see if its a ES6 export declaration
* @param {ASTNode} astNode - any node
* @returns {boolean} whether the given node represents a export declaration
*/
function looksLikeExport(astNode) {
return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" ||
astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier";
}
switch (node.type) {
case "FunctionDeclaration":
if (looksLikeExport(parent)) {
return findJSDocComment(parent.leadingComments);
} else {
return findJSDocComment(node.leadingComments);
}
break;
case "ArrowFunctionExpression":
case "FunctionExpression":
if (parent.type !== "CallExpression" || parent.callee !== node) {
while (parent && !parent.leadingComments && !/Function/.test(parent.type)) {
parent = parent.parent;
}
return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parent.leadingComments) : null;
}
// falls through
default:
return null;
}
};
};
});
/**
* Gets nodes that are ancestors of current node.
@ -931,32 +899,6 @@ module.exports = (function() {
return controller.parents();
};
/**
* Gets the deepest node containing a range index.
* @param {int} index Range index of the desired node.
* @returns {ASTNode} [description]
*/
api.getNodeByRangeIndex = function(index) {
var result = null;
estraverse.traverse(controller.root, {
enter: function (node) {
if (node.range[0] <= index && index < node.range[1]) {
result = node;
} else {
this.skip();
}
},
leave: function (node) {
if (node === result) {
this.break();
}
}
});
return result;
};
/**
* Gets the scope for the current node.
* @returns {Object} An object representing the current node's scope.
@ -968,17 +910,23 @@ module.exports = (function() {
// Don't do this for Program nodes - they have no parents
if (parents.length) {
// if current node is function declaration, add it to the list
// if current node introduces a scope, add it to the list
var current = controller.current();
if (["FunctionDeclaration", "FunctionExpression",
"ArrowFunctionExpression", "SwitchStatement"].indexOf(current.type) >= 0) {
parents.push(current);
if (currentConfig.ecmaFeatures.blockBindings) {
if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
parents.push(current);
}
} else {
if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
parents.push(current);
}
}
// Ascend the current node's parents
for (var i = parents.length - 1; i >= 0; --i) {
scope = scopeManager.acquire(parents[i]);
// Get the innermost scope
scope = scopeManager.acquire(parents[i], true);
if (scope) {
if (scope.type === "function-expression-name") {
return scope.childScopes[0];
@ -1067,6 +1015,29 @@ module.exports = (function() {
return require("../conf/eslint.json");
};
/**
* Gets variables that are declared by a specified node.
*
* The variables are its `defs[].node` or `defs[].parent` is same as the specified node.
* Specifically, below:
*
* - `VariableDeclaration` - variables of its all declarators.
* - `VariableDeclarator` - variables.
* - `FunctionDeclaration`/`FunctionExpression` - its function name and parameters.
* - `ArrowFunctionExpression` - its parameters.
* - `ClassDeclaration`/`ClassExpression` - its class name.
* - `CatchClause` - variables of its exception.
* - `ImportDeclaration` - variables of its all specifiers.
* - `ImportSpecifier`/`ImportDefaultSpecifier`/`ImportNamespaceSpecifier` - a variable.
* - others - always an empty array.
*
* @param {ASTNode} node A node to get.
* @returns {escope.Variable[]} Variables that are declared by the node.
*/
api.getDeclaredVariables = function(node) {
return (scopeManager && scopeManager.getDeclaredVariables(node)) || [];
};
return api;
}());

53
tools/eslint/lib/file-finder.js

@ -2,6 +2,8 @@
* @fileoverview Util class to find config files.
* @author Aliaksei Shytkin
* @copyright 2014 Michael McLaughlin. All rights reserved.
* @copyright 2014 Aliaksei Shytkin. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -54,13 +56,14 @@ function FileFinder() {
* @param {string} directory The directory to start the search from.
* @returns {string} Path of the file found, or an empty string if not found.
*/
FileFinder.prototype.findInDirectoryOrParents = function (directory) {
FileFinder.prototype.findInDirectoryOrParents = function(directory) {
var cache = this.cache,
child,
dirs,
filePath,
i,
name,
names,
searched;
if (!directory) {
@ -74,20 +77,26 @@ FileFinder.prototype.findInDirectoryOrParents = function (directory) {
dirs = [];
searched = 0;
name = this.fileNames[0];
names = Array.isArray(name) ? name : [name];
while (directory !== child) {
dirs[searched++] = directory;
(function() {
while (directory !== child) {
dirs[searched++] = directory;
if (getDirectoryEntries(directory).indexOf(name) !== -1 && fs.statSync(path.resolve(directory, name)).isFile()) {
filePath = path.resolve(directory, name);
break;
}
for (var k = 0, found = false; k < names.length && !found; k++) {
child = directory;
if (getDirectoryEntries(directory).indexOf(names[k]) !== -1 && fs.statSync(path.resolve(directory, names[k])).isFile()) {
filePath = path.resolve(directory, names[k]);
return;
}
}
// Assign parent directory to directory.
directory = path.dirname(directory);
}
child = directory;
// Assign parent directory to directory.
directory = path.dirname(directory);
}
}());
for (i = 0; i < searched; i++) {
cache[dirs[i]] = filePath;
@ -105,7 +114,7 @@ FileFinder.prototype.findInDirectoryOrParents = function (directory) {
* @param {string} directory The directory to start the search from.
* @returns {string[]} The file paths found.
*/
FileFinder.prototype.findAllInDirectoryAndParents = function (directory) {
FileFinder.prototype.findAllInDirectoryAndParents = function(directory) {
var cache = this.cache,
child,
dirs,
@ -137,14 +146,24 @@ FileFinder.prototype.findAllInDirectoryAndParents = function (directory) {
for (i = 0; i < fileNamesCount; i++) {
name = fileNames[i];
if (getDirectoryEntries(directory).indexOf(name) !== -1 && fs.statSync(path.resolve(directory, name)).isFile()) {
filePath = path.resolve(directory, name);
// convert to an array for easier handling
if (!Array.isArray(name)) {
name = [name];
}
// Add the file path to the cache of each directory searched.
for (j = 0; j < searched; j++) {
cache[dirs[j]].push(filePath);
for (var k = 0, found = false; k < name.length && !found; k++) {
if (getDirectoryEntries(directory).indexOf(name[k]) !== -1 && fs.statSync(path.resolve(directory, name[k])).isFile()) {
filePath = path.resolve(directory, name[k]);
found = true;
// Add the file path to the cache of each directory searched.
for (j = 0; j < searched; j++) {
cache[dirs[j]].push(filePath);
}
}
}
}
child = directory;

15
tools/eslint/lib/formatters/checkstyle.js

@ -8,6 +8,12 @@
// Helper Functions
//------------------------------------------------------------------------------
/**
* Returns the severity of warning or error
* @param {object} message message object to examine
* @returns {string} severity level
* @private
*/
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "error";
@ -16,6 +22,12 @@ function getMessageType(message) {
}
}
/**
* Returns the escaped value for a character
* @param {string} s string to examine
* @returns {string} severity level
* @private
*/
function xmlEscape(s) {
return ("" + s).replace(/[<>&"']/g, function(c) {
switch (c) {
@ -55,7 +67,8 @@ module.exports = function(results) {
"column=\"" + xmlEscape(message.column) + "\" " +
"severity=\"" + xmlEscape(getMessageType(message)) + "\" " +
"message=\"" + xmlEscape(message.message) +
(message.ruleId ? " (" + message.ruleId + ")" : "") + "\" />";
(message.ruleId ? " (" + message.ruleId + ")" : "") + "\" " +
"source=\"" + (message.ruleId ? xmlEscape("eslint.rules." + message.ruleId) : "") + "\" />";
});
output += "</file>";

6
tools/eslint/lib/formatters/compact.js

@ -8,6 +8,12 @@
// Helper Functions
//------------------------------------------------------------------------------
/**
* Returns the severity of warning or error
* @param {object} message message object to examine
* @returns {string} severity level
* @private
*/
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";

130
tools/eslint/lib/formatters/html-template.html

@ -0,0 +1,130 @@
<!DOCTYPE html>
<head>
<title>ESLint Report</title>
<style>
body {
font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size:16px;
font-weight:normal;
margin:0;
padding:0;
color:#333
}
#overview {
padding:20px 30px
}
td, th {
padding:5px 10px
}
h1 {
margin:0
}
table {
margin:30px;
width:calc(100% - 60px);
max-width:1000px;
border-radius:5px;
border:1px solid #ddd;
border-spacing:0px;
}
th {
font-weight:400;
font-size:normal;
text-align:left;
cursor:pointer
}
td.clr-1, td.clr-2, th span {
font-weight:700
}
th span {
float:right;
margin-left:20px
}
th span:after {
content:"";
clear:both;
display:block
}
tr:last-child td {
border-bottom:none
}
tr td:first-child, tr td:last-child {
color:#9da0a4
}
#overview.bg-0, tr.bg-0 th {
color:#468847;
background:#dff0d8;
border-bottom:1px solid #d6e9c6
}
#overview.bg-1, tr.bg-1 th {
color:#f0ad4e;
background:#fcf8e3;
border-bottom:1px solid #fbeed5
}
#overview.bg-2, tr.bg-2 th {
color:#b94a48;
background:#f2dede;
border-bottom:1px solid #eed3d7
}
td {
border-bottom:1px solid #ddd
}
td.clr-1 {
color:#f0ad4e
}
td.clr-2 {
color:#b94a48
}
td a {
color:#3a33d1;
text-decoration:none
}
td a:hover {
color:#272296;
text-decoration:underline
}
</style>
</head>
<body>
<div id="overview" class="bg-{{getColor totalErrors totalWarnings}}">
<h1>ESLint Report</h1>
<div>
<span>{{renderText totalErrors totalWarnings}}</span> - Generated on {{date}}
</div>
</div>
<table>
<tbody>
{{#each results}}
<tr class="bg-{{getColor this.errorCount this.warningCount}}" data-group="f-{{@index}}">
<th colspan="4">
[+] {{this.filePath}}
<span>{{renderText this.errorCount this.warningCount}}</span>
</th>
</tr>
{{#each this.messages}}
<tr class="f-{{@../index}}" style="display:none">
<td>{{#if this.line}}{{this.line}}{{else}}0{{/if}}:{{#if this.column}}{{this.column}}{{else}}0{{/if}}</td>
{{getSeverity this.severity}}
<td>{{this.message}}</td>
<td>
<a href="http://eslint.org/docs/rules/{{this.ruleId}}" target="_blank">{{this.ruleId}}</a>
</td>
</tr>
{{/each}}
{{/each}}
</tbody>
</table>
<script type="text/javascript">
var groups = document.querySelectorAll("tr[data-group]");
for (i = 0; i < groups.length; i++) {
groups[i].addEventListener("click", function() {
var inGroup = document.getElementsByClassName(this.getAttribute("data-group"));
this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+");
for (var j = 0; j < inGroup.length; j++) {
inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row";
}
});
}
</script>
</body>
</html>

88
tools/eslint/lib/formatters/html.js

@ -0,0 +1,88 @@
/**
* @fileoverview HTML reporter
* @author Julian Laval
* @copyright 2015 Julian Laval. All rights reserved.
*/
"use strict";
var handlebars = require("handlebars").create();
var fs = require("fs");
var path = require("path");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Given a word and a count, append an s if count is not one.
* @param {string} word A word in its singular form.
* @param {int} count A number controlling whether word should be pluralized.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : word + "s");
}
/**
* Renders text along the template of x problems (x errors, x warnings)
* @param {string} totalErrors Total errors
* @param {string} totalWarnings Total warnings
* @returns {string} The formatted string, pluralized where necessary
*/
handlebars.registerHelper("renderText", function(totalErrors, totalWarnings) {
var totalProblems = totalErrors + totalWarnings;
var renderedText = totalProblems + " " + pluralize("problem", totalProblems);
if (totalProblems !== 0) {
renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")";
}
return renderedText;
});
/**
* Get the color based on whether there are errors/warnings...
* @param {string} totalErrors Total errors
* @param {string} totalWarnings Total warnings
* @returns {int} The color code (0 = green, 1 = yellow, 2 = red)
*/
handlebars.registerHelper("getColor", function(totalErrors, totalWarnings) {
if (totalErrors !== 0) {
return 2;
} else if (totalWarnings !== 0) {
return 1;
}
return 0;
});
/**
* Get the HTML row content based on the severity of the message
* @param {int} severity Severity of the message
* @returns {string} The generated HTML row
*/
handlebars.registerHelper("getSeverity", function(severity) {
// Return warning else error
return new handlebars.SafeString((severity === 1) ? "<td class=\"clr-1\">Warning</td>" : "<td class=\"clr-2\">Error</td>");
});
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(results) {
var template = fs.readFileSync(path.join(__dirname, "html-template.html"), "utf-8");
var data = {
date: new Date(),
totalErrors: 0,
totalWarnings: 0,
results: results
};
// Iterate over results to get totals
results.forEach(function(result) {
data.totalErrors += result.errorCount;
data.totalWarnings += result.warningCount;
});
return handlebars.compile(template)(data);
};

14
tools/eslint/lib/formatters/json.js

@ -0,0 +1,14 @@
/**
* @fileoverview JSON reporter
* @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(results) {
return JSON.stringify(results);
};

6
tools/eslint/lib/formatters/junit.js

@ -10,6 +10,12 @@ var xmlescape = require("xml-escape");
// Helper Functions
//------------------------------------------------------------------------------
/**
* Returns the severity of warning or error
* @param {object} message message object to examine
* @returns {string} severity level
* @private
*/
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";

59
tools/eslint/lib/formatters/unix.js

@ -0,0 +1,59 @@
/**
* @fileoverview unix-style formatter.
* @author oshi-shinobu
* @copyright 2015 oshi-shinobu. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Helper Functions
//------------------------------------------------------------------------------
/**
* Returns a canonical error level string based upon the error message passed in.
* @param {object} message Individual error message provided by eslint
* @returns {String} Error level string
*/
function getMessageType(message) {
if (message.fatal || message.severity === 2) {
return "Error";
} else {
return "Warning";
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function(results) {
var output = "",
total = 0;
results.forEach(function(result) {
var messages = result.messages;
total += messages.length;
messages.forEach(function(message) {
output += result.filePath + ":";
output += (message.line || 0) + ":";
output += (message.column || 0) + ":";
output += " " + message.message + " ";
output += "[" + getMessageType(message) +
(message.ruleId ? "/" + message.ruleId : "") + "]";
output += "\n";
});
});
if (total > 0) {
output += "\n" + total + " problem" + (total !== 1 ? "s" : "");
}
return output;
};

12
tools/eslint/lib/ignored-paths.js

@ -35,6 +35,12 @@ var ESLINT_IGNORE_FILENAME = ".eslintignore";
function loadIgnoreFile(filepath) {
var ignorePatterns = [];
/**
* Check if string is not empty
* @param {string} line string to examine
* @returns {boolean} True is its not empty
* @private
*/
function nonEmpty(line) {
return line.trim() !== "" && line[0] !== "#";
}
@ -85,7 +91,7 @@ function IgnoredPaths(patterns) {
* @param {Object} options object containing 'ignore' and 'ignorePath' properties
* @returns {IgnoredPaths} object, with patterns loaded from the ignore file
*/
IgnoredPaths.load = function (options) {
IgnoredPaths.load = function(options) {
var patterns;
options = options || {};
@ -97,7 +103,7 @@ IgnoredPaths.load = function (options) {
}
if (options.ignorePattern) {
patterns.push(options.ignorePattern);
patterns = patterns.concat(options.ignorePattern);
}
return new IgnoredPaths(patterns);
@ -108,7 +114,7 @@ IgnoredPaths.load = function (options) {
* @param {string} filepath Path to check
* @returns {boolean} true if the file path matches one or more patterns, false otherwise
*/
IgnoredPaths.prototype.contains = function (filepath) {
IgnoredPaths.prototype.contains = function(filepath) {
if (this.patterns === null) {
throw new Error("No ignore patterns loaded, call 'load' first");
}

2
tools/eslint/lib/load-rules.js

@ -33,7 +33,7 @@ module.exports = function(rulesDir) {
if (path.extname(file) !== ".js") {
return;
}
rules[file.slice(0, -3)] = require(path.join(rulesDir, file));
rules[file.slice(0, -3)] = path.join(rulesDir, file);
});
return rules;
};

25
tools/eslint/lib/logging.js

@ -0,0 +1,25 @@
/**
* @fileoverview Handle logging for Eslint
* @author Gyandeep Singh
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
"use strict";
/* istanbul ignore next */
module.exports = {
/**
* Cover for console.log
* @returns {void}
*/
info: function() {
console.log.apply(console, Array.prototype.slice.call(arguments));
},
/**
* Cover for console.error
* @returns {void}
*/
error: function() {
console.error.apply(console, Array.prototype.slice.call(arguments));
}
};

297
tools/eslint/lib/options.js

@ -1,6 +1,7 @@
/**
* @fileoverview Options configuration for optionator.
* @author George Zahariev
* See LICENSE in root directory for full license.
*/
"use strict";
@ -19,114 +20,190 @@ module.exports = optionator({
prepend: "eslint [options] file.js [file.js] [dir]",
concatRepeatedArrays: true,
mergeRepeatedObjects: true,
options: [{
heading: "Options"
}, {
option: "help",
alias: "h",
type: "Boolean",
description: "Show help"
}, {
option: "config",
alias: "c",
type: "path::String",
description: "Use configuration from this file"
}, {
option: "rulesdir",
type: "[path::String]",
description: "Use additional rules from this directory"
}, {
option: "format",
alias: "f",
type: "String",
default: "stylish",
description: "Use a specific output format"
}, {
option: "version",
alias: "v",
type: "Boolean",
description: "Outputs the version number"
}, {
option: "reset",
type: "Boolean",
default: "false",
description: "Set all default rules to off"
}, {
option: "eslintrc",
type: "Boolean",
default: "true",
description: "Disable use of configuration from .eslintrc"
}, {
option: "env",
type: "[String]",
description: "Specify environments"
}, {
option: "ext",
type: "[String]",
default: ".js",
description: "Specify JavaScript file extensions"
}, {
option: "plugin",
type: "[String]",
description: "Specify plugins"
}, {
option: "global",
type: "[String]",
description: "Define global variables"
}, {
option: "rule",
type: "Object",
description: "Specify rules"
},
{
option: "ignore-path",
type: "path::String",
description: "Specify path of ignore file"
},
{
option: "ignore",
type: "Boolean",
default: "true",
description: "Disable use of .eslintignore"
},
{
option: "ignore-pattern",
type: "String",
description: "Pattern of files to ignore (in addition to those in .eslintignore)"
},
{
option: "color",
type: "Boolean",
default: "true",
description: "Disable color in piped output"
},
{
option: "output-file",
alias: "o",
type: "path::String",
description: "Specify file to write report to"
},
{
option: "quiet",
type: "Boolean",
default: "false",
description: "Report errors only"
},
{
option: "stdin",
type: "Boolean",
default: "false",
description: "Lint code provided on <STDIN>"
},
{
option: "stdin-filename",
type: "String",
description: "Specify filename to process STDIN as"
},
{
option: "init",
type: "Boolean",
default: "false",
description: "Run config initialization wizard"
}]
options: [
{
heading: "Basic configuration"
},
{
option: "config",
alias: "c",
type: "path::String",
description: "Use configuration from this file or shareable config"
},
{
option: "eslintrc",
type: "Boolean",
default: "true",
description: "Disable use of configuration from .eslintrc"
},
{
option: "env",
type: "[String]",
description: "Specify environments"
},
{
option: "ext",
type: "[String]",
default: ".js",
description: "Specify JavaScript file extensions"
},
{
option: "global",
type: "[String]",
description: "Define global variables"
},
{
option: "parser",
type: "String",
default: "espree",
description: "Specify the parser to be used"
},
{
heading: "Caching"
},
{
option: "cache",
type: "Boolean",
default: "false",
description: "Only check changed files"
},
{
option: "cache-file",
type: "path::String",
default: ".eslintcache",
description: "Path to the cache file. Deprecated: use --cache-location"
},
{
option: "cache-location",
type: "path::String",
description: "Path to the cache file or directory"
},
{
heading: "Specifying rules and plugins"
},
{
option: "rulesdir",
type: "[path::String]",
description: "Use additional rules from this directory"
},
{
option: "plugin",
type: "[String]",
description: "Specify plugins"
},
{
option: "rule",
type: "Object",
description: "Specify rules"
},
{
heading: "Ignoring files"
},
{
option: "ignore-path",
type: "path::String",
description: "Specify path of ignore file"
},
{
option: "ignore",
type: "Boolean",
default: "true",
description: "Disable use of .eslintignore"
},
{
option: "ignore-pattern",
type: "[String]",
description: "Pattern of files to ignore (in addition to those in .eslintignore)"
},
{
heading: "Using stdin"
},
{
option: "stdin",
type: "Boolean",
default: "false",
description: "Lint code provided on <STDIN>"
},
{
option: "stdin-filename",
type: "String",
description: "Specify filename to process STDIN as"
},
{
heading: "Handling warnings"
},
{
option: "quiet",
type: "Boolean",
default: "false",
description: "Report errors only"
},
{
option: "max-warnings",
type: "Number",
default: "-1",
description: "Number of warnings to trigger nonzero exit code"
},
{
heading: "Output"
},
{
option: "output-file",
alias: "o",
type: "path::String",
description: "Specify file to write report to"
},
{
option: "format",
alias: "f",
type: "String",
default: "stylish",
description: "Use a specific output format"
},
{
option: "color",
type: "Boolean",
default: "true",
description: "Disable color in piped output"
},
{
heading: "Miscellaneous"
},
{
option: "init",
type: "Boolean",
default: "false",
description: "Run config initialization wizard"
},
{
option: "fix",
type: "Boolean",
default: false,
description: "Automatically fix problems"
},
{
option: "debug",
type: "Boolean",
default: false,
description: "Output debugging information"
},
{
option: "help",
alias: "h",
type: "Boolean",
description: "Show help"
},
{
option: "version",
alias: "v",
type: "Boolean",
description: "Outputs the version number"
},
{
option: "inline-config",
type: "Boolean",
default: "true",
description: "Allow comments to change eslint config/rules"
}
]
});

108
tools/eslint/lib/rule-context.js

@ -1,37 +1,61 @@
/**
* @fileoverview RuleContext utility for rules
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var RuleFixer = require("./util/rule-fixer");
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
var PASSTHROUGHS = [
"getAllComments",
"getAncestors",
"getComments",
"getFilename",
"getFirstToken",
"getFirstTokens",
"getJSDocComment",
"getLastToken",
"getLastTokens",
"getNodeByRangeIndex",
"getScope",
"getSource",
"getSourceLines",
"getTokenAfter",
"getTokenBefore",
"getTokenByRangeStart",
"getTokens",
"getTokensAfter",
"getTokensBefore",
"getTokensBetween",
"markVariableAsUsed",
"isMarkedAsUsed"
];
"getAllComments",
"getAncestors",
"getComments",
"getDeclaredVariables",
"getFilename",
"getFirstToken",
"getFirstTokens",
"getJSDocComment",
"getLastToken",
"getLastTokens",
"getNodeByRangeIndex",
"getScope",
"getSource",
"getSourceLines",
"getTokenAfter",
"getTokenBefore",
"getTokenByRangeStart",
"getTokens",
"getTokensAfter",
"getTokensBefore",
"getTokensBetween",
"markVariableAsUsed",
"isMarkedAsUsed"
];
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/**
* An error message description
* @typedef {Object} MessageDescriptor
* @property {string} nodeType The type of node.
* @property {Location} loc The location of the problem.
* @property {string} message The problem message.
* @property {Object} [data] Optional data to use to fill in placeholders in the
* message.
* @property {Function} fix The function to call that creates a fix command.
*/
//------------------------------------------------------------------------------
// Rule Definition
@ -87,15 +111,47 @@ function RuleContext(ruleId, eslint, severity, options, settings, ecmaFeatures)
/**
* Passthrough to eslint.report() that automatically assigns the rule ID and severity.
* @param {ASTNode} node The AST node related to the message.
* @param {ASTNode|MessageDescriptor} nodeOrDescriptor The AST node related to the message or a message
* descriptor.
* @param {Object=} location The location of the error.
* @param {string} message The message to display to the user.
* @param {Object} opts Optional template data which produces a formatted message
* with symbols being replaced by this object's values.
* @returns {void}
*/
this.report = function(node, location, message, opts) {
eslint.report(ruleId, severity, node, location, message, opts);
this.report = function(nodeOrDescriptor, location, message, opts) {
var descriptor,
fix = null;
// check to see if it's a new style call
if (arguments.length === 1) {
descriptor = nodeOrDescriptor;
// if there's a fix specified, get it
if (typeof descriptor.fix === "function") {
fix = descriptor.fix(new RuleFixer());
}
eslint.report(
ruleId, severity, descriptor.node,
descriptor.loc || descriptor.node.loc.start,
descriptor.message, descriptor.data, fix
);
return;
}
// old style call
eslint.report(ruleId, severity, nodeOrDescriptor, location, message, opts);
};
/**
* Passthrough to eslint.getSourceCode().
* @returns {SourceCode} The SourceCode object for the code.
*/
this.getSourceCode = function() {
return eslint.getSourceCode();
};
}

30
tools/eslint/lib/rules.js

@ -31,8 +31,6 @@ function define(ruleId, ruleModule) {
rules[ruleId] = ruleModule;
}
exports.define = define;
/**
* Loads and registers all rules from passed rules directory.
* @param {String} [rulesDir] Path to rules directory, may be relative. Defaults to `lib/rules`.
@ -45,39 +43,49 @@ function load(rulesDir) {
});
}
exports.load = load;
/**
* Registers all given rules of a plugin.
* @param {Object} pluginRules A key/value map of rule definitions.
* @param {String} pluginName The name of the plugin without prefix (`eslint-plugin-`).
* @returns {void}
*/
exports.import = function (pluginRules, pluginName) {
Object.keys(pluginRules).forEach(function (ruleId) {
function importPlugin(pluginRules, pluginName) {
Object.keys(pluginRules).forEach(function(ruleId) {
var qualifiedRuleId = pluginName + "/" + ruleId,
rule = pluginRules[ruleId];
define(qualifiedRuleId, rule);
});
};
}
/**
* Access rule handler by id (file name).
* @param {String} ruleId Rule id (file name).
* @returns {Function} Rule handler.
*/
exports.get = function(ruleId) {
return rules[ruleId];
};
function get(ruleId) {
if (typeof rules[ruleId] === "string") {
return require(rules[ruleId]);
} else {
return rules[ruleId];
}
}
/**
* Reset rules storage.
* Should be used only in tests.
* @returns {void}
*/
exports.testClear = function() {
function testClear() {
rules = Object.create(null);
}
module.exports = {
define: define,
load: load,
import: importPlugin,
get: get,
testClear: testClear
};
//------------------------------------------------------------------------------

88
tools/eslint/lib/rules/accessor-pairs.js

@ -6,6 +6,62 @@
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a given node is an `Identifier` node which was named a given name.
* @param {ASTNode} node - A node to check.
* @param {string} name - An expected name of the node.
* @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
*/
function isIdentifier(node, name) {
return node.type === "Identifier" && node.name === name;
}
/**
* Checks whether or not a given node is an argument of a specified method call.
* @param {ASTNode} node - A node to check.
* @param {number} index - An expected index of the node in arguments.
* @param {string} object - An expected name of the object of the method.
* @param {string} property - An expected name of the method.
* @returns {boolean} `true` if the node is an argument of the specified method call.
*/
function isArgumentOfMethodCall(node, index, object, property) {
var parent = node.parent;
return (
parent.type === "CallExpression" &&
parent.callee.type === "MemberExpression" &&
parent.callee.computed === false &&
isIdentifier(parent.callee.object, object) &&
isIdentifier(parent.callee.property, property) &&
parent.arguments[index] === node
);
}
/**
* Checks whether or not a given node is a property descriptor.
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is a property descriptor.
*/
function isPropertyDescriptor(node) {
// Object.defineProperty(obj, "foo", {set: ...})
if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
) {
return true;
}
// Object.defineProperties(obj, {foo: {set: ...}})
// Object.create(proto, {foo: {set: ...}})
node = node.parent.parent;
return node.type === "ObjectExpression" && (
isArgumentOfMethodCall(node, 1, "Object", "create") ||
isArgumentOfMethodCall(node, 1, "Object", "defineProperties")
);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -24,10 +80,19 @@ module.exports = function(context) {
function checkLonelySetGet(node) {
var isSetPresent = false;
var isGetPresent = false;
var propLength = node.properties.length;
var isDescriptor = isPropertyDescriptor(node);
for (var i = 0; i < propLength; i++) {
var propToCheck = node.properties[i].kind === "init" ? node.properties[i].key.name : node.properties[i].kind;
for (var i = 0, end = node.properties.length; i < end; i++) {
var property = node.properties[i];
var propToCheck = "";
if (property.kind === "init") {
if (isDescriptor && !property.computed) {
propToCheck = property.key.name;
}
} else {
propToCheck = property.kind;
}
switch (propToCheck) {
case "set":
@ -55,7 +120,7 @@ module.exports = function(context) {
}
return {
"ObjectExpression": function (node) {
"ObjectExpression": function(node) {
if (checkSetWithoutGet || checkGetWithoutSet) {
checkLonelySetGet(node);
}
@ -63,3 +128,18 @@ module.exports = function(context) {
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"getWithoutSet": {
"type": "boolean"
},
"setWithoutGet": {
"type": "boolean"
}
},
"additionalProperties": false
}
];

113
tools/eslint/lib/rules/array-bracket-spacing.js

@ -8,12 +8,15 @@
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var spaced = context.options[0] === "always";
var spaced = context.options[0] === "always",
sourceCode = context.getSourceCode();
/**
* Determines whether an option is set, relative to the spacing option.
@ -23,7 +26,7 @@ module.exports = function(context) {
* @returns {boolean} Whether or not the property is excluded.
*/
function isOptionSet(option) {
return context.options[1] != null ? context.options[1][option] === !spaced : false;
return context.options[1] ? context.options[1][option] === !spaced : false;
}
var options = {
@ -37,26 +40,6 @@ module.exports = function(context) {
// Helpers
//--------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced(left, right) {
return left.range[1] < right.range[0];
}
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isSameLine(left, right) {
return left.loc.start.line === right.loc.start.line;
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
@ -64,8 +47,15 @@ module.exports = function(context) {
* @returns {void}
*/
function reportNoBeginningSpace(node, token) {
context.report(node, token.loc.start,
"There should be no space after '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
var nextToken = context.getSourceCode().getTokenAfter(token);
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
/**
@ -75,8 +65,15 @@ module.exports = function(context) {
* @returns {void}
*/
function reportNoEndingSpace(node, token) {
context.report(node, token.loc.start,
"There should be no space before '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
var previousToken = context.getSourceCode().getTokenBefore(token);
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
});
}
/**
@ -86,8 +83,14 @@ module.exports = function(context) {
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
context.report(node, token.loc.start,
"A space is required after '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "A space is required after '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
/**
@ -97,8 +100,32 @@ module.exports = function(context) {
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
context.report(node, token.loc.start,
"A space is required before '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "A space is required before '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
/**
* Determines if a node is an object type
* @param {ASTNode} node - The node to check.
* @returns {boolean} Whether or not the node is an object type.
*/
function isObjectType(node) {
return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
}
/**
* Determines if a node is an array type
* @param {ASTNode} node - The node to check.
* @returns {boolean} Whether or not the node is an array type.
*/
function isArrayType(node) {
return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
}
/**
@ -107,41 +134,43 @@ module.exports = function(context) {
* @returns {void}
*/
function validateArraySpacing(node) {
if (node.elements.length === 0) {
if (options.spaced && node.elements.length === 0) {
return;
}
var first = context.getFirstToken(node),
second = context.getFirstToken(node, 1),
penultimate = context.getLastToken(node, 1),
last = context.getLastToken(node);
last = context.getLastToken(node),
firstElement = node.elements[0],
lastElement = node.elements[node.elements.length - 1];
var openingBracketMustBeSpaced =
options.objectsInArraysException && second.value === "{" ||
options.arraysInArraysException && second.value === "[" ||
options.objectsInArraysException && isObjectType(firstElement) ||
options.arraysInArraysException && isArrayType(firstElement) ||
options.singleElementException && node.elements.length === 1
? !options.spaced : options.spaced;
var closingBracketMustBeSpaced =
options.objectsInArraysException && penultimate.value === "}" ||
options.arraysInArraysException && penultimate.value === "]" ||
options.objectsInArraysException && isObjectType(lastElement) ||
options.arraysInArraysException && isArrayType(lastElement) ||
options.singleElementException && node.elements.length === 1
? !options.spaced : options.spaced;
if (isSameLine(first, second)) {
if (openingBracketMustBeSpaced && !isSpaced(first, second)) {
if (astUtils.isTokenOnSameLine(first, second)) {
if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
reportRequiredBeginningSpace(node, first);
}
if (!openingBracketMustBeSpaced && isSpaced(first, second)) {
if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
reportNoBeginningSpace(node, first);
}
}
if (isSameLine(penultimate, last)) {
if (closingBracketMustBeSpaced && !isSpaced(penultimate, last)) {
if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) {
if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportRequiredEndingSpace(node, last);
}
if (!closingBracketMustBeSpaced && isSpaced(penultimate, last)) {
if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportNoEndingSpace(node, last);
}
}

71
tools/eslint/lib/rules/arrow-body-style.js

@ -0,0 +1,71 @@
/**
* @fileoverview Rule to require braces in arrow function body.
* @author Alberto Rodríguez
* @copyright 2015 Alberto Rodríguez. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var always = context.options[0] === "always";
var asNeeded = !context.options[0] || context.options[0] === "as-needed";
/**
* Determines whether a arrow function body needs braces
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function validate(node) {
var arrowBody = node.body;
if (arrowBody.type === "BlockStatement") {
var blockBody = arrowBody.body;
if (blockBody.length > 1) {
return;
}
if (blockBody.length === 0) {
var hasComments = context.getComments(arrowBody).trailing.length > 0;
if (hasComments) {
return;
}
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected empty block in arrow body."
});
} else {
if (asNeeded && blockBody[0].type === "ReturnStatement") {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Unexpected block statement surrounding arrow body."
});
}
}
} else {
if (always) {
context.report({
node: node,
loc: arrowBody.loc.start,
message: "Expected block statement surrounding arrow body."
});
}
}
}
return {
"ArrowFunctionExpression": validate
};
};
module.exports.schema = [
{
"enum": ["always", "as-needed"]
}
];

52
tools/eslint/lib/rules/arrow-parens.js

@ -0,0 +1,52 @@
/**
* @fileoverview Rule to require parens in arrow function arguments.
* @author Jxck
* @copyright 2015 Jxck. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var message = "Expected parentheses around arrow function argument.";
var asNeededMessage = "Unexpected parentheses around single function argument";
var asNeeded = context.options[0] === "as-needed";
/**
* Determines whether a arrow function argument end with `)`
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function parens(node) {
var token = context.getFirstToken(node);
// as-needed: x => x
if (asNeeded && node.params.length === 1 && node.params[0].type === "Identifier") {
if (token.type === "Punctuator" && token.value === "(") {
context.report(node, asNeededMessage);
}
return;
}
if (token.type === "Identifier") {
var after = context.getTokenAfter(token);
// (x) => x
if (after.value !== ")") {
context.report(node, message);
}
}
}
return {
"ArrowFunctionExpression": parens
};
};
module.exports.schema = [
{
"enum": ["always", "as-needed"]
}
];

124
tools/eslint/lib/rules/arrow-spacing.js

@ -0,0 +1,124 @@
/**
* @fileoverview Rule to require parens in arrow function arguments.
* @author Jxck
* @copyright 2015 Jxck. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
// merge rules with default
var rule = { before: true, after: true };
var option = context.options[0] || {};
rule.before = option.before !== false;
rule.after = option.after !== false;
/**
* Get tokens of arrow(`=>`) and before/after arrow.
* @param {ASTNode} node The arrow function node.
* @returns {Object} Tokens of arrow and before/after arrow.
*/
function getTokens(node) {
var t = context.getFirstToken(node);
var before;
while (t.type !== "Punctuator" || t.value !== "=>") {
before = t;
t = context.getTokenAfter(t);
}
var after = context.getTokenAfter(t);
return { before: before, arrow: t, after: after };
}
/**
* Count spaces before/after arrow(`=>`) token.
* @param {Object} tokens Tokens before/after arrow.
* @returns {Object} count of space before/after arrow.
*/
function countSpaces(tokens) {
var before = tokens.arrow.range[0] - tokens.before.range[1];
var after = tokens.after.range[0] - tokens.arrow.range[1];
return { before: before, after: after };
}
/**
* Determines whether space(s) before after arrow(`=>`) is satisfy rule.
* if before/after value is `true`, there should be space(s).
* if before/after value is `false`, there should be no space.
* @param {ASTNode} node The arrow function node.
* @returns {void}
*/
function spaces(node) {
var tokens = getTokens(node);
var countSpace = countSpaces(tokens);
if (rule.before) {
// should be space(s) before arrow
if (countSpace.before === 0) {
context.report({
node: tokens.before,
message: "Missing space before =>",
fix: function(fixer) {
return fixer.insertTextBefore(tokens.arrow, " ");
}
});
}
} else {
// should be no space before arrow
if (countSpace.before > 0) {
context.report({
node: tokens.before,
message: "Unexpected space before =>",
fix: function(fixer) {
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
}
});
}
}
if (rule.after) {
// should be space(s) after arrow
if (countSpace.after === 0) {
context.report({
node: tokens.after,
message: "Missing space after =>",
fix: function(fixer) {
return fixer.insertTextAfter(tokens.arrow, " ");
}
});
}
} else {
// should be no space after arrow
if (countSpace.after > 0) {
context.report({
node: tokens.after,
message: "Unexpected space after =>",
fix: function(fixer) {
return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
}
});
}
}
}
return {
"ArrowFunctionExpression": spaces
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"before": {
"type": "boolean"
},
"after": {
"type": "boolean"
}
},
"additionalProperties": false
}
];

356
tools/eslint/lib/rules/block-scoped-var.js

@ -1,318 +1,140 @@
/**
* @fileoverview Rule to check for "block scoped" variables by binding context
* @author Matt DuVall <http://www.mattduvall.com>
* @copyright 2015 Toru Nagashima. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
// Helpers
//------------------------------------------------------------------------------
module.exports = function(context) {
var scopeStack = [];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines whether an identifier is in declaration position or is a non-declaration reference.
* @param {ASTNode} id The identifier.
* @param {ASTNode} parent The identifier's parent AST node.
* @returns {Boolean} true when the identifier is in declaration position.
*/
function isDeclaration(id, parent) {
switch (parent.type) {
case "FunctionDeclaration":
case "FunctionExpression":
return parent.params.indexOf(id) > -1 || id === parent.id;
case "VariableDeclarator":
return id === parent.id;
/**
* Collects unresolved references from the global scope, then creates a map to references from its name.
* @param {RuleContext} context - The current context.
* @returns {object} A map object. Its key is the variable names. Its value is the references of each variable.
*/
function collectUnresolvedReferences(context) {
var unresolved = Object.create(null);
var references = context.getScope().through;
case "CatchClause":
return id === parent.param;
for (var i = 0; i < references.length; ++i) {
var reference = references[i];
var name = reference.identifier.name;
default:
return false;
if (name in unresolved === false) {
unresolved[name] = [];
}
unresolved[name].push(reference);
}
/**
* Determines whether an identifier is in property position.
* @param {ASTNode} id The identifier.
* @param {ASTNode} parent The identifier's parent AST node.
* @returns {Boolean} true when the identifier is in property position.
*/
function isProperty(id, parent) {
switch (parent.type) {
case "MemberExpression":
return id === parent.property && !parent.computed;
return unresolved;
}
case "Property":
return id === parent.key;
default:
return false;
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/**
* Pushes a new scope object on the scope stack.
* @returns {void}
*/
function pushScope() {
scopeStack.push([]);
}
module.exports = function(context) {
var unresolvedReferences = Object.create(null);
var stack = [];
/**
* Removes the topmost scope object from the scope stack.
* Makes a block scope.
* @param {ASTNode} node - A node of a scope.
* @returns {void}
*/
function popScope() {
scopeStack.pop();
function enterScope(node) {
stack.push(node.range);
}
/**
* Declares the given names in the topmost scope object.
* @param {[String]} names A list of names to declare.
* Pops the last block scope.
* @returns {void}
*/
function declare(names) {
[].push.apply(scopeStack[scopeStack.length - 1], names);
function exitScope() {
stack.pop();
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
/**
* Declares all relevant identifiers for module imports.
* @param {ASTNode} node The AST node representing an import.
* Reports a given reference.
* @param {escope.Reference} reference - A reference to report.
* @returns {void}
* @private
*/
function declareImports(node) {
declare([node.local.name]);
if (node.imported && node.imported.name !== node.local.name) {
declare([node.imported.name]);
}
function report(reference) {
var identifier = reference.identifier;
context.report(
identifier,
"\"{{name}}\" used outside of binding context.",
{name: identifier.name});
}
/**
* Declares all relevant identifiers for classes.
* @param {ASTNode} node The AST node representing a class.
* Finds and reports references which are outside of valid scopes.
* @param {ASTNode} node - A node to get variables.
* @returns {void}
* @private
*/
function declareClass(node) {
if (node.id) {
declare([node.id.name]);
function checkForVariables(node) {
if (node.kind !== "var") {
return;
}
pushScope();
}
/**
* Declares all relevant identifiers for classes.
* @param {ASTNode} node The AST node representing a class.
* @returns {void}
* @private
*/
function declareClassMethod(node) {
pushScope();
var isGlobal = context.getScope().type === "global";
declare([node.key.name]);
}
// Defines a predicate to check whether or not a given reference is outside of valid scope.
var scopeRange = stack[stack.length - 1];
/**
* Add declarations based on the type of node being passed.
* @param {ASTNode} node The node containing declarations.
* @returns {void}
* @private
*/
function declareByNodeType(node) {
var declarations = [];
switch (node.type) {
case "Identifier":
declarations.push(node.name);
break;
case "ObjectPattern":
node.properties.forEach(function(property) {
declarations.push(property.key.name);
if (property.value) {
declarations.push(property.value.name);
}
});
break;
case "ArrayPattern":
node.elements.forEach(function(element) {
if (element) {
declarations.push(element.name);
}
});
break;
case "AssignmentPattern":
declareByNodeType(node.left);
break;
case "RestElement":
declareByNodeType(node.argument);
break;
// no default
/**
* Check if a reference is out of scope
* @param {ASTNode} reference node to examine
* @returns {boolean} True is its outside the scope
* @private
*/
function isOutsideOfScope(reference) {
var idRange = reference.identifier.range;
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
}
declare(declarations);
// Gets declared variables, and checks its references.
var variables = context.getDeclaredVariables(node);
for (var i = 0; i < variables.length; ++i) {
var variable = variables[i];
var references = variable.references;
}
/**
* Adds declarations of the function parameters and pushes the scope
* @param {ASTNode} node The node containing declarations.
* @returns {void}
* @private
*/
function functionHandler(node) {
pushScope();
node.params.forEach(function(param) {
declareByNodeType(param);
});
declare(node.rest ? [node.rest.name] : []);
declare(["arguments"]);
}
/**
* Adds declaration of the function name in its parent scope then process the function
* @param {ASTNode} node The node containing declarations.
* @returns {void}
* @private
*/
function functionDeclarationHandler(node) {
declare(node.id ? [node.id.name] : []);
functionHandler(node);
}
/**
* Process function declarations and declares its name in its own scope
* @param {ASTNode} node The node containing declarations.
* @returns {void}
* @private
*/
function functionExpressionHandler(node) {
functionHandler(node);
declare(node.id ? [node.id.name] : []);
}
function variableDeclarationHandler(node) {
node.declarations.forEach(function(declaration) {
declareByNodeType(declaration.id);
});
// Global variables are not resolved.
// In this case, use unresolved references.
if (isGlobal && variable.name in unresolvedReferences) {
references = unresolvedReferences[variable.name];
}
// Reports.
references.filter(isOutsideOfScope).forEach(report);
}
}
return {
"Program": function() {
var scope = context.getScope();
scopeStack = [scope.variables.map(function(v) {
return v.name;
})];
// global return creates another scope
if (context.ecmaFeatures.globalReturn) {
scope = scope.childScopes[0];
scopeStack.push(scope.variables.map(function(v) {
return v.name;
}));
}
},
"ImportSpecifier": declareImports,
"ImportDefaultSpecifier": declareImports,
"ImportNamespaceSpecifier": declareImports,
"BlockStatement": function(node) {
var statements = node.body;
pushScope();
statements.forEach(function(stmt) {
if (stmt.type === "VariableDeclaration") {
variableDeclarationHandler(stmt);
} else if (stmt.type === "FunctionDeclaration") {
declare([stmt.id.name]);
}
});
"Program": function(node) {
unresolvedReferences = collectUnresolvedReferences(context);
stack = [node.range];
},
"VariableDeclaration": function (node) {
variableDeclarationHandler(node);
},
"BlockStatement:exit": popScope,
"CatchClause": function(node) {
pushScope();
declare([node.param.name]);
},
"CatchClause:exit": popScope,
"FunctionDeclaration": functionDeclarationHandler,
"FunctionDeclaration:exit": popScope,
"ClassDeclaration": declareClass,
"ClassDeclaration:exit": popScope,
"ClassExpression": declareClass,
"ClassExpression:exit": popScope,
"MethodDefinition": declareClassMethod,
"MethodDefinition:exit": popScope,
"FunctionExpression": functionExpressionHandler,
"FunctionExpression:exit": popScope,
// Arrow functions cannot have names
"ArrowFunctionExpression": functionHandler,
"ArrowFunctionExpression:exit": popScope,
"ForStatement": function() {
pushScope();
},
"ForStatement:exit": popScope,
"ForInStatement": function() {
pushScope();
},
"ForInStatement:exit": popScope,
"ForOfStatement": function() {
pushScope();
},
"ForOfStatement:exit": popScope,
"Identifier": function(node) {
var ancestor = context.getAncestors().pop();
if (isDeclaration(node, ancestor) || isProperty(node, ancestor) || ancestor.type === "LabeledStatement") {
return;
}
for (var i = 0, l = scopeStack.length; i < l; i++) {
if (scopeStack[i].indexOf(node.name) > -1) {
return;
}
}
context.report(node, "\"" + node.name + "\" used outside of binding context.");
}
// Manages scopes.
"BlockStatement": enterScope,
"BlockStatement:exit": exitScope,
"ForStatement": enterScope,
"ForStatement:exit": exitScope,
"ForInStatement": enterScope,
"ForInStatement:exit": exitScope,
"ForOfStatement": enterScope,
"ForOfStatement:exit": exitScope,
"SwitchStatement": enterScope,
"SwitchStatement:exit": exitScope,
"CatchClause": enterScope,
"CatchClause:exit": exitScope,
// Finds and reports references which are outside of valid scope.
"VariableDeclaration": checkForVariables
};
};

119
tools/eslint/lib/rules/block-spacing.js

@ -0,0 +1,119 @@
/**
* @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
var util = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var always = (context.options[0] !== "never"),
message = always ? "Requires a space" : "Unexpected space(s)",
sourceCode = context.getSourceCode();
/**
* Gets the open brace token from a given node.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
if (node.type === "SwitchStatement") {
if (node.cases.length > 0) {
return context.getTokenBefore(node.cases[0]);
}
return context.getLastToken(node, 1);
}
return context.getFirstToken(node);
}
/**
* Checks whether or not:
* - given tokens are on same line.
* - there is/isn't a space between given tokens.
* @param {Token} left - A token to check.
* @param {Token} right - The token which is next to `left`.
* @returns {boolean}
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
* If given tokens are not on same line, it's always `true`.
*/
function isValid(left, right) {
return (
!util.isTokenOnSameLine(left, right) ||
sourceCode.isSpaceBetweenTokens(left, right) === always
);
}
/**
* Reports invalid spacing style inside braces.
* @param {ASTNode} node - A BlockStatement/SwitchStatement node to get.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
// Gets braces and the first/last token of content.
var openBrace = getOpenBrace(node);
var closeBrace = context.getLastToken(node);
var firstToken = sourceCode.getTokenOrCommentAfter(openBrace);
var lastToken = sourceCode.getTokenOrCommentBefore(closeBrace);
// Skip if the node is invalid or empty.
if (openBrace.type !== "Punctuator" ||
openBrace.value !== "{" ||
closeBrace.type !== "Punctuator" ||
closeBrace.value !== "}" ||
firstToken === closeBrace
) {
return;
}
// Skip line comments for option never
if (!always && firstToken.type === "Line") {
return;
}
// Check.
if (!isValid(openBrace, firstToken)) {
context.report({
node: node,
loc: openBrace.loc.start,
message: message + " after \"{\".",
fix: function(fixer) {
if (always) {
return fixer.insertTextBefore(firstToken, " ");
}
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
}
});
}
if (!isValid(lastToken, closeBrace)) {
context.report({
node: node,
loc: closeBrace.loc.start,
message: message + " before \"}\".",
fix: function(fixer) {
if (always) {
return fixer.insertTextAfter(lastToken, " ");
}
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
}
});
}
}
return {
BlockStatement: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
};
module.exports.schema = [
{enum: ["always", "never"]}
];

73
tools/eslint/lib/rules/brace-style.js

@ -14,10 +14,11 @@ module.exports = function(context) {
var params = context.options[1] || {};
var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.",
OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.",
BODY_MESSAGE = "Statement inside of curly braces should be on next line.",
CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.",
CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
CLOSE_MESSAGE_STROUSTRUP = "Closing curly brace appears on the same line as the subsequent block.";
CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block.";
//--------------------------------------------------------------------------
// Helpers
@ -57,28 +58,30 @@ module.exports = function(context) {
return function(node) {
[].forEach.call(blockProperties, function(blockProp) {
var block = node[blockProp], previousToken, curlyToken, curlyTokenEnd, curlyTokensOnSameLine;
block = node[blockProp];
if (isBlock(block)) {
if (!isBlock(block)) {
return;
}
previousToken = context.getTokenBefore(block);
curlyToken = context.getFirstToken(block);
curlyTokenEnd = context.getLastToken(block);
curlyTokensOnSameLine = curlyToken.loc.start.line === curlyTokenEnd.loc.start.line;
previousToken = context.getTokenBefore(block);
curlyToken = context.getFirstToken(block);
curlyTokenEnd = context.getLastToken(block);
curlyTokensOnSameLine = curlyToken.loc.start.line === curlyTokenEnd.loc.start.line;
if (previousToken.loc.start.line !== curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE);
} else if (block.body.length && params.allowSingleLine) {
if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line && !params.allowSingleLine) {
context.report(node, OPEN_MESSAGE_ALLMAN);
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line && !curlyTokensOnSameLine) {
context.report(block.body[0], BODY_MESSAGE);
} else if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line && !curlyTokensOnSameLine) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
}
if (!block.body.length || curlyTokensOnSameLine && params.allowSingleLine) {
return;
}
} else if (block.body.length && curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE);
}
if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
context.report(block.body[0], BODY_MESSAGE);
} else if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) {
context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
}
});
};
@ -106,13 +109,13 @@ module.exports = function(context) {
tokens = context.getTokensBefore(node.alternate, 2);
if (style === "1tbs") {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line && isCurlyPunctuator(tokens[0]) ) {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
node.consequent.type === "BlockStatement" &&
isCurlyPunctuator(tokens[0]) ) {
context.report(node.alternate, CLOSE_MESSAGE);
}
} else if (style === "stroustrup") {
if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
@ -136,10 +139,8 @@ module.exports = function(context) {
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE);
}
} else if (style === "stroustrup") {
if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP);
}
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
}
@ -161,9 +162,9 @@ module.exports = function(context) {
if (previousToken.loc.start.line !== firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE);
}
} else if (style === "stroustrup") {
} else {
if (previousToken.loc.start.line === firstToken.loc.start.line) {
context.report(node, CLOSE_MESSAGE_STROUSTRUP);
context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
}
}
}
@ -179,14 +180,14 @@ module.exports = function(context) {
var tokens;
if (node.cases && node.cases.length) {
tokens = context.getTokensBefore(node.cases[0], 2);
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE);
}
} else {
tokens = context.getLastTokens(node, 3);
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE);
}
}
if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE);
} else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
context.report(node, OPEN_MESSAGE_ALLMAN);
}
}
@ -214,7 +215,7 @@ module.exports = function(context) {
module.exports.schema = [
{
"enum": ["1tbs", "stroustrup"]
"enum": ["1tbs", "stroustrup", "allman"]
},
{
"type": "object",

142
tools/eslint/lib/rules/callback-return.js

@ -0,0 +1,142 @@
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var callbacks = context.options[0] || ["callback", "cb", "next"];
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Find the closest parent matching a list of types.
* @param {ASTNode} node The node whose parents we are searching
* @param {Array} types The node types to match
* @returns {ASTNode} The matched node or undefined.
*/
function findClosestParentOfType(node, types) {
if (!node.parent) {
return null;
}
if (types.indexOf(node.parent.type) === -1) {
return findClosestParentOfType(node.parent, types);
}
return node.parent;
}
/**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {Boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return node.callee.type === "Identifier" && callbacks.indexOf(node.callee.name) > -1;
}
/**
* Determines whether or not the callback is part of a callback expression.
* @param {ASTNode} node The callback node
* @param {ASTNode} parentNode The expression node
* @returns {boolean} Whether or not this is part of a callback expression
*/
function isCallbackExpression(node, parentNode) {
// ensure the parent node exists and is an expression
if (!parentNode || parentNode.type !== "ExpressionStatement") {
return false;
}
// cb()
if (parentNode.expression === node) {
return true;
}
// special case for cb && cb() and similar
if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
if (parentNode.expression.right === node) {
return true;
}
}
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"CallExpression": function(node) {
// if we"re not a callback we can return
if (!isCallback(node)) {
return;
}
// find the closest block, return or loop
var closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {},
lastItem, parentType;
// if our parent is a return we know we're ok
if (closestBlock.type === "ReturnStatement" ) {
return;
}
// arrow functions don't always have blocks and implicitly return
if (closestBlock.type === "ArrowFunctionExpression") {
return;
}
// block statements are part of functions and most if statements
if (closestBlock.type === "BlockStatement") {
// find the last item in the block
lastItem = closestBlock.body[closestBlock.body.length - 1];
// if the callback is the last thing in a block that might be ok
if (isCallbackExpression(node, lastItem)) {
parentType = closestBlock.parent.type;
// but only if the block is part of a function
if (parentType === "FunctionExpression" ||
parentType === "FunctionDeclaration" ||
parentType === "ArrowFunctionExpression"
) {
return;
}
}
// ending a block with a return is also ok
if (lastItem.type === "ReturnStatement") {
// but only if the callback is immediately before
if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) {
return;
}
}
}
// as long as you're the child of a function at this point you should be asked to return
if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
context.report(node, "Expected return with your callback function.");
}
}
};
};
module.exports.schema = [{
type: "array",
items: { type: "string" }
}];

209
tools/eslint/lib/rules/comma-dangle.js

@ -1,60 +1,203 @@
/**
* @fileoverview Rule to forbid or enforce dangling commas.
* @author Ian Christian Myers
* @copyright 2015 Toru Nagashima
* @copyright 2015 Mathias Schreck
* @copyright 2013 Ian Christian Myers
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Gets the last element of a given array.
*
* @param {*[]} xs - An array to get.
* @returns {*} The last element, or undefined.
*/
function getLast(xs) {
if (xs.length === 0) {
return null;
}
return xs[xs.length - 1];
}
/**
* Checks whether or not a trailing comma is allowed in a given node.
* `ArrayPattern` which has `RestElement` disallows it.
*
* @param {ASTNode} node - A node to check.
* @param {ASTNode} lastItem - The node of the last element in the given node.
* @returns {boolean} `true` if a trailing comma is allowed.
*/
function isTrailingCommaAllowed(node, lastItem) {
switch (node.type) {
case "ArrayPattern":
// TODO(t-nagashima): Remove SpreadElement after https://github.com/eslint/espree/issues/194 was fixed.
return (
lastItem.type !== "RestElement" &&
lastItem.type !== "SpreadElement"
);
// TODO(t-nagashima): Remove this case after https://github.com/eslint/espree/issues/195 was fixed.
case "ArrayExpression":
return (
node.parent.type !== "ForOfStatement" ||
node.parent.left !== node ||
lastItem.type !== "SpreadElement"
);
default:
return true;
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function (context) {
var allowDangle = context.options[0];
var forbidDangle = allowDangle !== "always-multiline" && allowDangle !== "always";
module.exports = function(context) {
var mode = context.options[0];
var UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
var MISSING_MESSAGE = "Missing trailing comma.";
/**
* Checks the given node for trailing comma and reports violations.
* @param {ASTNode} node The node of an ObjectExpression or ArrayExpression
* Checks whether or not a given node is multiline.
* This rule handles a given node as multiline when the closing parenthesis
* and the last element are not on the same line.
*
* @param {ASTNode} node - A ndoe to check.
* @returns {boolean} `true` if the node is multiline.
*/
function isMultiline(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers);
if (!lastItem) {
return false;
}
var sourceCode = context.getSourceCode(),
penultimateToken = sourceCode.getLastToken(lastItem),
lastToken = sourceCode.getLastToken(node);
if (lastToken.value === ",") {
penultimateToken = lastToken;
lastToken = sourceCode.getTokenAfter(lastToken);
}
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
/**
* Reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function checkForTrailingComma(node) {
var items = node.properties || node.elements,
length = items.length,
lastTokenOnNewLine,
lastItem,
penultimateToken,
hasDanglingComma;
if (length) {
lastItem = items[length - 1];
if (lastItem) {
penultimateToken = context.getLastToken(node, 1);
hasDanglingComma = penultimateToken.value === ",";
if (forbidDangle && hasDanglingComma) {
context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
} else if (allowDangle === "always-multiline") {
lastTokenOnNewLine = node.loc.end.line !== penultimateToken.loc.end.line;
if (hasDanglingComma && !lastTokenOnNewLine) {
context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
} else if (!hasDanglingComma && lastTokenOnNewLine) {
context.report(lastItem, penultimateToken.loc.end, MISSING_MESSAGE);
}
} else if (allowDangle === "always" && !hasDanglingComma) {
context.report(lastItem, lastItem.loc.end, MISSING_MESSAGE);
}
}
function forbidTrailingComma(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
}
if (trailingToken.value === ",") {
context.report(
lastItem,
trailingToken.loc.start,
UNEXPECTED_MESSAGE);
}
}
/**
* Reports the last element of a given node if it does not have a trailing
* comma.
*
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
* comma is disallowed, so report if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingComma(node) {
var lastItem = getLast(node.properties || node.elements || node.specifiers);
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
return;
}
if (!isTrailingCommaAllowed(node, lastItem)) {
forbidTrailingComma(node);
return;
}
var sourceCode = context.getSourceCode(),
trailingToken;
// last item can be surrounded by parentheses for object and array literals
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
} else {
trailingToken = sourceCode.getTokenAfter(lastItem);
}
if (trailingToken.value !== ",") {
context.report(
lastItem,
lastItem.loc.end,
MISSING_MESSAGE);
}
}
/**
* If a given node is multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
*
* @param {ASTNode} node - A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingCommaIfMultiline(node) {
if (isMultiline(node)) {
forceTrailingComma(node);
} else {
forbidTrailingComma(node);
}
}
// Chooses a checking function.
var checkForTrailingComma;
if (mode === "always") {
checkForTrailingComma = forceTrailingComma;
} else if (mode === "always-multiline") {
checkForTrailingComma = forceTrailingCommaIfMultiline;
} else {
checkForTrailingComma = forbidTrailingComma;
}
return {
"ObjectExpression": checkForTrailingComma,
"ArrayExpression": checkForTrailingComma
"ObjectPattern": checkForTrailingComma,
"ArrayExpression": checkForTrailingComma,
"ArrayPattern": checkForTrailingComma,
"ImportDeclaration": checkForTrailingComma,
"ExportNamedDeclaration": checkForTrailingComma
};
};

182
tools/eslint/lib/rules/comma-spacing.js

@ -5,12 +5,17 @@
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
var tokensAndComments = sourceCode.tokensAndComments;
var options = {
before: context.options[0] ? !!context.options[0].before : false,
after: context.options[0] ? !!context.options[0].after : true
@ -20,47 +25,8 @@ module.exports = function(context) {
// Helpers
//--------------------------------------------------------------------------
// the index of the last comment that was checked
var lastCommentIndex = 0;
var allComments;
/**
* Determines the length of comment between 2 tokens
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {number} Length of comment in between tokens
*/
function getCommentLengthBetweenTokens(left, right) {
return allComments.reduce(function(val, comment) {
if (left.range[1] <= comment.range[0] && comment.range[1] <= right.range[0]) {
val = val + comment.range[1] - comment.range[0];
}
return val;
}, 0);
}
/**
* Determines whether two adjacent tokens have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced(left, right) {
var punctuationLength = context.getTokensBetween(left, right).length; // the length of any parenthesis
var commentLenth = getCommentLengthBetweenTokens(left, right);
return (left.range[1] + punctuationLength + commentLenth) < right.range[0];
}
/**
* Checks whether two tokens are on the same line.
* @param {ASTNode} left The leftmost token.
* @param {ASTNode} right The rightmost token.
* @returns {boolean} True if the tokens are on the same line, false if not.
* @private
*/
function isSameLine(left, right) {
return left.loc.end.line === right.loc.start.line;
}
// list of comma tokens to ignore for the check of leading whitespace
var commaTokensToIgnore = [];
/**
* Determines if a given token is a comma operator.
@ -76,13 +42,39 @@ module.exports = function(context) {
* Reports a spacing error with an appropriate message.
* @param {ASTNode} node The binary expression node to report.
* @param {string} dir Is the error "before" or "after" the comma?
* @param {ASTNode} otherNode The node at the left or right of `node`
* @returns {void}
* @private
*/
function report(node, dir) {
context.report(node, options[dir] ?
"A space is required " + dir + " ','." :
"There should be no space " + dir + " ','.");
function report(node, dir, otherNode) {
context.report({
node: node,
fix: function(fixer) {
if (options[dir]) {
if (dir === "before") {
return fixer.insertTextBefore(node, " ");
} else {
return fixer.insertTextAfter(node, " ");
}
} else {
var start, end;
var newText = "";
if (dir === "before") {
start = otherNode.range[1];
end = node.range[0];
} else {
start = node.range[1];
end = otherNode.range[0];
}
return fixer.replaceTextRange([start, end], newText);
}
},
message: options[dir] ?
"A space is required " + dir + " ','." :
"There should be no space " + dir + " ','."
});
}
/**
@ -96,47 +88,46 @@ module.exports = function(context) {
* @private
*/
function validateCommaItemSpacing(tokens, reportItem) {
if (tokens.left && isSameLine(tokens.left, tokens.comma) &&
(options.before !== isSpaced(tokens.left, tokens.comma))
if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
(options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
) {
report(reportItem, "before");
report(reportItem, "before", tokens.left);
}
if (tokens.right && !options.after && tokens.right.type === "Line") {
return false;
}
if (tokens.right && isSameLine(tokens.comma, tokens.right) &&
(options.after !== isSpaced(tokens.comma, tokens.right))
if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) {
report(reportItem, "after");
report(reportItem, "after", tokens.right);
}
}
/**
* Determines if a given source index is in a comment or not by checking
* the index against the comment range. Since the check goes straight
* through the file, once an index is passed a certain comment, we can
* go to the next comment to check that.
* @param {int} index The source index to check.
* @param {ASTNode[]} comments An array of comment nodes.
* @returns {boolean} True if the index is within a comment, false if not.
* @private
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
* @returns {void}
*/
function isIndexInComment(index, comments) {
var comment;
function addNullElementsToIgnoreList(node) {
var previousToken = context.getFirstToken(node);
while (lastCommentIndex < comments.length) {
node.elements.forEach(function(element) {
var token;
comment = comments[lastCommentIndex];
if (element === null) {
token = context.getTokenAfter(previousToken);
if (comment.range[0] <= index && index < comment.range[1]) {
return true;
} else if (index > comment.range[1]) {
lastCommentIndex++;
if (isComma(token)) {
commaTokensToIgnore.push(token);
}
} else {
break;
token = context.getTokenAfter(element);
}
}
return false;
previousToken = token;
});
}
//--------------------------------------------------------------------------
@ -144,33 +135,34 @@ module.exports = function(context) {
//--------------------------------------------------------------------------
return {
"Program": function() {
"Program:exit": function() {
var source = context.getSource(),
pattern = /,/g,
commaToken,
previousToken,
var previousToken,
nextToken;
allComments = context.getAllComments();
while (pattern.test(source)) {
// do not flag anything inside of comments
if (!isIndexInComment(pattern.lastIndex, allComments)) {
commaToken = context.getTokenByRangeStart(pattern.lastIndex - 1);
if (commaToken && commaToken.type !== "JSXText") {
previousToken = context.getTokenBefore(commaToken);
nextToken = context.getTokenAfter(commaToken);
validateCommaItemSpacing({
comma: commaToken,
left: isComma(previousToken) ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
}, commaToken);
}
tokensAndComments.forEach(function(token, i) {
if (!isComma(token)) {
return;
}
}
}
if (token && token.type === "JSXText") {
return;
}
previousToken = tokensAndComments[i - 1];
nextToken = tokensAndComments[i + 1];
validateCommaItemSpacing({
comma: token,
left: isComma(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
right: isComma(nextToken) ? null : nextToken
}, token);
});
},
"ArrayExpression": addNullElementsToIgnoreList,
"ArrayPattern": addNullElementsToIgnoreList
};
};

25
tools/eslint/lib/rules/comma-style.js

@ -7,6 +7,8 @@
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -24,17 +26,6 @@ module.exports = function(context) {
// Helpers
//--------------------------------------------------------------------------
/**
* Checks whether two tokens are on the same line.
* @param {ASTNode} left The leftmost token.
* @param {ASTNode} right The rightmost token.
* @returns {boolean} True if the tokens are on the same line, false if not.
* @private
*/
function isSameLine(left, right) {
return left.loc.end.line === right.loc.start.line;
}
/**
* Determines if a given token is a comma operator.
* @param {ASTNode} token The token to check.
@ -57,13 +48,13 @@ module.exports = function(context) {
function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
// if single line
if (isSameLine(commaToken, currentItemToken) &&
isSameLine(previousItemToken, commaToken)) {
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
return;
} else if (!isSameLine(commaToken, currentItemToken) &&
!isSameLine(previousItemToken, commaToken)) {
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
// lone comma
context.report(reportItem, {
@ -71,11 +62,11 @@ module.exports = function(context) {
column: commaToken.loc.start.column
}, "Bad line breaking before and after ','.");
} else if (style === "first" && !isSameLine(commaToken, currentItemToken)) {
} else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
context.report(reportItem, "',' should be placed first.");
} else if (style === "last" && isSameLine(commaToken, currentItemToken)) {
} else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
context.report(reportItem, {
line: commaToken.loc.end.line,

31
tools/eslint/lib/rules/complexity.js

@ -21,18 +21,28 @@ module.exports = function(context) {
// Using a stack to store complexity (handling nested functions)
var fns = [];
// When parsing a new function, store it in our function stack
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var complexity = fns.pop(),
name = "anonymous";
if (node.id) {
name = node.id.name;
} else if (node.parent.type === "MethodDefinition") {
} else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
name = node.parent.key.name;
}
@ -41,12 +51,23 @@ module.exports = function(context) {
}
}
/**
* Increase the complexity of the function in context
* @returns {void}
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1] ++;
}
}
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
@ -54,6 +75,12 @@ module.exports = function(context) {
}
}
/**
* Increase the logical path complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseLogicalComplexity(node) {
// Avoiding &&
if (node.operator === "||") {

85
tools/eslint/lib/rules/computed-property-spacing.js

@ -5,57 +5,54 @@
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var sourceCode = context.getSourceCode();
var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced(left, right) {
return left.range[1] < right.range[0];
}
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isSameLine(left, right) {
return left.loc.start.line === right.loc.start.line;
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenAfter - The token after `token`.
* @returns {void}
*/
function reportNoBeginningSpace(node, token) {
context.report(node, token.loc.start,
"There should be no space after '" + token.value + "'");
function reportNoBeginningSpace(node, token, tokenAfter) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space after '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
});
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {Token} tokenBefore - The token before `token`.
* @returns {void}
*/
function reportNoEndingSpace(node, token) {
context.report(node, token.loc.start,
"There should be no space before '" + token.value + "'");
function reportNoEndingSpace(node, token, tokenBefore) {
context.report({
node: node,
loc: token.loc.start,
message: "There should be no space before '" + token.value + "'",
fix: function(fixer) {
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
/**
@ -65,8 +62,14 @@ module.exports = function(context) {
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
context.report(node, token.loc.start,
"A space is required after '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "A space is required after '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
/**
@ -76,8 +79,14 @@ module.exports = function(context) {
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
context.report(node, token.loc.start,
"A space is required before '" + token.value + "'");
context.report({
node: node,
loc: token.loc.start,
message: "A space is required before '" + token.value + "'",
fix: function(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
/**
@ -99,26 +108,26 @@ module.exports = function(context) {
last = context.getLastToken(property),
after = context.getTokenAfter(property);
if (isSameLine(before, first)) {
if (astUtils.isTokenOnSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
if (!isSpaced(before, first) && isSameLine(before, first)) {
if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
reportRequiredBeginningSpace(node, before);
}
} else {
if (isSpaced(before, first)) {
reportNoBeginningSpace(node, before);
if (sourceCode.isSpaceBetweenTokens(before, first)) {
reportNoBeginningSpace(node, before, first);
}
}
}
if (isSameLine(last, after)) {
if (astUtils.isTokenOnSameLine(last, after)) {
if (propertyNameMustBeSpaced) {
if (!isSpaced(last, after) && isSameLine(last, after)) {
if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
reportRequiredEndingSpace(node, after);
}
} else {
if (isSpaced(last, after)) {
reportNoEndingSpace(node, after);
if (sourceCode.isSpaceBetweenTokens(last, after)) {
reportNoEndingSpace(node, after, last);
}
}
}

58
tools/eslint/lib/rules/consistent-this.js

@ -53,39 +53,35 @@ module.exports = function(context) {
*/
function ensureWasAssigned() {
var scope = context.getScope();
var variable = scope.set.get(alias);
if (!variable) {
return;
}
if (variable.defs.some(function(def) {
return def.node.type === "VariableDeclarator" &&
def.node.init !== null;
})) {
return;
}
scope.variables.some(function (variable) {
var lookup;
if (variable.name === alias) {
if (variable.defs.some(function (def) {
return def.node.type === "VariableDeclarator" &&
def.node.init !== null;
})) {
return true;
}
lookup = scope.type === "global" ? scope : variable;
// The alias has been declared and not assigned: check it was
// assigned later in the same scope.
if (!lookup.references.some(function (reference) {
var write = reference.writeExpr;
if (reference.from === scope &&
write && write.type === "ThisExpression" &&
write.parent.operator === "=") {
return true;
}
})) {
variable.defs.map(function (def) {
return def.node;
}).forEach(reportBadAssignment);
}
var lookup = (variable.references.length === 0 && scope.type === "global") ? scope : variable;
// The alias has been declared and not assigned: check it was
// assigned later in the same scope.
if (!lookup.references.some(function(reference) {
var write = reference.writeExpr;
if (reference.from === scope &&
write && write.type === "ThisExpression" &&
write.parent.operator === "=") {
return true;
}
});
})) {
variable.defs.map(function(def) {
return def.node;
}).forEach(reportBadAssignment);
}
}
return {
@ -93,7 +89,7 @@ module.exports = function(context) {
"FunctionExpression:exit": ensureWasAssigned,
"FunctionDeclaration:exit": ensureWasAssigned,
"VariableDeclarator": function (node) {
"VariableDeclarator": function(node) {
var id = node.id;
var isDestructuring =
id.type === "ArrayPattern" || id.type === "ObjectPattern";
@ -103,7 +99,7 @@ module.exports = function(context) {
}
},
"AssignmentExpression": function (node) {
"AssignmentExpression": function(node) {
if (node.left.type === "Identifier") {
checkAssignment(node, node.left.name, node.right);
}

8
tools/eslint/lib/rules/constructor-super.js

@ -18,7 +18,7 @@ module.exports = function(context) {
* @returns {ClassDeclaration|ClassExpression|null} the found class node or `null`.
*/
function getClassInAncestor(node) {
while (node != null) {
while (node) {
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
return node;
}
@ -34,7 +34,7 @@ module.exports = function(context) {
* @returns {boolean} whether or not a node is the null literal.
*/
function isNullLiteral(node) {
return node != null && node.type === "Literal" && node.value === null;
return node && node.type === "Literal" && node.value === null;
}
/**
@ -43,7 +43,7 @@ module.exports = function(context) {
* @returns {boolean} whether or not the current traversal context is on constructors.
*/
function isOnConstructor(item) {
return item != null && item.scope === context.getScope().variableScope.upper.variableScope;
return item && item.scope === context.getScope().variableScope.upper.variableScope;
}
// A stack for checking context.
@ -78,7 +78,7 @@ module.exports = function(context) {
var classNode = getClassInAncestor(node);
/* istanbul ignore if */
if (classNode == null) {
if (!classNode) {
return;
}

267
tools/eslint/lib/rules/curly.js

@ -1,9 +1,16 @@
/**
* @fileoverview Rule to flag statements without curly braces
* @author Nicholas C. Zakas
* @copyright 2015 Ivan Nikulin. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -12,6 +19,8 @@ module.exports = function(context) {
var multiOnly = (context.options[0] === "multi");
var multiLine = (context.options[0] === "multi-line");
var multiOrNest = (context.options[0] === "multi-or-nest");
var consistent = (context.options[1] === "consistent");
//--------------------------------------------------------------------------
// Helpers
@ -30,45 +39,189 @@ module.exports = function(context) {
}
/**
* Checks the body of a node to see if it's a block statement. Depending on
* the rule options, reports the appropriate problems.
* Determines if a given node is a one-liner.
* @param {ASTNode} node The node to check.
* @returns {boolean} True if the node is a one-liner.
* @private
*/
function isOneLiner(node) {
var first = context.getFirstToken(node),
last = context.getLastToken(node);
return first.loc.start.line === last.loc.end.line;
}
/**
* Gets the `else` keyword token of a given `IfStatement` node.
* @param {ASTNode} node - A `IfStatement` node to get.
* @returns {Token} The `else` keyword token.
*/
function getElseKeyword(node) {
var sourceCode = context.getSourceCode();
var token = sourceCode.getTokenAfter(node.consequent);
while (token.type !== "Keyword" || token.value !== "else") {
token = sourceCode.getTokenAfter(token);
}
return token;
}
/**
* Checks a given IfStatement node requires braces of the consequent chunk.
* This returns `true` when below:
*
* 1. The given node has the `alternate` node.
* 2. There is a `IfStatement` which doesn't have `alternate` node in the
* trailing statement chain of the `consequent` node.
*
* @param {ASTNode} node - A IfStatement node to check.
* @returns {boolean} `true` if the node requires braces of the consequent chunk.
*/
function requiresBraceOfConsequent(node) {
if (node.alternate && node.consequent.type === "BlockStatement") {
if (node.consequent.body.length >= 2) {
return true;
}
node = node.consequent.body[0];
while (node) {
if (node.type === "IfStatement" && !node.alternate) {
return true;
}
node = astUtils.getTrailingStatement(node);
}
}
return false;
}
/**
* Reports "Expected { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportExpectedBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Expected { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Reports "Unnecessary { after ..." error
* @param {ASTNode} node The node to report.
* @param {string} name The name to report.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @private
*/
function reportUnnecessaryBraceError(node, name, suffix) {
context.report({
node: node,
loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
message: "Unnecessary { after '{{name}}'{{suffix}}.",
data: {
name: name,
suffix: (suffix ? " " + suffix : "")
}
});
}
/**
* Prepares to check the body of a node to see if it's a block statement.
* @param {ASTNode} node The node to report if there's a problem.
* @param {ASTNode} body The body node to check for blocks.
* @param {string} name The name to report if there's a problem.
* @param {string} suffix Additional string to add to the end of a report.
* @returns {void}
* @returns {object} a prepared check object, with "actual", "expected", "check" properties.
* "actual" will be `true` or `false` whether the body is already a block statement.
* "expected" will be `true` or `false` if the body should be a block statement or not, or
* `null` if it doesn't matter, depending on the rule options. It can be modified to change
* the final behavior of "check".
* "check" will be a function reporting appropriate problems depending on the other
* properties.
*/
function checkBody(node, body, name, suffix) {
function prepareCheck(node, body, name, suffix) {
var hasBlock = (body.type === "BlockStatement");
var expected = null;
if (multiOnly) {
if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) {
expected = true;
} else if (multiOnly) {
if (hasBlock && body.body.length === 1) {
context.report(node, "Unnecessary { after '{{name}}'{{suffix}}.",
{
name: name,
suffix: (suffix ? " " + suffix : "")
}
);
expected = false;
}
} else if (multiLine) {
if (!hasBlock && !isCollapsedOneLiner(body)) {
context.report(node, "Expected { after '{{name}}'{{suffix}}.",
{
name: name,
suffix: (suffix ? " " + suffix : "")
}
);
if (!isCollapsedOneLiner(body)) {
expected = true;
}
} else if (multiOrNest) {
if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
expected = false;
} else if (!isOneLiner(body)) {
expected = true;
}
} else {
if (!hasBlock) {
context.report(node, "Expected { after '{{name}}'{{suffix}}.",
{
name: name,
suffix: (suffix ? " " + suffix : "")
expected = true;
}
return {
actual: hasBlock,
expected: expected,
check: function() {
if (this.expected !== null && this.expected !== this.actual) {
if (this.expected) {
reportExpectedBraceError(node, name, suffix);
} else {
reportUnnecessaryBraceError(node, name, suffix);
}
);
}
}
};
}
/**
* Prepares to check the bodies of a "if", "else if" and "else" chain.
* @param {ASTNode} node The first IfStatement node of the chain.
* @returns {object[]} prepared checks for each body of the chain. See `prepareCheck` for more
* information.
*/
function prepareIfChecks(node) {
var preparedChecks = [];
do {
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
if (node.alternate && node.alternate.type !== "IfStatement") {
preparedChecks.push(prepareCheck(node, node.alternate, "else"));
break;
}
node = node.alternate;
} while (node);
if (consistent) {
// If any node should have or already have braces, make sure they all have braces.
// If all nodes shouldn't have braces, make sure they don't.
var expected = preparedChecks.some(function(preparedCheck) {
if (preparedCheck.expected !== null) {
return preparedCheck.expected;
}
return preparedCheck.actual;
});
preparedChecks.forEach(function(preparedCheck) {
preparedCheck.expected = expected;
});
}
return preparedChecks;
}
//--------------------------------------------------------------------------
@ -76,34 +229,66 @@ module.exports = function(context) {
//--------------------------------------------------------------------------
return {
"IfStatement": function(node) {
checkBody(node, node.consequent, "if", "condition");
if (node.alternate && node.alternate.type !== "IfStatement") {
checkBody(node, node.alternate, "else");
if (node.parent.type !== "IfStatement") {
prepareIfChecks(node).forEach(function(preparedCheck) {
preparedCheck.check();
});
}
},
"WhileStatement": function(node) {
checkBody(node, node.body, "while", "condition");
prepareCheck(node, node.body, "while", "condition").check();
},
"DoWhileStatement": function (node) {
checkBody(node, node.body, "do");
"DoWhileStatement": function(node) {
prepareCheck(node, node.body, "do").check();
},
"ForStatement": function(node) {
checkBody(node, node.body, "for", "condition");
prepareCheck(node, node.body, "for", "condition").check();
},
"ForInStatement": function(node) {
prepareCheck(node, node.body, "for-in").check();
},
"ForOfStatement": function(node) {
prepareCheck(node, node.body, "for-of").check();
}
};
};
module.exports.schema = [
{
"enum": ["all", "multi", "multi-line"]
}
];
module.exports.schema = {
"anyOf": [
{
"type": "array",
"items": [
{
"enum": [0, 1, 2]
},
{
"enum": ["all"]
}
],
"minItems": 1,
"maxItems": 2
},
{
"type": "array",
"items": [
{
"enum": [0, 1, 2]
},
{
"enum": ["multi", "multi-line", "multi-or-nest"]
},
{
"enum": ["consistent"]
}
],
"minItems": 1,
"maxItems": 3
}
]
};

25
tools/eslint/lib/rules/dot-location.js

@ -6,27 +6,18 @@
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function (context) {
module.exports = function(context) {
var config = context.options[0],
// default to onObject if no preference is passed
onObject = config === "object" || !config;
/**
* Checks whether two tokens are on the same line.
* @param {Object} left The leftmost token.
* @param {Object} right The rightmost token.
* @returns {boolean} True if the tokens are on the same line, false if not.
* @private
*/
function isSameLine(left, right) {
return left.loc.end.line === right.loc.start.line;
}
/**
* Reports if the dot between object and property is on the correct loccation.
* @param {ASTNode} obj The object owning the property.
@ -39,10 +30,10 @@ module.exports = function (context) {
if (dot.type === "Punctuator" && dot.value === ".") {
if (onObject) {
if (!isSameLine(obj, dot)) {
if (!astUtils.isTokenOnSameLine(obj, dot)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as object.");
}
} else if (!isSameLine(dot, prop)) {
} else if (!astUtils.isTokenOnSameLine(dot, prop)) {
context.report(node, dot.loc.start, "Expected dot to be on same line as property.");
}
}
@ -61,3 +52,9 @@ module.exports = function (context) {
"MemberExpression": checkNode
};
};
module.exports.schema = [
{
"enum": ["object", "property"]
}
];

62
tools/eslint/lib/rules/dot-notation.js

@ -9,67 +9,7 @@
//------------------------------------------------------------------------------
var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
var keywords = [
"this",
"function",
"if",
"return",
"var",
"else",
"for",
"new",
"in",
"typeof",
"while",
"case",
"break",
"try",
"catch",
"delete",
"throw",
"switch",
"continue",
"default",
"instanceof",
"do",
"void",
"finally",
"with",
"debugger",
"implements",
"interface",
"package",
"private",
"protected",
"public",
"static",
"class",
"enum",
"export",
"extends",
"import",
"super",
"true",
"false",
"null",
"abstract",
"boolean",
"byte",
"char",
"const",
"double",
"final",
"float",
"goto",
"int",
"long",
"native",
"short",
"synchronized",
"throws",
"transient",
"volatile"
];
var keywords = require("../util/keywords");
module.exports = function(context) {
var options = context.options[0] || {};

21
tools/eslint/lib/rules/eol-last.js

@ -18,8 +18,10 @@ module.exports = function(context) {
"Program": function checkBadEOF(node) {
// Get the whole source code, not for node only.
var src = context.getSource(), location = {column: 1};
var src = context.getSource(),
location = {column: 1},
linebreakStyle = context.options[0] || "unix",
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
if (src.length === 0) {
return;
}
@ -27,7 +29,14 @@ module.exports = function(context) {
if (src[src.length - 1] !== "\n") {
// file is not newline-terminated
location.line = src.split(/\n/g).length;
context.report(node, location, "Newline required at end of file but not found.");
context.report({
node: node,
loc: location,
message: "Newline required at end of file but not found.",
fix: function(fixer) {
return fixer.insertTextAfterRange([0, src.length], linebreak);
}
});
}
}
@ -35,4 +44,8 @@ module.exports = function(context) {
};
module.exports.schema = [];
module.exports.schema = [
{
"enum": ["unix", "windows"]
}
];

36
tools/eslint/lib/rules/eqeqeq.js

@ -1,6 +1,8 @@
/**
* @fileoverview Rule to flag statements that use != and == instead of !== and ===
* @author Nicholas C. Zakas
* @copyright 2013 Nicholas C. Zakas. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -11,6 +13,12 @@
module.exports = function(context) {
var sourceCode = context.getSourceCode(),
replacements = {
"==": "===",
"!=": "!=="
};
/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
@ -71,7 +79,7 @@ module.exports = function(context) {
}
if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
areLiteralsAndSameType(node)) || isNullCheck(node)) {
areLiteralsAndSameType(node) || isNullCheck(node))) {
return;
}
@ -79,11 +87,27 @@ module.exports = function(context) {
return;
}
context.report(
node, getOperatorLocation(node),
"Expected '{{op}}=' and instead saw '{{op}}'.",
{op: node.operator}
);
context.report({
node: node,
loc: getOperatorLocation(node),
message: "Expected '{{op}}=' and instead saw '{{op}}'.",
data: { op: node.operator },
fix: function(fixer) {
var tokens = sourceCode.getTokensBetween(node.left, node.right),
opToken,
i;
for (i = 0; i < tokens.length; ++i) {
if (tokens[i].value === node.operator) {
opToken = tokens[i];
break;
}
}
return fixer.replaceTextRange(opToken.range, replacements[node.operator]);
}
});
}
};

59
tools/eslint/lib/rules/func-style.js

@ -12,38 +12,73 @@
module.exports = function(context) {
var style = context.options[0],
enforceDeclarations = (style === "declaration");
allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions === true,
enforceDeclarations = (style === "declaration"),
stack = [];
return {
var nodesToCheck = {
"Program": function() {
stack = [];
},
"FunctionDeclaration": function(node) {
stack.push(false);
if (!enforceDeclarations) {
context.report(node, "Expected a function expression.");
}
},
"FunctionDeclaration:exit": function() {
stack.pop();
},
"FunctionExpression": function() {
var parent = context.getAncestors().pop();
"FunctionExpression": function(node) {
stack.push(false);
if (enforceDeclarations && parent.type === "VariableDeclarator") {
context.report(parent, "Expected a function declaration.");
if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
},
"FunctionExpression:exit": function() {
stack.pop();
},
"ArrowFunctionExpression": function() {
var parent = context.getAncestors().pop();
if (enforceDeclarations && parent.type === "VariableDeclarator") {
context.report(parent, "Expected a function declaration.");
"ThisExpression": function() {
if (stack.length > 0) {
stack[stack.length - 1] = true;
}
}
};
if (!allowArrowFunctions) {
nodesToCheck.ArrowFunctionExpression = function() {
stack.push(false);
};
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
var hasThisExpr = stack.pop();
if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
context.report(node.parent, "Expected a function declaration.");
}
};
}
return nodesToCheck;
};
module.exports.schema = [
{
"enum": ["declaration", "expression"]
},
{
"type": "object",
"properties": {
"allowArrowFunctions": {
"type": "boolean"
}
},
"additionalProperties": false
}
];

26
tools/eslint/lib/rules/generator-star-spacing.js

@ -14,7 +14,7 @@
module.exports = function(context) {
var mode = (function(option) {
if (option == null || typeof option === "string") {
if (!option || typeof option === "string") {
return {
before: { before: true, after: false },
after: { before: false, after: true },
@ -36,14 +36,24 @@ module.exports = function(context) {
*/
function checkSpacing(side, leftToken, rightToken) {
if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) {
context.report(
leftToken.value === "*" ? leftToken : rightToken,
"{{type}} space {{side}} *.",
{
type: mode[side] ? "Missing" : "Unexpected",
side: side
var after = leftToken.value === "*";
var spaceRequired = mode[side];
var node = after ? leftToken : rightToken;
var type = spaceRequired ? "Missing" : "Unexpected";
var message = type + " space " + side + " *.";
context.report({
node: node,
message: message,
fix: function(fixer) {
if (spaceRequired) {
if (after) {
return fixer.insertTextAfter(node, " ");
}
return fixer.insertTextBefore(node, " ");
}
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
);
});
}
}

76
tools/eslint/lib/rules/generator-star.js

@ -1,76 +0,0 @@
/**
* @fileoverview Rule to check for the position of the * in your generator functions
* @author Jamund Ferguson
* @copyright 2014 Jamund Ferguson. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var position = context.options[0] || "end";
/**
* Check the position of the star compared to the expected position.
* @param {ASTNode} node - the entire function node
* @returns {void}
*/
function checkStarPosition(node) {
var starToken;
if (!node.generator) {
return;
}
// Blocked, pending decision to fix or work around in eslint/espree#36
if (context.getAncestors().pop().method) {
return;
}
starToken = context.getFirstToken(node, 1);
// check for function *name() {}
if (position === "end") {
// * starts where the next identifier begins
if (starToken.range[1] !== context.getTokenAfter(starToken).range[0]) {
context.report(node, "Expected a space before *.");
}
}
// check for function* name() {}
if (position === "start") {
// * begins where the previous identifier ends
if (starToken.range[0] !== context.getTokenBefore(starToken).range[1]) {
context.report(node, "Expected no space before *.");
}
}
// check for function * name() {}
if (position === "middle") {
// must be a space before and afer the *
if (starToken.range[0] <= context.getTokenBefore(starToken).range[1] ||
starToken.range[1] >= context.getTokenAfter(starToken).range[0]) {
context.report(node, "Expected spaces around *.");
}
}
}
return {
"FunctionDeclaration": checkStarPosition,
"FunctionExpression": checkStarPosition
};
};
module.exports.schema = [
{
"enum": ["start", "middle", "end"]
}
];

35
tools/eslint/lib/rules/global-require.js

@ -0,0 +1,35 @@
/**
* @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
"use strict";
var ACCEPTABLE_PARENTS = [
"AssignmentExpression",
"VariableDeclarator",
"MemberExpression",
"ExpressionStatement",
"CallExpression",
"ConditionalExpression",
"Program",
"VariableDeclaration"
];
module.exports = function(context) {
return {
"CallExpression": function(node) {
if (node.callee.name === "require") {
var isGoodRequire = context.getAncestors().every(function(parent) {
return ACCEPTABLE_PARENTS.indexOf(parent.type) > -1;
});
if (!isGoodRequire) {
context.report(node, "Unexpected require().");
}
}
}
};
};
module.exports.schema = [];

49
tools/eslint/lib/rules/global-strict.js

@ -1,49 +0,0 @@
/**
* @fileoverview Rule to flag or require global strict mode.
* @author Nicholas C. Zakas
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var mode = context.options[0];
if (mode === "always") {
return {
"Program": function(node) {
if (node.body.length > 0) {
var statement = node.body[0];
if (!(statement.type === "ExpressionStatement" && statement.expression.value === "use strict")) {
context.report(node, "Use the global form of \"use strict\".");
}
}
}
};
} else { // mode = "never"
return {
"ExpressionStatement": function(node) {
var parent = context.getAncestors().pop();
if (node.expression.value === "use strict" && parent.type === "Program") {
context.report(node, "Use the function form of \"use strict\".");
}
}
};
}
};
module.exports.schema = [
{
"enum": ["always", "never"]
}
];

2
tools/eslint/lib/rules/handle-callback-err.js

@ -44,7 +44,7 @@ module.exports = function(context) {
* @returns {array} All parameters of the given scope.
*/
function getParameters(scope) {
return scope.variables.filter(function (variable) {
return scope.variables.filter(function(variable) {
return variable.defs[0] && variable.defs[0].type === "Parameter";
});
}

106
tools/eslint/lib/rules/id-length.js

@ -0,0 +1,106 @@
/**
* @fileoverview Rule that warns when identifier names are shorter or longer
* than the values provided in configuration.
* @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var options = context.options[0] || {};
var minLength = typeof options.min !== "undefined" ? options.min : 2;
var maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
var properties = options.properties !== "never";
var exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) {
obj[item] = true;
return obj;
}, {});
var SUPPORTED_EXPRESSIONS = {
"MemberExpression": properties && function(parent) {
return !parent.computed && (
// regular property assignment
parent.parent.left === parent || (
// or the last identifier in an ObjectPattern destructuring
parent.parent.type === "Property" && parent.parent.value === parent &&
parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent
)
);
},
"AssignmentPattern": function(parent, node) {
return parent.left === node;
},
"VariableDeclarator": function(parent, node) {
return parent.id === node;
},
"Property": properties && function(parent, node) {
return parent.key === node;
},
"ImportDefaultSpecifier": true,
"RestElement": true,
"FunctionExpression": true,
"ArrowFunctionExpression": true,
"ClassDeclaration": true,
"FunctionDeclaration": true,
"MethodDefinition": true,
"CatchClause": true
};
return {
Identifier: function(node) {
var name = node.name;
var parent = node.parent;
var isShort = name.length < minLength;
var isLong = name.length > maxLength;
if (!(isShort || isLong) || exceptions[name]) {
return; // Nothing to report
}
var isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) {
context.report(
node,
isShort ?
"Identifier name '{{name}}' is too short. (< {{min}})" :
"Identifier name '{{name}}' is too long. (> {{max}})",
{ name: name, min: minLength, max: maxLength }
);
}
}
};
};
module.exports.schema = [
{
"type": "object",
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"exceptions": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
}
},
"properties": {
"enum": ["always", "never"]
}
},
"additionalProperties": false
}
];

129
tools/eslint/lib/rules/id-match.js

@ -0,0 +1,129 @@
/**
* @fileoverview Rule to flag non-matching identifiers
* @author Matthieu Larcher
* @copyright 2015 Matthieu Larcher. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
var pattern = context.options[0] || "^.+$",
regexp = new RegExp(pattern);
var options = context.options[1] || {},
properties = options.properties;
// cast to boolean and default to false
properties = !!properties;
/**
* Checks if a string matches the provided pattern
* @param {String} name The string to check.
* @returns {boolean} if the string is a match
* @private
*/
function isInvalid(name) {
return !regexp.test(name);
}
/**
* Verifies if we should report an error or not based on the effective
* parent node and the identifier name.
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported
* @param {String} name The identifier name of the identifier node
* @returns {boolean} whether an error should be reported or not
*/
function shouldReport(effectiveParent, name) {
return effectiveParent.type !== "CallExpression"
&& effectiveParent.type !== "NewExpression" &&
isInvalid(name);
}
/**
* Reports an AST node as a rule violation.
* @param {ASTNode} node The node to report.
* @returns {void}
* @private
*/
function report(node) {
context.report(node, "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", {
name: node.name,
pattern: pattern
});
}
return {
"Identifier": function(node) {
var name = node.name,
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// return early if properties is false
if (!properties) {
return;
}
// Always check object names
if (node.parent.object.type === "Identifier" &&
node.parent.object.name === node.name) {
if (isInvalid(name)) {
report(node);
}
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" &&
(effectiveParent.right.type !== "MemberExpression" ||
effectiveParent.left.type === "MemberExpression" &&
effectiveParent.left.property.name === node.name)) {
if (isInvalid(name)) {
report(node);
}
}
// Properties have their own rules
} else if (node.parent.type === "Property") {
// return early if properties is false
if (!properties) {
return;
}
if (shouldReport(effectiveParent, name)) {
report(node);
}
// Report anything that is a match and not a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
}
};
};
module.exports.schema = [
{
"type": "string"
},
{
"type": "object",
"properties": {
"properties": {
"type": "boolean"
}
}
}
];

868
tools/eslint/lib/rules/indent.js

File diff suppressed because it is too large

120
tools/eslint/lib/rules/init-declarations.js

@ -0,0 +1,120 @@
/**
* @fileoverview A rule to control the style of variable initializations.
* @author Colin Ihrig
* @copyright 2015 Colin Ihrig. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a given node is a for loop.
* @param {ASTNode} block - A node to check.
* @returns {boolean} `true` when the node is a for loop.
*/
function isForLoop(block) {
return block.type === "ForInStatement" ||
block.type === "ForOfStatement" ||
block.type === "ForStatement";
}
/**
* Checks whether or not a given declarator node has its initializer.
* @param {ASTNode} node - A declarator node to check.
* @returns {boolean} `true` when the node has its initializer.
*/
function isInitialized(node) {
var declaration = node.parent;
var block = declaration.parent;
if (isForLoop(block)) {
if (block.type === "ForStatement") {
return block.init === declaration;
}
return block.left === declaration;
}
return Boolean(node.init);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var MODE_ALWAYS = "always",
MODE_NEVER = "never";
var mode = context.options[0] || MODE_ALWAYS;
var params = context.options[1] || {};
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"VariableDeclaration:exit": function(node) {
var kind = node.kind,
declarations = node.declarations;
for (var i = 0; i < declarations.length; ++i) {
var declaration = declarations[i],
id = declaration.id,
initialized = isInitialized(declaration),
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
if (id.type !== "Identifier") {
continue;
}
if (mode === MODE_ALWAYS && !initialized) {
context.report(declaration, "Variable '" + id.name + "' should be initialized on declaration.");
} else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) {
context.report(declaration, "Variable '" + id.name + "' should not be initialized on declaration.");
}
}
}
};
};
module.exports.schema = {
"anyOf": [
{
"type": "array",
"items": [
{
"enum": [0, 1, 2]
},
{
"enum": ["always"]
}
],
"minItems": 1,
"maxItems": 2
},
{
"type": "array",
"items": [
{
"enum": [0, 1, 2]
},
{
"enum": ["never"]
},
{
"type": "object",
"properties": {
"ignoreForLoopInit": {
"type": "boolean"
}
},
"additionalProperties": false
}
],
"minItems": 1,
"maxItems": 3
}
]
};

63
tools/eslint/lib/rules/jsx-quotes.js

@ -0,0 +1,63 @@
/**
* @fileoverview A rule to ensure consistent quotes used in jsx syntax.
* @author Mathias Schreck <https://github.com/lo1tuma>
* @copyright 2015 Mathias Schreck
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
var QUOTE_SETTINGS = {
"prefer-double": {
quote: "\"",
description: "singlequote"
},
"prefer-single": {
quote: "'",
description: "doublequote"
}
};
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var quoteOption = context.options[0] || "prefer-double",
setting = QUOTE_SETTINGS[quoteOption];
/**
* Checks if the given string literal node uses the expected quotes
* @param {ASTNode} node - A string literal node.
* @returns {boolean} Whether or not the string literal used the expected quotes.
* @public
*/
function usesExpectedQuotes(node) {
return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote);
}
return {
"JSXAttribute": function(node) {
var attributeValue = node.value;
if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) {
context.report(attributeValue, "Unexpected usage of {{description}}.", setting);
}
}
};
};
module.exports.schema = [
{
"enum": [ "prefer-single", "prefer-double" ]
}
];

50
tools/eslint/lib/rules/key-spacing.js

@ -94,9 +94,27 @@ module.exports = function(context) {
var options = context.options[0] || {},
align = options.align,
mode = options.mode || "strict",
beforeColon = +!!options.beforeColon, // Defaults to false
afterColon = +!(options.afterColon === false); // Defaults to true
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the last token before a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
var prevNode;
while (node && (node.type !== "Punctuator" || node.value !== ":")) {
prevNode = node;
node = context.getTokenAfter(node);
}
return prevNode;
}
/**
* Gets an object literal property's key as the identifier name or string value.
* @param {ASTNode} property Property node whose key to retrieve.
@ -127,7 +145,9 @@ module.exports = function(context) {
firstTokenAfterColon = context.getTokenAfter(key, 1),
location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
if (diff && !(expected && containsLineTerminator(whitespace))) {
if ((diff && mode === "strict" || diff < 0 && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
context.report(property[side], location, messages[side], {
error: diff > 0 ? "Extra" : "Missing",
computed: property.computed ? "computed " : "",
@ -143,26 +163,17 @@ module.exports = function(context) {
* @returns {int} Width of the key.
*/
function getKeyWidth(property) {
var key = property.key,
startToken, endToken;
var startToken, endToken;
// [computed]: value
if (property.computed) {
startToken = context.getTokenBefore(key);
endToken = context.getTokenAfter(key);
return endToken.range[1] - startToken.range[0];
// Ignore shorthand methods and properties, as they have no colon
if (property.method || property.shorthand) {
return 0;
}
// name: value
if (key.type === "Identifier") {
return key.name.length;
}
startToken = context.getFirstToken(property);
endToken = getLastTokenBeforeColon(property.key);
// "literal": value
// 42: value
if (key.type === "Literal") {
return key.raw.length;
}
return endToken.range[1] - startToken.range[0];
}
/**
@ -297,7 +308,7 @@ module.exports = function(context) {
} else { // Strictly obey beforeColon and afterColon in each property
return {
"Property": function (node) {
"Property": function(node) {
verifySpacing(node);
}
};
@ -313,6 +324,9 @@ module.exports.schema = [
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},

57
tools/eslint/lib/rules/linebreak-style.js

@ -12,26 +12,61 @@
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function (context) {
module.exports = function(context) {
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Builds a fix function that replaces text at the specified range in the source text.
* @param {int[]} range The range to replace
* @param {string} text The text to insert.
* @returns {function} Fixer function
* @private
*/
function createFix(range, text) {
return function(fixer) {
return fixer.replaceTextRange(range, text);
};
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"Program": function checkForlinebreakStyle(node) {
var linebreakStyle = context.options[0] || "unix",
expectedLF = linebreakStyle === "unix",
linebreaks = context.getSource().match(/\r\n|\r|\n|\u2028|\u2029/g),
lineOfError = -1;
expectedLFChars = expectedLF ? "\n" : "\r\n",
source = context.getSource(),
pattern = /\r\n|\r|\n|\u2028|\u2029/g,
match,
index,
range;
if (linebreaks !== null) {
lineOfError = linebreaks.indexOf(expectedLF ? "\r\n" : "\n");
}
var i = 0;
while ((match = pattern.exec(source)) !== null) {
i++;
if (match[0] === expectedLFChars) {
continue;
}
if (lineOfError !== -1) {
context.report(node, {
line: lineOfError + 1,
column: context.getSourceLines()[lineOfError].length
}, expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG);
index = match.index;
range = [index, index + match[0].length];
context.report({
node: node,
loc: {
line: i,
column: context.getSourceLines()[i - 1].length
},
message: expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG,
fix: createFix(range, expectedLFChars)
});
}
}
};

156
tools/eslint/lib/rules/lines-around-comment.js

@ -1,10 +1,22 @@
/**
* @fileoverview Enforces empty lines around comments.
* @author Jamund Ferguson
* @copyright 2015 Mathieu M-Gosselin. All rights reserved.
* @copyright 2015 Jamund Ferguson. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var assign = require("object-assign");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Return an array with with any line numbers that are empty.
* @param {Array} lines An array of each line of the file.
@ -31,7 +43,7 @@ function getEmptyLineNums(lines) {
*/
function getCommentLineNums(comments) {
var lines = [];
comments.forEach(function (token) {
comments.forEach(function(token) {
var start = token.loc.start.line;
var end = token.loc.end.line;
lines.push(start, end);
@ -55,7 +67,7 @@ function contains(val, array) {
module.exports = function(context) {
var options = context.options[0] || {};
var options = context.options[0] ? assign({}, context.options[0]) : {};
options.beforeLineComment = options.beforeLineComment || false;
options.afterLineComment = options.afterLineComment || false;
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
@ -63,60 +75,139 @@ module.exports = function(context) {
options.allowBlockStart = options.allowBlockStart || false;
options.allowBlockEnd = options.allowBlockEnd || false;
var sourceCode = context.getSourceCode();
/**
* Returns whether or not comments are not on lines starting with or ending with code
* Returns whether or not comments are on lines starting with or ending with code
* @param {ASTNode} node The comment node to check.
* @returns {boolean} True if the comment is not alone.
*/
function codeAroundComment(node) {
var token;
var lines = context.getSourceLines();
token = node;
do {
token = sourceCode.getTokenOrCommentBefore(token);
} while (token && (token.type === "Block" || token.type === "Line"));
// Get the whole line and cut it off at the start of the comment
var startLine = lines[node.loc.start.line - 1];
var endLine = lines[node.loc.end.line - 1];
if (token && token.loc.end.line === node.loc.start.line) {
return true;
}
var preamble = startLine.slice(0, node.loc.start.column).trim();
token = node;
do {
token = sourceCode.getTokenOrCommentAfter(token);
} while (token && (token.type === "Block" || token.type === "Line"));
// Also check after the comment
var postamble = endLine.slice(node.loc.end.column).trim();
if (token && token.loc.start.line === node.loc.end.line) {
return true;
}
// Should be false if there was only whitespace around the comment
return !!(preamble || postamble);
return false;
}
/**
* Returns whether or not comments are at the block start or not.
* Returns whether or not comments are inside a node type or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block start.
* @param {ASTNode} parent The Comment parent node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is inside nodeType.
*/
function isCommentAtBlockStart(node) {
function isCommentInsideNodeType(node, parent, nodeType) {
return parent.type === nodeType ||
(parent.body && parent.body.type === nodeType) ||
(parent.consequent && parent.consequent.type === nodeType);
}
/**
* Returns whether or not comments are at the parent start or not.
* @param {ASTNode} node The Comment node.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent start.
*/
function isCommentAtParentStart(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
}
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
node.loc.start.line - parent.loc.start.line === 1;
}
/**
* Returns whether or not comments are at the block end or not.
* Returns whether or not comments are at the parent end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block end.
* @param {string} nodeType The parent type to check against.
* @returns {boolean} True if the comment is at parent end.
*/
function isCommentAtBlockEnd(node) {
function isCommentAtParentEnd(node, nodeType) {
var ancestors = context.getAncestors();
var parent;
if (ancestors.length) {
parent = ancestors.pop();
}
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
parent.loc.end.line - node.loc.end.line === 1;
}
/**
* Returns whether or not comments are at the block start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block start.
*/
function isCommentAtBlockStart(node) {
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement");
}
/**
* Returns whether or not comments are at the block end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at block end.
*/
function isCommentAtBlockEnd(node) {
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement");
}
/**
* Returns whether or not comments are at the object start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object start.
*/
function isCommentAtObjectStart(node) {
return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern");
}
/**
* Returns whether or not comments are at the object end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at object end.
*/
function isCommentAtObjectEnd(node) {
return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern");
}
/**
* Returns whether or not comments are at the array start or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array start.
*/
function isCommentAtArrayStart(node) {
return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern");
}
/**
* Returns whether or not comments are at the array end or not.
* @param {ASTNode} node The Comment node.
* @returns {boolean} True if the comment is at array end.
*/
function isCommentAtArrayEnd(node) {
return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern");
}
/**
* Checks if a comment node has lines around it (ignores inline comments)
* @param {ASTNode} node The Comment node.
@ -142,7 +233,14 @@ module.exports = function(context) {
commentIsNotAlone = codeAroundComment(node);
var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node);
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node),
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node),
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node),
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node),
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node);
var exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
var exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
// ignore top of the file and bottom of the file
if (prevLineNum < 1) {
@ -158,12 +256,12 @@ module.exports = function(context) {
}
// check for newline before
if (!blockStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
if (!exceptionStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line before comment.");
}
// check for newline after
if (!blockEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
if (!exceptionEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
context.report(node, "Expected line after comment.");
}
@ -217,6 +315,18 @@ module.exports.schema = [
},
"allowBlockEnd": {
"type": "boolean"
},
"allowObjectStart": {
"type": "boolean"
},
"allowObjectEnd": {
"type": "boolean"
},
"allowArrayStart": {
"type": "boolean"
},
"allowArrayEnd": {
"type": "boolean"
}
},
"additionalProperties": false

21
tools/eslint/lib/rules/max-depth.js

@ -19,14 +19,30 @@ module.exports = function(context) {
var functionStack = [],
maxDepth = context.options[0] || 4;
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
/**
* When parsing is done then pop out the reference
* @returns {void}
* @private
*/
function endFunction() {
functionStack.pop();
}
/**
* Save the block and Evaluate the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function pushBlock(node) {
var len = ++functionStack[functionStack.length - 1];
@ -36,6 +52,11 @@ module.exports = function(context) {
}
}
/**
* Pop the saved block
* @returns {void}
* @private
*/
function popBlock() {
functionStack[functionStack.length - 1]--;
}

103
tools/eslint/lib/rules/max-len.js

@ -11,6 +11,12 @@
//------------------------------------------------------------------------------
module.exports = function(context) {
// takes some ideas from http://tools.ietf.org/html/rfc3986#appendix-B, however:
// - They're matching an entire string that we know is a URI
// - We're matching part of a string where we think there *might* be a URL
// - We're only concerned about URLs, as picking out any URI would cause too many false positives
// - We don't care about matching the entire URL, any small segment is fine
var URL_REGEXP = /[^:/?#]:\/\/[^?#]/;
/**
* Creates a string that is made up of repeating a given string a certain
@ -32,23 +38,91 @@ module.exports = function(context) {
return result;
}
var tabWidth = context.options[1] || 4;
var maxLength = context.options[0] || 80,
tabWidth = context.options[1] || 4,
ignoreOptions = context.options[2] || {},
ignorePattern = ignoreOptions.ignorePattern || null,
ignoreComments = ignoreOptions.ignoreComments || false,
ignoreUrls = ignoreOptions.ignoreUrls || false,
tabString = stringRepeat(" ", tabWidth);
if (ignorePattern) {
ignorePattern = new RegExp(ignorePattern);
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Tells if a given comment is trailing: it starts on the current line and
* extends to or past the end of the current line.
* @param {string} line The source line we want to check for a trailing comment on
* @param {number} lineNumber The one-indexed line number for line
* @param {ASTNode} comment The comment to inspect
* @returns {boolean} If the comment is trailing on the given line
*/
function isTrailingComment(line, lineNumber, comment) {
return comment &&
(comment.loc.start.line <= lineNumber && lineNumber <= comment.loc.end.line) &&
(comment.loc.end.line > lineNumber || comment.loc.end.column === line.length);
}
/**
* Gets the line after the comment and any remaining trailing whitespace is
* stripped.
* @param {string} line The source line with a trailing comment
* @param {number} lineNumber The one-indexed line number this is on
* @param {ASTNode} comment The comment to remove
* @returns {string} Line without comment and trailing whitepace
*/
function stripTrailingComment(line, lineNumber, comment) {
if (comment.loc.start.line < lineNumber) {
// this entire line is a comment
return "";
} else {
// loc.column is zero-indexed
return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
}
}
/**
* Check the program for max length
* @param {ASTNode} node Node to examine
* @returns {void}
* @private
*/
function checkProgramForMaxLength(node) {
var lines = context.getSourceLines();
// split (honors line-ending)
var lines = context.getSourceLines(),
// list of comments to ignore
comments = ignoreComments ? context.getAllComments() : [],
// we iterate over comments in parallel with the lines
commentsIndex = 0;
// Replace the tabs
// Split (honors line-ending)
// Iterate
lines.forEach(function(line, i) {
// i is zero-indexed, line numbers are one-indexed
var lineNumber = i + 1;
// we can short-circuit the comment checks if we're already out of comments to check
if (commentsIndex < comments.length) {
// iterate over comments until we find one past the current line
do {
var comment = comments[++commentsIndex];
} while (comment && comment.loc.start.line <= lineNumber);
// and step back by one
comment = comments[--commentsIndex];
if (isTrailingComment(line, lineNumber, comment)) {
line = stripTrailingComment(line, lineNumber, comment);
}
}
if (ignorePattern && ignorePattern.test(line) ||
ignoreUrls && URL_REGEXP.test(line)) {
// ignore this line
return;
}
// replace the tabs
if (line.replace(/\t/g, tabString).length > maxLength) {
context.report(node, { line: i + 1, col: 1 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + ".");
context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + ".");
}
});
}
@ -72,5 +146,20 @@ module.exports.schema = [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"ignorePattern": {
"type": "string"
},
"ignoreComments": {
"type": "boolean"
},
"ignoreUrls": {
"type": "boolean"
}
},
"additionalProperties": false
}
];

2
tools/eslint/lib/rules/max-nested-callbacks.js

@ -16,7 +16,7 @@ module.exports = function(context) {
// Constants
//--------------------------------------------------------------------------
var THRESHOLD = context.options[0];
var THRESHOLD = context.options[0] || 10;
//--------------------------------------------------------------------------
// Helpers

17
tools/eslint/lib/rules/max-statements.js

@ -19,10 +19,21 @@ module.exports = function(context) {
var functionStack = [],
maxStatements = context.options[0] || 10;
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
functionStack.push(0);
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
var count = functionStack.pop();
@ -32,6 +43,12 @@ module.exports = function(context) {
}
}
/**
* Increment the count of the functions
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function countStatements(node) {
functionStack[functionStack.length - 1] += node.body.length;
}

23
tools/eslint/lib/rules/new-cap.js

@ -7,6 +7,16 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var assign = require("object-assign");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
var CAPS_ALLOWED = [
"Array",
"Boolean",
@ -28,6 +38,7 @@ var CAPS_ALLOWED = [
* @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
*/
function checkArray(obj, key, fallback) {
/* istanbul ignore if */
if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
throw new TypeError(key + ", if provided, must be an Array");
}
@ -66,9 +77,10 @@ function calculateCapIsNewExceptions(config) {
module.exports = function(context) {
var config = context.options[0] || {};
var config = context.options[0] ? assign({}, context.options[0]) : {};
config.newIsCap = config.newIsCap !== false;
config.capIsNew = config.capIsNew !== false;
var skipProperties = config.properties === false;
var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
@ -134,15 +146,17 @@ module.exports = function(context) {
* @returns {Boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName]) {
if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) {
return true;
}
if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
// allow if callee is Date.UTC
return node.callee.object.type === "Identifier" &&
node.callee.object.name === "Date";
}
return false;
return skipProperties && node.callee.type === "MemberExpression";
}
/**
@ -217,6 +231,9 @@ module.exports.schema = [
"items": {
"type": "string"
}
},
"properties": {
"type": "boolean"
}
},
"additionalProperties": false

61
tools/eslint/lib/rules/newline-after-var.js

@ -3,6 +3,7 @@
* @author Gopal Venkatesan
* @copyright 2015 Gopal Venkatesan. All rights reserved.
* @copyright 2015 Casey Visco. All rights reserved.
* @copyright 2015 Ian VanSchooten. All rights reserved.
*/
"use strict";
@ -20,10 +21,11 @@ module.exports = function(context) {
// be treated as "always" and the only special case is "never"
var mode = context.options[0] === "never" ? "never" : "always";
// Cache line numbers of comments for faster lookup
var comments = context.getAllComments().map(function (token) {
return token.loc.start.line;
});
// Cache starting and ending line numbers of comments for faster lookup
var commentEndLine = context.getAllComments().reduce(function(result, token) {
result[token.loc.start.line] = token.loc.end.line;
return result;
}, {});
//--------------------------------------------------------------------------
@ -61,6 +63,42 @@ module.exports = function(context) {
nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
}
/**
* Determine if provided nodeType is a function specifier
* @private
* @param {string} nodeType - nodeType to test
* @returns {boolean} True if `nodeType` is a function specifier
*/
function isFunctionSpecifier(nodeType) {
return nodeType === "FunctionDeclaration" || nodeType === "FunctionExpression" ||
nodeType === "ArrowFunctionExpression";
}
/**
* Determine if provided node is the last of his parent
* @private
* @param {ASTNode} node - node to test
* @returns {boolean} True if `node` is last of his parent
*/
function isLastNode(node) {
return node.parent.body[node.parent.body.length - 1] === node;
}
/**
* Determine if a token starts more than one line after a comment ends
* @param {token} token The token being checked
* @param {integer} commentStartLine The line number on which the comment starts
* @returns {boolean} True if `token` does not start immediately after a comment
*/
function hasBlankLineAfterComment(token, commentStartLine) {
var commentEnd = commentEndLine[commentStartLine];
// If there's another comment, repeat check for blank line
if (commentEndLine[commentEnd + 1]) {
return hasBlankLineAfterComment(token, commentEnd + 1);
}
return (token.loc.start.line > commentEndLine[commentStartLine] + 1);
}
/**
* Checks that a blank line exists after a variable declaration when mode is
* set to "always", or checks that there is no blank line when mode is set
@ -97,15 +135,26 @@ module.exports = function(context) {
return;
}
// Ignore if it is last statement in a function
if (node.parent.parent && isFunctionSpecifier(node.parent.parent.type) && isLastNode(node)) {
return;
}
// Next statement is not a `var`...
noNextLineToken = nextToken.loc.start.line > nextLineNum;
hasNextLineComment = comments.indexOf(nextLineNum) >= 0;
hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
if (mode === "never" && noNextLineToken && !hasNextLineComment) {
context.report(node, NEVER_MESSAGE, { identifier: node.name });
}
if (mode === "always" && (!noNextLineToken || hasNextLineComment)) {
// Token on the next line, or comment without blank line
if (
mode === "always" && (
!noNextLineToken ||
hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
)
) {
context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
}
}

9
tools/eslint/lib/rules/no-alert.js

@ -52,7 +52,7 @@ function getPropertyName(memberExpressionNode) {
* @returns {Reference|undefined} Returns the found reference or undefined if none were found.
*/
function findReference(scope, node) {
var references = scope.references.filter(function (reference) {
var references = scope.references.filter(function(reference) {
return reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1];
});
@ -69,9 +69,8 @@ function findReference(scope, node) {
* @returns {boolean} Whether or not the name is shadowed globally.
*/
function isGloballyShadowed(globalScope, identifierName) {
return globalScope.variables.some(function (variable) {
return variable.name === identifierName && variable.defs.length > 0;
});
var variable = globalScope.set.get(identifierName);
return Boolean(variable && variable.defs.length > 0);
}
/**
@ -120,7 +119,7 @@ module.exports = function(context) {
return {
"Program": function () {
"Program": function() {
globalScope = context.getScope();
},

6
tools/eslint/lib/rules/no-array-constructor.js

@ -11,6 +11,12 @@
module.exports = function(context) {
/**
* Disallow construction of dense arrays using the Array constructor
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function check(node) {
if (
node.arguments.length !== 1 &&

88
tools/eslint/lib/rules/no-arrow-condition.js

@ -0,0 +1,88 @@
/**
* @fileoverview A rule to warn against using arrow functions in conditions.
* @author Jxck <https://github.com/Jxck>
* @copyright 2015 Luke Karrys. All rights reserved.
* The MIT License (MIT)
* Copyright (c) 2015 Jxck
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a node is an arrow function expression.
* @param {ASTNode} node - node to test
* @returns {boolean} `true` if the node is an arrow function expression.
*/
function isArrowFunction(node) {
return node.test && node.test.type === "ArrowFunctionExpression";
}
/**
* Checks whether or not a node is a conditional expression.
* @param {ASTNode} node - node to test
* @returns {boolean} `true` if the node is a conditional expression.
*/
function isConditional(node) {
return node.body && node.body.type === "ConditionalExpression";
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Reports if a conditional statement is an arrow function.
* @param {ASTNode} node - A node to check and report.
* @returns {void}
*/
function checkCondition(node) {
if (isArrowFunction(node)) {
context.report(node, "Arrow function `=>` used inside {{statementType}} instead of comparison operator.", {statementType: node.type});
}
}
/**
* Reports if an arrow function contains an ambiguous conditional.
* @param {ASTNode} node - A node to check and report.
* @returns {void}
*/
function checkArrowFunc(node) {
if (isConditional(node)) {
context.report(node, "Arrow function used ambiguously with a conditional expression.");
}
}
return {
"IfStatement": checkCondition,
"WhileStatement": checkCondition,
"ForStatement": checkCondition,
"ConditionalExpression": checkCondition,
"ArrowFunctionExpression": checkArrowFunc
};
};
module.exports.schema = [];

47
tools/eslint/lib/rules/no-case-declarations.js

@ -0,0 +1,47 @@
/**
* @fileoverview Rule to flag use of an lexical declarations inside a case clause
* @author Erik Arvidsson
* @copyright 2015 Erik Arvidsson. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Checks whether or not a node is a lexical declaration.
* @param {ASTNode} node A direct child statement of a switch case.
* @returns {boolean} Whether or not the node is a lexical declaration.
*/
function isLexicalDeclaration(node) {
switch (node.type) {
case "FunctionDeclaration":
case "ClassDeclaration":
return true;
case "VariableDeclaration":
return node.kind !== "var";
default:
return false;
}
}
return {
"SwitchCase": function(node) {
for (var i = 0; i < node.consequent.length; i++) {
var statement = node.consequent[i];
if (isLexicalDeclaration(statement)) {
context.report({
node: node,
message: "Unexpected lexical declaration in case block."
});
}
}
}
};
};
module.exports.schema = [];

32
tools/eslint/lib/rules/no-catch-shadow.js

@ -5,6 +5,12 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@ -15,20 +21,14 @@ module.exports = function(context) {
// Helpers
//--------------------------------------------------------------------------
/**
* Check if the parameters are been shadowed
* @param {object} scope current scope
* @param {string} name parameter name
* @returns {boolean} True is its been shadowed
*/
function paramIsShadowing(scope, name) {
var found = scope.variables.some(function(variable) {
return variable.name === name;
});
if (found) {
return true;
}
if (scope.upper) {
return paramIsShadowing(scope.upper, name);
}
return false;
return astUtils.getVariableByName(scope, name) !== null;
}
//--------------------------------------------------------------------------
@ -40,6 +40,12 @@ module.exports = function(context) {
"CatchClause": function(node) {
var scope = context.getScope();
// When blockBindings is enabled, CatchClause creates its own scope
// so start from one upper scope to exclude the current node
if (scope.block === node) {
scope = scope.upper;
}
if (paramIsShadowing(scope, node.param.name)) {
context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.",
{ name: node.param.name });

48
tools/eslint/lib/rules/no-class-assign.js

@ -0,0 +1,48 @@
/**
* @fileoverview A rule to disallow modifying variables of class declarations
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"`{{name}}` is a class.",
{name: reference.identifier.name});
});
}
/**
* Finds and reports references that are non initializer and writable.
* @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check.
* @returns {void}
*/
function checkForClass(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
}
return {
"ClassDeclaration": checkForClass,
"ClassExpression": checkForClass
};
};
module.exports.schema = [];

45
tools/eslint/lib/rules/no-comma-dangle.js

@ -1,45 +0,0 @@
/**
* @fileoverview Rule to flag trailing commas in object literals.
* @author Ian Christian Myers
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
//-------------------------------------------------------------------------
// Helpers
//-------------------------------------------------------------------------
function checkForTrailingComma(node) {
var items = node.properties || node.elements,
length = items.length,
lastItem, penultimateToken;
if (length) {
lastItem = items[length - 1];
if (lastItem) {
penultimateToken = context.getLastToken(node, 1);
if (penultimateToken.value === ",") {
context.report(lastItem, penultimateToken.loc.start, "Trailing comma.");
}
}
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
"ObjectExpression": checkForTrailingComma,
"ArrayExpression": checkForTrailingComma
};
};
module.exports.schema = [];

18
tools/eslint/lib/rules/no-cond-assign.js

@ -38,11 +38,11 @@ module.exports = function(context) {
function findConditionalAncestor(node) {
var currentAncestor = node;
while ((currentAncestor = currentAncestor.parent)) {
do {
if (isConditionalTestExpression(currentAncestor)) {
return currentAncestor.parent;
}
}
} while ((currentAncestor = currentAncestor.parent));
return null;
}
@ -80,9 +80,19 @@ module.exports = function(context) {
* @returns {void}
*/
function testForAssign(node) {
if (node.test && (node.test.type === "AssignmentExpression") && !isParenthesisedTwice(node.test)) {
if (node.test &&
(node.test.type === "AssignmentExpression") &&
(node.type === "ForStatement" ?
!isParenthesised(node.test) :
!isParenthesisedTwice(node.test)
)
) {
// must match JSHint's error message
context.report(node, "Expected a conditional expression and instead saw an assignment.");
context.report({
node: node,
loc: node.test.loc.start,
message: "Expected a conditional expression and instead saw an assignment."
});
}
}

41
tools/eslint/lib/rules/no-const-assign.js

@ -0,0 +1,41 @@
/**
* @fileoverview A rule to disallow modifying variables that are declared using `const`
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"`{{name}}` is constant.",
{name: reference.identifier.name});
});
}
return {
"VariableDeclaration": function(node) {
if (node.kind === "const") {
context.getDeclaredVariables(node).forEach(checkVariable);
}
}
};
};
module.exports.schema = [];

6
tools/eslint/lib/rules/no-control-regex.js

@ -11,6 +11,12 @@
module.exports = function(context) {
/**
* Get the regex expression
* @param {ASTNode} node node to evaluate
* @returns {*} Regex if found else null
* @private
*/
function getRegExp(node) {
if (node.value instanceof RegExp) {

76
tools/eslint/lib/rules/no-dupe-args.js

@ -2,6 +2,8 @@
* @fileoverview Rule to flag duplicate arguments
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -16,6 +18,15 @@ module.exports = function(context) {
// Helpers
//--------------------------------------------------------------------------
/**
* Checks whether or not a given definition is a parameter's.
* @param {escope.DefEntry} def - A definition to check.
* @returns {boolean} `true` if the definition is a parameter's.
*/
function isParameter(def) {
return def.type === "Parameter";
}
/**
* Determines if a given node has duplicate parameters.
* @param {ASTNode} node The node to check.
@ -23,56 +34,29 @@ module.exports = function(context) {
* @private
*/
function checkParams(node) {
var params = {},
dups = {};
var variables = context.getDeclaredVariables(node);
var keyMap = Object.create(null);
for (var i = 0; i < variables.length; ++i) {
var variable = variables[i];
/**
* Marks a given param as either seen or duplicated.
* @param {string} name The name of the param to mark.
* @returns {void}
* @private
*/
function markParam(name) {
if (params.hasOwnProperty(name)) {
dups[name] = 1;
} else {
params[name] = 1;
// TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79
var key = "$" + variable.name; // to avoid __proto__.
if (!isParameter(variable.defs[0]) || keyMap[key]) {
continue;
}
}
// loop through and find each duplicate param
node.params.forEach(function(param) {
switch (param.type) {
case "Identifier":
markParam(param.name);
break;
case "ObjectPattern":
param.properties.forEach(function(property) {
markParam(property.key.name);
});
break;
case "ArrayPattern":
param.elements.forEach(function(element) {
// Arrays can be sparse (unwanted arguments)
if (element !== null) {
markParam(element.name);
}
});
break;
// no default
keyMap[key] = true;
// Checks and reports duplications.
var defs = variable.defs.filter(isParameter);
if (defs.length >= 2) {
context.report({
node: node,
message: "Duplicate param '{{name}}'.",
data: {name: variable.name}
});
}
});
// log an error for each duplicate (not 2 for each)
Object.keys(dups).forEach(function(currentParam) {
context.report(node, "Duplicate param '{{key}}'.", { key: currentParam });
});
}
}
//--------------------------------------------------------------------------

82
tools/eslint/lib/rules/no-dupe-class-members.js

@ -0,0 +1,82 @@
/**
* @fileoverview A rule to disallow duplicate name in class members.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var stack = [];
/**
* Gets state of a given member name.
* @param {string} name - A name of a member.
* @param {boolean} isStatic - A flag which specifies that is a static member.
* @returns {object} A state of a given member name.
* - retv.init {boolean} A flag which shows the name is declared as normal member.
* - retv.get {boolean} A flag which shows the name is declared as getter.
* - retv.set {boolean} A flag which shows the name is declared as setter.
*/
function getState(name, isStatic) {
var stateMap = stack[stack.length - 1];
var key = "$" + name; // to avoid "__proto__".
if (!stateMap[key]) {
stateMap[key] = {
nonStatic: {init: false, get: false, set: false},
static: {init: false, get: false, set: false}
};
}
return stateMap[key][isStatic ? "static" : "nonStatic"];
}
return {
// Initializes the stack of state of member declarations.
"Program": function() {
stack = [];
},
// Initializes state of member declarations for the class.
"ClassBody": function() {
stack.push(Object.create(null));
},
// Disposes the state for the class.
"ClassBody:exit": function() {
stack.pop();
},
// Reports the node if its name has been declared already.
"MethodDefinition": function(node) {
if (node.computed) {
return;
}
var name = node.key.name;
var state = getState(name, node.static);
var isDuplicate = false;
if (node.kind === "get") {
isDuplicate = (state.init || state.get);
state.get = true;
} else if (node.kind === "set") {
isDuplicate = (state.init || state.set);
state.set = true;
} else {
isDuplicate = (state.init || state.get || state.set);
state.init = true;
}
if (isDuplicate) {
context.report(node, "Duplicate name \"{{name}}\".", {name: name});
}
}
};
};
module.exports.schema = [];

7
tools/eslint/lib/rules/no-dupe-keys.js

@ -22,13 +22,18 @@ module.exports = function(context) {
var nodeProps = Object.create(null);
node.properties.forEach(function(property) {
if (property.type !== "Property") {
return;
}
var keyName = property.key.name || property.key.value,
key = property.kind + "-" + keyName,
checkProperty = (!property.computed || property.key.type === "Literal");
if (checkProperty) {
if (nodeProps[key]) {
context.report(node, "Duplicate key '{{key}}'.", { key: keyName });
context.report(node, property.loc.start, "Duplicate key '{{key}}'.", { key: keyName });
} else {
nodeProps[key] = true;
}

56
tools/eslint/lib/rules/no-duplicate-case.js

@ -1,7 +1,9 @@
/**
* @fileoverview Rule to disallow a duplicate case label.
* @author Dieter Oberkofler
* @author Dieter Oberkofler
* @author Burak Yigit Kaya
* @copyright 2015 Dieter Oberkofler. All rights reserved.
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
*/
"use strict";
@ -12,56 +14,20 @@
module.exports = function(context) {
/**
* Get a hash value for the node
* @param {ASTNode} node The node.
* @returns {string} A hash value for the node.
* @private
*/
function getHash(node) {
if (node.type === "Literal") {
return node.type + typeof node.value + node.value;
} else if (node.type === "Identifier") {
return node.type + typeof node.name + node.name;
} else if (node.type === "MemberExpression") {
return node.type + getHash(node.object) + getHash(node.property);
} else if (node.type === "CallExpression") {
return node.type + getHash(node.callee) + node.arguments.map(getHash).join("");
} else if (node.type === "BinaryExpression") {
return node.type + getHash(node.left) + node.operator + getHash(node.right);
} else if (node.type === "ConditionalExpression") {
return node.type + getHash(node.test) + getHash(node.consequent) + getHash(node.alternate);
}
}
var switchStatement = [];
return {
"SwitchStatement": function(node) {
var mapping = {};
"SwitchStatement": function(/*node*/) {
switchStatement.push({});
},
"SwitchStatement:exit": function(/*node*/) {
switchStatement.pop();
},
"SwitchCase": function(node) {
var currentSwitch = switchStatement[switchStatement.length - 1],
hashValue;
if (node.test) {
hashValue = getHash(node.test);
if (typeof hashValue !== "undefined" && currentSwitch.hasOwnProperty(hashValue)) {
context.report(node, "Duplicate case label.");
node.cases.forEach(function(switchCase) {
var key = context.getSource(switchCase.test);
if (mapping[key]) {
context.report(switchCase, "Duplicate case label.");
} else {
currentSwitch[hashValue] = true;
mapping[key] = switchCase;
}
}
});
}
};
};
module.exports.schema = [];

61
tools/eslint/lib/rules/no-else-return.js

@ -80,15 +80,30 @@ module.exports = function(context) {
/**
* Check the consequent/body node to make sure it is not
* a ReturnStatement or an IfStatement that returns on both
* code paths. If it is, display the context report.
* code paths.
*
* @param {Node} node The consequent or body node
* @param {Node} alternate The alternate node
* @returns {void}
* @returns {boolean} `true` if it is a Return/If node that always returns.
*/
function checkForReturnOrIf(node) {
return checkForReturn(node) || checkForIf(node);
}
/**
* Check whether a node returns in every codepath.
* @param {Node} node The node to be checked
* @returns {boolean} `true` if it returns on every codepath.
*/
function checkForReturnOrIf(node, alternate) {
if (checkForReturn(node) || checkForIf(node)) {
displayReport(alternate);
function alwaysReturns(node) {
// If we have a BlockStatement, check each consequent body node.
if (node.type === "BlockStatement") {
return node.body.some(checkForReturnOrIf);
// If not a block statement, make sure the consequent isn't a ReturnStatement
// or an IfStatement with returns on both paths
} else {
return checkForReturnOrIf(node);
}
}
@ -98,23 +113,27 @@ module.exports = function(context) {
return {
"IfStatement": function (node) {
// Don't bother finding a ReturnStatement, if there's no `else`
// or if the alternate is also an if (indicating an else if).
if (hasElse(node)) {
var consequent = node.consequent,
alternate = node.alternate;
// If we have a BlockStatement, check each consequent body node.
if (consequent.type === "BlockStatement") {
var body = consequent.body;
body.forEach(function (bodyNode) {
checkForReturnOrIf(bodyNode, alternate);
});
// If not a block statement, make sure the consequent isn't a ReturnStatement
// or an IfStatement with returns on both paths
} else {
checkForReturnOrIf(consequent, alternate);
"IfStatement": function(node) {
var parent = context.getAncestors().pop(),
consequents,
alternate;
// Only "top-level" if statements are checked, meaning the first `if`
// in a `if-else-if-...` chain.
if (parent.type === "IfStatement" && parent.alternate === node) {
return;
}
for (consequents = []; node.type === "IfStatement"; node = node.alternate) {
if (!node.alternate) {
return;
}
consequents.push(node.consequent);
alternate = node.alternate;
}
if (consequents.every(alwaysReturns)) {
displayReport(alternate);
}
}

2
tools/eslint/lib/rules/no-empty-character-class.js

@ -41,3 +41,5 @@ module.exports = function(context) {
};
};
module.exports.schema = [];

45
tools/eslint/lib/rules/no-empty-class.js

@ -1,45 +0,0 @@
/**
* @fileoverview Rule to flag the use of empty character classes in regular expressions
* @author Ian Christian Myers
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/*
plain-English description of the following regexp:
0. `^` fix the match at the beginning of the string
1. `\/`: the `/` that begins the regexp
2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
2.1. `\\.`: an escape sequence
2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
3. `\/` the `/` that ends the regexp
4. `[gimy]*`: optional regexp flags
5. `$`: fix the match at the end of the string
*/
var regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimy]*$/;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
return {
"Literal": function(node) {
var token = context.getFirstToken(node);
if (token.type === "RegularExpression" && !regex.test(token.value)) {
context.report(node, "Empty class.");
}
}
};
};
module.exports.schema = [];

2
tools/eslint/lib/rules/no-empty-label.js

@ -17,7 +17,7 @@ module.exports = function(context) {
var type = node.body.type;
if (type !== "ForStatement" && type !== "WhileStatement" && type !== "DoWhileStatement" && type !== "SwitchStatement" && type !== "ForInStatement" && type !== "ForOfStatement") {
context.report(node, "Unexpected label {{l}}", {l: node.label.name});
context.report(node, "Unexpected label \"{{l}}\"", {l: node.label.name});
}
}
};

28
tools/eslint/lib/rules/no-empty-pattern.js

@ -0,0 +1,28 @@
/**
* @fileoverview Rule to disallow an empty pattern
* @author Alberto Rodríguez
* @copyright 2015 Alberto Rodríguez. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
return {
"ObjectPattern": function(node) {
if (node.properties.length === 0) {
context.report(node, "Unexpected empty object pattern.");
}
},
"ArrayPattern": function(node) {
if (node.elements.length === 0) {
context.report(node, "Unexpected empty array pattern.");
}
}
};
};
module.exports.schema = [];

35
tools/eslint/lib/rules/no-ex-assign.js

@ -5,36 +5,31 @@
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
var catchStack = [];
/**
* Finds and reports references that are non initializer and writable.
* @param {Variable} variable - A variable to check.
* @returns {void}
*/
function checkVariable(variable) {
astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
context.report(
reference.identifier,
"Do not assign to the exception parameter.");
});
}
return {
"CatchClause": function(node) {
catchStack.push(node.param.name);
},
"CatchClause:exit": function() {
catchStack.pop();
},
"AssignmentExpression": function(node) {
if (catchStack.length > 0) {
var exceptionName = catchStack[catchStack.length - 1];
if (node.left.name && node.left.name === exceptionName) {
context.report(node, "Do not assign to the exception parameter.");
}
}
context.getDeclaredVariables(node).forEach(checkVariable);
}
};
};

21
tools/eslint/lib/rules/no-extend-native.js

@ -9,11 +9,7 @@
// Requirements
//------------------------------------------------------------------------------
var BUILTINS = [
"Object", "Function", "Array", "String", "Boolean", "Number", "Date",
"RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
"SyntaxError", "TypeError", "URIError"
];
var globals = require("globals");
//------------------------------------------------------------------------------
// Rule Definition
@ -23,10 +19,12 @@ module.exports = function(context) {
var config = context.options[0] || {};
var exceptions = config.exceptions || [];
var modifiedBuiltins = BUILTINS;
var modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) {
return builtin[0].toUpperCase() === builtin[0];
});
if (exceptions.length) {
modifiedBuiltins = BUILTINS.filter(function(builtIn) {
modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) {
return exceptions.indexOf(builtIn) === -1;
});
}
@ -56,22 +54,21 @@ module.exports = function(context) {
});
},
// handle the Object.defineProperty(Array.prototype) case
// handle the Object.definePropert[y|ies](Array.prototype) case
"CallExpression": function(node) {
var callee = node.callee,
subject,
object;
// only worry about Object.defineProperty
// only worry about Object.definePropert[y|ies]
if (callee.type === "MemberExpression" &&
callee.object.name === "Object" &&
callee.property.name === "defineProperty") {
(callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) {
// verify the object being added to is a native prototype
subject = node.arguments[0];
object = subject.object;
object = subject && subject.object;
if (object &&
object.type === "Identifier" &&
(modifiedBuiltins.indexOf(object.name) > -1) &&

7
tools/eslint/lib/rules/no-extra-bind.js

@ -2,6 +2,7 @@
* @fileoverview Rule to flag unnecessary bind calls
* @author Bence Dányi <bence@danyi.me>
* @copyright 2014 Bence Dányi. All rights reserved.
* See LICENSE in root directory for full license.
*/
"use strict";
@ -56,8 +57,10 @@ module.exports = function(context) {
}
},
"CallExpression:exit": function(node) {
var top = getTopScope();
if (top.call === node && top.found === 0) {
var top = getTopScope(),
isArrowFunction = node.callee.type === "MemberExpression" && node.callee.object.type === "ArrowFunctionExpression";
if (top.call === node && (top.found === 0 || isArrowFunction)) {
context.report(node, "The function binding is unnecessary.");
scope.pop();
}

2
tools/eslint/lib/rules/no-extra-boolean-cast.js

@ -12,7 +12,7 @@
module.exports = function(context) {
return {
"UnaryExpression": function (node) {
"UnaryExpression": function(node) {
var ancestors = context.getAncestors(),
parent = ancestors.pop(),
grandparent = ancestors.pop();

117
tools/eslint/lib/rules/no-extra-parens.js

@ -2,6 +2,7 @@
* @fileoverview Disallow parenthesising higher precedence subexpressions.
* @author Michael Ficarra
* @copyright 2014 Michael Ficarra. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
@ -74,6 +75,74 @@ module.exports = function(context) {
return ruleApplies(node) && isParenthesisedTwice(node);
}
/**
* Checks whether or not a given node is located at the head of ExpressionStatement.
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is located at the head of ExpressionStatement.
*/
function isHeadOfExpressionStatement(node) {
var parent = node.parent;
while (parent) {
switch (parent.type) {
case "SequenceExpression":
if (parent.expressions[0] !== node || isParenthesised(node)) {
return false;
}
break;
case "UnaryExpression":
case "UpdateExpression":
if (parent.prefix || isParenthesised(node)) {
return false;
}
break;
case "BinaryExpression":
case "LogicalExpression":
if (parent.left !== node || isParenthesised(node)) {
return false;
}
break;
case "ConditionalExpression":
if (parent.test !== node || isParenthesised(node)) {
return false;
}
break;
case "CallExpression":
if (parent.callee !== node || isParenthesised(node)) {
return false;
}
break;
case "MemberExpression":
if (parent.object !== node || isParenthesised(node)) {
return false;
}
break;
case "ExpressionStatement":
return true;
default:
return false;
}
node = parent;
parent = parent.parent;
}
/* istanbul ignore next */
throw new Error("unreachable");
}
/**
* Get the precedence level based on the node type
* @param {ASTNode} node node to evaluate
* @returns {int} precedence level
* @private
*/
function precedence(node) {
switch (node.type) {
@ -149,17 +218,35 @@ module.exports = function(context) {
return 18;
}
/**
* Report the node
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function report(node) {
var previousToken = context.getTokenBefore(node);
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
}
/**
* Evaluate Unary update
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function dryUnaryUpdate(node) {
if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
report(node.argument);
}
}
/**
* Evaluate a new call
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function dryCallNew(node) {
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
node.type === "CallExpression" &&
@ -182,6 +269,12 @@ module.exports = function(context) {
}
}
/**
* Evaluate binary logicals
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function dryBinaryLogical(node) {
var prec = precedence(node);
if (hasExcessParens(node.left) && precedence(node.left) >= prec) {
@ -239,11 +332,26 @@ module.exports = function(context) {
},
"ExpressionStatement": function(node) {
var firstToken;
if (hasExcessParens(node.expression) && node.expression.type !== "CallExpression") {
if (hasExcessParens(node.expression)) {
firstToken = context.getFirstToken(node.expression);
// Pure object literals ({}) do not need parentheses but
// member expressions do ({}.toString())
if (firstToken.value !== "{" || node.expression.type === "ObjectExpression") {
if ((
firstToken.value !== "{" ||
node.expression.type === "ObjectExpression"
) &&
// For such as `(function(){}.foo.bar)`
(
firstToken.value !== "function" ||
node.expression.type === "FunctionExpression"
) &&
// For such as `(class{}.foo.bar)`
(
firstToken.value !== "class" ||
node.expression.type === "ClassExpression"
)
) {
report(node.expression);
}
}
@ -291,6 +399,11 @@ module.exports = function(context) {
// RegExp literal is allowed to have parens (#1589)
(node.object.type === "Literal" && node.object.regex)
)
) &&
!(
(node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") &&
isHeadOfExpressionStatement(node) &&
!hasDoubleExcessParens(node.object)
)
) {
report(node.object);

19
tools/eslint/lib/rules/no-extra-semi.js

@ -17,7 +17,13 @@ module.exports = function(context) {
* @returns {void}
*/
function report(nodeOrToken) {
context.report(nodeOrToken, "Unnecessary semicolon.");
context.report({
node: nodeOrToken,
message: "Unnecessary semicolon.",
fix: function(fixer) {
return fixer.remove(nodeOrToken);
}
});
}
/**
@ -40,11 +46,18 @@ module.exports = function(context) {
return {
/**
* Reports this empty statement.
* Reports this empty statement, except if the parent node is a loop.
* @param {Node} node - A EmptyStatement node to be reported.
* @returns {void}
*/
"EmptyStatement": report,
"EmptyStatement": function(node) {
var parent = node.parent,
allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"];
if (allowedParentTypes.indexOf(parent.type) === -1) {
report(node);
}
},
/**
* Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body.

86
tools/eslint/lib/rules/no-extra-strict.js

@ -1,86 +0,0 @@
/**
* @fileoverview Rule to flag unnecessary strict directives.
* @author Ian Christian Myers
* @copyright 2014 Ian Christian Myers. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
function directives(block) {
var ds = [], body = block.body, e, i, l;
if (body) {
for (i = 0, l = body.length; i < l; ++i) {
e = body[i];
if (
e.type === "ExpressionStatement" &&
e.expression.type === "Literal" &&
typeof e.expression.value === "string"
) {
ds.push(e.expression);
} else {
break;
}
}
}
return ds;
}
function isStrict(directive) {
return directive.value === "use strict";
}
function checkForUnnecessaryUseStrict(node) {
var useStrictDirectives = directives(node).filter(isStrict),
scope,
upper;
switch (useStrictDirectives.length) {
case 0:
break;
case 1:
scope = context.getScope();
upper = scope.upper;
if (upper && upper.functionExpressionScope) {
upper = upper.upper;
}
if (upper && upper.isStrict) {
context.report(useStrictDirectives[0], "Unnecessary 'use strict'.");
}
break;
default:
context.report(useStrictDirectives[1], "Multiple 'use strict' directives.");
}
}
return {
"Program": checkForUnnecessaryUseStrict,
"ArrowFunctionExpression": function(node) {
checkForUnnecessaryUseStrict(node.body);
},
"FunctionExpression": function(node) {
checkForUnnecessaryUseStrict(node.body);
},
"FunctionDeclaration": function(node) {
checkForUnnecessaryUseStrict(node.body);
}
};
};
module.exports.schema = [];

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save