Comparing version 8.45.0 to 8.47.0
@@ -215,2 +215,34 @@ /** | ||
/** | ||
* The error type when there's an eslintrc-style options in a flat config. | ||
*/ | ||
class IncompatibleKeyError extends Error { | ||
/** | ||
* @param {string} key The invalid key. | ||
*/ | ||
constructor(key) { | ||
super("This appears to be in eslintrc format rather than flat config format."); | ||
this.messageTemplate = "eslintrc-incompat"; | ||
this.messageData = { key }; | ||
} | ||
} | ||
/** | ||
* The error type when there's an eslintrc-style plugins array found. | ||
*/ | ||
class IncompatiblePluginsError extends Error { | ||
/** | ||
* Creates a new instance. | ||
* @param {Array<string>} plugins The plugins array. | ||
*/ | ||
constructor(plugins) { | ||
super("This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); | ||
this.messageTemplate = "eslintrc-plugins"; | ||
this.messageData = { plugins }; | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
@@ -307,2 +339,7 @@ // Low-Level Schemas | ||
// make sure it's not an array, which would mean eslintrc-style is used | ||
if (Array.isArray(value)) { | ||
throw new IncompatiblePluginsError(value); | ||
} | ||
// second check the keys to make sure they are objects | ||
@@ -443,2 +480,30 @@ for (const key of Object.keys(value)) { | ||
/** | ||
* Creates a schema that always throws an error. Useful for warning | ||
* about eslintrc-style keys. | ||
* @param {string} key The eslintrc key to create a schema for. | ||
* @returns {ObjectPropertySchema} The schema. | ||
*/ | ||
function createEslintrcErrorSchema(key) { | ||
return { | ||
merge: "replace", | ||
validate() { | ||
throw new IncompatibleKeyError(key); | ||
} | ||
}; | ||
} | ||
const eslintrcKeys = [ | ||
"env", | ||
"extends", | ||
"globals", | ||
"ignorePatterns", | ||
"noInlineConfig", | ||
"overrides", | ||
"parser", | ||
"parserOptions", | ||
"reportUnusedDisableDirectives", | ||
"root" | ||
]; | ||
//----------------------------------------------------------------------------- | ||
@@ -449,2 +514,7 @@ // Full schema | ||
exports.flatConfigSchema = { | ||
// eslintrc-style keys that should always error | ||
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])), | ||
// flat config keys | ||
settings: deepObjectAssignSchema, | ||
@@ -451,0 +521,0 @@ linterOptions: { |
@@ -717,8 +717,6 @@ /** | ||
// ensure the rule exists | ||
if (!rule) { | ||
throw new TypeError(`Could not find the rule "${ruleId}".`); | ||
// ignore unknown rules | ||
if (rule) { | ||
resultRules.set(ruleId, rule); | ||
} | ||
resultRules.set(ruleId, rule); | ||
} | ||
@@ -725,0 +723,0 @@ } |
@@ -17,2 +17,12 @@ /** | ||
onPatternEnter() { | ||
/* | ||
* `RegExpValidator` may parse the pattern twice in one `validatePattern`. | ||
* So `this._controlChars` should be cleared here as well. | ||
* | ||
* For example, the `/(?<a>\x1f)/` regex will parse the pattern twice. | ||
* This is based on the content described in Annex B. | ||
* If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. | ||
* See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb | ||
*/ | ||
this._controlChars = []; | ||
@@ -36,6 +46,9 @@ } | ||
const uFlag = typeof flags === "string" && flags.includes("u"); | ||
const vFlag = typeof flags === "string" && flags.includes("v"); | ||
this._controlChars = []; | ||
this._source = regexpStr; | ||
try { | ||
this._source = regexpStr; | ||
this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook | ||
this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook | ||
} catch { | ||
@@ -42,0 +55,0 @@ |
@@ -9,15 +9,13 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
/* | ||
* plain-English description of the following regexp: | ||
* 0. `^` fix the match at the beginning of the string | ||
* 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following | ||
* 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) | ||
* 1.1. `\\.`: an escape sequence | ||
* 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty | ||
* 2. `$`: fix the match at the end of the string | ||
*/ | ||
const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u; | ||
const parser = new RegExpParser(); | ||
const QUICK_TEST_REGEX = /\[\]/u; | ||
@@ -49,5 +47,28 @@ //------------------------------------------------------------------------------ | ||
"Literal[regex]"(node) { | ||
if (!regex.test(node.regex.pattern)) { | ||
context.report({ node, messageId: "unexpected" }); | ||
const { pattern, flags } = node.regex; | ||
if (!QUICK_TEST_REGEX.test(pattern)) { | ||
return; | ||
} | ||
let regExpAST; | ||
try { | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, { | ||
unicode: flags.includes("u"), | ||
unicodeSets: flags.includes("v") | ||
}); | ||
} catch { | ||
// Ignore regular expressions that regexpp cannot parse | ||
return; | ||
} | ||
visitRegExpAST(regExpAST, { | ||
onCharacterClassEnter(characterClass) { | ||
if (!characterClass.negate && characterClass.elements.length === 0) { | ||
context.report({ node, messageId: "unexpected" }); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -54,0 +75,0 @@ }; |
@@ -7,2 +7,4 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
@@ -23,3 +25,14 @@ // Rule Definition | ||
schema: [], | ||
schema: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
allowObjectPatternsAsParameters: { | ||
type: "boolean", | ||
default: false | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
], | ||
@@ -32,7 +45,29 @@ messages: { | ||
create(context) { | ||
const options = context.options[0] || {}, | ||
allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false; | ||
return { | ||
ObjectPattern(node) { | ||
if (node.properties.length === 0) { | ||
context.report({ node, messageId: "unexpected", data: { type: "object" } }); | ||
if (node.properties.length > 0) { | ||
return; | ||
} | ||
// Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true | ||
if ( | ||
allowObjectPatternsAsParameters && | ||
( | ||
astUtils.isFunction(node.parent) || | ||
( | ||
node.parent.type === "AssignmentPattern" && | ||
astUtils.isFunction(node.parent.parent) && | ||
node.parent.right.type === "ObjectExpression" && | ||
node.parent.right.properties.length === 0 | ||
) | ||
) | ||
) { | ||
return; | ||
} | ||
context.report({ node, messageId: "unexpected", data: { type: "object" } }); | ||
}, | ||
@@ -39,0 +74,0 @@ ArrayPattern(node) { |
@@ -13,3 +13,3 @@ /** | ||
const validator = new RegExpValidator(); | ||
const validFlags = /[dgimsuy]/gu; | ||
const validFlags = /[dgimsuvy]/gu; | ||
const undefined1 = void 0; | ||
@@ -112,8 +112,10 @@ | ||
* @param {string} pattern The RegExp pattern to validate. | ||
* @param {boolean} uFlag The Unicode flag. | ||
* @param {Object} flags The RegExp flags to validate. | ||
* @param {boolean} [flags.unicode] The Unicode flag. | ||
* @param {boolean} [flags.unicodeSets] The UnicodeSets flag. | ||
* @returns {string|null} The syntax error. | ||
*/ | ||
function validateRegExpPattern(pattern, uFlag) { | ||
function validateRegExpPattern(pattern, flags) { | ||
try { | ||
validator.validatePattern(pattern, undefined1, undefined1, uFlag); | ||
validator.validatePattern(pattern, undefined1, undefined1, flags); | ||
return null; | ||
@@ -136,6 +138,15 @@ } catch (err) { | ||
validator.validateFlags(flags); | ||
return null; | ||
} catch { | ||
return `Invalid flags supplied to RegExp constructor '${flags}'`; | ||
} | ||
/* | ||
* `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, | ||
* but this rule may check only the flag when the pattern is unidentifiable, so check it here. | ||
* https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern | ||
*/ | ||
if (flags.includes("u") && flags.includes("v")) { | ||
return "Regex 'u' and 'v' flags cannot be used together"; | ||
} | ||
return null; | ||
} | ||
@@ -172,4 +183,8 @@ | ||
flags === null | ||
? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) | ||
: validateRegExpPattern(pattern, flags.includes("u")) | ||
? ( | ||
validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) && | ||
validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) && | ||
validateRegExpPattern(pattern, { unicode: false, unicodeSets: false }) | ||
) | ||
: validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }) | ||
); | ||
@@ -176,0 +191,0 @@ |
@@ -189,3 +189,3 @@ /** | ||
const references = sourceCode.getScope(node).through; | ||
const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); | ||
const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name); | ||
@@ -192,0 +192,0 @@ if (unsafeRefs.length > 0) { |
@@ -21,3 +21,3 @@ /** | ||
* so this function reverts CharacterClassRange syntax and restore the sequence. | ||
* @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences. | ||
* @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences. | ||
* @returns {IterableIterator<number[]>} The list of character sequences. | ||
@@ -41,2 +41,5 @@ */ | ||
case "CharacterSet": | ||
case "CharacterClass": // [[]] nesting character class | ||
case "ClassStringDisjunction": // \q{...} | ||
case "ExpressionCharacterClass": // [A--B] | ||
if (seq.length > 0) { | ||
@@ -149,3 +152,6 @@ yield seq; | ||
pattern.length, | ||
flags.includes("u") | ||
{ | ||
unicode: flags.includes("u"), | ||
unicodeSets: flags.includes("v") | ||
} | ||
); | ||
@@ -152,0 +158,0 @@ } catch { |
@@ -9,2 +9,8 @@ /** | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
const { getVariableByName } = require("./utils/ast-utils"); | ||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -32,2 +38,3 @@ //------------------------------------------------------------------------------ | ||
create(context) { | ||
const { sourceCode } = context; | ||
@@ -38,9 +45,14 @@ return { | ||
const wrapperObjects = ["String", "Number", "Boolean"]; | ||
const { name } = node.callee; | ||
if (wrapperObjects.includes(node.callee.name)) { | ||
context.report({ | ||
node, | ||
messageId: "noConstructor", | ||
data: { fn: node.callee.name } | ||
}); | ||
if (wrapperObjects.includes(name)) { | ||
const variable = getVariableByName(sourceCode.getScope(node), name); | ||
if (variable && variable.identifiers.length === 0) { | ||
context.report({ | ||
node, | ||
messageId: "noConstructor", | ||
data: { fn: name } | ||
}); | ||
} | ||
} | ||
@@ -47,0 +59,0 @@ } |
@@ -80,3 +80,3 @@ /** | ||
try { | ||
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); | ||
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); | ||
} catch { | ||
@@ -159,3 +159,2 @@ | ||
const patternNode = node.arguments[0]; | ||
const flagsNode = node.arguments[1]; | ||
@@ -166,4 +165,20 @@ if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { | ||
const rawPatternStartRange = patternNode.range[0] + 1; | ||
const flags = isString(flagsNode) ? flagsNode.value : ""; | ||
let flags; | ||
if (node.arguments.length < 2) { | ||
// It has no flags. | ||
flags = ""; | ||
} else { | ||
const flagsNode = node.arguments[1]; | ||
if (isString(flagsNode)) { | ||
flags = flagsNode.value; | ||
} else { | ||
// The flags cannot be determined. | ||
return; | ||
} | ||
} | ||
checkRegex( | ||
@@ -170,0 +185,0 @@ node, |
/** | ||
* @fileoverview Disallows unnecessary `return await` | ||
* @author Jordan Harband | ||
* @deprecated in ESLint v8.46.0 | ||
*/ | ||
@@ -29,2 +30,6 @@ "use strict"; | ||
deprecated: true, | ||
replacedBy: [], | ||
schema: [ | ||
@@ -31,0 +36,0 @@ ], |
@@ -98,3 +98,3 @@ /** | ||
try { | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); | ||
regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); | ||
} catch { | ||
@@ -101,0 +101,0 @@ |
@@ -9,3 +9,8 @@ /** | ||
const astUtils = require("./utils/ast-utils"); | ||
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); | ||
/** | ||
* @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass | ||
* @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass | ||
*/ | ||
//------------------------------------------------------------------------------ | ||
@@ -32,52 +37,14 @@ // Rule Definition | ||
/** | ||
* Parses a regular expression into a list of characters with character class info. | ||
* @param {string} regExpText The raw text used to create the regular expression | ||
* @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class. | ||
* @example | ||
* | ||
* parseRegExp("a\\b[cd-]"); | ||
* | ||
* // returns: | ||
* [ | ||
* { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false }, | ||
* { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false }, | ||
* { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false }, | ||
* { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }, | ||
* { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false } | ||
* ]; | ||
* | ||
/* | ||
* Set of characters that require escaping in character classes in `unicodeSets` mode. | ||
* ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter | ||
*/ | ||
function parseRegExp(regExpText) { | ||
const charList = []; | ||
const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-")); | ||
regExpText.split("").reduce((state, char, index) => { | ||
if (!state.escapeNextChar) { | ||
if (char === "\\") { | ||
return Object.assign(state, { escapeNextChar: true }); | ||
} | ||
if (char === "[" && !state.inCharClass) { | ||
return Object.assign(state, { inCharClass: true, startingCharClass: true }); | ||
} | ||
if (char === "]" && state.inCharClass) { | ||
if (charList.length && charList[charList.length - 1].inCharClass) { | ||
charList[charList.length - 1].endsCharClass = true; | ||
} | ||
return Object.assign(state, { inCharClass: false, startingCharClass: false }); | ||
} | ||
} | ||
charList.push({ | ||
text: char, | ||
index, | ||
escaped: state.escapeNextChar, | ||
inCharClass: state.inCharClass, | ||
startsCharClass: state.startingCharClass, | ||
endsCharClass: false | ||
}); | ||
return Object.assign(state, { escapeNextChar: false, startingCharClass: false }); | ||
}, { escapeNextChar: false, inCharClass: false, startingCharClass: false }); | ||
/* | ||
* A single character set of ClassSetReservedDoublePunctuator. | ||
* && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator | ||
*/ | ||
const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~"); | ||
return charList; | ||
} | ||
/** @type {import('../shared/types').Rule} */ | ||
@@ -108,2 +75,3 @@ module.exports = { | ||
const sourceCode = context.sourceCode; | ||
const parser = new RegExpParser(); | ||
@@ -115,5 +83,6 @@ /** | ||
* @param {string} character The uselessly escaped character (not including the backslash) | ||
* @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off. | ||
* @returns {void} | ||
*/ | ||
function report(node, startOffset, character) { | ||
function report(node, startOffset, character, disableEscapeBackslashSuggest) { | ||
const rangeStart = node.range[0] + startOffset; | ||
@@ -141,8 +110,12 @@ const range = [rangeStart, rangeStart + 1]; | ||
}, | ||
{ | ||
messageId: "escapeBackslash", | ||
fix(fixer) { | ||
return fixer.insertTextBeforeRange(range, "\\"); | ||
} | ||
} | ||
...disableEscapeBackslashSuggest | ||
? [] | ||
: [ | ||
{ | ||
messageId: "escapeBackslash", | ||
fix(fixer) { | ||
return fixer.insertTextBeforeRange(range, "\\"); | ||
} | ||
} | ||
] | ||
] | ||
@@ -191,2 +164,129 @@ }); | ||
/** | ||
* Checks if the escape character in given regexp is unnecessary. | ||
* @private | ||
* @param {ASTNode} node node to validate. | ||
* @returns {void} | ||
*/ | ||
function validateRegExp(node) { | ||
const { pattern, flags } = node.regex; | ||
let patternNode; | ||
const unicode = flags.includes("u"); | ||
const unicodeSets = flags.includes("v"); | ||
try { | ||
patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets }); | ||
} catch { | ||
// Ignore regular expressions with syntax errors | ||
return; | ||
} | ||
/** @type {(CharacterClass | ExpressionCharacterClass)[]} */ | ||
const characterClassStack = []; | ||
visitRegExpAST(patternNode, { | ||
onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), | ||
onCharacterClassLeave: () => characterClassStack.shift(), | ||
onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), | ||
onExpressionCharacterClassLeave: () => characterClassStack.shift(), | ||
onCharacterEnter(characterNode) { | ||
if (!characterNode.raw.startsWith("\\")) { | ||
// It's not an escaped character. | ||
return; | ||
} | ||
const escapedChar = characterNode.raw.slice(1); | ||
if (escapedChar !== String.fromCodePoint(characterNode.value)) { | ||
// It's a valid escape. | ||
return; | ||
} | ||
let allowedEscapes; | ||
if (characterClassStack.length) { | ||
allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES; | ||
} else { | ||
allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; | ||
} | ||
if (allowedEscapes.has(escapedChar)) { | ||
return; | ||
} | ||
const reportedIndex = characterNode.start + 1; | ||
let disableEscapeBackslashSuggest = false; | ||
if (characterClassStack.length) { | ||
const characterClassNode = characterClassStack[0]; | ||
if (escapedChar === "^") { | ||
/* | ||
* The '^' character is also a special case; it must always be escaped outside of character classes, but | ||
* it only needs to be escaped in character classes if it's at the beginning of the character class. To | ||
* account for this, consider it to be a valid escape character outside of character classes, and filter | ||
* out '^' characters that appear at the start of a character class. | ||
*/ | ||
if (characterClassNode.start + 1 === characterNode.start) { | ||
return; | ||
} | ||
} | ||
if (!unicodeSets) { | ||
if (escapedChar === "-") { | ||
/* | ||
* The '-' character is a special case, because it's only valid to escape it if it's in a character | ||
* class, and is not at either edge of the character class. To account for this, don't consider '-' | ||
* characters to be valid in general, and filter out '-' characters that appear in the middle of a | ||
* character class. | ||
*/ | ||
if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) { | ||
return; | ||
} | ||
} | ||
} else { // unicodeSets mode | ||
if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) { | ||
// Escaping is valid if it is a ClassSetReservedDoublePunctuator. | ||
if (pattern[characterNode.end] === escapedChar) { | ||
return; | ||
} | ||
if (pattern[characterNode.start - 1] === escapedChar) { | ||
if (escapedChar !== "^") { | ||
return; | ||
} | ||
// If the previous character is a `negate` caret(`^`), escape to caret is unnecessary. | ||
if (!characterClassNode.negate) { | ||
return; | ||
} | ||
const negateCaretIndex = characterClassNode.start + 1; | ||
if (negateCaretIndex < characterNode.start - 1) { | ||
return; | ||
} | ||
} | ||
} | ||
if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") { | ||
disableEscapeBackslashSuggest = true; | ||
} | ||
} | ||
} | ||
report( | ||
node, | ||
reportedIndex, | ||
escapedChar, | ||
disableEscapeBackslashSuggest | ||
); | ||
} | ||
}); | ||
} | ||
/** | ||
* Checks if a node has an escape. | ||
@@ -229,28 +329,3 @@ * @param {ASTNode} node node to check. | ||
} else if (node.regex) { | ||
parseRegExp(node.regex.pattern) | ||
/* | ||
* The '-' character is a special case, because it's only valid to escape it if it's in a character | ||
* class, and is not at either edge of the character class. To account for this, don't consider '-' | ||
* characters to be valid in general, and filter out '-' characters that appear in the middle of a | ||
* character class. | ||
*/ | ||
.filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass)) | ||
/* | ||
* The '^' character is also a special case; it must always be escaped outside of character classes, but | ||
* it only needs to be escaped in character classes if it's at the beginning of the character class. To | ||
* account for this, consider it to be a valid escape character outside of character classes, and filter | ||
* out '^' characters that appear at the start of a character class. | ||
*/ | ||
.filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass)) | ||
// Filter out characters that aren't escaped. | ||
.filter(charInfo => charInfo.escaped) | ||
// Filter out characters that are valid to escape, based on their position in the regular expression. | ||
.filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) | ||
// Report all the remaining characters. | ||
.forEach(charInfo => report(node, charInfo.index, charInfo.text)); | ||
validateRegExp(node); | ||
} | ||
@@ -257,0 +332,0 @@ |
@@ -115,10 +115,13 @@ /** | ||
* @param {ASTNode} regexNode AST node which contains the regular expression. | ||
* @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. | ||
* @param {string|null} flags The regular expression flags to be checked. | ||
* @returns {void} | ||
*/ | ||
function checkRegex(pattern, node, regexNode, uFlag) { | ||
function checkRegex(pattern, node, regexNode, flags) { | ||
let ast; | ||
try { | ||
ast = parser.parsePattern(pattern, 0, pattern.length, uFlag); | ||
ast = parser.parsePattern(pattern, 0, pattern.length, { | ||
unicode: Boolean(flags && flags.includes("u")), | ||
unicodeSets: Boolean(flags && flags.includes("v")) | ||
}); | ||
} catch { | ||
@@ -152,3 +155,3 @@ | ||
if (node.regex) { | ||
checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); | ||
checkRegex(node.regex.pattern, node, node, node.regex.flags); | ||
} | ||
@@ -171,3 +174,3 @@ }, | ||
if (regex) { | ||
checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u")); | ||
checkRegex(regex, refNode, refNode.arguments[0], flags); | ||
} | ||
@@ -174,0 +177,0 @@ } |
@@ -244,3 +244,3 @@ /** | ||
* @param {number} ecmaVersion The ecmaVersion to convert. | ||
* @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. | ||
* @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. | ||
*/ | ||
@@ -301,3 +301,6 @@ function getRegexppEcmaVersion(ecmaVersion) { | ||
try { | ||
validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false); | ||
validator.validatePattern(pattern, 0, pattern.length, { | ||
unicode: flags ? flags.includes("u") : false, | ||
unicodeSets: flags ? flags.includes("v") : false | ||
}); | ||
if (flags) { | ||
@@ -466,3 +469,6 @@ validator.validateFlags(flags); | ||
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); | ||
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, { | ||
unicode: flags ? flags.includes("u") : false, | ||
unicodeSets: flags ? flags.includes("v") : false | ||
}); | ||
@@ -469,0 +475,0 @@ visitRegExpAST(ast, { |
@@ -31,3 +31,3 @@ /** | ||
docs: { | ||
description: "Enforce the use of `u` flag on RegExp", | ||
description: "Enforce the use of `u` or `v` flag on RegExp", | ||
recommended: false, | ||
@@ -55,3 +55,3 @@ url: "https://eslint.org/docs/latest/rules/require-unicode-regexp" | ||
if (!flags.includes("u")) { | ||
if (!flags.includes("u") && !flags.includes("v")) { | ||
context.report({ | ||
@@ -90,3 +90,3 @@ messageId: "requireUFlag", | ||
if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { | ||
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) { | ||
context.report({ | ||
@@ -93,0 +93,0 @@ messageId: "requireUFlag", |
@@ -11,3 +11,3 @@ /** | ||
const REGEXPP_LATEST_ECMA_VERSION = 2022; | ||
const REGEXPP_LATEST_ECMA_VERSION = 2024; | ||
@@ -32,3 +32,3 @@ /** | ||
try { | ||
validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); | ||
validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true }); | ||
} catch { | ||
@@ -35,0 +35,0 @@ return false; |
{ | ||
"name": "eslint", | ||
"version": "8.45.0", | ||
"version": "8.47.0", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -64,9 +64,9 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"@eslint-community/eslint-utils": "^4.2.0", | ||
"@eslint-community/regexpp": "^4.4.0", | ||
"@eslint/eslintrc": "^2.1.0", | ||
"@eslint/js": "8.44.0", | ||
"@eslint-community/regexpp": "^4.6.1", | ||
"@eslint/eslintrc": "^2.1.2", | ||
"@eslint/js": "^8.47.0", | ||
"@humanwhocodes/config-array": "^0.11.10", | ||
"@humanwhocodes/module-importer": "^1.0.1", | ||
"@nodelib/fs.walk": "^1.2.8", | ||
"ajv": "^6.10.0", | ||
"ajv": "^6.12.4", | ||
"chalk": "^4.0.0", | ||
@@ -77,5 +77,5 @@ "cross-spawn": "^7.0.2", | ||
"escape-string-regexp": "^4.0.0", | ||
"eslint-scope": "^7.2.0", | ||
"eslint-visitor-keys": "^3.4.1", | ||
"espree": "^9.6.0", | ||
"eslint-scope": "^7.2.2", | ||
"eslint-visitor-keys": "^3.4.3", | ||
"espree": "^9.6.1", | ||
"esquery": "^1.4.2", | ||
@@ -82,0 +82,0 @@ "esutils": "^2.0.2", |
@@ -252,2 +252,7 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/ota-meshi"> | ||
<img src="https://github.com/ota-meshi.png?s=75" width="75" height="75"><br /> | ||
Yosuke Ota | ||
</a> | ||
</td></tr></tbody></table> | ||
@@ -288,3 +293,3 @@ | ||
<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3> | ||
<p><a href="https://iboysoft.com/"><img src="https://images.opencollective.com/iboysoft-software/7f9d60e/avatar.png" alt="iBoysoft" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465?v=4" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p> | ||
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p> | ||
<!--sponsorsend--> | ||
@@ -291,0 +296,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2902000
406
68580
300
+ Added@eslint/js@8.57.0(transitive)
- Removed@eslint/js@8.44.0(transitive)
Updated@eslint/eslintrc@^2.1.2
Updated@eslint/js@^8.47.0
Updatedajv@^6.12.4
Updatedeslint-scope@^7.2.2
Updatedeslint-visitor-keys@^3.4.3
Updatedespree@^9.6.1