tools: update eslint to v1.10.3
PR-URL: https://github.com/nodejs/io.js/pull/2286 Reviewed-By: Roman Reiss <me@silverwind.io>
This commit is contained in:
parent
ed55169834
commit
2d441493a4
@ -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
|
||||
|
@ -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
Normal file
21
tools/eslint/conf/blank-script.json
Normal file
@ -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": []
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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
Normal file
150
tools/eslint/conf/json-schema-schema.json
Normal file
@ -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
Normal file
17
tools/eslint/conf/replacements.json
Normal file
@ -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"]
|
||||
}
|
||||
}
|
@ -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
Normal file
154
tools/eslint/lib/ast-utils.js
Normal file
@ -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;
|
||||
}
|
||||
};
|
@ -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");
|
||||
var text = fs.readFileSync(path.resolve(filename), "utf8"),
|
||||
result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig);
|
||||
|
||||
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
|
||||
|
||||
traverse({
|
||||
files: files,
|
||||
extensions: options.extensions,
|
||||
exclude: options.ignore ? exclude : false
|
||||
}, function(filename) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
if (currentOptions.quiet) {
|
||||
result.results = getErrorResults(result.results);
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (printResults(engine, result.results, currentOptions.format, currentOptions.outputFile)) {
|
||||
return result.errorCount ? 1 : 0;
|
||||
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) {
|
||||
debug("Quiet mode enabled - filtering out warnings");
|
||||
report.results = CLIEngine.getErrorResults(report.results);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
@ -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
|
||||
};
|
@ -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 (config.extends) {
|
||||
config = ConfigFile.applyExtends(config, filePath);
|
||||
}
|
||||
|
||||
if (path.basename(filePath) === PACKAGE_CONFIG_FILENAME) {
|
||||
config = config[PACKAGE_CONFIG_FIELD_NAME] || {};
|
||||
}
|
||||
|
||||
} 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_PATH && fs.existsSync(PERSONAL_CONFIG_PATH)) {
|
||||
debug("Using personal config");
|
||||
config = loadConfig(PERSONAL_CONFIG_PATH);
|
||||
if (PERSONAL_CONFIG_DIR) {
|
||||
filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
|
||||
|
||||
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);
|
||||
config = ConfigOps.merge({parser: this.parser}, this.baseConfig);
|
||||
|
||||
// Step 3: Merge in environment-specific globals and rules from .eslintrc files
|
||||
config = util.mergeConfigs(config, createEnvironmentConfig(userConfig.env, this.options.reset));
|
||||
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json
|
||||
config = ConfigOps.merge(config, userConfig);
|
||||
|
||||
// Step 4: Merge in the user-specified configuration from .eslintrc and package.json
|
||||
config = util.mergeConfigs(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
Normal file
440
tools/eslint/lib/config/config-file.js
Normal file
@ -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
Normal file
241
tools/eslint/lib/config/config-initializer.js
Normal file
@ -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
Normal file
186
tools/eslint/lib/config/config-ops.js
Normal file
@ -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
Normal file
163
tools/eslint/lib/config/config-validator.js
Normal file
@ -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
|
||||
};
|
@ -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;
|
||||
ecmaVersion,
|
||||
allowInlineConfig,
|
||||
text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
|
||||
|
||||
// set the current parsed filename
|
||||
currentFilename = filename;
|
||||
// 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,168 +798,99 @@ 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] || ""
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the entire source text split into an array of lines.
|
||||
* @returns {Array} The source text as an array of lines.
|
||||
*/
|
||||
api.getSourceLines = function() {
|
||||
return currentTextLines;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves an array containing all comments in the source code.
|
||||
* @returns {ASTNode[]} An array of comment nodes.
|
||||
*/
|
||||
api.getAllComments = function() {
|
||||
return currentAST.comments;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
column: location.column + 1, // switch to 1-base instead of 0-base
|
||||
nodeType: node && node.type,
|
||||
source: sourceCode.lines[location.line - 1] || ""
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Gets the SourceCode object representing the parsed source.
|
||||
* @returns {SourceCode} The SourceCode object.
|
||||
*/
|
||||
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) === "*") {
|
||||
|
||||
if (line - comments[i].loc.end.line <= 1) {
|
||||
return comments[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
api.getSourceCode = function() {
|
||||
return sourceCode;
|
||||
};
|
||||
|
||||
// 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"
|
||||
};
|
||||
|
||||
// copy over methods
|
||||
Object.keys(externalMethods).forEach(function(methodName) {
|
||||
var exMethodName = externalMethods[methodName];
|
||||
|
||||
// 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;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets nodes that are ancestors of current node.
|
||||
* @returns {ASTNode[]} Array of objects representing ancestors.
|
||||
@ -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;
|
||||
|
||||
}());
|
||||
|
@ -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++) {
|
||||
|
||||
if (getDirectoryEntries(directory).indexOf(names[k]) !== -1 && fs.statSync(path.resolve(directory, names[k])).isFile()) {
|
||||
filePath = path.resolve(directory, names[k]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
child = directory;
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -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>";
|
||||
|
@ -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
Normal file
130
tools/eslint/lib/formatters/html-template.html
Normal file
@ -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
Normal file
88
tools/eslint/lib/formatters/html.js
Normal file
@ -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
Normal file
14
tools/eslint/lib/formatters/json.js
Normal file
@ -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);
|
||||
};
|
@ -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
Normal file
59
tools/eslint/lib/formatters/unix.js
Normal file
@ -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;
|
||||
};
|
@ -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");
|
||||
}
|
||||
|
@ -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
Normal file
25
tools/eslint/lib/logging.js
Normal file
@ -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));
|
||||
}
|
||||
};
|
@ -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"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
}
|
||||
];
|
||||
|
@ -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
Normal file
71
tools/eslint/lib/rules/arrow-body-style.js
Normal file
@ -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
Normal file
52
tools/eslint/lib/rules/arrow-parens.js
Normal file
@ -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
Normal file
124
tools/eslint/lib/rules/arrow-spacing.js
Normal file
@ -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
|
||||
}
|
||||
];
|
@ -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";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
for (var i = 0; i < references.length; ++i) {
|
||||
var reference = references[i];
|
||||
var name = reference.identifier.name;
|
||||
|
||||
if (name in unresolved === false) {
|
||||
unresolved[name] = [];
|
||||
}
|
||||
unresolved[name].push(reference);
|
||||
}
|
||||
|
||||
return unresolved;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var scopeStack = [];
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
var unresolvedReferences = Object.create(null);
|
||||
var stack = [];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
case "CatchClause":
|
||||
return id === parent.param;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
case "Property":
|
||||
return id === parent.key;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a new scope object on the scope stack.
|
||||
* Makes a block scope.
|
||||
* @param {ASTNode} node - A node of a scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function pushScope() {
|
||||
scopeStack.push([]);
|
||||
function enterScope(node) {
|
||||
stack.push(node.range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the topmost scope object from the scope stack.
|
||||
* Pops the last block scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function popScope() {
|
||||
scopeStack.pop();
|
||||
function exitScope() {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares the given names in the topmost scope object.
|
||||
* @param {[String]} names A list of names to declare.
|
||||
* Reports a given reference.
|
||||
* @param {escope.Reference} reference - A reference to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function declare(names) {
|
||||
[].push.apply(scopeStack[scopeStack.length - 1], names);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for module imports.
|
||||
* @param {ASTNode} node The AST node representing an import.
|
||||
* @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();
|
||||
}
|
||||
var isGlobal = context.getScope().type === "global";
|
||||
|
||||
/**
|
||||
* Declares all relevant identifiers for classes.
|
||||
* @param {ASTNode} node The AST node representing a class.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function declareClassMethod(node) {
|
||||
pushScope();
|
||||
// Defines a predicate to check whether or not a given reference is outside of valid scope.
|
||||
var scopeRange = stack[stack.length - 1];
|
||||
|
||||
declare([node.key.name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}));
|
||||
}
|
||||
"Program": function(node) {
|
||||
unresolvedReferences = collectUnresolvedReferences(context);
|
||||
stack = [node.range];
|
||||
},
|
||||
|
||||
"ImportSpecifier": declareImports,
|
||||
"ImportDefaultSpecifier": declareImports,
|
||||
"ImportNamespaceSpecifier": declareImports,
|
||||
// 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,
|
||||
|
||||
"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]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"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.");
|
||||
}
|
||||
// Finds and reports references which are outside of valid scope.
|
||||
"VariableDeclaration": checkForVariables
|
||||
};
|
||||
|
||||
};
|
||||
|
119
tools/eslint/lib/rules/block-spacing.js
Normal file
119
tools/eslint/lib/rules/block-spacing.js
Normal file
@ -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"]}
|
||||
];
|
@ -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
Normal file
142
tools/eslint/lib/rules/callback-return.js
Normal file
@ -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" }
|
||||
}];
|
@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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 && isSameLine(tokens.comma, tokens.right) &&
|
||||
(options.after !== isSpaced(tokens.comma, tokens.right))
|
||||
|
||||
if (tokens.right && !options.after && tokens.right.type === "Line") {
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
function addNullElementsToIgnoreList(node) {
|
||||
var previousToken = context.getFirstToken(node);
|
||||
|
||||
var comment;
|
||||
node.elements.forEach(function(element) {
|
||||
var token;
|
||||
|
||||
while (lastCommentIndex < comments.length) {
|
||||
if (element === null) {
|
||||
token = context.getTokenAfter(previousToken);
|
||||
|
||||
comment = comments[lastCommentIndex];
|
||||
|
||||
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)) {
|
||||
tokensAndComments.forEach(function(token, i) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
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
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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 === "||") {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,39 +53,35 @@ module.exports = function(context) {
|
||||
*/
|
||||
function ensureWasAssigned() {
|
||||
var scope = context.getScope();
|
||||
var variable = scope.set.get(alias);
|
||||
if (!variable) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.variables.some(function (variable) {
|
||||
var lookup;
|
||||
if (variable.defs.some(function(def) {
|
||||
return def.node.type === "VariableDeclarator" &&
|
||||
def.node.init !== null;
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (variable.name === alias) {
|
||||
if (variable.defs.some(function (def) {
|
||||
return def.node.type === "VariableDeclarator" &&
|
||||
def.node.init !== null;
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
var lookup = (variable.references.length === 0 && scope.type === "global") ? scope : variable;
|
||||
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -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"]
|
||||
}
|
||||
];
|
||||
|
@ -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] || {};
|
||||
|
@ -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"]
|
||||
}
|
||||
];
|
||||
|
@ -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]);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
},
|
||||
|
||||
"FunctionExpression": function() {
|
||||
var parent = context.getAncestors().pop();
|
||||
|
||||
if (enforceDeclarations && parent.type === "VariableDeclarator") {
|
||||
context.report(parent, "Expected a function declaration.");
|
||||
}
|
||||
"FunctionDeclaration:exit": function() {
|
||||
stack.pop();
|
||||
},
|
||||
|
||||
"ArrowFunctionExpression": 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();
|
||||
},
|
||||
|
||||
"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
|
||||
}
|
||||
];
|
||||
|
@ -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]]);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
Normal file
35
tools/eslint/lib/rules/global-require.js
Normal file
@ -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 = [];
|
@ -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"]
|
||||
}
|
||||
];
|
@ -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
Normal file
106
tools/eslint/lib/rules/id-length.js
Normal file
@ -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
Normal file
129
tools/eslint/lib/rules/id-match.js
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
File diff suppressed because it is too large
Load Diff
120
tools/eslint/lib/rules/init-declarations.js
Normal file
120
tools/eslint/lib/rules/init-declarations.js
Normal file
@ -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
Normal file
63
tools/eslint/lib/rules/jsx-quotes.js
Normal file
@ -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" ]
|
||||
}
|
||||
];
|
@ -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"
|
||||
},
|
||||
|
@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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,26 +75,83 @@ 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 inside a node type or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @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 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 && isCommentInsideNodeType(node, parent, nodeType) &&
|
||||
node.loc.start.line - parent.loc.start.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the parent end 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 end.
|
||||
*/
|
||||
function isCommentAtParentEnd(node, nodeType) {
|
||||
var ancestors = context.getAncestors();
|
||||
var parent;
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
|
||||
return parent && isCommentInsideNodeType(node, parent, nodeType) &&
|
||||
parent.loc.end.line - node.loc.end.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,14 +160,7 @@ module.exports = function(context) {
|
||||
* @returns {boolean} True if the comment is at block start.
|
||||
*/
|
||||
function isCommentAtBlockStart(node) {
|
||||
var ancestors = context.getAncestors();
|
||||
var parent;
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
|
||||
node.loc.start.line - parent.loc.start.line === 1;
|
||||
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,14 +169,43 @@ module.exports = function(context) {
|
||||
* @returns {boolean} True if the comment is at block end.
|
||||
*/
|
||||
function isCommentAtBlockEnd(node) {
|
||||
var ancestors = context.getAncestors();
|
||||
var parent;
|
||||
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement");
|
||||
}
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
|
||||
parent.loc.end.line - node.loc.end.line === 1;
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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
|
||||
|
@ -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]--;
|
||||
}
|
||||
|
@ -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
|
||||
//--------------------------------------------------------------------------
|
||||
function checkProgramForMaxLength(node) {
|
||||
var lines = context.getSourceLines();
|
||||
|
||||
// Replace the tabs
|
||||
// Split (honors line-ending)
|
||||
// Iterate
|
||||
/**
|
||||
* 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) {
|
||||
// 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;
|
||||
|
||||
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
|
||||
}
|
||||
];
|
||||
|
@ -16,7 +16,7 @@ module.exports = function(context) {
|
||||
// Constants
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
var THRESHOLD = context.options[0];
|
||||
var THRESHOLD = context.options[0] || 10;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
|
@ -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
Normal file
88
tools/eslint/lib/rules/no-arrow-condition.js
Normal file
@ -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
Normal file
47
tools/eslint/lib/rules/no-case-declarations.js
Normal file
@ -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 = [];
|
@ -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
Normal file
48
tools/eslint/lib/rules/no-class-assign.js
Normal file
@ -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 = [];
|
@ -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 = [];
|
@ -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
Normal file
41
tools/eslint/lib/rules/no-const-assign.js
Normal file
@ -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 = [];
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
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}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
});
|
||||
|
||||
// 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
Normal file
82
tools/eslint/lib/rules/no-dupe-class-members.js
Normal file
@ -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 = [];
|
@ -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;
|
||||
}
|
||||
|
@ -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 = [];
|
||||
|
@ -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, alternate) {
|
||||
if (checkForReturn(node) || checkForIf(node)) {
|
||||
displayReport(alternate);
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,3 +41,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -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 = [];
|
@ -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
Normal file
28
tools/eslint/lib/rules/no-empty-pattern.js
Normal file
@ -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 = [];
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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) &&
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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 have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user