├── .gitignore ├── lib ├── rules │ ├── no-bigint.js │ ├── no-do-expression.js │ ├── no-bind-operator.js │ ├── no-async-iteration.js │ ├── no-optional-catch.js │ ├── no-async-generator.js │ ├── no-pipeline-operator.js │ ├── no-nullish-coalescing.js │ ├── no-dynamic-imports.js │ ├── no-class-static-blocks.js │ ├── no-exponentiation-operator.js │ ├── no-optional-chaining.js │ ├── no-private-class-fields.js │ ├── no-hashbang-comment.js │ ├── no-numeric-separators.js │ ├── no-public-static-class-fields.js │ ├── no-computed-public-class-fields.js │ ├── no-logical-assignment-operator.js │ ├── no-top-level-await.js │ ├── no-public-instance-class-fields.js │ ├── no-object-rest-spread.js │ ├── no-regexp-s-flag.js │ ├── no-regexp-v-flag.js │ ├── no-regexp-named-group.js │ ├── no-regexp-lookbehind.js │ ├── no-regexp-duplicate-named-groups.js │ └── no-edge-destructure-bug.js └── index.js ├── .babelrc.json ├── docs ├── no-hashbang-comment.md ├── no-bigint.md ├── no-numeric-separators.md ├── no-dynamic-imports.md ├── no-regexp-lookbehind.md ├── no-async-iteration.md ├── no-top-level-await.md ├── no-regexp-v-flag.md ├── no-optional-catch.md ├── no-nullish-coalescing.md ├── no-regexp-duplicate-named-groups.md ├── no-logical-assignment-operator.md ├── no-exponentiation-operator.md ├── no-async-generator.md ├── no-public-static-class-fields.md ├── no-regexp-s-flag.md ├── no-private-class-fields.md ├── no-class-static-blocks.md ├── no-public-instance-class-fields.md ├── no-regexp-named-group.md ├── no-optional-chaining.md ├── no-do-expression.md ├── no-bind-operator.md ├── no-pipeline-operator.md ├── no-computed-public-class-fields.md ├── no-object-rest-spread.md └── no-edge-destructure-bug.md ├── .github └── workflows │ ├── test.yml │ └── publish.yml ├── test ├── no-bigint.js ├── no-class-static-blocks.js ├── no-nullish-coalescing.js ├── no-async-iteration.js ├── no-pipeline-operator.js ├── no-hashbang-comment.js ├── no-async-generator.js ├── no-do-expression.js ├── no-optional-chaining.js ├── no-exponentiation-operator.js ├── no-object-rest-spread.js ├── no-optional-catch.js ├── no-top-level-await.js ├── no-logical-assignment-operator.js ├── no-regexp-named-group.js ├── no-bind-operator.js ├── no-regexp-lookbehind.js ├── no-dynamic-imports.js ├── no-regexp-s-flag.js ├── no-regexp-v-flag.js ├── no-numeric-separators.js ├── no-public-static-class-fields.js ├── no-regexp-duplicate-named-groups.js ├── no-private-class-fields.js ├── no-public-instance-class-fields.js ├── no-computed-public-class-fields.js ├── no-edge-destructure-bug.js └── check-rules.js ├── eslint.config.mjs ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /lib/rules/no-bigint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'Literal[bigint]'(node) { 5 | context.report(node, `BigInts are not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-do-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | DoExpression(node) { 5 | context.report(node, `Do Expressions are not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-bind-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | BindExpression(node) { 5 | context.report(node, `The Bind Operator is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-async-iteration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'ForOfStatement[await=true]'(node) { 5 | context.report(node, `Async Iteration is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-optional-catch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'CatchClause:not([param])'(node) { 5 | context.report(node, `Optional Catch Parameters are not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-async-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | ':function[async=true][generator=true]'(node) { 5 | context.report(node, `Async Generators are not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-pipeline-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'BinaryExpression[operator="|>"]'(node) { 5 | context.report(node, `The Pipeline Operator is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-nullish-coalescing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'LogicalExpression[operator="??"]'(node) { 5 | context.report(node, `the Nullish Coalescing Operator is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-dynamic-imports.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'ImportExpression, CallExpression[callee.type="Import"]'(node) { 5 | context.report(node, `Dynamic import is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /lib/rules/no-class-static-blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | StaticBlock(node) { 5 | context.report( 6 | node, 7 | `Class Static Blocks are not supported in ${badBrowser}` 8 | ); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /lib/rules/no-exponentiation-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'AssignmentExpression[operator="**="], BinaryExpression[operator="**"]'(node) { 5 | context.report(node, `Exponentiation Operator is not supported in ${badBrowser}`) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-do-expressions", 8 | "@babel/plugin-proposal-function-bind", 9 | [ "@babel/plugin-proposal-pipeline-operator", { "proposal": "hack", "topicToken": "^^" }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/rules/no-optional-chaining.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | OptionalMemberExpression(node) { 5 | context.report(node, `Optional Chaining is not supported in ${badBrowser}`) 6 | }, 7 | ChainExpression(node) { 8 | context.report(node, `Optional Chaining is not supported in ${badBrowser}`) 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /lib/rules/no-private-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | ClassPrivateProperty(node) { 5 | context.report(node, `Private Class Fields are not supported in ${badBrowser}`) 6 | }, 7 | PrivateIdentifier(node) { 8 | context.report(node, `Private Class Fields are not supported in ${badBrowser}`) 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /docs/no-hashbang-comment.md: -------------------------------------------------------------------------------- 1 | # no-hashbang-comment 2 | 3 | This prevents use of ES2023 hashang comments: 4 | 5 | ```js 6 | #!/usr/bin/env node 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge < 79 12 | - Safari < 13.1 13 | - Firefox < 67 14 | - Chrome < 74 15 | 16 | 17 | ## What is the Fix? 18 | 19 | You have to omit the comment. 20 | -------------------------------------------------------------------------------- /lib/rules/no-hashbang-comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => { 4 | const { sourceCode = context.getSourceCode() } = context; 5 | return { 6 | 'Program:exit' (node) { 7 | const [comment] = sourceCode.getAllComments(); 8 | if (comment && comment.type === 'Shebang') { 9 | context.report(node, `Hashbang comments are not supported in ${badBrowser}`) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/no-bigint.md: -------------------------------------------------------------------------------- 1 | # no-bigint 2 | 3 | This prevents the use of BigInt numeric literals. 4 | 5 | ```js 6 | 0n 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge < 79 12 | - Safari < 14 13 | - Firefox < 68 14 | - Chrome < 67 15 | 16 | 17 | ## What is the Fix? 18 | 19 | There is currently no way to use BigInts while supporting these browsers. Babel does not currently having a transform for BigInt. 20 | -------------------------------------------------------------------------------- /lib/rules/no-numeric-separators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | meta: { 5 | fixable: 'code' 6 | }, 7 | create: (context, badBrowser) => ({ 8 | 'Literal[raw=/_/][value>=0], Literal[raw=/_/][value<=0]'(node) { 9 | context.report({ 10 | node, 11 | message: `Numeric Separators are not supported in ${badBrowser}`, 12 | fix: fixer => fixer.replaceText(node, String(node.value)) 13 | }) 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Use Node.js 20.x 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 20.x 18 | - name: npm install, build, and test 19 | run: | 20 | npm it 21 | env: 22 | CI: true 23 | -------------------------------------------------------------------------------- /lib/rules/no-public-static-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | // Ignore type annotations that don't assign 5 | 'ClassProperty[static=true]:not([typeAnnotation]:not([value]))'(node) { 6 | context.report(node, `Static Class Fields are not supported in ${badBrowser}`) 7 | }, 8 | 'PropertyDefinition[static=true]:not([typeAnnotation]:not([value]))'(node) { 9 | context.report(node, `Static Class Fields are not supported in ${badBrowser}`) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /lib/rules/no-computed-public-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | // Ignore type annotations that don't assign 5 | 'ClassProperty[computed=true]:not([typeAnnotation]:not([value]))'(node) { 6 | context.report(node, `Computed Class Fields are not supported in ${badBrowser}`) 7 | }, 8 | 'PropertyDefinition[computed=true]:not([typeAnnotation]:not([value]))'(node) { 9 | context.report(node, `Computed Class Fields are not supported in ${badBrowser}`) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /docs/no-numeric-separators.md: -------------------------------------------------------------------------------- 1 | # no-numeric-separators 2 | 3 | This prevents the use of separators in Numeric Literals: 4 | 5 | ```js 6 | 1_000_000 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge < 79 12 | - Safari < 13 13 | - Firefox < 68 14 | - Chrome < 75 15 | 16 | 17 | ## What is the Fix? 18 | 19 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-numeric-separator` Babel plugin, or 20 | `@babel/preset-env`. 21 | -------------------------------------------------------------------------------- /docs/no-dynamic-imports.md: -------------------------------------------------------------------------------- 1 | # no-dynamic-imports 2 | 3 | This prevents the use of Dynamic Imports. 4 | 5 | ```js 6 | import('foo').then(...) 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge < 79 12 | - Safari < 11 13 | - Firefox < 67 14 | - Chrome < 63 15 | 16 | 17 | ## What is the Fix? 18 | 19 | This can be safely disabled if you intend to compile with a third party module loader configured to support dynamic imports, such as [Rollup](http://rollupjs.org) or [WebPack](https://webpack.js.org). 20 | -------------------------------------------------------------------------------- /docs/no-regexp-lookbehind.md: -------------------------------------------------------------------------------- 1 | # no-regexp-lookbehind 2 | 3 | This prevents the use of the RegExp lookbehind feature 4 | 5 | ```js 6 | /(?<=a>)b/ 7 | 8 | new RegExp("/(?<=a)b") 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 16.4 15 | - Firefox < 78 16 | - Chrome < 62 17 | 18 | ## What is the Fix? 19 | 20 | You may be able to rewrite your expression using (Negative) Lookaheads, but if not then there is no solution for this, aside from pulling in a custom RegExp library. 21 | -------------------------------------------------------------------------------- /test/no-bigint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-bigint'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2020}}) 7 | 8 | ruleTester.run('no-bigint', rule, { 9 | valid: [ 10 | {code: '0'}, 11 | {code: '1000000'}, 12 | ], 13 | invalid: [ 14 | { 15 | code: '0n', 16 | errors: [ 17 | { 18 | message: 19 | 'BigInts are not supported in undefined' 20 | } 21 | ] 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /docs/no-async-iteration.md: -------------------------------------------------------------------------------- 1 | # no-async-iteration 2 | 3 | This prevents the use of Async Iteration - through `for await (... of ...)` statements: 4 | 5 | ```js 6 | for await (const line of readLines(path)) { 7 | console.log(line) 8 | } 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 12 15 | - Firefox < 57 16 | - Chrome < 63 17 | 18 | ## What is the Fix? 19 | 20 | This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-async-generator-functions` Babel plugin 21 | -------------------------------------------------------------------------------- /lib/rules/no-logical-assignment-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'AssignmentExpression[operator="||="]'(node) { 5 | context.report(node, `Logical assignment operators are not supported in ${badBrowser}`) 6 | }, 7 | 'AssignmentExpression[operator="&&="]'(node) { 8 | context.report(node, `Logical assignment operators are not supported in ${badBrowser}`) 9 | }, 10 | 'AssignmentExpression[operator="??="]'(node) { 11 | context.report(node, `Logical assignment operators are not supported in ${badBrowser}`) 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import globals from "globals"; 3 | 4 | export default [ 5 | eslint.configs.recommended, 6 | { 7 | languageOptions: { 8 | globals: { 9 | ...globals.es2015, 10 | ...globals.node 11 | }, 12 | sourceType: 'script', 13 | ecmaVersion: 2022 14 | } 15 | }, 16 | { 17 | files: ['**/*.mjs'], 18 | languageOptions: { 19 | sourceType: 'module' 20 | } 21 | }, 22 | { 23 | files: ['**/*.js'], 24 | rules: { 25 | strict: ['error', 'global'] 26 | } 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /lib/rules/no-top-level-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const functionTypes = new Set([ 4 | 'FunctionDeclaration', 5 | 'FunctionExpression', 6 | 'ArrowFunctionExpression', 7 | ]); 8 | 9 | module.exports = (context, badBrowser) => ({ 10 | AwaitExpression(node) { 11 | let currentNode = node; 12 | while (currentNode.parent) { 13 | currentNode = currentNode.parent; 14 | if (functionTypes.has(currentNode.type) && currentNode.async) { 15 | return; 16 | } 17 | } 18 | context.report(node, `Top-level await is not supported in ${badBrowser}`) 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /lib/rules/no-public-instance-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | // Ignore type annotations that don't assign 5 | 'ClassProperty[static=false]:not([typeAnnotation]:not([value]))'(node) { 6 | if (node.value === null) return 7 | context.report(node, `Instance Class Fields are not supported in ${badBrowser}`) 8 | }, 9 | 'PropertyDefinition[static=false]:not([typeAnnotation]:not([value]))'(node) { 10 | if (node.value === null) return 11 | context.report(node, `Instance Class Fields are not supported in ${badBrowser}`) 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /test/no-class-static-blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require("../lib/index").rules["no-class-static-blocks"]; 4 | const RuleTester = require("eslint").RuleTester; 5 | 6 | const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022 }}); 7 | 8 | ruleTester.run("no-class-static-blocks", rule, { 9 | valid: [{ code: "class Foo { static x = 1 }" }], 10 | invalid: [ 11 | { 12 | code: "class Foo { static { x = 1 } }", 13 | errors: [ 14 | { 15 | message: "Class Static Blocks are not supported in undefined", 16 | }, 17 | ], 18 | }, 19 | ], 20 | }); 21 | -------------------------------------------------------------------------------- /test/no-nullish-coalescing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-nullish-coalescing'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2020}}) 7 | 8 | ruleTester.run('no-nullish-coalescing', rule, { 9 | valid: [ 10 | {code: 'foo !== undefined && foo !== null ? foo : 1'}, 11 | ], 12 | invalid: [ 13 | { 14 | code: 'foo ?? 1', 15 | errors: [ 16 | { 17 | message: 18 | 'the Nullish Coalescing Operator is not supported in undefined' 19 | } 20 | ] 21 | } 22 | ] 23 | }) 24 | -------------------------------------------------------------------------------- /test/no-async-iteration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-async-iteration'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-async-iteration', rule, { 9 | valid: [ 10 | {code: 'async function foo() { for(const a of b) {} }'}, 11 | ], 12 | invalid: [ 13 | { 14 | code: 'async function foo() { for await(const a of b) {} }', 15 | errors: [ 16 | { 17 | message: 18 | 'Async Iteration is not supported in undefined' 19 | } 20 | ] 21 | } 22 | ] 23 | }) 24 | -------------------------------------------------------------------------------- /test/no-pipeline-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-pipeline-operator'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTester = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | 9 | ruleTester.run('no-pipeline-operator', rule, { 10 | valid: [ 11 | {code: 'bar(foo)'}, 12 | ], 13 | invalid: [ 14 | { 15 | code: 'foo |> bar(^^)', 16 | errors: [ 17 | { 18 | message: 19 | 'The Pipeline Operator is not supported in undefined' 20 | } 21 | ] 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /test/no-hashbang-comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-hashbang-comment'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-hashbang-comment', rule, { 9 | valid: [ 10 | {code: '// Regular comment'}, 11 | {code: '/* Regular comment */'}, 12 | {code: 'noComment;'}, 13 | ], 14 | invalid: [ 15 | { 16 | code: '#!/usr/bin/env node', 17 | errors: [ 18 | { 19 | message: 20 | 'Hashbang comments are not supported in undefined' 21 | } 22 | ] 23 | } 24 | ] 25 | }) 26 | -------------------------------------------------------------------------------- /test/no-async-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-async-generator'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-async-generator', rule, { 9 | valid: [ 10 | {code: 'function*generator(){yield 42;}'}, 11 | {code: 'async function generator(){await 42;}'}, 12 | ], 13 | invalid: [ 14 | { 15 | code: 'async function*generator(){yield 42;}', 16 | errors: [ 17 | { 18 | message: 19 | 'Async Generators are not supported in undefined' 20 | } 21 | ] 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /docs/no-top-level-await.md: -------------------------------------------------------------------------------- 1 | # no-top-level-await 2 | 3 | This prevents the use of `await` at the top-level of documents (outside 4 | of an async function context) 5 | 6 | ```js 7 | await asyncMethod(); 8 | 9 | const results = await getAsyncResults(); 10 | ``` 11 | 12 | These will not be allowed because they are not supported in the following browsers: 13 | 14 | - Edge < 89 15 | - Safari < 15 16 | - Firefox < 89 17 | - Chrome < 89 18 | 19 | 20 | ## What is the Fix? 21 | 22 | You can wrap your `await` in an async Immediately Invoking Function 23 | Expression (IIFE). 24 | 25 | ```js 26 | (async () => { 27 | await asyncMethod(); 28 | 29 | const results = await getAsyncResults(); 30 | })(); 31 | ``` 32 | -------------------------------------------------------------------------------- /lib/rules/no-object-rest-spread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'ObjectExpression > SpreadElement'(node) { 5 | context.report(node, `Object Rest/Spread is not supported in ${badBrowser}`) 6 | }, 7 | 'ObjectPattern > RestElement'(node) { 8 | context.report(node, `Object Rest/Spread is not supported in ${badBrowser}`) 9 | }, 10 | 11 | // Catch older versions of eslint and babel-eslint 12 | ExperimentalRestProperty(node) { 13 | context.report(node, `Object Rest/Spread is not supported in ${badBrowser}`) 14 | }, 15 | ExperimentalSpreadProperty(node) { 16 | context.report(node, `Object Rest/Spread is not supported in ${badBrowser}`) 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /test/no-do-expression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-do-expression'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018, parser: babelEslintParser}}) 8 | 9 | ruleTester.run('no-do-expression', rule, { 10 | valid: [ 11 | {code: '() => { return 1 > 0 }'}, 12 | ], 13 | invalid: [ 14 | { 15 | code: '() => { return do { return 1 > 0 } === true }', 16 | errors: [ 17 | { 18 | message: 19 | 'Do Expressions are not supported in undefined' 20 | } 21 | ] 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /docs/no-regexp-v-flag.md: -------------------------------------------------------------------------------- 1 | # no-regexp-v-flag 2 | 3 | This prevents the use of the `v` flag in RegExps 4 | 5 | ```js 6 | /[\p{Letter}]/v 7 | 8 | new RegExp('[\\p{Letter}]', 'v') 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge > 0 14 | - Safari < 17 15 | - Firefox < 116 16 | - Chrome < 112 17 | 18 | ## What is the Fix? 19 | 20 | You can avoid using the `v` flag by expanding the sets to their unicode ranges: 21 | 22 | ```js 23 | let vFlag = /[\p{ASCII}]/v; 24 | let noVFlag = /[\0-\x7F]/; 25 | ``` 26 | 27 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-unicode-sets-regex` Babel plugin, or `@babel/preset-env`. 28 | -------------------------------------------------------------------------------- /docs/no-optional-catch.md: -------------------------------------------------------------------------------- 1 | # no-optional-catch 2 | 3 | This prevents the use of Optional Catch statements. 4 | 5 | ```js 6 | try { 7 | ... 8 | } catch { // no argument needed 9 | 10 | } 11 | ``` 12 | 13 | These will not be allowed because they are not supported in the following browsers: 14 | 15 | - Edge < 79 16 | - Safari < 11.1 17 | - Firefox < 68 18 | - Chrome < 66 19 | 20 | ## What is the Fix? 21 | 22 | The simplest solution to fix this is to provide the omitted argument: 23 | 24 | ```js 25 | try { 26 | ... 27 | } catch (e) { // add the argument 28 | 29 | } 30 | ``` 31 | 32 | This can be safely disabled if you intend to compile code with the `@babel/plugin-syntax-optional-catch-binding` Babel plugin, or `@babel/preset-env`. 33 | -------------------------------------------------------------------------------- /docs/no-nullish-coalescing.md: -------------------------------------------------------------------------------- 1 | # no-nullish-coalescing 2 | 3 | This prevents the use of the Nullish Coalescing Operator: 4 | 5 | ```js 6 | let foo = null 7 | const baz = foo ?? 1 8 | ``` 9 | 10 | These will not be allowed because they are not supported in the following browsers: 11 | 12 | - Edge < 80 13 | - Safari < 13.1 14 | - Firefox < 72 15 | - Chrome < 80 16 | 17 | ## What is the Fix? 18 | 19 | If the expression is short, you can consider using a ternary operator: 20 | 21 | ```js 22 | // these are all equivalent: 23 | foo ?? bar 24 | foo != null ? foo : bar 25 | foo !== null && foo !== undefined ? foo : bar 26 | ``` 27 | 28 | This can be safely disabled if you intend to compile code with the `@babel/plugin-syntax-nullish-coalescing-operator` Babel plugin. 29 | -------------------------------------------------------------------------------- /docs/no-regexp-duplicate-named-groups.md: -------------------------------------------------------------------------------- 1 | # no-regexp-duplicate-named-groups 2 | 3 | This prevents the use of the RegExp duplicate named groups feature 4 | 5 | ```js 6 | /(?\d{4})-(?\d{2})|(?\d{2})-(?\d{4})/; 7 | 8 | new RegExp('(?\\d{4})-(?\\d{2})|(?\\d{2})-(?\\d{4})') 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 125 14 | - Safari < 17 15 | - Firefox < 129 16 | - Chrome < 125 17 | 18 | 19 | ## What is the Fix? 20 | 21 | You will have to avoid getting the same name out-of-the-box for an 22 | alternative group. 23 | 24 | This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-duplicate-named-capturing-groups-regex` Babel plugin. 25 | -------------------------------------------------------------------------------- /docs/no-logical-assignment-operator.md: -------------------------------------------------------------------------------- 1 | # no-logical-assignment-operator 2 | 3 | This prevents use of the ES2021 Logical Assignment Operator: 4 | 5 | ```js 6 | a &&= b; 7 | 8 | a ||= b; 9 | 10 | a ??= b; 11 | ``` 12 | 13 | These will not be allowed because they are not supported in the following browsers: 14 | 15 | - Edge < 85 16 | - Safari < 14 17 | - Firefox < 79 18 | - Chrome < 85 19 | 20 | ## What is the Fix? 21 | 22 | You can instead safely repeat the variable in using just the logical operator in an assignment. For example the following lines are 23 | equivalent: 24 | 25 | ```js 26 | a = a && b; 27 | 28 | a = a || b; 29 | 30 | a = a ?? b; 31 | ``` 32 | 33 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-logical-assignment-operators` Babel plugin, or `@babel/preset-env`. 34 | -------------------------------------------------------------------------------- /test/no-optional-chaining.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-optional-chaining'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2020}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: '(foo||{}).bar'}, 13 | ], 14 | invalid: [ 15 | { 16 | code: 'foo?.bar', 17 | errors: [ 18 | { 19 | message: 20 | 'Optional Chaining is not supported in undefined' 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | 27 | ruleTester.run('no-optional-chaining', rule, tests) 28 | ruleTesterBabel.run('no-optional-chaining (babel)', rule, tests) 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-npm: 9 | name: Publish to npm 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 20 19 | registry-url: https://registry.npmjs.org/ 20 | cache: npm 21 | - run: npm install -g npm@latest 22 | - run: npm ci 23 | - run: npm test 24 | - run: npm version ${TAG_NAME} --git-tag-version=false 25 | env: 26 | TAG_NAME: ${{ github.event.release.tag_name }} 27 | - run: npm whoami; npm --ignore-scripts publish --provenance --access public 28 | env: 29 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 30 | -------------------------------------------------------------------------------- /docs/no-exponentiation-operator.md: -------------------------------------------------------------------------------- 1 | # no-exponentiation-operator 2 | 3 | This prevents use of the ES2017 Exponentiation Operator: 4 | 5 | ```js 6 | 2 ** 3 === 8 7 | 8 | let x = 2 9 | x **= 3 10 | ``` 11 | 12 | These will not be allowed because they are not supported in the following browsers: 13 | 14 | - Edge < 14 15 | - Safari < 10.1 16 | - Firefox < 52 17 | - Chrome < 52 18 | 19 | ## What is the Fix? 20 | 21 | You can instead safely use `Math.pow(a, b)` where `a` is the left hand operand 22 | and `b` is the right hand operand. For example the following lines are 23 | equivalent: 24 | 25 | ```js 26 | 2 ** 3 === 8 27 | Math.pow(2, 3) === 8 28 | 29 | let x = 2; x **= 3 30 | let x = 2; x = Math.pow(x, 3) 31 | ``` 32 | 33 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-exponentiation-operator` Babel plugin, or `@babel/preset-env`. 34 | -------------------------------------------------------------------------------- /docs/no-async-generator.md: -------------------------------------------------------------------------------- 1 | # no-async-generator 2 | 3 | This prevents the use of Async Generator functions, for example: 4 | 5 | ```js 6 | async function* readLines(path) { 7 | let file = await fileOpen(path); 8 | 9 | try { 10 | while (!file.EOF) { 11 | yield await file.readLine(); 12 | } 13 | } finally { 14 | await file.close(); 15 | } 16 | } 17 | ``` 18 | 19 | These will not be allowed because they are not supported in the following browsers: 20 | 21 | - Edge < 79 22 | - Safari < 12 23 | - Firefox < 57 24 | - Chrome < 63 25 | 26 | 27 | ## What is the Fix? 28 | 29 | You can safely manually create generator functions that yield promises, however an example is out of scope of this document. 30 | 31 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-async-generator-functions` Babel plugin, or `@babel/preset-env`. 32 | -------------------------------------------------------------------------------- /docs/no-public-static-class-fields.md: -------------------------------------------------------------------------------- 1 | # no-public-static-class-fields 2 | 3 | This prevents the use of Static Public Class Fields 4 | 5 | ```js 6 | class Foo { 7 | static bar = 1 8 | } 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 14.1 15 | - Firefox < 75 16 | - Chrome < 72 17 | 18 | 19 | ## What is the Fix? 20 | 21 | You can modify a class using `Object.defineProperty` (or a simple assignment) to add static properties: 22 | 23 | ```js 24 | class Foo {} 25 | Object.defineProperty(Foo, 'bar', { configurable: true, enumerable: true, writable: true, value: 1 }) 26 | ``` 27 | 28 | ```js 29 | class Foo {} 30 | Foo.bar = 1 31 | ``` 32 | 33 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-class-properties` Babel plugin, or 34 | `@babel/preset-env`. 35 | -------------------------------------------------------------------------------- /lib/rules/no-regexp-s-flag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'Literal[regex]'(node) { 5 | if (node.regex.flags.includes('s')) { 6 | context.report(node, `RegExp "s" flag is not supported in ${badBrowser}`) 7 | } 8 | }, 9 | 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { 10 | const [, flags] = node.arguments; 11 | if ( 12 | flags && 13 | ( 14 | ( 15 | flags.type === 'Literal' && 16 | typeof flags.value === 'string' && 17 | flags.value.includes('s') 18 | ) || 19 | ( 20 | flags.type === 'TemplateLiteral' && 21 | flags.quasis.some(({value: {raw}}) => raw.includes('s')) 22 | ) 23 | ) 24 | ) { 25 | context.report(node, `RegExp "s" flag is not supported in ${badBrowser}`) 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /lib/rules/no-regexp-v-flag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (context, badBrowser) => ({ 4 | 'Literal[regex]'(node) { 5 | if (node.regex.flags.includes('v')) { 6 | context.report(node, `RegExp "v" flag is not supported in ${badBrowser}`) 7 | } 8 | }, 9 | 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { 10 | const [, flags] = node.arguments; 11 | if ( 12 | flags && 13 | ( 14 | ( 15 | flags.type === 'Literal' && 16 | typeof flags.value === 'string' && 17 | flags.value.includes('v') 18 | ) || 19 | ( 20 | flags.type === 'TemplateLiteral' && 21 | flags.quasis.some(({value: {raw}}) => raw.includes('v')) 22 | ) 23 | ) 24 | ) { 25 | context.report(node, `RegExp "v" flag is not supported in ${badBrowser}`) 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /docs/no-regexp-s-flag.md: -------------------------------------------------------------------------------- 1 | # no-regexp-s-flag 2 | 3 | This prevents the use of the `s` flag in RegExps 4 | 5 | ```js 6 | /abc/s 7 | 8 | new RegExp('abc', 's') 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 11.1 15 | - Firefox < 78 16 | - Chrome < 62 17 | 18 | 19 | ## What is the Fix? 20 | 21 | The `s` flag is relatively simple sugar for `[\0-\uFFFF]`, so you can simply opt to write the un-sugared syntax: 22 | 23 | ```js 24 | let dotAll = /./s 25 | 26 | let dotAll = /[\0-\uFFFF]/ 27 | ``` 28 | 29 | If you are using it in combination with the `u` flag then you may adjust the pattern: 30 | 31 | ```js 32 | let dotAll = /./su 33 | 34 | let dotAll = /[\0-\u{10FFFF}]/ 35 | ``` 36 | 37 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-dotall-regex` Babel plugin, or `@babel/preset-env`. 38 | -------------------------------------------------------------------------------- /test/no-exponentiation-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-exponentiation-operator'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-exponentiation-operator', rule, { 9 | valid: [ 10 | {code: 'Math.pow(2, 2)'}, 11 | {code: '2 * 2 * 2'}, 12 | {code: 'a = Math.pow(a * 2)'}, 13 | {code: 'a = a * 2 * 2'}, 14 | ], 15 | invalid: [ 16 | { 17 | code: '2 ** 2', 18 | errors: [ 19 | { 20 | message: 21 | 'Exponentiation Operator is not supported in undefined' 22 | } 23 | ] 24 | }, 25 | { 26 | code: 'a **= 2', 27 | errors: [ 28 | { 29 | message: 30 | 'Exponentiation Operator is not supported in undefined' 31 | } 32 | ] 33 | } 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /docs/no-private-class-fields.md: -------------------------------------------------------------------------------- 1 | # no-private-class-fields 2 | 3 | This prevents the use of Private Class Fields 4 | 5 | ```js 6 | class Foo { 7 | static #bar = 1 8 | #bar = 1 9 | 10 | isFoo() { 11 | return #bar === 1 12 | } 13 | } 14 | ``` 15 | 16 | These will not be allowed because they are not supported in the following browsers: 17 | 18 | - Edge < 79 19 | - Safari < 14.1 20 | - Firefox < 90 21 | - Chrome < 74 22 | 23 | 24 | ## What is the Fix? 25 | 26 | Use of a WeakMap will cover most use cases: 27 | 28 | ```js 29 | const fooPrivateState = new WeakMap() 30 | 31 | class Foo { 32 | constructor() { 33 | fooPrivateState.set(this, { bar: 1 }) 34 | } 35 | 36 | isFoo() { 37 | return (fooPrivateState.get(this) || {}).bar === 1 38 | } 39 | } 40 | ``` 41 | 42 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-private-property-in-object` Babel plugin, or 43 | `@babel/preset-env`. 44 | -------------------------------------------------------------------------------- /test/no-object-rest-spread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-object-rest-spread'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2019}}) 7 | 8 | ruleTester.run('no-object-rest-spread', rule, { 9 | valid: [ 10 | {code:'const x = { a, b, c }'}, 11 | {code:'const { a, b, c } = x'}, 12 | {code:'const x = [...[1], ...[2]]'}, 13 | {code:'const [x, ...y] = [1,2,3]'}, 14 | ], 15 | invalid: [ 16 | { 17 | code: 'const x = { ...b }', 18 | errors: [ 19 | { 20 | message: 21 | 'Object Rest/Spread is not supported in undefined' 22 | } 23 | ] 24 | }, 25 | { 26 | code: 'const { ...b } = x', 27 | errors: [ 28 | { 29 | message: 30 | 'Object Rest/Spread is not supported in undefined' 31 | } 32 | ] 33 | } 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /test/no-optional-catch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-optional-catch'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2019}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'try { foo() } catch (error) {}'}, 13 | {code: 'try { foo() } catch (e) {}'}, 14 | {code: 'try { foo() } catch (_) {}'} 15 | ], 16 | invalid: [ 17 | { 18 | code: 'try { foo() } catch {}', 19 | errors: [ 20 | { 21 | message: 22 | 'Optional Catch Parameters are not supported in undefined' 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | 29 | ruleTester.run('no-optional-catch', rule, tests) 30 | ruleTesterBabel.run('no-optional-catch (babel)', rule, tests) 31 | -------------------------------------------------------------------------------- /lib/rules/no-regexp-named-group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hasNamedGroup = s => /\(\?<[_$\w]/.test(s) 4 | 5 | module.exports = (context, badBrowser) => ({ 6 | 'Literal[regex]'(node) { 7 | if (hasNamedGroup(node.regex.pattern)) { 8 | context.report(node, `RegExp named groups are not supported in ${badBrowser}`) 9 | } 10 | }, 11 | 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { 12 | const [source] = node.arguments; 13 | if ( 14 | source && 15 | ( 16 | ( 17 | source.type === 'Literal' && 18 | typeof source.value === 'string' && 19 | hasNamedGroup(source.value) 20 | ) || 21 | ( 22 | source.type === 'TemplateLiteral' && 23 | source.quasis.some(({value: {raw}}) => hasNamedGroup(raw)) 24 | ) 25 | ) 26 | ) { 27 | context.report(node, `RegExp named groups are not supported in ${badBrowser}`) 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /lib/rules/no-regexp-lookbehind.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hasLookbehind = s => s.includes('(?<=') || s.includes('(? ({ 6 | 'Literal[regex]'(node) { 7 | if (hasLookbehind(node.regex.pattern)) { 8 | context.report(node, `RegExp lookbehinds are not supported in ${badBrowser}`) 9 | } 10 | }, 11 | 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { 12 | const [source] = node.arguments; 13 | if ( 14 | source && 15 | ( 16 | ( 17 | source.type === 'Literal' && 18 | typeof source.value === 'string' && 19 | hasLookbehind(source.value) 20 | ) || 21 | ( 22 | source.type === 'TemplateLiteral' && 23 | source.quasis.some(({value: {raw}}) => hasLookbehind(raw)) 24 | ) 25 | ) 26 | ) { 27 | context.report(node, `RegExp lookbehinds are not supported in ${badBrowser}`) 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /docs/no-class-static-blocks.md: -------------------------------------------------------------------------------- 1 | # no-class-static-blocks 2 | 3 | This prevents the use of Static Blocks within Classes, for example: 4 | 5 | ```js 6 | class Foo { 7 | static { 8 | this.x = 1; 9 | } 10 | } 11 | ``` 12 | 13 | These will not be allowed because they are not supported in the following 14 | browsers: 15 | 16 | - Edge < 94 17 | - Safari < 16.4 18 | - Firefox < 93 19 | - Chrome < 94 20 | 21 | ## What is the Fix? 22 | 23 | You can safely use _static fields_, where each property is given the `static` 24 | keyword: 25 | 26 | ```js 27 | class Foo { 28 | static x = 1; 29 | } 30 | ``` 31 | 32 | If you wish to evaluate a field using logic, you can do so with an IIFE: 33 | 34 | ```js 35 | class Foo { 36 | static x = (() => { 37 | if (env == "DEBUG") { 38 | return 4; 39 | } 40 | return Math.random(); 41 | })(); 42 | } 43 | ``` 44 | 45 | This can be safely disabled if you intend to compile code with the 46 | `@babel/plugin-transform-class-static-block` Babel plugin, or 47 | `@babel/preset-env`. 48 | -------------------------------------------------------------------------------- /docs/no-public-instance-class-fields.md: -------------------------------------------------------------------------------- 1 | # no-public-instance-class-fields 2 | 3 | This prevents the use of Instance Public Class Fields 4 | 5 | ```js 6 | class Foo { 7 | bar = 1 8 | } 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 16.0 15 | - Firefox < 69 16 | - Chrome < 72 17 | 18 | 19 | ## What is the Fix? 20 | 21 | You can move these assignments to within the class constructor function, using either `Object.defineProperty` (or a simple assignment) to add instance properties: 22 | 23 | ```js 24 | class Foo { 25 | constructor() { 26 | Object.defineProperty(this, 'bar', { configurable: true, enumerable: true, writable: true, value: 1 }) 27 | } 28 | } 29 | ``` 30 | 31 | ```js 32 | class Foo { 33 | constructor() { 34 | this.bar = 1 35 | } 36 | } 37 | ``` 38 | 39 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-class-properties` Babel plugin, or 40 | `@babel/preset-env`. 41 | -------------------------------------------------------------------------------- /docs/no-regexp-named-group.md: -------------------------------------------------------------------------------- 1 | # no-regexp-named-group 2 | 3 | This prevents the use of the RegExp named groups feature 4 | 5 | ```js 6 | /(?\d{4})-(?\d{2})-(?\d{2})/ 7 | 8 | new RegExp('(?\\d{4})-(?\\d{2})-(?\\d{2})') 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 11.1 15 | - Firefox < 78 16 | - Chrome < 64 17 | 18 | 19 | ## What is the Fix? 20 | 21 | If readability is the main concern, using non-named groups with array-destructuring can be usable without hampering readability too much: 22 | 23 | ```js 24 | // With named: 25 | const {year, month, day} = '2020-01-01'.match(/(?\d{4})-(?\d{2})-(?\d{2})/).groups 26 | 27 | // Without named 28 | const [_, year, month, day] = '2020-01-01'.match(/(\d{4})-(\d{2})-(\d{2})/) || [] 29 | ``` 30 | 31 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-named-capturing-groups-regex` Babel plugin, or `@babel/preset-env`. 32 | -------------------------------------------------------------------------------- /lib/rules/no-regexp-duplicate-named-groups.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hasDuplicateNamedGroups = s => /(\(\?<[_$\w]*?)>.*?\1>/.test(s) 4 | 5 | module.exports = (context, badBrowser) => ({ 6 | 'Literal[regex]'(node) { 7 | if (hasDuplicateNamedGroups(node.regex.pattern)) { 8 | context.report(node, `RegExp duplicate named groups are not supported in ${badBrowser}`) 9 | } 10 | }, 11 | 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { 12 | const [source] = node.arguments; 13 | if ( 14 | source && 15 | ( 16 | ( 17 | source.type === 'Literal' && 18 | typeof source.value === 'string' && 19 | hasDuplicateNamedGroups(source.value) 20 | ) || 21 | ( 22 | source.type === 'TemplateLiteral' && 23 | source.quasis.some(({value: {raw}}) => hasDuplicateNamedGroups(raw)) 24 | ) 25 | ) 26 | ) { 27 | context.report(node, `RegExp duplicate named groups are not supported in ${badBrowser}`) 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /test/no-top-level-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-top-level-await'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2022}}) 7 | 8 | ruleTester.run('no-top-level-await', rule, { 9 | valid: [ 10 | {code: 'async function get () { return await asyncMethod(); }'}, 11 | {code: '(async () => { await getAsyncResults(); })();'}, 12 | {code: 'const get = async function get () { return await asyncMethod(); }'}, 13 | {code: 'const get = async () => { return await asyncMethod(); }'}, 14 | ], 15 | invalid: [ 16 | { 17 | code: 'await asyncMethod();', 18 | errors: [ 19 | { 20 | message: 'Top-level await is not supported in undefined' 21 | } 22 | ] 23 | }, 24 | { 25 | code: 'const results = await getAsyncResults();', 26 | errors: [ 27 | { 28 | message: 'Top-level await is not supported in undefined' 29 | } 30 | ] 31 | }, 32 | ] 33 | }) 34 | -------------------------------------------------------------------------------- /docs/no-optional-chaining.md: -------------------------------------------------------------------------------- 1 | # no-optional-chaining 2 | 3 | This prevents the use of Optional Chaining: 4 | 5 | ```js 6 | const baz = obj?.foo?.bar?.baz; // 42 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge < 80 12 | - Safari < 13.1 13 | - Firefox < 72 14 | - Chrome < 80 15 | 16 | ## What is the Fix? 17 | 18 | If the expression is short, you can consider using a ternary operator: 19 | 20 | ```js 21 | // these are equivalent: 22 | foo?.bar 23 | foo == null ? void 0 : foo.bar; 24 | ``` 25 | 26 | You can also use the Logical OR operator to avoid throwing, although these can look messy: 27 | 28 | ```js 29 | const baz = (((obj || {}).foo || {}).bar || {}).baz 30 | ``` 31 | 32 | Lastly, you could consider using a utility function such as [lodash' `get`](https://lodash.com/docs/4.17.15#get) 33 | 34 | ```js 35 | const baz = _.get(obj, 'foo.bar.baz') 36 | ``` 37 | 38 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-optional-chaining` Babel plugin, or 39 | `@babel/preset-env`. 40 | -------------------------------------------------------------------------------- /test/no-logical-assignment-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-logical-assignment-operator'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2021}}) 7 | 8 | ruleTester.run('no-logical-assignment-operator', rule, { 9 | valid: [ 10 | {code: 'a = a || b'}, 11 | {code: 'a = a && b'}, 12 | {code: 'a = a ?? b'}, 13 | ], 14 | invalid: [ 15 | { 16 | code: 'a ||= b', 17 | errors: [ 18 | { 19 | message: 20 | 'Logical assignment operators are not supported in undefined' 21 | } 22 | ] 23 | }, 24 | { 25 | code: 'a &&= b', 26 | errors: [ 27 | { 28 | message: 29 | 'Logical assignment operators are not supported in undefined' 30 | } 31 | ] 32 | }, 33 | { 34 | code: 'a ??= b', 35 | errors: [ 36 | { 37 | message: 38 | 'Logical assignment operators are not supported in undefined' 39 | } 40 | ] 41 | } 42 | ] 43 | }) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Keith Cirkel 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 | -------------------------------------------------------------------------------- /docs/no-do-expression.md: -------------------------------------------------------------------------------- 1 | # no-do-expression 2 | 3 | This prevents the use of Do Expressions 4 | 5 | ```js 6 | let a = do { 7 | if(x > 10) { 8 | 'big' 9 | } else { 10 | 'small' 11 | } 12 | } 13 | ``` 14 | 15 | These will not be allowed because they are not supported in the following browsers: 16 | 17 | - Edge (any version at the time of writing) 18 | - Safari (any version at the time of writing) 19 | - Firefox (any version at the time of writing) 20 | - Chrome (any version at the time of writing) 21 | 22 | ## What is the Fix? 23 | 24 | If the expression is short, you can consider using a ternary operator: 25 | 26 | ```js 27 | // these are equivalent: 28 | let a = do { 29 | if(x > 10) { 30 | 'big' 31 | } else { 32 | 'small' 33 | } 34 | } 35 | let a = x > 10 ? 'big' : 'small' 36 | ``` 37 | 38 | You can also avoid the use of the do expression, and declare a variable upfront without assigning it immediately: 39 | 40 | ```js 41 | let a 42 | if(x > 10) { 43 | let a = 'big' 44 | } else { 45 | let a = 'small' 46 | } 47 | ``` 48 | 49 | This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-do-expressions` Babel plugin. 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-escompat", 3 | "version": "3.6.0", 4 | "description": "", 5 | "keywords": [], 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/keithamus/eslint-plugin-escompat" 9 | }, 10 | "homepage": "https://github.com/keithamus/eslint-plugin-escompat", 11 | "license": "MIT", 12 | "author": "Keith Cirkel (https://keithcirkel.co.uk/)", 13 | "files": [ 14 | "lib/*" 15 | ], 16 | "main": "lib/index.js", 17 | "scripts": { 18 | "lint": "eslint .", 19 | "test": "npm run lint && mocha" 20 | }, 21 | "devDependencies": { 22 | "@babel/eslint-parser": "^7.24.7", 23 | "@babel/plugin-proposal-do-expressions": "^7.24.7", 24 | "@babel/plugin-proposal-function-bind": "^7.24.7", 25 | "@babel/plugin-proposal-pipeline-operator": "^7.24.7", 26 | "@babel/preset-env": "^7.24.7", 27 | "@babel/preset-typescript": "^7.24.7", 28 | "@eslint/js": "^9.6.0", 29 | "eslint": "^9.6.0", 30 | "globals": "^15.7.0", 31 | "mocha": "^10.5.2" 32 | }, 33 | "peerDependencies": { 34 | "eslint": ">=5.14.1" 35 | }, 36 | "dependencies": { 37 | "browserslist": "^4.23.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/no-bind-operator.md: -------------------------------------------------------------------------------- 1 | # no-bind-operator 2 | 3 | This prevents the use of the Bind operator (`::`) 4 | 5 | ```js 6 | obj::func 7 | ::obj.func 8 | 9 | ::obj.func(val) 10 | ``` 11 | 12 | These will not be allowed because they are not supported in the following browsers: 13 | 14 | - Edge (any version at the time of writing) 15 | - Safari (any version at the time of writing) 16 | - Firefox (any version at the time of writing) 17 | - Chrome (any version at the time of writing) 18 | 19 | ## What is the Fix? 20 | 21 | You can use the `.bind` function prototype method to create a bound function: 22 | 23 | ```js 24 | // these are equivalent: 25 | obj::func 26 | func.bind(obj) 27 | 28 | // these are equivalent: 29 | ::obj.func 30 | obj.func.bind(obj) 31 | ``` 32 | 33 | If you're using the bind operator as a call expression, then you can use the `.call` function prototype method: 34 | 35 | ```js 36 | // these are equivalent: 37 | obj::func(val) 38 | func.call(obj, val) 39 | 40 | // these are equivalent: 41 | ::obj.func(val) 42 | obj.func.call(obj, val) 43 | ``` 44 | 45 | 46 | This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-function-bind` Babel plugin. 47 | -------------------------------------------------------------------------------- /docs/no-pipeline-operator.md: -------------------------------------------------------------------------------- 1 | # no-pipeline-operator 2 | 3 | This prevents the use of the Pipeline Operator 4 | 5 | ```js 6 | const result = "hello" |> capitalize |> exclaim 7 | ``` 8 | 9 | These will not be allowed because they are not supported in the following browsers: 10 | 11 | - Edge (any version at the time of writing) 12 | - Safari (any version at the time of writing) 13 | - Firefox (any version at the time of writing) 14 | - Chrome (any version at the time of writing) 15 | 16 | ## What is the Fix? 17 | 18 | For simple expressions you can wrap each function inside the previous pipeline call: 19 | 20 | ```js 21 | // these are equivalent: 22 | const result = "hello" |> capitalize |> exclaim 23 | const result = exclaim(capitalize("hello")) 24 | ``` 25 | 26 | Lastly, you could consider using a utility function such as [lodash' `_.flow`](https://lodash.com/docs/4.17.15#flow) or [`_.flowRight`](https://lodash.com/docs/4.17.15#flowRight) 27 | 28 | ```js 29 | const result = _.flow(capitalize, exclaim)("hello") 30 | const result = _.flowRight(exclaim, capitalize)("hello") 31 | ``` 32 | 33 | This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-pipeline-operator` Babel plugin. 34 | -------------------------------------------------------------------------------- /docs/no-computed-public-class-fields.md: -------------------------------------------------------------------------------- 1 | # no-computed-public-class-fields 2 | 3 | This prevents the use of computed properties as Class Fields 4 | 5 | ```js 6 | const computed = 'bar' 7 | class Foo { 8 | [computed] = 1 9 | } 10 | 11 | class Foo { 12 | static [computed] = 1 13 | } 14 | ``` 15 | 16 | These will not be allowed because Chrome 72, while supporting class fields, does not support computed properties in class fields. 17 | 18 | ## What is the Fix? 19 | 20 | Either make the property non-computed, or you can move these assignments to within the class constructor function for instance properties, or modify the class for static ones: 21 | 22 | ```js 23 | const computed = 'bar' 24 | class Foo { 25 | constructor() { 26 | Object.defineProperty(this, computed, { configurable: true, enumerable: true, writable: true, value: 1 }) 27 | } 28 | } 29 | 30 | class Foo { 31 | constructor() { 32 | this[computed] = 1 33 | } 34 | } 35 | 36 | // Static 37 | Object.defineProperty(Foo, computed, { configurable: true, enumerable: true, writable: true, value: 1 }) 38 | Foo[computed] = 1 39 | ``` 40 | 41 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-class-properties` Babel plugin. 42 | -------------------------------------------------------------------------------- /test/no-regexp-named-group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-regexp-named-group'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2020}}) 7 | 8 | ruleTester.run('no-regexp-named-group', rule, { 9 | valid: [ 10 | {code: '/(?:a)/'}, 11 | {code: '/(?:a)/g'}, 12 | {code: 'RegExp("(?:a)b")'}, 13 | {code: 'RegExp("(?:a)b", "g")'}, 14 | ], 15 | invalid: [ 16 | { 17 | code: '/(?)/', 18 | errors: [ 19 | { 20 | message: 'RegExp named groups are not supported in undefined' 21 | } 22 | ] 23 | }, 24 | { 25 | code: 'new RegExp("(?)")', 26 | errors: [ 27 | { 28 | message: 'RegExp named groups are not supported in undefined' 29 | } 30 | ] 31 | }, 32 | { 33 | code: '/(?<$name>)/', 34 | errors: [ 35 | { 36 | message: 'RegExp named groups are not supported in undefined' 37 | } 38 | ] 39 | }, 40 | { 41 | code: '/(?<_name>)/', 42 | errors: [ 43 | { 44 | message: 'RegExp named groups are not supported in undefined' 45 | } 46 | ] 47 | }, 48 | ] 49 | }) 50 | -------------------------------------------------------------------------------- /lib/rules/no-edge-destructure-bug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const objectPatternHasDefaults = node => 4 | node.type === 'ObjectPattern' && node.properties.some(prop => prop.value.type === 'AssignmentPattern') 5 | 6 | module.exports = function(context) { 7 | return { 8 | ArrowFunctionExpression(node) { 9 | // Unary functions don't trip on this bug 10 | if (node.params.length < 2) return 11 | 12 | // This bug only occurs when some arguments use Object destructuring 13 | if (!node.params.some(param => param.type === 'ObjectPattern')) return 14 | 15 | const objectPatternArgs = node.params.filter(node => node.type === 'ObjectPattern') 16 | 17 | // This bug is only occurs when an argument uses Object Destructuring with Default assignment 18 | if (!objectPatternArgs.some(objectPatternHasDefaults)) return 19 | 20 | // This bug gets fixed if the first argument uses Object destructuring with default assignments! 21 | if (node.params[0].type === 'ObjectPattern' && objectPatternHasDefaults(node.params[0])) return 22 | 23 | context.report( 24 | node, 25 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 26 | ) 27 | } 28 | } 29 | } 30 | 31 | module.exports.schema = [] 32 | -------------------------------------------------------------------------------- /test/no-bind-operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-bind-operator'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018, parser: babelEslintParser}}) 8 | 9 | ruleTester.run('no-bind-operator', rule, { 10 | valid: [ 11 | {code: 'console.log.bind(console)'}, 12 | {code: 'console.log.call(console)'}, 13 | ], 14 | invalid: [ 15 | { 16 | code: 'console::log', 17 | errors: [ 18 | { 19 | message: 20 | 'The Bind Operator is not supported in undefined' 21 | } 22 | ] 23 | }, 24 | { 25 | code: '::console.log', 26 | errors: [ 27 | { 28 | message: 29 | 'The Bind Operator is not supported in undefined' 30 | } 31 | ] 32 | }, 33 | { 34 | code: 'console::log(1)', 35 | errors: [ 36 | { 37 | message: 38 | 'The Bind Operator is not supported in undefined' 39 | } 40 | ] 41 | }, 42 | { 43 | code: '::console.log(1)', 44 | errors: [ 45 | { 46 | message: 47 | 'The Bind Operator is not supported in undefined' 48 | } 49 | ] 50 | }, 51 | ] 52 | }) 53 | -------------------------------------------------------------------------------- /test/no-regexp-lookbehind.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-regexp-lookbehind'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2020}}) 7 | 8 | ruleTester.run('no-regexp-lookbehind', rule, { 9 | valid: [ 10 | {code: '/(?:a)b/'}, 11 | {code: '/(?:a)b/g'}, 12 | {code: '/(?)/'}, 13 | {code: 'RegExp("(?:a)b", "g")'}, 14 | {code: 'RegExp("(?)")'}, 15 | ], 16 | invalid: [ 17 | { 18 | code: '/(?<=a)b/', 19 | errors: [ 20 | { 21 | message: 'RegExp lookbehinds are not supported in undefined' 22 | } 23 | ] 24 | }, 25 | { 26 | code: 'new RegExp("/(?<=a)b")', 27 | errors: [ 28 | { 29 | message: 'RegExp lookbehinds are not supported in undefined' 30 | } 31 | ] 32 | }, 33 | { 34 | code: '/(?<=a)b/g', 35 | errors: [ 36 | { 37 | message: 'RegExp lookbehinds are not supported in undefined' 38 | } 39 | ] 40 | }, 41 | { 42 | code: 'new RegExp("/(?<=a)b", "g")', 43 | errors: [ 44 | { 45 | message: 'RegExp lookbehinds are not supported in undefined' 46 | } 47 | ] 48 | }, 49 | ] 50 | }) 51 | -------------------------------------------------------------------------------- /test/no-dynamic-imports.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-dynamic-imports'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({ languageOptions: {parser: babelEslintParser} }) 8 | const ruleTester = new RuleTester({languageOptions: {sourceType: 'module', ecmaVersion: 2020}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'import foo from "foo"'}, 13 | {code: 'Import("foo").then'}, 14 | {code: 'System.import("foo").then'}, 15 | ], 16 | invalid: [ 17 | { 18 | code: 'import("foo")', 19 | errors: [ 20 | { 21 | message: 22 | 'Dynamic import is not supported in undefined' 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | 29 | ruleTesterBabel.run('no-dynamic-imports (babel)', rule, { 30 | valid: [ 31 | {code: 'import foo from "foo"'}, 32 | {code: 'Import("foo").then'}, 33 | {code: 'System.import("foo").then'}, 34 | ], 35 | invalid: [ 36 | { 37 | code: 'import("foo")', 38 | errors: [ 39 | { 40 | message: 41 | 'Dynamic import is not supported in undefined' 42 | } 43 | ] 44 | } 45 | ] 46 | }) 47 | 48 | ruleTester.run('no-dynamic-imports', rule, tests) 49 | ruleTesterBabel.run('no-dynamic-imports', rule, tests) 50 | -------------------------------------------------------------------------------- /test/no-regexp-s-flag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-regexp-s-flag'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-regexp-s-flag', rule, { 9 | valid: [ 10 | {code: '/foo.bar/'}, 11 | {code: '/foo.bar/g'}, 12 | {code: 'new RegExp("foo.bar")'}, 13 | {code: 'new RegExp("foo.bar", flags)'}, 14 | {code: 'new RegExp("foo.bar", "u")'}, 15 | {code: 'new RegExp("foo.bar", "g")'}, 16 | {code: 'RegExp("foo.bar", "g")'}, 17 | ], 18 | invalid: [ 19 | { 20 | code: '/foo.bar/s', 21 | errors: [ 22 | { 23 | message: 'RegExp "s" flag is not supported in undefined' 24 | } 25 | ] 26 | }, 27 | { 28 | code: 'new RegExp("foo.bar", "s")', 29 | errors: [ 30 | { 31 | message: 'RegExp "s" flag is not supported in undefined' 32 | } 33 | ] 34 | }, 35 | { 36 | code: 'new RegExp("foo.bar", `s`)', 37 | errors: [ 38 | { 39 | message: 'RegExp "s" flag is not supported in undefined' 40 | } 41 | ] 42 | }, 43 | { 44 | code: 'RegExp("foo.bar", "s")', 45 | errors: [ 46 | { 47 | message: 'RegExp "s" flag is not supported in undefined' 48 | } 49 | ] 50 | }, 51 | ] 52 | }) 53 | -------------------------------------------------------------------------------- /test/no-regexp-v-flag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-regexp-v-flag'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2024}}) 7 | 8 | ruleTester.run('no-regexp-v-flag', rule, { 9 | valid: [ 10 | {code: '/foo.bar/'}, 11 | {code: '/foo.bar/g'}, 12 | {code: 'new RegExp("foo.bar")'}, 13 | {code: 'new RegExp("foo.bar", flags)'}, 14 | {code: 'new RegExp("foo.bar", "u")'}, 15 | {code: 'new RegExp("foo.bar", "g")'}, 16 | {code: 'RegExp("foo.bar", "g")'}, 17 | ], 18 | invalid: [ 19 | { 20 | code: '/foo.bar/v', 21 | errors: [ 22 | { 23 | message: 'RegExp "v" flag is not supported in undefined' 24 | } 25 | ] 26 | }, 27 | { 28 | code: 'new RegExp("foo.bar", "v")', 29 | errors: [ 30 | { 31 | message: 'RegExp "v" flag is not supported in undefined' 32 | } 33 | ] 34 | }, 35 | { 36 | code: 'new RegExp("foo.bar", `v`)', 37 | errors: [ 38 | { 39 | message: 'RegExp "v" flag is not supported in undefined' 40 | } 41 | ] 42 | }, 43 | { 44 | code: 'RegExp("foo.bar", "v")', 45 | errors: [ 46 | { 47 | message: 'RegExp "v" flag is not supported in undefined' 48 | } 49 | ] 50 | }, 51 | ] 52 | }) 53 | -------------------------------------------------------------------------------- /test/no-numeric-separators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-numeric-separators'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({ languageOptions: {parser: babelEslintParser} }) 8 | const ruleTester = new RuleTester({languageOptions: {sourceType: 'module', ecmaVersion: 2021}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: '100000000'}, 13 | {code: '1.00000000'}, 14 | {code: '1e8'}, 15 | {code: '"1_000_000"'}, 16 | {code: '0'}, 17 | ], 18 | invalid: [ 19 | { 20 | code: '100_000_000', 21 | output: '100000000', 22 | errors: [ 23 | { 24 | message: 25 | 'Numeric Separators are not supported in undefined' 26 | } 27 | ] 28 | }, 29 | { 30 | code: '1_000_000', 31 | output: '1000000', 32 | errors: [ 33 | { 34 | message: 35 | 'Numeric Separators are not supported in undefined' 36 | } 37 | ] 38 | }, 39 | { 40 | code: '100_0', 41 | output: '1000', 42 | errors: [ 43 | { 44 | message: 45 | 'Numeric Separators are not supported in undefined' 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | 52 | ruleTester.run('no-numeric-separators (babel)', rule, tests) 53 | ruleTesterBabel.run('no-numeric-separators (babel)', rule, tests) 54 | -------------------------------------------------------------------------------- /test/no-public-static-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-public-static-class-fields'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2022}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'class Foo { bar(){} }'}, 13 | {code: 'class Foo { static bar() {} }'}, 14 | {code: 'class Foo { bar = () => {} }'}, 15 | {code: 'class Foo { bar = 1 }'}, 16 | ], 17 | invalid: [ 18 | { 19 | code: 'class Foo { static bar = () => {} }', 20 | errors: [ 21 | { 22 | message: 23 | 'Static Class Fields are not supported in undefined' 24 | } 25 | ] 26 | }, 27 | { 28 | code: 'class Foo { static bar = 1 }', 29 | errors: [ 30 | { 31 | message: 32 | 'Static Class Fields are not supported in undefined' 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | 39 | ruleTester.run('no-public-static-class-fields', rule, tests) 40 | ruleTesterBabel.run('no-public-static-class-fields', rule, { 41 | valid: [ 42 | ...tests.valid, 43 | // This doesn't catch instance class fields. 44 | // TODO: fixme 45 | // {code: 'class Foo { bar: AType }'}, 46 | ], 47 | invalid: [ 48 | ...tests.invalid 49 | ] 50 | }) 51 | -------------------------------------------------------------------------------- /test/no-regexp-duplicate-named-groups.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-regexp-duplicate-named-groups'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2025}}) 7 | 8 | ruleTester.run('no-regexp-duplicate-named-groups', rule, { 9 | valid: [ 10 | {code: '/(?:a)/'}, 11 | {code: '/(?:a)/g'}, 12 | {code: 'RegExp("(?:a)b")'}, 13 | {code: 'RegExp("(?:a)b", "g")'}, 14 | {code: '/(?a)/'}, 15 | {code: 'RegExp("(?a)")'}, 16 | {code: '/(?a)|(?a)/'}, 17 | ], 18 | invalid: [ 19 | { 20 | code: '/(?a)|(?b)/', 21 | errors: [ 22 | { 23 | message: 'RegExp duplicate named groups are not supported in undefined' 24 | } 25 | ] 26 | }, 27 | { 28 | code: 'new RegExp("(?a)|(?b)")', 29 | errors: [ 30 | { 31 | message: 'RegExp duplicate named groups are not supported in undefined' 32 | } 33 | ] 34 | }, 35 | { 36 | code: '/(?<$name>a)|(?<$name>b)/', 37 | errors: [ 38 | { 39 | message: 'RegExp duplicate named groups are not supported in undefined' 40 | } 41 | ] 42 | }, 43 | { 44 | code: '/(?<_name>)|(?<_name>)/', 45 | errors: [ 46 | { 47 | message: 'RegExp duplicate named groups are not supported in undefined' 48 | } 49 | ] 50 | }, 51 | ] 52 | }) 53 | -------------------------------------------------------------------------------- /test/no-private-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-private-class-fields'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2022}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'class Foo { bar(){} }'}, 13 | {code: 'class Foo { static bar() {} }'}, 14 | ], 15 | invalid: [ 16 | { 17 | code: 'class Foo { #bar = () => {} }', 18 | errors: [ 19 | { 20 | message: 21 | 'Private Class Fields are not supported in undefined' 22 | } 23 | ] 24 | }, 25 | { 26 | code: 'class Foo { #bar = 1 }', 27 | errors: [ 28 | { 29 | message: 30 | 'Private Class Fields are not supported in undefined' 31 | } 32 | ] 33 | }, 34 | { 35 | code: 'class Foo { static #bar = () => {} }', 36 | errors: [ 37 | { 38 | message: 39 | 'Private Class Fields are not supported in undefined' 40 | } 41 | ] 42 | }, 43 | { 44 | code: 'class Foo { static #bar = 1 }', 45 | errors: [ 46 | { 47 | message: 48 | 'Private Class Fields are not supported in undefined' 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | 55 | ruleTester.run('no-private-class-fields', rule, tests) 56 | ruleTesterBabel.run('no-private-class-fields (babel)', rule, tests) 57 | -------------------------------------------------------------------------------- /docs/no-object-rest-spread.md: -------------------------------------------------------------------------------- 1 | # no-object-rest-spread 2 | 3 | This prevents the use of Object Rest & Spread patterns, for example: 4 | 5 | ```js 6 | let { a, ...x } = obj 7 | 8 | let other = { a: 1, ...x } 9 | ``` 10 | 11 | These will not be allowed because they are not supported in the following browsers: 12 | 13 | - Edge < 79 14 | - Safari < 11.1 15 | - Firefox < 55 16 | - Chrome < 60 17 | 18 | 19 | ## What is the Fix? 20 | 21 | If you want to use the Spread pattern in Object literals then it is safe to use 22 | `Object.assign` instead. The following lines are equivalent: 23 | 24 | ```js 25 | let other = { a: 1, ...x } 26 | 27 | let other = Object.assign({ a: 1 }, x) 28 | ``` 29 | 30 | If you want to use the Rest pattern in Object Assignment, then the easiest way 31 | is to use `Object.keys(x).reduce`. The following two code samples are equivalent: 32 | 33 | ```js 34 | let { a, ...x } = obj 35 | 36 | let a = obj.a, x = Object.keys(obj).reduce((x, key) => { 37 | if (key !== 'a') x[key] = obj[key] 38 | return x 39 | }, {}) 40 | ``` 41 | 42 | This could lead to a lot of repetitive code, so you could also lift this function into a helper: 43 | 44 | ```js 45 | const without = (source, ...excluded) => Object.keys(obj).reduce((acc, key) => { 46 | if (excluded.indexOf(key) === -1) acc[key] = obj[key] 47 | return acc 48 | }, {}) 49 | 50 | let a = obj.a, x = without(obj, 'a') 51 | ``` 52 | 53 | You can also use Lodash's [`without`](https://lodash.com/docs/4.17.11#without) method, or Ramda's [`omit`](https://ramdajs.com/docs/#omit). 54 | 55 | This can be safely disabled if you intend to compile code with the `@babel/plugin-transform-object-rest-spread` Babel plugin, or `@babel/preset-env`. 56 | -------------------------------------------------------------------------------- /test/no-public-instance-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-public-instance-class-fields'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2022}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'class Foo { bar(){} }'}, 13 | {code: 'class Foo { static bar() {} }'}, 14 | {code: 'class Foo { static bar = () => {} }'}, 15 | {code: 'class Foo { static bar = 1 }'}, 16 | {code: 'class Foo { foo /*: CommentType*/ }'}, 17 | ], 18 | invalid: [ 19 | { 20 | code: 'class Foo { bar = () => {} }', 21 | errors: [ 22 | { 23 | message: 24 | 'Instance Class Fields are not supported in undefined' 25 | } 26 | ] 27 | }, 28 | { 29 | code: 'class Foo { bar = 1 }', 30 | errors: [ 31 | { 32 | message: 33 | 'Instance Class Fields are not supported in undefined' 34 | } 35 | ] 36 | }, 37 | { 38 | code: 'class Foo { bar = null }', 39 | errors: [ 40 | { 41 | message: 42 | 'Instance Class Fields are not supported in undefined' 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | 49 | ruleTester.run('no-public-instance-class-fields', rule, tests) 50 | ruleTesterBabel.run('no-public-instance-class-fields', rule, { 51 | valid: [ 52 | ...tests.valid, 53 | // TODO: fixme 54 | // {code: 'class Foo { bar: AType }'}, 55 | // This doesn't catch static class fields. 56 | // TODO: fixme 57 | // {code: 'class Foo { static bar: AType }'}, 58 | ], 59 | invalid: [ 60 | ...tests.invalid 61 | ] 62 | }) 63 | -------------------------------------------------------------------------------- /test/no-computed-public-class-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-computed-public-class-fields'] 4 | const RuleTester = require('eslint').RuleTester 5 | const babelEslintParser = require('@babel/eslint-parser'); 6 | 7 | const ruleTesterBabel = new RuleTester({languageOptions: {parser: babelEslintParser}}) 8 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2022}}) 9 | 10 | const tests = { 11 | valid: [ 12 | {code: 'class Foo { bar(){} }'}, 13 | {code: 'class Foo { static bar() {} }'}, 14 | {code: 'class Foo { ["bar"]() {} }'}, 15 | {code: 'class Foo { static ["bar"]() {} }'}, 16 | {code: 'class Foo { static bar = () => {} }'}, 17 | {code: 'class Foo { static bar = 1 }'}, 18 | {code: 'class Foo { bar = () => {} }'}, 19 | {code: 'class Foo { bar = 1 }'}, 20 | ], 21 | invalid: [ 22 | { 23 | code: 'class Foo { ["bar"] = () => {} }', 24 | errors: [ 25 | { 26 | message: 27 | 'Computed Class Fields are not supported in undefined' 28 | } 29 | ] 30 | }, 31 | { 32 | code: 'class Foo { ["bar"] = 1 }', 33 | errors: [ 34 | { 35 | message: 36 | 'Computed Class Fields are not supported in undefined' 37 | } 38 | ] 39 | }, 40 | { 41 | code: 'class Foo { static ["bar"] = () => {} }', 42 | errors: [ 43 | { 44 | message: 45 | 'Computed Class Fields are not supported in undefined' 46 | } 47 | ] 48 | }, 49 | { 50 | code: 'class Foo { static ["bar"] = 1 }', 51 | errors: [ 52 | { 53 | message: 54 | 'Computed Class Fields are not supported in undefined' 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | 61 | ruleTester.run('no-computed-public-class-fields', rule, tests) 62 | ruleTesterBabel.run('no-computed-public-class-fields (babel)', rule, tests) 63 | -------------------------------------------------------------------------------- /docs/no-edge-destructure-bug.md: -------------------------------------------------------------------------------- 1 | # no-edge-destructure-bug 2 | 3 | There's an interesting bug within Edge 15-17 where if you use _object destructuring_ with _default assignments_ _only_ on the _second_ argument of an _arrow function_, then you'll get a SyntaxError. In other words: 4 | 5 | - `({a}) => a` ✔️ passes 6 | - `({a}, {b}) => a + b` ✔️ passes 7 | - `({a = 1}, {b}) => a + b` ✔️ passes 8 | - `({a = 1}, {b = 1}) => a + b` ✔️ passes 9 | - `(a, {b}) => a + b` ✔️ passes 10 | - `(a, {b = 1}) => a + b` ❌ SyntaxError 11 | - `([a], {b=1}) => a + b` ❌ SyntaxError 12 | - `([a=1], {b=1}) => a + b` ❌ SyntaxError 13 | - `(a, {b=1} = {}) => a + b` ✔️ passes 14 | - `function (a, {b = 1}) { return a + b }` ✔️ passes 15 | 16 | A fairly exhaustive set of rules to trigger this error: 17 | 18 | - It has to be an arrow function, anonymous `function`s work fine. 19 | - If the first argument is object destructuring with defaults, it fixes the error. 20 | - Subsequent arguments must use defaults, if you're just destructuring without defaults it works fine. 21 | 22 | ## What is the Fix? 23 | 24 | The most straight forward fix is to stop using destructuring with defaults on your second parameter. There are several options to avoid this, going from most straightforward to least straightforward: 25 | 26 | - Use an anonymous function expression, instead of an arrow function. Change `(a, { b = 1 }) => {}` to `function(a, { b = 1}) {}` 27 | - Ensure to assign the destructuring expression to an object: change `(a, { b = 1 }) => {}` to `(a, { b = 1 } = {}) => {}` 28 | - Destructure in the function body: change `(a, { b = 1 }) => {}` to `(a, o) => { let {b = 1} = o }` 29 | - Default the arguments in the function body: change `(a, { b = 1 }) => {}` to `(a, { b }) => { b = b || 1 }` 30 | - If you can, destructure the first argument and use defaults. I'm not going to add an example because this fix will hugely depend on your code - only do this if you're confident enough to do so. 31 | -------------------------------------------------------------------------------- /test/no-edge-destructure-bug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../lib/index').rules['no-edge-destructure-bug'] 4 | const RuleTester = require('eslint').RuleTester 5 | 6 | const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2018}}) 7 | 8 | ruleTester.run('no-edge-destructure-bug', rule, { 9 | valid: [ 10 | {code: '({a}) => a'}, 11 | {code: '({a}, {b}) => a + b'}, 12 | {code: '({a = 1}, {b}) => a + b'}, 13 | {code: '({a = 1}, {b = 1}) => a + b'}, 14 | {code: '({a = 1}, {b = 1}, {c = 1}) => a + b'}, 15 | {code: '({a = 1}, {b}, {c = 1}) => a + b'}, 16 | {code: '([a], {b}) => a + b'}, 17 | {code: '([a=1], {b}) => a + b'}, 18 | {code: '(a, {b = 1} = {b: 1}) => a + b'}, 19 | {code: '(a, {b}) => a + b'}, 20 | {code: 'const f = function (a, {b = 1}) { return a + b }'} 21 | ], 22 | invalid: [ 23 | { 24 | code: '(a, {b = 1}) => a + b', 25 | errors: [ 26 | { 27 | message: 28 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 29 | } 30 | ] 31 | }, 32 | { 33 | code: '([a], {b=1}) => a + b', 34 | errors: [ 35 | { 36 | message: 37 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 38 | } 39 | ] 40 | }, 41 | { 42 | code: '([a=1], {b=1}) => a + b', 43 | errors: [ 44 | { 45 | message: 46 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 47 | } 48 | ] 49 | }, 50 | { 51 | code: '({a}, {b=1}) => a + b', 52 | errors: [ 53 | { 54 | message: 55 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 56 | } 57 | ] 58 | }, 59 | { 60 | code: '({a}, {b}, {c=1}) => a + b + c', 61 | errors: [ 62 | { 63 | message: 64 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 65 | } 66 | ] 67 | }, 68 | { 69 | code: '({a}, {b=1}, {c=1}) => a + b + c', 70 | errors: [ 71 | { 72 | message: 73 | 'There is an Edge 15-17 bug which causes second argument destructuring to fail. See https://git.io/fhd7N for more' 74 | } 75 | ] 76 | } 77 | ] 78 | }) 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-escompat 2 | 3 | This plugin will report eslint errors for code which - if left untranspiled - 4 | will not work in some browsers. 5 | 6 | This is useful if you intend to ship code without first using a transpiler, such 7 | as [Babel](https://babeljs.io). 8 | 9 | This _won't_ lint for features that can be polyfilled. For that you can use 10 | [eslint-plugin-compat][epc]. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm install --save-dev eslint-plugin-escompat 16 | ``` 17 | 18 | ## Usage for Flat Configs (eslint.config.js) - ESLint >= 8 19 | 20 | ```js 21 | // eslint.config.js 22 | 23 | import globals from 'globals'; 24 | import escompat from 'eslint-plugin-escompat'; 25 | 26 | export default [ 27 | { 28 | plugins: { 29 | escompat 30 | }, 31 | languageOptions: { 32 | globals: globals.browser 33 | }, 34 | rules: { 35 | // Configure the individual `"escompat/*"` rules, e.g.: 36 | 'escompat/no-async-generator': ['error'], 37 | 'escompat/no-numeric-separators': ['error'] 38 | } 39 | } 40 | ]; 41 | ``` 42 | 43 | Alternatively, you can use the `recommended` configuration which will do the 44 | plugins for you, with all recommended `"escompat/*"` rules reporting errors. 45 | 46 | ```js 47 | import globals from 'globals'; 48 | import escompat from 'eslint-plugin-escompat'; 49 | 50 | export default [ 51 | { 52 | languageOptions: { 53 | globals: globals.browser 54 | } 55 | }, 56 | escompat.configs['flat/recommended'] 57 | ]; 58 | ``` 59 | 60 | 61 | ## Usage for .eslintrc configs - ESLint < 9 62 | 63 | Add `"escompat"` to `.eslintrc` `"plugins"` section, add `"browser": true` to 64 | `"env"`, then configure the individual `"escompat/*"` rules. 65 | 66 | Alternatively, you can use the `recommended` configuration which will do this 67 | for you, with the `"escompat/*"` rules reporting errors (as in the snippet 68 | above). 69 | 70 | ```js 71 | // .eslintrc 72 | { 73 | "extends": ["plugin:escompat/recommended"] 74 | } 75 | ``` 76 | 77 | ### TypeScript Users 78 | 79 | Aside from the `recommended` config, there are also multiple `typescript` 80 | configs which can be used if you're using TypeScript. The TypeScript configs 81 | only enable some of the rules, avoiding enabling rules for which `typescript` 82 | safely transpiles down to a more compatible syntax. Extend the typescript config 83 | that matches your `tsconfig.json` `target` value. 84 | 85 | For flat configs: 86 | 87 | ```js 88 | import globals from 'globals'; 89 | import escompat from 'eslint-plugin-escompat'; 90 | 91 | export default [ 92 | { 93 | languageOptions: { 94 | globals: globals.browser 95 | } 96 | }, 97 | 98 | // The TypeScript configs are in array form, so we need to 99 | // spread them out here 100 | ...escompat.configs['flat/typescript-2016'] 101 | ]; 102 | ``` 103 | 104 | or for `.eslintrc`: 105 | 106 | ```js 107 | { 108 | "extends": ["plugin:escompat/typescript-2016"] 109 | } 110 | ``` 111 | 112 | ## Targeting Browsers 113 | 114 | `eslint-plugin-escompat` uses the `browserslist` configuration in `package.json` 115 | 116 | If you have a browserslist, it is safe to enable all of these rules - as any that 117 | do not coincide with your chosen browsers will be turned off automatically. 118 | 119 | See [browserslist/browserslist](https://github.com/browserslist/browserslist) 120 | for configuration. Here's some examples: 121 | 122 | ```js 123 | // Simple configuration (package.json) 124 | { 125 | // ... 126 | "browserslist": ["last 1 versions", "not ie <= 8"], 127 | } 128 | ``` 129 | 130 | ```js 131 | // Use development and production configurations (package.json) 132 | { 133 | // ... 134 | "browserslist": { 135 | "development": ["last 2 versions"], 136 | "production": ["last 4 versions"] 137 | } 138 | } 139 | ``` 140 | 141 | :bulb: You can also define browsers in a 142 | [separate browserslist file](https://github.com/browserslist/browserslist#config-file) 143 | 144 | ## Rules 145 | 146 | - [no-async-generator](./docs/no-async-generator.md) 147 | - [no-async-iteration](./docs/no-async-iteration.md) 148 | - [no-bigint](./docs/no-bigint.md) 149 | - [no-bind-operator](./docs/no-bind-operator.md) 150 | - [no-class-static-blocks](./docs/no-class-static-blocks.md) 151 | - [no-computed-public-class-fields](./docs/no-computed-public-class-fields.md) 152 | - [no-do-expression](./docs/no-do-expression.md) 153 | - [no-dynamic-imports](./docs/no-dynamic-imports.md) 154 | - [no-edge-destructure-bug](./docs/no-edge-destructure-bug.md) 155 | - [no-exponentiation-operator](./docs/no-exponentiation-operator.md) 156 | - [no-hashbang-comment](./docs/no-hashbang-comment.md) 157 | - [no-logical-assignment-operator](./docs/no-logical-assignment-operator.md) 158 | - [no-nullish-coalescing](./docs/no-nullish-coalescing.md) 159 | - [no-numeric-separators](./docs/no-numeric-separators.md) 160 | - [no-object-rest-spread](./docs/no-object-rest-spread.md) 161 | - [no-optional-catch](./docs/no-optional-catch.md) 162 | - [no-optional-chaining](./docs/no-optional-chaining.md) 163 | - [no-pipeline-operator](./docs/no-pipeline-operator.md) 164 | - [no-private-class-fields](./docs/no-private-class-fields.md) 165 | - [no-public-instance-class-fields](./docs/no-public-instance-class-fields.md) 166 | - [no-public-static-class-fields](./docs/no-public-static-class-fields.md) 167 | - [no-regexp-duplicate-named-groups](./docs/no-regexp-duplicate-named-groups.md) 168 | - [no-regexp-lookbehind](./docs/no-regexp-lookbehind.md) 169 | - [no-regexp-named-group](./docs/no-regexp-named-group.md) 170 | - [no-regexp-s-flag](./docs/no-regexp-s-flag.md) 171 | - [no-regexp-v-flag](./docs/no-regexp-v-flag.md) 172 | - [no-top-level-await](./docs/no-top-level-await.md) 173 | 174 | ## Inspiration 175 | 176 | This project was largely inspired by the great [eslint-plugin-compat][epc] 177 | library. 178 | 179 | [epc]: https://github.com/amilajack/eslint-plugin-compat 180 | -------------------------------------------------------------------------------- /test/check-rules.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it*/ 2 | 'use strict'; 3 | 4 | const config = require('../lib/index') 5 | const fs = require('fs') 6 | const assert = require('assert') 7 | const path = require('path') 8 | const globals = require('globals') 9 | const docDir = './docs' 10 | 11 | globalThis.ESLINT_TESTING = true; // Flag for rule creation 12 | 13 | const RuleTester = require('eslint').RuleTester 14 | const ruleTester = new RuleTester({languageOptions: {globals: {...globals.es2020}, sourceType: 'module'}}) 15 | 16 | function rulesFromDir(dir) { 17 | try { 18 | return fs.readdirSync(`./${dir}`).map(f => path.basename(f, path.extname(f))) 19 | } catch { 20 | return [] 21 | } 22 | } 23 | 24 | function* extractCodeblocks(lines) { 25 | let inCodeBlock = false 26 | let codeLines = [] 27 | let startLine = 0 28 | let endLine = 0 29 | let lang = '' 30 | for (const i in lines) { 31 | const line = lines[i] 32 | if (!inCodeBlock && line.startsWith('```')) { 33 | lang = line.slice(3) 34 | startLine = i 35 | codeLines = [] 36 | inCodeBlock = true 37 | continue 38 | } else if (inCodeBlock && line.startsWith('```')) { 39 | endLine = i 40 | yield {code: codeLines, startLine, endLine, lang} 41 | inCodeBlock = false 42 | continue 43 | } 44 | if (inCodeBlock) { 45 | codeLines.push(line) 46 | } 47 | } 48 | } 49 | 50 | describe('smoke tests', () => { 51 | it('has file for each exported rule and rule for each exported file', () => { 52 | assert.deepStrictEqual( 53 | Object.keys(config.rules).sort(), 54 | rulesFromDir('lib/rules').sort(), 55 | 'Expected lib/rules/*.js to be inside lib/index.js#rules' 56 | ) 57 | }) 58 | 59 | for (const flavour in config.configs) { 60 | describe(`${flavour} config`, () => { 61 | it('exports valid rules', () => { 62 | const exportedRules = new Set(Object.keys(config.rules)) 63 | const ceRules = Object.keys(config.configs[flavour].rules || []).filter(rule => rule.startsWith('escompat/')) 64 | const violations = ceRules.filter(rule => !exportedRules.has(rule.replace(/^escompat\//, ''))) 65 | assert.deepStrictEqual(violations, [], 'All custom-elements/ rules should exist in lib/index.js#rules') 66 | }) 67 | }) 68 | } 69 | }) 70 | 71 | describe('test coverage', () => { 72 | it('has tests for each rule and rules for each test', () => { 73 | const tests = rulesFromDir('test').filter(name => name !== 'check-rules') 74 | assert.deepStrictEqual(rulesFromDir('lib/rules'), tests, 'Expected lib/rules/*.js to have same files as test/*.js') 75 | }) 76 | }) 77 | 78 | describe('documentation', () => { 79 | it('has rule for each doc file and doc file for each rule', () => { 80 | assert.deepStrictEqual(rulesFromDir(docDir), rulesFromDir('lib/rules')) 81 | }) 82 | 83 | it('has readme link to each doc', () => { 84 | const contents = fs.readFileSync(`./README.md`, 'utf-8').split('\n') 85 | const i = contents.indexOf('## Rules') 86 | let n = contents.findIndex((line, index) => index > i && line.startsWith('#')) 87 | if (n < i) n = contents.length 88 | const ruleLinks = contents 89 | .slice(i + 1, n) 90 | .filter(Boolean) 91 | .map(x => x.trim()) 92 | const desiredRuleLinks = rulesFromDir(docDir).map(rule => `- [${rule}](${docDir}/${rule}.md)`) 93 | assert.deepStrictEqual(desiredRuleLinks, ruleLinks, `Expected each rule in ${docDir}/*.md to have README link`) 94 | }) 95 | 96 | for (const doc of rulesFromDir(docDir)) { 97 | it(`has correct headings in ${doc}.md`, () => { 98 | const contents = fs.readFileSync(`${docDir}/${doc}.md`, 'utf-8').split('\n') 99 | let consume = true 100 | const headings = contents.filter(line => { 101 | // Discard lines that aren't headers or thumbs 102 | if (!(line.startsWith('#') || line.startsWith('\ud83d')) || line.startsWith('#!')) return false 103 | // Ignore all sub headings/thumbs between `### Options` and `## When Not To Use It` 104 | if (line === '### Options') { 105 | consume = false 106 | return true 107 | } else if (line === '## When Not To Use It') { 108 | consume = true 109 | } 110 | return consume 111 | }) 112 | const desiredHeadings = [ 113 | `# ${doc}`, 114 | '## What is the Fix?', 115 | ].filter(Boolean) 116 | assert.deepStrictEqual(headings, desiredHeadings, 'Expected doc to have correct headings') 117 | }) 118 | 119 | it(`has working examples in ${doc}.md`, () => { 120 | const rules = {valid: [], invalid: []} 121 | const lines = fs.readFileSync(`${docDir}/${doc}.md`, 'utf-8').split('\n') 122 | 123 | for (const {code, startLine} of extractCodeblocks(lines)) { 124 | const validIndex = lines.lastIndexOf('👍 Examples of **correct** code for this rule:', startLine) 125 | const invalidIndex = lines.lastIndexOf('👎 Examples of **incorrect** code for this rule:', startLine) 126 | 127 | if (validIndex === invalidIndex) { 128 | continue 129 | } 130 | 131 | let filename = '' 132 | if (code[0].match(/\s*\/\/ .*\.[jt]s$/)) { 133 | filename = code[0].replace('// ', '').trim() 134 | } 135 | 136 | if (validIndex > invalidIndex) { 137 | rules.valid.push({code: code.join('\n')}) 138 | } else { 139 | rules.invalid.push({code: code.join('\n'), errors: 1, filename}) 140 | } 141 | } 142 | 143 | const rule = require('../lib/index').rules[doc] 144 | ruleTester.run(doc, rule, rules) 145 | }) 146 | 147 | it(`has javascript examples in ${doc}.md`, () => { 148 | const lines = fs.readFileSync(`${docDir}/${doc}.md`, 'utf-8').split('\n') 149 | for (const {lang, startLine} of extractCodeblocks(lines)) { 150 | assert.equal(lang, 'js', `Expected codeblock on line ${startLine} to equal "js"`) 151 | } 152 | }) 153 | } 154 | }) 155 | 156 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require("path"); 4 | const browserslist = require("browserslist"); 5 | const { findConfig } = require("browserslist/node"); 6 | const { version, homepage } = require("../package.json"); 7 | const flatTypeScriptConfigs = {}; 8 | const createRule = (name, browserstring, description, { ts = null } = {}) => { 9 | const rule = require(`./rules/${name}`); 10 | module.exports.rules[name] = { 11 | meta: Object.assign( 12 | { 13 | type: "problem", 14 | docs: { 15 | description, 16 | recommended: true, 17 | url: `${homepage}/blob/v${version}/docs/${name}.md`, 18 | }, 19 | fixable: false, 20 | schema: [], 21 | deprecated: false, 22 | replacedBy: null, 23 | }, 24 | rule.meta || {} 25 | ), 26 | create(context) { 27 | let browsers = browserslist(browserstring); 28 | const config = findConfig(path.dirname( 29 | context.filename ?? context.getFilename() 30 | )) || { 31 | defaults: "defaults", 32 | }; 33 | const desiredBrowsers = browserslist(config.defaults); 34 | const badBrowsers = desiredBrowsers 35 | .filter((browser) => browsers.includes(browser)) 36 | .join(", "); 37 | if (globalThis.ESLINT_TESTING || badBrowsers) { 38 | const create = typeof rule === "function" ? rule : rule.create; 39 | return create(context, globalThis.ESLINT_TESTING ? undefined : badBrowsers); 40 | } 41 | return {}; 42 | }, 43 | }; 44 | 45 | const configName = `typescript-${ts || "base"}`; 46 | const flatConfigName = `flat/${configName}`; 47 | if (!module.exports.configs[configName]) { 48 | flatTypeScriptConfigs[configName] = [{ 49 | name: `escompat/${configName}`, 50 | plugins: { 51 | escompat: module.exports 52 | }, 53 | rules: {} 54 | }]; 55 | const config = { rules: {} }; 56 | if (ts === 2016) { 57 | config.extends = [`plugin:escompat/typescript-base`]; 58 | flatTypeScriptConfigs[configName].unshift( 59 | ...flatTypeScriptConfigs['typescript-base'] 60 | ); 61 | } else if (ts) { 62 | let previous = ts - 1; 63 | while (!module.exports.configs[`typescript-${previous}`]) previous -= 1; 64 | 65 | config.extends = [`plugin:escompat/typescript-${previous}`]; 66 | flatTypeScriptConfigs[configName].unshift( 67 | ...flatTypeScriptConfigs[`typescript-${previous}`] 68 | ); 69 | } 70 | module.exports.configs[configName] = config; 71 | module.exports.configs[flatConfigName] = flatTypeScriptConfigs[configName]; 72 | } 73 | module.exports.configs[`typescript-base`].rules[`escompat/${name}`] = "off"; 74 | module.exports.configs[`flat/typescript-base`].at(-1).rules[`escompat/${name}`] = "off"; 75 | module.exports.configs[configName].rules[`escompat/${name}`] = "error"; 76 | module.exports.configs[flatConfigName].at(-1).rules[`escompat/${name}`] = "error"; 77 | }; 78 | 79 | module.exports = { rules: {}, configs: {} }; 80 | // ES2015 81 | createRule( 82 | "no-edge-destructure-bug", 83 | "edge < 18", 84 | "disallow the use of specific destructuring patterns that cause bugs in old Edge" 85 | ); 86 | 87 | // ES2016 88 | createRule( 89 | "no-exponentiation-operator", 90 | "chrome < 52, edge < 14, firefox < 52, safari < 10.1", 91 | "disallow use of exponentiation operator (**)", 92 | { ts: 2016 } 93 | ); 94 | 95 | // ES2018 96 | createRule( 97 | "no-async-iteration", 98 | "edge < 79, safari < 12, firefox < 57, chrome < 63", 99 | "disallow the use of `for await of` style loops", 100 | { ts: 2018 } 101 | ); 102 | createRule( 103 | "no-async-generator", 104 | "edge < 79, safari < 12, firefox < 57, chrome < 63", 105 | "disallow the use of async generator functions", 106 | { ts: 2018 } 107 | ); 108 | createRule( 109 | "no-object-rest-spread", 110 | "edge < 79, safari < 11.1, firefox < 55, chrome < 60", 111 | "disallow object rest/spread patterns", 112 | { ts: 2018 } 113 | ); 114 | createRule( 115 | "no-regexp-s-flag", 116 | "edge < 79, safari < 11.1, firefox < 78, chrome < 62", 117 | "disallow the use of the RegExp `s` flag" 118 | ); 119 | createRule( 120 | "no-regexp-lookbehind", 121 | "edge < 79, safari < 16.4, firefox < 78, chrome < 62", 122 | "disallow the use of RegExp lookbehinds" 123 | ); 124 | createRule( 125 | "no-regexp-named-group", 126 | "edge < 79, safari 11.1, firefox < 78, chrome < 64", 127 | "disallow the use of RegExp named groups" 128 | ); 129 | 130 | // ES2019 131 | createRule( 132 | "no-optional-catch", 133 | "edge < 79, safari < 11.1, firefox < 58, chrome < 66", 134 | "always require catch() to have an argument", 135 | { ts: 2019 } 136 | ); 137 | 138 | // ES2020 139 | createRule( 140 | "no-dynamic-imports", 141 | "edge < 79, safari < 11, firefox < 67, chrome < 63", 142 | "disallow dynamic import statements" 143 | ); 144 | createRule( 145 | "no-optional-chaining", 146 | "edge < 80, safari < 13.1, firefox < 72, chrome < 80", 147 | "disallow the .? optional chaining operator", 148 | { ts: 2020 } 149 | ); 150 | createRule( 151 | "no-nullish-coalescing", 152 | "edge < 80, safari < 13.1, firefox < 72, chrome < 80", 153 | "disallow the ?? nullish coalescing operator", 154 | { ts: 2020 } 155 | ); 156 | createRule( 157 | "no-bigint", 158 | "edge < 79, safari < 14, firefox < 68, chrome < 67", 159 | "disallow bigints" 160 | ); 161 | 162 | // ES2021 163 | createRule( 164 | "no-numeric-separators", 165 | "edge < 79, safari < 13, firefox < 68, chrome < 75", 166 | "disallow use of numeric separators like 1_000_000", 167 | { ts: 2021 } 168 | ); 169 | 170 | createRule( 171 | "no-logical-assignment-operator", 172 | "edge < 85, safari < 14, firefox < 79, chrome < 85", 173 | "disallow logical assignment operators like &&=", 174 | { ts: 2021 } 175 | ); 176 | 177 | // ES2022 178 | createRule( 179 | "no-public-static-class-fields", 180 | "edge < 79, safari < 14.5, firefox < 75, chrome < 72", 181 | "disallow public static class fields like foo = 1", 182 | { ts: 2022 } 183 | ); 184 | createRule( 185 | "no-public-instance-class-fields", 186 | "edge < 79, safari < 14.5, firefox < 69, chrome < 72", 187 | "disallow public class fields like foo = 1", 188 | { ts: 2022 } 189 | ); 190 | createRule( 191 | "no-computed-public-class-fields", 192 | "edge < 79, safari < 14.5, firefox < 69, chrome < 74", 193 | "disallow computed public static or instance class fields like [foo] = 1", 194 | { ts: 2022 } 195 | ); 196 | createRule( 197 | "no-private-class-fields", 198 | "edge < 79, safari < 14.5, firefox < 90, chrome < 74", 199 | "disallow private class fields like #foo = 1", 200 | { ts: 2022 } 201 | ); 202 | createRule( 203 | "no-class-static-blocks", 204 | "edge < 94, safari < 16.4, firefox < 93, chrome < 94", 205 | "disallow static blocks like `static { x = 1 }`", 206 | { ts: 2022 } 207 | ); 208 | createRule( 209 | "no-top-level-await", 210 | "edge < 89, safari < 15, firefox < 89, chrome < 89", 211 | "disallow await keyword outside of async function context", 212 | { ts: 2022 } 213 | ); 214 | 215 | // ES2023 216 | createRule( 217 | "no-hashbang-comment", 218 | "edge < 79, safari < 13.1, firefox < 67, chrome < 74", 219 | "disallow hashbang comments", 220 | { ts: 2023 } 221 | ); 222 | 223 | // ES2024 224 | createRule( 225 | "no-regexp-v-flag", 226 | "edge > 0, safari < 17, firefox < 116, chrome < 112", 227 | "disallow the use of the RegExp `v` flag", 228 | { ts: 2024 } 229 | ); 230 | 231 | // ES2025 232 | createRule( 233 | "no-regexp-duplicate-named-groups", 234 | "edge < 125, safari < 17, firefox < 129, chrome < 125", 235 | "disallow the use of RegExp duplicate named groups", 236 | { ts: 2025 } 237 | ); 238 | 239 | // Proposals... 240 | createRule( 241 | "no-do-expression", 242 | "edge > 0, safari > 0, firefox > 0, chrome > 0", 243 | 'disallow "do" expressions' 244 | ); 245 | createRule( 246 | "no-bind-operator", 247 | "edge > 0, safari > 0, firefox > 0, chrome > 0", 248 | "disallow the :: bind operator" 249 | ); 250 | createRule( 251 | "no-pipeline-operator", 252 | "edge > 0, safari > 0, firefox > 0, chrome > 0", 253 | "disallow the > pipeline operator" 254 | ); 255 | 256 | module.exports.configs.recommended = { 257 | plugins: ["escompat"], 258 | parserOptions: { ecmaVersion: 2025 }, 259 | rules: Object.keys(module.exports.rules).reduce( 260 | (o, r) => ((o["escompat/" + r] = ["error"]), o), 261 | {} 262 | ), 263 | }; 264 | 265 | module.exports.configs["flat/recommended"] = { 266 | name: 'escompat/flat/recommended', 267 | plugins: { 268 | escompat: module.exports 269 | }, 270 | languageOptions: { 271 | ecmaVersion: 2025 272 | }, 273 | rules: Object.keys(module.exports.rules).reduce( 274 | (o, r) => ((o["escompat/" + r] = ["error"]), o), 275 | {} 276 | ), 277 | }; 278 | 279 | module.exports.configs.typescript = { 280 | extends: ["plugin:escompat/typescript-2025"], 281 | }; 282 | module.exports.configs['flat/typescript'] = flatTypeScriptConfigs[ 283 | 'typescript-2025' 284 | ]; 285 | 286 | if (require.main === module) { 287 | console.log(require("util").inspect(module.exports, { depth: Infinity })); 288 | } 289 | 290 | --------------------------------------------------------------------------------