From 23b867e26f1c70d58007e7d244d00432706934ea Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Wed, 24 Jul 2024 22:01:57 +0800 Subject: [PATCH] feat: add rule `no-regexp-duplicate-named-groups` (and bump to ES2025) (#32) * feat: add `no-regexp-v-flag` (and bump to ES2024) Also: - fix: ensures no-hashbang-comment rule is in TS 2023 - docs: remove unneeded backticks in no-regexp-s-flag rule docs * feat: add rule `no-regexp-duplicate-named-groups` (and bump to ES2025) Also: - docs: add required escapes to `no-regexp-named-group` docs --------- Co-authored-by: Keith Cirkel --- README.md | 1 + docs/no-regexp-duplicate-named-groups.md | 24 +++++++++ docs/no-regexp-named-group.md | 4 +- lib/index.js | 12 ++++- lib/rules/no-regexp-duplicate-named-groups.js | 30 +++++++++++ test/no-regexp-duplicate-named-groups.js | 52 +++++++++++++++++++ 6 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 docs/no-regexp-duplicate-named-groups.md create mode 100644 lib/rules/no-regexp-duplicate-named-groups.js create mode 100644 test/no-regexp-duplicate-named-groups.js diff --git a/README.md b/README.md index abbd98d..90aafb6 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ for configuration. Here's some examples: - [no-private-class-fields](./docs/no-private-class-fields.md) - [no-public-instance-class-fields](./docs/no-public-instance-class-fields.md) - [no-public-static-class-fields](./docs/no-public-static-class-fields.md) +- [no-regexp-duplicate-named-groups](./docs/no-regexp-duplicate-named-groups.md) - [no-regexp-lookbehind](./docs/no-regexp-lookbehind.md) - [no-regexp-named-group](./docs/no-regexp-named-group.md) - [no-regexp-s-flag](./docs/no-regexp-s-flag.md) diff --git a/docs/no-regexp-duplicate-named-groups.md b/docs/no-regexp-duplicate-named-groups.md new file mode 100644 index 0000000..2f4bd90 --- /dev/null +++ b/docs/no-regexp-duplicate-named-groups.md @@ -0,0 +1,24 @@ +# no-regexp-duplicate-named-groups + +This prevents the use of the RegExp duplicate named groups feature + +```js +/(?\d{4})-(?\d{2})|(?\d{2})-(?\d{4})/; + +new RegExp('(?\\d{4})-(?\\d{2})|(?\\d{2})-(?\\d{4})') +``` + +These will not be allowed because they are not supported in the following browsers: + + - Edge < 125 + - Safari < 17 + - Firefox < 129 + - Chrome < 125 + + +## What is the Fix? + +You will have to avoid getting the same name out-of-the-box for an +alternative group. + +This can be safely disabled if you intend to compile code with the `@babel/plugin-proposal-duplicate-named-capturing-groups-regex` Babel plugin. diff --git a/docs/no-regexp-named-group.md b/docs/no-regexp-named-group.md index d4ae19b..9effcb2 100644 --- a/docs/no-regexp-named-group.md +++ b/docs/no-regexp-named-group.md @@ -5,7 +5,7 @@ This prevents the use of the RegExp named groups feature ```js /(?\d{4})-(?\d{2})-(?\d{2})/ -new RegExp('(?\d{4})-(?\d{2})-(?\d{2})') +new RegExp('(?\\d{4})-(?\\d{2})-(?\\d{2})') ``` These will not be allowed because they are not supported in the following browsers: @@ -22,7 +22,7 @@ If readability is the main concern, using non-named groups with array-destructur ```js // With named: -const {year,month,day} = '2020-01-01'.match(/(?\d{4})-(?\d{2})-(?\d{2})/).groups +const {year, month, day} = '2020-01-01'.match(/(?\d{4})-(?\d{2})-(?\d{2})/).groups // Without named const [_, year, month, day] = '2020-01-01'.match(/(\d{4})-(\d{2})-(\d{2})/) || [] diff --git a/lib/index.js b/lib/index.js index 4a3415c..27000e3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -228,6 +228,14 @@ createRule( { ts: 2024 } ); +// ES2025 +createRule( + "no-regexp-duplicate-named-groups", + "edge < 125, safari < 17, firefox < 129, chrome < 125", + "disallow the use of RegExp duplicate named groups", + { ts: 2025 } +); + // Proposals... createRule( "no-do-expression", @@ -247,7 +255,7 @@ createRule( module.exports.configs.recommended = { plugins: ["escompat"], - parserOptions: { ecmaVersion: 2024 }, + parserOptions: { ecmaVersion: 2025 }, rules: Object.keys(module.exports.rules).reduce( (o, r) => ((o["escompat/" + r] = ["error"]), o), {} @@ -260,7 +268,7 @@ module.exports.configs["flat/recommended"] = { escompat: module.exports }, languageOptions: { - ecmaVersion: 2024 + ecmaVersion: 2025 }, rules: Object.keys(module.exports.rules).reduce( (o, r) => ((o["escompat/" + r] = ["error"]), o), diff --git a/lib/rules/no-regexp-duplicate-named-groups.js b/lib/rules/no-regexp-duplicate-named-groups.js new file mode 100644 index 0000000..5853340 --- /dev/null +++ b/lib/rules/no-regexp-duplicate-named-groups.js @@ -0,0 +1,30 @@ +'use strict'; + +const hasDuplicateNamedGroups = s => /(\(\?<[_$\w]*?)>.*?\1>/.test(s) + +module.exports = (context, badBrowser) => ({ + 'Literal[regex]'(node) { + if (hasDuplicateNamedGroups(node.regex.pattern)) { + context.report(node, `RegExp duplicate named groups are not supported in ${badBrowser}`) + } + }, + 'CallExpression[callee.name="RegExp"], NewExpression[callee.name="RegExp"]'(node) { + const [source] = node.arguments; + if ( + source && + ( + ( + source.type === 'Literal' && + typeof source.value === 'string' && + hasDuplicateNamedGroups(source.value) + ) || + ( + source.type === 'TemplateLiteral' && + source.quasis.some(({value: {raw}}) => hasDuplicateNamedGroups(raw)) + ) + ) + ) { + context.report(node, `RegExp duplicate named groups are not supported in ${badBrowser}`) + } + } +}) diff --git a/test/no-regexp-duplicate-named-groups.js b/test/no-regexp-duplicate-named-groups.js new file mode 100644 index 0000000..76a3631 --- /dev/null +++ b/test/no-regexp-duplicate-named-groups.js @@ -0,0 +1,52 @@ +'use strict'; + +const rule = require('../lib/index').rules['no-regexp-duplicate-named-groups'] +const RuleTester = require('eslint').RuleTester + +const ruleTester = new RuleTester({languageOptions: {ecmaVersion: 2025}}) + +ruleTester.run('no-regexp-duplicate-named-groups', rule, { + valid: [ + {code: '/(?:a)/'}, + {code: '/(?:a)/g'}, + {code: 'RegExp("(?:a)b")'}, + {code: 'RegExp("(?:a)b", "g")'}, + {code: '/(?a)/'}, + {code: 'RegExp("(?a)")'}, + {code: '/(?a)|(?a)/'}, + ], + invalid: [ + { + code: '/(?a)|(?b)/', + errors: [ + { + message: 'RegExp duplicate named groups are not supported in undefined' + } + ] + }, + { + code: 'new RegExp("(?a)|(?b)")', + errors: [ + { + message: 'RegExp duplicate named groups are not supported in undefined' + } + ] + }, + { + code: '/(?<$name>a)|(?<$name>b)/', + errors: [ + { + message: 'RegExp duplicate named groups are not supported in undefined' + } + ] + }, + { + code: '/(?<_name>)|(?<_name>)/', + errors: [ + { + message: 'RegExp duplicate named groups are not supported in undefined' + } + ] + }, + ] +})