diff --git a/docs/tools/markdown-it-rule-example.js b/docs/tools/markdown-it-rule-example.js index 94f6d1e04dbf..1e37b00411be 100644 --- a/docs/tools/markdown-it-rule-example.js +++ b/docs/tools/markdown-it-rule-example.js @@ -73,7 +73,7 @@ function markdownItRuleExample({ open, close }) { } const { type, languageOptionsJSON } = - /^\s*(?\S+)(\s+(?\S.*?))?\s*$/u.exec( + /^\s*(?\S+)(?:\s+(?\S(?:.*\S)?))?/u.exec( tagToken.info, ).groups; const languageOptions = languageOptionsJSON diff --git a/lib/cli-engine/file-enumerator.js b/lib/cli-engine/file-enumerator.js index a07886219739..91c4013c995c 100644 --- a/lib/cli-engine/file-enumerator.js +++ b/lib/cli-engine/file-enumerator.js @@ -51,7 +51,7 @@ const debug = require("debug")("eslint:file-enumerator"); //------------------------------------------------------------------------------ const minimatchOpts = { dot: true, matchBase: true }; -const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u; +const dotfilesPattern = /(?:^\.|[/\\]\.)[^/\\.].*/u; const NONE = 0; const IGNORED_SILENTLY = 1; const IGNORED = 2; diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index ff977a7679e5..f393a99988b7 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -230,7 +230,7 @@ function assertIsRuleSeverity(ruleId, value) { * @throws {TypeError} If the string isn't in the correct format. */ function assertIsPluginMemberName(value) { - if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) { + if (!/[\w\-@$]+(?:\/[\w\-$]+)+$/iu.test(value)) { throw new TypeError( `Expected string in the form "pluginName/objectName" but found "${value}".`, ); diff --git a/lib/languages/js/source-code/source-code.js b/lib/languages/js/source-code/source-code.js index b5ae715a2e22..783b26c2b079 100644 --- a/lib/languages/js/source-code/source-code.js +++ b/lib/languages/js/source-code/source-code.js @@ -1056,7 +1056,7 @@ class SourceCode extends TokenStore { // only certain comment types are supported as line comments return ( comment.type !== "Line" || - !!/^eslint-disable-(next-)?line$/u.test(directive.label) + !!/^eslint-disable-(?:next-)?line$/u.test(directive.label) ); }); @@ -1091,9 +1091,8 @@ class SourceCode extends TokenStore { } = commentParser.parseDirective(comment.value); // Step 2: Extract the directive value - const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test( - label, - ); + const lineCommentSupported = + /^eslint-disable-(?:next-)?line$/u.test(label); if (comment.type === "Line" && !lineCommentSupported) { return; diff --git a/lib/linter/interpolate.js b/lib/linter/interpolate.js index b1275181aed3..6327f76e7f1e 100644 --- a/lib/linter/interpolate.js +++ b/lib/linter/interpolate.js @@ -14,7 +14,7 @@ * @returns {RegExp} Global regular expression matching placeholders */ function getPlaceholderMatcher() { - return /\{\{([^{}]+?)\}\}/gu; + return /\{\{([^{}]+)\}\}/gu; } /** diff --git a/lib/linter/linter.js b/lib/linter/linter.js index db059b268525..40ef86ae186f 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -528,9 +528,8 @@ function getDirectiveComments( justification: justificationPart, } = directive; - const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test( - label, - ); + const lineCommentSupported = + /^eslint-disable-(?:next-)?line$/u.test(label); if (comment.type === "Line" && !lineCommentSupported) { return; @@ -853,7 +852,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { return LATEST_ECMA_VERSION; } -const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu; +const eslintEnvPattern = /\/\*\s*eslint-env\s.+?(?:\*\/|$)/gsu; /** * Checks whether or not there is a comment which has "eslint-env *" in a given text. diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js index 707789a16003..d6e4d05ce41e 100644 --- a/lib/rules/dot-notation.js +++ b/lib/rules/dot-notation.js @@ -15,7 +15,7 @@ const keywords = require("./utils/keywords"); // Rule Definition //------------------------------------------------------------------------------ -const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u; +const validIdentifier = /^[a-zA-Z_$][\w$]*$/u; // `null` literal must be handled separately. const literalTypesToCheck = new Set(["string", "boolean"]); diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js index 447b89129e3d..9261fe29697a 100644 --- a/lib/rules/indent-legacy.js +++ b/lib/rules/indent-legacy.js @@ -1162,7 +1162,7 @@ module.exports = { * @returns {boolean} the result */ function isWrappedInParenthesis(node) { - const regex = /^return\s*?\(\s*?\);*?/u; + const regex = /^return\s*\(\s*\)/u; const statementWithoutArgument = sourceCode .getText(node) diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js index c77cd1c66699..d1a3c5baadf5 100644 --- a/lib/rules/no-alert.js +++ b/lib/rules/no-alert.js @@ -24,7 +24,7 @@ const { * @returns {boolean} Whether or not the name is prohibited. */ function isProhibitedIdentifier(name) { - return /^(alert|confirm|prompt)$/u.test(name); + return /^(?:alert|confirm|prompt)$/u.test(name); } /** diff --git a/lib/rules/no-empty-function.js b/lib/rules/no-empty-function.js index 8bf6e0028d85..b83bdd1564e2 100644 --- a/lib/rules/no-empty-function.js +++ b/lib/rules/no-empty-function.js @@ -162,7 +162,7 @@ module.exports = { } } - if (/(g|s)etters|methods$/iu.test(kind)) { + if (/(?:g|s)etters|methods$/iu.test(kind)) { if ( (node.parent.decorators?.length && allow.includes("decoratedFunctions")) || diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index 60669c7c21f9..4aaaad1d267f 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -19,8 +19,8 @@ const astUtils = require("./utils/ast-utils"); const ALL_IRREGULARS = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u; const IRREGULAR_WHITESPACE = - /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gmu; -const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gmu; + /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gu; +const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gu; const LINE_BREAK = astUtils.createGlobalLinebreakMatcher(); //------------------------------------------------------------------------------ diff --git a/lib/rules/no-mixed-spaces-and-tabs.js b/lib/rules/no-mixed-spaces-and-tabs.js index 3a365724d102..a915ac8988f4 100644 --- a/lib/rules/no-mixed-spaces-and-tabs.js +++ b/lib/rules/no-mixed-spaces-and-tabs.js @@ -99,6 +99,7 @@ module.exports = { * At least one space followed by a tab * before non-tab/-space characters begin. */ + // eslint-disable-next-line regexp/no-empty-lookarounds-assertion -- False positive regex = /^(?=(\t*))\1(?=( +))\2\t/u; } diff --git a/lib/rules/no-octal.js b/lib/rules/no-octal.js index 28dd9108bbd9..e5ea9bda0b01 100644 --- a/lib/rules/no-octal.js +++ b/lib/rules/no-octal.js @@ -30,10 +30,7 @@ module.exports = { create(context) { return { Literal(node) { - if ( - typeof node.value === "number" && - /^0[0-9]/u.test(node.raw) - ) { + if (typeof node.value === "number" && /^0\d/u.test(node.raw)) { context.report({ node, messageId: "noOctal", diff --git a/lib/rules/no-trailing-spaces.js b/lib/rules/no-trailing-spaces.js index d8e1ddc46589..f101f72fdffc 100644 --- a/lib/rules/no-trailing-spaces.js +++ b/lib/rules/no-trailing-spaces.js @@ -82,7 +82,8 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; - const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", + const BLANK_CLASS = + "[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u3000]", SKIP_BLANK = `^${BLANK_CLASS}*$`, NONBLANK = `${BLANK_CLASS}+$`; diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js index 541b3a527d29..480b5159773a 100644 --- a/lib/rules/no-useless-escape.js +++ b/lib/rules/no-useless-escape.js @@ -387,7 +387,7 @@ module.exports = { const value = isTemplateElement ? sourceCode.getText(node) : node.raw; - const pattern = /\\[^\d]/gu; + const pattern = /\\\D/gu; let match; while ((match = pattern.exec(value))) { diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index ce2e82358dac..f1414dfa91a3 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -522,7 +522,7 @@ module.exports = { } if ( - !/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.>[\dA-Fa-f]+)\}/uy; + const regExp = /\{(?[\dA-F]+)\}/iuy; regExp.lastIndex = reader.pos; const match = regExp.exec(reader.source); diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index 6b6893498fe0..1ee74712da09 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -20,7 +20,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} Whether or not it is a comparison operator. */ function isComparisonOperator(operator) { - return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator); + return /^(?:==|===|!=|!==|<|>|<=|>=)$/u.test(operator); } /** @@ -29,7 +29,7 @@ function isComparisonOperator(operator) { * @returns {boolean} Whether or not it is an equality operator. */ function isEqualityOperator(operator) { - return /^(==|===)$/u.test(operator); + return /^(?:==|===)$/u.test(operator); } /** diff --git a/lib/shared/naming.js b/lib/shared/naming.js index a9d065f82a4b..86621d138263 100644 --- a/lib/shared/naming.js +++ b/lib/shared/naming.js @@ -4,7 +4,7 @@ "use strict"; -const NAMESPACE_REGEX = /^@.*\//iu; +const NAMESPACE_REGEX = /^@.*\//u; /** * Brings package name to correct format based on prefix diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index bd0cbd59d3b4..ba0cb0ae2304 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -4,6 +4,7 @@ const js = require("@eslint/js"); const jsdoc = require("eslint-plugin-jsdoc"); const eslintCommentsPluginConfigs = require("@eslint-community/eslint-plugin-eslint-comments/configs"); const unicorn = require("eslint-plugin-unicorn"); +const regexp = require("eslint-plugin-regexp"); // extends eslint recommended config /** @@ -294,6 +295,12 @@ const eslintCommentsConfigs = [ }, ]; +// extends eslint-plugin-regexp's recommended config +/** + * @type {import("eslint").Linter.Config[]} + */ +const regexpConfigs = [regexp.configs["flat/recommended"]]; + /** * @type {import("eslint").Linter.Config[]} */ @@ -305,8 +312,9 @@ module.exports = [ reportUnusedInlineConfigs: "error", }, }, - ...jsConfigs, ...unicornConfigs, ...jsdocConfigs, ...eslintCommentsConfigs, + ...regexpConfigs, + ...jsConfigs, ]; diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 79fa33d21036..06cd2d3c7e40 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -66,6 +66,7 @@ "@eslint/js": "^9.0.0", "eslint-plugin-jsdoc": "^48.2.3", "eslint-plugin-n": "^17.11.1", + "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^52.0.0" }, "devDependencies": { diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index ba8a489be651..cb21548ef310 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -21,8 +21,8 @@ const assert = require("node:assert"), // Helpers //------------------------------------------------------------------------------ -const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu; -const languageOptionsPattern = /\/\*languageOptions\s+((?:.|[\r\n])+?)\s*\*\//u; +const expectedPattern = /\/\*expected\s((?:.|[\r\n])+?)\*\//gu; +const languageOptionsPattern = /\/\*languageOptions\s((?:.|[\r\n])+?)\*\//u; const lineEndingPattern = /\r?\n/gu; const linter = new Linter(); @@ -39,7 +39,7 @@ function getExpectedDotArrows(source) { let m; while ((m = expectedPattern.exec(source)) !== null) { - retv.push(m[1].replace(lineEndingPattern, "\n")); + retv.push(m[1].trim().replace(lineEndingPattern, "\n")); } return retv; diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 30a43bdf9cc4..4c73a7285468 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -2152,12 +2152,12 @@ describe("ast-utils", () => { { nodeA: { type: "Literal", - value: /(?:)/u, + value: /(?:)/u, // eslint-disable-line regexp/no-empty-group -- Test data for regex comparison regex: { pattern: "(?:)", flags: "u" }, }, nodeB: { type: "Literal", - value: /(?:)/u, + value: /(?:)/u, // eslint-disable-line regexp/no-empty-group -- Test data for regex comparison regex: { pattern: "(?:)", flags: "u" }, }, expected: true, @@ -2183,7 +2183,7 @@ describe("ast-utils", () => { }, nodeB: { type: "Literal", - value: /(?:)/, // eslint-disable-line require-unicode-regexp -- Checking non-Unicode regex + value: /(?:)/, // eslint-disable-line require-unicode-regexp, regexp/no-empty-group -- Checking non-Unicode regex regex: { pattern: "(?:)", flags: "" }, }, expected: false, diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js index ed39eeda13c3..4a1cdf1bc90a 100644 --- a/tests/lib/shared/runtime-info.js +++ b/tests/lib/shared/runtime-info.js @@ -222,7 +222,7 @@ describe("RuntimeInfo", () => { assert.throws( RuntimeInfo.environment, - /^Unexpected token .*T.* JSON/u, + /^Unexpected token [^\n\rT\u2028\u2029]*T.* JSON/u, ); assert.strictEqual( logErrorStub.args[0][0], @@ -236,7 +236,7 @@ describe("RuntimeInfo", () => { assert.throws( RuntimeInfo.environment, - /^Unexpected token .*T.* JSON/u, + /^Unexpected token [^\n\rT\u2028\u2029]*T.* JSON/u, ); assert.strictEqual( logErrorStub.args[0][0], diff --git a/tools/update-readme.js b/tools/update-readme.js index 1308e49f1f5e..74453c6533fa 100644 --- a/tools/update-readme.js +++ b/tools/update-readme.js @@ -122,7 +122,7 @@ const HTML_TEMPLATE = stripIndents` // replace all of the section let newReadme = readme.replace( - /[\w\W]*?/u, + /[\s\S]*?/u, ejs.render(HTML_TEMPLATE, { team, formatTeamMembers, @@ -130,7 +130,7 @@ const HTML_TEMPLATE = stripIndents` ); newReadme = newReadme.replace( - /[\w\W]*?/u, + /[\s\S]*?/u, `\n\n${allSponsors}\n\n`, ); diff --git a/tools/update-rule-type-headers.js b/tools/update-rule-type-headers.js index 9328a9092727..9dbe9381e5bd 100644 --- a/tools/update-rule-type-headers.js +++ b/tools/update-rule-type-headers.js @@ -191,7 +191,7 @@ function getTextPositionsMap(sourceText, consideredRuleIds) { */ function paraphraseDescription(description) { let newDescription; - const match = /^(Disallow|Enforce|Require) /u.exec(description); + const match = /^(?:Disallow|Enforce|Require) /u.exec(description); if (match) { newDescription = `Rule to ${description[0].toLowerCase()}${description.slice(1)}`;