mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
tools: update ESLint to 2.7.0
PR-URL: https://github.com/nodejs/node/pull/6132 Reviewed-By: Brian White <mscdex@mscdex.net> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: thefourtheye <thechargingvolcano@gmail.com>
This commit is contained in:
2040 files changed
+40696
-36989
No files matched your search
File diff suppressed because it is too large.
Load diff
+20
-12
@@ -8,7 +8,7 @@
|
||||
|
||||
# ESLint
|
||||
|
||||
[Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint)
|
||||
[Website](http://eslint.org) | [Configuring](http://eslint.org/docs/user-guide/configuring) | [Rules](http://eslint.org/docs/rules/) | [Contributing](http://eslint.org/docs/developer-guide/contributing) | [Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) | [Twitter](https://twitter.com/geteslint) | [Mailing List](https://groups.google.com/group/eslint) | [Chat Room](https://gitter.im/eslint/eslint)
|
||||
|
||||
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions:
|
||||
|
||||
@@ -39,17 +39,17 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory.
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"semi": [2, "always"],
|
||||
"quotes": [2, "double"]
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "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)
|
||||
* `"off"` or `0` - turn the rule off
|
||||
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
|
||||
* `"error"` or `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)).
|
||||
|
||||
@@ -66,12 +66,14 @@ These folks keep the project moving and are resources for help:
|
||||
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
|
||||
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
|
||||
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - reviewer
|
||||
* Toru Nagashima ([@mysticatea](https://github.com/mysticatea)) - reviewer
|
||||
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - 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
|
||||
* Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo)) - committer
|
||||
* Michael Ficarra ([@michaelficarra](https://github.com/michaelficarra)) - committer
|
||||
|
||||
## Releases
|
||||
|
||||
@@ -83,6 +85,7 @@ Before filing an issue, please be sure to read the guidelines for what you're re
|
||||
|
||||
* [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs)
|
||||
* [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules)
|
||||
* [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes)
|
||||
* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes)
|
||||
|
||||
## Frequently Asked Questions
|
||||
@@ -109,18 +112,23 @@ 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.
|
||||
|
||||
### 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).
|
||||
|
||||
### Does ESLint support JSX?
|
||||
|
||||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](http://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics.
|
||||
|
||||
### What about ECMAScript 7/2016 and experimental features?
|
||||
### 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).
|
||||
|
||||
### What about experimental features?
|
||||
|
||||
ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel.
|
||||
|
||||
Once a language feature has been adopted into the ECMAScript standard, we will accept
|
||||
issues and pull requests related to the new feature, subject to our [contributing
|
||||
guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use
|
||||
the appropriate parser and plugin(s) for your experimental feature.
|
||||
|
||||
### Where to ask for help?
|
||||
|
||||
Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)
|
||||
|
||||
Executable → Regular
+20
-1
@@ -29,12 +29,31 @@ if (debug) {
|
||||
|
||||
// now we can safely include the other modules that use debug
|
||||
var concat = require("concat-stream"),
|
||||
cli = require("../lib/cli");
|
||||
cli = require("../lib/cli"),
|
||||
path = require("path"),
|
||||
fs = require("fs");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Execution
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
process.on("uncaughtException", function(err){
|
||||
// lazy load
|
||||
var lodash = require("lodash");
|
||||
|
||||
if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
|
||||
var template = lodash.template(fs.readFileSync(path.resolve(__dirname, "../messages/" + err.messageTemplate + ".txt"), "utf-8"));
|
||||
|
||||
console.log("\nOops! Something went wrong! :(");
|
||||
console.log("\n" + template(err.messageData || {}));
|
||||
} else {
|
||||
console.log(err.message);
|
||||
console.log(err.stack);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (useStdIn) {
|
||||
process.stdin.pipe(concat({ encoding: "string" }, function(text) {
|
||||
try {
|
||||
|
||||
@@ -28,6 +28,5 @@ module.exports = {
|
||||
cacheLocation: "",
|
||||
cacheFile: ".eslintcache",
|
||||
fix: false,
|
||||
allowInlineConfig: true,
|
||||
cwd: process.cwd()
|
||||
allowInlineConfig: true
|
||||
};
|
||||
+209
-205
@@ -2,210 +2,214 @@
|
||||
"parser": "espree",
|
||||
"ecmaFeatures": {},
|
||||
"rules": {
|
||||
"no-alert": 0,
|
||||
"no-array-constructor": 0,
|
||||
"no-bitwise": 0,
|
||||
"no-caller": 0,
|
||||
"no-case-declarations": 2,
|
||||
"no-catch-shadow": 0,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-confusing-arrow": 0,
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"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": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": 0,
|
||||
"no-empty": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-function": 0,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 0,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 0,
|
||||
"no-extra-bind": 0,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-label": 0,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 0,
|
||||
"no-func-assign": 2,
|
||||
"no-implicit-coercion": 0,
|
||||
"no-implicit-globals": 0,
|
||||
"no-implied-eval": 0,
|
||||
"no-inline-comments": 0,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-invalid-this": 0,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 0,
|
||||
"no-label-var": 0,
|
||||
"no-labels": 0,
|
||||
"no-lone-blocks": 0,
|
||||
"no-lonely-if": 0,
|
||||
"no-loop-func": 0,
|
||||
"no-mixed-requires": 0,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"linebreak-style": 0,
|
||||
"no-multi-spaces": 0,
|
||||
"no-multi-str": 0,
|
||||
"no-multiple-empty-lines": 0,
|
||||
"no-native-reassign": 0,
|
||||
"no-negated-condition": 0,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-nested-ternary": 0,
|
||||
"no-new": 0,
|
||||
"no-new-func": 0,
|
||||
"no-new-object": 0,
|
||||
"no-new-require": 0,
|
||||
"no-new-symbol": 2,
|
||||
"no-new-wrappers": 0,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-path-concat": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-process-env": 0,
|
||||
"no-process-exit": 0,
|
||||
"no-proto": 0,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-restricted-imports": 0,
|
||||
"no-restricted-modules": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-return-assign": 0,
|
||||
"no-script-url": 0,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 0,
|
||||
"no-sequences": 0,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 0,
|
||||
"no-whitespace-before-property": 0,
|
||||
"no-spaced-func": 0,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-sync": 0,
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 0,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 0,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 0,
|
||||
"no-undefined": 0,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-unmodified-loop-condition": 0,
|
||||
"no-unneeded-ternary": 0,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-expressions": 0,
|
||||
"no-unused-labels": 2,
|
||||
"no-unused-vars": 2,
|
||||
"no-use-before-define": 0,
|
||||
"no-useless-call": 0,
|
||||
"no-useless-concat": 0,
|
||||
"no-useless-constructor": 0,
|
||||
"no-void": 0,
|
||||
"no-var": 0,
|
||||
"no-warning-comments": 0,
|
||||
"no-with": 0,
|
||||
"no-magic-numbers": 0,
|
||||
|
||||
"array-bracket-spacing": 0,
|
||||
"array-callback-return": 0,
|
||||
"arrow-body-style": 0,
|
||||
"arrow-parens": 0,
|
||||
"arrow-spacing": 0,
|
||||
"accessor-pairs": 0,
|
||||
"block-scoped-var": 0,
|
||||
"block-spacing": 0,
|
||||
"brace-style": 0,
|
||||
"callback-return": 0,
|
||||
"camelcase": 0,
|
||||
"comma-dangle": 2,
|
||||
"comma-spacing": 0,
|
||||
"comma-style": 0,
|
||||
"complexity": [0, 11],
|
||||
"computed-property-spacing": 0,
|
||||
"consistent-return": 0,
|
||||
"consistent-this": 0,
|
||||
"constructor-super": 2,
|
||||
"curly": 0,
|
||||
"default-case": 0,
|
||||
"dot-location": 0,
|
||||
"dot-notation": 0,
|
||||
"eol-last": 0,
|
||||
"eqeqeq": 0,
|
||||
"func-names": 0,
|
||||
"func-style": 0,
|
||||
"generator-star-spacing": 0,
|
||||
"global-require": 0,
|
||||
"guard-for-in": 0,
|
||||
"handle-callback-err": 0,
|
||||
"id-length": 0,
|
||||
"indent": 0,
|
||||
"init-declarations": 0,
|
||||
"jsx-quotes": 0,
|
||||
"key-spacing": 0,
|
||||
"keyword-spacing": 0,
|
||||
"lines-around-comment": 0,
|
||||
"max-depth": 0,
|
||||
"max-len": 0,
|
||||
"max-nested-callbacks": 0,
|
||||
"max-params": 0,
|
||||
"max-statements": 0,
|
||||
"new-cap": 0,
|
||||
"new-parens": 0,
|
||||
"newline-after-var": 0,
|
||||
"newline-per-chained-call": 0,
|
||||
"object-curly-spacing": [0, "never"],
|
||||
"object-shorthand": 0,
|
||||
"one-var": 0,
|
||||
"one-var-declaration-per-line": 0,
|
||||
"operator-assignment": 0,
|
||||
"operator-linebreak": 0,
|
||||
"padded-blocks": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"prefer-const": 0,
|
||||
"prefer-reflect": 0,
|
||||
"prefer-rest-params": 0,
|
||||
"prefer-spread": 0,
|
||||
"prefer-template": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": 0,
|
||||
"radix": 0,
|
||||
"id-match": 0,
|
||||
"id-blacklist": 0,
|
||||
"require-jsdoc": 0,
|
||||
"require-yield": 0,
|
||||
"semi": 0,
|
||||
"semi-spacing": 0,
|
||||
"sort-vars": 0,
|
||||
"sort-imports": 0,
|
||||
"space-before-blocks": 0,
|
||||
"space-before-function-paren": 0,
|
||||
"space-in-parens": 0,
|
||||
"space-infix-ops": 0,
|
||||
"space-unary-ops": 0,
|
||||
"spaced-comment": 0,
|
||||
"strict": 0,
|
||||
"template-curly-spacing": 0,
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 0,
|
||||
"valid-typeof": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": 0,
|
||||
"wrap-regex": 0,
|
||||
"yield-star-spacing": 0,
|
||||
"yoda": 0
|
||||
"no-alert": "off",
|
||||
"no-array-constructor": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-caller": "off",
|
||||
"no-case-declarations": "error",
|
||||
"no-catch-shadow": "off",
|
||||
"no-class-assign": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-confusing-arrow": "off",
|
||||
"no-console": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": "error",
|
||||
"no-continue": "off",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-div-regex": "off",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-duplicate-imports": "off",
|
||||
"no-else-return": "off",
|
||||
"no-empty": "error",
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-function": "off",
|
||||
"no-empty-pattern": "error",
|
||||
"no-eq-null": "off",
|
||||
"no-eval": "off",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "off",
|
||||
"no-extra-bind": "off",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-extra-label": "off",
|
||||
"no-extra-parens": "off",
|
||||
"no-extra-semi": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-floating-decimal": "off",
|
||||
"no-func-assign": "error",
|
||||
"no-implicit-coercion": "off",
|
||||
"no-implicit-globals": "off",
|
||||
"no-implied-eval": "off",
|
||||
"no-inline-comments": "off",
|
||||
"no-inner-declarations": "error",
|
||||
"no-invalid-regexp": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "off",
|
||||
"no-label-var": "off",
|
||||
"no-labels": "off",
|
||||
"no-lone-blocks": "off",
|
||||
"no-lonely-if": "off",
|
||||
"no-loop-func": "off",
|
||||
"no-mixed-requires": "off",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"linebreak-style": "off",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"no-native-reassign": "off",
|
||||
"no-negated-condition": "off",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-nested-ternary": "off",
|
||||
"no-new": "off",
|
||||
"no-new-func": "off",
|
||||
"no-new-object": "off",
|
||||
"no-new-require": "off",
|
||||
"no-new-symbol": "error",
|
||||
"no-new-wrappers": "off",
|
||||
"no-obj-calls": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-path-concat": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-process-env": "off",
|
||||
"no-process-exit": "off",
|
||||
"no-proto": "off",
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-restricted-globals": "off",
|
||||
"no-restricted-imports": "off",
|
||||
"no-restricted-modules": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-return-assign": "off",
|
||||
"no-script-url": "off",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "off",
|
||||
"no-sequences": "off",
|
||||
"no-shadow": "off",
|
||||
"no-shadow-restricted-names": "off",
|
||||
"no-whitespace-before-property": "off",
|
||||
"no-spaced-func": "off",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-sync": "off",
|
||||
"no-ternary": "off",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "off",
|
||||
"no-undef": "error",
|
||||
"no-undef-init": "off",
|
||||
"no-undefined": "off",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unmodified-loop-condition": "off",
|
||||
"no-unneeded-ternary": "off",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-expressions": "off",
|
||||
"no-unused-labels": "error",
|
||||
"no-unused-vars": "error",
|
||||
"no-use-before-define": "off",
|
||||
"no-useless-call": "off",
|
||||
"no-useless-concat": "off",
|
||||
"no-useless-constructor": "off",
|
||||
"no-useless-escape": "off",
|
||||
"no-void": "off",
|
||||
"no-var": "off",
|
||||
"no-warning-comments": "off",
|
||||
"no-with": "off",
|
||||
"no-magic-numbers": "off",
|
||||
"array-bracket-spacing": "off",
|
||||
"array-callback-return": "off",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": "off",
|
||||
"arrow-spacing": "off",
|
||||
"accessor-pairs": "off",
|
||||
"block-scoped-var": "off",
|
||||
"block-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"comma-dangle": "error",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": "off",
|
||||
"complexity": ["off", 11],
|
||||
"computed-property-spacing": "off",
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"constructor-super": "error",
|
||||
"curly": "off",
|
||||
"default-case": "off",
|
||||
"dot-location": "off",
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"func-names": "off",
|
||||
"func-style": "off",
|
||||
"generator-star-spacing": "off",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "off",
|
||||
"handle-callback-err": "off",
|
||||
"id-length": "off",
|
||||
"indent": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "off",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"lines-around-comment": "off",
|
||||
"max-depth": "off",
|
||||
"max-len": "off",
|
||||
"max-nested-callbacks": "off",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"new-cap": "off",
|
||||
"new-parens": "off",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"object-curly-spacing": ["off", "never"],
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "off",
|
||||
"operator-assignment": "off",
|
||||
"operator-linebreak": "off",
|
||||
"padded-blocks": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "off",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": "off",
|
||||
"radix": "off",
|
||||
"id-match": "off",
|
||||
"id-blacklist": "off",
|
||||
"require-jsdoc": "off",
|
||||
"require-yield": "off",
|
||||
"semi": "off",
|
||||
"semi-spacing": "off",
|
||||
"sort-vars": "off",
|
||||
"sort-imports": "off",
|
||||
"space-before-blocks": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": "off",
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "off",
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
"template-curly-spacing": "off",
|
||||
"use-isnan": "error",
|
||||
"valid-jsdoc": "off",
|
||||
"valid-typeof": "error",
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": "off",
|
||||
"wrap-regex": "off",
|
||||
"yield-star-spacing": "off",
|
||||
"yoda": "off"
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,21 @@ var thisTagPattern = /^[\s\*]*@this/m;
|
||||
* @private
|
||||
*/
|
||||
function isModifyingReference(reference, index, references) {
|
||||
var identifier = reference.identifier;
|
||||
var identifier = reference.identifier,
|
||||
modifyingDifferentIdentifier;
|
||||
|
||||
/*
|
||||
* Destructuring assignments can have multiple default value, so
|
||||
* possibly there are multiple writeable references for the same
|
||||
* identifier.
|
||||
*/
|
||||
modifyingDifferentIdentifier = index === 0 ||
|
||||
references[index - 1].identifier !== 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)
|
||||
modifyingDifferentIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,6 +162,7 @@ function isMethodWhichHasThisArg(node) {
|
||||
*/
|
||||
function hasJSDocThisTag(node, sourceCode) {
|
||||
var jsdocComment = sourceCode.getJSDocComment(node);
|
||||
|
||||
if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
|
||||
return true;
|
||||
}
|
||||
@@ -168,6 +176,22 @@ function hasJSDocThisTag(node, sourceCode) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by parentheses.
|
||||
* @param {RuleContext} context The context object passed to the rule
|
||||
* @param {ASTNode} node The node to be checked.
|
||||
* @returns {boolean} True if the node is parenthesised.
|
||||
* @private
|
||||
*/
|
||||
function isParenthesised(context, node) {
|
||||
var previousToken = context.getTokenBefore(node),
|
||||
nextToken = context.getTokenAfter(node);
|
||||
|
||||
return Boolean(previousToken && nextToken) &&
|
||||
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -187,8 +211,10 @@ module.exports = {
|
||||
|
||||
isNullOrUndefined: isNullOrUndefined,
|
||||
isCallee: isCallee,
|
||||
isES5Constructor: isES5Constructor,
|
||||
getUpperFunction: getUpperFunction,
|
||||
isArrayFromMethod: isArrayFromMethod,
|
||||
isParenthesised: isParenthesised,
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a string literal.
|
||||
@@ -261,6 +287,7 @@ module.exports = {
|
||||
*/
|
||||
isDirectiveComment: function(node) {
|
||||
var comment = node.value.trim();
|
||||
|
||||
return (
|
||||
node.type === "Line" && comment.indexOf("eslint-") === 0 ||
|
||||
node.type === "Block" && (
|
||||
@@ -293,8 +320,10 @@ module.exports = {
|
||||
*/
|
||||
getVariableByName: function(initScope, name) {
|
||||
var scope = initScope;
|
||||
|
||||
while (scope) {
|
||||
var variable = scope.set.get(name);
|
||||
|
||||
if (variable) {
|
||||
return variable;
|
||||
}
|
||||
@@ -333,10 +362,13 @@ module.exports = {
|
||||
|
||||
while (node) {
|
||||
var parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
// Looks up the destination.
|
||||
// e.g.
|
||||
// obj.foo = nativeFoo || function foo() { ... };
|
||||
|
||||
/*
|
||||
* Looks up the destination.
|
||||
* e.g., obj.foo = nativeFoo || function foo() { ... };
|
||||
*/
|
||||
case "LogicalExpression":
|
||||
case "ConditionalExpression":
|
||||
node = parent;
|
||||
@@ -350,6 +382,7 @@ module.exports = {
|
||||
// })();
|
||||
case "ReturnStatement":
|
||||
var func = getUpperFunction(parent);
|
||||
|
||||
if (func === null || !isCallee(func)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+99
-102
@@ -22,8 +22,7 @@ var fs = require("fs"),
|
||||
|
||||
lodash = require("lodash"),
|
||||
debug = require("debug"),
|
||||
glob = require("glob"),
|
||||
shell = require("shelljs"),
|
||||
isAbsolute = require("path-is-absolute"),
|
||||
|
||||
rules = require("./rules"),
|
||||
eslint = require("./eslint"),
|
||||
@@ -36,8 +35,8 @@ var fs = require("fs"),
|
||||
SourceCodeFixer = require("./util/source-code-fixer"),
|
||||
validator = require("./config/config-validator"),
|
||||
stringify = require("json-stable-stringify"),
|
||||
hash = require("./util/hash"),
|
||||
|
||||
crypto = require( "crypto" ),
|
||||
pkg = require("../package.json");
|
||||
|
||||
|
||||
@@ -73,12 +72,6 @@ var fs = require("fs"),
|
||||
* @property {LintMessage[]} messages All of the messages for the result.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
defaultOptions = lodash.assign({}, defaultOptions, {cwd: process.cwd()});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -141,13 +134,14 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
config,
|
||||
messages,
|
||||
stats,
|
||||
fileExtension = path.extname(filename),
|
||||
fileExtension,
|
||||
processor,
|
||||
loadedPlugins,
|
||||
fixedResult;
|
||||
|
||||
if (filename) {
|
||||
filePath = path.resolve(filename);
|
||||
fileExtension = path.extname(filename);
|
||||
}
|
||||
|
||||
filename = filename || "<text>";
|
||||
@@ -171,6 +165,7 @@ function processText(text, configHelper, filename, fix, allowInlineConfig) {
|
||||
debug("Using processor");
|
||||
var parsedBlocks = processor.preprocess(text, filename);
|
||||
var unprocessedMessages = [];
|
||||
|
||||
parsedBlocks.forEach(function(block) {
|
||||
unprocessedMessages.push(eslint.verify(block, config, {
|
||||
filename: filename,
|
||||
@@ -262,17 +257,6 @@ 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
|
||||
@@ -286,8 +270,11 @@ function md5Hash(str) {
|
||||
* @returns {string} the resolved path to the cache file
|
||||
*/
|
||||
function getCacheFile(cacheFile, cwd) {
|
||||
// make sure the path separators are normalized for the environment/os
|
||||
// keeping the trailing path separator if present
|
||||
|
||||
/*
|
||||
* 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(cwd, cacheFile);
|
||||
@@ -298,7 +285,7 @@ function getCacheFile(cacheFile, cwd) {
|
||||
* @returns {string} the resolved path to the cacheFile
|
||||
*/
|
||||
function getCacheFileForDirectory() {
|
||||
return path.join(resolvedCacheFile, ".cache_" + md5Hash(cwd));
|
||||
return path.join(resolvedCacheFile, ".cache_" + hash(cwd));
|
||||
}
|
||||
|
||||
var fileStats;
|
||||
@@ -310,23 +297,31 @@ function getCacheFile(cacheFile, cwd) {
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
/*
|
||||
* 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
|
||||
|
||||
/*
|
||||
* 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.
|
||||
/*
|
||||
* 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) {
|
||||
@@ -347,7 +342,12 @@ function getCacheFile(cacheFile, cwd) {
|
||||
*/
|
||||
function CLIEngine(options) {
|
||||
|
||||
options = lodash.assign(Object.create(null), defaultOptions, options);
|
||||
options = lodash.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
{cwd: process.cwd()},
|
||||
options
|
||||
);
|
||||
|
||||
/**
|
||||
* Stored options for this instance
|
||||
@@ -358,8 +358,9 @@ function CLIEngine(options) {
|
||||
var cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Cache used to avoid operating on files that haven't changed since the
|
||||
* last successful execution (e.g., file passed linting with no errors and
|
||||
* no warnings).
|
||||
* @type {Object}
|
||||
*/
|
||||
this._fileCache = fileEntryCache.create(cacheFile);
|
||||
@@ -371,6 +372,7 @@ function CLIEngine(options) {
|
||||
// load in additional rules
|
||||
if (this.options.rulePaths) {
|
||||
var cwd = this.options.cwd;
|
||||
|
||||
this.options.rulePaths.forEach(function(rulesdir) {
|
||||
debug("Loading rules from " + rulesdir);
|
||||
rules.load(rulesdir, cwd);
|
||||
@@ -404,7 +406,9 @@ CLIEngine.getFormatter = function(format) {
|
||||
|
||||
// if there's a slash, then it's a file
|
||||
if (format.indexOf("/") > -1) {
|
||||
formatterPath = path.resolve(this.options.cwd, format);
|
||||
var cwd = this.options ? this.options.cwd : process.cwd();
|
||||
|
||||
formatterPath = path.resolve(cwd, format);
|
||||
} else {
|
||||
formatterPath = "./formatters/" + format;
|
||||
}
|
||||
@@ -476,7 +480,7 @@ CLIEngine.prototype = {
|
||||
* @returns {string[]} The equivalent glob patterns.
|
||||
*/
|
||||
resolveFileGlobPatterns: function(patterns) {
|
||||
return globUtil.resolveFileGlobPatterns(patterns, this.options.extensions);
|
||||
return globUtil.resolveFileGlobPatterns(patterns, this.options);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -490,26 +494,11 @@ CLIEngine.prototype = {
|
||||
options = this.options,
|
||||
fileCache = this._fileCache,
|
||||
configHelper = new Config(options),
|
||||
ignoredPaths = new IgnoredPaths(options),
|
||||
globOptions,
|
||||
fileList,
|
||||
stats,
|
||||
startTime,
|
||||
prevConfig; // the previous configuration used
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
globOptions = {
|
||||
nodir: true
|
||||
};
|
||||
|
||||
var cwd = options.cwd || process.cwd;
|
||||
patterns = this.resolveFileGlobPatterns(patterns.map(function(pattern) {
|
||||
if (pattern.indexOf("/") > 0) {
|
||||
return path.join(cwd, pattern);
|
||||
}
|
||||
return pattern;
|
||||
}));
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -524,13 +513,16 @@ CLIEngine.prototype = {
|
||||
|
||||
// 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
|
||||
|
||||
/*
|
||||
* 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));
|
||||
prevConfig.hash = hash(eslintVersion + "_" + stringify(config));
|
||||
}
|
||||
|
||||
return prevConfig.hash;
|
||||
@@ -546,48 +538,41 @@ CLIEngine.prototype = {
|
||||
function executeOnFile(filename, warnIgnored) {
|
||||
var hashOfConfig;
|
||||
|
||||
if (options.ignore !== false) {
|
||||
|
||||
if (ignoredPaths.contains(filename, "custom")) {
|
||||
if (warnIgnored) {
|
||||
results.push(createIgnoreResult(filename));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoredPaths.contains(filename, "default")) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
filename = path.resolve(filename);
|
||||
if (processed[filename]) {
|
||||
if (warnIgnored) {
|
||||
results.push(createIgnoreResult(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.cache) {
|
||||
// get the descriptor for this file
|
||||
// with the metadata and the flag that determines if
|
||||
// the file has changed
|
||||
|
||||
/*
|
||||
* 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)
|
||||
|
||||
/*
|
||||
* 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
|
||||
/*
|
||||
* Add the the cached results (always will be 0 error and
|
||||
* 0 warnings). We should not cache results for files that
|
||||
* failed, in order to guarantee that next execution will
|
||||
* process those files as well.
|
||||
*/
|
||||
results.push(descriptor.meta.results);
|
||||
|
||||
// move to the next file
|
||||
@@ -602,17 +587,25 @@ CLIEngine.prototype = {
|
||||
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 ) {
|
||||
|
||||
/*
|
||||
* 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 );
|
||||
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
|
||||
|
||||
/*
|
||||
* 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 errors and
|
||||
* 0 warnings.
|
||||
*/
|
||||
descriptor.meta.hashOfConfig = hashOfConfig;
|
||||
descriptor.meta.results = res;
|
||||
}
|
||||
@@ -621,23 +614,20 @@ CLIEngine.prototype = {
|
||||
results.push(res);
|
||||
}
|
||||
|
||||
patterns.forEach(function(pattern) {
|
||||
startTime = Date.now();
|
||||
|
||||
var file = path.resolve(pattern);
|
||||
|
||||
if (shell.test("-f", file)) {
|
||||
executeOnFile(fs.realpathSync(pattern), !shell.test("-d", file));
|
||||
} else {
|
||||
glob.sync(pattern, globOptions).forEach(function(globMatch) {
|
||||
executeOnFile(globMatch, false);
|
||||
});
|
||||
}
|
||||
|
||||
patterns = this.resolveFileGlobPatterns(patterns);
|
||||
fileList = globUtil.listFilesToProcess(patterns, options);
|
||||
fileList.forEach(function(fileInfo) {
|
||||
executeOnFile(fileInfo.filename, fileInfo.ignored);
|
||||
});
|
||||
|
||||
stats = calculateStatsPerRun(results);
|
||||
|
||||
if (options.cache) {
|
||||
|
||||
// persist the cache to disk
|
||||
fileCache.reconcile();
|
||||
}
|
||||
@@ -665,7 +655,12 @@ CLIEngine.prototype = {
|
||||
configHelper = new Config(options),
|
||||
ignoredPaths = new IgnoredPaths(options);
|
||||
|
||||
// resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
|
||||
if (filename && !isAbsolute(filename)) {
|
||||
filename = path.resolve(options.cwd, filename);
|
||||
}
|
||||
if (filename && (options.ignore !== false) && ignoredPaths.contains(filename)) {
|
||||
|
||||
results.push(createIgnoreResult(filename));
|
||||
} else {
|
||||
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig));
|
||||
@@ -689,6 +684,7 @@ CLIEngine.prototype = {
|
||||
*/
|
||||
getConfigForFile: function(filePath) {
|
||||
var configHelper = new Config(this.options);
|
||||
|
||||
return configHelper.getConfig(filePath);
|
||||
},
|
||||
|
||||
@@ -699,10 +695,11 @@ CLIEngine.prototype = {
|
||||
*/
|
||||
isPathIgnored: function(filePath) {
|
||||
var ignoredPaths;
|
||||
var resolvedPath = path.resolve(this.options.cwd, filePath);
|
||||
|
||||
if (this.options.ignore) {
|
||||
ignoredPaths = new IgnoredPaths(this.options);
|
||||
return ignoredPaths.contains(filePath);
|
||||
return ignoredPaths.contains(resolvedPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -51,6 +51,7 @@ function translateOptions(cliOptions) {
|
||||
rulePaths: cliOptions.rulesdir,
|
||||
useEslintrc: cliOptions.eslintrc,
|
||||
parser: cliOptions.parser,
|
||||
parserOptions: cliOptions.parserOptions,
|
||||
cache: cliOptions.cache,
|
||||
cacheFile: cliOptions.cacheFile,
|
||||
cacheLocation: cliOptions.cacheLocation,
|
||||
@@ -171,6 +172,7 @@ var cli = {
|
||||
}
|
||||
|
||||
var fileConfig = engine.getConfigForFile(files[0]);
|
||||
|
||||
log.info(JSON.stringify(fileConfig, null, " "));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ function isCaseNode(node) {
|
||||
*/
|
||||
function isForkingByTrueOrFalse(node) {
|
||||
var parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
case "ConditionalExpression":
|
||||
case "IfStatement":
|
||||
@@ -128,8 +129,8 @@ function isIdentifierReference(node) {
|
||||
*
|
||||
* To separate the current and the head is in order to not make useless segments.
|
||||
*
|
||||
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" events
|
||||
* are fired.
|
||||
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
|
||||
* events are fired.
|
||||
*
|
||||
* @param {CodePathAnalyzer} analyzer - The instance.
|
||||
* @param {ASTNode} node - The current AST node.
|
||||
@@ -206,6 +207,7 @@ function leaveFromCurrentSegment(analyzer, node) {
|
||||
node);
|
||||
}
|
||||
}
|
||||
|
||||
state.currentSegments = [];
|
||||
}
|
||||
|
||||
@@ -234,9 +236,12 @@ function preprocess(analyzer, node) {
|
||||
|
||||
case "ConditionalExpression":
|
||||
case "IfStatement":
|
||||
// Fork if this node is at `consequent`/`alternate`.
|
||||
// `popForkContext()` exists at `IfStatement:exit` and
|
||||
// `ConditionalExpression:exit`.
|
||||
|
||||
/*
|
||||
* Fork if this node is at `consequent`/`alternate`.
|
||||
* `popForkContext()` exists at `IfStatement:exit` and
|
||||
* `ConditionalExpression:exit`.
|
||||
*/
|
||||
if (parent.consequent === node) {
|
||||
state.makeIfConsequent();
|
||||
} else if (parent.alternate === node) {
|
||||
@@ -299,9 +304,12 @@ function preprocess(analyzer, node) {
|
||||
break;
|
||||
|
||||
case "AssignmentPattern":
|
||||
// Fork if this node is at `right`.
|
||||
// `left` is executed always, so it uses the current path.
|
||||
// `popForkContext()` exists at `AssignmentPattern:exit`.
|
||||
|
||||
/*
|
||||
* Fork if this node is at `right`.
|
||||
* `left` is executed always, so it uses the current path.
|
||||
* `popForkContext()` exists at `AssignmentPattern:exit`.
|
||||
*/
|
||||
if (parent.right === node) {
|
||||
state.pushForkContext();
|
||||
state.forkBypassPath();
|
||||
@@ -332,6 +340,7 @@ function processCodePathToEnter(analyzer, node) {
|
||||
case "FunctionExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
if (codePath) {
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
debug.dumpState(node, state, false);
|
||||
@@ -370,9 +379,12 @@ function processCodePathToEnter(analyzer, node) {
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
// Fork if this node is after the 2st node in `cases`.
|
||||
// It's similar to `else` blocks.
|
||||
// The next `test` node is processed in this path.
|
||||
|
||||
/*
|
||||
* Fork if this node is after the 2st node in `cases`.
|
||||
* It's similar to `else` blocks.
|
||||
* The next `test` node is processed in this path.
|
||||
*/
|
||||
if (parent.discriminant !== node && parent.cases[0] !== node) {
|
||||
state.forkPath();
|
||||
}
|
||||
@@ -425,9 +437,12 @@ function processCodePathToExit(analyzer, node) {
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
// This is the same as the process at the 1st `consequent` node in
|
||||
// `preprocess` function.
|
||||
// Must do if this `consequent` is empty.
|
||||
|
||||
/*
|
||||
* This is the same as the process at the 1st `consequent` node in
|
||||
* `preprocess` function.
|
||||
* Must do if this `consequent` is empty.
|
||||
*/
|
||||
if (node.consequent.length === 0) {
|
||||
state.makeSwitchCaseBody(true, !node.test);
|
||||
}
|
||||
@@ -499,9 +514,12 @@ function processCodePathToExit(analyzer, node) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip updating the current segment to avoid creating useless segments if
|
||||
// the node type is the same as the parent node type.
|
||||
/*
|
||||
* Skip updating the current segment to avoid creating useless segments if
|
||||
* the node type is the same as the parent node type.
|
||||
*/
|
||||
if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ function isReachable(segment) {
|
||||
* @param {boolean} reachable - A flag which shows this is reachable.
|
||||
*/
|
||||
function CodePathSegment(id, allPrevSegments, reachable) {
|
||||
|
||||
/**
|
||||
* The identifier of this code path.
|
||||
* Rules use it to store additional information of each rule.
|
||||
@@ -120,7 +121,8 @@ function CodePathSegment(id, allPrevSegments, reachable) {
|
||||
|
||||
// Internal data.
|
||||
Object.defineProperty(this, "internal", {value: {
|
||||
used: false
|
||||
used: false,
|
||||
loopedPrevSegments: []
|
||||
}});
|
||||
|
||||
/* istanbul ignore if */
|
||||
@@ -130,6 +132,20 @@ function CodePathSegment(id, allPrevSegments, reachable) {
|
||||
}
|
||||
}
|
||||
|
||||
CodePathSegment.prototype = {
|
||||
constructor: CodePathSegment,
|
||||
|
||||
/**
|
||||
* Checks a given previous segment is coming from the end of a loop.
|
||||
*
|
||||
* @param {CodePathSegment} segment - A previous segment to check.
|
||||
* @returns {boolean} `true` if the segment is coming from the end of a loop.
|
||||
*/
|
||||
isLoopedPrevSegment: function(segment) {
|
||||
return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the root segment.
|
||||
*
|
||||
@@ -191,6 +207,7 @@ CodePathSegment.markUsed = function(segment) {
|
||||
segment.internal.used = true;
|
||||
|
||||
var i;
|
||||
|
||||
if (segment.reachable) {
|
||||
for (i = 0; i < segment.allPrevSegments.length; ++i) {
|
||||
var prevSegment = segment.allPrevSegments[i];
|
||||
@@ -205,4 +222,15 @@ CodePathSegment.markUsed = function(segment) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks a previous segment as looped.
|
||||
*
|
||||
* @param {CodePathSegment} segment - A segment.
|
||||
* @param {CodePathSegment} prevSegment - A previous segment to mark.
|
||||
* @returns {void}
|
||||
*/
|
||||
CodePathSegment.markPrevSegmentAsLooped = function(segment, prevSegment) {
|
||||
segment.internal.loopedPrevSegments.push(prevSegment);
|
||||
};
|
||||
|
||||
module.exports = CodePathSegment;
|
||||
@@ -55,6 +55,7 @@ function getContinueContext(state, label) {
|
||||
}
|
||||
|
||||
var context = state.loopContext;
|
||||
|
||||
while (context) {
|
||||
if (context.label === label) {
|
||||
return context;
|
||||
@@ -75,6 +76,7 @@ function getContinueContext(state, label) {
|
||||
*/
|
||||
function getBreakContext(state, label) {
|
||||
var context = state.breakContext;
|
||||
|
||||
while (context) {
|
||||
if (label ? context.label === label : context.breakable) {
|
||||
return context;
|
||||
@@ -94,6 +96,7 @@ function getBreakContext(state, label) {
|
||||
*/
|
||||
function getReturnContext(state) {
|
||||
var context = state.tryContext;
|
||||
|
||||
while (context) {
|
||||
if (context.hasFinalizer && context.position !== "finally") {
|
||||
return context;
|
||||
@@ -112,6 +115,7 @@ function getReturnContext(state) {
|
||||
*/
|
||||
function getThrowContext(state) {
|
||||
var context = state.tryContext;
|
||||
|
||||
while (context) {
|
||||
if (context.position === "try" ||
|
||||
(context.hasFinalizer && context.position === "catch")
|
||||
@@ -168,6 +172,7 @@ function removeConnection(prevSegments, nextSegments) {
|
||||
*/
|
||||
function makeLooped(state, fromSegments, toSegments) {
|
||||
var end = Math.min(fromSegments.length, toSegments.length);
|
||||
|
||||
for (var i = 0; i < end; ++i) {
|
||||
var fromSegment = fromSegments[i];
|
||||
var toSegment = toSegments[i];
|
||||
@@ -181,6 +186,10 @@ function makeLooped(state, fromSegments, toSegments) {
|
||||
fromSegment.allNextSegments.push(toSegment);
|
||||
toSegment.allPrevSegments.push(fromSegment);
|
||||
|
||||
if (toSegment.allPrevSegments.length >= 2) {
|
||||
CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
|
||||
}
|
||||
|
||||
state.notifyLooped(fromSegment, toSegment);
|
||||
}
|
||||
}
|
||||
@@ -237,6 +246,7 @@ function CodePathState(idGenerator, onLooped) {
|
||||
var final = this.finalSegments = [];
|
||||
var returned = this.returnedForkContext = [];
|
||||
var thrown = this.thrownForkContext = [];
|
||||
|
||||
returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
|
||||
thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
|
||||
}
|
||||
@@ -259,6 +269,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
get parentForkContext() {
|
||||
var current = this.forkContext;
|
||||
|
||||
return current && current.upper;
|
||||
},
|
||||
|
||||
@@ -362,6 +373,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
popChoiceContext: function() {
|
||||
var context = this.choiceContext;
|
||||
|
||||
this.choiceContext = context.upper;
|
||||
|
||||
var forkContext = this.forkContext;
|
||||
@@ -370,43 +382,61 @@ CodePathState.prototype = {
|
||||
switch (context.kind) {
|
||||
case "&&":
|
||||
case "||":
|
||||
// If any result were not transferred from child contexts,
|
||||
// this sets the head segments to both cases.
|
||||
// The head segments are the path of the right-hand operand.
|
||||
|
||||
/*
|
||||
* If any result were not transferred from child contexts,
|
||||
* this sets the head segments to both cases.
|
||||
* The head segments are the path of the right-hand operand.
|
||||
*/
|
||||
if (!context.processed) {
|
||||
context.trueForkContext.add(headSegments);
|
||||
context.falseForkContext.add(headSegments);
|
||||
}
|
||||
|
||||
// Transfers results to upper context if this context is in
|
||||
// test chunk.
|
||||
/*
|
||||
* Transfers results to upper context if this context is in
|
||||
* test chunk.
|
||||
*/
|
||||
if (context.isForkingAsResult) {
|
||||
var parentContext = this.choiceContext;
|
||||
|
||||
parentContext.trueForkContext.addAll(context.trueForkContext);
|
||||
parentContext.falseForkContext.addAll(context.falseForkContext);
|
||||
parentContext.processed = true;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "test":
|
||||
if (!context.processed) {
|
||||
// The head segments are the path of the `if` block here.
|
||||
// Updates the `true` path with the end of the `if` block.
|
||||
|
||||
/*
|
||||
* The head segments are the path of the `if` block here.
|
||||
* Updates the `true` path with the end of the `if` block.
|
||||
*/
|
||||
context.trueForkContext.clear();
|
||||
context.trueForkContext.add(headSegments);
|
||||
} else {
|
||||
// The head segments are the path of the `else` block here.
|
||||
// Updates the `false` path with the end of the `else` block.
|
||||
|
||||
/*
|
||||
* The head segments are the path of the `else` block here.
|
||||
* Updates the `false` path with the end of the `else`
|
||||
* block.
|
||||
*/
|
||||
context.falseForkContext.clear();
|
||||
context.falseForkContext.add(headSegments);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "loop":
|
||||
// Loops are addressed in popLoopContext().
|
||||
// This is called from popLoopContext().
|
||||
|
||||
/*
|
||||
* Loops are addressed in popLoopContext().
|
||||
* This is called from popLoopContext().
|
||||
*/
|
||||
return context;
|
||||
|
||||
/* istanbul ignore next */
|
||||
@@ -416,6 +446,7 @@ CodePathState.prototype = {
|
||||
|
||||
// Merges all paths.
|
||||
var prevForkContext = context.trueForkContext;
|
||||
|
||||
prevForkContext.addAll(context.falseForkContext);
|
||||
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
|
||||
|
||||
@@ -433,8 +464,11 @@ CodePathState.prototype = {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (context.processed) {
|
||||
// This got segments already from the child choice context.
|
||||
// Creates the next path from own true/false fork context.
|
||||
|
||||
/*
|
||||
* This got segments already from the child choice context.
|
||||
* Creates the next path from own true/false fork context.
|
||||
*/
|
||||
var prevForkContext =
|
||||
context.kind === "&&" ? context.trueForkContext :
|
||||
/* kind === "||" */ context.falseForkContext;
|
||||
@@ -444,13 +478,18 @@ CodePathState.prototype = {
|
||||
|
||||
context.processed = false;
|
||||
} else {
|
||||
// This did not get segments from the child choice context.
|
||||
// So addresses the head segments.
|
||||
// The head segments are the path of the left-hand operand.
|
||||
|
||||
/*
|
||||
* This did not get segments from the child choice context.
|
||||
* So addresses the head segments.
|
||||
* The head segments are the path of the left-hand operand.
|
||||
*/
|
||||
if (context.kind === "&&") {
|
||||
|
||||
// The path does short-circuit if false.
|
||||
context.falseForkContext.add(forkContext.head);
|
||||
} else {
|
||||
|
||||
// The path does short-circuit if true.
|
||||
context.trueForkContext.add(forkContext.head);
|
||||
}
|
||||
@@ -468,13 +507,16 @@ CodePathState.prototype = {
|
||||
var context = this.choiceContext;
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
// If any result were not transferred from child contexts,
|
||||
// this sets the head segments to both cases.
|
||||
// The head segments are the path of the test expression.
|
||||
/*
|
||||
* If any result were not transferred from child contexts,
|
||||
* this sets the head segments to both cases.
|
||||
* The head segments are the path of the test expression.
|
||||
*/
|
||||
if (!context.processed) {
|
||||
context.trueForkContext.add(forkContext.head);
|
||||
context.falseForkContext.add(forkContext.head);
|
||||
}
|
||||
|
||||
context.processed = false;
|
||||
|
||||
// Creates new path from the `true` case.
|
||||
@@ -492,8 +534,10 @@ CodePathState.prototype = {
|
||||
var context = this.choiceContext;
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
// The head segments are the path of the `if` block.
|
||||
// Updates the `true` path with the end of the `if` block.
|
||||
/*
|
||||
* The head segments are the path of the `if` block.
|
||||
* Updates the `true` path with the end of the `if` block.
|
||||
*/
|
||||
context.trueForkContext.clear();
|
||||
context.trueForkContext.add(forkContext.head);
|
||||
context.processed = true;
|
||||
@@ -526,6 +570,7 @@ CodePathState.prototype = {
|
||||
lastIsDefault: false,
|
||||
countForks: 0
|
||||
};
|
||||
|
||||
this.pushBreakContext(true, label);
|
||||
},
|
||||
|
||||
@@ -541,41 +586,57 @@ CodePathState.prototype = {
|
||||
*/
|
||||
popSwitchContext: function() {
|
||||
var context = this.switchContext;
|
||||
|
||||
this.switchContext = context.upper;
|
||||
|
||||
var forkContext = this.forkContext;
|
||||
var brokenForkContext = this.popBreakContext().brokenForkContext;
|
||||
|
||||
if (context.countForks === 0) {
|
||||
// When there is only one `default` chunk and there is one or more
|
||||
// `break` statements, even if forks are nothing, it needs to merge
|
||||
// those.
|
||||
|
||||
/*
|
||||
* When there is only one `default` chunk and there is one or more
|
||||
* `break` statements, even if forks are nothing, it needs to merge
|
||||
* those.
|
||||
*/
|
||||
if (!brokenForkContext.empty) {
|
||||
brokenForkContext.add(forkContext.makeNext(-1, -1));
|
||||
forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var lastSegments = forkContext.head;
|
||||
|
||||
this.forkBypassPath();
|
||||
var lastCaseSegments = forkContext.head;
|
||||
|
||||
// `brokenForkContext` is used to make the next segment.
|
||||
// It must add the last segment into `brokenForkContext`.
|
||||
/*
|
||||
* `brokenForkContext` is used to make the next segment.
|
||||
* It must add the last segment into `brokenForkContext`.
|
||||
*/
|
||||
brokenForkContext.add(lastSegments);
|
||||
|
||||
// A path which is failed in all case test should be connected to path
|
||||
// of `default` chunk.
|
||||
/*
|
||||
* A path which is failed in all case test should be connected to path
|
||||
* of `default` chunk.
|
||||
*/
|
||||
if (!context.lastIsDefault) {
|
||||
if (context.defaultBodySegments) {
|
||||
// Remove a link from `default` label to its chunk.
|
||||
// It's false route.
|
||||
|
||||
/*
|
||||
* Remove a link from `default` label to its chunk.
|
||||
* It's false route.
|
||||
*/
|
||||
removeConnection(context.defaultSegments, context.defaultBodySegments);
|
||||
makeLooped(this, lastCaseSegments, context.defaultBodySegments);
|
||||
} else {
|
||||
// It handles the last case body as broken if `default` chunk
|
||||
// does not exist.
|
||||
|
||||
/*
|
||||
* It handles the last case body as broken if `default` chunk
|
||||
* does not exist.
|
||||
*/
|
||||
brokenForkContext.add(lastCaseSegments);
|
||||
}
|
||||
}
|
||||
@@ -584,8 +645,11 @@ CodePathState.prototype = {
|
||||
for (var i = 0; i < context.countForks; ++i) {
|
||||
this.forkContext = this.forkContext.upper;
|
||||
}
|
||||
// Creates a path from all brokenForkContext paths.
|
||||
// This is a path after switch statement.
|
||||
|
||||
/*
|
||||
* Creates a path from all brokenForkContext paths.
|
||||
* This is a path after switch statement.
|
||||
*/
|
||||
this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
|
||||
},
|
||||
|
||||
@@ -598,20 +662,26 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeSwitchCaseBody: function(isEmpty, isDefault) {
|
||||
var context = this.switchContext;
|
||||
|
||||
if (!context.hasCase) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge forks.
|
||||
// The parent fork context has two segments.
|
||||
// Those are from the current case and the body of the previous case.
|
||||
/*
|
||||
* Merge forks.
|
||||
* The parent fork context has two segments.
|
||||
* Those are from the current case and the body of the previous case.
|
||||
*/
|
||||
var parentForkContext = this.forkContext;
|
||||
var forkContext = this.pushForkContext();
|
||||
|
||||
forkContext.add(parentForkContext.makeNext(0, -1));
|
||||
|
||||
// Save `default` chunk info.
|
||||
// If the `default` label is not at the last, we must make a path from
|
||||
// the last `case` to the `default` chunk.
|
||||
/*
|
||||
* Save `default` chunk info.
|
||||
* If the `default` label is not at the last, we must make a path from
|
||||
* the last `case` to the `default` chunk.
|
||||
*/
|
||||
if (isDefault) {
|
||||
context.defaultSegments = parentForkContext.head;
|
||||
if (isEmpty) {
|
||||
@@ -625,6 +695,7 @@ CodePathState.prototype = {
|
||||
context.defaultBodySegments = forkContext.head;
|
||||
}
|
||||
}
|
||||
|
||||
context.lastIsDefault = isDefault;
|
||||
context.countForks += 1;
|
||||
},
|
||||
@@ -645,9 +716,11 @@ CodePathState.prototype = {
|
||||
upper: this.tryContext,
|
||||
position: "try",
|
||||
hasFinalizer: hasFinalizer,
|
||||
|
||||
returnedForkContext: hasFinalizer
|
||||
? ForkContext.newEmpty(this.forkContext)
|
||||
: null,
|
||||
|
||||
thrownForkContext: ForkContext.newEmpty(this.forkContext),
|
||||
lastOfTryIsReachable: false,
|
||||
lastOfCatchIsReachable: false
|
||||
@@ -661,24 +734,31 @@ CodePathState.prototype = {
|
||||
*/
|
||||
popTryContext: function() {
|
||||
var context = this.tryContext;
|
||||
|
||||
this.tryContext = context.upper;
|
||||
|
||||
if (context.position === "catch") {
|
||||
|
||||
// Merges two paths from the `try` block and `catch` block merely.
|
||||
this.popForkContext();
|
||||
return;
|
||||
}
|
||||
// The following process is executed only when there is the `finally`
|
||||
// block.
|
||||
|
||||
/*
|
||||
* The following process is executed only when there is the `finally`
|
||||
* block.
|
||||
*/
|
||||
|
||||
var returned = context.returnedForkContext;
|
||||
var thrown = context.thrownForkContext;
|
||||
|
||||
if (returned.empty && thrown.empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Separate head to normal paths and leaving paths.
|
||||
var headSegments = this.forkContext.head;
|
||||
|
||||
this.forkContext = this.forkContext.upper;
|
||||
var normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
|
||||
var leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
|
||||
@@ -744,6 +824,7 @@ CodePathState.prototype = {
|
||||
|
||||
// Update state.
|
||||
if (context.position === "catch") {
|
||||
|
||||
// Merges two paths from the `try` block and `catch` block.
|
||||
this.popForkContext();
|
||||
forkContext = this.forkContext;
|
||||
@@ -755,14 +836,18 @@ CodePathState.prototype = {
|
||||
context.position = "finally";
|
||||
|
||||
if (returned.empty && thrown.empty) {
|
||||
|
||||
// This path does not leave.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a parallel segment from merging returned and thrown.
|
||||
// This segment will leave at the end of this finally block.
|
||||
/*
|
||||
* Create a parallel segment from merging returned and thrown.
|
||||
* This segment will leave at the end of this finally block.
|
||||
*/
|
||||
var segments = forkContext.makeNext(-1, -1);
|
||||
var j;
|
||||
|
||||
for (var i = 0; i < forkContext.count; ++i) {
|
||||
var prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
|
||||
|
||||
@@ -790,11 +875,13 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeFirstThrowablePathInTryBlock: function() {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (!forkContext.reachable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var context = getThrowContext(this);
|
||||
|
||||
if (context === this ||
|
||||
context.position !== "try" ||
|
||||
!context.thrownForkContext.empty
|
||||
@@ -893,6 +980,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
popLoopContext: function() {
|
||||
var context = this.loopContext;
|
||||
|
||||
this.loopContext = context.upper;
|
||||
|
||||
var forkContext = this.forkContext;
|
||||
@@ -923,6 +1011,7 @@ CodePathState.prototype = {
|
||||
|
||||
// `true` paths go to looping.
|
||||
var segmentsList = choiceContext.trueForkContext.segmentsList;
|
||||
|
||||
for (var i = 0; i < segmentsList.length; ++i) {
|
||||
makeLooped(
|
||||
this,
|
||||
@@ -1069,6 +1158,7 @@ CodePathState.prototype = {
|
||||
|
||||
// Update state.
|
||||
var updateSegments = forkContext.makeDisconnected(-1, -1);
|
||||
|
||||
context.continueDestSegments = context.updateSegments = updateSegments;
|
||||
forkContext.replaceHead(updateSegments);
|
||||
},
|
||||
@@ -1104,10 +1194,15 @@ CodePathState.prototype = {
|
||||
}
|
||||
|
||||
var bodySegments = context.endOfTestSegments;
|
||||
|
||||
if (!bodySegments) {
|
||||
// If there is not the `test` part, the `body` path comes from the
|
||||
// `init` part and the `update` part.
|
||||
|
||||
/*
|
||||
* If there is not the `test` part, the `body` path comes from the
|
||||
* `init` part and the `update` part.
|
||||
*/
|
||||
var prevForkContext = ForkContext.newEmpty(forkContext);
|
||||
|
||||
prevForkContext.add(context.endOfInitSegments);
|
||||
if (context.endOfUpdateSegments) {
|
||||
prevForkContext.add(context.endOfUpdateSegments);
|
||||
@@ -1146,6 +1241,7 @@ CodePathState.prototype = {
|
||||
var context = this.loopContext;
|
||||
var forkContext = this.forkContext;
|
||||
var temp = ForkContext.newEmpty(forkContext);
|
||||
|
||||
temp.add(context.prevSegments);
|
||||
var rightSegments = temp.makeNext(-1, -1);
|
||||
|
||||
@@ -1164,6 +1260,7 @@ CodePathState.prototype = {
|
||||
var context = this.loopContext;
|
||||
var forkContext = this.forkContext;
|
||||
var temp = ForkContext.newEmpty(forkContext);
|
||||
|
||||
temp.add(context.endOfLeftSegments);
|
||||
var bodySegments = temp.makeNext(-1, -1);
|
||||
|
||||
@@ -1205,11 +1302,13 @@ CodePathState.prototype = {
|
||||
popBreakContext: function() {
|
||||
var context = this.breakContext;
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
this.breakContext = context.upper;
|
||||
|
||||
// Process this context here for other than switches and loops.
|
||||
if (!context.breakable) {
|
||||
var brokenForkContext = context.brokenForkContext;
|
||||
|
||||
if (!brokenForkContext.empty) {
|
||||
brokenForkContext.add(forkContext.head);
|
||||
forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
|
||||
@@ -1230,15 +1329,18 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeBreak: function(label) {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (!forkContext.reachable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var context = getBreakContext(this, label);
|
||||
|
||||
/* istanbul ignore else: foolproof (syntax error) */
|
||||
if (context) {
|
||||
context.brokenForkContext.add(forkContext.head);
|
||||
}
|
||||
|
||||
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
|
||||
},
|
||||
|
||||
@@ -1253,11 +1355,13 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeContinue: function(label) {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (!forkContext.reachable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var context = getContinueContext(this, label);
|
||||
|
||||
/* istanbul ignore else: foolproof (syntax error) */
|
||||
if (context) {
|
||||
if (context.continueDestSegments) {
|
||||
@@ -1286,6 +1390,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeReturn: function() {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (forkContext.reachable) {
|
||||
getReturnContext(this).returnedForkContext.add(forkContext.head);
|
||||
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
|
||||
@@ -1302,6 +1407,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeThrow: function() {
|
||||
var forkContext = this.forkContext;
|
||||
|
||||
if (forkContext.reachable) {
|
||||
getThrowContext(this).thrownForkContext.add(forkContext.head);
|
||||
forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
|
||||
@@ -1314,6 +1420,7 @@ CodePathState.prototype = {
|
||||
*/
|
||||
makeFinal: function() {
|
||||
var segments = this.currentSegments;
|
||||
|
||||
if (segments.length > 0 && segments[0].reachable) {
|
||||
this.returnedForkContext.add(segments);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ var IdGenerator = require("./id-generator");
|
||||
* @param {function} onLooped - A callback function to notify looping.
|
||||
*/
|
||||
function CodePath(id, upper, onLooped) {
|
||||
|
||||
/**
|
||||
* The identifier of this code path.
|
||||
* Rules use it to store additional information of each rule.
|
||||
@@ -102,6 +103,123 @@ CodePath.prototype = {
|
||||
*/
|
||||
get currentSegments() {
|
||||
return this.internal.currentSegments;
|
||||
},
|
||||
|
||||
/**
|
||||
* Traverses all segments in this code path.
|
||||
*
|
||||
* codePath.traverseSegments(function(segment, controller) {
|
||||
* // do something.
|
||||
* });
|
||||
*
|
||||
* This method enumerates segments in order from the head.
|
||||
*
|
||||
* The `controller` object has two methods.
|
||||
*
|
||||
* - `controller.skip()` - Skip the following segments in this branch.
|
||||
* - `controller.break()` - Skip all following segments.
|
||||
*
|
||||
* @param {object} [options] - Omittable.
|
||||
* @param {CodePathSegment} [options.first] - The first segment to traverse.
|
||||
* @param {CodePathSegment} [options.last] - The last segment to traverse.
|
||||
* @param {function} callback - A callback function.
|
||||
* @returns {void}
|
||||
*/
|
||||
traverseSegments: function(options, callback) {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var startSegment = options.first || this.internal.initialSegment;
|
||||
var lastSegment = options.last;
|
||||
|
||||
var item = null;
|
||||
var index = 0;
|
||||
var end = 0;
|
||||
var segment = null;
|
||||
var visited = Object.create(null);
|
||||
var stack = [[startSegment, 0]];
|
||||
var skippedSegment = null;
|
||||
var broken = false;
|
||||
var controller = {
|
||||
skip: function() {
|
||||
if (stack.length <= 1) {
|
||||
broken = true;
|
||||
} else {
|
||||
skippedSegment = stack[stack.length - 2][0];
|
||||
}
|
||||
},
|
||||
break: function() {
|
||||
broken = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks a given previous segment has been visited.
|
||||
* @param {CodePathSegment} prevSegment - A previous segment to check.
|
||||
* @returns {boolean} `true` if the segment has been visited.
|
||||
*/
|
||||
function isVisited(prevSegment) {
|
||||
return (
|
||||
visited[prevSegment.id] ||
|
||||
segment.isLoopedPrevSegment(prevSegment)
|
||||
);
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
item = stack[stack.length - 1];
|
||||
segment = item[0];
|
||||
index = item[1];
|
||||
|
||||
if (index === 0) {
|
||||
|
||||
// Skip if this segment has been visited already.
|
||||
if (visited[segment.id]) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if all previous segments have not been visited.
|
||||
if (segment !== startSegment &&
|
||||
segment.prevSegments.length > 0 &&
|
||||
!segment.prevSegments.every(isVisited)
|
||||
) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset the flag of skipping if all branches have been skipped.
|
||||
if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
|
||||
skippedSegment = null;
|
||||
}
|
||||
visited[segment.id] = true;
|
||||
|
||||
// Call the callback when the first time.
|
||||
if (!skippedSegment) {
|
||||
callback.call(this, segment, controller); // eslint-disable-line callback-return
|
||||
if (segment === lastSegment) {
|
||||
controller.skip();
|
||||
}
|
||||
if (broken) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the stack.
|
||||
end = segment.nextSegments.length - 1;
|
||||
if (index < end) {
|
||||
item[1] += 1;
|
||||
stack.push([segment.nextSegments[index], 0]);
|
||||
} else if (index === end) {
|
||||
item[0] = segment.nextSegments[index];
|
||||
item[1] = 0;
|
||||
} else {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ function getId(segment) { // eslint-disable-line require-jsdoc
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* A flag that debug dumping is enabled or not.
|
||||
* @type {boolean}
|
||||
@@ -57,6 +58,7 @@ module.exports = {
|
||||
dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) {
|
||||
for (var i = 0; i < state.currentSegments.length; ++i) {
|
||||
var segInternal = state.currentSegments[i].internal;
|
||||
|
||||
if (leaving) {
|
||||
segInternal.exitNodes.push(node);
|
||||
} else {
|
||||
@@ -153,12 +155,14 @@ module.exports = {
|
||||
var item = stack.pop();
|
||||
var segment = item[0];
|
||||
var index = item[1];
|
||||
|
||||
if (done[segment.id] && index === 0) {
|
||||
continue;
|
||||
}
|
||||
done[segment.id] = segment;
|
||||
|
||||
var nextSegment = segment.allNextSegments[index];
|
||||
|
||||
if (!nextSegment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ function isReachable(segment) {
|
||||
*/
|
||||
function makeSegments(context, begin, end, create) {
|
||||
var list = context.segmentsList;
|
||||
|
||||
if (begin < 0) {
|
||||
begin = list.length + begin;
|
||||
}
|
||||
@@ -55,6 +56,7 @@ function makeSegments(context, begin, end, create) {
|
||||
}
|
||||
|
||||
var segments = [];
|
||||
|
||||
for (var i = 0; i < context.count; ++i) {
|
||||
var allPrevSegments = [];
|
||||
|
||||
@@ -81,6 +83,7 @@ function makeSegments(context, begin, end, create) {
|
||||
function mergeExtraSegments(context, segments) {
|
||||
while (segments.length > context.count) {
|
||||
var merged = [];
|
||||
|
||||
for (var i = 0, length = segments.length / 2 | 0; i < length; ++i) {
|
||||
merged.push(CodePathSegment.newNext(
|
||||
context.idGenerator.next(),
|
||||
@@ -120,6 +123,7 @@ ForkContext.prototype = {
|
||||
*/
|
||||
get head() {
|
||||
var list = this.segmentsList;
|
||||
|
||||
return list.length === 0 ? [] : list[list.length - 1];
|
||||
},
|
||||
|
||||
@@ -137,6 +141,7 @@ ForkContext.prototype = {
|
||||
*/
|
||||
get reachable() {
|
||||
var segments = this.head;
|
||||
|
||||
return segments.length > 0 && segments.some(isReachable);
|
||||
},
|
||||
|
||||
@@ -212,6 +217,7 @@ ForkContext.prototype = {
|
||||
assert(context.count === this.count);
|
||||
|
||||
var source = context.segmentsList;
|
||||
|
||||
for (var i = 0; i < source.length; ++i) {
|
||||
this.segmentsList.push(source[i]);
|
||||
}
|
||||
|
||||
@@ -33,10 +33,12 @@ function IdGenerator(prefix) {
|
||||
*/
|
||||
IdGenerator.prototype.next = function() {
|
||||
this.n = 1 + this.n | 0;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (this.n < 0) {
|
||||
this.n = 1;
|
||||
}
|
||||
|
||||
return this.prefix + this.n;
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ function loadConfig(configToLoad) {
|
||||
* @private
|
||||
*/
|
||||
function getPersonalConfig() {
|
||||
var config = {},
|
||||
var config,
|
||||
filename;
|
||||
|
||||
if (PERSONAL_CONFIG_DIR) {
|
||||
@@ -90,7 +90,7 @@ function getPersonalConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
return config || {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +166,7 @@ function Config(options) {
|
||||
this.ignorePath = options.ignorePath;
|
||||
this.cache = {};
|
||||
this.parser = options.parser;
|
||||
this.parserOptions = options.parserOptions || {};
|
||||
|
||||
this.baseConfig = options.baseConfig ? loadConfig(options.baseConfig) : { rules: {} };
|
||||
|
||||
@@ -176,10 +177,17 @@ function Config(options) {
|
||||
return envs;
|
||||
}, {});
|
||||
|
||||
/*
|
||||
* Handle declared globals.
|
||||
* For global variable foo, handle "foo:false" and "foo:true" to set
|
||||
* whether global is writable.
|
||||
* If user declares "foo", convert to "foo:false".
|
||||
*/
|
||||
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");
|
||||
|
||||
return globals;
|
||||
}, {});
|
||||
|
||||
@@ -226,7 +234,7 @@ Config.prototype.getConfig = function(filePath) {
|
||||
}
|
||||
|
||||
// Step 2: Create a copy of the baseConfig
|
||||
config = ConfigOps.merge({parser: this.parser}, this.baseConfig);
|
||||
config = ConfigOps.merge({parser: this.parser, parserOptions: this.parserOptions}, this.baseConfig);
|
||||
|
||||
// Step 3: Merge in the user-specified configuration from .eslintrc and package.json
|
||||
config = ConfigOps.merge(config, userConfig);
|
||||
@@ -237,6 +245,7 @@ Config.prototype.getConfig = function(filePath) {
|
||||
|
||||
config = ConfigOps.merge(config, this.useSpecificConfig);
|
||||
}
|
||||
|
||||
// Step 5: Merge in command line environments
|
||||
debug("Merging command line environment settings");
|
||||
config = ConfigOps.merge(config, { env: this.env });
|
||||
|
||||
@@ -15,6 +15,7 @@ var lodash = require("lodash"),
|
||||
debug = require("debug"),
|
||||
eslint = require("../eslint"),
|
||||
configRule = require("./config-rule"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
recConfig = require("../../conf/eslint.json");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -91,6 +92,7 @@ Registry.prototype = {
|
||||
*/
|
||||
populateFromCoreRules: function() {
|
||||
var rulesConfig = configRule.createCoreRuleConfigs();
|
||||
|
||||
this.rules = makeRegistryItems(rulesConfig);
|
||||
},
|
||||
|
||||
@@ -123,18 +125,32 @@ Registry.prototype = {
|
||||
* @returns {void}
|
||||
*/
|
||||
var addRuleToRuleSet = function(rule) {
|
||||
// This check ensures that there is a rule configuration, and that
|
||||
// it either has fewer than the max cominbations allowed, or if it has
|
||||
// too many configs, we will only use the most basic of them.
|
||||
|
||||
/*
|
||||
* This check ensures that there is a rule configuration and that
|
||||
* it has fewer than the max combinations allowed.
|
||||
* If it has too many configs, we will only use the most basic of
|
||||
* the possible configurations.
|
||||
*/
|
||||
var hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS);
|
||||
|
||||
if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) {
|
||||
// If the rule has too many possible combinations, only take simple ones, avoiding objects.
|
||||
|
||||
/*
|
||||
* If the rule has too many possible combinations, only take
|
||||
* simple ones, avoiding objects.
|
||||
*/
|
||||
if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
ruleSets[idx] = ruleSets[idx] || {};
|
||||
ruleSets[idx][rule] = this.rules[rule][idx].config;
|
||||
// Initialize errorCount to zero, since this is a config which will be linted
|
||||
|
||||
/*
|
||||
* Initialize errorCount to zero, since this is a config which
|
||||
* will be linted.
|
||||
*/
|
||||
this.rules[rule][idx].errorCount = 0;
|
||||
}
|
||||
}.bind(this);
|
||||
@@ -164,6 +180,7 @@ Registry.prototype = {
|
||||
var errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) {
|
||||
return (registryItem.errorCount === 0);
|
||||
});
|
||||
|
||||
if (errorFreeItems.length > 0) {
|
||||
newRegistry.rules[ruleId] = errorFreeItems;
|
||||
} else {
|
||||
@@ -208,6 +225,7 @@ Registry.prototype = {
|
||||
var failingConfigs = this.rules[ruleId].filter(function(registryItem) {
|
||||
return (registryItem.errorCount > 0);
|
||||
});
|
||||
|
||||
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
|
||||
failingRegistry.rules[ruleId] = failingConfigs;
|
||||
}
|
||||
@@ -273,26 +291,37 @@ Registry.prototype = {
|
||||
|
||||
lintedRegistry = new Registry();
|
||||
lintedRegistry.rules = lodash.assign({}, this.rules);
|
||||
|
||||
ruleSets = lintedRegistry.buildRuleSets();
|
||||
|
||||
lintedRegistry = lintedRegistry.stripExtraConfigs();
|
||||
|
||||
debug("Linting with all possible rule combinations");
|
||||
|
||||
filenames = Object.keys(sourceCodes);
|
||||
|
||||
totalFilesLinting = filenames.length * ruleSets.length;
|
||||
|
||||
filenames.forEach(function(filename) {
|
||||
debug("Linting file: " + filename);
|
||||
|
||||
ruleSetIdx = 0;
|
||||
|
||||
ruleSets.forEach(function(ruleSet) {
|
||||
lintConfig = lodash.assign({}, config, {rules: ruleSet});
|
||||
var lintResults = eslint.verify(sourceCodes[filename], lintConfig);
|
||||
|
||||
lintResults.forEach(function(result) {
|
||||
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
|
||||
});
|
||||
|
||||
ruleSetIdx += 1;
|
||||
|
||||
if (cb) {
|
||||
cb(totalFilesLinting); // eslint-disable-line callback-return
|
||||
}
|
||||
});
|
||||
|
||||
// Deallocate for GC
|
||||
sourceCodes[filename] = null;
|
||||
});
|
||||
@@ -312,8 +341,11 @@ Registry.prototype = {
|
||||
*/
|
||||
function extendFromRecommended(config) {
|
||||
var newConfig = lodash.assign({}, config);
|
||||
|
||||
ConfigOps.normalizeToStrings(newConfig);
|
||||
|
||||
var recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
|
||||
return (recConfig.rules[ruleId] === 2 || recConfig.rules[ruleId][0] === 2);
|
||||
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
|
||||
});
|
||||
|
||||
recRules.forEach(function(ruleId) {
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* @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";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -17,11 +19,14 @@ var debug = require("debug"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
validator = require("./config-validator"),
|
||||
Plugins = require("./plugins"),
|
||||
resolveModule = require("resolve"),
|
||||
pathUtil = require("../util/path-util"),
|
||||
ModuleResolver = require("../util/module-resolver"),
|
||||
pathIsInside = require("path-is-inside"),
|
||||
stripComments = require("strip-json-comments"),
|
||||
stringify = require("json-stable-stringify"),
|
||||
isAbsolutePath = require("path-is-absolute");
|
||||
isAbsolutePath = require("path-is-absolute"),
|
||||
defaultOptions = require("../../conf/eslint.json"),
|
||||
requireUncached = require("require-uncached");
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -54,6 +59,8 @@ var CONFIG_FILES = [
|
||||
"package.json"
|
||||
];
|
||||
|
||||
var resolver = new ModuleResolver();
|
||||
|
||||
debug = debug("eslint:config-file");
|
||||
|
||||
/**
|
||||
@@ -92,6 +99,7 @@ function loadYAMLConfigFile(filePath) {
|
||||
var yaml = require("js-yaml");
|
||||
|
||||
try {
|
||||
|
||||
// empty YAML file can be null, so always use
|
||||
return yaml.safeLoad(readFile(filePath)) || {};
|
||||
} catch (e) {
|
||||
@@ -152,7 +160,7 @@ function loadLegacyConfigFile(filePath) {
|
||||
function loadJSConfigFile(filePath) {
|
||||
debug("Loading JS config file: " + filePath);
|
||||
try {
|
||||
return require(filePath);
|
||||
return requireUncached(filePath);
|
||||
} catch (e) {
|
||||
debug("Error reading JavaScript file: " + filePath);
|
||||
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
|
||||
@@ -186,9 +194,8 @@ function loadPackageJSONConfigFile(filePath) {
|
||||
* @private
|
||||
*/
|
||||
function loadConfigFile(file) {
|
||||
var config;
|
||||
|
||||
var filePath = file.filePath;
|
||||
var config,
|
||||
filePath = file.filePath;
|
||||
|
||||
switch (path.extname(filePath)) {
|
||||
case ".js":
|
||||
@@ -232,6 +239,7 @@ function writeJSONConfigFile(config, filePath) {
|
||||
debug("Writing JSON config file: " + filePath);
|
||||
|
||||
var content = stringify(config, {cmp: sortByKey, space: 4});
|
||||
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
@@ -249,6 +257,7 @@ function writeYAMLConfigFile(config, filePath) {
|
||||
var yaml = require("js-yaml");
|
||||
|
||||
var content = yaml.safeDump(config, {sortKeys: true});
|
||||
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
@@ -263,6 +272,7 @@ function writeJSConfigFile(config, filePath) {
|
||||
debug("Writing JS config file: " + filePath);
|
||||
|
||||
var content = "module.exports = " + stringify(config, {cmp: sortByKey, space: 4}) + ";";
|
||||
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
@@ -295,24 +305,42 @@ function write(config, filePath) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the lookup path for node packages referenced in a config file.
|
||||
* If the config
|
||||
* Determines the base directory for node packages referenced in a config file.
|
||||
* This does not include node_modules in the path so it can be used for all
|
||||
* references relative to a config file.
|
||||
* @param {string} configFilePath The config file referencing the file.
|
||||
* @returns {string} The base directory for the file path.
|
||||
* @private
|
||||
*/
|
||||
function getBaseDir(configFilePath) {
|
||||
|
||||
// calculates the path of the project including ESLint as dependency
|
||||
var projectPath = path.resolve(__dirname, "../../../");
|
||||
|
||||
if (configFilePath && pathIsInside(configFilePath, projectPath)) {
|
||||
|
||||
// be careful of https://github.com/substack/node-resolve/issues/78
|
||||
return path.join(path.resolve(configFilePath));
|
||||
}
|
||||
|
||||
/*
|
||||
* default to ESLint project path since it's unlikely that plugins will be
|
||||
* in this directory
|
||||
*/
|
||||
return path.join(projectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the lookup path, including node_modules, for package
|
||||
* references relative to a config file.
|
||||
* @param {string} configFilePath The config file referencing the file.
|
||||
* @returns {string} The lookup path for the file path.
|
||||
* @private
|
||||
*/
|
||||
function getLookupPath(configFilePath) {
|
||||
var basedir = getBaseDir(configFilePath);
|
||||
|
||||
// calculates the path of the project including ESLint as dependency
|
||||
var projectPath = path.resolve(__dirname, "../../../");
|
||||
if (configFilePath && pathIsInside(configFilePath, projectPath)) {
|
||||
// be careful of https://github.com/substack/node-resolve/issues/78
|
||||
return path.resolve(configFilePath);
|
||||
}
|
||||
|
||||
// default to ESLint project path since it's unlikely that plugins will be
|
||||
// in this directory
|
||||
return projectPath;
|
||||
return path.join(basedir, "node_modules");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,11 +348,12 @@ function getLookupPath(configFilePath) {
|
||||
* @param {Object} config The configuration information.
|
||||
* @param {string} filePath The file path from which the configuration information
|
||||
* was loaded.
|
||||
* @param {string} [relativeTo] The path to resolve relative to.
|
||||
* @returns {Object} A new configuration object with all of the "extends" fields
|
||||
* loaded and merged.
|
||||
* @private
|
||||
*/
|
||||
function applyExtends(config, filePath) {
|
||||
function applyExtends(config, filePath, relativeTo) {
|
||||
var configExtends = config.extends;
|
||||
|
||||
// normalize into an array for easier handling
|
||||
@@ -336,12 +365,18 @@ function applyExtends(config, filePath) {
|
||||
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
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -350,11 +385,15 @@ function applyExtends(config, filePath) {
|
||||
|
||||
try {
|
||||
debug("Loading " + parentPath);
|
||||
return ConfigOps.merge(load(parentPath), previousValue);
|
||||
return ConfigOps.merge(load(parentPath, false, relativeTo), 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
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -372,16 +411,33 @@ function applyExtends(config, filePath) {
|
||||
* @private
|
||||
*/
|
||||
function normalizePackageName(name, prefix) {
|
||||
|
||||
/*
|
||||
* On Windows, name can come in with Windows slashes instead of Unix slashes.
|
||||
* Normalize to Unix first to avoid errors later on.
|
||||
* https://github.com/eslint/eslint/issues/5644
|
||||
*/
|
||||
if (name.indexOf("\\") > -1) {
|
||||
name = pathUtil.convertPathToPosix(name);
|
||||
}
|
||||
|
||||
if (name.charAt(0) === "@") {
|
||||
// it's a scoped package
|
||||
// package name is "eslint-config", or just a username
|
||||
|
||||
/*
|
||||
* it's a scoped package
|
||||
* package name is "eslint-config", or just a username
|
||||
*/
|
||||
var scopedPackageShortcutRegex = new RegExp("^(@[^\/]+)(?:\/(?:" + prefix + ")?)?$"),
|
||||
scopedPackageNameRegex = new RegExp("^" + prefix + "(-|$)");
|
||||
|
||||
if (scopedPackageShortcutRegex.test(name)) {
|
||||
name = name.replace(scopedPackageShortcutRegex, "$1/" + prefix);
|
||||
} else if (!scopedPackageNameRegex.test(name.split("/")[1])) {
|
||||
// for scoped packages, insert the eslint-config after the first / unless
|
||||
// the path is already @scope/eslint or @scope/eslint-config-xxx
|
||||
|
||||
/*
|
||||
* for scoped packages, insert the eslint-config after the first / unless
|
||||
* the path is already @scope/eslint or @scope/eslint-config-xxx
|
||||
*/
|
||||
name = name.replace(/^@([^\/]+)\/(.*)$/, "@$1/" + prefix + "-$2");
|
||||
}
|
||||
} else if (name.indexOf(prefix + "-") !== 0) {
|
||||
@@ -400,21 +456,23 @@ function normalizePackageName(name, prefix) {
|
||||
* @private
|
||||
*/
|
||||
function resolve(filePath, relativeTo) {
|
||||
|
||||
if (isFilePath(filePath)) {
|
||||
return { filePath: path.resolve(relativeTo || "", filePath) };
|
||||
} else {
|
||||
var normalizedPackageName;
|
||||
|
||||
if (filePath.indexOf("plugin:") === 0) {
|
||||
var packagePath = filePath.substr(7, filePath.lastIndexOf("/") - 7);
|
||||
var configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
|
||||
filePath = resolveModule.sync(normalizePackageName(packagePath, "eslint-plugin"), {
|
||||
basedir: getLookupPath(relativeTo)
|
||||
});
|
||||
|
||||
normalizedPackageName = normalizePackageName(packagePath, "eslint-plugin");
|
||||
debug("Attempting to resolve " + normalizedPackageName);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath: filePath, configName: configName };
|
||||
} else {
|
||||
filePath = resolveModule.sync(normalizePackageName(filePath, "eslint-config"), {
|
||||
basedir: getLookupPath(relativeTo)
|
||||
});
|
||||
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
|
||||
debug("Attempting to resolve " + normalizedPackageName);
|
||||
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
||||
return { filePath: filePath };
|
||||
}
|
||||
}
|
||||
@@ -426,12 +484,15 @@ function resolve(filePath, relativeTo) {
|
||||
* @param {string} filePath The filename or package name to load the configuration
|
||||
* information from.
|
||||
* @param {boolean} [applyEnvironments=false] Set to true to merge in environment settings.
|
||||
* @param {string} [relativeTo] The path to resolve relative to.
|
||||
* @returns {Object} The configuration information.
|
||||
* @private
|
||||
*/
|
||||
function load(filePath, applyEnvironments) {
|
||||
|
||||
var resolvedPath = resolve(filePath),
|
||||
function load(filePath, applyEnvironments, relativeTo) {
|
||||
var resolvedPath = resolve(filePath, relativeTo),
|
||||
dirname = path.dirname(resolvedPath.filePath),
|
||||
basedir = getBaseDir(dirname),
|
||||
lookupPath = getLookupPath(dirname),
|
||||
config = loadConfigFile(resolvedPath);
|
||||
|
||||
if (config) {
|
||||
@@ -441,23 +502,33 @@ function load(filePath, applyEnvironments) {
|
||||
Plugins.loadAll(config.plugins);
|
||||
}
|
||||
|
||||
// remove parser from config if it is the default parser
|
||||
if (config.parser === defaultOptions.parser) {
|
||||
config.parser = null;
|
||||
}
|
||||
|
||||
// include full path of parser if present
|
||||
if (config.parser) {
|
||||
config.parser = resolveModule.sync(config.parser, {
|
||||
basedir: getLookupPath(path.dirname(path.resolve(filePath)))
|
||||
});
|
||||
if (isFilePath(config.parser)) {
|
||||
config.parser = path.resolve(basedir || "", config.parser);
|
||||
} else {
|
||||
config.parser = resolver.resolve(config.parser, lookupPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
config = applyExtends(config, filePath, basedir);
|
||||
}
|
||||
|
||||
if (config.env && applyEnvironments) {
|
||||
|
||||
// Merge in environment-specific globals and parserOptions.
|
||||
config = ConfigOps.applyEnvironments(config);
|
||||
}
|
||||
@@ -473,11 +544,13 @@ function load(filePath, applyEnvironments) {
|
||||
|
||||
module.exports = {
|
||||
|
||||
getBaseDir: getBaseDir,
|
||||
getLookupPath: getLookupPath,
|
||||
load: load,
|
||||
resolve: resolve,
|
||||
write: write,
|
||||
applyExtends: applyExtends,
|
||||
normalizePackageName: normalizePackageName,
|
||||
CONFIG_FILES: CONFIG_FILES,
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ var util = require("util"),
|
||||
ProgressBar = require("progress"),
|
||||
autoconfig = require("./autoconfig.js"),
|
||||
ConfigFile = require("./config-file"),
|
||||
ConfigOps = require("./config-ops"),
|
||||
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
|
||||
npmUtil = require("../util/npm-util"),
|
||||
recConfig = require("../../conf/eslint.json"),
|
||||
@@ -39,6 +40,7 @@ function writeFile(config, format) {
|
||||
|
||||
// default is .js
|
||||
var extname = ".js";
|
||||
|
||||
if (format === "YAML") {
|
||||
extname = ".yml";
|
||||
} else if (format === "JSON") {
|
||||
@@ -148,12 +150,14 @@ function configureRules(answers, config) {
|
||||
|
||||
// Create a list of recommended rules, because we don't want to disable them
|
||||
var recRules = Object.keys(recConfig.rules).filter(function(ruleId) {
|
||||
return (recConfig.rules[ruleId] === 2 || recConfig.rules[ruleId][0] === 2);
|
||||
return ConfigOps.isErrorSeverity(recConfig.rules[ruleId]);
|
||||
});
|
||||
|
||||
// Find and disable rules which had no error-free configuration
|
||||
failingRegistry = registry.getFailingRulesRegistry();
|
||||
|
||||
Object.keys(failingRegistry.rules).forEach(function(ruleId) {
|
||||
|
||||
// If the rule is recommended, set it to error, otherwise disable it
|
||||
disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0;
|
||||
});
|
||||
@@ -184,8 +188,9 @@ function configureRules(answers, config) {
|
||||
bar.update(BAR_TOTAL);
|
||||
|
||||
// Log out some stats to let the user know what happened
|
||||
var totalRules = Object.keys(newConfig.rules).length;
|
||||
var enabledRules = Object.keys(newConfig.rules).filter(function(ruleId) {
|
||||
var finalRuleIds = Object.keys(newConfig.rules),
|
||||
totalRules = finalRuleIds.length;
|
||||
var enabledRules = finalRuleIds.filter(function(ruleId) {
|
||||
return (newConfig.rules[ruleId] !== 0);
|
||||
}).length;
|
||||
var resultMessage = [
|
||||
@@ -193,7 +198,10 @@ function configureRules(answers, config) {
|
||||
"rules based on " + fileQty,
|
||||
"file" + ((fileQty === 1) ? "." : "s.")
|
||||
].join(" ");
|
||||
|
||||
log.info(resultMessage);
|
||||
|
||||
ConfigOps.normalizeToStrings(newConfig);
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
@@ -230,10 +238,10 @@ function processAnswers(answers) {
|
||||
|
||||
if (answers.source === "prompt") {
|
||||
config.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"];
|
||||
config.rules.indent = ["error", answers.indent];
|
||||
config.rules.quotes = ["error", answers.quotes];
|
||||
config.rules["linebreak-style"] = ["error", answers.linebreak];
|
||||
config.rules.semi = ["error", answers.semi ? "always" : "never"];
|
||||
}
|
||||
|
||||
installModules(config);
|
||||
@@ -242,6 +250,8 @@ function processAnswers(answers) {
|
||||
config = configureRules(answers, config);
|
||||
config = autoconfig.extendFromRecommended(config);
|
||||
}
|
||||
|
||||
ConfigOps.normalizeToStrings(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -256,6 +266,7 @@ function getConfigForStyleGuide(guide) {
|
||||
airbnb: {extends: "airbnb", plugins: ["react"]},
|
||||
standard: {extends: "standard", plugins: ["standard"]}
|
||||
};
|
||||
|
||||
if (!guides[guide]) {
|
||||
throw new Error("You referenced an unsupported guide.");
|
||||
}
|
||||
@@ -273,6 +284,7 @@ function getConfigForStyleGuide(guide) {
|
||||
*/
|
||||
function promptUser(callback) {
|
||||
var config;
|
||||
|
||||
inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
@@ -387,11 +399,8 @@ function promptUser(callback) {
|
||||
// early exit if you are using automatic style generation
|
||||
if (earlyAnswers.source === "auto") {
|
||||
try {
|
||||
if (secondAnswers.jsx) {
|
||||
log.error("Unfortunately, autoconfig does not yet work for JSX code.\nPlease see https://github.com/eslint/eslint/issues/5007 for current status.");
|
||||
return;
|
||||
}
|
||||
var combinedAnswers = lodash.assign({}, earlyAnswers, secondAnswers);
|
||||
|
||||
config = processAnswers(combinedAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
@@ -408,7 +417,7 @@ function promptUser(callback) {
|
||||
type: "list",
|
||||
name: "indent",
|
||||
message: "What style of indentation do you use?",
|
||||
default: "tabs",
|
||||
default: "tab",
|
||||
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
|
||||
},
|
||||
{
|
||||
@@ -441,6 +450,7 @@ function promptUser(callback) {
|
||||
], function(answers) {
|
||||
try {
|
||||
var totalAnswers = lodash.assign({}, earlyAnswers, secondAnswers, answers);
|
||||
|
||||
config = processAnswers(totalAnswers);
|
||||
installModules(config);
|
||||
writeFile(config, answers.format);
|
||||
|
||||
@@ -21,6 +21,12 @@ var lodash = require("lodash"),
|
||||
|
||||
debug = debug("eslint:config-ops");
|
||||
|
||||
var RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
|
||||
RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce(function(map, value, index) {
|
||||
map[value] = index;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -99,6 +105,7 @@ module.exports = {
|
||||
* @returns {Object} merged config object.
|
||||
*/
|
||||
merge: function deepmerge(target, src, combine, isRule) {
|
||||
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -122,7 +129,12 @@ module.exports = {
|
||||
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.
|
||||
|
||||
/*
|
||||
* 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 && [] || {};
|
||||
|
||||
@@ -130,7 +142,9 @@ module.exports = {
|
||||
isRule = !!isRule;
|
||||
if (array) {
|
||||
target = target || [];
|
||||
if (isRule && src.length > 1) {
|
||||
|
||||
// src could be a string, so check for array
|
||||
if (isRule && Array.isArray(src) && src.length > 1) {
|
||||
dst = dst.concat(src);
|
||||
} else {
|
||||
dst = dst.concat(target);
|
||||
@@ -176,7 +190,66 @@ module.exports = {
|
||||
}
|
||||
|
||||
return dst;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts new-style severity settings (off, warn, error) into old-style
|
||||
* severity settings (0, 1, 2) for all rules. Assumption is that severity
|
||||
* values have already been validated as correct.
|
||||
* @param {Object} config The config object to normalize.
|
||||
* @returns {void}
|
||||
*/
|
||||
normalize: function(config) {
|
||||
|
||||
if (config.rules) {
|
||||
Object.keys(config.rules).forEach(function(ruleId) {
|
||||
var ruleConfig = config.rules[ruleId];
|
||||
|
||||
if (typeof ruleConfig === "string") {
|
||||
config.rules[ruleId] = RULE_SEVERITY[ruleConfig.toLowerCase()] || 0;
|
||||
} else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "string") {
|
||||
ruleConfig[0] = RULE_SEVERITY[ruleConfig[0].toLowerCase()] || 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts old-style severity settings (0, 1, 2) into new-style
|
||||
* severity settings (off, warn, error) for all rules. Assumption is that severity
|
||||
* values have already been validated as correct.
|
||||
* @param {Object} config The config object to normalize.
|
||||
* @returns {void}
|
||||
*/
|
||||
normalizeToStrings: function(config) {
|
||||
|
||||
if (config.rules) {
|
||||
Object.keys(config.rules).forEach(function(ruleId) {
|
||||
var ruleConfig = config.rules[ruleId];
|
||||
|
||||
if (typeof ruleConfig === "number") {
|
||||
config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0];
|
||||
} else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") {
|
||||
ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the severity for the given rule configuration represents an error.
|
||||
* @param {int|string|Array} ruleConfig The configuration for an individual rule.
|
||||
* @returns {boolean} True if the rule represents an error, false if not.
|
||||
*/
|
||||
isErrorSeverity: function(ruleConfig) {
|
||||
|
||||
var severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
|
||||
|
||||
if (typeof severity === "string") {
|
||||
severity = RULE_SEVERITY[severity.toLowerCase()] || 0;
|
||||
}
|
||||
|
||||
return (typeof severity === "number" && severity === 2);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@@ -44,6 +44,7 @@ function explodeArray(xs) {
|
||||
*/
|
||||
function combineArrays(arr1, arr2) {
|
||||
var res = [];
|
||||
|
||||
if (arr1.length === 0) {
|
||||
return explodeArray(arr2);
|
||||
}
|
||||
@@ -81,9 +82,11 @@ function combineArrays(arr1, arr2) {
|
||||
function groupByProperty(objects) {
|
||||
var groupedObj = objects.reduce(function(accumulator, obj) {
|
||||
var prop = Object.keys(obj)[0];
|
||||
|
||||
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
return Object.keys(groupedObj).map(function(prop) {
|
||||
return groupedObj[prop];
|
||||
});
|
||||
@@ -144,6 +147,7 @@ function groupByProperty(objects) {
|
||||
*/
|
||||
function combinePropertyObjects(objArr1, objArr2) {
|
||||
var res = [];
|
||||
|
||||
if (objArr1.length === 0) {
|
||||
return objArr2;
|
||||
}
|
||||
@@ -155,6 +159,7 @@ function combinePropertyObjects(objArr1, objArr2) {
|
||||
var combinedObj = {};
|
||||
var obj1Props = Object.keys(obj1);
|
||||
var obj2Props = Object.keys(obj2);
|
||||
|
||||
obj1Props.forEach(function(prop1) {
|
||||
combinedObj[prop1] = obj1[prop1];
|
||||
});
|
||||
@@ -201,10 +206,12 @@ RuleConfigSet.prototype = {
|
||||
*/
|
||||
addErrorSeverity: function(severity) {
|
||||
severity = severity || 2;
|
||||
|
||||
this.ruleConfigs = this.ruleConfigs.map(function(config) {
|
||||
config.unshift(severity);
|
||||
return config;
|
||||
});
|
||||
|
||||
// Add a single config at the beginning consisting of only the severity
|
||||
this.ruleConfigs.unshift(severity);
|
||||
},
|
||||
@@ -228,6 +235,7 @@ RuleConfigSet.prototype = {
|
||||
objectConfigs: [],
|
||||
add: function(property, values) {
|
||||
var optionObj;
|
||||
|
||||
for (var idx = 0; idx < values.length; idx++) {
|
||||
optionObj = {};
|
||||
optionObj[property] = values[idx];
|
||||
@@ -241,8 +249,11 @@ RuleConfigSet.prototype = {
|
||||
}, []);
|
||||
}
|
||||
};
|
||||
// The object schema could have multiple independent properties.
|
||||
// If any contain enums or booleans, they can be added and then combined
|
||||
|
||||
/*
|
||||
* The object schema could have multiple independent properties.
|
||||
* If any contain enums or booleans, they can be added and then combined
|
||||
*/
|
||||
Object.keys(obj.properties).forEach(function(prop) {
|
||||
if (obj.properties[prop].enum) {
|
||||
objectConfigSet.add(prop, obj.properties[prop].enum);
|
||||
@@ -266,15 +277,19 @@ RuleConfigSet.prototype = {
|
||||
*/
|
||||
function generateConfigsFromSchema(schema) {
|
||||
var configSet = new RuleConfigSet();
|
||||
|
||||
if (Array.isArray(schema)) {
|
||||
schema.forEach(function(opt) {
|
||||
if (opt.enum) {
|
||||
configSet.addEnums(opt.enum);
|
||||
}
|
||||
|
||||
if (opt.type && opt.type === "object") {
|
||||
configSet.addObject(opt);
|
||||
}
|
||||
|
||||
if (opt.oneOf) {
|
||||
|
||||
// TODO (IanVS): not yet implemented
|
||||
}
|
||||
});
|
||||
@@ -289,9 +304,11 @@ function generateConfigsFromSchema(schema) {
|
||||
*/
|
||||
function createCoreRuleConfigs() {
|
||||
var ruleList = loadRules();
|
||||
|
||||
return Object.keys(ruleList).reduce(function(accumulator, id) {
|
||||
var rule = rules.get(id);
|
||||
var schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;
|
||||
|
||||
accumulator[id] = generateConfigsFromSchema(schema);
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
var rules = require("../rules"),
|
||||
Environments = require("./environments"),
|
||||
schemaValidator = require("is-my-json-valid");
|
||||
schemaValidator = require("is-my-json-valid"),
|
||||
util = require("util");
|
||||
|
||||
var validators = {
|
||||
rules: Object.create(null)
|
||||
@@ -83,7 +84,10 @@ function validateRuleOptions(id, options, source) {
|
||||
localOptions = [];
|
||||
}
|
||||
|
||||
validSeverity = (severity === 0 || severity === 1 || severity === 2);
|
||||
validSeverity = (
|
||||
severity === 0 || severity === 1 || severity === 2 ||
|
||||
(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))
|
||||
);
|
||||
|
||||
if (validateRule) {
|
||||
validateRule(localOptions);
|
||||
@@ -97,7 +101,10 @@ function validateRuleOptions(id, options, source) {
|
||||
|
||||
if (!validSeverity) {
|
||||
message.push(
|
||||
"\tSeverity should be one of the following: 0 = off, 1 = warning, 2 = error (you passed \"", severity, "\").\n");
|
||||
"\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '",
|
||||
util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, ""),
|
||||
"').\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (validateRule && validateRule.errors) {
|
||||
@@ -136,6 +143,7 @@ function validateEnvironment(environment, source) {
|
||||
source, ":\n",
|
||||
"\tEnvironment key \"", env, "\" is unknown\n"
|
||||
];
|
||||
|
||||
throw new Error(message.join(""));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -117,6 +117,10 @@ module.exports = {
|
||||
} catch (err) {
|
||||
debug("Failed to load plugin eslint-plugin-" + pluginNameWithoutPrefix + ". Proceeding without it.");
|
||||
err.message = "Failed to load plugin " + pluginName + ": " + err.message;
|
||||
err.messageTemplate = "plugin-missing";
|
||||
err.messageData = {
|
||||
pluginName: pluginNameWithoutPrefix
|
||||
};
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
+81
-28
@@ -11,7 +11,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var lodash = require("lodash"),
|
||||
estraverse = require("./util/estraverse"),
|
||||
Traverser = require("./util/traverser"),
|
||||
escope = require("escope"),
|
||||
Environments = require("./config/environments"),
|
||||
blankScriptAST = require("../conf/blank-script.json"),
|
||||
@@ -43,16 +43,20 @@ var DEFAULT_PARSER = require("../conf/eslint.json").parser;
|
||||
*/
|
||||
function parseBooleanConfig(string, comment) {
|
||||
var items = {};
|
||||
|
||||
// Collapse whitespace around : to make parsing easier
|
||||
string = string.replace(/\s*:\s*/g, ":");
|
||||
|
||||
// Collapse whitespace around ,
|
||||
string = string.replace(/\s*,\s*/g, ",");
|
||||
|
||||
string.split(/\s|,+/).forEach(function(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
var pos = name.indexOf(":"),
|
||||
value;
|
||||
|
||||
if (pos !== -1) {
|
||||
value = name.substring(pos + 1, name.length);
|
||||
name = name.substring(0, pos);
|
||||
@@ -76,6 +80,7 @@ function parseBooleanConfig(string, comment) {
|
||||
*/
|
||||
function parseJsonConfig(string, location, messages) {
|
||||
var items = {};
|
||||
|
||||
string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
|
||||
try {
|
||||
items = JSON.parse("{" + string + "}");
|
||||
@@ -103,8 +108,10 @@ function parseJsonConfig(string, location, messages) {
|
||||
*/
|
||||
function parseListConfig(string) {
|
||||
var items = {};
|
||||
|
||||
// Collapse whitespace around ,
|
||||
string = string.replace(/\s*,\s*/g, ",");
|
||||
|
||||
string.split(/,+/).forEach(function(name) {
|
||||
name = name.trim();
|
||||
if (!name) {
|
||||
@@ -136,6 +143,7 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
if (config.env[name]) {
|
||||
var env = Environments.get(name),
|
||||
environmentGlobals = env && env.globals;
|
||||
|
||||
if (environmentGlobals) {
|
||||
lodash.assign(declaredGlobals, environmentGlobals);
|
||||
}
|
||||
@@ -148,6 +156,7 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
|
||||
Object.keys(declaredGlobals).forEach(function(name) {
|
||||
var variable = globalScope.set.get(name);
|
||||
|
||||
if (!variable) {
|
||||
variable = new escope.Variable(name, globalScope);
|
||||
variable.eslintExplicitGlobal = false;
|
||||
@@ -159,6 +168,7 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
|
||||
Object.keys(explicitGlobals).forEach(function(name) {
|
||||
var variable = globalScope.set.get(name);
|
||||
|
||||
if (!variable) {
|
||||
variable = new escope.Variable(name, globalScope);
|
||||
variable.eslintExplicitGlobal = true;
|
||||
@@ -172,23 +182,33 @@ function addDeclaredGlobals(program, globalScope, config) {
|
||||
// mark all exported variables as such
|
||||
Object.keys(exportedGlobals).forEach(function(name) {
|
||||
var variable = globalScope.set.get(name);
|
||||
|
||||
if (variable) {
|
||||
variable.eslintUsed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// "through" contains all references that their definition cannot be found.
|
||||
// Since we augment the global scope using configuration, we need to update references and remove the ones that were added by configuration.
|
||||
/*
|
||||
* "through" contains all references which definitions cannot be found.
|
||||
* Since we augment the global scope using configuration, we need to update
|
||||
* references and remove the ones that were added by configuration.
|
||||
*/
|
||||
globalScope.through = globalScope.through.filter(function(reference) {
|
||||
var name = reference.identifier.name;
|
||||
var variable = globalScope.set.get(name);
|
||||
|
||||
if (variable) {
|
||||
// Links the variable and the reference.
|
||||
// And this reference is removed from `Scope#through`.
|
||||
|
||||
/*
|
||||
* Links the variable and the reference.
|
||||
* And this reference is removed from `Scope#through`.
|
||||
*/
|
||||
reference.resolved = variable;
|
||||
variable.references.push(reference);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -234,15 +254,17 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
|
||||
if (rulesToEnable.length) {
|
||||
rulesToEnable.forEach(function(rule) {
|
||||
for (i = reportingConfig.length - 1; i >= 0; i--) {
|
||||
if (!reportingConfig[i].end && reportingConfig[i].rule === rule ) {
|
||||
if (!reportingConfig[i].end && reportingConfig[i].rule === rule) {
|
||||
reportingConfig[i].end = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
// find all previous disabled locations if they was started as list of rules
|
||||
var prevStart;
|
||||
|
||||
for (i = reportingConfig.length - 1; i >= 0; i--) {
|
||||
if (prevStart && prevStart !== reportingConfig[i].start) {
|
||||
break;
|
||||
@@ -280,7 +302,7 @@ 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|exported|globals?)(\s|$)/.exec(value);
|
||||
var match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
|
||||
|
||||
if (match) {
|
||||
value = value.substring(match.index + match[1].length);
|
||||
@@ -310,8 +332,10 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
|
||||
case "eslint":
|
||||
var items = parseJsonConfig(value, comment.loc, messages);
|
||||
|
||||
Object.keys(items).forEach(function(name) {
|
||||
var ruleValue = items[name];
|
||||
|
||||
validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
|
||||
commentRules[name] = ruleValue;
|
||||
});
|
||||
@@ -319,11 +343,13 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
|
||||
// no default
|
||||
}
|
||||
} else {
|
||||
// comment.type === "Line"
|
||||
} else { // comment.type === "Line"
|
||||
if (match[1] === "eslint-disable-line") {
|
||||
disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value)));
|
||||
enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
|
||||
} else if (match[1] === "eslint-disable-next-line") {
|
||||
disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
|
||||
enableReporting(reportingConfig, { "line": comment.loc.start.line + 2 }, Object.keys(parseListConfig(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,6 +358,7 @@ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messa
|
||||
// apply environment configs
|
||||
Object.keys(commentConfig.env).forEach(function(name) {
|
||||
var env = Environments.get(name);
|
||||
|
||||
if (env) {
|
||||
commentConfig = ConfigOps.merge(commentConfig, env);
|
||||
}
|
||||
@@ -353,6 +380,7 @@ function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
|
||||
for (var i = 0, c = reportingConfig.length; i < c; i++) {
|
||||
|
||||
var ignore = reportingConfig[i];
|
||||
|
||||
if ((!ignore.rule || ignore.rule === ruleId) &&
|
||||
(location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
|
||||
(!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
|
||||
@@ -380,6 +408,7 @@ function prepareConfig(config) {
|
||||
if (typeof config.rules === "object") {
|
||||
Object.keys(config.rules).forEach(function(k) {
|
||||
var rule = config.rules[k];
|
||||
|
||||
if (rule === null) {
|
||||
throw new Error("Invalid config for rule '" + k + "'\.");
|
||||
}
|
||||
@@ -395,6 +424,7 @@ function prepareConfig(config) {
|
||||
if (typeof config.env === "object") {
|
||||
Object.keys(config.env).forEach(function(envName) {
|
||||
var env = Environments.get(envName);
|
||||
|
||||
if (config.env[envName] && env && env.parserOptions) {
|
||||
parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
|
||||
}
|
||||
@@ -418,7 +448,7 @@ function prepareConfig(config) {
|
||||
// can't have global return inside of modules
|
||||
preparedConfig.parserOptions.ecmaFeatures.globalReturn = false;
|
||||
|
||||
// also need at least ES6 six for modules
|
||||
// also need at least ES6 for modules
|
||||
if (!preparedConfig.parserOptions.ecmaVersion || preparedConfig.parserOptions.ecmaVersion < 6) {
|
||||
preparedConfig.parserOptions.ecmaVersion = 6;
|
||||
}
|
||||
@@ -462,8 +492,10 @@ function createStubRule(message) {
|
||||
function getRuleReplacementMessage(ruleId) {
|
||||
if (ruleId in replacements.rules) {
|
||||
var newRules = replacements.rules[ruleId];
|
||||
|
||||
return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", ");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -478,6 +510,7 @@ function findEslintEnv(text) {
|
||||
var match, retv;
|
||||
|
||||
eslintEnvPattern.lastIndex = 0;
|
||||
|
||||
while ((match = eslintEnvPattern.exec(text))) {
|
||||
retv = lodash.assign(retv || {}, parseListConfig(match[1]));
|
||||
}
|
||||
@@ -492,9 +525,12 @@ function findEslintEnv(text) {
|
||||
* @returns {string} The stripped text.
|
||||
*/
|
||||
function stripUnicodeBOM(text) {
|
||||
// Check Unicode BOM.
|
||||
// In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
|
||||
// http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
|
||||
|
||||
/*
|
||||
* Check Unicode BOM.
|
||||
* In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
|
||||
* http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
|
||||
*/
|
||||
if (text.charCodeAt(0) === 0xFEFF) {
|
||||
return text.slice(1);
|
||||
}
|
||||
@@ -518,7 +554,7 @@ module.exports = (function() {
|
||||
scopeMap = null,
|
||||
scopeManager = null,
|
||||
currentFilename = null,
|
||||
controller = null,
|
||||
traverser = null,
|
||||
reportingConfig = [],
|
||||
sourceCode = null;
|
||||
|
||||
@@ -636,7 +672,7 @@ module.exports = (function() {
|
||||
currentScopes = null;
|
||||
scopeMap = null;
|
||||
scopeManager = null;
|
||||
controller = null;
|
||||
traverser = null;
|
||||
reportingConfig = [];
|
||||
sourceCode = null;
|
||||
};
|
||||
@@ -689,6 +725,7 @@ module.exports = (function() {
|
||||
|
||||
// search and apply "eslint-env *".
|
||||
var envInFile = findEslintEnv(text || textOrSourceCode.text);
|
||||
|
||||
if (envInFile) {
|
||||
if (!config || !config.env) {
|
||||
config = lodash.assign({}, config || {}, {env: envInFile});
|
||||
@@ -703,6 +740,7 @@ module.exports = (function() {
|
||||
|
||||
// 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);
|
||||
@@ -734,6 +772,9 @@ module.exports = (function() {
|
||||
config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
|
||||
}
|
||||
|
||||
// ensure that severities are normalized in the config
|
||||
ConfigOps.normalize(config);
|
||||
|
||||
// enable appropriate rules
|
||||
Object.keys(config.rules).filter(function(key) {
|
||||
return getRuleSeverity(config.rules[key]) > 0;
|
||||
@@ -744,8 +785,10 @@ module.exports = (function() {
|
||||
rule;
|
||||
|
||||
ruleCreator = rules.get(key);
|
||||
|
||||
if (!ruleCreator) {
|
||||
var replacementMsg = getRuleReplacementMessage(key);
|
||||
|
||||
if (replacementMsg) {
|
||||
ruleCreator = createStubRule(replacementMsg);
|
||||
} else {
|
||||
@@ -761,6 +804,7 @@ module.exports = (function() {
|
||||
var ruleContext = new RuleContext(
|
||||
key, api, severity, options,
|
||||
config.settings, config.parserOptions, config.parser, ruleCreator.meta);
|
||||
|
||||
rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
|
||||
ruleCreator(ruleContext);
|
||||
|
||||
@@ -779,19 +823,21 @@ module.exports = (function() {
|
||||
|
||||
// save config so rules can access as necessary
|
||||
currentConfig = config;
|
||||
controller = new estraverse.Controller();
|
||||
traverser = new Traverser();
|
||||
|
||||
ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {};
|
||||
ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5;
|
||||
|
||||
// gather data that may be needed by the rules
|
||||
// gather scope data that may be needed by the rules
|
||||
scopeManager = escope.analyze(ast, {
|
||||
ignoreEval: true,
|
||||
nodejsScope: ecmaFeatures.globalReturn,
|
||||
impliedStrict: ecmaFeatures.impliedStrict,
|
||||
ecmaVersion: ecmaVersion,
|
||||
sourceType: currentConfig.parserOptions.sourceType || "script"
|
||||
sourceType: currentConfig.parserOptions.sourceType || "script",
|
||||
fallback: Traverser.getKeys
|
||||
});
|
||||
|
||||
currentScopes = scopeManager.scopes;
|
||||
|
||||
/*
|
||||
@@ -799,11 +845,14 @@ module.exports = (function() {
|
||||
* lookup in getScope.
|
||||
*/
|
||||
scopeMap = [];
|
||||
|
||||
currentScopes.forEach(function(scope, index) {
|
||||
var range = scope.block.range[0];
|
||||
|
||||
// Sometimes two scopes are returned for a given node. This is
|
||||
// handled later in a known way, so just don't overwrite here.
|
||||
/*
|
||||
* Sometimes two scopes are returned for a given node. This is
|
||||
* handled later in a known way, so just don't overwrite here.
|
||||
*/
|
||||
if (!scopeMap[range]) {
|
||||
scopeMap[range] = index;
|
||||
}
|
||||
@@ -822,15 +871,17 @@ module.exports = (function() {
|
||||
}
|
||||
|
||||
var eventGenerator = new NodeEventGenerator(api);
|
||||
|
||||
eventGenerator = new CodePathAnalyzer(eventGenerator);
|
||||
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
|
||||
* that this type of node has been found and react accordingly.
|
||||
* 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 that this type of node has been found
|
||||
* and react accordingly.
|
||||
*/
|
||||
controller.traverse(ast, {
|
||||
traverser.traverse(ast, {
|
||||
enter: function(node, parent) {
|
||||
node.parent = parent;
|
||||
eventGenerator.enterNode(node);
|
||||
@@ -884,6 +935,7 @@ module.exports = (function() {
|
||||
message = location;
|
||||
location = node.loc.start;
|
||||
}
|
||||
|
||||
// else, assume location was provided, so node may be omitted
|
||||
|
||||
if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
|
||||
@@ -966,7 +1018,7 @@ module.exports = (function() {
|
||||
* @returns {ASTNode[]} Array of objects representing ancestors.
|
||||
*/
|
||||
api.getAncestors = function() {
|
||||
return controller.parents();
|
||||
return traverser.parents();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -974,14 +1026,15 @@ module.exports = (function() {
|
||||
* @returns {Object} An object representing the current node's scope.
|
||||
*/
|
||||
api.getScope = function() {
|
||||
var parents = controller.parents(),
|
||||
var parents = traverser.parents(),
|
||||
scope = currentScopes[0];
|
||||
|
||||
// Don't do this for Program nodes - they have no parents
|
||||
if (parents.length) {
|
||||
|
||||
// if current node introduces a scope, add it to the list
|
||||
var current = controller.current();
|
||||
var current = traverser.current();
|
||||
|
||||
if (currentConfig.parserOptions.ecmaVersion >= 6) {
|
||||
if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
||||
parents.push(current);
|
||||
@@ -1039,7 +1092,7 @@ module.exports = (function() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} while ( (scope = scope.upper) );
|
||||
} while ((scope = scope.upper));
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -58,9 +58,11 @@ function FileFinder(files, cwd) {
|
||||
*/
|
||||
function normalizeDirectoryEntries(entries, directory, supportedConfigs) {
|
||||
var fileHash = {};
|
||||
|
||||
entries.forEach(function(entry) {
|
||||
if (supportedConfigs.indexOf(entry) >= 0) {
|
||||
var resolvedEntry = path.resolve(directory, entry);
|
||||
|
||||
if (fs.statSync(resolvedEntry).isFile()) {
|
||||
fileHash[entry] = resolvedEntry;
|
||||
}
|
||||
@@ -103,6 +105,7 @@ FileFinder.prototype.findInDirectoryOrParents = function(directory) {
|
||||
while (directory !== child) {
|
||||
dirs[searched++] = directory;
|
||||
var filesMap = normalizeDirectoryEntries(getDirectoryEntries(directory), directory, names);
|
||||
|
||||
if (Object.keys(filesMap).length) {
|
||||
for (var k = 0; k < names.length; k++) {
|
||||
if (filesMap[names[k]]) {
|
||||
|
||||
@@ -35,6 +35,7 @@ module.exports = function(results) {
|
||||
results.forEach(function(result) {
|
||||
|
||||
var messages = result.messages;
|
||||
|
||||
total += messages.length;
|
||||
|
||||
messages.forEach(function(message) {
|
||||
|
||||
@@ -36,6 +36,7 @@ function pluralize(word, count) {
|
||||
function renderSummary(totalErrors, totalWarnings) {
|
||||
var totalProblems = totalErrors + totalWarnings;
|
||||
var renderedText = totalProblems + " " + pluralize("problem", totalProblems);
|
||||
|
||||
if (totalProblems !== 0) {
|
||||
renderedText += " (" + totalErrors + " " + pluralize("error", totalErrors) + ", " + totalWarnings + " " + pluralize("warning", totalWarnings) + ")";
|
||||
}
|
||||
@@ -64,6 +65,7 @@ function renderColor(totalErrors, totalWarnings) {
|
||||
* @returns {string} HTML (table rows) describing the messages.
|
||||
*/
|
||||
function renderMessages(messages, parentIndex) {
|
||||
|
||||
/**
|
||||
* Get HTML (table row) describing a message.
|
||||
* @param {Object} message Message.
|
||||
|
||||
@@ -45,6 +45,7 @@ module.exports = function(results) {
|
||||
|
||||
messages.forEach(function(message) {
|
||||
var type = message.fatal ? "error" : "failure";
|
||||
|
||||
output += "<testcase time=\"0\" name=\"org.eslint." + (message.ruleId || "unknown") + "\">";
|
||||
output += "<" + type + " message=\"" + lodash.escape(message.message || "") + "\">";
|
||||
output += "<![CDATA[";
|
||||
|
||||
@@ -31,6 +31,7 @@ function getMessageType(message) {
|
||||
function outputDiagnostics(diagnostic) {
|
||||
var prefix = " ";
|
||||
var output = prefix + "---\n";
|
||||
|
||||
output += prefix + yaml.safeDump(diagnostic).split("\n").join("\n" + prefix);
|
||||
output += "...\n";
|
||||
return output;
|
||||
|
||||
@@ -35,6 +35,7 @@ module.exports = function(results) {
|
||||
results.forEach(function(result) {
|
||||
|
||||
var messages = result.messages;
|
||||
|
||||
total += messages.length;
|
||||
|
||||
messages.forEach(function(message) {
|
||||
|
||||
@@ -38,6 +38,7 @@ module.exports = function(results) {
|
||||
results.forEach(function(result) {
|
||||
|
||||
var messages = result.messages;
|
||||
|
||||
total += messages.length;
|
||||
|
||||
messages.forEach(function(message) {
|
||||
|
||||
@@ -12,7 +12,8 @@ var lodash = require("lodash"),
|
||||
fs = require("fs"),
|
||||
path = require("path"),
|
||||
debug = require("debug"),
|
||||
ignore = require("ignore");
|
||||
ignore = require("ignore"),
|
||||
pathUtil = require("./util/path-util");
|
||||
|
||||
debug = debug("eslint:ignored-paths");
|
||||
|
||||
@@ -23,8 +24,8 @@ debug = debug("eslint:ignored-paths");
|
||||
|
||||
var ESLINT_IGNORE_FILENAME = ".eslintignore";
|
||||
var DEFAULT_IGNORE_PATTERNS = [
|
||||
"/node_modules/",
|
||||
"/bower_components/"
|
||||
"/node_modules/*",
|
||||
"/bower_components/*"
|
||||
];
|
||||
var DEFAULT_OPTIONS = {
|
||||
dotfiles: false,
|
||||
@@ -46,61 +47,10 @@ function findIgnoreFile(cwd) {
|
||||
cwd = cwd || DEFAULT_OPTIONS.cwd;
|
||||
|
||||
var ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME);
|
||||
|
||||
return fs.existsSync(ignoreFilePath) ? ignoreFilePath : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Windows with Unix style paths and remove ./ prefix
|
||||
* @param {string} filepath Path to normalize
|
||||
* @returns {string} Normalized filepath
|
||||
*/
|
||||
function normalizeFilepath(filepath) {
|
||||
filepath = filepath.replace(/\\/g, "/");
|
||||
filepath = filepath.replace(/^\.\//, "");
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a prefix from a filepath
|
||||
* @param {string} filepath Path to remove the prefix from
|
||||
* @param {string} prefix Prefix to remove from filepath
|
||||
* @returns {string} Normalized filepath
|
||||
*/
|
||||
function removePrefixFromFilepath(filepath, prefix) {
|
||||
prefix += "/";
|
||||
if (filepath.indexOf(prefix) === 0) {
|
||||
filepath = filepath.substr(prefix.length);
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a filepath
|
||||
* @param {string} filepath Path resolve
|
||||
* @param {string} baseDir Base directory to resolve the filepath from
|
||||
* @returns {string} Resolved filepath
|
||||
*/
|
||||
function resolveFilepath(filepath, baseDir) {
|
||||
if (baseDir) {
|
||||
var base = normalizeFilepath(path.resolve(baseDir));
|
||||
filepath = removePrefixFromFilepath(filepath, base);
|
||||
filepath = removePrefixFromFilepath(filepath, fs.realpathSync(base));
|
||||
}
|
||||
filepath.replace(/^\//, "");
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize and resolve a filepath relative to a given base directory
|
||||
* @param {string} filepath Path resolve
|
||||
* @param {string} baseDir Base directory to resolve the filepath from
|
||||
* @returns {string} Normalized and resolved filepath
|
||||
*/
|
||||
function normalizeAndResolveFilepath(filepath, baseDir) {
|
||||
filepath = normalizeFilepath(filepath);
|
||||
return resolveFilepath(filepath, baseDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge options with defaults
|
||||
* @param {object} options Options to merge with DEFAULT_OPTIONS constant
|
||||
@@ -142,35 +92,40 @@ function IgnoredPaths(options) {
|
||||
* @returns {array} raw ignore rules
|
||||
*/
|
||||
function addIgnoreFile(ig, filepath) {
|
||||
return ig.addIgnoreFile(filepath);
|
||||
ig.ignoreFiles.push(filepath);
|
||||
return ig.add(fs.readFileSync(filepath, "utf8"));
|
||||
}
|
||||
|
||||
this.defaultPatterns = DEFAULT_IGNORE_PATTERNS.concat(options.patterns || []);
|
||||
this.baseDir = ".";
|
||||
this.baseDir = options.cwd;
|
||||
|
||||
this.ig = {
|
||||
custom: new ignore.Ignore({
|
||||
twoGlobstars: true,
|
||||
ignore: []
|
||||
}),
|
||||
default: new ignore.Ignore({
|
||||
twoGlobstars: true,
|
||||
ignore: []
|
||||
})
|
||||
custom: ignore(),
|
||||
default: ignore()
|
||||
};
|
||||
|
||||
// Add a way to keep track of ignored files. This was present in node-ignore
|
||||
// 2.x, but dropped for now as of 3.0.10.
|
||||
this.ig.custom.ignoreFiles = [];
|
||||
this.ig.default.ignoreFiles = [];
|
||||
|
||||
if (options.dotfiles !== true) {
|
||||
addPattern(this.ig.default, ".*");
|
||||
|
||||
/*
|
||||
* ignore files beginning with a dot, but not files in a parent or
|
||||
* ancestor directory (which in relative format will begin with `../`).
|
||||
*/
|
||||
addPattern(this.ig.default, [".*", "!../"]);
|
||||
}
|
||||
|
||||
addPattern(this.ig.default, this.defaultPatterns);
|
||||
|
||||
if (options.ignore !== false) {
|
||||
|
||||
addPattern(this.ig.default, this.defaultPatterns);
|
||||
|
||||
var ignorePath;
|
||||
|
||||
if (options.ignorePattern) {
|
||||
addPattern(this.ig.custom, options.ignorePattern);
|
||||
addPattern(this.ig.default, options.ignorePattern);
|
||||
}
|
||||
|
||||
if (options.ignorePath) {
|
||||
@@ -198,8 +153,9 @@ function IgnoredPaths(options) {
|
||||
|
||||
if (ignorePath) {
|
||||
debug("Adding " + ignorePath);
|
||||
this.baseDir = path.dirname(ignorePath);
|
||||
this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
|
||||
addIgnoreFile(this.ig.custom, ignorePath);
|
||||
addIgnoreFile(this.ig.default, ignorePath);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -217,14 +173,15 @@ function IgnoredPaths(options) {
|
||||
IgnoredPaths.prototype.contains = function(filepath, category) {
|
||||
|
||||
var result = false;
|
||||
filepath = normalizeAndResolveFilepath(filepath, this.baseDir);
|
||||
var absolutePath = path.resolve(this.options.cwd, filepath);
|
||||
var relativePath = pathUtil.getRelativePath(absolutePath, this.options.cwd);
|
||||
|
||||
if ((typeof category === "undefined") || (category === "default")) {
|
||||
result = result || (this.ig.default.filter([filepath]).length === 0);
|
||||
result = result || (this.ig.default.filter([relativePath]).length === 0);
|
||||
}
|
||||
|
||||
if ((typeof category === "undefined") || (category === "custom")) {
|
||||
result = result || (this.ig.custom.filter([filepath]).length === 0);
|
||||
result = result || (this.ig.custom.filter([relativePath]).length === 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -30,6 +30,7 @@ module.exports = function(rulesDir, cwd) {
|
||||
}
|
||||
|
||||
var rules = Object.create(null);
|
||||
|
||||
fs.readdirSync(rulesDir).forEach(function(file) {
|
||||
if (path.extname(file) !== ".js") {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Handle logging for Eslint
|
||||
* @fileoverview Handle logging for ESLint
|
||||
* @author Gyandeep Singh
|
||||
* @copyright 2015 Gyandeep Singh. All rights reserved.
|
||||
*/
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
/* istanbul ignore next */
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Cover for console.log
|
||||
* @returns {void}
|
||||
|
||||
@@ -60,6 +60,11 @@ module.exports = optionator({
|
||||
default: "espree",
|
||||
description: "Specify the parser to be used"
|
||||
},
|
||||
{
|
||||
option: "parser-options",
|
||||
type: "Object",
|
||||
description: "Specify parser options"
|
||||
},
|
||||
{
|
||||
heading: "Caching"
|
||||
},
|
||||
@@ -202,7 +207,7 @@ module.exports = optionator({
|
||||
option: "version",
|
||||
alias: "v",
|
||||
type: "Boolean",
|
||||
description: "Outputs the version number"
|
||||
description: "Output the version number"
|
||||
},
|
||||
{
|
||||
option: "inline-config",
|
||||
|
||||
@@ -73,6 +73,7 @@ var PASSTHROUGHS = [
|
||||
* @param {Object} meta The metadata of the rule
|
||||
*/
|
||||
function RuleContext(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta) {
|
||||
|
||||
// public.
|
||||
this.id = ruleId;
|
||||
this.options = options;
|
||||
@@ -149,9 +150,8 @@ RuleContext.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
// copy over passthrough methods
|
||||
// Copy over passthrough methods. All functions will have 5 or fewer parameters.
|
||||
PASSTHROUGHS.forEach(function(name) {
|
||||
// All functions expected to have less arguments than 5.
|
||||
this[name] = function(a, b, c, d, e) {
|
||||
return this.eslint[name](a, b, c, d, e);
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ function define(ruleId, ruleModule) {
|
||||
*/
|
||||
function load(rulesDir, cwd) {
|
||||
var newRules = loadRules(rulesDir, cwd);
|
||||
|
||||
Object.keys(newRules).forEach(function(ruleId) {
|
||||
define(ruleId, newRules[ruleId]);
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ function isIdentifier(node, name) {
|
||||
*/
|
||||
function isArgumentOfMethodCall(node, index, object, property) {
|
||||
var parent = node.parent;
|
||||
|
||||
return (
|
||||
parent.type === "CallExpression" &&
|
||||
parent.callee.type === "MemberExpression" &&
|
||||
@@ -46,6 +47,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
|
||||
* @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")
|
||||
@@ -53,9 +55,12 @@ function isPropertyDescriptor(node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Object.defineProperties(obj, {foo: {set: ...}})
|
||||
// Object.create(proto, {foo: {set: ...}})
|
||||
/*
|
||||
* 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")
|
||||
@@ -106,6 +111,7 @@ module.exports = {
|
||||
var property = node.properties[i];
|
||||
|
||||
var propToCheck = "";
|
||||
|
||||
if (property.kind === "init") {
|
||||
if (isDescriptor && !property.computed) {
|
||||
propToCheck = property.key.name;
|
||||
@@ -124,6 +130,7 @@ module.exports = {
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ module.exports = {
|
||||
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]]);
|
||||
}
|
||||
});
|
||||
@@ -100,6 +101,7 @@ module.exports = {
|
||||
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]]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,10 +107,13 @@ function isTargetMethod(node) {
|
||||
function isCallbackOfArrayMethod(node) {
|
||||
while (node) {
|
||||
var parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
// Looks up the destination.
|
||||
// e.g.
|
||||
// foo.every(nativeFoo || function foo() { ... });
|
||||
|
||||
/*
|
||||
* Looks up the destination. e.g.,
|
||||
* foo.every(nativeFoo || function foo() { ... });
|
||||
*/
|
||||
case "LogicalExpression":
|
||||
case "ConditionalExpression":
|
||||
node = parent;
|
||||
@@ -124,6 +127,7 @@ function isCallbackOfArrayMethod(node) {
|
||||
// })());
|
||||
case "ReturnStatement":
|
||||
var func = astUtils.getUpperFunction(parent);
|
||||
|
||||
if (func === null || !astUtils.isCallee(func)) {
|
||||
return false;
|
||||
}
|
||||
@@ -195,6 +199,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Stacks this function's information.
|
||||
"onCodePathStart": function(codePath, node) {
|
||||
funcInfo = {
|
||||
|
||||
@@ -21,6 +21,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function validate(node) {
|
||||
var arrowBody = node.body;
|
||||
|
||||
if (arrowBody.type === "BlockStatement") {
|
||||
var blockBody = arrowBody.body;
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
// merge rules with default
|
||||
var rule = { before: true, after: true };
|
||||
var option = context.options[0] || {};
|
||||
var rule = { before: true, after: true },
|
||||
option = context.options[0] || {};
|
||||
|
||||
rule.before = option.before !== false;
|
||||
rule.after = option.after !== false;
|
||||
|
||||
@@ -24,11 +26,13 @@ module.exports = function(context) {
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -40,6 +44,7 @@ module.exports = function(context) {
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -55,6 +60,7 @@ module.exports = function(context) {
|
||||
var countSpace = countSpaces(tokens);
|
||||
|
||||
if (rule.before) {
|
||||
|
||||
// should be space(s) before arrow
|
||||
if (countSpace.before === 0) {
|
||||
context.report({
|
||||
@@ -66,6 +72,7 @@ module.exports = function(context) {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
// should be no space before arrow
|
||||
if (countSpace.before > 0) {
|
||||
context.report({
|
||||
@@ -79,6 +86,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
if (rule.after) {
|
||||
|
||||
// should be space(s) after arrow
|
||||
if (countSpace.after === 0) {
|
||||
context.report({
|
||||
@@ -90,6 +98,7 @@ module.exports = function(context) {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
// should be no space after arrow
|
||||
if (countSpace.after > 0) {
|
||||
context.report({
|
||||
|
||||
@@ -37,6 +37,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function report(reference) {
|
||||
var identifier = reference.identifier;
|
||||
|
||||
context.report(
|
||||
identifier,
|
||||
"'{{name}}' used outside of binding context.",
|
||||
@@ -64,12 +65,15 @@ module.exports = function(context) {
|
||||
*/
|
||||
function isOutsideOfScope(reference) {
|
||||
var idRange = reference.identifier.range;
|
||||
|
||||
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
|
||||
}
|
||||
|
||||
// Gets declared variables, and checks its references.
|
||||
var variables = context.getDeclaredVariables(node);
|
||||
|
||||
for (var i = 0; i < variables.length; ++i) {
|
||||
|
||||
// Reports.
|
||||
variables[i]
|
||||
.references
|
||||
|
||||
@@ -56,6 +56,7 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkSpacingInsideBraces(node) {
|
||||
|
||||
// Gets braces and the first/last token of content.
|
||||
var openBrace = getOpenBrace(node);
|
||||
var closeBrace = context.getLastToken(node);
|
||||
|
||||
@@ -57,8 +57,12 @@ module.exports = function(context) {
|
||||
var blockProperties = arguments;
|
||||
|
||||
return function(node) {
|
||||
[].forEach.call(blockProperties, function(blockProp) {
|
||||
var block = node[blockProp], previousToken, curlyToken, curlyTokenEnd, curlyTokensOnSameLine;
|
||||
Array.prototype.forEach.call(blockProperties, function(blockProp) {
|
||||
var block = node[blockProp],
|
||||
previousToken,
|
||||
curlyToken,
|
||||
curlyTokenEnd,
|
||||
allOnSameLine;
|
||||
|
||||
if (!isBlock(block)) {
|
||||
return;
|
||||
@@ -67,21 +71,27 @@ module.exports = function(context) {
|
||||
previousToken = sourceCode.getTokenBefore(block);
|
||||
curlyToken = sourceCode.getFirstToken(block);
|
||||
curlyTokenEnd = sourceCode.getLastToken(block);
|
||||
curlyTokensOnSameLine = curlyToken.loc.start.line === curlyTokenEnd.loc.start.line;
|
||||
allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
|
||||
|
||||
if (allOnSameLine && params.allowSingleLine) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
} else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
|
||||
context.report(node, OPEN_MESSAGE_ALLMAN);
|
||||
}
|
||||
|
||||
if (!block.body.length || curlyTokensOnSameLine && params.allowSingleLine) {
|
||||
if (!block.body.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
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,7 +116,7 @@ module.exports = function(context) {
|
||||
if (style === "1tbs") {
|
||||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
|
||||
node.consequent.type === "BlockStatement" &&
|
||||
isCurlyPunctuator(tokens[0]) ) {
|
||||
isCurlyPunctuator(tokens[0])) {
|
||||
context.report(node.alternate, CLOSE_MESSAGE);
|
||||
}
|
||||
} else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
|
||||
@@ -172,6 +182,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkSwitchStatement(node) {
|
||||
var tokens;
|
||||
|
||||
if (node.cases && node.cases.length) {
|
||||
tokens = sourceCode.getTokensBefore(node.cases[0], 2);
|
||||
} else {
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = function(context) {
|
||||
* @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;
|
||||
@@ -86,7 +87,7 @@ module.exports = function(context) {
|
||||
lastItem, parentType;
|
||||
|
||||
// if our parent is a return we know we're ok
|
||||
if (closestBlock.type === "ReturnStatement" ) {
|
||||
if (closestBlock.type === "ReturnStatement") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,11 @@ module.exports = function(context) {
|
||||
return {
|
||||
|
||||
"Identifier": function(node) {
|
||||
// Leading and trailing underscores are commonly used to flag private/protected identifiers, strip them
|
||||
|
||||
/*
|
||||
* Leading and trailing underscores are commonly used to flag
|
||||
* private/protected identifiers, strip them
|
||||
*/
|
||||
var name = node.name.replace(/^_+|_+$/g, ""),
|
||||
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
|
||||
|
||||
@@ -83,6 +87,7 @@ module.exports = function(context) {
|
||||
|
||||
// Properties have their own rules
|
||||
} else if (node.parent.type === "Property") {
|
||||
|
||||
// "never" check properties
|
||||
if (properties === "never") {
|
||||
return;
|
||||
|
||||
@@ -10,21 +10,10 @@
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
var lodash = require("lodash");
|
||||
|
||||
/**
|
||||
* Checks whether or not a trailing comma is allowed in a given node.
|
||||
@@ -37,6 +26,7 @@ function getLast(xs) {
|
||||
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" &&
|
||||
@@ -74,7 +64,8 @@ module.exports = function(context) {
|
||||
* @returns {boolean} `true` if the node is multiline.
|
||||
*/
|
||||
function isMultiline(node) {
|
||||
var lastItem = getLast(node.properties || node.elements || node.specifiers);
|
||||
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
||||
|
||||
if (!lastItem) {
|
||||
return false;
|
||||
}
|
||||
@@ -106,7 +97,8 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function forbidTrailingComma(node) {
|
||||
var lastItem = getLast(node.properties || node.elements || node.specifiers);
|
||||
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
||||
|
||||
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
||||
return;
|
||||
}
|
||||
@@ -142,7 +134,8 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function forceTrailingComma(node) {
|
||||
var lastItem = getLast(node.properties || node.elements || node.specifiers);
|
||||
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
||||
|
||||
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
||||
return;
|
||||
}
|
||||
@@ -205,6 +198,7 @@ module.exports = function(context) {
|
||||
|
||||
// Chooses a checking function.
|
||||
var checkForTrailingComma;
|
||||
|
||||
if (mode === "always") {
|
||||
checkForTrailingComma = forceTrailingComma;
|
||||
} else if (mode === "always-multiline") {
|
||||
|
||||
@@ -11,8 +11,18 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var option = context.options[0],
|
||||
THRESHOLD = 20;
|
||||
|
||||
var THRESHOLD = (typeof context.options[0] !== "undefined") ? context.options[0] : 20;
|
||||
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
|
||||
THRESHOLD = option.maximum;
|
||||
}
|
||||
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
|
||||
THRESHOLD = option.max;
|
||||
}
|
||||
if (typeof option === "number") {
|
||||
THRESHOLD = option;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -58,7 +68,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function increaseComplexity() {
|
||||
if (fns.length) {
|
||||
fns[fns.length - 1] ++;
|
||||
fns[fns.length - 1]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +79,7 @@ module.exports = function(context) {
|
||||
* @private
|
||||
*/
|
||||
function increaseSwitchComplexity(node) {
|
||||
|
||||
// Avoiding `default`
|
||||
if (node.test) {
|
||||
increaseComplexity(node);
|
||||
@@ -82,6 +93,7 @@ module.exports = function(context) {
|
||||
* @private
|
||||
*/
|
||||
function increaseLogicalComplexity(node) {
|
||||
|
||||
// Avoiding &&
|
||||
if (node.operator === "||") {
|
||||
increaseComplexity(node);
|
||||
@@ -116,7 +128,25 @@ module.exports = function(context) {
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -34,20 +40,25 @@ module.exports = function(context) {
|
||||
function checkLastSegment(node) {
|
||||
var loc, type;
|
||||
|
||||
// Skip if it expected no return value or unreachable.
|
||||
// When unreachable, all paths are returned or thrown.
|
||||
/*
|
||||
* Skip if it expected no return value or unreachable.
|
||||
* When unreachable, all paths are returned or thrown.
|
||||
*/
|
||||
if (!funcInfo.hasReturnValue ||
|
||||
funcInfo.codePath.currentSegments.every(isUnreachable)
|
||||
funcInfo.codePath.currentSegments.every(isUnreachable) ||
|
||||
astUtils.isES5Constructor(node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust a location and a message.
|
||||
if (node.type === "Program") {
|
||||
|
||||
// The head of program.
|
||||
loc = {line: 1, column: 0};
|
||||
type = "program";
|
||||
} else if (node.type === "ArrowFunctionExpression") {
|
||||
|
||||
// `=>` token
|
||||
loc = context.getSourceCode().getTokenBefore(node.body).loc.start;
|
||||
type = "function";
|
||||
@@ -55,10 +66,12 @@ module.exports = function(context) {
|
||||
node.parent.type === "MethodDefinition" ||
|
||||
(node.parent.type === "Property" && node.parent.method)
|
||||
) {
|
||||
|
||||
// Method name.
|
||||
loc = node.parent.key.loc.start;
|
||||
type = "method";
|
||||
} else {
|
||||
|
||||
// Function name or `function` keyword.
|
||||
loc = (node.id || node).loc.start;
|
||||
type = "function";
|
||||
@@ -74,6 +87,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Initializes/Disposes state of each code path.
|
||||
"onCodePathStart": function(codePath) {
|
||||
funcInfo = {
|
||||
|
||||
@@ -79,6 +79,7 @@ module.exports = function(context) {
|
||||
// assigned later in the same scope.
|
||||
if (!variable.references.some(function(reference) {
|
||||
var write = reference.writeExpr;
|
||||
|
||||
return (
|
||||
reference.from === scope &&
|
||||
write && write.type === "ThisExpression" &&
|
||||
|
||||
@@ -36,19 +36,25 @@ function isConstructorFunction(node) {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
// {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
|
||||
// Information for each constructor.
|
||||
// - upper: Information of the upper constructor.
|
||||
// - hasExtends: A flag which shows whether own class has a valid `extends`
|
||||
// part.
|
||||
// - scope: The scope of own class.
|
||||
// - codePath: The code path object of the constructor.
|
||||
|
||||
/*
|
||||
* {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
|
||||
* Information for each constructor.
|
||||
* - upper: Information of the upper constructor.
|
||||
* - hasExtends: A flag which shows whether own class has a valid `extends`
|
||||
* part.
|
||||
* - scope: The scope of own class.
|
||||
* - codePath: The code path object of the constructor.
|
||||
*/
|
||||
var funcInfo = null;
|
||||
|
||||
// {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
|
||||
// Information for each code path segment.
|
||||
// - calledInSomePaths: A flag of be called `super()` in some code paths.
|
||||
// - calledInEveryPaths: A flag of be called `super()` in all code paths.
|
||||
/*
|
||||
* {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
|
||||
* Information for each code path segment.
|
||||
* - calledInSomePaths: A flag of be called `super()` in some code paths.
|
||||
* - calledInEveryPaths: A flag of be called `super()` in all code paths.
|
||||
* - validNodes:
|
||||
*/
|
||||
var segInfoMap = Object.create(null);
|
||||
|
||||
/**
|
||||
@@ -66,10 +72,22 @@ module.exports = function(context) {
|
||||
* @returns {boolean} The flag which shows `super()` is called in all paths.
|
||||
*/
|
||||
function isCalledInEveryPath(segment) {
|
||||
|
||||
/*
|
||||
* If specific segment is the looped segment of the current segment,
|
||||
* skip the segment.
|
||||
* If not skipped, this never becomes true after a loop.
|
||||
*/
|
||||
if (segment.nextSegments.length === 1 &&
|
||||
segment.nextSegments[0].isLoopedPrevSegment(segment)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return segInfoMap[segment.id].calledInEveryPaths;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Stacks a constructor information.
|
||||
* @param {CodePath} codePath - A code path which was started.
|
||||
@@ -77,21 +95,28 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
"onCodePathStart": function(codePath, node) {
|
||||
if (!isConstructorFunction(node)) {
|
||||
return;
|
||||
}
|
||||
if (isConstructorFunction(node)) {
|
||||
|
||||
// Class > ClassBody > MethodDefinition > FunctionExpression
|
||||
var classNode = node.parent.parent.parent;
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
hasExtends: Boolean(
|
||||
classNode.superClass &&
|
||||
!astUtils.isNullOrUndefined(classNode.superClass)
|
||||
),
|
||||
scope: context.getScope(),
|
||||
codePath: codePath
|
||||
};
|
||||
// Class > ClassBody > MethodDefinition > FunctionExpression
|
||||
var classNode = node.parent.parent.parent;
|
||||
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
isConstructor: true,
|
||||
hasExtends: Boolean(
|
||||
classNode.superClass &&
|
||||
!astUtils.isNullOrUndefined(classNode.superClass)
|
||||
),
|
||||
codePath: codePath
|
||||
};
|
||||
} else {
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
isConstructor: false,
|
||||
hasExtends: false,
|
||||
codePath: codePath
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -102,13 +127,12 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
"onCodePathEnd": function(codePath, node) {
|
||||
if (!isConstructorFunction(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if own class which has a valid `extends` part.
|
||||
var hasExtends = funcInfo.hasExtends;
|
||||
|
||||
funcInfo = funcInfo.upper;
|
||||
|
||||
if (!hasExtends) {
|
||||
return;
|
||||
}
|
||||
@@ -117,6 +141,7 @@ module.exports = function(context) {
|
||||
var segments = codePath.returnedSegments;
|
||||
var calledInEveryPaths = segments.every(isCalledInEveryPath);
|
||||
var calledInSomePaths = segments.some(isCalledInSomePath);
|
||||
|
||||
if (!calledInEveryPaths) {
|
||||
context.report({
|
||||
message: calledInSomePaths ?
|
||||
@@ -133,52 +158,110 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
"onCodePathSegmentStart": function(segment) {
|
||||
// Skip if this is not in a constructor of a class which has a valid
|
||||
// `extends` part.
|
||||
if (!(
|
||||
funcInfo &&
|
||||
funcInfo.hasExtends &&
|
||||
funcInfo.scope === context.getScope().variableScope
|
||||
)) {
|
||||
|
||||
/*
|
||||
* Skip if this is not in a constructor of a class which has a
|
||||
* valid `extends` part.
|
||||
*/
|
||||
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize info.
|
||||
var info = segInfoMap[segment.id] = {
|
||||
calledInSomePaths: false,
|
||||
calledInEveryPaths: false
|
||||
calledInEveryPaths: false,
|
||||
validNodes: []
|
||||
};
|
||||
|
||||
// When there are previous segments, aggregates these.
|
||||
var prevSegments = segment.prevSegments;
|
||||
|
||||
if (prevSegments.length > 0) {
|
||||
info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
|
||||
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update information of the code path segment when a code path was
|
||||
* looped.
|
||||
* @param {CodePathSegment} fromSegment - The code path segment of the
|
||||
* end of a loop.
|
||||
* @param {CodePathSegment} toSegment - A code path segment of the head
|
||||
* of a loop.
|
||||
* @returns {void}
|
||||
*/
|
||||
"onCodePathSegmentLoop": function(fromSegment, toSegment) {
|
||||
|
||||
/*
|
||||
* Skip if this is not in a constructor of a class which has a
|
||||
* valid `extends` part.
|
||||
*/
|
||||
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update information inside of the loop.
|
||||
var isRealLoop = toSegment.prevSegments.length >= 2;
|
||||
|
||||
funcInfo.codePath.traverseSegments(
|
||||
{first: toSegment, last: fromSegment},
|
||||
function(segment) {
|
||||
var info = segInfoMap[segment.id];
|
||||
|
||||
// Updates flags.
|
||||
var prevSegments = segment.prevSegments;
|
||||
|
||||
info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
|
||||
info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
|
||||
|
||||
// If flags become true anew, reports the valid nodes.
|
||||
if (info.calledInSomePaths || isRealLoop) {
|
||||
var nodes = info.validNodes;
|
||||
|
||||
info.validNodes = [];
|
||||
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
var node = nodes[i];
|
||||
|
||||
context.report({
|
||||
message: "Unexpected duplicate 'super()'.",
|
||||
node: node
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks for a call of `super()`.
|
||||
* @param {ASTNode} node - A CallExpression node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
"CallExpression:exit": function(node) {
|
||||
|
||||
// Skip if the node is not `super()`.
|
||||
if (node.callee.type !== "Super") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if this is not in a constructor.
|
||||
if (!(funcInfo && funcInfo.scope === context.getScope().variableScope)) {
|
||||
if (!(funcInfo && funcInfo.isConstructor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reports if needed.
|
||||
if (funcInfo.hasExtends) {
|
||||
// This class has a valid `extends` part.
|
||||
// Checks duplicate `super()`;
|
||||
|
||||
/*
|
||||
* This class has a valid `extends` part.
|
||||
* Checks duplicate `super()`;
|
||||
*/
|
||||
var segments = funcInfo.codePath.currentSegments;
|
||||
var duplicate = false;
|
||||
|
||||
for (var i = 0; i < segments.length; ++i) {
|
||||
var info = segInfoMap[segments[i].id];
|
||||
|
||||
@@ -191,10 +274,15 @@ module.exports = function(context) {
|
||||
message: "Unexpected duplicate 'super()'.",
|
||||
node: node
|
||||
});
|
||||
} else {
|
||||
info.validNodes.push(node);
|
||||
}
|
||||
} else {
|
||||
// This class does not have a valid `extends` part.
|
||||
// Disallow `super()`.
|
||||
|
||||
/*
|
||||
* This class does not have a valid `extends` part.
|
||||
* Disallow `super()`.
|
||||
*/
|
||||
context.report({
|
||||
message: "Unexpected 'super()'.",
|
||||
node: node
|
||||
|
||||
@@ -35,6 +35,7 @@ module.exports = function(context) {
|
||||
function isCollapsedOneLiner(node) {
|
||||
var before = context.getTokenBefore(node),
|
||||
last = context.getLastToken(node);
|
||||
|
||||
return before.loc.start.line === last.loc.end.line;
|
||||
}
|
||||
|
||||
@@ -197,6 +198,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function prepareIfChecks(node) {
|
||||
var preparedChecks = [];
|
||||
|
||||
do {
|
||||
preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
|
||||
if (node.alternate && node.alternate.type !== "IfStatement") {
|
||||
@@ -207,8 +209,12 @@ module.exports = function(context) {
|
||||
} 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.
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -34,8 +34,11 @@ module.exports = function(context) {
|
||||
"SwitchStatement": function(node) {
|
||||
|
||||
if (!node.cases.length) {
|
||||
// skip check of empty switch because there is no easy way
|
||||
// to extract comments inside it now
|
||||
|
||||
/*
|
||||
* skip check of empty switch because there is no easy way
|
||||
* to extract comments inside it now
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,6 +52,7 @@ module.exports = function(context) {
|
||||
var comments;
|
||||
|
||||
var lastCase = last(node.cases);
|
||||
|
||||
comments = context.getComments(lastCase).trailing;
|
||||
|
||||
if (comments.length) {
|
||||
|
||||
@@ -15,8 +15,10 @@ var astUtils = require("../ast-utils");
|
||||
module.exports = function(context) {
|
||||
|
||||
var config = context.options[0],
|
||||
// default to onObject if no preference is passed
|
||||
onObject = config === "object" || !config;
|
||||
onObject;
|
||||
|
||||
// default to onObject if no preference is passed
|
||||
onObject = config === "object" || !config;
|
||||
|
||||
/**
|
||||
* Reports if the dot between object and property is on the correct loccation.
|
||||
|
||||
@@ -16,6 +16,7 @@ module.exports = function(context) {
|
||||
var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
|
||||
|
||||
var allowPattern;
|
||||
|
||||
if (options.allowPattern) {
|
||||
allowPattern = new RegExp(options.allowPattern);
|
||||
}
|
||||
@@ -29,7 +30,7 @@ module.exports = function(context) {
|
||||
(allowKeywords || keywords.indexOf("" + node.property.value) === -1)
|
||||
) {
|
||||
if (!(allowPattern && allowPattern.test(node.property.value))) {
|
||||
context.report(node, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
|
||||
context.report(node.property, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -37,7 +38,7 @@ module.exports = function(context) {
|
||||
!node.computed &&
|
||||
keywords.indexOf("" + node.property.name) !== -1
|
||||
) {
|
||||
context.report(node, "." + node.property.name + " is a syntax error.");
|
||||
context.report(node.property, "." + node.property.name + " is a syntax error.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = function(context) {
|
||||
return {
|
||||
|
||||
"Program": function checkBadEOF(node) {
|
||||
|
||||
// Get the whole source code, not for node only.
|
||||
var src = context.getSource(),
|
||||
location = {column: 1},
|
||||
@@ -24,6 +25,7 @@ module.exports = function(context) {
|
||||
linebreak = linebreakStyle === "unix" ? "\n" : "\r\n";
|
||||
|
||||
if (src[src.length - 1] !== "\n") {
|
||||
|
||||
// file is not newline-terminated
|
||||
location.line = src.split(/\n/g).length;
|
||||
context.report({
|
||||
|
||||
@@ -63,6 +63,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function getOperatorLocation(node) {
|
||||
var opToken = context.getTokenAfter(node.left);
|
||||
|
||||
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ module.exports = function(context) {
|
||||
var node = after ? leftToken : rightToken;
|
||||
var type = spaceRequired ? "Missing" : "Unexpected";
|
||||
var message = type + " space " + side + " *.";
|
||||
|
||||
context.report({
|
||||
node: node,
|
||||
message: message,
|
||||
@@ -81,11 +82,8 @@ module.exports = function(context) {
|
||||
checkSpacing("before", prevToken, starToken);
|
||||
}
|
||||
|
||||
// Only check after when followed by an identifier
|
||||
nextToken = context.getTokenAfter(starToken);
|
||||
if (nextToken.type === "Identifier") {
|
||||
checkSpacing("after", starToken, nextToken);
|
||||
}
|
||||
checkSpacing("after", starToken, nextToken);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -45,6 +45,7 @@ function findReference(scope, node) {
|
||||
*/
|
||||
function isShadowed(scope, node) {
|
||||
var reference = findReference(scope, node);
|
||||
|
||||
return reference && reference.resolved && reference.resolved.defs.length > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function isPattern(stringToCheck) {
|
||||
var firstChar = stringToCheck[0];
|
||||
|
||||
return firstChar === "^";
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ module.exports = function(context) {
|
||||
function matchesConfiguredErrorName(name) {
|
||||
if (isPattern(errorArgument)) {
|
||||
var regexp = new RegExp(errorArgument);
|
||||
|
||||
return regexp.test(name);
|
||||
}
|
||||
return name === errorArgument;
|
||||
|
||||
@@ -27,8 +27,10 @@ module.exports = function(context) {
|
||||
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
|
||||
@@ -61,6 +63,7 @@ module.exports = function(context) {
|
||||
|
||||
var isShort = name.length < minLength;
|
||||
var isLong = name.length > maxLength;
|
||||
|
||||
if (!(isShort || isLong) || exceptions[name]) {
|
||||
return; // Nothing to report
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ module.exports = function(context) {
|
||||
|
||||
// MemberExpressions get special rules
|
||||
if (node.parent.type === "MemberExpression") {
|
||||
|
||||
// return early if properties is false
|
||||
if (!properties) {
|
||||
return;
|
||||
@@ -95,6 +96,7 @@ module.exports = function(context) {
|
||||
|
||||
// Properties have their own rules
|
||||
} else if (node.parent.type === "Property") {
|
||||
|
||||
// return early if properties is false
|
||||
if (!properties) {
|
||||
return;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -61,8 +62,10 @@ module.exports = function(context) {
|
||||
|
||||
if (context.options[1]) {
|
||||
var opts = context.options[1];
|
||||
|
||||
options.SwitchCase = opts.SwitchCase || 0;
|
||||
var variableDeclaratorRules = opts.VariableDeclarator;
|
||||
|
||||
if (typeof variableDeclaratorRules === "number") {
|
||||
options.VariableDeclarator = {
|
||||
var: variableDeclaratorRules,
|
||||
@@ -193,6 +196,24 @@ module.exports = function(context) {
|
||||
return startLine !== endLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check indent for node
|
||||
* @param {ASTNode} node Node to check
|
||||
* @param {int} indent needed indent
|
||||
* @param {boolean} [excludeCommas=false] skip comma on start of line
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkNodeIndent(node, indent, excludeCommas) {
|
||||
var nodeIndent = getNodeIndent(node, false, excludeCommas);
|
||||
|
||||
if (
|
||||
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" &&
|
||||
nodeIndent !== indent && isNodeFirstInLine(node)
|
||||
) {
|
||||
report(node, indent, nodeIndent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check indent for nodes list
|
||||
* @param {ASTNode[]} nodes list of node objects
|
||||
@@ -202,13 +223,12 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkNodesIndent(nodes, indent, excludeCommas) {
|
||||
nodes.forEach(function(node) {
|
||||
var nodeIndent = getNodeIndent(node, false, excludeCommas);
|
||||
if (
|
||||
node.type !== "ArrayExpression" && node.type !== "ObjectExpression" &&
|
||||
nodeIndent !== indent && isNodeFirstInLine(node)
|
||||
) {
|
||||
report(node, indent, nodeIndent);
|
||||
if (node.type === "IfStatement" && node.alternate) {
|
||||
var elseToken = context.getTokenBefore(node.alternate);
|
||||
|
||||
checkNodeIndent(elseToken, indent, excludeCommas);
|
||||
}
|
||||
checkNodeIndent(node, indent, excludeCommas);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -241,6 +261,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkFirstNodeLineIndent(node, firstLineIndent) {
|
||||
var startIndent = getNodeIndent(node, false);
|
||||
|
||||
if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) {
|
||||
report(
|
||||
node,
|
||||
@@ -303,26 +324,30 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkIndentInFunctionBlock(node) {
|
||||
|
||||
// Search first caller in chain.
|
||||
// Ex.:
|
||||
//
|
||||
// Models <- Identifier
|
||||
// .User
|
||||
// .find()
|
||||
// .exec(function() {
|
||||
// // function body
|
||||
// });
|
||||
//
|
||||
// Looks for 'Models'
|
||||
/*
|
||||
* Search first caller in chain.
|
||||
* Ex.:
|
||||
*
|
||||
* Models <- Identifier
|
||||
* .User
|
||||
* .find()
|
||||
* .exec(function() {
|
||||
* // function body
|
||||
* });
|
||||
*
|
||||
* Looks for 'Models'
|
||||
*/
|
||||
var calleeNode = node.parent; // FunctionExpression
|
||||
var indent;
|
||||
|
||||
if (calleeNode.parent &&
|
||||
(calleeNode.parent.type === "Property" ||
|
||||
calleeNode.parent.type === "ArrayExpression")) {
|
||||
|
||||
// If function is part of array or object, comma can be put at left
|
||||
indent = getNodeIndent(calleeNode, false, false);
|
||||
} else {
|
||||
|
||||
// If function is standalone, simple calculate indent
|
||||
indent = getNodeIndent(calleeNode);
|
||||
}
|
||||
@@ -348,6 +373,7 @@ module.exports = function(context) {
|
||||
|
||||
// check if the node is inside a variable
|
||||
var parentVarNode = getVariableDeclaratorNode(node);
|
||||
|
||||
if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
|
||||
indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
|
||||
}
|
||||
@@ -393,6 +419,7 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkIndentInArrayOrObjectBlock(node) {
|
||||
|
||||
// Skip inline
|
||||
if (isSingleLineNode(node)) {
|
||||
return;
|
||||
@@ -429,9 +456,15 @@ module.exports = function(context) {
|
||||
nodeIndent = getNodeIndent(effectiveParent);
|
||||
if (parentVarNode && parentVarNode.loc.start.line !== node.loc.start.line) {
|
||||
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
|
||||
if (parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
|
||||
if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === effectiveParent.loc.start.line) {
|
||||
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
|
||||
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
||||
} else if (
|
||||
parent.type === "ObjectExpression" ||
|
||||
parent.type === "ArrayExpression" ||
|
||||
parent.type === "CallExpression" ||
|
||||
parent.type === "ArrowFunctionExpression" ||
|
||||
parent.type === "NewExpression"
|
||||
) {
|
||||
nodeIndent = nodeIndent + indentSize;
|
||||
}
|
||||
}
|
||||
@@ -447,8 +480,10 @@ module.exports = function(context) {
|
||||
elementsIndent = nodeIndent + indentSize;
|
||||
}
|
||||
|
||||
// check if the node is a multiple variable declaration, if yes then make sure indentation takes into account
|
||||
// variable indentation concept
|
||||
/*
|
||||
* Check if the node is a multiple variable declaration; if so, then
|
||||
* make sure indentation takes that into account.
|
||||
*/
|
||||
if (isNodeInVarOnTop(node, parentVarNode)) {
|
||||
elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
|
||||
}
|
||||
@@ -457,6 +492,7 @@ module.exports = function(context) {
|
||||
checkNodesIndent(elements, elementsIndent, true);
|
||||
|
||||
if (elements.length > 0) {
|
||||
|
||||
// Skip last block line check if last item in same line
|
||||
if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
|
||||
return;
|
||||
@@ -472,7 +508,7 @@ module.exports = function(context) {
|
||||
* @returns {boolean} True if it or its body is a block statement
|
||||
*/
|
||||
function isNodeBodyBlock(node) {
|
||||
return node.type === "BlockStatement" || (node.body && node.body.type === "BlockStatement") ||
|
||||
return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
|
||||
(node.consequent && node.consequent.type === "BlockStatement");
|
||||
}
|
||||
|
||||
@@ -482,6 +518,7 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function blockIndentationCheck(node) {
|
||||
|
||||
// Skip inline blocks
|
||||
if (isSingleLineNode(node)) {
|
||||
return;
|
||||
@@ -499,10 +536,12 @@ module.exports = function(context) {
|
||||
var indent;
|
||||
var nodesToCheck = [];
|
||||
|
||||
// For this statements we should check indent from statement begin
|
||||
// (not from block begin)
|
||||
/*
|
||||
* For this statements we should check indent from statement beginning,
|
||||
* not from the beginning of the block.
|
||||
*/
|
||||
var statementsWithProperties = [
|
||||
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement"
|
||||
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration"
|
||||
];
|
||||
|
||||
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
|
||||
@@ -570,6 +609,7 @@ module.exports = function(context) {
|
||||
var tokenBeforeLastElement = context.getTokenBefore(lastElement);
|
||||
|
||||
if (tokenBeforeLastElement.value === ",") {
|
||||
|
||||
// Special case for comma-first syntax where the semicolon is indented
|
||||
checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement));
|
||||
} else {
|
||||
@@ -620,6 +660,7 @@ module.exports = function(context) {
|
||||
return {
|
||||
"Program": function(node) {
|
||||
if (node.body.length > 0) {
|
||||
|
||||
// Root nodes should have no indent
|
||||
checkNodesIndent(node.body, getNodeIndent(node));
|
||||
}
|
||||
@@ -660,9 +701,11 @@ module.exports = function(context) {
|
||||
},
|
||||
|
||||
"SwitchStatement": function(node) {
|
||||
|
||||
// Switch is not a 'BlockStatement'
|
||||
var switchIndent = getNodeIndent(node);
|
||||
var caseIndent = expectedCaseIndent(node, switchIndent);
|
||||
|
||||
checkNodesIndent(node.cases, caseIndent);
|
||||
|
||||
|
||||
@@ -670,11 +713,13 @@ module.exports = function(context) {
|
||||
},
|
||||
|
||||
"SwitchCase": function(node) {
|
||||
|
||||
// Skip inline cases
|
||||
if (isSingleLineNode(node)) {
|
||||
return;
|
||||
}
|
||||
var caseIndent = expectedCaseIndent(node);
|
||||
|
||||
checkNodesIndent(node.consequent, caseIndent + indentSize);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ module.exports = function(context) {
|
||||
|
||||
var mode = context.options[0] || MODE_ALWAYS;
|
||||
var params = context.options[1] || {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -65,6 +66,7 @@ module.exports = function(context) {
|
||||
id = declaration.id,
|
||||
initialized = isInitialized(declaration),
|
||||
isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
|
||||
|
||||
if (id.type !== "Identifier") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function verifySpacing(node, lineOptions) {
|
||||
var actual = getPropertyWhitespace(node);
|
||||
|
||||
if (actual) { // Object literal getters/setters lack colons
|
||||
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
|
||||
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
|
||||
@@ -359,7 +360,7 @@ module.exports = function(context) {
|
||||
return {
|
||||
"ObjectExpression": function(node) {
|
||||
if (isSingleLine(node)) {
|
||||
verifyListSpacing(node.properties);
|
||||
verifyListSpacing(node.properties.filter(isKeyValueProperty));
|
||||
} else {
|
||||
verifyAlignment(node);
|
||||
}
|
||||
@@ -370,7 +371,7 @@ module.exports = function(context) {
|
||||
|
||||
return {
|
||||
"Property": function(node) {
|
||||
verifySpacing(node, isSingleLine(node) ? singleLineOptions : multiLineOptions);
|
||||
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ module.exports = function(context) {
|
||||
pattern = pattern || PREV_TOKEN;
|
||||
|
||||
var prevToken = sourceCode.getTokenBefore(token);
|
||||
|
||||
if (prevToken &&
|
||||
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
|
||||
!isOpenParenOfTemplate(prevToken) &&
|
||||
@@ -109,6 +110,7 @@ module.exports = function(context) {
|
||||
pattern = pattern || PREV_TOKEN;
|
||||
|
||||
var prevToken = sourceCode.getTokenBefore(token);
|
||||
|
||||
if (prevToken &&
|
||||
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
|
||||
!isOpenParenOfTemplate(prevToken) &&
|
||||
@@ -138,6 +140,7 @@ module.exports = function(context) {
|
||||
pattern = pattern || NEXT_TOKEN;
|
||||
|
||||
var nextToken = sourceCode.getTokenAfter(token);
|
||||
|
||||
if (nextToken &&
|
||||
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
|
||||
!isCloseParenOfTemplate(nextToken) &&
|
||||
@@ -167,6 +170,7 @@ module.exports = function(context) {
|
||||
pattern = pattern || NEXT_TOKEN;
|
||||
|
||||
var nextToken = sourceCode.getTokenAfter(token);
|
||||
|
||||
if (nextToken &&
|
||||
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
|
||||
!isCloseParenOfTemplate(nextToken) &&
|
||||
@@ -209,6 +213,7 @@ module.exports = function(context) {
|
||||
if (override) {
|
||||
var thisBefore = ("before" in override) ? override.before : before;
|
||||
var thisAfter = ("after" in override) ? override.after : after;
|
||||
|
||||
retv[key] = {
|
||||
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
|
||||
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
|
||||
@@ -269,6 +274,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkSpacingAroundFirstToken(node) {
|
||||
var firstToken = node && sourceCode.getFirstToken(node);
|
||||
|
||||
if (firstToken && firstToken.type === "Keyword") {
|
||||
checkSpacingAround(firstToken);
|
||||
}
|
||||
@@ -286,6 +292,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkSpacingBeforeFirstToken(node) {
|
||||
var firstToken = node && sourceCode.getFirstToken(node);
|
||||
|
||||
if (firstToken && firstToken.type === "Keyword") {
|
||||
checkSpacingBefore(firstToken);
|
||||
}
|
||||
@@ -301,6 +308,7 @@ module.exports = function(context) {
|
||||
function checkSpacingAroundTokenBefore(node) {
|
||||
if (node) {
|
||||
var token = sourceCode.getTokenBefore(node);
|
||||
|
||||
while (token.type !== "Keyword") {
|
||||
token = sourceCode.getTokenBefore(token);
|
||||
}
|
||||
@@ -382,6 +390,7 @@ module.exports = function(context) {
|
||||
|
||||
// `of` is not a keyword token.
|
||||
var token = sourceCode.getTokenBefore(node.right);
|
||||
|
||||
while (token.value !== "of") {
|
||||
token = sourceCode.getTokenBefore(token);
|
||||
}
|
||||
@@ -402,11 +411,13 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkSpacingForModuleDeclaration(node) {
|
||||
var firstToken = sourceCode.getFirstToken(node);
|
||||
|
||||
checkSpacingBefore(firstToken, PREV_TOKEN_M);
|
||||
checkSpacingAfter(firstToken, NEXT_TOKEN_M);
|
||||
|
||||
if (node.source) {
|
||||
var fromToken = sourceCode.getTokenBefore(node.source);
|
||||
|
||||
checkSpacingBefore(fromToken, PREV_TOKEN_M);
|
||||
checkSpacingAfter(fromToken, NEXT_TOKEN_M);
|
||||
}
|
||||
@@ -421,6 +432,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function checkSpacingForImportNamespaceSpecifier(node) {
|
||||
var asToken = sourceCode.getFirstToken(node, 1);
|
||||
|
||||
checkSpacingBefore(asToken, PREV_TOKEN_M);
|
||||
}
|
||||
|
||||
@@ -440,11 +452,13 @@ module.exports = function(context) {
|
||||
node,
|
||||
node.static ? 1 : 0
|
||||
);
|
||||
|
||||
checkSpacingAround(token);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Statements
|
||||
DebuggerStatement: checkSpacingAroundFirstToken,
|
||||
WithStatement: checkSpacingAroundFirstToken,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Rule to forbid mixing LF and LFCR line breaks.
|
||||
* @fileoverview Rule to enforce a single linebreak style.
|
||||
* @author Erik Mueller
|
||||
* @copyright 2015 Varun Verma. All rights reserverd.
|
||||
* @copyright 2015 James Whitney. All rights reserved.
|
||||
@@ -50,6 +50,7 @@ module.exports = function(context) {
|
||||
range;
|
||||
|
||||
var i = 0;
|
||||
|
||||
while ((match = pattern.exec(source)) !== null) {
|
||||
i++;
|
||||
if (match[0] === expectedLFChars) {
|
||||
|
||||
@@ -33,6 +33,7 @@ function getEmptyLineNums(lines) {
|
||||
}).map(function(line) {
|
||||
return line.num;
|
||||
});
|
||||
|
||||
return emptyLines;
|
||||
}
|
||||
|
||||
@@ -43,9 +44,11 @@ function getEmptyLineNums(lines) {
|
||||
*/
|
||||
function getCommentLineNums(comments) {
|
||||
var lines = [];
|
||||
|
||||
comments.forEach(function(token) {
|
||||
var start = token.loc.start.line;
|
||||
var end = token.loc.end.line;
|
||||
|
||||
lines.push(start, end);
|
||||
});
|
||||
return lines;
|
||||
@@ -68,6 +71,7 @@ function contains(val, array) {
|
||||
module.exports = function(context) {
|
||||
|
||||
var options = context.options[0] ? lodash.assign({}, context.options[0]) : {};
|
||||
|
||||
options.beforeLineComment = options.beforeLineComment || false;
|
||||
options.afterLineComment = options.afterLineComment || false;
|
||||
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
|
||||
@@ -76,6 +80,7 @@ module.exports = function(context) {
|
||||
options.allowBlockEnd = options.allowBlockEnd || false;
|
||||
|
||||
var sourceCode = context.getSourceCode();
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are on lines starting with or ending with code
|
||||
* @param {ASTNode} node The comment node to check.
|
||||
@@ -160,7 +165,7 @@ module.exports = function(context) {
|
||||
* @returns {boolean} True if the comment is at block start.
|
||||
*/
|
||||
function isCommentAtBlockStart(node) {
|
||||
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement");
|
||||
return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +174,7 @@ module.exports = function(context) {
|
||||
* @returns {boolean} True if the comment is at block end.
|
||||
*/
|
||||
function isCommentAtBlockEnd(node) {
|
||||
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement");
|
||||
return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,18 @@ module.exports = function(context) {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
var functionStack = [],
|
||||
maxDepth = context.options[0] || 4;
|
||||
option = context.options[0],
|
||||
maxDepth = 4;
|
||||
|
||||
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
|
||||
maxDepth = option.maximum;
|
||||
}
|
||||
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
|
||||
maxDepth = option.max;
|
||||
}
|
||||
if (typeof option === "number") {
|
||||
maxDepth = option;
|
||||
}
|
||||
|
||||
/**
|
||||
* When parsing a new function, store it in our function stack
|
||||
@@ -105,7 +116,25 @@ module.exports = function(context) {
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -11,11 +11,15 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
/*
|
||||
* Inspired by 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 = /[^:/?#]:\/\/[^?#]/;
|
||||
|
||||
/**
|
||||
@@ -28,10 +32,12 @@ module.exports = function(context) {
|
||||
*/
|
||||
function computeLineLength(line, tabWidth) {
|
||||
var extraCharacterCount = 0;
|
||||
|
||||
line.replace(/\t/g, function(match, offset) {
|
||||
var totalOffset = offset + extraCharacterCount,
|
||||
previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
|
||||
spaceCount = tabWidth - previousTabStopOffset;
|
||||
|
||||
extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
|
||||
});
|
||||
return line.length + extraCharacterCount;
|
||||
@@ -40,10 +46,12 @@ module.exports = function(context) {
|
||||
// The options object must be the last option specified…
|
||||
var lastOption = context.options[context.options.length - 1];
|
||||
var options = typeof lastOption === "object" ? Object.create(lastOption) : {};
|
||||
|
||||
// …but max code length…
|
||||
if (typeof context.options[0] === "number") {
|
||||
options.code = context.options[0];
|
||||
}
|
||||
|
||||
// …and tabWidth can be optionally specified directly as integers.
|
||||
if (typeof context.options[1] === "number") {
|
||||
options.tabWidth = context.options[1];
|
||||
@@ -104,6 +112,7 @@ module.exports = function(context) {
|
||||
* @returns {string} Line without comment and trailing whitepace
|
||||
*/
|
||||
function stripTrailingComment(line, lineNumber, comment) {
|
||||
|
||||
// loc.column is zero-indexed
|
||||
return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
|
||||
}
|
||||
@@ -115,26 +124,38 @@ module.exports = function(context) {
|
||||
* @private
|
||||
*/
|
||||
function checkProgramForMaxLength(node) {
|
||||
|
||||
// split (honors line-ending)
|
||||
var lines = context.getSourceLines(),
|
||||
|
||||
// list of comments to ignore
|
||||
comments = ignoreComments || maxCommentLength ? context.getAllComments() : [],
|
||||
comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? 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;
|
||||
// if we're checking comment length; we need to know whether this
|
||||
// line is a comment
|
||||
|
||||
/*
|
||||
* if we're checking comment length; we need to know whether this
|
||||
* line is a comment
|
||||
*/
|
||||
var lineIsComment = false;
|
||||
|
||||
// we can short-circuit the comment checks if we're already out of comments to check
|
||||
/*
|
||||
* 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];
|
||||
|
||||
@@ -146,6 +167,7 @@ module.exports = function(context) {
|
||||
}
|
||||
if (ignorePattern && ignorePattern.test(line) ||
|
||||
ignoreUrls && URL_REGEXP.test(line)) {
|
||||
|
||||
// ignore this line
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,18 @@ module.exports = function(context) {
|
||||
//--------------------------------------------------------------------------
|
||||
// Constants
|
||||
//--------------------------------------------------------------------------
|
||||
var option = context.options[0],
|
||||
THRESHOLD = 10;
|
||||
|
||||
var THRESHOLD = context.options[0] || 10;
|
||||
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
|
||||
THRESHOLD = option.maximum;
|
||||
}
|
||||
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
|
||||
THRESHOLD = option.max;
|
||||
}
|
||||
if (typeof option === "number") {
|
||||
THRESHOLD = option;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -39,6 +49,7 @@ module.exports = function(context) {
|
||||
|
||||
if (callbackStack.length > THRESHOLD) {
|
||||
var opts = {num: callbackStack.length, max: THRESHOLD};
|
||||
|
||||
context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +79,25 @@ module.exports = function(context) {
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -13,7 +13,18 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var numParams = context.options[0] || 3;
|
||||
var option = context.options[0],
|
||||
numParams = 3;
|
||||
|
||||
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
|
||||
numParams = option.maximum;
|
||||
}
|
||||
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
|
||||
numParams = option.max;
|
||||
}
|
||||
if (typeof option === "number") {
|
||||
numParams = option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a function to see if it has too many parameters.
|
||||
@@ -40,7 +51,25 @@ module.exports = function(context) {
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @fileoverview Specify the maximum number of statements allowed per line.
|
||||
* @author Kenneth Williams
|
||||
* @copyright 2016 Kenneth Williams. All rights reserved.
|
||||
* @copyright 2016 Michael Ficarra. All rights reserved.
|
||||
* See LICENSE file in root directory for full license.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var options = context.options[0] || {},
|
||||
lastStatementLine = 0,
|
||||
numberOfStatementsOnThisLine = 0,
|
||||
maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reports a node
|
||||
* @param {ASTNode} node The node to report
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function report(node) {
|
||||
context.report(
|
||||
node,
|
||||
"This line has too many statements. Maximum allowed is {{max}}.",
|
||||
{ max: maxStatementsPerLine });
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce a maximum number of statements per line
|
||||
* @param {ASTNode} nodes Array of nodes to evaluate
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function enforceMaxStatementsPerLine(nodes) {
|
||||
if (nodes.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0, l = nodes.length; i < l; ++i) {
|
||||
var currentStatement = nodes[i];
|
||||
|
||||
if (currentStatement.loc.start.line === lastStatementLine) {
|
||||
++numberOfStatementsOnThisLine;
|
||||
} else {
|
||||
numberOfStatementsOnThisLine = 1;
|
||||
lastStatementLine = currentStatement.loc.end.line;
|
||||
}
|
||||
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
|
||||
report(currentStatement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each line in the body of a node
|
||||
* @param {ASTNode} node node to evaluate
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkLinesInBody(node) {
|
||||
enforceMaxStatementsPerLine(node.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each line in the consequent of a switch case
|
||||
* @param {ASTNode} node node to evaluate
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkLinesInConsequent(node) {
|
||||
enforceMaxStatementsPerLine(node.consequent);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"Program": checkLinesInBody,
|
||||
"BlockStatement": checkLinesInBody,
|
||||
"SwitchCase": checkLinesInConsequent
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
@@ -17,10 +17,21 @@ module.exports = function(context) {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
var functionStack = [],
|
||||
maxStatements = context.options[0] || 10,
|
||||
option = context.options[0],
|
||||
maxStatements = 10,
|
||||
ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
|
||||
topLevelFunctions = [];
|
||||
|
||||
if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") {
|
||||
maxStatements = option.maximum;
|
||||
}
|
||||
if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") {
|
||||
maxStatements = option.max;
|
||||
}
|
||||
if (typeof option === "number") {
|
||||
maxStatements = option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a node if it has too many statements
|
||||
* @param {ASTNode} node node to evaluate
|
||||
@@ -55,6 +66,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function endFunction(node) {
|
||||
var count = functionStack.pop();
|
||||
|
||||
if (ignoreTopLevelFunctions && functionStack.length === 0) {
|
||||
topLevelFunctions.push({ node: node, count: count});
|
||||
} else {
|
||||
@@ -95,6 +107,7 @@ module.exports = function(context) {
|
||||
topLevelFunctions.forEach(function(element) {
|
||||
var count = element.count;
|
||||
var node = element.node;
|
||||
|
||||
reportIfTooManyStatements(node, count, maxStatements);
|
||||
});
|
||||
}
|
||||
@@ -104,8 +117,26 @@ module.exports = function(context) {
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maximum": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
@@ -38,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");
|
||||
@@ -78,6 +79,7 @@ function calculateCapIsNewExceptions(config) {
|
||||
module.exports = function(context) {
|
||||
|
||||
var config = context.options[0] ? lodash.assign({}, context.options[0]) : {};
|
||||
|
||||
config.newIsCap = config.newIsCap !== false;
|
||||
config.capIsNew = config.capIsNew !== false;
|
||||
var skipProperties = config.properties === false;
|
||||
@@ -129,6 +131,7 @@ module.exports = function(context) {
|
||||
var firstCharUpper = firstChar.toUpperCase();
|
||||
|
||||
if (firstCharLower === firstCharUpper) {
|
||||
|
||||
// char has no uppercase variant, so it's non-alphabetic
|
||||
return "non-alpha";
|
||||
} else if (firstChar === firstCharLower) {
|
||||
@@ -151,6 +154,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
|
||||
|
||||
// allow if callee is Date.UTC
|
||||
return node.callee.object.type === "Identifier" &&
|
||||
node.callee.object.name === "Date";
|
||||
@@ -183,9 +187,11 @@ module.exports = function(context) {
|
||||
listeners.NewExpression = function(node) {
|
||||
|
||||
var constructorName = extractNameFromExpression(node);
|
||||
|
||||
if (constructorName) {
|
||||
var capitalization = getCap(constructorName);
|
||||
var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
|
||||
|
||||
if (!isAllowed) {
|
||||
report(node, "A constructor name should not start with a lowercase letter.");
|
||||
}
|
||||
@@ -197,9 +203,11 @@ module.exports = function(context) {
|
||||
listeners.CallExpression = function(node) {
|
||||
|
||||
var calleeName = extractNameFromExpression(node);
|
||||
|
||||
if (calleeName) {
|
||||
var capitalization = getCap(calleeName);
|
||||
var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
|
||||
|
||||
if (!isAllowed) {
|
||||
report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ module.exports = function(context) {
|
||||
var prenticesTokens = tokens.filter(function(token) {
|
||||
return token.value === "(" || token.value === ")";
|
||||
});
|
||||
|
||||
if (prenticesTokens.length < 2) {
|
||||
context.report(node, "Missing '()' invoking a constructor");
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function isLastNode(node) {
|
||||
var token = sourceCode.getTokenAfter(node);
|
||||
|
||||
return !token || (token.type === "Punctuator" && token.value === "}");
|
||||
}
|
||||
|
||||
@@ -83,10 +84,12 @@ module.exports = function(context) {
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
|
||||
/**
|
||||
* @fileoverview Rule to require newlines before `return` statement
|
||||
* @author Kai Cataldo
|
||||
* @copyright 2016 Kai Cataldo. All rights reserved.
|
||||
* See LICENSE file in root directory for full license.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var sourceCode = context.getSourceCode();
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Tests whether node is preceded by supplied tokens
|
||||
* @param {ASTNode} node - node to check
|
||||
* @param {array} testTokens - array of tokens to test against
|
||||
* @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
|
||||
* @private
|
||||
*/
|
||||
function isPrecededByTokens(node, testTokens) {
|
||||
var tokenBefore = sourceCode.getTokenBefore(node);
|
||||
|
||||
return testTokens.some(function(token) {
|
||||
return tokenBefore.value === token;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether node is the first node after statement or in block
|
||||
* @param {ASTNode} node - node to check
|
||||
* @returns {boolean} Whether or not the node is the first node after statement or in block
|
||||
* @private
|
||||
*/
|
||||
function isFirstNode(node) {
|
||||
var parentType = node.parent.type;
|
||||
|
||||
if (node.parent.body) {
|
||||
return Array.isArray(node.parent.body)
|
||||
? node.parent.body[0] === node
|
||||
: node.parent.body === node;
|
||||
}
|
||||
|
||||
if (parentType === "IfStatement") {
|
||||
return isPrecededByTokens(node, ["else", ")"]);
|
||||
} else if (parentType === "DoWhileStatement") {
|
||||
return isPrecededByTokens(node, ["do"]);
|
||||
} else if (parentType === "SwitchCase") {
|
||||
return isPrecededByTokens(node, [":"]);
|
||||
} else {
|
||||
return isPrecededByTokens(node, [")"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of lines of comments that precede the node
|
||||
* @param {ASTNode} node - node to check for overlapping comments
|
||||
* @param {token} tokenBefore - previous token to check for overlapping comments
|
||||
* @returns {number} Number of lines of comments that precede the node
|
||||
* @private
|
||||
*/
|
||||
function calcCommentLines(node, tokenBefore) {
|
||||
var comments = sourceCode.getComments(node).leading;
|
||||
var numLinesComments = 0;
|
||||
|
||||
if (!comments.length) {
|
||||
return numLinesComments;
|
||||
}
|
||||
|
||||
comments.forEach(function(comment) {
|
||||
numLinesComments++;
|
||||
|
||||
if (comment.type === "Block") {
|
||||
numLinesComments += comment.loc.end.line - comment.loc.start.line;
|
||||
}
|
||||
|
||||
// avoid counting lines with inline comments twice
|
||||
if (comment.loc.start.line === tokenBefore.loc.end.line) {
|
||||
numLinesComments--;
|
||||
}
|
||||
|
||||
if (comment.loc.end.line === node.loc.start.line) {
|
||||
numLinesComments--;
|
||||
}
|
||||
});
|
||||
|
||||
return numLinesComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether node is preceded by a newline
|
||||
* @param {ASTNode} node - node to check
|
||||
* @returns {boolean} Whether or not the node is preceded by a newline
|
||||
* @private
|
||||
*/
|
||||
function hasNewlineBefore(node) {
|
||||
var tokenBefore = sourceCode.getTokenBefore(node);
|
||||
var lineNumTokenBefore = tokenBefore.loc.end.line;
|
||||
var lineNumNode = node.loc.start.line;
|
||||
var commentLines = calcCommentLines(node, tokenBefore);
|
||||
|
||||
return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports expected/unexpected newline before return statement
|
||||
* @param {ASTNode} node - the node to report in the event of an error
|
||||
* @param {boolean} isExpected - whether the newline is expected or not
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function reportError(node, isExpected) {
|
||||
var expected = isExpected ? "Expected" : "Unexpected";
|
||||
|
||||
context.report({
|
||||
node: node,
|
||||
message: expected + " newline before return statement."
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ReturnStatement: function(node) {
|
||||
if (isFirstNode(node) && hasNewlineBefore(node)) {
|
||||
reportError(node, false);
|
||||
} else if (!isFirstNode(node) && !hasNewlineBefore(node)) {
|
||||
reportError(node, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* @fileoverview Rule to ensure newline per method call when chaining calls
|
||||
* @author Rajendra Patil
|
||||
* @author Burak Yigit Kaya
|
||||
* @copyright 2016 Rajendra Patil. All rights reserved.
|
||||
* @copyright 2016 Burak Yigit Kaya. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -13,90 +15,31 @@
|
||||
module.exports = function(context) {
|
||||
|
||||
var options = context.options[0] || {},
|
||||
codeStateMap = {},
|
||||
ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
|
||||
|
||||
/**
|
||||
* Check and Capture State if the chained calls/members
|
||||
* @param {ASTNode} node The node to check
|
||||
* @param {object} codeState The state of the code current code to be filled
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkAndCaptureStateRecursively(node, codeState) {
|
||||
var valid = false,
|
||||
objectLineNumber,
|
||||
propertyLineNumber;
|
||||
if (node.callee) {
|
||||
node = node.callee;
|
||||
codeState.hasFunctionCall = true;
|
||||
}
|
||||
|
||||
if (node.object) {
|
||||
codeState.depth++;
|
||||
|
||||
objectLineNumber = node.object.loc.end.line;
|
||||
propertyLineNumber = node.property.loc.end.line;
|
||||
valid = node.computed || propertyLineNumber > objectLineNumber;
|
||||
|
||||
if (!valid) {
|
||||
codeState.reports.push({
|
||||
node: node,
|
||||
text: "Expected line break after `{{code}}`.",
|
||||
depth: codeState.depth
|
||||
});
|
||||
}
|
||||
// Recurse
|
||||
checkAndCaptureStateRecursively(node.object, codeState);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Verify and report the captured state report
|
||||
* @param {object} codeState contains the captured state with `hasFunctionCall, reports and depth`
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportState(codeState) {
|
||||
var report;
|
||||
if (codeState.hasFunctionCall && codeState.depth > ignoreChainWithDepth && codeState.reports) {
|
||||
while (codeState.reports.length) {
|
||||
report = codeState.reports.shift();
|
||||
context.report(report.node, report.text, {
|
||||
code: context.getSourceCode().getText(report.node.object).replace(/\r\n|\r|\n/g, "\\n") // Not to print newlines in error report
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the node state object with default values.
|
||||
* @returns {void}
|
||||
*/
|
||||
function initializeState() {
|
||||
return {
|
||||
visited: false,
|
||||
hasFunctionCall: false,
|
||||
depth: 1,
|
||||
reports: []
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Process the said node and recurse internally
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {void}
|
||||
*/
|
||||
function processNode(node) {
|
||||
var stateKey = [node.loc.start.line, node.loc.start.column].join("@"),
|
||||
codeState = codeStateMap[stateKey] = (codeStateMap[stateKey] || initializeState());
|
||||
if (!codeState.visited) {
|
||||
codeState.visited = true;
|
||||
checkAndCaptureStateRecursively(node, codeState);
|
||||
}
|
||||
reportState(codeState);
|
||||
}
|
||||
|
||||
return {
|
||||
"CallExpression": processNode,
|
||||
"MemberExpression": processNode
|
||||
"CallExpression:exit": function(node) {
|
||||
if (!node.callee || node.callee.type !== "MemberExpression") {
|
||||
return;
|
||||
}
|
||||
|
||||
var callee = node.callee;
|
||||
var parent = callee.object;
|
||||
var depth = 1;
|
||||
|
||||
while (parent && parent.callee) {
|
||||
depth += 1;
|
||||
parent = parent.callee.object;
|
||||
}
|
||||
|
||||
if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
|
||||
context.report(
|
||||
callee.property,
|
||||
callee.property.loc.start,
|
||||
"Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`."
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ function findReference(scope, node) {
|
||||
*/
|
||||
function isShadowed(scope, globalScope, node) {
|
||||
var reference = findReference(scope, node);
|
||||
|
||||
return reference && reference.resolved && reference.resolved.defs.length > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ module.exports = function(context) {
|
||||
"SwitchCase": function(node) {
|
||||
for (var i = 0; i < node.consequent.length; i++) {
|
||||
var statement = node.consequent[i];
|
||||
|
||||
if (isLexicalDeclaration(statement)) {
|
||||
context.report({
|
||||
node: node,
|
||||
|
||||
@@ -87,6 +87,7 @@ module.exports = function(context) {
|
||||
!isParenthesisedTwice(node.test)
|
||||
)
|
||||
) {
|
||||
|
||||
// must match JSHint's error message
|
||||
context.report({
|
||||
node: node,
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var astUtils = require("../ast-utils.js");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -38,7 +40,7 @@
|
||||
* @returns {boolean} `true` if the node is a conditional expression.
|
||||
*/
|
||||
function isConditional(node) {
|
||||
return node.body && node.body.type === "ConditionalExpression";
|
||||
return node && node.type === "ConditionalExpression";
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -46,13 +48,17 @@ function isConditional(node) {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var config = context.options[0] || {};
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
var body = node.body;
|
||||
|
||||
if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) {
|
||||
context.report(node, "Arrow function used ambiguously with a conditional expression.");
|
||||
}
|
||||
}
|
||||
@@ -62,4 +68,10 @@ module.exports = function(context) {
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
module.exports.schema = [{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowParens: {type: "boolean"}
|
||||
},
|
||||
additionalProperties: false
|
||||
}];
|
||||
@@ -19,7 +19,7 @@ module.exports = function(context) {
|
||||
if (node.object.name === "console") {
|
||||
var blockConsole = true;
|
||||
|
||||
if ( context.options.length > 0 ) {
|
||||
if (context.options.length > 0) {
|
||||
var allowedProperties = context.options[0].allow;
|
||||
var passedProperty = node.property.name;
|
||||
var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1);
|
||||
|
||||
@@ -30,15 +30,20 @@ module.exports = function(context) {
|
||||
case "ObjectExpression":
|
||||
case "ArrayExpression":
|
||||
return true;
|
||||
|
||||
case "UnaryExpression":
|
||||
return isConstant(node.argument);
|
||||
|
||||
case "BinaryExpression":
|
||||
case "LogicalExpression":
|
||||
return isConstant(node.left) && isConstant(node.right) && node.operator !== "in";
|
||||
|
||||
case "AssignmentExpression":
|
||||
return (node.operator === "=") && isConstant(node.right);
|
||||
|
||||
case "SequenceExpression":
|
||||
return isConstant(node.expressions[node.expressions.length - 1]);
|
||||
|
||||
// no default
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -23,9 +23,11 @@ module.exports = function(context) {
|
||||
} else if (typeof node.value === "string") {
|
||||
|
||||
var parent = context.getAncestors().pop();
|
||||
|
||||
if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
|
||||
parent.callee.type === "Identifier" && parent.callee.name === "RegExp"
|
||||
) {
|
||||
|
||||
// there could be an invalid regular expression string
|
||||
try {
|
||||
return new RegExp(node.value);
|
||||
|
||||
@@ -42,6 +42,7 @@ module.exports = function(context) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -49,6 +50,7 @@ module.exports = function(context) {
|
||||
|
||||
// Checks and reports duplications.
|
||||
var defs = variable.defs.filter(isParameter);
|
||||
|
||||
if (defs.length >= 2) {
|
||||
context.report({
|
||||
node: node,
|
||||
|
||||
@@ -53,6 +53,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Initializes the stack of state of member declarations.
|
||||
"Program": function() {
|
||||
stack = [];
|
||||
@@ -77,6 +78,7 @@ module.exports = function(context) {
|
||||
var name = getName(node.key);
|
||||
var state = getState(name, node.static);
|
||||
var isDuplicate = false;
|
||||
|
||||
if (node.kind === "get") {
|
||||
isDuplicate = (state.init || state.get);
|
||||
state.get = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ module.exports = function(context) {
|
||||
|
||||
node.cases.forEach(function(switchCase) {
|
||||
var key = context.getSource(switchCase.test);
|
||||
|
||||
if (mapping[key]) {
|
||||
context.report(switchCase, "Duplicate case label.");
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @fileoverview Restrict usage of duplicate imports.
|
||||
* @author Simen Bekkhus
|
||||
* @copyright 2016 Simen Bekkhus. All rights reserved.
|
||||
* See LICENSE file in root directory for full license.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the name of the module imported or re-exported.
|
||||
*
|
||||
* @param {ASTNode} node - A node to get.
|
||||
* @returns {string} the name of the module, or empty string if no name.
|
||||
*/
|
||||
function getValue(node) {
|
||||
if (node && node.source && node.source.value) {
|
||||
return node.source.value.trim();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the name of the import or export exists in the given array, and reports if so.
|
||||
*
|
||||
* @param {RuleContext} context - The ESLint rule context object.
|
||||
* @param {ASTNode} node - A node to get.
|
||||
* @param {string} value - The name of the imported or exported module.
|
||||
* @param {string[]} array - The array containing other imports or exports in the file.
|
||||
* @param {string} message - A message to be reported after the name of the module
|
||||
*
|
||||
* @returns {void} No return value
|
||||
*/
|
||||
function checkAndReport(context, node, value, array, message) {
|
||||
if (array.indexOf(value) !== -1) {
|
||||
context.report({
|
||||
node: node,
|
||||
message: "'{{module}}' " + message,
|
||||
data: {module: value}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback nodeCallback
|
||||
* @param {ASTNode} node - A node to handle.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a function handling the imports of a given file
|
||||
*
|
||||
* @param {RuleContext} context - The ESLint rule context object.
|
||||
* @param {boolean} includeExports - Whether or not to check for exports in addition to imports.
|
||||
* @param {string[]} importsInFile - The array containing other imports in the file.
|
||||
* @param {string[]} exportsInFile - The array containing other exports in the file.
|
||||
*
|
||||
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
|
||||
*/
|
||||
function handleImports(context, includeExports, importsInFile, exportsInFile) {
|
||||
return function(node) {
|
||||
var value = getValue(node);
|
||||
|
||||
if (value) {
|
||||
checkAndReport(context, node, value, importsInFile, "import is duplicated.");
|
||||
|
||||
if (includeExports) {
|
||||
checkAndReport(context, node, value, exportsInFile, "import is duplicated as export.");
|
||||
}
|
||||
|
||||
importsInFile.push(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function handling the exports of a given file
|
||||
*
|
||||
* @param {RuleContext} context - The ESLint rule context object.
|
||||
* @param {string[]} importsInFile - The array containing other imports in the file.
|
||||
* @param {string[]} exportsInFile - The array containing other exports in the file.
|
||||
*
|
||||
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
|
||||
*/
|
||||
function handleExports(context, importsInFile, exportsInFile) {
|
||||
return function(node) {
|
||||
var value = getValue(node);
|
||||
|
||||
if (value) {
|
||||
checkAndReport(context, node, value, exportsInFile, "export is duplicated.");
|
||||
checkAndReport(context, node, value, importsInFile, "export is duplicated as import.");
|
||||
|
||||
exportsInFile.push(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function(context) {
|
||||
var includeExports = (context.options[0] || {}).includeExports,
|
||||
importsInFile = [],
|
||||
exportsInFile = [];
|
||||
|
||||
var handlers = {
|
||||
"ImportDeclaration": handleImports(context, includeExports, importsInFile, exportsInFile)
|
||||
};
|
||||
|
||||
if (includeExports) {
|
||||
handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile);
|
||||
handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile);
|
||||
}
|
||||
|
||||
return handlers;
|
||||
};
|
||||
|
||||
module.exports.schema = [{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"includeExports": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}];
|
||||
@@ -97,12 +97,16 @@ module.exports = function(context) {
|
||||
* @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") {
|
||||
|
||||
// If we have a BlockStatement, check each consequent body node.
|
||||
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 {
|
||||
|
||||
/*
|
||||
* If not a block statement, make sure the consequent isn't a
|
||||
* ReturnStatement or an IfStatement with returns on both paths.
|
||||
*/
|
||||
return checkForReturnOrIf(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ module.exports = function(context) {
|
||||
|
||||
"Literal": function(node) {
|
||||
var token = context.getFirstToken(node);
|
||||
|
||||
if (token.type === "RegularExpression" && !regex.test(token.value)) {
|
||||
context.report(node, "Empty class.");
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ function getKind(node) {
|
||||
|
||||
// Detects prefix.
|
||||
var prefix = "";
|
||||
|
||||
if (node.generator) {
|
||||
prefix = "generator";
|
||||
} else if (node.async) {
|
||||
|
||||
@@ -15,6 +15,7 @@ var FUNCTION_TYPE = /^(?:ArrowFunctionExpression|Function(?:Declaration|Expressi
|
||||
module.exports = function(context) {
|
||||
return {
|
||||
"BlockStatement": function(node) {
|
||||
|
||||
// if the body is not empty, we can just return immediately
|
||||
if (node.body.length !== 0) {
|
||||
return;
|
||||
|
||||
@@ -157,11 +157,13 @@ module.exports = function(context) {
|
||||
for (var i = 0; i < candidatesOfGlobalObject.length; ++i) {
|
||||
var name = candidatesOfGlobalObject[i];
|
||||
var variable = astUtils.getVariableByName(globalScope, name);
|
||||
|
||||
if (!variable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var references = variable.references;
|
||||
|
||||
for (var j = 0; j < references.length; ++j) {
|
||||
var identifier = references[j].identifier;
|
||||
var node = identifier.parent;
|
||||
@@ -187,16 +189,19 @@ module.exports = function(context) {
|
||||
*/
|
||||
function reportAccessingEval(globalScope) {
|
||||
var variable = astUtils.getVariableByName(globalScope, "eval");
|
||||
|
||||
if (!variable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var references = variable.references;
|
||||
|
||||
for (var i = 0; i < references.length; ++i) {
|
||||
var reference = references[i];
|
||||
var id = reference.identifier;
|
||||
|
||||
if (id.name === "eval" && !astUtils.isCallee(id)) {
|
||||
|
||||
// Is accessing to eval (excludes direct calls to eval)
|
||||
report(id);
|
||||
}
|
||||
@@ -204,11 +209,12 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
if (allowIndirect) {
|
||||
// Checks only direct calls to eval.
|
||||
// It's simple!
|
||||
|
||||
// Checks only direct calls to eval. It's simple!
|
||||
return {
|
||||
"CallExpression:exit": function(node) {
|
||||
var callee = node.callee;
|
||||
|
||||
if (isIdentifier(callee, "eval")) {
|
||||
report(callee);
|
||||
}
|
||||
@@ -219,6 +225,7 @@ module.exports = function(context) {
|
||||
return {
|
||||
"CallExpression:exit": function(node) {
|
||||
var callee = node.callee;
|
||||
|
||||
if (isIdentifier(callee, "eval")) {
|
||||
report(callee);
|
||||
}
|
||||
@@ -261,8 +268,10 @@ module.exports = function(context) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `this.eval` is found.
|
||||
// Checks whether or not the value of `this` is the global object.
|
||||
/*
|
||||
* `this.eval` is found.
|
||||
* Checks whether or not the value of `this` is the global object.
|
||||
*/
|
||||
if (!funcInfo.initialized) {
|
||||
funcInfo.initialized = true;
|
||||
funcInfo.defaultThis = astUtils.isDefaultThisBinding(
|
||||
@@ -270,7 +279,9 @@ module.exports = function(context) {
|
||||
sourceCode
|
||||
);
|
||||
}
|
||||
|
||||
if (!funcInfo.strict && funcInfo.defaultThis) {
|
||||
|
||||
// `this.eval` is possible built-in `eval`.
|
||||
report(node.parent);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ module.exports = function(context) {
|
||||
|
||||
// handle the Array.prototype.extra style case
|
||||
"AssignmentExpression": function(node) {
|
||||
var lhs = node.left, affectsProto;
|
||||
var lhs = node.left,
|
||||
affectsProto;
|
||||
|
||||
if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") {
|
||||
return;
|
||||
|
||||
@@ -45,6 +45,7 @@ module.exports = function(context) {
|
||||
if (node.property.expressions.length === 0) {
|
||||
return node.property.quasis[0].value.cooked;
|
||||
}
|
||||
|
||||
// fallthrough
|
||||
default:
|
||||
return false;
|
||||
@@ -66,6 +67,7 @@ module.exports = function(context) {
|
||||
function isCalleeOfBindMethod(node) {
|
||||
var parent = node.parent;
|
||||
var grandparent = parent.parent;
|
||||
|
||||
return (
|
||||
grandparent &&
|
||||
grandparent.type === "CallExpression" &&
|
||||
|
||||
@@ -31,6 +31,7 @@ module.exports = function(context) {
|
||||
return (
|
||||
(BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 &&
|
||||
node === parent.test) ||
|
||||
|
||||
// !<bool>
|
||||
(parent.type === "UnaryExpression" &&
|
||||
parent.operator === "!")
|
||||
@@ -52,6 +53,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
if (isInBooleanContext(parent, grandparent) ||
|
||||
|
||||
// Boolean(<bool>) and new Boolean(<bool>)
|
||||
((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") &&
|
||||
grandparent.callee.type === "Identifier" &&
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var astUtils = require("../ast-utils.js");
|
||||
|
||||
module.exports = function(context) {
|
||||
var isParenthesised = astUtils.isParenthesised.bind(astUtils, context);
|
||||
var ALL_NODES = context.options[0] !== "functions";
|
||||
var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
|
||||
var sourceCode = context.getSourceCode();
|
||||
@@ -26,21 +28,6 @@ module.exports = function(context) {
|
||||
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by parentheses.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the node is parenthesised.
|
||||
* @private
|
||||
*/
|
||||
function isParenthesised(node) {
|
||||
var previousToken = context.getTokenBefore(node),
|
||||
nextToken = context.getTokenAfter(node);
|
||||
|
||||
return previousToken && nextToken &&
|
||||
previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by parentheses twice.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
@@ -110,6 +97,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function isHeadOfExpressionStatement(node) {
|
||||
var parent = node.parent;
|
||||
|
||||
while (parent) {
|
||||
switch (parent.type) {
|
||||
case "SequenceExpression":
|
||||
@@ -191,11 +179,14 @@ module.exports = function(context) {
|
||||
return 4;
|
||||
case "&&":
|
||||
return 5;
|
||||
|
||||
// no default
|
||||
}
|
||||
|
||||
/* falls through */
|
||||
|
||||
case "BinaryExpression":
|
||||
|
||||
switch (node.operator) {
|
||||
case "|":
|
||||
return 6;
|
||||
@@ -226,21 +217,29 @@ module.exports = function(context) {
|
||||
case "/":
|
||||
case "%":
|
||||
return 13;
|
||||
|
||||
// no default
|
||||
}
|
||||
|
||||
/* falls through */
|
||||
|
||||
case "UnaryExpression":
|
||||
return 14;
|
||||
|
||||
case "UpdateExpression":
|
||||
return 15;
|
||||
|
||||
case "CallExpression":
|
||||
|
||||
// IIFE is allowed to have parens in any position (#655)
|
||||
if (node.callee.type === "FunctionExpression") {
|
||||
return -1;
|
||||
}
|
||||
return 16;
|
||||
|
||||
case "NewExpression":
|
||||
return 17;
|
||||
|
||||
// no default
|
||||
}
|
||||
return 18;
|
||||
@@ -254,6 +253,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function report(node) {
|
||||
var previousToken = context.getTokenBefore(node);
|
||||
|
||||
context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
|
||||
}
|
||||
|
||||
@@ -279,6 +279,7 @@ module.exports = function(context) {
|
||||
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "FunctionExpression" &&
|
||||
|
||||
// One set of parentheses are allowed for a function expression
|
||||
!hasDoubleExcessParens(node.callee)
|
||||
)) {
|
||||
@@ -305,6 +306,7 @@ module.exports = function(context) {
|
||||
*/
|
||||
function dryBinaryLogical(node) {
|
||||
var prec = precedence(node);
|
||||
|
||||
if (hasExcessParens(node.left) && precedence(node.left) >= prec) {
|
||||
report(node.left);
|
||||
}
|
||||
@@ -321,6 +323,7 @@ module.exports = function(context) {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"ArrowFunctionExpression": function(node) {
|
||||
if (node.body.type !== "BlockStatement") {
|
||||
if (node.body.type !== "ObjectExpression" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
|
||||
@@ -335,13 +338,16 @@ module.exports = function(context) {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"AssignmentExpression": function(node) {
|
||||
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
|
||||
"BinaryExpression": dryBinaryLogical,
|
||||
"CallExpression": dryCallNew,
|
||||
|
||||
"ConditionalExpression": function(node) {
|
||||
if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
|
||||
report(node.test);
|
||||
@@ -353,13 +359,16 @@ module.exports = function(context) {
|
||||
report(node.alternate);
|
||||
}
|
||||
},
|
||||
|
||||
"DoWhileStatement": function(node) {
|
||||
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
|
||||
"ExpressionStatement": function(node) {
|
||||
var firstToken, secondToken, firstTokens;
|
||||
|
||||
if (hasExcessParens(node.expression)) {
|
||||
firstTokens = context.getFirstTokens(node.expression, 2);
|
||||
firstToken = firstTokens[0];
|
||||
@@ -380,16 +389,19 @@ module.exports = function(context) {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ForInStatement": function(node) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
|
||||
"ForOfStatement": function(node) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
|
||||
"ForStatement": function(node) {
|
||||
if (node.init && hasExcessParens(node.init)) {
|
||||
report(node.init);
|
||||
@@ -403,12 +415,15 @@ module.exports = function(context) {
|
||||
report(node.update);
|
||||
}
|
||||
},
|
||||
|
||||
"IfStatement": function(node) {
|
||||
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
|
||||
"LogicalExpression": dryBinaryLogical,
|
||||
|
||||
"MemberExpression": function(node) {
|
||||
if (
|
||||
hasExcessParens(node.object) &&
|
||||
@@ -420,6 +435,7 @@ module.exports = function(context) {
|
||||
typeof node.object.value === "number" &&
|
||||
/^[0-9]+$/.test(context.getFirstToken(node.object).value))
|
||||
||
|
||||
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
(node.object.type === "Literal" && node.object.regex)
|
||||
)
|
||||
@@ -436,25 +452,31 @@ module.exports = function(context) {
|
||||
report(node.property);
|
||||
}
|
||||
},
|
||||
|
||||
"NewExpression": dryCallNew,
|
||||
|
||||
"ObjectExpression": function(node) {
|
||||
[].forEach.call(node.properties, function(e) {
|
||||
var v = e.value;
|
||||
|
||||
if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(v);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"ReturnStatement": function(node) {
|
||||
var returnToken = sourceCode.getFirstToken(node);
|
||||
|
||||
if (node.argument &&
|
||||
hasExcessParensNoLineTerminator(returnToken, node.argument) &&
|
||||
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
!(node.argument.type === "Literal" && node.argument.regex)) {
|
||||
report(node.argument);
|
||||
}
|
||||
},
|
||||
|
||||
"SequenceExpression": function(node) {
|
||||
[].forEach.call(node.expressions, function(e) {
|
||||
if (hasExcessParens(e) && precedence(e) >= precedence(node)) {
|
||||
@@ -462,16 +484,19 @@ module.exports = function(context) {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"SwitchCase": function(node) {
|
||||
if (node.test && hasExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
|
||||
"SwitchStatement": function(node) {
|
||||
if (hasDoubleExcessParens(node.discriminant)) {
|
||||
report(node.discriminant);
|
||||
}
|
||||
},
|
||||
|
||||
"ThrowStatement": function(node) {
|
||||
var throwToken = sourceCode.getFirstToken(node);
|
||||
|
||||
@@ -479,26 +504,32 @@ module.exports = function(context) {
|
||||
report(node.argument);
|
||||
}
|
||||
},
|
||||
|
||||
"UnaryExpression": dryUnaryUpdate,
|
||||
"UpdateExpression": dryUnaryUpdate,
|
||||
|
||||
"VariableDeclarator": function(node) {
|
||||
if (node.init && hasExcessParens(node.init) &&
|
||||
precedence(node.init) >= precedence({type: "AssignmentExpression"}) &&
|
||||
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
!(node.init.type === "Literal" && node.init.regex)) {
|
||||
report(node.init);
|
||||
}
|
||||
},
|
||||
|
||||
"WhileStatement": function(node) {
|
||||
if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
|
||||
"WithStatement": function(node) {
|
||||
if (hasDoubleExcessParens(node.object)) {
|
||||
report(node.object);
|
||||
}
|
||||
},
|
||||
|
||||
"YieldExpression": function(node) {
|
||||
var yieldToken;
|
||||
|
||||
|
||||
Loaded 100 of 2040 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user