├── .npmrc ├── .eslintignore ├── .github ├── FUNDING.yml └── workflows │ └── CI.yml ├── .gitignore ├── .eslintrc.yml ├── .nycrc.yml ├── lib ├── processors.js ├── configs │ ├── +browser.js │ ├── _override-2016.js │ ├── _override-2019.js │ ├── _override-2018.js │ ├── _override-2020.js │ ├── es5.js │ ├── es2015.js │ ├── es2016.js │ ├── es2017.js │ ├── es2018.js │ ├── _override-2017.js │ ├── es2019.js │ ├── es2020.js │ ├── _override-mocha.js │ ├── _override-special.js │ ├── +modules.js │ ├── +eslint-plugin.js │ ├── _override-2015.js │ ├── +node.js │ ├── _override-ts.js │ ├── _override-vue.js │ ├── _base.js │ └── _browser-globals.js ├── foreign-rules │ ├── prettier.js │ ├── node.js │ ├── vue.js │ ├── eslint-plugin.js │ ├── eslint-comments.js │ └── ts.js ├── configs.js ├── rules.js ├── utils.js ├── rules │ ├── no-literal-call.js │ ├── no-instanceof-array.js │ ├── no-this-in-static.js │ ├── arrow-parens.js │ ├── no-use-ignored-vars.js │ ├── no-instanceof-wrapper.js │ ├── no-useless-rest-spread.js │ ├── block-scoped-var.js │ └── prefer-for-of.js └── processors │ └── vue.js ├── index.js ├── docs └── rules │ ├── no-instanceof-array.md │ ├── no-literal-call.md │ ├── no-instanceof-wrapper.md │ ├── no-useless-rest-spread.md │ ├── no-this-in-static.md │ ├── prefer-for-of.md │ ├── no-use-ignored-vars.md │ ├── arrow-parens.md │ └── block-scoped-var.md ├── tests └── lib │ ├── configs │ ├── es2019.js │ ├── es2020.js │ ├── es2016.js │ ├── es2017.js │ ├── es2018.js │ ├── browser.js │ ├── mocha.js │ ├── modules.js │ ├── special.js │ ├── node.js │ ├── ts.js │ ├── vue.js │ ├── eslint-plugin.js │ ├── es2015.js │ ├── es5.js │ └── _rules.js │ └── rules │ ├── no-instanceof-array.js │ ├── no-this-in-static.js │ ├── no-use-ignored-vars.js │ ├── no-instanceof-wrapper.js │ ├── block-scoped-var.js │ ├── no-literal-call.js │ ├── no-useless-rest-spread.js │ ├── arrow-parens.js │ └── prefer-for-of.js ├── scripts ├── generate-configs.js ├── generate-rules.js └── generate-browser-globals.js ├── LICENSE ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | !.eslintrc.* 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 2 | - mysticatea 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.nyc_output 2 | /coverage 3 | /node_modules 4 | /npm-debug.log 5 | /test.js 6 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - plugin:@mysticatea/es2015 4 | - plugin:@mysticatea/+eslint-plugin 5 | -------------------------------------------------------------------------------- /.nycrc.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - lib 3 | exclude: 4 | - lib/processors/vue.js 5 | reporter: 6 | - text-summary 7 | - lcov 8 | -------------------------------------------------------------------------------- /lib/processors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-configs.js'. 3 | */ 4 | "use strict" 5 | 6 | module.exports = { 7 | ".vue": require("./processors/vue"), 8 | } 9 | -------------------------------------------------------------------------------- /lib/configs/+browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const globals = require("./_browser-globals") 8 | 9 | module.exports = { 10 | globals, 11 | } 12 | -------------------------------------------------------------------------------- /lib/configs/_override-2016.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2016, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /lib/configs/_override-2019.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2019, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /lib/foreign-rules/prettier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const rules = require("eslint-plugin-prettier").rules 8 | 9 | module.exports = { prettier: rules.prettier } 10 | -------------------------------------------------------------------------------- /lib/configs/_override-2018.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | }, 11 | rules: { 12 | "prefer-object-spread": "error", 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | configs: require("./lib/configs"), 9 | processors: require("./lib/processors"), 10 | rules: require("./lib/rules"), 11 | utils: require("./lib/utils"), 12 | } 13 | -------------------------------------------------------------------------------- /lib/foreign-rules/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const rules = require("eslint-plugin-node").rules 8 | 9 | module.exports = Object.keys(rules).reduce((obj, ruleId) => { 10 | obj[`node/${ruleId}`] = rules[ruleId] 11 | return obj 12 | }, {}) 13 | -------------------------------------------------------------------------------- /lib/foreign-rules/vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const rules = require("eslint-plugin-vue").rules 8 | 9 | module.exports = Object.keys(rules).reduce((obj, ruleId) => { 10 | obj[`vue/${ruleId}`] = rules[ruleId] 11 | return obj 12 | }, {}) 13 | -------------------------------------------------------------------------------- /lib/foreign-rules/eslint-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const rules = require("eslint-plugin-eslint-plugin").rules 8 | 9 | module.exports = Object.keys(rules).reduce((obj, ruleId) => { 10 | obj[`eslint-plugin/${ruleId}`] = rules[ruleId] 11 | return obj 12 | }, {}) 13 | -------------------------------------------------------------------------------- /lib/configs/_override-2020.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2020, 10 | }, 11 | globals: { 12 | BigInt: "readonly", 13 | BigInt64Array: "readonly", 14 | BigUint64Array: "readonly", 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /docs/rules/no-instanceof-array.md: -------------------------------------------------------------------------------- 1 | # Disallow 'instanceof' for Array (no-instanceof-array) 2 | 3 | ## Rule Details 4 | 5 | Examples of **incorrect** code for this rule: 6 | 7 | ```js 8 | /*eslint no-instanceof-array: "error"*/ 9 | 10 | x instanceof Array 11 | ``` 12 | 13 | Examples of **correct** code for this rule: 14 | 15 | ```js 16 | /*eslint no-instanceof-array: "error"*/ 17 | 18 | Array.isArray(x) 19 | ``` 20 | -------------------------------------------------------------------------------- /tests/lib/configs/es2019.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const Rules = require("./_rules") 8 | 9 | describe("'es2019.js'", () => { 10 | const config = require("../../../lib/configs/es2019") 11 | 12 | it("should be a valid config.", () => { 13 | Rules.validateConfig(config, "es2019.js") 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/lib/configs/es2020.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const Rules = require("./_rules") 8 | 9 | describe("'es2020.js'", () => { 10 | const config = require("../../../lib/configs/es2020") 11 | 12 | it("should be a valid config.", () => { 13 | Rules.validateConfig(config, "es2020.js") 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /lib/configs/es5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-ts.js"), 11 | require.resolve("./_override-vue.js"), 12 | require.resolve("./_override-mocha.js"), 13 | require.resolve("./_override-special.js"), 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /lib/foreign-rules/eslint-comments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const { rules, utils } = require("eslint-plugin-eslint-comments") 8 | 9 | utils.patch("@mysticatea/eslint-comments/no-unused-disable") 10 | 11 | module.exports = Object.keys(rules).reduce((obj, ruleId) => { 12 | obj[`eslint-comments/${ruleId}`] = rules[ruleId] 13 | return obj 14 | }, {}) 15 | -------------------------------------------------------------------------------- /tests/lib/configs/es2016.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'es2016.js'", () => { 11 | const config = require("../../../lib/configs/es2016") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "es2016.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/es2017.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'es2017.js'", () => { 11 | const config = require("../../../lib/configs/es2017") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "es2017.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/es2018.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'es2018.js'", () => { 11 | const config = require("../../../lib/configs/es2018") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "es2018.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'+browser.js'", () => { 11 | const config = require("../../../lib/configs/+browser") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "+browser.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/mocha.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'mocha.js'", () => { 11 | const config = require("../../../lib/configs/_override-mocha") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "mocha.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'+modules.js'", () => { 11 | const config = require("../../../lib/configs/+modules") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "+modules.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/lib/configs/special.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const Rules = require("./_rules") 9 | 10 | describe("'special.js'", () => { 11 | const config = require("../../../lib/configs/_override-special") 12 | 13 | it("should be a valid config.", () => { 14 | Rules.validateConfig(config, "special.js") 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /docs/rules/no-literal-call.md: -------------------------------------------------------------------------------- 1 | # Disallow a call of a literal (no-literal-call) 2 | 3 | A call of a literal is a valid syntax, but it would cause a runtime error. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```js 10 | /*eslint no-literal-call: "error"*/ 11 | 12 | 123(); 13 | "hello"(); 14 | ``` 15 | 16 | Examples of **correct** code for this rule: 17 | 18 | ```js 19 | /*eslint no-literal-call: "error"*/ 20 | 21 | foo(); 22 | (function() {})(); 23 | ``` 24 | -------------------------------------------------------------------------------- /lib/configs/es2015.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-ts.js"), 12 | require.resolve("./_override-vue.js"), 13 | require.resolve("./_override-mocha.js"), 14 | require.resolve("./_override-special.js"), 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /lib/configs/es2016.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-2016.js"), 12 | require.resolve("./_override-ts.js"), 13 | require.resolve("./_override-vue.js"), 14 | require.resolve("./_override-mocha.js"), 15 | require.resolve("./_override-special.js"), 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /lib/foreign-rules/ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | // This import requires `typescript` package. The MODULE_NOT_FOUND error should 8 | // be ignored if people want to lint only JS. 9 | let rules = null 10 | try { 11 | rules = require("@typescript-eslint/eslint-plugin").rules 12 | } catch (_error) { 13 | rules = {} 14 | } 15 | 16 | module.exports = Object.keys(rules).reduce((obj, ruleId) => { 17 | obj[`ts/${ruleId}`] = rules[ruleId] 18 | return obj 19 | }, {}) 20 | -------------------------------------------------------------------------------- /lib/configs/es2017.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-2016.js"), 12 | require.resolve("./_override-2017.js"), 13 | require.resolve("./_override-ts.js"), 14 | require.resolve("./_override-vue.js"), 15 | require.resolve("./_override-mocha.js"), 16 | require.resolve("./_override-special.js"), 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /lib/configs/es2018.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-2016.js"), 12 | require.resolve("./_override-2017.js"), 13 | require.resolve("./_override-2018.js"), 14 | require.resolve("./_override-ts.js"), 15 | require.resolve("./_override-vue.js"), 16 | require.resolve("./_override-mocha.js"), 17 | require.resolve("./_override-special.js"), 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /lib/configs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-configs.js'. 3 | */ 4 | "use strict" 5 | 6 | module.exports = { 7 | "+browser": require("./configs/+browser"), 8 | "+eslint-plugin": require("./configs/+eslint-plugin"), 9 | "+modules": require("./configs/+modules"), 10 | "+node": require("./configs/+node"), 11 | es2015: require("./configs/es2015"), 12 | es2016: require("./configs/es2016"), 13 | es2017: require("./configs/es2017"), 14 | es2018: require("./configs/es2018"), 15 | es2019: require("./configs/es2019"), 16 | es2020: require("./configs/es2020"), 17 | es5: require("./configs/es5"), 18 | } 19 | -------------------------------------------------------------------------------- /lib/configs/_override-2017.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2017, 10 | }, 11 | globals: { 12 | Atomics: "readonly", 13 | SharedArrayBuffer: "readonly", 14 | }, 15 | rules: { 16 | "@mysticatea/prettier": [ 17 | "error", 18 | { 19 | tabWidth: 4, 20 | semi: false, 21 | trailingComma: "all", 22 | }, 23 | { 24 | usePrettierrc: false, 25 | }, 26 | ], 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /docs/rules/no-instanceof-wrapper.md: -------------------------------------------------------------------------------- 1 | # Disallow 'instanceof' for wrapper objects (no-instanceof-wrapper) 2 | 3 | ## Rule Details 4 | 5 | Examples of **incorrect** code for this rule: 6 | 7 | ```js 8 | /*eslint no-instanceof-wrapper: "error"*/ 9 | 10 | x instanceof Boolean 11 | x instanceof Number 12 | x instanceof String 13 | x instanceof Object 14 | x instanceof Function 15 | x instanceof Symbol 16 | ``` 17 | 18 | Examples of **correct** code for this rule: 19 | 20 | ```js 21 | /*eslint no-instanceof-wrapper: "error"*/ 22 | 23 | typeof x === "boolean" 24 | typeof x === "number" 25 | typeof x === "string" 26 | typeof x === "object" 27 | typeof x === "function" 28 | typeof x === "symbol" 29 | ``` 30 | -------------------------------------------------------------------------------- /lib/configs/es2019.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-2016.js"), 12 | require.resolve("./_override-2017.js"), 13 | require.resolve("./_override-2018.js"), 14 | require.resolve("./_override-2019.js"), 15 | require.resolve("./_override-ts.js"), 16 | require.resolve("./_override-vue.js"), 17 | require.resolve("./_override-mocha.js"), 18 | require.resolve("./_override-special.js"), 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /lib/configs/es2020.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | extends: [ 9 | require.resolve("./_base.js"), 10 | require.resolve("./_override-2015.js"), 11 | require.resolve("./_override-2016.js"), 12 | require.resolve("./_override-2017.js"), 13 | require.resolve("./_override-2018.js"), 14 | require.resolve("./_override-2019.js"), 15 | require.resolve("./_override-2020.js"), 16 | require.resolve("./_override-ts.js"), 17 | require.resolve("./_override-vue.js"), 18 | require.resolve("./_override-mocha.js"), 19 | require.resolve("./_override-special.js"), 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /lib/configs/_override-mocha.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | overrides: [ 9 | { 10 | files: ["**/test/**", "**/tests/**"], 11 | globals: { 12 | after: "readonly", 13 | afterEach: "readonly", 14 | before: "readonly", 15 | beforeEach: "readonly", 16 | describe: "readonly", 17 | it: "readonly", 18 | mocha: "readonly", 19 | xdescribe: "readonly", 20 | xit: "readonly", 21 | }, 22 | rules: { 23 | "max-nested-callbacks": "off", 24 | }, 25 | }, 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /tests/lib/configs/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | describe("'+node.js'", () => { 12 | const config = require("../../../lib/configs/+node") 13 | const configuredRules = Rules.getRulesOfConfig(config, "+node") 14 | const existingRules = Rules.getPluginRuleNames("node") 15 | 16 | it("should be a valid config.", () => { 17 | Rules.validateConfig(config, "+node.js") 18 | }) 19 | 20 | for (const ruleId of existingRules) { 21 | it(`should include existing rule '${ruleId}'.`, () => { 22 | assert(ruleId in configuredRules) 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /tests/lib/configs/ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | describe("'ts.js'", () => { 12 | const config = require("../../../lib/configs/_override-ts") 13 | const configuredRules = Rules.getRulesOfConfig(config, "_override-ts") 14 | const existingRules = Rules.getPluginRuleNames("ts") 15 | 16 | it("should be a valid config.", () => { 17 | Rules.validateConfig(config, "ts.js") 18 | }) 19 | 20 | for (const ruleId of existingRules) { 21 | it(`should include existing rule '${ruleId}'.`, () => { 22 | assert(ruleId in configuredRules) 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /tests/lib/configs/vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | describe("'vue.js'", () => { 12 | const config = require("../../../lib/configs/_override-vue") 13 | const configuredRules = Rules.getRulesOfConfig(config, "_override-ts") 14 | const existingRules = Rules.getPluginRuleNames("vue") 15 | 16 | it("should be a valid config.", () => { 17 | Rules.validateConfig(config, "vue.js") 18 | }) 19 | 20 | for (const ruleId of existingRules) { 21 | it(`should include existing rule '${ruleId}'.`, () => { 22 | assert(ruleId in configuredRules) 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | schedule: 8 | - cron: 0 0 * * 0 9 | 10 | jobs: 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: [12.x, 10.x, 8.x] 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v1 20 | with: 21 | fetch-depth: 1 22 | - name: Install Node.js ${{ matrix.node }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node }} 26 | - name: Install Packages 27 | run: npm install 28 | - name: Test 29 | run: npm test 30 | - name: Send Coverage 31 | run: npm run -s codecov 32 | env: 33 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /tests/lib/configs/eslint-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | describe("'+eslint-plugin.js'", () => { 12 | const config = require("../../../lib/configs/+eslint-plugin") 13 | const configuredRules = Rules.getRulesOfConfig(config, "+eslint-plugin") 14 | const existingRules = Rules.getPluginRuleNames("eslint-plugin") 15 | 16 | it("should be a valid config.", () => { 17 | Rules.validateConfig(config, "+eslint-plugin.js") 18 | }) 19 | 20 | for (const ruleId of existingRules) { 21 | it(`should include existing rule '${ruleId}'.`, () => { 22 | assert(ruleId in configuredRules) 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /docs/rules/no-useless-rest-spread.md: -------------------------------------------------------------------------------- 1 | # Disallow unnecessary rest/spread operators (no-useless-rest-spread) 2 | 3 | (fixable) The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```js 10 | /*eslint no-useless-rest-spread: "error"*/ 11 | 12 | let list = [a, ...[b, c], d] 13 | let obj = {a, ...{b, c}, d} 14 | foo(...[a, b, c]) 15 | 16 | let [a, b, ...[c, d]] = list; 17 | let {a, b, ...{c, d}} = obj; 18 | function foo(a, b, ...[c, d]) { 19 | } 20 | ``` 21 | 22 | Examples of **correct** code for this rule: 23 | 24 | ```js 25 | /*eslint no-useless-rest-spread: "error"*/ 26 | 27 | let list = [a, b, c, d] 28 | let obj = {a, b, c, d} 29 | foo(a, b, c) 30 | 31 | let [a, b, c, d] = list; 32 | let {a, b, c, d} = obj; 33 | function foo(a, b, c, d) { 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /scripts/generate-configs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const fs = require("fs") 8 | const path = require("path") 9 | const { CLIEngine } = require("eslint") 10 | 11 | const targetFile = path.resolve(__dirname, "../lib/configs.js") 12 | 13 | fs.writeFileSync( 14 | targetFile, 15 | `/** 16 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-configs.js'. 17 | */ 18 | "use strict" 19 | 20 | module.exports = { 21 | ${fs 22 | .readdirSync(path.resolve(__dirname, "../lib/configs")) 23 | .map(fileName => path.basename(fileName, ".js")) 24 | .filter(id => !id.startsWith("_")) 25 | .map(id => ` "${id}": require("./configs/${id}"),`) 26 | .join("\n")} 27 | } 28 | ` 29 | ) 30 | 31 | const linter = new CLIEngine({ fix: true }) 32 | const result = linter.executeOnFiles([targetFile]) 33 | CLIEngine.outputFixes(result) 34 | -------------------------------------------------------------------------------- /lib/rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-rules.js'. 3 | */ 4 | "use strict" 5 | 6 | module.exports = Object.assign( 7 | require("./foreign-rules/eslint-comments"), 8 | require("./foreign-rules/eslint-plugin"), 9 | require("./foreign-rules/node"), 10 | require("./foreign-rules/prettier"), 11 | require("./foreign-rules/ts"), 12 | require("./foreign-rules/vue"), 13 | { 14 | "arrow-parens": require("./rules/arrow-parens"), 15 | "block-scoped-var": require("./rules/block-scoped-var"), 16 | "no-instanceof-array": require("./rules/no-instanceof-array"), 17 | "no-instanceof-wrapper": require("./rules/no-instanceof-wrapper"), 18 | "no-literal-call": require("./rules/no-literal-call"), 19 | "no-this-in-static": require("./rules/no-this-in-static"), 20 | "no-use-ignored-vars": require("./rules/no-use-ignored-vars"), 21 | "no-useless-rest-spread": require("./rules/no-useless-rest-spread"), 22 | "prefer-for-of": require("./rules/prefer-for-of"), 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /scripts/generate-rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const fs = require("fs") 8 | const path = require("path") 9 | const { CLIEngine } = require("eslint") 10 | 11 | const targetFile = path.resolve(__dirname, "../lib/rules.js") 12 | 13 | fs.writeFileSync( 14 | targetFile, 15 | `/** 16 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-rules.js'. 17 | */ 18 | "use strict" 19 | 20 | module.exports = Object.assign( 21 | ${fs 22 | .readdirSync(path.resolve(__dirname, "../lib/foreign-rules")) 23 | .map(fileName => path.basename(fileName, ".js")) 24 | .map(id => ` require("./foreign-rules/${id}"),`) 25 | .join("\n")} 26 | { 27 | ${fs 28 | .readdirSync(path.resolve(__dirname, "../lib/rules")) 29 | .map(fileName => path.basename(fileName, ".js")) 30 | .map(id => ` "${id}": require("./rules/${id}"),`) 31 | .join("\n")} 32 | } 33 | ) 34 | ` 35 | ) 36 | 37 | const linter = new CLIEngine({ fix: true }) 38 | const result = linter.executeOnFiles([targetFile]) 39 | CLIEngine.outputFixes(result) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Toru Nagashima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /docs/rules/no-this-in-static.md: -------------------------------------------------------------------------------- 1 | # Disallow `this`/`super` in static methods (no-this-in-static) 2 | 3 | `this` keyword on static methods refers the class (the constructor) instance. 4 | However, probably it's confusing maintainers since this behavior is different to 5 | most other languages. 6 | 7 | This rule enforces a use of class itself to access static methods. 8 | 9 | ## Rule Details 10 | 11 | Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | /*eslint no-this-in-static: "error"*/ 15 | 16 | class A { 17 | static foo() { 18 | doSomething() 19 | } 20 | 21 | static bar() { 22 | this.foo() //ERROR: Unexpected 'this'. 23 | } 24 | } 25 | 26 | class B extends A { 27 | static foo() { 28 | super.foo() //ERROR: Unexpected 'super'. 29 | } 30 | } 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```js 36 | /*eslint no-this-in-static: "error"*/ 37 | 38 | class A { 39 | static foo() { 40 | doSomething() 41 | } 42 | 43 | static bar() { 44 | A.foo() 45 | } 46 | } 47 | 48 | class B extends A { 49 | static foo() { 50 | A.foo() 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/rules/prefer-for-of.md: -------------------------------------------------------------------------------- 1 | # Requires `for-of` statements instead of `Array#forEach` (prefer-for-of) 2 | 3 | ES2015 introduced new `for-of` syntax. 4 | It's more readable than `Array#forEach` since it does not require closures. 5 | 6 | ## Rule Details 7 | 8 | :-1: Examples of **incorrect** code for this rule: 9 | 10 | ```js 11 | /*eslint prefer-for-of: "error"*/ 12 | 13 | list.forEach(value => { 14 | doSomething(value) 15 | }) 16 | 17 | for (let i = 0; i < list.length; ++i) { 18 | const value = list[i] 19 | doSomething(value) 20 | } 21 | 22 | for (const i in list) { 23 | if (obj.hasOwnProperty(i)) { 24 | doSomething(obj[i]) 25 | } 26 | } 27 | 28 | for (const key in obj) { 29 | doSomething(key, obj[key]) 30 | } 31 | ``` 32 | 33 | :+1: Examples of **correct** code for this rule: 34 | 35 | ```js 36 | /*eslint prefer-for-of: "error"*/ 37 | 38 | for (const value of list) { 39 | doSomething(value) 40 | } 41 | 42 | for (const key of Object.keys(obj)) { 43 | doSomething(key, obj[key]) 44 | } 45 | 46 | // Allow for indices. 47 | list.forEach((value, index) => { 48 | doSomething(index, value) 49 | }) 50 | ``` 51 | 52 | ## Options 53 | 54 | Nothing. 55 | -------------------------------------------------------------------------------- /docs/rules/no-use-ignored-vars.md: -------------------------------------------------------------------------------- 1 | # Disallow a use of ignored variables (no-use-ignored-vars) 2 | 3 | We can specify to ignore unused variables by a name pattern for [no-unused-vars] rule. 4 | But [no-unused-vars] rule does not notify a use of the ignored variables. 5 | A use of ignored variables causes confusing to developers. 6 | 7 | This rule disallows a use of the ignored variables. 8 | 9 | ## Rule Details 10 | 11 | Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | /*eslint no-use-ignored-vars: "error"*/ 15 | 16 | let _foo = 0; 17 | doSomething(_foo); 18 | 19 | function foo(_a) { 20 | doSomething(_a); 21 | } 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```js 27 | /*eslint no-use-ignored-vars: "error"*/ 28 | 29 | let _foo = 0; 30 | 31 | function foo(_a) { 32 | doSomething(); 33 | } 34 | ``` 35 | 36 | ## Options 37 | 38 | This rule has a string option. 39 | 40 | ```json 41 | { 42 | "no-use-ignored-vars": ["error", "^_[a-zA-Z]+$"] 43 | } 44 | ``` 45 | 46 | The string option is a regular expression of the ignored name pattern. 47 | Default is `"^_[a-zA-Z]+$"`. 48 | 49 | [no-unused-vars]: http://eslint.org/docs/rules/no-unused-vars 50 | -------------------------------------------------------------------------------- /docs/rules/arrow-parens.md: -------------------------------------------------------------------------------- 1 | # Require parens of the arrow function argument list. (arrow-parens) 2 | 3 | (fixable) The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) automatically fixes problems reported by this rule. 4 | 5 | ## Rule Details 6 | 7 | This rule aims to flag argument lists which don't have parens. 8 | The arrow function is able to not have parens for its argument list. 9 | 10 | This rule ignores arrow functions that there is a open paren before itself. 11 | 12 | ### The following patterns are considered warnings: 13 | 14 | ```js 15 | const twice = x => 2 * x; 16 | ``` 17 | 18 | ```js 19 | const obj = { 20 | twich: x => 2 * x 21 | }; 22 | ``` 23 | 24 | ```js 25 | p.then(x => 2 * x, err => console.error(err)); 26 | ``` 27 | 28 | ### The following patterns are not considered warnings: 29 | 30 | ```js 31 | const twice = (x) => 2 * x; 32 | const twice = (x => 2 * x); 33 | ``` 34 | 35 | ```js 36 | const obj = { 37 | twich: (x) => 2 * x 38 | }; 39 | const obj2 = { 40 | twich: (x => 2 * x) 41 | }; 42 | ``` 43 | 44 | ```js 45 | xs.map(x => 2 * x); 46 | ``` 47 | 48 | ```js 49 | p.then(x => 2 * x, (err) => console.error(err)); 50 | ``` 51 | -------------------------------------------------------------------------------- /lib/configs/_override-special.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | overrides: [ 9 | { 10 | files: [ 11 | "**/scripts/**/*", 12 | ".babelrc.js", 13 | ".eslintrc.js", 14 | "webpack.config.js", 15 | "**/.vuepress/config.js", 16 | "*.webpack.config.js", 17 | ], 18 | extends: [require.resolve("./+node.js")], 19 | rules: { 20 | "no-console": "off", 21 | "no-process-env": "off", 22 | }, 23 | }, 24 | { 25 | files: [ 26 | "**/scripts/rollup-plugin/**/*", 27 | "rollup.config.js", 28 | "*.rollup.config.js", 29 | ], 30 | extends: [ 31 | require.resolve("./+node.js"), 32 | require.resolve("./+modules.js"), 33 | ], 34 | rules: { 35 | "no-console": "off", 36 | "no-process-env": "off", 37 | }, 38 | }, 39 | ], 40 | } 41 | -------------------------------------------------------------------------------- /tests/lib/rules/no-instanceof-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | const RuleTester = require("eslint").RuleTester 13 | const rule = require("../../../lib/rules/no-instanceof-array") 14 | 15 | //------------------------------------------------------------------------------ 16 | // Tests 17 | //------------------------------------------------------------------------------ 18 | 19 | const tester = new RuleTester() 20 | 21 | tester.run("no-instanceof-array", rule, { 22 | valid: [ 23 | "Array", 24 | "Array.isArray(x)", 25 | "function foo(Array) { x instanceof Array }", 26 | ], 27 | invalid: [ 28 | { 29 | code: "x instanceof Array", 30 | output: "Array.isArray(x)", 31 | errors: [ 32 | "Unexpected 'instanceof' operator. Use 'Array.isArray' instead.", 33 | ], 34 | }, 35 | ], 36 | }) 37 | -------------------------------------------------------------------------------- /lib/configs/+modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaFeatures: { globalReturn: false }, 10 | sourceType: "module", 11 | }, 12 | globals: { 13 | __dirname: "off", 14 | __filename: "off", 15 | exports: "off", 16 | module: "off", 17 | require: "off", 18 | }, 19 | rules: { 20 | "@mysticatea/node/no-extraneous-import": "error", 21 | "@mysticatea/node/file-extension-in-import": [ 22 | "error", 23 | "always", 24 | { ".js": "never", ".ts": "never", ".tsx": "never" }, 25 | ], 26 | "@mysticatea/node/no-missing-import": "error", 27 | "@mysticatea/node/no-unpublished-import": "error", 28 | "@mysticatea/node/no-unsupported-features/es-syntax": [ 29 | "error", 30 | { ignores: ["modules", "dynamicImport"] }, 31 | ], 32 | }, 33 | overrides: [ 34 | { 35 | files: ["*.ts", "*.tsx", "*.vue"], 36 | rules: { 37 | "@mysticatea/node/no-unsupported-features/es-syntax": "off", 38 | }, 39 | }, 40 | ], 41 | } 42 | -------------------------------------------------------------------------------- /tests/lib/configs/es2015.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | /** 12 | * Checks whether a given core rule is an ES6 rule or not. 13 | * 14 | * @param {string} ruleId - The name of a core rule. 15 | * @returns {boolean} `true` if the rule is an ES6 rule. 16 | */ 17 | function isES6Rule(ruleId) { 18 | const def = Rules.getRuleDefinition(ruleId) 19 | const category = def && def.meta && def.meta.docs && def.meta.docs.category 20 | 21 | return category === "ECMAScript 6" 22 | } 23 | 24 | describe("'es2015.js'", () => { 25 | const config = require("../../../lib/configs/es2015") 26 | const configuredRules = Rules.getRulesOfConfig(config, "es2015") 27 | const existingRules = Rules.getCoreRuleNames() 28 | 29 | it("should be a valid config.", () => { 30 | Rules.validateConfig(config, "es2015.js") 31 | }) 32 | 33 | for (const ruleId of existingRules) { 34 | if (isES6Rule(ruleId)) { 35 | it(`should include ES2015 rule '${ruleId}'.`, () => { 36 | assert(ruleId in configuredRules) 37 | }) 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /scripts/generate-browser-globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const fs = require("fs") 8 | const path = require("path") 9 | const { CLIEngine } = require("eslint") 10 | const { browser: originalGlobals } = require("globals") 11 | 12 | const targetFile = path.resolve(__dirname, "../lib/configs/_browser-globals.js") 13 | const globals = {} 14 | const allows = new Set([ 15 | "atob", 16 | "btoa", 17 | "cancelAnimationFrame", 18 | "document", 19 | "fetch", 20 | "indexedDB", 21 | "localStorage", 22 | "location", 23 | "matchMedia", 24 | "navigator", 25 | "requestAnimationFrame", 26 | "sessionStorage", 27 | "window", 28 | ]) 29 | 30 | for (const key of Object.keys(originalGlobals).sort()) { 31 | if (key[0] === key[0].toUpperCase() || allows.has(key)) { 32 | globals[key] = originalGlobals[key] ? "writable" : "readonly" 33 | } 34 | } 35 | 36 | const linter = new CLIEngine({ fix: true }) 37 | const rawCode = `/** 38 | * DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-browser-globals.js'. 39 | */ 40 | "use strict" 41 | 42 | module.exports = ${JSON.stringify(globals, null, 4)} 43 | ` 44 | const code = 45 | linter.executeOnText(rawCode, "_browser-globals.js").results[0].output || 46 | rawCode 47 | 48 | fs.writeFileSync(targetFile, code) 49 | -------------------------------------------------------------------------------- /docs/rules/block-scoped-var.md: -------------------------------------------------------------------------------- 1 | # Treat `var` as Block Scoped (block-scoped-var) 2 | 3 | This rule treats variables which are defined with `var` declarations as the same behavior as variables which are defined with `let` declarations. 4 | 5 | Please turn `no-redeclare` rule off if you use this rule. 6 | 7 | ## Rule Details 8 | 9 | This rule aims to flag below about variables which are defined with `var` declaration: 10 | 11 | - References from outside of the block which declare the variable. 12 | - Re-declarations in a same block. 13 | - Shadowing in a same function scope. 14 | 15 | ### The following patterns are considered warnings: 16 | 17 | ```js 18 | { 19 | var a = 0; 20 | } 21 | console.log(a); // not defined. 22 | ``` 23 | 24 | ```js 25 | for (var a = 0;;) { 26 | } 27 | console.log(a); // not defined. 28 | ``` 29 | 30 | ```js 31 | var a = 0; 32 | var a = 0; // already defined. 33 | ``` 34 | 35 | ```js 36 | for (var a = 0;;) { 37 | var a = 0; // already defined. 38 | } 39 | ``` 40 | 41 | ```js 42 | function foo(a) { 43 | var a = 0; // already defined. 44 | } 45 | ``` 46 | 47 | ```js 48 | var a = 0; 49 | { 50 | var a = 0; // already defined in the upper scope. 51 | } 52 | ``` 53 | 54 | ```js 55 | function foo(a) { 56 | if (Math.random() < 0.5) { 57 | var a = 0; // already defined in the upper scope. 58 | } 59 | } 60 | ``` 61 | 62 | ### The following patterns are not considered warnings: 63 | 64 | ```js 65 | if (Math.random() < 0.5) { 66 | var a = 0; 67 | console.log(a); 68 | } 69 | else { 70 | var a = 1; 71 | console.log(a); 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /tests/lib/configs/es5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const assert = require("assert") 9 | const Rules = require("./_rules") 10 | 11 | /** 12 | * Checks whether a given core rule is an ES6 rule or not. 13 | * 14 | * @param {string} ruleId - The name of a core rule. 15 | * @returns {boolean} `true` if the rule is an ES6 rule. 16 | */ 17 | function isES6Rule(ruleId) { 18 | const def = Rules.getRuleDefinition(ruleId) 19 | const category = def && def.meta && def.meta.docs && def.meta.docs.category 20 | 21 | return category === "ECMAScript 6" 22 | } 23 | 24 | describe("'es5.js'", () => { 25 | const config = require("../../../lib/configs/es5") 26 | const configuredRules = Rules.getRulesOfConfig(config, "es5") 27 | const existingRules = [].concat( 28 | Rules.getCoreRuleNames(), 29 | Rules.getPluginRuleNames("mysticatea"), 30 | Rules.getPluginRuleNames("eslint-comments"), 31 | Rules.getPluginRuleNames("prettier") 32 | ) 33 | 34 | it("should be a valid config.", () => { 35 | Rules.validateConfig(config, "es5.js") 36 | }) 37 | 38 | for (const ruleId of existingRules) { 39 | it(`should include existing rule '${ruleId}'.`, () => { 40 | assert(ruleId in configuredRules) 41 | }) 42 | 43 | if (isES6Rule(ruleId)) { 44 | it(`should disable ES2015 rule '${ruleId}'.`, () => { 45 | assert.strictEqual(configuredRules[ruleId], "off") 46 | }) 47 | } 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /tests/lib/rules/no-this-in-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | const RuleTester = require("eslint").RuleTester 13 | const rule = require("../../../lib/rules/no-this-in-static") 14 | 15 | //------------------------------------------------------------------------------ 16 | // Tests 17 | //------------------------------------------------------------------------------ 18 | 19 | const tester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }) 20 | 21 | tester.run("no-this-in-static", rule, { 22 | valid: [ 23 | "function foo() { this }", 24 | "() => { this }", 25 | "class A { constructor() { this } }", 26 | "class A { foo() { this } }", 27 | "class A { static foo() { function foo() { this } } }", 28 | ], 29 | invalid: [ 30 | { 31 | code: "class A { static foo() { this } }", 32 | errors: ["Unexpected 'this'."], 33 | }, 34 | { 35 | code: "class A { static foo() { () => { this } } }", 36 | errors: ["Unexpected 'this'."], 37 | }, 38 | { 39 | code: "class A { static foo() { super.foo() } }", 40 | errors: ["Unexpected 'super'."], 41 | }, 42 | { 43 | code: "class A { static foo() { () => { super.foo() } } }", 44 | errors: ["Unexpected 'super'."], 45 | }, 46 | ], 47 | }) 48 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const fs = require("fs") 8 | const path = require("path") 9 | let configuredRulesDocumentUrl = null 10 | 11 | /** 12 | * Get the base URL from a given repository information. 13 | * @param {string|{type:string,url:string}} repository The repository information. 14 | * @returns {string|null} The base URL. 15 | */ 16 | function getBaseUrl(repository) { 17 | if (typeof repository === "string") { 18 | return `https://github.com/${repository}` 19 | } 20 | if ( 21 | repository && 22 | typeof repository.url === "string" && 23 | /^git\+.+\.git$/u.test(repository.url) 24 | ) { 25 | return repository.url.slice(4, -4) 26 | } 27 | 28 | return null 29 | } 30 | 31 | module.exports = { 32 | /** 33 | * The URL of rule's documentation for the `+eslint-plugin` config. 34 | * @type {string} 35 | */ 36 | get rulesDocumentUrl() { 37 | if (configuredRulesDocumentUrl) { 38 | return configuredRulesDocumentUrl 39 | } 40 | try { 41 | const { version, repository } = JSON.parse( 42 | fs.readFileSync( 43 | path.join(process.cwd(), "package.json"), 44 | "utf8" 45 | ) 46 | ) 47 | const baseUrl = getBaseUrl(repository) 48 | if (baseUrl) { 49 | return `${baseUrl}/blob/v${version}/docs/rules/{{name}}.md` 50 | } 51 | } catch (_error) { 52 | // ignore 53 | } 54 | return undefined 55 | }, 56 | 57 | set rulesDocumentUrl(value) { 58 | configuredRulesDocumentUrl = value 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /lib/rules/no-literal-call.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Rule to disallow a call of a literal. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Helpers 9 | //------------------------------------------------------------------------------ 10 | 11 | const LITERAL_TYPE = /^(?:(?:Array|Object)Expression|(?:Template)?Literal)$/u 12 | const LITERAL_AND_CLASS_TYPE = /^(?:(?:Array|Class|Object)Expression|(?:Template)?Literal)$/u 13 | 14 | //------------------------------------------------------------------------------ 15 | // Rule Definition 16 | //------------------------------------------------------------------------------ 17 | 18 | module.exports = { 19 | meta: { 20 | docs: { 21 | description: "Disallow a call of a literal.", 22 | category: "Possible Errors", 23 | recommended: false, 24 | url: 25 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-literal-call.md", 26 | }, 27 | fixable: null, 28 | schema: [], 29 | type: "problem", 30 | }, 31 | 32 | create(context) { 33 | /** 34 | * Reports a given node if it's a literal. 35 | * 36 | * @param {ASTNode} callee - A callee node to report. 37 | * @param {RegExp} pattern - A pattern of literal types. 38 | * @returns {void} 39 | */ 40 | function check(callee, pattern) { 41 | if (pattern.test(callee.type)) { 42 | context.report({ 43 | node: callee, 44 | message: "This is not a function.", 45 | }) 46 | } 47 | } 48 | 49 | return { 50 | CallExpression(node) { 51 | check(node.callee, LITERAL_AND_CLASS_TYPE) 52 | }, 53 | 54 | NewExpression(node) { 55 | check(node.callee, LITERAL_TYPE) 56 | }, 57 | 58 | TaggedTemplateExpression(node) { 59 | check(node.tag, LITERAL_AND_CLASS_TYPE) 60 | }, 61 | } 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /tests/lib/rules/no-use-ignored-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-use-ignored-vars rule. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | const RuleTester = require("eslint").RuleTester 12 | const rule = require("../../../lib/rules/no-use-ignored-vars") 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | const ruleTester = new RuleTester() 19 | 20 | ruleTester.run("no-use-ignored-vars", rule, { 21 | valid: [ 22 | "var foo; doSomething(foo)", 23 | "var _1; doSomething(_1)", 24 | "var _foo;", 25 | "function a(foo) { doSomething(foo) }", 26 | "function a(_1) { doSomething(_1) }", 27 | "function a(_foo) { }", 28 | "function a(obj) { for (_key in obj) return true; return false; }", 29 | "for (var _key in obj) { }", 30 | { 31 | code: "var _foo; doSomething(_foo)", 32 | options: ["x"], 33 | }, 34 | ], 35 | invalid: [ 36 | { 37 | code: "var _foo; doSomething(_foo)", 38 | errors: [ 39 | "Unexpected a use of '_foo'. This name is matched to ignored pattern.", 40 | ], 41 | }, 42 | { 43 | code: "var _bar; doSomething(_bar)", 44 | errors: [ 45 | "Unexpected a use of '_bar'. This name is matched to ignored pattern.", 46 | ], 47 | }, 48 | { 49 | code: "function a(_foo) { doSomething(_foo) }", 50 | errors: [ 51 | "Unexpected a use of '_foo'. This name is matched to ignored pattern.", 52 | ], 53 | }, 54 | { 55 | code: "var x; doSomething(x)", 56 | options: ["x"], 57 | errors: [ 58 | "Unexpected a use of 'x'. This name is matched to ignored pattern.", 59 | ], 60 | }, 61 | ], 62 | }) 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mysticatea/eslint-plugin", 3 | "version": "13.0.0", 4 | "description": "Additional ESLint rules.", 5 | "engines": { 6 | "node": ">=8.10.0" 7 | }, 8 | "main": "index.js", 9 | "files": [ 10 | "lib" 11 | ], 12 | "peerDependencies": { 13 | "eslint": ">=6.6.0" 14 | }, 15 | "dependencies": { 16 | "@typescript-eslint/eslint-plugin": "~2.6.1", 17 | "@typescript-eslint/parser": "~2.6.1", 18 | "eslint-plugin-eslint-comments": "~3.1.2", 19 | "eslint-plugin-eslint-plugin": "~2.1.0", 20 | "eslint-plugin-node": "~10.0.0", 21 | "eslint-plugin-prettier": "~3.1.1", 22 | "eslint-plugin-vue": "~6.0.0", 23 | "prettier": "~1.19.1", 24 | "vue-eslint-parser": "^7.0.0" 25 | }, 26 | "devDependencies": { 27 | "@mysticatea/eslint-plugin": "file:.", 28 | "codecov": "^3.6.1", 29 | "eslint": "~6.6.0", 30 | "fs-extra": "^8.1.0", 31 | "globals": "^12.1.1", 32 | "mocha": "^6.2.2", 33 | "npm-run-all": "^4.1.5", 34 | "nyc": "^14.1.1", 35 | "opener": "^1.5.1", 36 | "rimraf": "^3.0.0", 37 | "typescript": "~3.7.2" 38 | }, 39 | "scripts": { 40 | "clean": "rimraf .nyc_output coverage", 41 | "coverage": "opener coverage/lcov-report/index.html", 42 | "codecov": "codecov --disable=gcov -t $CODECOV_TOKEN", 43 | "lint": "eslint lib scripts tests", 44 | "test": "npm run -s lint && nyc mocha \"tests/lib/**/*.js\" --reporter dot", 45 | "update": "node scripts/generate-browser-globals && node scripts/generate-configs && node scripts/generate-rules", 46 | "preversion": "run-s clean update test", 47 | "version": "eslint lib/rules --fix && git add lib", 48 | "postversion": "git push && git push --tags", 49 | "watch": "mocha \"tests/lib/**/*.js\" --reporter dot --watch --growl" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "git+https://github.com/mysticatea/eslint-plugin.git" 54 | }, 55 | "keywords": [ 56 | "eslint", 57 | "eslintplugin", 58 | "block", 59 | "scope", 60 | "instanceof", 61 | "isArray", 62 | "static", 63 | "this", 64 | "rest", 65 | "spread", 66 | "ignore" 67 | ], 68 | "author": "Toru Nagashima", 69 | "license": "MIT", 70 | "bugs": { 71 | "url": "https://github.com/mysticatea/eslint-plugin/issues" 72 | }, 73 | "homepage": "https://github.com/mysticatea/eslint-plugin#readme" 74 | } 75 | -------------------------------------------------------------------------------- /lib/processors/vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This was copied from eslint-plugin-vue. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | preprocess(code) { 9 | return [code] 10 | }, 11 | 12 | postprocess(messages) { 13 | const state = { 14 | block: { 15 | disableAll: false, 16 | disableRules: new Set(), 17 | }, 18 | line: { 19 | disableAll: false, 20 | disableRules: new Set(), 21 | }, 22 | } 23 | 24 | // Filter messages which are in disabled area. 25 | return messages[0].filter(message => { 26 | if (message.ruleId === "@mysticatea/vue/comment-directive") { 27 | const rules = message.message.split(" ") 28 | const type = rules.shift() 29 | const group = rules.shift() 30 | switch (type) { 31 | case "--": 32 | state[group].disableAll = true 33 | break 34 | case "++": 35 | state[group].disableAll = false 36 | break 37 | case "-": 38 | for (const rule of rules) { 39 | state[group].disableRules.add(rule) 40 | } 41 | break 42 | case "+": 43 | for (const rule of rules) { 44 | state[group].disableRules.delete(rule) 45 | } 46 | break 47 | case "clear": 48 | state.block.disableAll = false 49 | state.block.disableRules.clear() 50 | state.line.disableAll = false 51 | state.line.disableRules.clear() 52 | break 53 | // no default 54 | } 55 | return false 56 | } 57 | return !( 58 | state.block.disableAll || 59 | state.line.disableAll || 60 | state.block.disableRules.has(message.ruleId) || 61 | state.line.disableRules.has(message.ruleId) 62 | ) 63 | }) 64 | }, 65 | 66 | supportsAutofix: true, 67 | } 68 | -------------------------------------------------------------------------------- /lib/rules/no-instanceof-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = { 13 | meta: { 14 | docs: { 15 | description: "disallow 'instanceof' for Array", 16 | category: "Best Practices", 17 | url: 18 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-instanceof-array.md", 19 | }, 20 | fixable: "code", 21 | schema: [], 22 | type: "problem", 23 | }, 24 | 25 | create(context) { 26 | const sourceCode = context.getSourceCode() 27 | 28 | /** 29 | * Checks whether the given node is RHS of instanceof. 30 | * 31 | * @param {ASTNode} node - The node to check. 32 | * @returns {boolean} `true` if the node is RHS of instanceof. 33 | */ 34 | function isRhsOfInstanceof(node) { 35 | return ( 36 | node.parent.type === "BinaryExpression" && 37 | node.parent.operator === "instanceof" && 38 | node.parent.right === node 39 | ) 40 | } 41 | 42 | return { 43 | "Program:exit"() { 44 | const globalScope = context.getScope() 45 | const variable = globalScope.set.get("Array") 46 | 47 | // Skip if undefined or shadowed 48 | if (variable == null || variable.defs.length > 0) { 49 | return 50 | } 51 | 52 | for (const reference of variable.references) { 53 | const id = reference.identifier 54 | const node = id.parent 55 | 56 | // Skip if it's not instanceof 57 | if (!isRhsOfInstanceof(id)) { 58 | continue 59 | } 60 | 61 | // Report 62 | context.report({ 63 | node, 64 | loc: node.loc, 65 | message: 66 | "Unexpected 'instanceof' operator. Use 'Array.isArray' instead.", 67 | fix: fixer => 68 | fixer.replaceText( 69 | node, 70 | `Array.isArray(${sourceCode.getText( 71 | node.left 72 | )})` 73 | ), 74 | }) 75 | } 76 | }, 77 | } 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /lib/configs/+eslint-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const { rulesDocumentUrl } = require("../utils") 8 | 9 | module.exports = { 10 | extends: [require.resolve("./+node.js")], 11 | overrides: [ 12 | { 13 | files: ["**/rules/**", "**/internal-rules/**"], 14 | rules: { 15 | // Enabled rules 16 | "@mysticatea/eslint-plugin/consistent-output": "error", 17 | "@mysticatea/eslint-plugin/fixer-return": "error", 18 | "@mysticatea/eslint-plugin/meta-property-ordering": [ 19 | "error", 20 | [ 21 | "deprecated", 22 | "docs", 23 | "fixable", 24 | "messages", 25 | "replacedBy", 26 | "schema", 27 | "type", 28 | ], 29 | ], 30 | "@mysticatea/eslint-plugin/no-deprecated-context-methods": 31 | "error", 32 | "@mysticatea/eslint-plugin/no-deprecated-report-api": "error", 33 | "@mysticatea/eslint-plugin/no-identical-tests": "error", 34 | "@mysticatea/eslint-plugin/no-missing-placeholders": "error", 35 | "@mysticatea/eslint-plugin/no-unused-placeholders": "error", 36 | "@mysticatea/eslint-plugin/no-useless-token-range": "error", 37 | "@mysticatea/eslint-plugin/prefer-output-null": "error", 38 | "@mysticatea/eslint-plugin/prefer-placeholders": "error", 39 | "@mysticatea/eslint-plugin/prefer-replace-text": "error", 40 | "@mysticatea/eslint-plugin/report-message-format": [ 41 | "error", 42 | "[^a-z'\"{].*\\.$", 43 | ], 44 | "@mysticatea/eslint-plugin/require-meta-docs-url": [ 45 | "error", 46 | { pattern: rulesDocumentUrl }, 47 | ], 48 | "@mysticatea/eslint-plugin/require-meta-fixable": "error", 49 | "@mysticatea/eslint-plugin/require-meta-type": "error", 50 | "@mysticatea/eslint-plugin/test-case-property-ordering": [ 51 | "error", 52 | [ 53 | "filename", 54 | "code", 55 | "output", 56 | "options", 57 | "parser", 58 | "parserOptions", 59 | "globals", 60 | "env", 61 | "errors", 62 | ], 63 | ], 64 | "@mysticatea/eslint-plugin/test-case-shorthand-strings": 65 | "error", 66 | }, 67 | }, 68 | ], 69 | } 70 | -------------------------------------------------------------------------------- /lib/rules/no-this-in-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = { 13 | meta: { 14 | docs: { 15 | description: "Disallow `this`/`super` in static methods", 16 | category: "Best Practices", 17 | url: 18 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-this-in-static.md", 19 | }, 20 | fixable: null, 21 | schema: [], 22 | type: "suggestion", 23 | }, 24 | 25 | create(context) { 26 | const sourceCode = context.getSourceCode() 27 | let funcInfo = null 28 | 29 | /** 30 | * Checks whether the given function node is a static method or not. 31 | * 32 | * @param {ASTNode} node - The function node to check. 33 | * @returns {boolean} `true` if the node is a static method. 34 | */ 35 | function isStaticMethod(node) { 36 | return ( 37 | node.type === "FunctionExpression" && 38 | node.parent.type === "MethodDefinition" && 39 | node.parent.static === true 40 | ) 41 | } 42 | 43 | /** 44 | * Updates the stack of function information. 45 | * 46 | * @param {ASTNode} node - The function node to make information. 47 | * @returns {void} 48 | */ 49 | function enterFunction(node) { 50 | funcInfo = { 51 | upper: funcInfo, 52 | static: isStaticMethod(node), 53 | } 54 | } 55 | 56 | /** 57 | * Updates the stack of function information. 58 | * 59 | * @returns {void} 60 | */ 61 | function exitFunction() { 62 | funcInfo = funcInfo.upper 63 | } 64 | 65 | /** 66 | * Reports the `this`/`super` node if this is inside of a static method. 67 | * 68 | * @param {ASTNode} node - The node to report. 69 | * @returns {void} 70 | */ 71 | function reportIfStatic(node) { 72 | if (funcInfo != null && funcInfo.static) { 73 | context.report({ 74 | node, 75 | loc: node.loc, 76 | message: "Unexpected '{{type}}'.", 77 | data: { type: sourceCode.getText(node) }, 78 | }) 79 | } 80 | } 81 | 82 | return { 83 | FunctionDeclaration: enterFunction, 84 | FunctionExpression: enterFunction, 85 | "FunctionDeclaration:exit": exitFunction, 86 | "FunctionExpression:exit": exitFunction, 87 | ThisExpression: reportIfStatic, 88 | Super: reportIfStatic, 89 | } 90 | }, 91 | } 92 | -------------------------------------------------------------------------------- /lib/configs/_override-2015.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaVersion: 2015, 10 | }, 11 | globals: { 12 | ArrayBuffer: "readonly", 13 | DataView: "readonly", 14 | Float32Array: "readonly", 15 | Float64Array: "readonly", 16 | Int16Array: "readonly", 17 | Int32Array: "readonly", 18 | Int8Array: "readonly", 19 | Map: "readonly", 20 | Promise: "readonly", 21 | Proxy: "readonly", 22 | Reflect: "readonly", 23 | Set: "readonly", 24 | Symbol: "readonly", 25 | Uint16Array: "readonly", 26 | Uint32Array: "readonly", 27 | Uint8Array: "readonly", 28 | Uint8ClampedArray: "readonly", 29 | WeakMap: "readonly", 30 | WeakSet: "readonly", 31 | }, 32 | rules: { 33 | // Enabled rules as errors 34 | "arrow-body-style": "error", 35 | "constructor-super": "error", 36 | "default-param-last": "error", 37 | "no-class-assign": "error", 38 | "no-const-assign": "error", 39 | "no-dupe-class-members": "error", 40 | "no-duplicate-imports": ["error", { includeExports: true }], 41 | "no-import-assign": "error", 42 | "no-new-symbol": "error", 43 | "no-template-curly-in-string": "error", 44 | "no-this-before-super": "error", 45 | "no-useless-computed-key": "error", 46 | "no-useless-constructor": "error", 47 | "no-useless-rename": "error", 48 | "no-var": "error", 49 | "object-shorthand": [ 50 | "error", 51 | "always", 52 | { avoidExplicitReturnArrows: true }, 53 | ], 54 | "prefer-arrow-callback": "error", 55 | "prefer-const": "error", 56 | "prefer-numeric-literals": "error", 57 | "prefer-rest-params": "error", 58 | "prefer-spread": "error", 59 | "prefer-template": "error", 60 | "require-unicode-regexp": "error", 61 | "require-yield": "error", 62 | "symbol-description": "error", 63 | 64 | // Enabled rules as warnings 65 | "class-methods-use-this": "warn", 66 | 67 | // Disabled rules as favor of Prettier. 68 | "arrow-parens": "off", 69 | "arrow-spacing": "off", 70 | "generator-star-spacing": "off", 71 | "no-confusing-arrow": "off", 72 | "rest-spread-spacing": "off", 73 | "template-curly-spacing": "off", 74 | "yield-star-spacing": "off", 75 | 76 | // Desabled rules 77 | "no-inner-declarations": "off", 78 | "no-restricted-imports": "off", 79 | "prefer-destructuring": "off", 80 | "sort-imports": "off", 81 | 82 | // 83 | // Plugins 84 | // 85 | 86 | // my own 87 | "@mysticatea/block-scoped-var": "off", 88 | "@mysticatea/no-this-in-static": "error", 89 | "@mysticatea/no-useless-rest-spread": "error", 90 | "@mysticatea/prefer-for-of": "error", 91 | }, 92 | } 93 | -------------------------------------------------------------------------------- /tests/lib/rules/no-instanceof-wrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | const RuleTester = require("eslint").RuleTester 13 | const rule = require("../../../lib/rules/no-instanceof-wrapper") 14 | 15 | //------------------------------------------------------------------------------ 16 | // Tests 17 | //------------------------------------------------------------------------------ 18 | 19 | const tester = new RuleTester() 20 | 21 | tester.run("no-instanceof-wrapper", rule, { 22 | valid: [ 23 | 'typeof x === "boolean"', 24 | 'typeof x === "number"', 25 | 'typeof x === "string"', 26 | 'typeof x === "object"', 27 | 'typeof x === "function"', 28 | { code: 'typeof x === "symbol"', env: { es6: true } }, 29 | "function foo(Boolean) { x instanceof Boolean }", 30 | "function foo(Number) { x instanceof Number }", 31 | "function foo(String) { x instanceof String }", 32 | "function foo(Object) { x instanceof Object }", 33 | "function foo(Function) { x instanceof Function }", 34 | { 35 | code: "function foo(Symbol) { x instanceof Symbol }", 36 | env: { es6: true }, 37 | }, 38 | "Boolean", 39 | ], 40 | invalid: [ 41 | { 42 | code: "x instanceof Boolean", 43 | output: 'typeof x === "boolean"', 44 | errors: [ 45 | "Unexpected 'instanceof' operator. Use 'typeof x === \"boolean\"' instead.", 46 | ], 47 | }, 48 | { 49 | code: "x instanceof Number", 50 | output: 'typeof x === "number"', 51 | errors: [ 52 | "Unexpected 'instanceof' operator. Use 'typeof x === \"number\"' instead.", 53 | ], 54 | }, 55 | { 56 | code: "x instanceof String", 57 | output: 'typeof x === "string"', 58 | errors: [ 59 | "Unexpected 'instanceof' operator. Use 'typeof x === \"string\"' instead.", 60 | ], 61 | }, 62 | { 63 | code: "x instanceof Object", 64 | output: 'typeof x === "object"', 65 | errors: [ 66 | "Unexpected 'instanceof' operator. Use 'typeof x === \"object\"' instead.", 67 | ], 68 | }, 69 | { 70 | code: "x instanceof Function", 71 | output: 'typeof x === "function"', 72 | errors: [ 73 | "Unexpected 'instanceof' operator. Use 'typeof x === \"function\"' instead.", 74 | ], 75 | }, 76 | { 77 | code: "x instanceof Symbol", 78 | output: 'typeof x === "symbol"', 79 | env: { es6: true }, 80 | errors: [ 81 | "Unexpected 'instanceof' operator. Use 'typeof x === \"symbol\"' instead.", 82 | ], 83 | }, 84 | ], 85 | }) 86 | -------------------------------------------------------------------------------- /lib/rules/arrow-parens.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2015 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | /** 9 | * Checks whether or not a given token is `(`. 10 | * @param {Token} token - A token to check. 11 | * @returns {boolean} `true` when the token is `(`. 12 | */ 13 | function isOpenParen(token) { 14 | return token.type === "Punctuator" && token.value === "(" 15 | } 16 | 17 | /** 18 | * Checks whether or not given two tokens are at a same line. 19 | * @param {Token} a - A left token. 20 | * @param {Token} b - A right token. 21 | * @returns {boolean} `true` when the tokens are at a same line. 22 | */ 23 | function isSameLine(a, b) { 24 | return a.loc.end.line === b.loc.start.line 25 | } 26 | 27 | module.exports = { 28 | meta: { 29 | docs: { 30 | description: "enforce the parentheses style of arrow functions.", 31 | category: "Stylistic Issues", 32 | recommended: false, 33 | url: 34 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/arrow-parens.md", 35 | }, 36 | fixable: "code", 37 | schema: [], 38 | type: "suggestion", 39 | }, 40 | create(context) { 41 | return { 42 | ArrowFunctionExpression(node) { 43 | const first = context 44 | .getSourceCode() 45 | .getFirstToken(node, node.async ? 1 : 0) 46 | const before = context.getSourceCode().getTokenBefore(first) 47 | 48 | if (isOpenParen(first)) { 49 | if ( 50 | node.params.length === 1 && 51 | node.params[0].type === "Identifier" && 52 | isOpenParen(before) && 53 | isSameLine(before, first) 54 | ) { 55 | context.report({ 56 | node, 57 | message: 58 | "Unexpected parentheses enclosing this argument.", 59 | fix(fixer) { 60 | const id = node.params[0] 61 | const begin = first.range[0] 62 | const end = context 63 | .getSourceCode() 64 | .getTokenAfter(id).range[1] 65 | 66 | return fixer.replaceTextRange( 67 | [begin, end], 68 | id.name 69 | ) 70 | }, 71 | }) 72 | } 73 | } else if (!isOpenParen(before) || !isSameLine(before, first)) { 74 | context.report({ 75 | node, 76 | message: 77 | "Expected to enclose this argument with parentheses.", 78 | fix(fixer) { 79 | const id = node.params[0] 80 | 81 | return fixer.replaceText(id, `(${id.name})`) 82 | }, 83 | }) 84 | } 85 | }, 86 | } 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /lib/rules/no-use-ignored-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Rule to disallow a use of ignored variables. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Helpers 9 | //------------------------------------------------------------------------------ 10 | 11 | const DEFAULT_IGNORE_PATTERN = /^_[a-zA-Z]+$/u 12 | 13 | //------------------------------------------------------------------------------ 14 | // Rule Definition 15 | //------------------------------------------------------------------------------ 16 | 17 | module.exports = { 18 | meta: { 19 | docs: { 20 | description: "Disallow a use of ignored variables.", 21 | category: "Stylistic Issues", 22 | recommended: false, 23 | url: 24 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-use-ignored-vars.md", 25 | }, 26 | fixable: null, 27 | schema: [{ type: "string" }], 28 | type: "suggestion", 29 | }, 30 | 31 | create(context) { 32 | const ignorePattern = 33 | context.options[0] != null 34 | ? new RegExp(context.options[0], "u") 35 | : DEFAULT_IGNORE_PATTERN 36 | 37 | /** 38 | * Checks whether a given variable is ignored or not. 39 | * 40 | * @param {escope.Variable} variable - A variable to check. 41 | * @returns {boolean} `true` if the variable is ignored. 42 | */ 43 | function isIgnored(variable) { 44 | return ignorePattern.test(variable.name) 45 | } 46 | 47 | /** 48 | * Checks whether a given reference is an initializer or not. 49 | * 50 | * @param {escope.Reference} reference - A reference to check. 51 | * @returns {boolean} `true` if the reference ia an initializer. 52 | */ 53 | function isNotInitializer(reference) { 54 | return !reference.init 55 | } 56 | 57 | /** 58 | * Reports a reference. 59 | * 60 | * @param {escope.Reference} reference - A reference to report. 61 | * @returns {void} 62 | */ 63 | function report(reference) { 64 | const id = reference.identifier 65 | 66 | context.report({ 67 | node: id, 68 | message: 69 | "Unexpected a use of '{{name}}'. This name is matched to ignored pattern.", 70 | data: id, 71 | }) 72 | } 73 | 74 | /** 75 | * Reports references of a given variable. 76 | * 77 | * @param {escope.Variable} variable - A variable to report. 78 | * @returns {void} 79 | */ 80 | function reportReferences(variable) { 81 | variable.references.filter(isNotInitializer).forEach(report) 82 | } 83 | 84 | return { 85 | "Program:exit"() { 86 | const queue = [context.getScope()] 87 | let scope = null 88 | 89 | while ((scope = queue.pop()) != null) { 90 | scope.variables.filter(isIgnored).forEach(reportReferences) 91 | 92 | Array.prototype.push.apply(queue, scope.childScopes) 93 | } 94 | }, 95 | } 96 | }, 97 | } 98 | -------------------------------------------------------------------------------- /lib/rules/no-instanceof-wrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = { 13 | meta: { 14 | docs: { 15 | description: "disallow 'instanceof' for wrapper objects", 16 | category: "Best Practices", 17 | url: 18 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-instanceof-wrapper.md", 19 | }, 20 | fixable: "code", 21 | schema: [], 22 | type: "problem", 23 | }, 24 | 25 | create(context) { 26 | const sourceCode = context.getSourceCode() 27 | const targetTypes = [ 28 | "Boolean", 29 | "Number", 30 | "String", 31 | "Object", 32 | "Function", 33 | "Symbol", 34 | ] 35 | 36 | /** 37 | * Checks whether the given node is RHS of instanceof. 38 | * 39 | * @param {ASTNode} node - The node to check. 40 | * @returns {boolean} `true` if the node is RHS of instanceof. 41 | */ 42 | function isRhsOfInstanceof(node) { 43 | return ( 44 | node.parent.type === "BinaryExpression" && 45 | node.parent.operator === "instanceof" && 46 | node.parent.right === node 47 | ) 48 | } 49 | 50 | return { 51 | "Program:exit"() { 52 | const globalScope = context.getScope() 53 | 54 | for (const ctorName of targetTypes) { 55 | const typeName = ctorName.toLowerCase() 56 | const variable = globalScope.set.get(ctorName) 57 | 58 | // Skip if undefined or shadowed 59 | if (variable == null || variable.defs.length > 0) { 60 | continue 61 | } 62 | 63 | for (const reference of variable.references) { 64 | const id = reference.identifier 65 | const node = id.parent 66 | 67 | // Skip if it's not instanceof 68 | if (!isRhsOfInstanceof(id)) { 69 | continue 70 | } 71 | 72 | // Report 73 | context.report({ 74 | node, 75 | loc: node.loc, 76 | message: 77 | "Unexpected 'instanceof' operator. Use 'typeof x === \"{{typeName}}\"' instead.", 78 | data: { typeName }, 79 | fix: fixer => 80 | fixer.replaceText( 81 | node, 82 | `typeof ${sourceCode.getText( 83 | node.left 84 | )} === "${typeName}"` 85 | ), 86 | }) 87 | } 88 | } 89 | }, 90 | } 91 | }, 92 | } 93 | -------------------------------------------------------------------------------- /lib/configs/+node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | parserOptions: { 9 | ecmaFeatures: { globalReturn: true }, 10 | ecmaVersion: 2020, 11 | sourceType: "script", 12 | }, 13 | globals: { 14 | // ECMAScript (experimental) 15 | globalThis: "readonly", 16 | // ECMA-404 17 | Intl: "readonly", 18 | 19 | // Web Standard 20 | TextDecoder: "readonly", 21 | TextEncoder: "readonly", 22 | URL: "readonly", 23 | URLSearchParams: "readonly", 24 | WebAssembly: "readonly", 25 | clearInterval: "readonly", 26 | clearTimeout: "readonly", 27 | console: "readonly", 28 | queueMicrotask: "readonly", 29 | setInterval: "readonly", 30 | setTimeout: "readonly", 31 | 32 | // Node.js 33 | Buffer: "readonly", 34 | GLOBAL: "readonly", 35 | clearImmediate: "readonly", 36 | global: "readonly", 37 | process: "readonly", 38 | root: "readonly", 39 | setImmediate: "readonly", 40 | 41 | // CommonJS 42 | __dirname: "readonly", 43 | __filename: "readonly", 44 | exports: "writable", 45 | module: "readonly", 46 | require: "readonly", 47 | }, 48 | rules: { 49 | "@mysticatea/node/exports-style": ["error", "module.exports"], 50 | "@mysticatea/node/file-extension-in-import": [ 51 | "error", 52 | "always", 53 | { ".js": "never", ".ts": "never", ".tsx": "never" }, 54 | ], 55 | "@mysticatea/node/no-callback-literal": "off", 56 | "@mysticatea/node/no-deprecated-api": "error", 57 | "@mysticatea/node/no-exports-assign": "error", 58 | "@mysticatea/node/no-extraneous-import": "error", 59 | "@mysticatea/node/no-extraneous-require": "error", 60 | "@mysticatea/node/no-missing-import": "error", 61 | "@mysticatea/node/no-missing-require": "error", 62 | "@mysticatea/node/no-unpublished-bin": "error", 63 | "@mysticatea/node/no-unpublished-import": "error", 64 | "@mysticatea/node/no-unpublished-require": "error", 65 | "@mysticatea/node/no-unsupported-features/es-builtins": "error", 66 | "@mysticatea/node/no-unsupported-features/es-syntax": "error", 67 | "@mysticatea/node/no-unsupported-features/node-builtins": "error", 68 | "@mysticatea/node/prefer-global/buffer": "error", 69 | "@mysticatea/node/prefer-global/console": "error", 70 | "@mysticatea/node/prefer-global/process": "error", 71 | "@mysticatea/node/prefer-global/text-decoder": "off", 72 | "@mysticatea/node/prefer-global/text-encoder": "off", 73 | "@mysticatea/node/prefer-global/url-search-params": "off", 74 | "@mysticatea/node/prefer-global/url": "off", 75 | "@mysticatea/node/prefer-promises/dns": "off", 76 | "@mysticatea/node/prefer-promises/fs": "off", 77 | "@mysticatea/node/process-exit-as-throw": "error", 78 | "@mysticatea/node/shebang": "error", 79 | }, 80 | settings: { 81 | node: { 82 | tryExtensions: [ 83 | ".vue", 84 | ".tsx", 85 | ".ts", 86 | ".mjs", 87 | ".cjs", 88 | ".js", 89 | ".json", 90 | ".node", 91 | ], 92 | }, 93 | }, 94 | overrides: [ 95 | { 96 | files: ["*.mjs", "*.ts", "*.tsx", "*.vue"], 97 | extends: [require.resolve("./+modules.js")], 98 | }, 99 | ], 100 | } 101 | -------------------------------------------------------------------------------- /tests/lib/rules/block-scoped-var.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2015 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const RuleTester = require("eslint").RuleTester 9 | const rule = require("../../../lib/rules/block-scoped-var") 10 | new RuleTester().run("block-scoped-var", rule, { 11 | valid: [ 12 | "{ var a; a; } { var a; a; }", 13 | "{ var a; a; } { { var a; a; } { var a; { a; } } }", 14 | "if (true) { var a; a; } else if (true) { var a; a; } else { var a; a; }", 15 | "while (true) { var a; a; } do { var a; a; } while (true);", 16 | { 17 | code: 18 | "for (var a = 0; a; a) { a; var b; b; } for (var a in []) { a; var b; b; } for (var a of []) { a; var b; b; }", 19 | env: { es6: true }, 20 | }, 21 | "switch (0) { case 0: var a; a; case 1: a; default: a; } { var a; a; }", 22 | "var a = {}; module.exports = a", 23 | 24 | // below should be warned by no-shadow rule. 25 | // this rule ignores those merely. 26 | "var a; function foo() { var a; }", 27 | "var a; function foo(a) { }", 28 | "function a() { var a; }", 29 | "(function a() { var a; })();", 30 | { code: "class a { foo() { var a; } }", env: { es6: true } }, 31 | { code: "(class a { foo() { var a; } })();", env: { es6: true } }, 32 | ], 33 | invalid: [ 34 | { 35 | code: "{ var a; a; } a;", 36 | errors: [{ type: "Identifier", message: '"a" is not defined.' }], 37 | }, 38 | { 39 | code: "a; { var a; a; }", 40 | errors: [{ type: "Identifier", message: '"a" is not defined.' }], 41 | }, 42 | { 43 | code: "for (var a; a; a) { } a;", 44 | errors: [{ type: "Identifier", message: '"a" is not defined.' }], 45 | }, 46 | { 47 | code: "a; for (var a; a; a) { }", 48 | errors: [{ type: "Identifier", message: '"a" is not defined.' }], 49 | }, 50 | { 51 | code: "{ var a; var a; }", 52 | errors: [ 53 | { type: "Identifier", message: '"a" is already defined.' }, 54 | ], 55 | }, 56 | { 57 | code: "for (var a; a; a) { var a; }", 58 | errors: [ 59 | { type: "Identifier", message: '"a" is already defined.' }, 60 | ], 61 | }, 62 | { 63 | code: "function foo(a) { var a; } var a;", 64 | errors: [ 65 | { type: "Identifier", message: '"a" is already defined.' }, 66 | ], 67 | }, 68 | { 69 | code: "{ var a; { var a; } }", 70 | errors: [ 71 | { 72 | type: "Identifier", 73 | message: '"a" is already defined in the upper scope.', 74 | }, 75 | ], 76 | }, 77 | { 78 | code: "{ var a; } { var a; a; }", 79 | errors: [ 80 | { 81 | type: "Identifier", 82 | message: '"a" is defined but never used.', 83 | column: 7, 84 | }, 85 | ], 86 | }, 87 | { 88 | code: 89 | "{ var {x: [a = 0]} = {x: [1]}; a; } { var a; ({x: [a = 0]} = {x: [1]}); }", 90 | env: { es6: true }, 91 | errors: [ 92 | { 93 | type: "Identifier", 94 | message: '"a" is defined but never used.', 95 | column: 43, 96 | }, 97 | ], 98 | }, 99 | ], 100 | }) 101 | -------------------------------------------------------------------------------- /tests/lib/configs/_rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | const { Linter } = require("eslint") 8 | const { 9 | ConfigArrayFactory, 10 | } = require("eslint/lib/cli-engine/config-array-factory") 11 | const Validator = require("eslint/lib/shared/config-validator") 12 | const { rules: PluginRulesIndex } = require("@mysticatea/eslint-plugin") 13 | 14 | const coreRules = new Linter().getRules() 15 | const pluginRules = new Map( 16 | Object.keys(PluginRulesIndex).map(key => [ 17 | `@mysticatea/${key}`, 18 | PluginRulesIndex[key], 19 | ]) 20 | ) 21 | const allRules = new Map([...coreRules, ...pluginRules]) 22 | 23 | const deprecatedRuleNames = new Set( 24 | Array.from(allRules) 25 | .filter(([, rule]) => rule && rule.meta && rule.meta.deprecated) 26 | .map(([ruleId]) => ruleId) 27 | ) 28 | const removedRuleNames = new Set( 29 | Object.keys(require("eslint/conf/replacements.json").rules) 30 | ) 31 | 32 | const configFactory = new ConfigArrayFactory() 33 | 34 | module.exports = { 35 | /** 36 | * Validate the given config object. 37 | * @param {any} config The config object to check. 38 | * @param {string} source The filename of the configuration to show error messages. 39 | * @returns {void} 40 | */ 41 | validateConfig(config, source) { 42 | Validator.validate(config, source, ruleId => allRules.get(ruleId)) 43 | 44 | /* istanbul ignore next */ 45 | for (const ruleId of [].concat( 46 | Object.keys(config.rules || {}), 47 | ...(config.overrides || []).map(c => Object.keys(c.rules || {})) 48 | )) { 49 | const rule = allRules.get(ruleId) 50 | if (rule == null) { 51 | throw new Error(`The '${ruleId}' rule does not exist.`) 52 | } 53 | if (deprecatedRuleNames.has(ruleId)) { 54 | throw new Error(`The '${ruleId}' rule was deprecated.`) 55 | } 56 | if (removedRuleNames.has(ruleId)) { 57 | throw new Error(`The '${ruleId}' rule was removed.`) 58 | } 59 | } 60 | }, 61 | 62 | /** 63 | * Get the rule definition of the given ID. 64 | * @param {string} ruleId The rule ID to get. 65 | * @returns {object} The rule definition. 66 | */ 67 | getRuleDefinition(ruleId) { 68 | return allRules.get(ruleId) 69 | }, 70 | 71 | /** 72 | * Get the core rules. 73 | * @returns {string[]} The core rule names. 74 | */ 75 | getCoreRuleNames() { 76 | return Array.from(coreRules.keys()).filter( 77 | ruleId => 78 | !deprecatedRuleNames.has(ruleId) && 79 | !removedRuleNames.has(ruleId) 80 | ) 81 | }, 82 | 83 | /** 84 | * Get the plugin rules. 85 | * @param {"eslint-comments"|"node"|"ts"|"vue"} pluginName The plugin name to get. 86 | * @returns {object} The core rules. Keys are rule IDs and values are each rule definition. 87 | */ 88 | getPluginRuleNames(pluginName) { 89 | return Object.keys(PluginRulesIndex) 90 | .filter(ruleId => 91 | pluginName === "mysticatea" 92 | ? !ruleId.includes("/") 93 | : ruleId.startsWith(`${pluginName}/`) 94 | ) 95 | .map(ruleId => `@mysticatea/${ruleId}`) 96 | .filter( 97 | ruleId => 98 | !deprecatedRuleNames.has(ruleId) && 99 | !removedRuleNames.has(ruleId) 100 | ) 101 | }, 102 | 103 | *iterateRulesOfConfig(config, name) { 104 | const filePath = require.resolve(`../../../lib/configs/${name}`) 105 | for (const element of configFactory.create(config, { filePath })) { 106 | if (element.rules) { 107 | yield* Object.entries(element.rules) 108 | } 109 | } 110 | }, 111 | 112 | getRulesOfConfig(config, name) { 113 | const rules = {} 114 | for (const [key, value] of this.iterateRulesOfConfig(config, name)) { 115 | rules[key] = value 116 | } 117 | return rules 118 | }, 119 | } 120 | -------------------------------------------------------------------------------- /tests/lib/rules/no-literal-call.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-literal-call rule. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | const RuleTester = require("eslint").RuleTester 12 | const rule = require("../../../lib/rules/no-literal-call") 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | const ruleTester = new RuleTester() 19 | 20 | ruleTester.run("no-literal-call", rule, { 21 | valid: [ 22 | "foo();", 23 | "obj.foo();", 24 | "(function() {})();", 25 | { code: "(() => 0)();", env: { es6: true } }, 26 | "new foo();", 27 | "new obj.foo();", 28 | "new (function() {})();", 29 | { code: "new (class {})();", env: { es6: true } }, 30 | { code: "new (() => 0)();", env: { es6: true } }, 31 | { code: "foo``;", env: { es6: true } }, 32 | { code: "obj.foo``;", env: { es6: true } }, 33 | { code: "(function() {})``;", env: { es6: true } }, 34 | { code: "(() => 0)``;", env: { es6: true } }, 35 | ], 36 | invalid: [ 37 | { code: "true();", errors: ["This is not a function."] }, 38 | { code: "false();", errors: ["This is not a function."] }, 39 | { code: "null();", errors: ["This is not a function."] }, 40 | { code: "100();", errors: ["This is not a function."] }, 41 | { code: '"hello"();', errors: ["This is not a function."] }, 42 | { code: "/abc/();", errors: ["This is not a function."] }, 43 | { code: "[1,2,3]();", errors: ["This is not a function."] }, 44 | { code: "({foo: 0})();", errors: ["This is not a function."] }, 45 | { 46 | code: "`hello`();", 47 | env: { es6: true }, 48 | errors: ["This is not a function."], 49 | }, 50 | { 51 | code: "(class A {})();", 52 | env: { es6: true }, 53 | errors: ["This is not a function."], 54 | }, 55 | { code: "new true();", errors: ["This is not a function."] }, 56 | { code: "new false();", errors: ["This is not a function."] }, 57 | { code: "new null();", errors: ["This is not a function."] }, 58 | { code: "new 100();", errors: ["This is not a function."] }, 59 | { code: 'new "hello"();', errors: ["This is not a function."] }, 60 | { code: "new /abc/();", errors: ["This is not a function."] }, 61 | { code: "new [1,2,3]();", errors: ["This is not a function."] }, 62 | { code: "new ({foo: 0})();", errors: ["This is not a function."] }, 63 | { 64 | code: "new `hello`();", 65 | env: { es6: true }, 66 | errors: ["This is not a function."], 67 | }, 68 | { 69 | code: "true``;", 70 | env: { es6: true }, 71 | errors: ["This is not a function."], 72 | }, 73 | { 74 | code: "false``;", 75 | env: { es6: true }, 76 | errors: ["This is not a function."], 77 | }, 78 | { 79 | code: "null``;", 80 | env: { es6: true }, 81 | errors: ["This is not a function."], 82 | }, 83 | { 84 | code: "100``;", 85 | env: { es6: true }, 86 | errors: ["This is not a function."], 87 | }, 88 | { 89 | code: '"hello"``;', 90 | env: { es6: true }, 91 | errors: ["This is not a function."], 92 | }, 93 | { 94 | code: "/abc/``;", 95 | env: { es6: true }, 96 | errors: ["This is not a function."], 97 | }, 98 | { 99 | code: "[1,2,3]``;", 100 | env: { es6: true }, 101 | errors: ["This is not a function."], 102 | }, 103 | { 104 | code: "({foo: 0})``;", 105 | env: { es6: true }, 106 | errors: ["This is not a function."], 107 | }, 108 | { 109 | code: "`hello```;", 110 | env: { es6: true }, 111 | errors: ["This is not a function."], 112 | }, 113 | { 114 | code: "(class A {})``;", 115 | env: { es6: true }, 116 | errors: ["This is not a function."], 117 | }, 118 | ], 119 | }) 120 | -------------------------------------------------------------------------------- /tests/lib/rules/no-useless-rest-spread.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-useless-rest-spread rule. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | const RuleTester = require("eslint").RuleTester 12 | const rule = require("../../../lib/rules/no-useless-rest-spread") 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | const ruleTester = new RuleTester({ 19 | parserOptions: { 20 | ecmaVersion: 2018, 21 | }, 22 | }) 23 | 24 | ruleTester.run("no-useless-rest-spread", rule, { 25 | valid: [ 26 | "let list = [...a, ...b]", 27 | "foo(...a, ...b)", 28 | "let [a, b, c, ...d] = list", 29 | "function foo(a, b, c, ...d) {}", 30 | "let obj = {...a, ...b}", 31 | "let {a, ...b} = obj", 32 | "let [a, ...{b, c}] = list", 33 | "function foo(a, ...{b, c}) {}", 34 | "foo(...{a}, ...{b})", 35 | "let list = [...{a}]", 36 | "let obj = {...[a]}", 37 | "f(...g())", 38 | ], 39 | invalid: [ 40 | { 41 | code: "let list = [...[x, y, x], ...b]", 42 | output: "let list = [x, y, x, ...b]", 43 | errors: ["Redundant spread element."], 44 | }, 45 | { 46 | code: "foo(...a, ...[x, y, x])", 47 | output: "foo(...a, x, y, x)", 48 | errors: ["Redundant spread element."], 49 | }, 50 | { 51 | code: "let [a, ...[b, c, ...d]] = obj", 52 | output: "let [a, b, c, ...d] = obj", 53 | errors: ["Redundant rest element."], 54 | }, 55 | { 56 | code: "function foo(a, ...[b, c, ...d]) {}", 57 | output: "function foo(a, b, c, ...d) {}", 58 | errors: ["Redundant rest parameter."], 59 | }, 60 | { 61 | code: "let obj = {...{x, y, x}, ...b}", 62 | output: "let obj = {x, y, x, ...b}", 63 | errors: ["Redundant spread property."], 64 | }, 65 | 66 | // Trailing commas 67 | { 68 | code: "let list = [...[x, y, x, ], ...b]", 69 | output: "let list = [x, y, x, ...b]", 70 | errors: ["Redundant spread element."], 71 | }, 72 | { 73 | code: "foo(...a, ...[x, y, x, ])", 74 | output: "foo(...a, x, y, x)", 75 | errors: ["Redundant spread element."], 76 | }, 77 | { 78 | code: "let [a, ...[b, c, ]] = obj", 79 | output: "let [a, b, c] = obj", 80 | errors: ["Redundant rest element."], 81 | }, 82 | { 83 | code: "function foo(a, ...[b, c, ]) {}", 84 | output: "function foo(a, b, c) {}", 85 | errors: ["Redundant rest parameter."], 86 | }, 87 | { 88 | code: "let obj = {...{x, y, x, }, ...b}", 89 | output: "let obj = {x, y, x, ...b}", 90 | errors: ["Redundant spread property."], 91 | }, 92 | 93 | // Empty literals 94 | { 95 | code: "let list = [...[], ...b]", 96 | output: "let list = [ ...b]", 97 | errors: ["Redundant spread element."], 98 | }, 99 | { 100 | code: "foo(...a, ...[])", 101 | output: "foo(...a)", 102 | errors: ["Redundant spread element."], 103 | }, 104 | { 105 | code: "let [a, ...[]] = obj", 106 | output: "let [a] = obj", 107 | errors: ["Redundant rest element."], 108 | }, 109 | { 110 | code: "let obj = {...{}, ...b}", 111 | output: "let obj = { ...b}", 112 | errors: ["Redundant spread property."], 113 | }, 114 | 115 | // Don't auto-fix if the inner array has holes. 116 | { 117 | code: "let list = [a, ...[,]]", 118 | output: null, 119 | errors: ["Redundant spread element."], 120 | }, 121 | { 122 | code: "let list = [a, ...[b,,c]]", 123 | output: null, 124 | errors: ["Redundant spread element."], 125 | }, 126 | { 127 | code: "foo(a, ...[,])", 128 | output: null, 129 | errors: ["Redundant spread element."], 130 | }, 131 | { 132 | code: "let [a, ...[,]] = list", 133 | output: null, 134 | errors: ["Redundant rest element."], 135 | }, 136 | { 137 | code: "function foo(a, ...[,]) {}", 138 | output: null, 139 | errors: ["Redundant rest parameter."], 140 | }, 141 | ], 142 | }) 143 | -------------------------------------------------------------------------------- /lib/rules/no-useless-rest-spread.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Rule to disallow unnecessary spread operators. 3 | * @author Toru Nagashima 4 | */ 5 | "use strict" 6 | 7 | //------------------------------------------------------------------------------ 8 | // Helpers 9 | //------------------------------------------------------------------------------ 10 | 11 | const FUNC_TYPE = /^(?:FunctionDeclaration|(?:New|Call|(?:Arrow)?Function)Expression)$/u 12 | const PROPERTY_PATTERN = /^(?:Experimental)?(Rest|Spread)Property$/u 13 | 14 | /** 15 | * Checks whether the given token is a comma. 16 | * 17 | * @param {Token} token - The token to check. 18 | * @returns {boolean} `true` if the token is a comma. 19 | */ 20 | function isCommaToken(token) { 21 | return token.type === "Punctuator" && token.value === "," 22 | } 23 | 24 | /** 25 | * Check whether a given node is a hole. 26 | * @param {ASTNode|null} element The node to check. 27 | * @returns {boolean} `true` if the node is a hole. 28 | */ 29 | function isHole(element) { 30 | return element == null 31 | } 32 | 33 | /** 34 | * Gets the last token of the given node's elements. 35 | * This skips trailing commas. 36 | * 37 | * @param {SourceCode} sourceCode - The source code object to get tokens. 38 | * @param {ASTNode} node - The node to get. This is one of ArrayExpression, 39 | * ArrayPattern, ObjectExpression, and ObjectPattern. 40 | * @returns {Token} The last element token. 41 | */ 42 | function getLastElementToken(sourceCode, node) { 43 | const token = sourceCode.getLastToken(node, 1) 44 | 45 | if (isCommaToken(token)) { 46 | return sourceCode.getTokenBefore(token) 47 | } 48 | return token 49 | } 50 | 51 | /** 52 | * Defines a fixer function. 53 | * 54 | * @param {SourceCode} sourceCode - The source code object to get tokens. 55 | * @param {ASTNode} node - A node to fix. 56 | * @returns {function} A fixer function. 57 | */ 58 | function defineFixer(sourceCode, node) { 59 | return fixer => { 60 | const child = node.argument 61 | 62 | // If the inner array includes holes, do nothing. 63 | if (child.elements != null && child.elements.some(isHole)) { 64 | return null 65 | } 66 | 67 | // Remove this element if it's empty. 68 | if ((child.elements || child.properties).length === 0) { 69 | const next = sourceCode.getTokenAfter(node) 70 | if (isCommaToken(next)) { 71 | return fixer.removeRange([node.range[0], next.range[1]]) 72 | } 73 | 74 | const prev = sourceCode.getTokenBefore(node) 75 | if (isCommaToken(prev)) { 76 | return fixer.removeRange([prev.range[0], node.range[1]]) 77 | } 78 | 79 | return fixer.remove(node) 80 | } 81 | 82 | // Unwrap. 83 | const first = sourceCode.getFirstToken(child, 1) 84 | const last = getLastElementToken(sourceCode, child) 85 | const replaceText = sourceCode.text.slice(first.range[0], last.range[1]) 86 | return fixer.replaceText(node, replaceText) 87 | } 88 | } 89 | 90 | //------------------------------------------------------------------------------ 91 | // Rule Definition 92 | //------------------------------------------------------------------------------ 93 | 94 | module.exports = { 95 | meta: { 96 | docs: { 97 | description: "Disallow unnecessary spread operators.", 98 | category: "Best Practices", 99 | recommended: false, 100 | url: 101 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-useless-rest-spread.md", 102 | }, 103 | fixable: "code", 104 | schema: [], 105 | type: "suggestion", 106 | }, 107 | 108 | create(context) { 109 | const sourceCode = context.getSourceCode() 110 | 111 | /** 112 | * Verify the given SpreadElement or RestElement. 113 | * @param {ASTNode} node The node to verify. 114 | * @returns {void} 115 | */ 116 | function verify(node) { 117 | const nodeType = node.type.replace( 118 | PROPERTY_PATTERN, 119 | t => `${t}Element` 120 | ) 121 | const parentType = node.parent.type 122 | const argumentType = node.argument.type 123 | const isArray = argumentType.startsWith("Array") 124 | const isObject = !isArray && argumentType.startsWith("Object") 125 | const isRedundant = 126 | ((isArray || isObject) && argumentType === parentType) || 127 | (isArray && FUNC_TYPE.test(parentType)) 128 | 129 | if (isRedundant) { 130 | const isRestParameter = 131 | nodeType === "RestElement" && argumentType !== parentType 132 | const type1 = nodeType === "RestElement" ? "rest" : "spread" 133 | const type2 = 134 | /*eslint-disable @mysticatea/prettier */ 135 | isRestParameter ? "parameter" : 136 | isArray ? "element" : 137 | /* otherwise */ "property" 138 | /*eslint-enable @mysticatea/prettier */ 139 | 140 | context.report({ 141 | node, 142 | message: "Redundant {{type1}} {{type2}}.", 143 | data: { type1, type2 }, 144 | fix: defineFixer(sourceCode, node), 145 | }) 146 | } 147 | } 148 | 149 | return { 150 | SpreadElement: verify, 151 | RestElement: verify, 152 | 153 | // Legacy for espree and babel-eslint. 154 | // SpreadProperty and RestProperty were replaced by SpreadElement and RestElement. 155 | SpreadProperty: verify, 156 | RestProperty: verify, 157 | ExperimentalSpreadProperty: verify, 158 | ExperimentalRestProperty: verify, 159 | } 160 | }, 161 | } 162 | -------------------------------------------------------------------------------- /tests/lib/rules/arrow-parens.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2015 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | const RuleTester = require("eslint").RuleTester 9 | const rule = require("../../../lib/rules/arrow-parens") 10 | new RuleTester({ parserOptions: { ecmaVersion: 2015 } }).run( 11 | "arrow-parens", 12 | rule, 13 | { 14 | valid: [ 15 | "var foo = (x) => x;", 16 | "var foo = (x => x);", 17 | "foo(x => x);", 18 | "foo(() => 0);", 19 | "foo((x, y) => x);", 20 | "foo((x = 0) => x);", 21 | "foo(([x]) => x);", 22 | "foo(({x}) => x);", 23 | "foo(x => x, (x) => x);", 24 | "foo(\n (x) => x,\n (x) => x\n);", 25 | 26 | { 27 | code: "var foo = async (x) => x;", 28 | parserOptions: { ecmaVersion: 2017 }, 29 | }, 30 | { 31 | code: "var foo = async (x => x);", 32 | parserOptions: { ecmaVersion: 2017 }, 33 | }, 34 | { 35 | code: "foo(async () => 0);", 36 | parserOptions: { ecmaVersion: 2017 }, 37 | }, 38 | { 39 | code: "foo(async (x, y) => x);", 40 | parserOptions: { ecmaVersion: 2017 }, 41 | }, 42 | { 43 | code: "foo(async (x = 0) => x);", 44 | parserOptions: { ecmaVersion: 2017 }, 45 | }, 46 | { 47 | code: "foo(async ([x]) => x);", 48 | parserOptions: { ecmaVersion: 2017 }, 49 | }, 50 | { 51 | code: "foo(async ({x}) => x);", 52 | parserOptions: { ecmaVersion: 2017 }, 53 | }, 54 | { 55 | code: "foo(x => x, async (x) => x);", 56 | parserOptions: { ecmaVersion: 2017 }, 57 | }, 58 | { 59 | code: "foo(\n async (x) => x,\n async (x) => x\n);", 60 | parserOptions: { ecmaVersion: 2017 }, 61 | }, 62 | ], 63 | invalid: [ 64 | { 65 | code: "var foo = x => x;", 66 | output: "var foo = (x) => x;", 67 | errors: [ 68 | { 69 | column: 11, 70 | message: 71 | "Expected to enclose this argument with parentheses.", 72 | }, 73 | ], 74 | }, 75 | { 76 | code: "foo(x => x, x => x);", 77 | output: "foo(x => x, (x) => x);", 78 | errors: [ 79 | { 80 | column: 13, 81 | message: 82 | "Expected to enclose this argument with parentheses.", 83 | }, 84 | ], 85 | }, 86 | { 87 | code: "foo(\n x => x,\n x => x\n);", 88 | output: "foo(\n (x) => x,\n (x) => x\n);", 89 | errors: [ 90 | { 91 | line: 2, 92 | message: 93 | "Expected to enclose this argument with parentheses.", 94 | }, 95 | { 96 | line: 3, 97 | message: 98 | "Expected to enclose this argument with parentheses.", 99 | }, 100 | ], 101 | }, 102 | { 103 | code: "foo((x) => x);", 104 | output: "foo(x => x);", 105 | errors: [ 106 | { 107 | message: 108 | "Unexpected parentheses enclosing this argument.", 109 | }, 110 | ], 111 | }, 112 | 113 | { 114 | code: "var foo = async x => x;", 115 | output: "var foo = async (x) => x;", 116 | parserOptions: { ecmaVersion: 2017 }, 117 | errors: [ 118 | { 119 | column: 11, 120 | message: 121 | "Expected to enclose this argument with parentheses.", 122 | }, 123 | ], 124 | }, 125 | { 126 | code: "foo(async x => x, async x => x);", 127 | output: "foo(async (x) => x, async (x) => x);", 128 | parserOptions: { ecmaVersion: 2017 }, 129 | errors: [ 130 | { 131 | column: 5, 132 | message: 133 | "Expected to enclose this argument with parentheses.", 134 | }, 135 | { 136 | column: 19, 137 | message: 138 | "Expected to enclose this argument with parentheses.", 139 | }, 140 | ], 141 | }, 142 | { 143 | code: "foo(\n async x => x,\n async x => x\n);", 144 | output: "foo(\n async (x) => x,\n async (x) => x\n);", 145 | parserOptions: { ecmaVersion: 2017 }, 146 | errors: [ 147 | { 148 | line: 2, 149 | message: 150 | "Expected to enclose this argument with parentheses.", 151 | }, 152 | { 153 | line: 3, 154 | message: 155 | "Expected to enclose this argument with parentheses.", 156 | }, 157 | ], 158 | }, 159 | ], 160 | } 161 | ) 162 | -------------------------------------------------------------------------------- /lib/configs/_override-ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | overrides: [ 9 | { 10 | files: ["*.ts", "*.tsx"], 11 | extends: [require.resolve("./+modules.js")], 12 | parser: require.resolve("@typescript-eslint/parser"), 13 | parserOptions: { 14 | loggerFn: false, 15 | project: "tsconfig.json", 16 | }, 17 | rules: { 18 | // Enabled rules 19 | "@mysticatea/ts/adjacent-overload-signatures": "error", 20 | "@mysticatea/ts/array-type": "error", 21 | "@mysticatea/ts/await-thenable": "error", 22 | "@mysticatea/ts/ban-ts-ignore": "error", 23 | "@mysticatea/ts/class-name-casing": "error", 24 | "@mysticatea/ts/consistent-type-assertions": "error", 25 | "@mysticatea/ts/explicit-member-accessibility": "error", 26 | "@mysticatea/ts/interface-name-prefix": "error", 27 | "@mysticatea/ts/member-naming": "error", 28 | "@mysticatea/ts/no-array-constructor": "error", 29 | "@mysticatea/ts/no-empty-interface": "error", 30 | "@mysticatea/ts/no-extraneous-class": "error", 31 | "@mysticatea/ts/no-floating-promises": "error", 32 | "@mysticatea/ts/no-for-in-array": "error", 33 | "@mysticatea/ts/no-inferrable-types": "error", 34 | "@mysticatea/ts/no-misused-new": "error", 35 | "@mysticatea/ts/no-misused-promises": "error", 36 | "@mysticatea/ts/no-parameter-properties": "error", 37 | "@mysticatea/ts/no-require-imports": "error", 38 | "@mysticatea/ts/no-this-alias": [ 39 | "error", 40 | { allowDestructuring: true }, 41 | ], 42 | "@mysticatea/ts/no-unnecessary-qualifier": "error", 43 | "@mysticatea/ts/no-unnecessary-type-arguments": "error", 44 | "@mysticatea/ts/no-unnecessary-type-assertion": "error", 45 | "@mysticatea/ts/no-var-requires": "error", 46 | // https://github.com/typescript-eslint/typescript-eslint/issues/454 47 | "@mysticatea/ts/prefer-function-type": "off", 48 | "@mysticatea/ts/prefer-includes": "error", 49 | "@mysticatea/ts/prefer-namespace-keyword": "error", 50 | // https://github.com/typescript-eslint/typescript-eslint/issues/946 51 | "@mysticatea/ts/prefer-readonly": "off", 52 | "@mysticatea/ts/prefer-regexp-exec": "error", 53 | "@mysticatea/ts/prefer-string-starts-ends-with": "error", 54 | "@mysticatea/ts/restrict-plus-operands": "error", 55 | "@mysticatea/ts/require-array-sort-compare": "error", 56 | "@mysticatea/ts/triple-slash-reference": "error", 57 | // なんか誤検知が多い... 58 | "@mysticatea/ts/unbound-method": [ 59 | "off", 60 | { ignoreStatic: true }, 61 | ], 62 | // https://github.com/typescript-eslint/typescript-eslint/issues/452 63 | "@mysticatea/ts/unified-signatures": "off", 64 | "@mysticatea/prettier": [ 65 | "error", 66 | { 67 | tabWidth: 4, 68 | semi: false, 69 | trailingComma: "all", 70 | parser: "typescript", 71 | }, 72 | { 73 | usePrettierrc: false, 74 | }, 75 | ], 76 | 77 | // Replacements 78 | camelcase: "off", 79 | "@mysticatea/ts/camelcase": "error", 80 | "no-empty-function": "off", 81 | "@mysticatea/ts/no-empty-function": "error", 82 | "no-useless-constructor": "off", 83 | "@mysticatea/ts/no-useless-constructor": "error", 84 | "require-await": "off", 85 | "@mysticatea/ts/require-await": "error", 86 | 87 | // Disabled rules 88 | "func-style": "off", 89 | "init-declarations": "off", 90 | "lines-between-class-members": "off", 91 | "no-dupe-class-members": "off", 92 | "no-invalid-this": "off", 93 | "no-loop-func": "off", 94 | "no-redeclare": "off", 95 | "no-undef": "off", 96 | "no-unused-vars": "off", 97 | "no-use-before-define": "off", 98 | "one-var": "off", 99 | "@mysticatea/ts/ban-types": "off", 100 | "@mysticatea/ts/brace-style": "off", // favor of Prettier. 101 | "@mysticatea/ts/consistent-type-definitions": "off", 102 | "@mysticatea/ts/explicit-function-return-type": "off", // I want but this is not so... 103 | "@mysticatea/ts/func-call-spacing": "off", // favor of Prettier. 104 | "@mysticatea/ts/generic-type-naming": "off", 105 | "@mysticatea/ts/indent": "off", // favor of Prettier. 106 | "@mysticatea/ts/member-delimiter-style": "off", // favor of Prettier. 107 | "@mysticatea/ts/member-ordering": "off", 108 | "@mysticatea/ts/no-explicit-any": "off", 109 | "@mysticatea/ts/no-extra-parens": "off", // favor of Prettier. 110 | "@mysticatea/ts/no-magic-numbers": "off", 111 | "@mysticatea/ts/no-namespace": "off", // I like the namespace for interfaces (type only things). 112 | "@mysticatea/ts/no-non-null-assertion": "off", 113 | "@mysticatea/ts/no-type-alias": "off", 114 | "@mysticatea/ts/no-unnecessary-condition": "off", // This was problematic for test code. 115 | "@mysticatea/ts/no-unused-vars": "off", // tsc verifies it. 116 | "@mysticatea/ts/no-use-before-define": "off", // tsc verifies it. 117 | "@mysticatea/ts/prefer-for-of": "off", 118 | "@mysticatea/ts/promise-function-async": "off", 119 | "@mysticatea/ts/quotes": "off", // favor of Prettier. 120 | "@mysticatea/ts/semi": "off", // favor of Prettier. 121 | "@mysticatea/ts/strict-boolean-expressions": "off", 122 | "@mysticatea/ts/type-annotation-spacing": "off", // favor of Prettier. 123 | "@mysticatea/ts/typedef": "off", 124 | }, 125 | }, 126 | { 127 | files: ["*.d.ts"], 128 | rules: { 129 | strict: "off", 130 | }, 131 | }, 132 | ], 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @mysticatea/eslint-plugin 2 | 3 | [![npm version](https://img.shields.io/npm/v/@mysticatea/eslint-plugin.svg)](https://www.npmjs.com/package/@mysticatea/eslint-plugin) 4 | [![Downloads/month](https://img.shields.io/npm/dm/@mysticatea/eslint-plugin.svg)](http://www.npmtrends.com/@mysticatea/eslint-plugin) 5 | [![Build Status](https://github.com/mysticatea/eslint-plugin/workflows/CI/badge.svg)](https://github.com/mysticatea/eslint-plugin/actions) 6 | [![codecov](https://codecov.io/gh/mysticatea/eslint-plugin/branch/master/graph/badge.svg)](https://codecov.io/gh/mysticatea/eslint-plugin) 7 | [![Dependency Status](https://david-dm.org/mysticatea/eslint-plugin.svg)](https://david-dm.org/mysticatea/eslint-plugin) 8 | 9 | Additional ESLint rules and ESLint configurations for me. 10 | 11 | ## 💿 Installation 12 | 13 | ``` 14 | npm install --save-dev eslint @mysticatea/eslint-plugin 15 | ``` 16 | 17 | ### Requirements 18 | 19 | - Node.js `^8.10.0` or newer versions. 20 | - ESLint `^6.3.0` or newer versions. 21 | 22 | ## 📖 Usage 23 | 24 | Write in your ESLint configurations: http://eslint.org/docs/user-guide/configuring#using-the-configuration-from-a-plugin 25 | 26 | ### Configs 27 | 28 | - `plugin:@mysticatea/es2020` ... Basic configuration for ES2020. 29 | - `plugin:@mysticatea/es2019` ... Basic configuration for ES2019. 30 | - `plugin:@mysticatea/es2018` ... Basic configuration for ES2018. 31 | - `plugin:@mysticatea/es2017` ... Basic configuration for ES2017. 32 | - `plugin:@mysticatea/es2016` ... Basic configuration for ES2016. 33 | - `plugin:@mysticatea/es2015` ... Basic configuration for ES2015. 34 | - `plugin:@mysticatea/es5` ... Basic configuration for ES5. 35 | - `plugin:@mysticatea/+modules` ... Additional configuration for ES modules. 36 | - `plugin:@mysticatea/+browser` ... Additional configuration for browser environment. 37 | - `plugin:@mysticatea/+node` ... Additional configuration for Node.js environment. 38 | - `plugin:@mysticatea/+eslint-plugin` ... Additional configuration for ESLint plugins. This includes `plugin:mysticatea/+node` setting. 39 | 40 | #### Details 41 | 42 | The main configurations `plugin:@mysticatea/es*` does: 43 | 44 | - detect bug-like code by ESLint rules. 45 | - enforce whitespace style by Prettier. 46 | - handle the `.ts` files as TypeScript then check by `typescript-eslint-parser` and `eslint-plugin-typescript`. 47 | - handle the `.vue` files as Vue.js SFC then check by `vue-eslint-parser` and `eslint-plugin-vue`. 48 | - handle the files in `test`/`tests` directory as `mocha`'s test code. 49 | - handle the files in `scripts` directory as Node.js environment. 50 | - handle the `.eslintrc.js` file as a Node.js script. 51 | - handle the `webpack.config.js` file as a Node.js script. 52 | - handle the `rollup.config.js` file as an ES module. 53 | 54 | You can use combination of a main configuration and some additional configurations. 55 | For examples: 56 | 57 | ##### For Node.js 58 | 59 | ```json 60 | { 61 | "extends": [ 62 | "plugin:@mysticatea/es2015", 63 | "plugin:@mysticatea/+node" 64 | ] 65 | } 66 | ``` 67 | 68 | > It handles `.js` files as scripts and `.mjs` files as modules. 69 | 70 | ##### For Browsers 71 | 72 | ```json 73 | { 74 | "extends": [ 75 | "plugin:@mysticatea/es2015", 76 | "plugin:@mysticatea/+browser" 77 | ] 78 | } 79 | ``` 80 | 81 | ##### For Browsers with ES modules 82 | 83 | ```json 84 | { 85 | "extends": [ 86 | "plugin:@mysticatea/es2015", 87 | "plugin:@mysticatea/+modules", 88 | "plugin:@mysticatea/+browser" 89 | ] 90 | } 91 | ``` 92 | 93 | ##### For ESLint plugins 94 | 95 | ```json 96 | { 97 | "extends": [ 98 | "plugin:@mysticatea/es2015", 99 | "plugin:@mysticatea/+eslint-plugin" 100 | ] 101 | } 102 | ``` 103 | 104 | ### Rules 105 | 106 | This plugin has some original rules and foreign rules. 107 | 108 | #### Original rules 109 | 110 | - [@mysticatea/arrow-parens](docs/rules/arrow-parens.md) enforces parens of argument lists (excludes too redundant parens) (fixable). 111 | - [@mysticatea/block-scoped-var](docs/rules/block-scoped-var.md) handles variables which are declared by `var` declaration as block-scoped. It disallows redeclarations, uses from outside of the scope, shadowing. 112 | - [@mysticatea/no-instanceof-array](docs/rules/no-instanceof-array.md) disallows 'instanceof' for Array (fixable). 113 | - [@mysticatea/no-instanceof-wrapper](docs/rules/no-instanceof-wrapper.md) disallows 'instanceof' for wrapper objects (fixable). 114 | - [@mysticatea/no-literal-call](docs/rules/no-literal-call.md) disallows a call of a literal. 115 | - [@mysticatea/no-this-in-static](docs/rules/no-this-in-static.md) disallows `this`/`super` in static methods. 116 | - [@mysticatea/no-use-ignored-vars](docs/rules/no-use-ignored-vars.md) disallows a use of ignored variables. 117 | - [@mysticatea/no-useless-rest-spread](docs/rules/no-useless-rest-spread.md) disallows unnecessary rest/spread operators (fixable). 118 | - [@mysticatea/prefer-for-of](docs/rules/prefer-for-of.md) requires `for-of` statements instead of `Array#forEach` or something like (fixable). 119 | 120 | #### Foreign rules 121 | 122 | - All `@mysticatea/eslint-comments/*` rules are imported from [eslint-plugin-eslint-comments](https://www.npmjs.com/package/eslint-plugin-eslint-comments). 123 | - All `@mysticatea/eslint-plugin/*` rules are imported from [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin). 124 | - All `@mysticatea/node/*` rules are imported from [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node). 125 | - All `@mysticatea/ts/*` rules are imported from [eslint-plugin-typescript](https://www.npmjs.com/package/eslint-plugin-typescript). 126 | - All `@mysticatea/vue/*` rules are imported from [eslint-plugin-vue](https://www.npmjs.com/package/eslint-plugin-vue). 127 | - The `@mysticatea/prettier` rule is imported from [eslint-plugin-prettier](https://www.npmjs.com/package/eslint-plugin-prettier). 128 | 129 | > **Q:** Why don't you use those plugins directly?
130 | > **A:** The combination with shareable configs and plugins has some problems because shareable configs were not designed to be used with plugins. @nzakas illustrated a way to use plugins as shareable configs together with other plugins in the discussion [eslint/eslint#3458](https://github.com/eslint/eslint/issues/3458#issuecomment-257161846). This is the way. 131 | 132 | ## 🚥 Semantic Versioning Policy 133 | 134 | This plugin follows [semantic versioning](http://semver.org/) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy). 135 | 136 | ## 📰 Changelog 137 | 138 | - [GitHub Releases](https://github.com/mysticatea/eslint-plugin/releases) 139 | 140 | ## ❤️ Contributing 141 | 142 | Welcome contributing! 143 | 144 | Please use GitHub's Issues/PRs. 145 | 146 | ### Development Tools 147 | 148 | - `npm test` runs tests and measures coverage. 149 | - `npm run clean` removes the coverage result of `npm test` command. 150 | - `npm run coverage` shows the coverage result of `npm test` command. 151 | - `npm run update` updates auto-generated files. 152 | - `npm run watch` runs tests and measures coverage when source code are changed. 153 | -------------------------------------------------------------------------------- /lib/configs/_override-vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | overrides: [ 9 | { 10 | files: ["*.vue"], 11 | extends: [ 12 | require.resolve("./+browser.js"), 13 | require.resolve("./+modules.js"), 14 | ], 15 | parser: require.resolve("vue-eslint-parser"), 16 | rules: { 17 | // Enabled rules 18 | "@mysticatea/vue/array-bracket-spacing": ["error", "never"], 19 | "@mysticatea/vue/arrow-spacing": "error", 20 | "@mysticatea/vue/block-spacing": "error", 21 | "@mysticatea/vue/brace-style": "error", 22 | "@mysticatea/vue/camelcase": "error", 23 | "@mysticatea/vue/comma-dangle": [ 24 | "error", 25 | { 26 | arrays: "always", 27 | objects: "always", 28 | imports: "always", 29 | exports: "always", 30 | functions: "always", 31 | }, 32 | ], 33 | "@mysticatea/vue/comment-directive": "error", 34 | "@mysticatea/vue/dot-location": "error", 35 | "@mysticatea/vue/eqeqeq": [ 36 | "error", 37 | "always", 38 | { null: "ignore" }, 39 | ], 40 | "@mysticatea/vue/jsx-uses-vars": "error", 41 | "@mysticatea/vue/key-spacing": "error", 42 | "@mysticatea/vue/keyword-spacing": "error", 43 | "@mysticatea/vue/match-component-file-name": "error", 44 | "@mysticatea/vue/max-attributes-per-line": [ 45 | "error", 46 | { 47 | singleline: 3, 48 | multiline: { 49 | max: 1, 50 | allowFirstLine: false, 51 | }, 52 | }, 53 | ], 54 | "@mysticatea/vue/no-async-in-computed-properties": "error", 55 | "@mysticatea/vue/no-boolean-default": "error", 56 | "@mysticatea/vue/no-deprecated-scope-attribute": "error", 57 | "@mysticatea/vue/no-dupe-keys": "error", 58 | "@mysticatea/vue/no-duplicate-attributes": "error", 59 | "@mysticatea/vue/no-empty-pattern": "error", 60 | "@mysticatea/vue/no-parsing-error": "error", 61 | "@mysticatea/vue/no-reserved-keys": "error", 62 | "@mysticatea/vue/no-shared-component-data": "error", 63 | "@mysticatea/vue/no-side-effects-in-computed-properties": 64 | "error", 65 | "@mysticatea/vue/no-template-key": "error", 66 | "@mysticatea/vue/no-textarea-mustache": "error", 67 | "@mysticatea/vue/no-unused-vars": "error", 68 | "@mysticatea/vue/object-curly-spacing": ["error", "always"], 69 | "@mysticatea/vue/require-component-is": "error", 70 | "@mysticatea/vue/require-direct-export": "error", 71 | "@mysticatea/vue/require-render-return": "error", 72 | "@mysticatea/vue/require-v-for-key": "error", 73 | "@mysticatea/vue/require-valid-default-prop": "error", 74 | "@mysticatea/vue/return-in-computed-property": "error", 75 | "@mysticatea/vue/space-infix-ops": "error", 76 | "@mysticatea/vue/space-unary-ops": "error", 77 | "@mysticatea/vue/v-on-function-call": "error", 78 | "@mysticatea/vue/v-slot-style": "error", 79 | "@mysticatea/vue/valid-template-root": "error", 80 | "@mysticatea/vue/valid-v-bind": "error", 81 | "@mysticatea/vue/valid-v-cloak": "error", 82 | "@mysticatea/vue/valid-v-else-if": "error", 83 | "@mysticatea/vue/valid-v-else": "error", 84 | "@mysticatea/vue/valid-v-for": "error", 85 | "@mysticatea/vue/valid-v-html": "error", 86 | "@mysticatea/vue/valid-v-if": "error", 87 | "@mysticatea/vue/valid-v-model": "error", 88 | "@mysticatea/vue/valid-v-on": "error", 89 | "@mysticatea/vue/valid-v-once": "error", 90 | "@mysticatea/vue/valid-v-pre": "error", 91 | "@mysticatea/vue/valid-v-show": "error", 92 | "@mysticatea/vue/valid-v-slot": "error", 93 | "@mysticatea/vue/valid-v-text": "error", 94 | "@mysticatea/vue/attribute-hyphenation": "error", 95 | "@mysticatea/vue/html-end-tags": "error", 96 | "@mysticatea/vue/html-indent": ["error", 4], 97 | "@mysticatea/vue/html-self-closing": "error", 98 | "@mysticatea/vue/mustache-interpolation-spacing": "error", 99 | "@mysticatea/vue/name-property-casing": "error", 100 | "@mysticatea/vue/no-multi-spaces": "error", 101 | "@mysticatea/vue/require-default-prop": "error", 102 | "@mysticatea/vue/require-prop-types": "error", 103 | "@mysticatea/vue/v-bind-style": "error", 104 | "@mysticatea/vue/v-on-style": "error", 105 | "@mysticatea/vue/attributes-order": "error", 106 | "@mysticatea/vue/html-quotes": "error", 107 | "@mysticatea/vue/order-in-components": "error", 108 | "@mysticatea/vue/this-in-template": "error", 109 | "@mysticatea/vue/html-closing-bracket-newline": [ 110 | "error", 111 | { 112 | singleline: "never", 113 | multiline: "always", 114 | }, 115 | ], 116 | "@mysticatea/vue/html-closing-bracket-spacing": "error", 117 | "@mysticatea/vue/prop-name-casing": "error", 118 | "@mysticatea/vue/component-name-in-template-casing": [ 119 | "error", 120 | "kebab-case", 121 | ], 122 | "@mysticatea/vue/multiline-html-element-content-newline": 123 | "error", 124 | "@mysticatea/vue/singleline-html-element-content-newline": 125 | "error", 126 | "@mysticatea/vue/no-spaces-around-equal-signs-in-attribute": 127 | "error", 128 | "@mysticatea/vue/no-template-shadow": "error", 129 | "@mysticatea/vue/no-unused-components": "error", 130 | "@mysticatea/vue/no-use-v-if-with-v-for": "error", 131 | "@mysticatea/vue/no-v-html": "error", 132 | "@mysticatea/vue/require-prop-type-constructor": "error", 133 | "@mysticatea/vue/use-v-on-exact": "error", 134 | 135 | // Disabled rules (prefer prettier) 136 | "@mysticatea/vue/no-restricted-syntax": "off", 137 | "@mysticatea/vue/script-indent": "off", 138 | }, 139 | }, 140 | ], 141 | } 142 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-for-of.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | const RuleTester = require("eslint").RuleTester 13 | const rule = require("../../../lib/rules/prefer-for-of") 14 | 15 | //------------------------------------------------------------------------------ 16 | // Tests 17 | //------------------------------------------------------------------------------ 18 | 19 | const tester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }) 20 | 21 | tester.run("prefer-for-of", rule, { 22 | valid: [ 23 | "for (const value of list);", 24 | "list.forEach(() => {})", 25 | "list.forEach((value, index) => {})", 26 | "list.forEach(function() {})", 27 | "list.forEach(function(value, index) {})", 28 | "list.forEach(doSomething)", 29 | "list.forEach(function foo(value) { foo })", 30 | "foo(list.forEach((value) => {}))", 31 | "for (let i = 0; i < list.length; ++i) { const value = list[i]; i }", 32 | "for (let i = 0; i < list.length && a; ++i) { const value = list[i]; }", 33 | "for (let i = 0; i < list.length; i += 2) { const value = list[i]; }", 34 | "forEach(function(value) {})", 35 | "forEach((value) => {})", 36 | "for (let i = 0; i < list.length; ++i) { const value = list[i]; list[i] = 0 }", 37 | ], 38 | invalid: [ 39 | { 40 | code: 41 | "list.forEach(function(value) { return; function foo() { return } });", 42 | output: 43 | "for (let value of list) { continue; function foo() { return } }", 44 | errors: ["Expected for-of statement."], 45 | }, 46 | { 47 | code: "list.forEach(function(value) { return; this.a });", 48 | output: "for (let value of list) { continue; list.a }", 49 | globals: { list: false, obj: false }, 50 | errors: ["Expected for-of statement."], 51 | }, 52 | { 53 | code: "a.b.c.forEach(function(value) { return; this.a });", 54 | output: "for (let value of a.b.c) { continue; a.b.c.a }", 55 | globals: { list: false, a: false }, 56 | errors: ["Expected for-of statement."], 57 | }, 58 | { 59 | code: "list.forEach(function(value) { return; this.a }, obj);", 60 | output: "for (let value of list) { continue; obj.a }", 61 | globals: { list: false, obj: false }, 62 | errors: ["Expected for-of statement."], 63 | }, 64 | { 65 | code: 66 | "list.forEach(function(value) { return; let obj; this.a }, obj);", 67 | output: null, 68 | globals: { list: false, obj: false }, 69 | errors: ["Expected for-of statement."], 70 | }, 71 | { 72 | code: "foo().forEach(function(value) { return; this.a });", 73 | output: null, 74 | globals: { list: false, foo: false }, 75 | errors: ["Expected for-of statement."], 76 | }, 77 | { 78 | code: "list.forEach(function(value) { return; this.a }, foo());", 79 | output: null, 80 | globals: { list: false, foo: false }, 81 | errors: ["Expected for-of statement."], 82 | }, 83 | { 84 | code: "list.forEach(function(value) { return this });", 85 | output: "for (let value of list) { continue; }", 86 | globals: { list: false, obj: false }, 87 | errors: ["Expected for-of statement."], 88 | }, 89 | { 90 | code: 91 | "list.forEach(function(value) { return; foo(a => this[a]) });", 92 | output: "for (let value of list) { continue; foo(a => list[a]) }", 93 | globals: { list: false, obj: false }, 94 | errors: ["Expected for-of statement."], 95 | }, 96 | { 97 | code: "list.forEach((value) => { return });", 98 | output: "for (let value of list) { continue; }", 99 | errors: ["Expected for-of statement."], 100 | }, 101 | { 102 | code: "list.forEach(value => { return });", 103 | output: "for (let value of list) { continue; }", 104 | errors: ["Expected for-of statement."], 105 | }, 106 | { 107 | code: "foo().forEach(value => { return });", 108 | output: "for (let value of foo()) { continue; }", 109 | errors: ["Expected for-of statement."], 110 | }, 111 | { 112 | code: "list.forEach(value => { this });", 113 | output: "for (let value of list) { this }", 114 | globals: { list: false }, 115 | errors: ["Expected for-of statement."], 116 | }, 117 | { 118 | code: "list.forEach(value => { let list; this });", 119 | output: "for (let value of list) { let list; this }", 120 | globals: { list: false }, 121 | errors: ["Expected for-of statement."], 122 | }, 123 | { 124 | code: "list.forEach(value => value);", 125 | output: "for (let value of list) value;", 126 | errors: ["Expected for-of statement."], 127 | }, 128 | { 129 | code: 130 | "list.filter(p)\n .map(t)\n .forEach(value => { return });", 131 | output: null, 132 | errors: ["Expected for-of statement."], 133 | }, 134 | { 135 | code: "for (const key in obj) { }", 136 | output: null, 137 | errors: ["Expected for-of statement."], 138 | }, 139 | { 140 | code: 141 | "function wrap() { for (let i = 0; i < list.length; ++i) { return } }", 142 | output: null, 143 | errors: ["Expected for-of statement."], 144 | }, 145 | { 146 | code: 147 | "function wrap() { for (let i = 0; i < list.length; ++i) { const value = list[i]; return } }", 148 | output: "function wrap() { for (let value of list) { return } }", 149 | errors: ["Expected for-of statement."], 150 | }, 151 | { 152 | code: 153 | "function wrap() { for (let i = 0; i < list.length; i++) { const value = list[i]; return } }", 154 | output: "function wrap() { for (let value of list) { return } }", 155 | errors: ["Expected for-of statement."], 156 | }, 157 | { 158 | code: 159 | "function wrap() { for (let i = 0; i < list.length; i += 1) { const value = list[i]; return } }", 160 | output: "function wrap() { for (let value of list) { return } }", 161 | errors: ["Expected for-of statement."], 162 | }, 163 | { 164 | code: 165 | "for (let i = 0, end = list.length; i < end;i = 1 + i) { const value = list[i]; }", 166 | output: "for (let value of list) { }", 167 | errors: ["Expected for-of statement."], 168 | }, 169 | { 170 | code: 171 | "for (let i = 0, length = list.length; i < length; i = i + 1) { const value = list[i]; }", 172 | output: "for (let value of list) { }", 173 | errors: ["Expected for-of statement."], 174 | }, 175 | ], 176 | }) 177 | -------------------------------------------------------------------------------- /lib/rules/block-scoped-var.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2015 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Helpers 10 | //------------------------------------------------------------------------------ 11 | 12 | const scopeNodeType = /^(?:(?:Block|Switch|For(?:In|Of)?)Statement|CatchClause|Program)$/u 13 | const containerNodeType = /^(?:For(?:In|Of)?Statement|(?:Arrow)?Function(?:Declaration|Expression))$/u 14 | 15 | /** 16 | * Checks whether or not a given definition should be skipped. 17 | * @param {escope.Variable.DefEntry} def - A definition to check. 18 | * @param {escope.Variable.DefEntry[]} defs - A definition list which includes `def`. 19 | * @param {escope.Variable} variable - A variable which is defined by the definition. 20 | * @returns {boolean} Whether or not the definition should be skipped. 21 | */ 22 | function shouldSkip(def, defs, variable) { 23 | // To check re-declarations. 24 | if (defs.length >= 2 && def.type !== "TDZ") { 25 | return false 26 | } 27 | 28 | switch (def.type) { 29 | case "ClassName": 30 | case "FunctionName": 31 | return variable.scope.block === def.node 32 | 33 | case "Parameter": 34 | case "TDZ": 35 | return true 36 | 37 | case "Variable": 38 | return def.parent.kind !== "var" 39 | 40 | default: 41 | return false 42 | } 43 | } 44 | 45 | /** 46 | * Pseudo scope information for `var`. 47 | * Finds and creates information of a containing scope of a given declaration. 48 | */ 49 | class PseudoScope { 50 | /** 51 | * @param {escope.Variable.DefineEntry} def - A declaration. 52 | */ 53 | constructor(def) { 54 | let node = null 55 | 56 | if (def.type === "Parameter") { 57 | node = def.node 58 | } else { 59 | node = (def.parent || def.node).parent 60 | 61 | while (!scopeNodeType.test(node.type)) { 62 | node = node.parent 63 | } 64 | if ( 65 | node.parent != null && 66 | containerNodeType.test(node.parent.type) 67 | ) { 68 | node = node.parent 69 | } 70 | } 71 | 72 | /** 73 | * The `Identifier` node of the declaration. 74 | * @type {ASTNode} 75 | */ 76 | this.identifier = def.name 77 | 78 | /** 79 | * The start position of the scope. 80 | * @type {number} 81 | */ 82 | this.start = node.range[0] 83 | 84 | /** 85 | * The end position of the scope. 86 | * @type {number} 87 | */ 88 | this.end = node.range[1] 89 | 90 | /** 91 | * The `Identifier` nodes of re-declarations. 92 | * @type {ASTNode[]} 93 | */ 94 | this.redeclarations = [] 95 | 96 | /** 97 | * The `PseudoScope` instances which are nested. 98 | * @type {PseudoScope[]} 99 | */ 100 | this.children = [] 101 | 102 | /** 103 | * The flag of shadowing. 104 | * @type {boolean} 105 | */ 106 | this.shadowing = false 107 | 108 | /** 109 | * The flag of used. 110 | * @type {boolean} 111 | */ 112 | this.used = false 113 | } 114 | 115 | /** 116 | * Creates pseudo scopes of a given variable. 117 | * @param {escope.Variable} variable - A variable to create. 118 | * @returns {PseudoScope[]} the created scopes. 119 | */ 120 | static createScopesFrom(variable) { 121 | const defs = variable.defs 122 | const scopes = [] 123 | for (const def of defs) { 124 | if (!shouldSkip(def, defs, variable)) { 125 | PseudoScope.push(scopes, new PseudoScope(def)) 126 | } 127 | } 128 | return scopes 129 | } 130 | 131 | /** 132 | * Adds a given scope into a given scope list. 133 | * This considers re-declarations and shadowing. 134 | * @param {PseudoScope[]} scopes - Scopes to be added. 135 | * @param {PseudoScope} newScope - A scope to add. 136 | * @returns {void} 137 | */ 138 | static push(scopes, newScope) { 139 | for (const scope of scopes) { 140 | if (scope.start === newScope.start && scope.end === newScope.end) { 141 | scope.redeclarations.push(newScope.identifier) 142 | return 143 | } 144 | if (scope.start <= newScope.start && newScope.end <= scope.end) { 145 | newScope.markAsShadowing() 146 | PseudoScope.push(scope.children, newScope) 147 | return 148 | } 149 | } 150 | 151 | scopes.push(newScope) 152 | } 153 | 154 | /** 155 | * Finds a containing scope of a given reference. 156 | * @param {PseudoScope[]} scopes - Scopes to be domain. 157 | * @param {escope.Reference} reference - A reference to find. 158 | * @returns {PseudoScope|null} A containing scope of the reference. 159 | */ 160 | static findScope(scopes, reference) { 161 | const range = reference.identifier.range 162 | 163 | for (const scope of scopes) { 164 | if (scope.start <= range[0] && range[1] <= scope.end) { 165 | return PseudoScope.findScope(scope.children, reference) || scope 166 | } 167 | } 168 | 169 | return null 170 | } 171 | 172 | /** 173 | * Turns a shadowing flag on. 174 | * @returns {void} 175 | */ 176 | markAsShadowing() { 177 | this.shadowing = true 178 | } 179 | 180 | /** 181 | * Turns an used flag on. 182 | * @returns {void} 183 | */ 184 | markAsUsed() { 185 | this.used = true 186 | } 187 | } 188 | 189 | //------------------------------------------------------------------------------ 190 | // Rule Definition 191 | //------------------------------------------------------------------------------ 192 | 193 | module.exports = { 194 | meta: { 195 | docs: { 196 | description: "disallow illegal usage of variables as block-scoped.", 197 | category: "Possible Errors", 198 | recommended: false, 199 | url: 200 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/block-scoped-var.md", 201 | }, 202 | fixable: null, 203 | schema: [], 204 | type: "suggestion", 205 | }, 206 | create(context) { 207 | /** 208 | * Finds and reports references which are outside of valid scopes. 209 | * @param {ASTNode} node - A node to get variables. 210 | * @returns {void} 211 | */ 212 | function checkForVariables(node) { 213 | const variables = context.getDeclaredVariables(node) 214 | for (const variable of variables) { 215 | const defs = variable.defs 216 | const lastDef = defs[defs.length - 1] 217 | 218 | // Skip except the last declaration. 219 | // Because `node.parent` is possibly not defined. 220 | if ((lastDef.parent || lastDef.node) !== node) { 221 | continue 222 | } 223 | 224 | // Collect the containing scopes. 225 | const scopes = PseudoScope.createScopesFrom(variable) 226 | if (scopes.length === 0) { 227 | continue 228 | } 229 | 230 | // Check whether or not any reading reference exists. 231 | // And while it does, warn references which does not belong to any 232 | // scope. 233 | let hasReadRef = false 234 | for (const reference of variable.references) { 235 | const scope = PseudoScope.findScope(scopes, reference) 236 | 237 | if (reference.isRead()) { 238 | hasReadRef = true 239 | if (scope != null) { 240 | scope.markAsUsed() 241 | } 242 | } 243 | 244 | if (scope == null) { 245 | context.report({ 246 | node: reference.identifier, 247 | message: '"{{name}}" is not defined.', 248 | data: { name: reference.identifier.name }, 249 | }) 250 | } 251 | } 252 | 253 | // Warn re-declarations, shadowing, and unused. 254 | scopes.forEach(function walk(scope) { 255 | for (const identifier of scope.redeclarations) { 256 | context.report({ 257 | node: identifier, 258 | message: '"{{name}}" is already defined.', 259 | data: { name: identifier.name }, 260 | }) 261 | } 262 | 263 | if (scope.shadowing) { 264 | context.report({ 265 | node: scope.identifier, 266 | message: 267 | '"{{name}}" is already defined in the upper scope.', 268 | data: { name: scope.identifier.name }, 269 | }) 270 | } 271 | 272 | if (hasReadRef && !scope.used) { 273 | context.report({ 274 | node: scope.identifier, 275 | message: '"{{name}}" is defined but never used.', 276 | data: { name: scope.identifier.name }, 277 | }) 278 | } 279 | 280 | scope.children.forEach(walk) 281 | }) 282 | } 283 | } 284 | 285 | return { 286 | VariableDeclaration: checkForVariables, 287 | FunctionDeclaration: checkForVariables, 288 | ClassDeclaration: checkForVariables, 289 | ImportDeclaration: checkForVariables, 290 | } 291 | }, 292 | } 293 | -------------------------------------------------------------------------------- /lib/configs/_base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | "use strict" 6 | 7 | module.exports = { 8 | root: true, 9 | plugins: ["@mysticatea"], 10 | parserOptions: { 11 | ecmaVersion: 5, 12 | sourceType: "script", 13 | }, 14 | globals: { 15 | Intl: "readonly", 16 | clearInterval: "readonly", 17 | clearTimeout: "readonly", 18 | console: "readonly", 19 | setInterval: "readonly", 20 | setTimeout: "readonly", 21 | }, 22 | rules: { 23 | // Enabled rules 24 | "accessor-pairs": ["error", { enforceForClassMembers: true }], 25 | "array-callback-return": "error", 26 | camelcase: "error", 27 | "consistent-return": "error", 28 | curly: "error", 29 | "default-case": "error", 30 | "default-param-last": "error", 31 | "dot-notation": "error", 32 | eqeqeq: ["error", "always", { null: "ignore" }], 33 | "for-direction": "error", 34 | "func-style": ["error", "declaration"], 35 | "getter-return": "error", 36 | "init-declarations": "error", 37 | "linebreak-style": ["error", "unix"], 38 | "lines-between-class-members": "error", 39 | "max-statements-per-line": ["error", { max: 1 }], 40 | "multiline-comment-style": ["error", "separate-lines"], 41 | "new-cap": "error", 42 | "no-alert": "error", 43 | "no-array-constructor": "error", 44 | "no-async-promise-executor": "error", 45 | "no-caller": "error", 46 | "no-case-declarations": "error", 47 | "no-compare-neg-zero": "error", 48 | "no-cond-assign": "error", 49 | "no-constant-condition": "error", 50 | "no-control-regex": "error", 51 | "no-debugger": "error", 52 | "no-delete-var": "error", 53 | "no-div-regex": "error", 54 | "no-dupe-args": "error", 55 | "no-dupe-keys": "error", 56 | "no-duplicate-case": "error", 57 | "no-else-return": "error", 58 | "no-empty": "error", 59 | "no-empty-character-class": "error", 60 | "no-empty-function": "error", 61 | "no-empty-pattern": "error", 62 | "no-eval": "error", 63 | "no-ex-assign": "error", 64 | "no-extend-native": "error", 65 | "no-extra-bind": "error", 66 | "no-extra-boolean-cast": "error", 67 | "no-extra-label": "error", 68 | "no-fallthrough": "error", 69 | "no-func-assign": "error", 70 | "no-global-assign": "error", 71 | "no-implicit-coercion": "error", 72 | "no-implicit-globals": "error", 73 | "no-implied-eval": "error", 74 | "no-import-assign": "error", 75 | "no-inner-declarations": ["error", "functions"], 76 | "no-invalid-regexp": "error", 77 | "no-invalid-this": "error", 78 | "no-irregular-whitespace": [ 79 | "error", 80 | { 81 | skipComments: false, 82 | skipRegExps: false, 83 | skipStrings: false, 84 | skipTemplates: false, 85 | }, 86 | ], 87 | "no-iterator": "error", 88 | "no-label-var": "error", 89 | "no-lone-blocks": "error", 90 | "no-lonely-if": "error", 91 | "no-loop-func": "error", 92 | "no-misleading-character-class": "error", 93 | "no-mixed-operators": [ 94 | "error", 95 | { 96 | groups: [ 97 | ["&", "|", "^", "~", "<<", ">>", ">>>"], 98 | ["&&", "||"], 99 | ], 100 | }, 101 | ], 102 | "no-new": "error", 103 | "no-new-object": "error", 104 | "no-new-require": "error", 105 | "no-new-wrappers": "error", 106 | "no-obj-calls": "error", 107 | "no-octal": "error", 108 | "no-octal-escape": "error", 109 | "no-param-reassign": ["error", { props: false }], 110 | "no-process-env": "error", 111 | "no-process-exit": "error", 112 | "no-prototype-builtins": "error", 113 | "no-redeclare": ["error", { builtinGlobals: true }], 114 | "no-regex-spaces": "error", 115 | "no-restricted-properties": [ 116 | "error", 117 | { property: "__count__" }, 118 | { property: "__noSuchMethod__" }, 119 | { property: "__parent__" }, 120 | { property: "__defineGetter__" }, 121 | { property: "__defineSetter__" }, 122 | { property: "__lookupGetter__" }, 123 | { property: "__lookupSetter__" }, 124 | ], 125 | "no-return-assign": "error", 126 | "no-return-await": "error", 127 | "no-script-url": "error", 128 | "no-self-assign": ["error", { props: true }], 129 | "no-self-compare": "error", 130 | "no-sequences": "error", 131 | "no-shadow": ["error", { builtinGlobals: true }], 132 | "no-shadow-restricted-names": "error", 133 | "no-sparse-arrays": "error", 134 | "no-tabs": "error", 135 | "no-throw-literal": "error", 136 | "no-undef": ["error", { typeof: true }], 137 | "no-unexpected-multiline": "error", 138 | "no-unmodified-loop-condition": "error", 139 | "no-unneeded-ternary": "error", 140 | "no-unreachable": "error", 141 | "no-unsafe-finally": "error", 142 | "no-unsafe-negation": ["error", { enforceForOrderingRelations: true }], 143 | "no-unused-expressions": "error", 144 | "no-unused-labels": "error", 145 | "no-unused-vars": [ 146 | "error", 147 | { 148 | args: "all", 149 | argsIgnorePattern: "^_(?:[^_].*)?$", 150 | caughtErrors: "all", 151 | vars: "all", 152 | varsIgnorePattern: "^_(?:[^_].*)?$", 153 | }, 154 | ], 155 | "no-use-before-define": ["error", "nofunc"], 156 | "no-useless-call": "error", 157 | "no-useless-catch": "error", 158 | "no-useless-concat": "error", 159 | "no-useless-escape": "error", 160 | "no-useless-return": "error", 161 | "no-void": "error", 162 | "no-with": "error", 163 | "one-var": [ 164 | "error", 165 | { 166 | initialized: "never", 167 | uninitialized: "always", 168 | }, 169 | ], 170 | "padding-line-between-statements": [ 171 | "error", 172 | { blankLine: "always", next: "*", prev: "directive" }, 173 | { blankLine: "always", next: "function", prev: "*" }, 174 | { blankLine: "always", next: "*", prev: "function" }, 175 | ], 176 | "prefer-promise-reject-errors": "error", 177 | "prefer-regex-literals": "error", 178 | quotes: ["error", "double", { avoidEscape: true }], 179 | radix: "error", 180 | "require-atomic-updates": "error", 181 | "require-await": "error", 182 | "spaced-comment": [ 183 | "error", 184 | "always", 185 | { 186 | block: { 187 | balanced: true, 188 | markers: [ 189 | "eslint", 190 | "eslint-env", 191 | "eslint-disable", 192 | "eslint-enable", 193 | "exported", 194 | "globals", 195 | "istanbul", 196 | ], 197 | }, 198 | line: { 199 | exceptions: ["-", "="], 200 | markers: [ 201 | "eslint-disable-line", 202 | "eslint-disable-next-line", 203 | "istanbul", 204 | "TODO:", 205 | "FIXME:", 206 | ], 207 | }, 208 | }, 209 | ], 210 | strict: ["error", "global"], 211 | "use-isnan": [ 212 | "error", 213 | { enforceForIndexOf: true, enforceForSwitchCase: true }, 214 | ], 215 | "valid-typeof": ["error", { requireStringLiterals: true }], 216 | yoda: ["error", "never", { exceptRange: true }], 217 | 218 | // Enabled rules as warnings. 219 | complexity: ["warn", { max: 16 }], 220 | "max-nested-callbacks": ["warn", { max: 4 }], 221 | "max-params": ["warn", { max: 8 }], 222 | "no-console": ["warn", { allow: ["assert", "error"] }], 223 | 224 | // Disabled rules as favor of Prettier. 225 | "array-bracket-newline": "off", 226 | "array-bracket-spacing": "off", 227 | "array-element-newline": "off", 228 | "arrow-parens": "off", 229 | "arrow-spacing": "off", 230 | "block-spacing": "off", 231 | "brace-style": "off", 232 | "comma-dangle": "off", 233 | "comma-spacing": "off", 234 | "comma-style": "off", 235 | "computed-property-spacing": "off", 236 | "dot-location": "off", 237 | "eol-last": "off", 238 | "func-call-spacing": "off", 239 | "function-call-argument-newline": "off", 240 | "function-paren-newline": "off", 241 | "generator-star-spacing": "off", 242 | "implicit-arrow-linebreak": "off", 243 | indent: "off", 244 | "jsx-quotes": "off", 245 | "key-spacing": "off", 246 | "keyword-spacing": "off", 247 | "multiline-ternary": "off", 248 | "new-parens": "off", 249 | "newline-per-chained-call": "off", 250 | "no-extra-parens": "off", 251 | "no-extra-semi": "off", 252 | "no-floating-decimal": "off", 253 | "no-mixed-spaces-and-tabs": "off", 254 | "no-multi-spaces": "off", 255 | "no-multiple-empty-lines": "off", 256 | "no-trailing-spaces": "off", 257 | "no-whitespace-before-property": "off", 258 | "nonblock-statement-body-position": "off", 259 | "object-curly-newline": "off", 260 | "object-curly-spacing": "off", 261 | "object-property-newline": "off", 262 | "one-var-declaration-per-line": "off", 263 | "operator-linebreak": "off", 264 | "padded-blocks": "off", 265 | "quote-props": "off", 266 | "rest-spread-spacing": "off", 267 | semi: "off", 268 | "semi-spacing": "off", 269 | "semi-style": "off", 270 | "space-before-blocks": "off", 271 | "space-before-function-paren": "off", 272 | "space-in-parens": "off", 273 | "space-infix-ops": "off", 274 | "space-unary-ops": "off", 275 | "switch-colon-spacing": "off", 276 | "template-curly-spacing": "off", 277 | "template-tag-spacing": "off", 278 | "unicode-bom": "off", 279 | "wrap-iife": "off", 280 | "wrap-regex": "off", 281 | "yield-star-spacing": "off", 282 | 283 | // Disabled rules 284 | "arrow-body-style": "off", 285 | "block-scoped-var": "off", 286 | "callback-return": "off", 287 | "capitalized-comments": "off", 288 | "class-methods-use-this": "off", 289 | "consistent-this": "off", 290 | "constructor-super": "off", 291 | "func-name-matching": "off", 292 | "func-names": "off", 293 | "global-require": "off", 294 | "guard-for-in": "off", 295 | "handle-callback-err": "off", 296 | "id-blacklist": "off", 297 | "id-length": "off", 298 | "id-match": "off", 299 | "line-comment-position": "off", 300 | "lines-around-comment": "off", 301 | "max-classes-per-file": "off", 302 | "max-depth": "off", 303 | "max-len": "off", 304 | "max-lines": "off", 305 | "max-lines-per-function": "off", 306 | "max-statements": "off", 307 | "no-await-in-loop": "off", 308 | "no-bitwise": "off", 309 | "no-buffer-constructor": "off", 310 | "no-class-assign": "off", 311 | "no-confusing-arrow": "off", 312 | "no-const-assign": "off", 313 | "no-continue": "off", 314 | "no-dupe-class-members": "off", 315 | "no-duplicate-imports": "off", 316 | "no-eq-null": "off", 317 | "no-inline-comments": "off", 318 | "no-labels": "off", 319 | "no-magic-numbers": "off", 320 | "no-mixed-requires": "off", 321 | "no-multi-assign": "off", 322 | "no-multi-str": "off", 323 | "no-negated-condition": "off", 324 | "no-nested-ternary": "off", 325 | "no-new-func": "off", 326 | "no-new-symbol": "off", 327 | "no-path-concat": "off", 328 | "no-plusplus": "off", 329 | "no-proto": "off", 330 | "no-restricted-globals": "off", 331 | "no-restricted-imports": "off", 332 | "no-restricted-modules": "off", 333 | "no-restricted-syntax": "off", 334 | "no-sync": "off", 335 | "no-template-curly-in-string": "off", 336 | "no-ternary": "off", 337 | "no-this-before-super": "off", 338 | "no-undef-init": "off", 339 | "no-undefined": "off", 340 | "no-underscore-dangle": "off", 341 | "no-useless-computed-key": "off", 342 | "no-useless-constructor": "off", 343 | "no-useless-rename": "off", 344 | "no-var": "off", 345 | "no-warning-comments": "off", 346 | "object-shorthand": "off", 347 | "operator-assignment": "off", 348 | "prefer-arrow-callback": "off", 349 | "prefer-const": "off", 350 | "prefer-destructuring": "off", 351 | "prefer-named-capture-group": "off", 352 | "prefer-numeric-literals": "off", 353 | "prefer-rest-params": "off", 354 | "prefer-object-spread": "off", 355 | "prefer-spread": "off", 356 | "prefer-template": "off", 357 | "require-yield": "off", 358 | "require-unicode-regexp": "off", 359 | "sort-imports": "off", 360 | "sort-keys": "off", 361 | "sort-vars": "off", 362 | "symbol-description": "off", 363 | "vars-on-top": "off", 364 | 365 | // 366 | // Plugins 367 | // 368 | 369 | // eslint-comments 370 | "@mysticatea/eslint-comments/disable-enable-pair": "error", 371 | "@mysticatea/eslint-comments/no-aggregating-enable": "error", 372 | "@mysticatea/eslint-comments/no-duplicate-disable": "error", 373 | "@mysticatea/eslint-comments/no-restricted-disable": "off", 374 | "@mysticatea/eslint-comments/no-unlimited-disable": "error", 375 | "@mysticatea/eslint-comments/no-unused-disable": "error", 376 | "@mysticatea/eslint-comments/no-unused-enable": "error", 377 | "@mysticatea/eslint-comments/no-use": [ 378 | "error", 379 | { 380 | allow: [ 381 | "eslint-disable", 382 | "eslint-disable-line", 383 | "eslint-disable-next-line", 384 | "eslint-enable", 385 | "eslint-env", 386 | "globals", 387 | ], 388 | }, 389 | ], 390 | 391 | // prettier 392 | "@mysticatea/prettier": [ 393 | "error", 394 | { 395 | tabWidth: 4, 396 | semi: false, 397 | trailingComma: "es5", 398 | }, 399 | { 400 | usePrettierrc: false, 401 | }, 402 | ], 403 | 404 | // my own 405 | "@mysticatea/arrow-parens": "off", 406 | "@mysticatea/block-scoped-var": "error", 407 | "@mysticatea/no-instanceof-array": "error", 408 | "@mysticatea/no-instanceof-wrapper": "error", 409 | "@mysticatea/no-literal-call": "error", 410 | "@mysticatea/no-this-in-static": "off", 411 | "@mysticatea/no-use-ignored-vars": ["error", "^_(?:[^_].*)?$"], 412 | "@mysticatea/no-useless-rest-spread": "off", 413 | "@mysticatea/prefer-for-of": "off", 414 | }, 415 | } 416 | -------------------------------------------------------------------------------- /lib/configs/_browser-globals.js: -------------------------------------------------------------------------------- 1 | // DON'T EDIT THIS FILE WHICH WAS GENERATED BY './scripts/generate-browser-globals.js'. 2 | "use strict" 3 | 4 | module.exports = { 5 | AbortController: "readonly", 6 | AbortSignal: "readonly", 7 | AnalyserNode: "readonly", 8 | Animation: "readonly", 9 | AnimationEffectReadOnly: "readonly", 10 | AnimationEffectTiming: "readonly", 11 | AnimationEffectTimingReadOnly: "readonly", 12 | AnimationEvent: "readonly", 13 | AnimationPlaybackEvent: "readonly", 14 | AnimationTimeline: "readonly", 15 | ApplicationCache: "readonly", 16 | ApplicationCacheErrorEvent: "readonly", 17 | Attr: "readonly", 18 | Audio: "readonly", 19 | AudioBuffer: "readonly", 20 | AudioBufferSourceNode: "readonly", 21 | AudioContext: "readonly", 22 | AudioDestinationNode: "readonly", 23 | AudioListener: "readonly", 24 | AudioNode: "readonly", 25 | AudioParam: "readonly", 26 | AudioProcessingEvent: "readonly", 27 | AudioScheduledSourceNode: "readonly", 28 | "AudioWorkletGlobalScope ": "readonly", 29 | AudioWorkletNode: "readonly", 30 | AudioWorkletProcessor: "readonly", 31 | BarProp: "readonly", 32 | BaseAudioContext: "readonly", 33 | BatteryManager: "readonly", 34 | BeforeUnloadEvent: "readonly", 35 | BiquadFilterNode: "readonly", 36 | Blob: "readonly", 37 | BlobEvent: "readonly", 38 | BroadcastChannel: "readonly", 39 | BudgetService: "readonly", 40 | ByteLengthQueuingStrategy: "readonly", 41 | CSS: "readonly", 42 | CSSConditionRule: "readonly", 43 | CSSFontFaceRule: "readonly", 44 | CSSGroupingRule: "readonly", 45 | CSSImportRule: "readonly", 46 | CSSKeyframeRule: "readonly", 47 | CSSKeyframesRule: "readonly", 48 | CSSMediaRule: "readonly", 49 | CSSNamespaceRule: "readonly", 50 | CSSPageRule: "readonly", 51 | CSSRule: "readonly", 52 | CSSRuleList: "readonly", 53 | CSSStyleDeclaration: "readonly", 54 | CSSStyleRule: "readonly", 55 | CSSStyleSheet: "readonly", 56 | CSSSupportsRule: "readonly", 57 | Cache: "readonly", 58 | CacheStorage: "readonly", 59 | CanvasCaptureMediaStreamTrack: "readonly", 60 | CanvasGradient: "readonly", 61 | CanvasPattern: "readonly", 62 | CanvasRenderingContext2D: "readonly", 63 | ChannelMergerNode: "readonly", 64 | ChannelSplitterNode: "readonly", 65 | CharacterData: "readonly", 66 | ClipboardEvent: "readonly", 67 | CloseEvent: "readonly", 68 | Comment: "readonly", 69 | CompositionEvent: "readonly", 70 | ConstantSourceNode: "readonly", 71 | ConvolverNode: "readonly", 72 | CountQueuingStrategy: "readonly", 73 | Credential: "readonly", 74 | CredentialsContainer: "readonly", 75 | Crypto: "readonly", 76 | CryptoKey: "readonly", 77 | CustomElementRegistry: "readonly", 78 | CustomEvent: "readonly", 79 | DOMError: "readonly", 80 | DOMException: "readonly", 81 | DOMImplementation: "readonly", 82 | DOMMatrix: "readonly", 83 | DOMMatrixReadOnly: "readonly", 84 | DOMParser: "readonly", 85 | DOMPoint: "readonly", 86 | DOMPointReadOnly: "readonly", 87 | DOMQuad: "readonly", 88 | DOMRect: "readonly", 89 | DOMRectReadOnly: "readonly", 90 | DOMStringList: "readonly", 91 | DOMStringMap: "readonly", 92 | DOMTokenList: "readonly", 93 | DataTransfer: "readonly", 94 | DataTransferItem: "readonly", 95 | DataTransferItemList: "readonly", 96 | DelayNode: "readonly", 97 | DeviceMotionEvent: "readonly", 98 | DeviceOrientationEvent: "readonly", 99 | Document: "readonly", 100 | DocumentFragment: "readonly", 101 | DocumentType: "readonly", 102 | DragEvent: "readonly", 103 | DynamicsCompressorNode: "readonly", 104 | Element: "readonly", 105 | ErrorEvent: "readonly", 106 | Event: "readonly", 107 | EventSource: "readonly", 108 | EventTarget: "readonly", 109 | File: "readonly", 110 | FileList: "readonly", 111 | FileReader: "readonly", 112 | FocusEvent: "readonly", 113 | FontFace: "readonly", 114 | FontFaceSetLoadEvent: "readonly", 115 | FormData: "readonly", 116 | GainNode: "readonly", 117 | Gamepad: "readonly", 118 | GamepadButton: "readonly", 119 | GamepadEvent: "readonly", 120 | HTMLAllCollection: "readonly", 121 | HTMLAnchorElement: "readonly", 122 | HTMLAreaElement: "readonly", 123 | HTMLAudioElement: "readonly", 124 | HTMLBRElement: "readonly", 125 | HTMLBaseElement: "readonly", 126 | HTMLBodyElement: "readonly", 127 | HTMLButtonElement: "readonly", 128 | HTMLCanvasElement: "readonly", 129 | HTMLCollection: "readonly", 130 | HTMLContentElement: "readonly", 131 | HTMLDListElement: "readonly", 132 | HTMLDataElement: "readonly", 133 | HTMLDataListElement: "readonly", 134 | HTMLDetailsElement: "readonly", 135 | HTMLDialogElement: "readonly", 136 | HTMLDirectoryElement: "readonly", 137 | HTMLDivElement: "readonly", 138 | HTMLDocument: "readonly", 139 | HTMLElement: "readonly", 140 | HTMLEmbedElement: "readonly", 141 | HTMLFieldSetElement: "readonly", 142 | HTMLFontElement: "readonly", 143 | HTMLFormControlsCollection: "readonly", 144 | HTMLFormElement: "readonly", 145 | HTMLFrameElement: "readonly", 146 | HTMLFrameSetElement: "readonly", 147 | HTMLHRElement: "readonly", 148 | HTMLHeadElement: "readonly", 149 | HTMLHeadingElement: "readonly", 150 | HTMLHtmlElement: "readonly", 151 | HTMLIFrameElement: "readonly", 152 | HTMLImageElement: "readonly", 153 | HTMLInputElement: "readonly", 154 | HTMLLIElement: "readonly", 155 | HTMLLabelElement: "readonly", 156 | HTMLLegendElement: "readonly", 157 | HTMLLinkElement: "readonly", 158 | HTMLMapElement: "readonly", 159 | HTMLMarqueeElement: "readonly", 160 | HTMLMediaElement: "readonly", 161 | HTMLMenuElement: "readonly", 162 | HTMLMetaElement: "readonly", 163 | HTMLMeterElement: "readonly", 164 | HTMLModElement: "readonly", 165 | HTMLOListElement: "readonly", 166 | HTMLObjectElement: "readonly", 167 | HTMLOptGroupElement: "readonly", 168 | HTMLOptionElement: "readonly", 169 | HTMLOptionsCollection: "readonly", 170 | HTMLOutputElement: "readonly", 171 | HTMLParagraphElement: "readonly", 172 | HTMLParamElement: "readonly", 173 | HTMLPictureElement: "readonly", 174 | HTMLPreElement: "readonly", 175 | HTMLProgressElement: "readonly", 176 | HTMLQuoteElement: "readonly", 177 | HTMLScriptElement: "readonly", 178 | HTMLSelectElement: "readonly", 179 | HTMLShadowElement: "readonly", 180 | HTMLSlotElement: "readonly", 181 | HTMLSourceElement: "readonly", 182 | HTMLSpanElement: "readonly", 183 | HTMLStyleElement: "readonly", 184 | HTMLTableCaptionElement: "readonly", 185 | HTMLTableCellElement: "readonly", 186 | HTMLTableColElement: "readonly", 187 | HTMLTableElement: "readonly", 188 | HTMLTableRowElement: "readonly", 189 | HTMLTableSectionElement: "readonly", 190 | HTMLTemplateElement: "readonly", 191 | HTMLTextAreaElement: "readonly", 192 | HTMLTimeElement: "readonly", 193 | HTMLTitleElement: "readonly", 194 | HTMLTrackElement: "readonly", 195 | HTMLUListElement: "readonly", 196 | HTMLUnknownElement: "readonly", 197 | HTMLVideoElement: "readonly", 198 | HashChangeEvent: "readonly", 199 | Headers: "readonly", 200 | History: "readonly", 201 | IDBCursor: "readonly", 202 | IDBCursorWithValue: "readonly", 203 | IDBDatabase: "readonly", 204 | IDBFactory: "readonly", 205 | IDBIndex: "readonly", 206 | IDBKeyRange: "readonly", 207 | IDBObjectStore: "readonly", 208 | IDBOpenDBRequest: "readonly", 209 | IDBRequest: "readonly", 210 | IDBTransaction: "readonly", 211 | IDBVersionChangeEvent: "readonly", 212 | IIRFilterNode: "readonly", 213 | IdleDeadline: "readonly", 214 | Image: "readonly", 215 | ImageBitmap: "readonly", 216 | ImageBitmapRenderingContext: "readonly", 217 | ImageCapture: "readonly", 218 | ImageData: "readonly", 219 | InputEvent: "readonly", 220 | IntersectionObserver: "readonly", 221 | IntersectionObserverEntry: "readonly", 222 | Intl: "readonly", 223 | KeyboardEvent: "readonly", 224 | KeyframeEffect: "readonly", 225 | KeyframeEffectReadOnly: "readonly", 226 | Location: "readonly", 227 | MIDIAccess: "readonly", 228 | MIDIConnectionEvent: "readonly", 229 | MIDIInput: "readonly", 230 | MIDIInputMap: "readonly", 231 | MIDIMessageEvent: "readonly", 232 | MIDIOutput: "readonly", 233 | MIDIOutputMap: "readonly", 234 | MIDIPort: "readonly", 235 | MediaDeviceInfo: "readonly", 236 | MediaDevices: "readonly", 237 | MediaElementAudioSourceNode: "readonly", 238 | MediaEncryptedEvent: "readonly", 239 | MediaError: "readonly", 240 | MediaKeyMessageEvent: "readonly", 241 | MediaKeySession: "readonly", 242 | MediaKeyStatusMap: "readonly", 243 | MediaKeySystemAccess: "readonly", 244 | MediaList: "readonly", 245 | MediaQueryList: "readonly", 246 | MediaQueryListEvent: "readonly", 247 | MediaRecorder: "readonly", 248 | MediaSettingsRange: "readonly", 249 | MediaSource: "readonly", 250 | MediaStream: "readonly", 251 | MediaStreamAudioDestinationNode: "readonly", 252 | MediaStreamAudioSourceNode: "readonly", 253 | MediaStreamEvent: "readonly", 254 | MediaStreamTrack: "readonly", 255 | MediaStreamTrackEvent: "readonly", 256 | MessageChannel: "readonly", 257 | MessageEvent: "readonly", 258 | MessagePort: "readonly", 259 | MimeType: "readonly", 260 | MimeTypeArray: "readonly", 261 | MouseEvent: "readonly", 262 | MutationEvent: "readonly", 263 | MutationObserver: "readonly", 264 | MutationRecord: "readonly", 265 | NamedNodeMap: "readonly", 266 | NavigationPreloadManager: "readonly", 267 | Navigator: "readonly", 268 | NetworkInformation: "readonly", 269 | Node: "readonly", 270 | NodeFilter: "readonly", 271 | NodeIterator: "readonly", 272 | NodeList: "readonly", 273 | Notification: "readonly", 274 | OfflineAudioCompletionEvent: "readonly", 275 | OfflineAudioContext: "readonly", 276 | OffscreenCanvas: "writable", 277 | Option: "readonly", 278 | OscillatorNode: "readonly", 279 | PageTransitionEvent: "readonly", 280 | PannerNode: "readonly", 281 | Path2D: "readonly", 282 | PaymentAddress: "readonly", 283 | PaymentRequest: "readonly", 284 | PaymentRequestUpdateEvent: "readonly", 285 | PaymentResponse: "readonly", 286 | Performance: "readonly", 287 | PerformanceEntry: "readonly", 288 | PerformanceLongTaskTiming: "readonly", 289 | PerformanceMark: "readonly", 290 | PerformanceMeasure: "readonly", 291 | PerformanceNavigation: "readonly", 292 | PerformanceNavigationTiming: "readonly", 293 | PerformanceObserver: "readonly", 294 | PerformanceObserverEntryList: "readonly", 295 | PerformancePaintTiming: "readonly", 296 | PerformanceResourceTiming: "readonly", 297 | PerformanceTiming: "readonly", 298 | PeriodicWave: "readonly", 299 | PermissionStatus: "readonly", 300 | Permissions: "readonly", 301 | PhotoCapabilities: "readonly", 302 | Plugin: "readonly", 303 | PluginArray: "readonly", 304 | PointerEvent: "readonly", 305 | PopStateEvent: "readonly", 306 | Presentation: "readonly", 307 | PresentationAvailability: "readonly", 308 | PresentationConnection: "readonly", 309 | PresentationConnectionAvailableEvent: "readonly", 310 | PresentationConnectionCloseEvent: "readonly", 311 | PresentationConnectionList: "readonly", 312 | PresentationReceiver: "readonly", 313 | PresentationRequest: "readonly", 314 | ProcessingInstruction: "readonly", 315 | ProgressEvent: "readonly", 316 | PromiseRejectionEvent: "readonly", 317 | PushManager: "readonly", 318 | PushSubscription: "readonly", 319 | PushSubscriptionOptions: "readonly", 320 | RTCCertificate: "readonly", 321 | RTCDataChannel: "readonly", 322 | RTCDataChannelEvent: "readonly", 323 | RTCDtlsTransport: "readonly", 324 | RTCIceCandidate: "readonly", 325 | RTCIceGatherer: "readonly", 326 | RTCIceTransport: "readonly", 327 | RTCPeerConnection: "readonly", 328 | RTCPeerConnectionIceEvent: "readonly", 329 | RTCRtpContributingSource: "readonly", 330 | RTCRtpReceiver: "readonly", 331 | RTCRtpSender: "readonly", 332 | RTCSctpTransport: "readonly", 333 | RTCSessionDescription: "readonly", 334 | RTCStatsReport: "readonly", 335 | RTCTrackEvent: "readonly", 336 | RadioNodeList: "readonly", 337 | Range: "readonly", 338 | ReadableStream: "readonly", 339 | RemotePlayback: "readonly", 340 | Request: "readonly", 341 | ResizeObserver: "readonly", 342 | ResizeObserverEntry: "readonly", 343 | Response: "readonly", 344 | SVGAElement: "readonly", 345 | SVGAngle: "readonly", 346 | SVGAnimateElement: "readonly", 347 | SVGAnimateMotionElement: "readonly", 348 | SVGAnimateTransformElement: "readonly", 349 | SVGAnimatedAngle: "readonly", 350 | SVGAnimatedBoolean: "readonly", 351 | SVGAnimatedEnumeration: "readonly", 352 | SVGAnimatedInteger: "readonly", 353 | SVGAnimatedLength: "readonly", 354 | SVGAnimatedLengthList: "readonly", 355 | SVGAnimatedNumber: "readonly", 356 | SVGAnimatedNumberList: "readonly", 357 | SVGAnimatedPreserveAspectRatio: "readonly", 358 | SVGAnimatedRect: "readonly", 359 | SVGAnimatedString: "readonly", 360 | SVGAnimatedTransformList: "readonly", 361 | SVGAnimationElement: "readonly", 362 | SVGCircleElement: "readonly", 363 | SVGClipPathElement: "readonly", 364 | SVGComponentTransferFunctionElement: "readonly", 365 | SVGDefsElement: "readonly", 366 | SVGDescElement: "readonly", 367 | SVGDiscardElement: "readonly", 368 | SVGElement: "readonly", 369 | SVGEllipseElement: "readonly", 370 | SVGFEBlendElement: "readonly", 371 | SVGFEColorMatrixElement: "readonly", 372 | SVGFEComponentTransferElement: "readonly", 373 | SVGFECompositeElement: "readonly", 374 | SVGFEConvolveMatrixElement: "readonly", 375 | SVGFEDiffuseLightingElement: "readonly", 376 | SVGFEDisplacementMapElement: "readonly", 377 | SVGFEDistantLightElement: "readonly", 378 | SVGFEDropShadowElement: "readonly", 379 | SVGFEFloodElement: "readonly", 380 | SVGFEFuncAElement: "readonly", 381 | SVGFEFuncBElement: "readonly", 382 | SVGFEFuncGElement: "readonly", 383 | SVGFEFuncRElement: "readonly", 384 | SVGFEGaussianBlurElement: "readonly", 385 | SVGFEImageElement: "readonly", 386 | SVGFEMergeElement: "readonly", 387 | SVGFEMergeNodeElement: "readonly", 388 | SVGFEMorphologyElement: "readonly", 389 | SVGFEOffsetElement: "readonly", 390 | SVGFEPointLightElement: "readonly", 391 | SVGFESpecularLightingElement: "readonly", 392 | SVGFESpotLightElement: "readonly", 393 | SVGFETileElement: "readonly", 394 | SVGFETurbulenceElement: "readonly", 395 | SVGFilterElement: "readonly", 396 | SVGForeignObjectElement: "readonly", 397 | SVGGElement: "readonly", 398 | SVGGeometryElement: "readonly", 399 | SVGGradientElement: "readonly", 400 | SVGGraphicsElement: "readonly", 401 | SVGImageElement: "readonly", 402 | SVGLength: "readonly", 403 | SVGLengthList: "readonly", 404 | SVGLineElement: "readonly", 405 | SVGLinearGradientElement: "readonly", 406 | SVGMPathElement: "readonly", 407 | SVGMarkerElement: "readonly", 408 | SVGMaskElement: "readonly", 409 | SVGMatrix: "readonly", 410 | SVGMetadataElement: "readonly", 411 | SVGNumber: "readonly", 412 | SVGNumberList: "readonly", 413 | SVGPathElement: "readonly", 414 | SVGPatternElement: "readonly", 415 | SVGPoint: "readonly", 416 | SVGPointList: "readonly", 417 | SVGPolygonElement: "readonly", 418 | SVGPolylineElement: "readonly", 419 | SVGPreserveAspectRatio: "readonly", 420 | SVGRadialGradientElement: "readonly", 421 | SVGRect: "readonly", 422 | SVGRectElement: "readonly", 423 | SVGSVGElement: "readonly", 424 | SVGScriptElement: "readonly", 425 | SVGSetElement: "readonly", 426 | SVGStopElement: "readonly", 427 | SVGStringList: "readonly", 428 | SVGStyleElement: "readonly", 429 | SVGSwitchElement: "readonly", 430 | SVGSymbolElement: "readonly", 431 | SVGTSpanElement: "readonly", 432 | SVGTextContentElement: "readonly", 433 | SVGTextElement: "readonly", 434 | SVGTextPathElement: "readonly", 435 | SVGTextPositioningElement: "readonly", 436 | SVGTitleElement: "readonly", 437 | SVGTransform: "readonly", 438 | SVGTransformList: "readonly", 439 | SVGUnitTypes: "readonly", 440 | SVGUseElement: "readonly", 441 | SVGViewElement: "readonly", 442 | Screen: "readonly", 443 | ScreenOrientation: "readonly", 444 | ScriptProcessorNode: "readonly", 445 | SecurityPolicyViolationEvent: "readonly", 446 | Selection: "readonly", 447 | ServiceWorker: "readonly", 448 | ServiceWorkerContainer: "readonly", 449 | ServiceWorkerRegistration: "readonly", 450 | ShadowRoot: "readonly", 451 | SharedWorker: "readonly", 452 | SourceBuffer: "readonly", 453 | SourceBufferList: "readonly", 454 | SpeechSynthesisEvent: "readonly", 455 | SpeechSynthesisUtterance: "readonly", 456 | StaticRange: "readonly", 457 | StereoPannerNode: "readonly", 458 | Storage: "readonly", 459 | StorageEvent: "readonly", 460 | StorageManager: "readonly", 461 | StyleSheet: "readonly", 462 | StyleSheetList: "readonly", 463 | SubtleCrypto: "readonly", 464 | TaskAttributionTiming: "readonly", 465 | Text: "readonly", 466 | TextDecoder: "readonly", 467 | TextEncoder: "readonly", 468 | TextEvent: "readonly", 469 | TextMetrics: "readonly", 470 | TextTrack: "readonly", 471 | TextTrackCue: "readonly", 472 | TextTrackCueList: "readonly", 473 | TextTrackList: "readonly", 474 | TimeRanges: "readonly", 475 | Touch: "readonly", 476 | TouchEvent: "readonly", 477 | TouchList: "readonly", 478 | TrackEvent: "readonly", 479 | TransitionEvent: "readonly", 480 | TreeWalker: "readonly", 481 | UIEvent: "readonly", 482 | URL: "readonly", 483 | URLSearchParams: "readonly", 484 | VTTCue: "readonly", 485 | ValidityState: "readonly", 486 | VisualViewport: "readonly", 487 | WaveShaperNode: "readonly", 488 | WebAssembly: "readonly", 489 | WebGL2RenderingContext: "readonly", 490 | WebGLActiveInfo: "readonly", 491 | WebGLBuffer: "readonly", 492 | WebGLContextEvent: "readonly", 493 | WebGLFramebuffer: "readonly", 494 | WebGLProgram: "readonly", 495 | WebGLQuery: "readonly", 496 | WebGLRenderbuffer: "readonly", 497 | WebGLRenderingContext: "readonly", 498 | WebGLSampler: "readonly", 499 | WebGLShader: "readonly", 500 | WebGLShaderPrecisionFormat: "readonly", 501 | WebGLSync: "readonly", 502 | WebGLTexture: "readonly", 503 | WebGLTransformFeedback: "readonly", 504 | WebGLUniformLocation: "readonly", 505 | WebGLVertexArrayObject: "readonly", 506 | WebSocket: "readonly", 507 | WheelEvent: "readonly", 508 | Window: "readonly", 509 | Worker: "readonly", 510 | WritableStream: "readonly", 511 | XMLDocument: "readonly", 512 | XMLHttpRequest: "readonly", 513 | XMLHttpRequestEventTarget: "readonly", 514 | XMLHttpRequestUpload: "readonly", 515 | XMLSerializer: "readonly", 516 | XPathEvaluator: "readonly", 517 | XPathExpression: "readonly", 518 | XPathResult: "readonly", 519 | XSLTProcessor: "readonly", 520 | atob: "readonly", 521 | btoa: "readonly", 522 | cancelAnimationFrame: "readonly", 523 | document: "readonly", 524 | fetch: "readonly", 525 | indexedDB: "readonly", 526 | localStorage: "readonly", 527 | location: "writable", 528 | matchMedia: "readonly", 529 | navigator: "readonly", 530 | requestAnimationFrame: "readonly", 531 | sessionStorage: "readonly", 532 | window: "readonly", 533 | } 534 | -------------------------------------------------------------------------------- /lib/rules/prefer-for-of.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Toru Nagashima 3 | * @copyright 2016 Toru Nagashima. All rights reserved. 4 | * See LICENSE file in root directory for full license. 5 | */ 6 | "use strict" 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | const assert = require("assert") 13 | 14 | //------------------------------------------------------------------------------ 15 | // Helpers 16 | //------------------------------------------------------------------------------ 17 | 18 | const SENTINEL_TYPE = /(?:Declaration|Statement)$/u 19 | const MESSAGE = "Expected for-of statement." 20 | 21 | /** 22 | * Checks whether the given outer node contains the given inner node. 23 | * 24 | * @param {ASTNode} outerNode - The outer node to check. 25 | * @param {ASTNode} innerNode - The inner node to check. 26 | * @returns {boolean} `true` if the outerNode contains the innerNode. 27 | */ 28 | function contains(outerNode, innerNode) { 29 | return ( 30 | outerNode.range[0] <= innerNode.range[0] && 31 | outerNode.range[1] >= innerNode.range[1] 32 | ) 33 | } 34 | 35 | /** 36 | * Checks whether the given function node is the callback of `Array#forEach` 37 | * which can be replaced by a statement or not. 38 | * 39 | * @param {ASTNode} node - The function node to check. 40 | * @returns {boolean} `true` if the node is the callback of `Array#forEach`. 41 | */ 42 | function isCallbackOfArrayForEach(node) { 43 | const parent = node.parent 44 | 45 | return ( 46 | parent.type === "CallExpression" && 47 | parent.parent.type === "ExpressionStatement" && 48 | parent.callee.type === "MemberExpression" && 49 | parent.callee.property.type === "Identifier" && 50 | parent.callee.property.name === "forEach" && 51 | parent.arguments.length >= 1 && 52 | parent.arguments[0] === node 53 | ) 54 | } 55 | 56 | /** 57 | * Checks whether the given function node has valid parameters to replace by a 58 | * for-of statement. 59 | * 60 | * @param {ASTNode} node - The function node to check. 61 | * @returns {boolean} `true` if the node has valid parameters. 62 | */ 63 | function isValidParams(node) { 64 | return ( 65 | node.params.length === 1 && node.params[0].type !== "AssignmentPattern" 66 | ) 67 | } 68 | 69 | /** 70 | * Checks whether the given node is an identifier or a member expression which 71 | * does not include call expressions. 72 | * 73 | * @param {ASTNode} node - The node to check. 74 | * @returns {boolean} `true` if the node is a simple reference. 75 | */ 76 | function isSimpleReference(node) { 77 | return ( 78 | node.type === "Identifier" || 79 | node.type === "Literal" || 80 | (node.type === "MemberExpression" && 81 | isSimpleReference(node.object) && 82 | isSimpleReference(node.property)) 83 | ) 84 | } 85 | 86 | /** 87 | * Checks whether the given function node is called recursively. 88 | * 89 | * @param {RuleContext} context - The rule context to get variables. 90 | * @param {ASTNode} node - The function node to check. 91 | * @returns {boolean} `true` if the node is called recursively. 92 | */ 93 | function isCalledRecursively(context, node) { 94 | return ( 95 | node.id != null && 96 | context.getDeclaredVariables(node)[0].references.length > 0 97 | ) 98 | } 99 | 100 | /** 101 | * Checks whether the given `for` loop statement is a simple array traversing. 102 | * 103 | * for (let i = 0; i < array.length; ++i) { 104 | * // do something. 105 | * } 106 | * 107 | * @param {ASTNode} node - The `for` loop node to check. 108 | * @returns {boolean} `true` if the node is a simple array traversing. 109 | */ 110 | //eslint-disable-next-line complexity 111 | function isTraversingArray(node) { 112 | const init = node.init 113 | const test = node.test 114 | const update = node.update 115 | let indexDecl = null 116 | let lengthDecl = null 117 | 118 | /*eslint-disable @mysticatea/prettier */ 119 | return ( 120 | init != null && 121 | init.type === "VariableDeclaration" && 122 | init.kind === "let" && 123 | init.declarations.length >= 1 && 124 | (indexDecl = init.declarations[0]) && 125 | indexDecl.id.type === "Identifier" && 126 | indexDecl.init != null && 127 | indexDecl.init.type === "Literal" && 128 | indexDecl.init.value === 0 && 129 | test != null && 130 | test.type === "BinaryExpression" && 131 | test.operator === "<" && 132 | test.left.type === "Identifier" && 133 | test.left.name === indexDecl.id.name && 134 | ( 135 | ( 136 | init.declarations.length === 1 && 137 | test.right.type === "MemberExpression" && 138 | test.right.property.type === "Identifier" && 139 | test.right.property.name === "length" 140 | ) || ( 141 | init.declarations.length === 2 && 142 | (lengthDecl = init.declarations[1]) && 143 | lengthDecl.id.type === "Identifier" && 144 | lengthDecl.init != null && 145 | lengthDecl.init.type === "MemberExpression" && 146 | lengthDecl.init.property.type === "Identifier" && 147 | lengthDecl.init.property.name === "length" && 148 | test.right.type === "Identifier" && 149 | test.right.name === lengthDecl.id.name 150 | ) 151 | ) && 152 | update != null && 153 | ( 154 | ( 155 | update.type === "UpdateExpression" && 156 | update.operator === "++" && 157 | update.argument.type === "Identifier" && 158 | update.argument.name === indexDecl.id.name 159 | ) || ( 160 | update.type === "AssignmentExpression" && 161 | update.operator === "+=" && 162 | update.left.type === "Identifier" && 163 | update.left.name === indexDecl.id.name && 164 | update.right.type === "Literal" && 165 | update.right.value === 1 166 | ) || ( 167 | update.type === "AssignmentExpression" && 168 | update.operator === "=" && 169 | update.left.type === "Identifier" && 170 | update.left.name === indexDecl.id.name && 171 | update.right.type === "BinaryExpression" && 172 | update.right.operator === "+" && 173 | ( 174 | ( 175 | update.right.left.type === "Identifier" && 176 | update.right.left.name === indexDecl.id.name && 177 | update.right.right.type === "Literal" && 178 | update.right.right.value === 1 179 | ) || ( 180 | update.right.left.type === "Literal" && 181 | update.right.left.value === 1 && 182 | update.right.right.type === "Identifier" && 183 | update.right.right.name === indexDecl.id.name 184 | ) 185 | ) 186 | ) 187 | ) 188 | ) 189 | /*eslint-enable @mysticatea/prettier */ 190 | } 191 | 192 | /** 193 | * Gets the iterating array's text of the given `for` loop. 194 | * 195 | * @param {SourceCode} sourceCode - The source code object to get text. 196 | * @param {ASTNode} node - The node of `for` loop statement. 197 | * @returns {string} The iterating array's text of the `for` loop. 198 | */ 199 | function getArrayTextOfForStatement(sourceCode, node) { 200 | return node.init.declarations.length === 2 201 | ? sourceCode.getText(node.init.declarations[1].init.object) 202 | : sourceCode.getText(node.test.right.object) 203 | } 204 | 205 | /** 206 | * Checks whether the given node is in an assignee or not. 207 | * @param {ASTNode} startNode The ndoe to check. 208 | * @returns {boolean} `true` if the node is in an assignee. 209 | */ 210 | function isAssignee(startNode) { 211 | let node = startNode 212 | 213 | while (node && node.parent && !SENTINEL_TYPE.test(node.type)) { 214 | const parent = node.parent 215 | const assignee = 216 | (parent.type === "AssignmentExpression" && parent.left === node) || 217 | (parent.type === "AssignmentPattern" && parent.left === node) || 218 | (parent.type === "VariableDeclarator" && parent.id === node) || 219 | (parent.type === "UpdateExpression" && parent.argument === node) 220 | 221 | if (assignee) { 222 | return true 223 | } 224 | 225 | node = parent 226 | } 227 | 228 | return false 229 | } 230 | 231 | /** 232 | * Checks whether the all references of the index variable are used to get 233 | * array elements. 234 | * 235 | * @param {RuleContext} context - The rule context object. 236 | * @param {ASTNode} node - The `for` loop node which is a simple array 237 | * traversing. 238 | * @returns {boolean} `true` if the the all references of the index variable are 239 | * used to get array elements. 240 | */ 241 | function isIndexVarOnlyUsedToGetArrayElements(context, node) { 242 | const sourceCode = context.getSourceCode() 243 | const arrayText = getArrayTextOfForStatement(sourceCode, node) 244 | const indexVar = context.getDeclaredVariables(node.init)[0] 245 | 246 | return indexVar.references.every(reference => { 247 | const id = reference.identifier 248 | 249 | return ( 250 | !contains(node.body, id) || 251 | (id.parent.type === "MemberExpression" && 252 | id.parent.property === id && 253 | sourceCode.getText(id.parent.object) === arrayText && 254 | !isAssignee(id.parent)) 255 | ) 256 | }) 257 | } 258 | 259 | /** 260 | * Checks whether the all references of the index variable are used to get 261 | * array elements. 262 | * 263 | * @param {RuleContext} context - The rule context object. 264 | * @param {ASTNode} node - The `for` loop node which is a simple array 265 | * traversing. 266 | * @returns {boolean} `true` if the the all references of the index variable are 267 | * used to get array elements. 268 | */ 269 | function isLengthVarOnlyUsedToTest(context, node) { 270 | if (node.init.declarations.length !== 2) { 271 | return true 272 | } 273 | const lengthVar = context.getDeclaredVariables(node.init.declarations[1])[0] 274 | 275 | return lengthVar.references.every( 276 | reference => reference.init || contains(node.test, reference.identifier) 277 | ) 278 | } 279 | 280 | /** 281 | * Gets the variable object of the given name. 282 | * 283 | * @param {RuleContext} context - The rule context to get variables. 284 | * @param {string} name - The variable name to get. 285 | * @returns {escope.Variable|null} The found variable. 286 | */ 287 | function getVariableByName(context, name) { 288 | let scope = context.getScope() 289 | 290 | while (scope != null) { 291 | const variable = scope.set.get(name) 292 | 293 | if (variable != null) { 294 | return variable 295 | } 296 | 297 | scope = scope.upper 298 | } 299 | return null 300 | } 301 | 302 | /** 303 | * Gets the context node, the node which is allocated to `this` of a function, 304 | * of the given function node. 305 | * If the found context node contains CallExpression, this ignores it. 306 | * 307 | * @param {ASTNode} node - The function node to get. 308 | * @returns {ASTNode|null} The found context node. 309 | */ 310 | function getContextNode(node) { 311 | const callNode = node.parent 312 | const contextNode = 313 | callNode.arguments.length >= 2 314 | ? callNode.arguments[1] 315 | : callNode.callee.object 316 | 317 | if (isSimpleReference(contextNode)) { 318 | return contextNode 319 | } 320 | return null 321 | } 322 | 323 | /** 324 | * Gets the variable object of the given context node. 325 | * 326 | * @param {RuleContext} context - The rule context to get variables. 327 | * @param {ASTNode} contextNode - The context node to get. 328 | * @returns {escope.Variable|null} The found variable of the context node. 329 | */ 330 | function getContextVariable(context, contextNode) { 331 | let node = contextNode 332 | while (node.type === "MemberExpression") { 333 | node = node.object 334 | } 335 | assert(node.type === "Identifier") 336 | 337 | const scope = context.getScope().upper 338 | return scope.set.get(node.name) || null 339 | } 340 | 341 | /** 342 | * Gets the 1st statement of the given `for` loop statement if the 1st statement 343 | * is variable declaration for the element variable. 344 | * 345 | * for (let i = 0; i < list.length; ++i) { 346 | * const element = list[i] 347 | * // do something. 348 | * } 349 | * 350 | * @param {SourceCode} sourceCode - The source code object to get the text of 351 | * nodes. 352 | * @param {ASTNode} node - The `for` loop statement to get. 353 | * @returns {ASTNode} The found declaration node. 354 | */ 355 | function getElementVariableDeclaration(sourceCode, node) { 356 | let declaration = null 357 | let declarator = null 358 | const indexText = node.test.left.name 359 | const arrayText = getArrayTextOfForStatement(sourceCode, node) 360 | const isElementVariableDeclaration = 361 | node.body.type === "BlockStatement" && 362 | node.body.body.length > 0 && 363 | (declaration = node.body.body[0]) && 364 | declaration.type === "VariableDeclaration" && 365 | (declarator = declaration.declarations[0]) && 366 | declarator.init.type === "MemberExpression" && 367 | declarator.init.computed && 368 | declarator.init.property.type === "Identifier" && 369 | declarator.init.property.name === indexText && 370 | sourceCode.getText(declarator.init.object) === arrayText 371 | 372 | if (isElementVariableDeclaration) { 373 | return declaration 374 | } 375 | return null 376 | } 377 | 378 | /** 379 | * Converts the given node to a replacement fix object. 380 | * 381 | * @param {string} replaceText - The replacement text. 382 | * @param {number} offset - The offset of node's range. 383 | * @param {ASTNode} node - The node to replace. 384 | * @returns {Fix} The created fix object. 385 | */ 386 | function convertToFix(replaceText, offset, node) { 387 | return { 388 | range: [node.range[0] - offset, node.range[1] - offset], 389 | text: replaceText, 390 | } 391 | } 392 | 393 | /** 394 | * Applies the given fixes to the given text. 395 | * 396 | * @param {string} originalText - The text to fix. 397 | * @param {Fix[]} fixes - The fixes to apply. 398 | * @returns {string} The replaced text. 399 | */ 400 | function applyFixes(originalText, fixes) { 401 | let text = "" 402 | let lastPos = 0 403 | 404 | fixes.sort((a, b) => a.range[0] - b.range[0]) 405 | 406 | for (const fix of fixes) { 407 | assert(fix.range[0] >= lastPos) 408 | 409 | text += originalText.slice(lastPos, fix.range[0]) 410 | text += fix.text 411 | lastPos = fix.range[1] 412 | } 413 | text += originalText.slice(lastPos) 414 | 415 | return text 416 | } 417 | 418 | /** 419 | * Fixes the given `Array#forEach` to for-of statement. 420 | * 421 | * @param {RuleContext} context - The rule context object. 422 | * @param {object} callbackInfo - The information of the callback function of 423 | * `Array#forEach`. 424 | * @param {RuleFixer} fixer - The fixer to fix. 425 | * @returns {Fix|null} The created fix object. 426 | */ 427 | function fixArrayForEach(context, callbackInfo, fixer) { 428 | const sourceCode = context.getSourceCode() 429 | const funcNode = callbackInfo.node 430 | const callNode = funcNode.parent 431 | const calleeNode = callNode.callee 432 | const returnNodes = callbackInfo.returnNodes 433 | const thisNodes = callbackInfo.thisNodes 434 | const contextNode = callbackInfo.contextNode 435 | const canReplaceAllThis = callbackInfo.canReplaceAllThis 436 | 437 | // Not fix if the callee is multiline. 438 | if (calleeNode.loc.start.line !== calleeNode.loc.end.line) { 439 | return null 440 | } 441 | 442 | // Not fix if thisNodes exist and cannot replace those. 443 | if (thisNodes.length > 0 && !canReplaceAllThis) { 444 | return null 445 | } 446 | 447 | const arrayText = sourceCode.getText(calleeNode.object) 448 | const elementText = sourceCode.getText(funcNode.params[0]) 449 | const originalBodyText = sourceCode.getText(funcNode.body) 450 | const contextText = contextNode && sourceCode.getText(contextNode) 451 | const semiText = funcNode.body.type !== "BlockStatement" ? ";" : "" 452 | const bodyOffset = funcNode.body.range[0] 453 | const bodyFixes = [].concat( 454 | returnNodes.map(convertToFix.bind(null, "continue;", bodyOffset)), 455 | thisNodes.map(convertToFix.bind(null, contextText, bodyOffset)) 456 | ) 457 | const bodyText = 458 | bodyFixes.length > 0 459 | ? applyFixes(originalBodyText, bodyFixes) 460 | : originalBodyText 461 | 462 | return fixer.replaceText( 463 | callNode.parent, 464 | `for (let ${elementText} of ${arrayText}) ${bodyText}${semiText}` 465 | ) 466 | } 467 | 468 | /** 469 | * Fixes the given `for` loop statement to for-of statement. 470 | * 471 | * @param {RuleContext} context - The rule context object. 472 | * @param {ASTNode} node - The `for` loop statement to fix. 473 | * @param {RuleFixer} fixer - The fixer to fix. 474 | * @returns {Fix|null} The created fix object. 475 | */ 476 | function fixForStatement(context, node, fixer) { 477 | const sourceCode = context.getSourceCode() 478 | const element = getElementVariableDeclaration(sourceCode, node) 479 | 480 | // Cannot fix if element name is unknown. 481 | if (element == null || !isLengthVarOnlyUsedToTest(context, node)) { 482 | return null 483 | } 484 | 485 | const arrayText = getArrayTextOfForStatement(sourceCode, node) 486 | const elementText = sourceCode.getText(element.declarations[0].id) 487 | 488 | return fixer.replaceTextRange( 489 | [node.range[0], element.range[1]], 490 | `for (let ${elementText} of ${arrayText}) {` 491 | ) 492 | } 493 | 494 | module.exports = { 495 | meta: { 496 | docs: { 497 | description: "requires for-of statements instead of Array#forEach", 498 | category: "Best Practices", 499 | recommended: false, 500 | url: 501 | "https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/prefer-for-of.md", 502 | }, 503 | fixable: "code", 504 | schema: [], 505 | type: "suggestion", 506 | }, 507 | create(context) { 508 | let funcInfo = null 509 | 510 | /** 511 | * Processes to enter a function. 512 | * Push new information object to the function stack. 513 | * 514 | * @param {ASTNode} node - The function node which is entered. 515 | * @returns {void} 516 | */ 517 | function enterFunction(node) { 518 | const isTarget = 519 | isCallbackOfArrayForEach(node) && 520 | isValidParams(node) && 521 | !isCalledRecursively(context, node) 522 | const contextNode = isTarget ? getContextNode(node) : null 523 | const contextVar = 524 | contextNode && getContextVariable(context, contextNode) 525 | 526 | funcInfo = { 527 | upper: funcInfo, 528 | isTarget, 529 | node, 530 | contextNode, 531 | contextVar, 532 | returnNodes: [], 533 | thisNodes: [], 534 | canReplaceAllThis: contextVar != null, 535 | } 536 | } 537 | 538 | /** 539 | * Processes to exit a function. 540 | * Pop the last item of the function stack and report it. 541 | * 542 | * @param {ASTNode} node - The function node which is exited. 543 | * @returns {void} 544 | */ 545 | function exitFunction() { 546 | if (funcInfo.isTarget) { 547 | const expressionStatementNode = funcInfo.node.parent.parent 548 | context.report({ 549 | node: expressionStatementNode, 550 | message: MESSAGE, 551 | fix: fixArrayForEach.bind(null, context, funcInfo), 552 | }) 553 | } 554 | funcInfo = funcInfo.upper 555 | } 556 | 557 | return { 558 | ArrowFunctionExpression: enterFunction, 559 | FunctionExpression: enterFunction, 560 | FunctionDeclaration: enterFunction, 561 | "ArrowFunctionExpression:exit": exitFunction, 562 | "FunctionExpression:exit": exitFunction, 563 | "FunctionDeclaration:exit": exitFunction, 564 | 565 | ReturnStatement(node) { 566 | if (funcInfo != null && funcInfo.isTarget) { 567 | funcInfo.returnNodes.push(node) 568 | } 569 | }, 570 | 571 | ThisExpression(node) { 572 | let thisFuncInfo = funcInfo 573 | while ( 574 | thisFuncInfo != null && 575 | thisFuncInfo.node.type === "ArrowFunctionExpression" 576 | ) { 577 | thisFuncInfo = thisFuncInfo.upper 578 | } 579 | 580 | if ( 581 | thisFuncInfo != null && 582 | thisFuncInfo.isTarget && 583 | !thisFuncInfo.returnNodes.some(returnNode => 584 | contains(returnNode, node) 585 | ) 586 | ) { 587 | thisFuncInfo.thisNodes.push(node) 588 | 589 | // If it replaced this by the context variable name, 590 | // verify whether the reference gets the context variable or not. 591 | if (thisFuncInfo.canReplaceAllThis) { 592 | if (thisFuncInfo.contextVar != null) { 593 | const variable = getVariableByName( 594 | context, 595 | thisFuncInfo.contextVar.name 596 | ) 597 | 598 | thisFuncInfo.canReplaceAllThis = 599 | variable === thisFuncInfo.contextVar 600 | } 601 | } 602 | } 603 | }, 604 | 605 | "ForStatement:exit"(node) { 606 | if ( 607 | isTraversingArray(node) && 608 | isIndexVarOnlyUsedToGetArrayElements(context, node) 609 | ) { 610 | context.report({ 611 | node, 612 | message: MESSAGE, 613 | fix: fixForStatement.bind(null, context, node), 614 | }) 615 | } 616 | }, 617 | 618 | ForInStatement(node) { 619 | context.report({ node, message: MESSAGE }) 620 | }, 621 | } 622 | }, 623 | } 624 | --------------------------------------------------------------------------------