Comparing version 9.9.1 to 9.10.0
@@ -13,5 +13,5 @@ /** | ||
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array"); | ||
const { flatConfigSchema, hasMethod } = require("./flat-config-schema"); | ||
const { RuleValidator } = require("./rule-validator"); | ||
const { flatConfigSchema } = require("./flat-config-schema"); | ||
const { defaultConfig } = require("./default-config"); | ||
const { Config } = require("./config"); | ||
@@ -27,59 +27,3 @@ //----------------------------------------------------------------------------- | ||
const ruleValidator = new RuleValidator(); | ||
/** | ||
* Splits a plugin identifier in the form a/b/c into two parts: a/b and c. | ||
* @param {string} identifier The identifier to parse. | ||
* @returns {{objectName: string, pluginName: string}} The parts of the plugin | ||
* name. | ||
*/ | ||
function splitPluginIdentifier(identifier) { | ||
const parts = identifier.split("/"); | ||
return { | ||
objectName: parts.pop(), | ||
pluginName: parts.join("/") | ||
}; | ||
} | ||
/** | ||
* Returns the name of an object in the config by reading its `meta` key. | ||
* @param {Object} object The object to check. | ||
* @returns {string?} The name of the object if found or `null` if there | ||
* is no name. | ||
*/ | ||
function getObjectId(object) { | ||
// first check old-style name | ||
let name = object.name; | ||
if (!name) { | ||
if (!object.meta) { | ||
return null; | ||
} | ||
name = object.meta.name; | ||
if (!name) { | ||
return null; | ||
} | ||
} | ||
// now check for old-style version | ||
let version = object.version; | ||
if (!version) { | ||
version = object.meta && object.meta.version; | ||
} | ||
// if there's a version then append that | ||
if (version) { | ||
return `${name}@${version}`; | ||
} | ||
return name; | ||
} | ||
/** | ||
* Wraps a config error with details about where the error occurred. | ||
@@ -128,39 +72,3 @@ * @param {Error} error The original error. | ||
/** | ||
* Converts a languageOptions object to a JSON representation. | ||
* @param {Record<string, any>} languageOptions The options to create a JSON | ||
* representation of. | ||
* @param {string} objectKey The key of the object being converted. | ||
* @returns {Record<string, any>} The JSON representation of the languageOptions. | ||
* @throws {TypeError} If a function is found in the languageOptions. | ||
*/ | ||
function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") { | ||
const result = {}; | ||
for (const [key, value] of Object.entries(languageOptions)) { | ||
if (value) { | ||
if (typeof value === "object") { | ||
const name = getObjectId(value); | ||
if (name && hasMethod(value)) { | ||
result[key] = name; | ||
} else { | ||
result[key] = languageOptionsToJSON(value, key); | ||
} | ||
continue; | ||
} | ||
if (typeof value === "function") { | ||
throw new TypeError(`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`); | ||
} | ||
} | ||
result[key] = value; | ||
} | ||
return result; | ||
} | ||
const originalBaseConfig = Symbol("originalBaseConfig"); | ||
@@ -311,112 +219,3 @@ const originalLength = Symbol("originalLength"); | ||
[ConfigArraySymbol.finalizeConfig](config) { | ||
const { plugins, language, languageOptions, processor } = config; | ||
let parserName, processorName, languageName; | ||
let invalidParser = false, | ||
invalidProcessor = false, | ||
invalidLanguage = false; | ||
// Check parser value | ||
if (languageOptions && languageOptions.parser) { | ||
const { parser } = languageOptions; | ||
if (typeof parser === "object") { | ||
parserName = getObjectId(parser); | ||
if (!parserName) { | ||
invalidParser = true; | ||
} | ||
} else { | ||
invalidParser = true; | ||
} | ||
} | ||
// Check language value | ||
if (language) { | ||
if (typeof language === "string") { | ||
const { pluginName, objectName: localLanguageName } = splitPluginIdentifier(language); | ||
languageName = language; | ||
if (!plugins || !plugins[pluginName] || !plugins[pluginName].languages || !plugins[pluginName].languages[localLanguageName]) { | ||
throw new TypeError(`Key "language": Could not find "${localLanguageName}" in plugin "${pluginName}".`); | ||
} | ||
config.language = plugins[pluginName].languages[localLanguageName]; | ||
} else { | ||
invalidLanguage = true; | ||
} | ||
try { | ||
config.language.validateLanguageOptions(config.languageOptions); | ||
} catch (error) { | ||
throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error }); | ||
} | ||
} | ||
// Check processor value | ||
if (processor) { | ||
if (typeof processor === "string") { | ||
const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor); | ||
processorName = processor; | ||
if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) { | ||
throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`); | ||
} | ||
config.processor = plugins[pluginName].processors[localProcessorName]; | ||
} else if (typeof processor === "object") { | ||
processorName = getObjectId(processor); | ||
if (!processorName) { | ||
invalidProcessor = true; | ||
} | ||
} else { | ||
invalidProcessor = true; | ||
} | ||
} | ||
ruleValidator.validate(config); | ||
// apply special logic for serialization into JSON | ||
/* eslint-disable object-shorthand -- shorthand would change "this" value */ | ||
Object.defineProperty(config, "toJSON", { | ||
value: function() { | ||
if (invalidParser) { | ||
throw new Error("Could not serialize parser object (missing 'meta' object)."); | ||
} | ||
if (invalidProcessor) { | ||
throw new Error("Could not serialize processor object (missing 'meta' object)."); | ||
} | ||
if (invalidLanguage) { | ||
throw new Error("Caching is not supported when language is an object."); | ||
} | ||
return { | ||
...this, | ||
plugins: Object.entries(plugins).map(([namespace, plugin]) => { | ||
const pluginId = getObjectId(plugin); | ||
if (!pluginId) { | ||
return namespace; | ||
} | ||
return `${namespace}:${pluginId}`; | ||
}), | ||
language: languageName, | ||
languageOptions: languageOptionsToJSON(languageOptions), | ||
processor: processorName | ||
}; | ||
} | ||
}); | ||
/* eslint-enable object-shorthand -- ok to enable now */ | ||
return config; | ||
return new Config(config); | ||
} | ||
@@ -423,0 +222,0 @@ /* eslint-enable class-methods-use-this -- Desired as instance method */ |
@@ -23,3 +23,3 @@ /** | ||
createEmitter = require("../../../linter/safe-emitter"), | ||
ConfigCommentParser = require("../../../linter/config-comment-parser"), | ||
{ ConfigCommentParser, VisitNodeStep, CallMethodStep } = require("@eslint/plugin-kit"), | ||
@@ -320,62 +320,3 @@ eslintScope = require("eslint-scope"); | ||
const STEP_KIND = { | ||
visit: 1, | ||
call: 2 | ||
}; | ||
/** | ||
* A class to represent a step in the traversal process. | ||
*/ | ||
class TraversalStep { | ||
/** | ||
* The type of the step. | ||
* @type {string} | ||
*/ | ||
type; | ||
/** | ||
* The kind of the step. Represents the same data as the `type` property | ||
* but it's a number for performance. | ||
* @type {number} | ||
*/ | ||
kind; | ||
/** | ||
* The target of the step. | ||
* @type {ASTNode|string} | ||
*/ | ||
target; | ||
/** | ||
* The phase of the step. | ||
* @type {number|undefined} | ||
*/ | ||
phase; | ||
/** | ||
* The arguments of the step. | ||
* @type {Array<any>} | ||
*/ | ||
args; | ||
/** | ||
* Creates a new instance. | ||
* @param {Object} options The options for the step. | ||
* @param {string} options.type The type of the step. | ||
* @param {ASTNode|string} options.target The target of the step. | ||
* @param {number|undefined} [options.phase] The phase of the step. | ||
* @param {Array<any>} options.args The arguments of the step. | ||
* @returns {void} | ||
*/ | ||
constructor({ type, target, phase, args }) { | ||
this.type = type; | ||
this.kind = STEP_KIND[type]; | ||
this.target = target; | ||
this.phase = phase; | ||
this.args = args; | ||
} | ||
} | ||
/** | ||
* A class to represent a directive comment. | ||
@@ -1007,7 +948,9 @@ * @implements {IDirective} | ||
const { directivePart } = commentParser.extractDirectiveComment(comment.value); | ||
const directive = commentParser.parseDirective(comment.value); | ||
const directiveMatch = directivesPattern.exec(directivePart); | ||
if (!directive) { | ||
return false; | ||
} | ||
if (!directiveMatch) { | ||
if (!directivesPattern.test(directive.label)) { | ||
return false; | ||
@@ -1017,3 +960,3 @@ } | ||
// only certain comment types are supported as line comments | ||
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]); | ||
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directive.label); | ||
}); | ||
@@ -1045,15 +988,12 @@ | ||
this.getInlineConfigNodes().forEach(comment => { | ||
const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value); | ||
// Step 1: Extract the directive text | ||
const match = directivesPattern.exec(directivePart); | ||
// Step 1: Parse the directive | ||
const { | ||
label, | ||
value, | ||
justification: justificationPart | ||
} = commentParser.parseDirective(comment.value); | ||
if (!match) { | ||
return; | ||
} | ||
const directiveText = match[1]; | ||
// Step 2: Extract the directive value | ||
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText); | ||
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label); | ||
@@ -1065,4 +1005,4 @@ if (comment.type === "Line" && !lineCommentSupported) { | ||
// Step 3: Validate the directive does not span multiple lines | ||
if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) { | ||
const message = `${directiveText} comment should not span multiple lines.`; | ||
if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) { | ||
const message = `${label} comment should not span multiple lines.`; | ||
@@ -1078,5 +1018,3 @@ problems.push({ | ||
// Step 4: Extract the directive value and create the Directive object | ||
const directiveValue = directivePart.slice(match.index + directiveText.length); | ||
switch (directiveText) { | ||
switch (label) { | ||
case "eslint-disable": | ||
@@ -1086,3 +1024,3 @@ case "eslint-enable": | ||
case "eslint-disable-line": { | ||
const directiveType = directiveText.slice("eslint-".length); | ||
const directiveType = label.slice("eslint-".length); | ||
@@ -1092,3 +1030,3 @@ directives.push(new Directive({ | ||
node: comment, | ||
value: directiveValue, | ||
value, | ||
justification: justificationPart | ||
@@ -1148,7 +1086,7 @@ })); | ||
const { directiveText, directiveValue } = commentParser.parseDirective(comment); | ||
const { label, value } = commentParser.parseDirective(comment.value); | ||
switch (directiveText) { | ||
switch (label) { | ||
case "exported": | ||
Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment)); | ||
Object.assign(exportedVariables, commentParser.parseListConfig(value)); | ||
break; | ||
@@ -1158,7 +1096,7 @@ | ||
case "global": | ||
for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) { | ||
for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) { | ||
let normalizedValue; | ||
try { | ||
normalizedValue = normalizeConfigGlobal(value); | ||
normalizedValue = normalizeConfigGlobal(idSetting); | ||
} catch (err) { | ||
@@ -1186,5 +1124,5 @@ problems.push({ | ||
case "eslint": { | ||
const parseResult = commentParser.parseJsonConfig(directiveValue); | ||
const parseResult = commentParser.parseJSONLikeConfig(value); | ||
if (parseResult.success) { | ||
if (parseResult.ok) { | ||
configs.push({ | ||
@@ -1266,4 +1204,3 @@ config: { | ||
enterNode(node) { | ||
steps.push(new TraversalStep({ | ||
type: "visit", | ||
steps.push(new VisitNodeStep({ | ||
target: node, | ||
@@ -1275,4 +1212,3 @@ phase: 1, | ||
leaveNode(node) { | ||
steps.push(new TraversalStep({ | ||
type: "visit", | ||
steps.push(new VisitNodeStep({ | ||
target: node, | ||
@@ -1300,4 +1236,3 @@ phase: 2, | ||
emitter.on(eventName, (...args) => { | ||
steps.push(new TraversalStep({ | ||
type: "call", | ||
steps.push(new CallMethodStep({ | ||
target: eventName, | ||
@@ -1304,0 +1239,0 @@ args |
@@ -63,21 +63,10 @@ /** | ||
* @param {Directive[]} directives Unused directives to be removed. | ||
* @param {Token} node The backing Comment token. | ||
* @param {{node: Token, value: string}} parentDirective Data about the backing directive. | ||
* @param {SourceCode} sourceCode The source code object for the file being linted. | ||
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. | ||
*/ | ||
function createIndividualDirectivesRemoval(directives, node, sourceCode) { | ||
function createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) { | ||
const range = sourceCode.getRange(node); | ||
/* | ||
* `node.value` starts right after `//` or `/*`. | ||
* All calculated offsets will be relative to this index. | ||
*/ | ||
const commentValueStart = range[0] + "//".length; | ||
// Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`) | ||
const listStartOffset = /^\s*\S+\s+/u.exec(node.value)[0].length; | ||
/* | ||
* Get the list text without any surrounding whitespace. In order to preserve the original | ||
* Get the list of the rules text without any surrounding whitespace. In order to preserve the original | ||
* formatting, we don't want to change that whitespace. | ||
@@ -88,7 +77,7 @@ * | ||
*/ | ||
const listText = node.value | ||
.slice(listStartOffset) // remove directive name and all whitespace before the list | ||
.split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists | ||
.trimEnd(); // remove all whitespace after the list | ||
const listText = parentDirective.value.trim(); | ||
// Calculate where it starts in the source code text | ||
const listStart = sourceCode.text.indexOf(listText, sourceCode.getRange(parentDirective.node)[0]); | ||
/* | ||
@@ -106,4 +95,4 @@ * We can assume that `listText` contains multiple elements. | ||
const matchedText = match[0]; | ||
const matchStartOffset = listStartOffset + match.index; | ||
const matchEndOffset = matchStartOffset + matchedText.length; | ||
const matchStart = listStart + match.index; | ||
const matchEnd = matchStart + matchedText.length; | ||
@@ -113,3 +102,3 @@ const firstIndexOfComma = matchedText.indexOf(","); | ||
let removalStartOffset, removalEndOffset; | ||
let removalStart, removalEnd; | ||
@@ -130,4 +119,4 @@ if (firstIndexOfComma !== lastIndexOfComma) { | ||
*/ | ||
removalStartOffset = matchStartOffset + firstIndexOfComma; | ||
removalEndOffset = matchStartOffset + lastIndexOfComma; | ||
removalStart = matchStart + firstIndexOfComma; | ||
removalEnd = matchStart + lastIndexOfComma; | ||
@@ -154,4 +143,4 @@ } else { | ||
*/ | ||
removalStartOffset = matchStartOffset; | ||
removalEndOffset = matchEndOffset; | ||
removalStart = matchStart; | ||
removalEnd = matchEnd; | ||
} | ||
@@ -163,4 +152,4 @@ | ||
range: [ | ||
commentValueStart + removalStartOffset, | ||
commentValueStart + removalEndOffset | ||
removalStart, | ||
removalEnd | ||
], | ||
@@ -216,3 +205,3 @@ text: "" | ||
return remainingRuleIds.size | ||
? createIndividualDirectivesRemoval(directives, parentDirective.node, sourceCode) | ||
? createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) | ||
: [createDirectiveRemoval(directives, parentDirective.node, sourceCode)]; | ||
@@ -219,0 +208,0 @@ } |
@@ -120,2 +120,3 @@ /** | ||
ImportDefaultSpecifier: true, | ||
ImportNamespaceSpecifier: true, | ||
RestElement: true, | ||
@@ -122,0 +123,0 @@ FunctionExpression: true, |
@@ -13,3 +13,3 @@ /** | ||
const validator = new RegExpValidator(); | ||
const validFlags = /[dgimsuvy]/gu; | ||
const validFlags = "dgimsuvy"; | ||
const undefined1 = void 0; | ||
@@ -53,9 +53,9 @@ | ||
const options = context.options[0]; | ||
let allowedFlags = null; | ||
let allowedFlags = []; | ||
if (options && options.allowConstructorFlags) { | ||
const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); | ||
const temp = options.allowConstructorFlags.join("").replace(new RegExp(`[${validFlags}]`, "gu"), ""); | ||
if (temp) { | ||
allowedFlags = new RegExp(`[${temp}]`, "gu"); | ||
allowedFlags = [...new Set(temp)]; | ||
} | ||
@@ -130,13 +130,16 @@ } | ||
* @param {string|null} flags The RegExp flags to validate. | ||
* @param {string|null} flagsToCheck The RegExp invalid flags. | ||
* @param {string} allFlags all valid and allowed flags. | ||
* @returns {string|null} The syntax error. | ||
*/ | ||
function validateRegExpFlags(flags) { | ||
if (!flags) { | ||
return null; | ||
function validateRegExpFlags(flags, flagsToCheck, allFlags) { | ||
const duplicateFlags = []; | ||
if (typeof flagsToCheck === "string") { | ||
for (const flag of flagsToCheck) { | ||
if (allFlags.includes(flag)) { | ||
duplicateFlags.push(flag); | ||
} | ||
} | ||
} | ||
try { | ||
validator.validateFlags(flags); | ||
} catch { | ||
return `Invalid flags supplied to RegExp constructor '${flags}'`; | ||
} | ||
@@ -148,6 +151,15 @@ /* | ||
*/ | ||
if (flags.includes("u") && flags.includes("v")) { | ||
if (flags && flags.includes("u") && flags.includes("v")) { | ||
return "Regex 'u' and 'v' flags cannot be used together"; | ||
} | ||
return null; | ||
if (duplicateFlags.length > 0) { | ||
return `Duplicate flags ('${duplicateFlags.join("")}') supplied to RegExp constructor`; | ||
} | ||
if (!flagsToCheck) { | ||
return null; | ||
} | ||
return `Invalid flags supplied to RegExp constructor '${flagsToCheck}'`; | ||
} | ||
@@ -161,9 +173,13 @@ | ||
let flags = getFlags(node); | ||
const flags = getFlags(node); | ||
let flagsToCheck = flags; | ||
const allFlags = allowedFlags.length > 0 ? validFlags.split("").concat(allowedFlags) : validFlags.split(""); | ||
if (flags && allowedFlags) { | ||
flags = flags.replace(allowedFlags, ""); | ||
if (flags) { | ||
allFlags.forEach(flag => { | ||
flagsToCheck = flagsToCheck.replace(flag, ""); | ||
}); | ||
} | ||
let message = validateRegExpFlags(flags); | ||
let message = validateRegExpFlags(flags, flagsToCheck, allFlags); | ||
@@ -170,0 +186,0 @@ if (message) { |
@@ -21,2 +21,22 @@ /** | ||
/** | ||
* Checks whether the flag configuration should be treated as a missing flag. | ||
* @param {"u"|"v"|undefined} requireFlag A particular flag to require | ||
* @param {string} flags The regex flags | ||
* @returns {boolean} Whether the flag configuration results in a missing flag. | ||
*/ | ||
function checkFlags(requireFlag, flags) { | ||
let missingFlag; | ||
if (requireFlag === "v") { | ||
missingFlag = !flags.includes("v"); | ||
} else if (requireFlag === "u") { | ||
missingFlag = !flags.includes("u"); | ||
} else { | ||
missingFlag = !flags.includes("u") && !flags.includes("v"); | ||
} | ||
return missingFlag; | ||
} | ||
//------------------------------------------------------------------------------ | ||
@@ -41,6 +61,18 @@ // Rule Definition | ||
addUFlag: "Add the 'u' flag.", | ||
requireUFlag: "Use the 'u' flag." | ||
addVFlag: "Add the 'v' flag.", | ||
requireUFlag: "Use the 'u' flag.", | ||
requireVFlag: "Use the 'v' flag." | ||
}, | ||
schema: [] | ||
schema: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
requireFlag: { | ||
enum: ["u", "v"] | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
@@ -52,2 +84,6 @@ | ||
const { | ||
requireFlag | ||
} = context.options[0] ?? {}; | ||
return { | ||
@@ -57,13 +93,31 @@ "Literal[regex]"(node) { | ||
if (!flags.includes("u") && !flags.includes("v")) { | ||
const missingFlag = checkFlags(requireFlag, flags); | ||
if (missingFlag) { | ||
context.report({ | ||
messageId: "requireUFlag", | ||
messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag", | ||
node, | ||
suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) | ||
suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern, requireFlag) | ||
? [ | ||
{ | ||
fix(fixer) { | ||
return fixer.insertTextAfter(node, "u"); | ||
const replaceFlag = requireFlag ?? "u"; | ||
const regex = sourceCode.getText(node); | ||
const slashPos = regex.lastIndexOf("/"); | ||
if (requireFlag) { | ||
const flag = requireFlag === "u" ? "v" : "u"; | ||
if (regex.includes(flag, slashPos)) { | ||
return fixer.replaceText( | ||
node, | ||
regex.slice(0, slashPos) + | ||
regex.slice(slashPos).replace(flag, requireFlag) | ||
); | ||
} | ||
} | ||
return fixer.insertTextAfter(node, replaceFlag); | ||
}, | ||
messageId: "addUFlag" | ||
messageId: requireFlag === "v" ? "addVFlag" : "addUFlag" | ||
} | ||
@@ -92,18 +146,45 @@ ] | ||
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) { | ||
let missingFlag = !flagsNode; | ||
if (typeof flags === "string") { | ||
missingFlag = checkFlags(requireFlag, flags); | ||
} | ||
if (missingFlag) { | ||
context.report({ | ||
messageId: "requireUFlag", | ||
messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag", | ||
node: refNode, | ||
suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) | ||
suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern, requireFlag) | ||
? [ | ||
{ | ||
fix(fixer) { | ||
const replaceFlag = requireFlag ?? "u"; | ||
if (flagsNode) { | ||
if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { | ||
const flagsNodeText = sourceCode.getText(flagsNode); | ||
const flag = requireFlag === "u" ? "v" : "u"; | ||
if (flags.includes(flag)) { | ||
// Avoid replacing "u" in escapes like `\uXXXX` | ||
if (flagsNode.type === "Literal" && flagsNode.raw.includes("\\")) { | ||
return null; | ||
} | ||
// Avoid replacing "u" in expressions like "`${regularFlags}g`" | ||
if (flagsNode.type === "TemplateLiteral" && ( | ||
flagsNode.expressions.length || | ||
flagsNode.quasis.some(({ value: { raw } }) => raw.includes("\\")) | ||
)) { | ||
return null; | ||
} | ||
return fixer.replaceText(flagsNode, flagsNodeText.replace(flag, replaceFlag)); | ||
} | ||
return fixer.replaceText(flagsNode, [ | ||
flagsNodeText.slice(0, flagsNodeText.length - 1), | ||
flagsNodeText.slice(flagsNodeText.length - 1) | ||
].join("u")); | ||
].join(replaceFlag)); | ||
} | ||
@@ -120,7 +201,7 @@ | ||
astUtils.isCommaToken(penultimateToken) | ||
? ' "u",' | ||
: ', "u"' | ||
? ` "${replaceFlag}",` | ||
: `, "${replaceFlag}"` | ||
); | ||
}, | ||
messageId: "addUFlag" | ||
messageId: requireFlag === "v" ? "addVFlag" : "addUFlag" | ||
} | ||
@@ -127,0 +208,0 @@ ] |
@@ -17,2 +17,3 @@ /** | ||
* @param {string} pattern The regular expression pattern to verify. | ||
* @param {"u"|"v"} flag The type of Unicode flag | ||
* @returns {boolean} `true` if the pattern would be valid with the `u` flag. | ||
@@ -22,6 +23,9 @@ * `false` if the pattern would be invalid with the `u` flag or the configured | ||
*/ | ||
function isValidWithUnicodeFlag(ecmaVersion, pattern) { | ||
if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag | ||
function isValidWithUnicodeFlag(ecmaVersion, pattern, flag = "u") { | ||
if (flag === "u" && ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag | ||
return false; | ||
} | ||
if (flag === "v" && ecmaVersion <= 2023) { | ||
return false; | ||
} | ||
@@ -33,3 +37,7 @@ const validator = new RegExpValidator({ | ||
try { | ||
validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true }); | ||
validator.validatePattern(pattern, void 0, void 0, flag === "u" ? { | ||
unicode: /* uFlag = */ true | ||
} : { | ||
unicodeSets: true | ||
}); | ||
} catch { | ||
@@ -36,0 +44,0 @@ return false; |
{ | ||
"name": "eslint", | ||
"version": "9.9.1", | ||
"version": "9.10.0", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -10,6 +10,16 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"main": "./lib/api.js", | ||
"types": "./lib/types/index.d.ts", | ||
"exports": { | ||
".": { | ||
"types": "./lib/types/index.d.ts", | ||
"default": "./lib/api.js" | ||
}, | ||
"./package.json": "./package.json", | ||
".": "./lib/api.js", | ||
"./use-at-your-own-risk": "./lib/unsupported-api.js" | ||
"./use-at-your-own-risk": { | ||
"types": "./lib/types/use-at-your-own-risk.d.ts", | ||
"default": "./lib/unsupported-api.js" | ||
}, | ||
"./rules": { | ||
"types": "./lib/types/rules/index.d.ts" | ||
} | ||
}, | ||
@@ -38,3 +48,4 @@ "scripts": { | ||
"test:performance": "node Makefile.js perf", | ||
"test:emfile": "node tools/check-emfile-handling.js" | ||
"test:emfile": "node tools/check-emfile-handling.js", | ||
"test:types": "tsc -p tests/lib/types/tsconfig.json" | ||
}, | ||
@@ -75,3 +86,4 @@ "gitHooks": { | ||
"@eslint/eslintrc": "^3.1.0", | ||
"@eslint/js": "9.9.1", | ||
"@eslint/js": "9.10.0", | ||
"@eslint/plugin-kit": "^0.1.0", | ||
"@humanwhocodes/module-importer": "^1.0.1", | ||
@@ -99,3 +111,2 @@ "@humanwhocodes/retry": "^0.3.0", | ||
"json-stable-stringify-without-jsonify": "^1.0.1", | ||
"levn": "^0.4.1", | ||
"lodash.merge": "^4.6.2", | ||
@@ -111,6 +122,7 @@ "minimatch": "^3.1.2", | ||
"@babel/preset-env": "^7.4.3", | ||
"@eslint/core": "^0.4.0", | ||
"@eslint/json": "^0.3.0", | ||
"@eslint/core": "^0.5.0", | ||
"@eslint/json": "^0.4.0", | ||
"@trunkio/launcher": "^1.3.0", | ||
"@types/estree": "^1.0.5", | ||
"@types/json-schema": "^7.0.15", | ||
"@types/node": "^20.11.5", | ||
@@ -117,0 +129,0 @@ "@wdio/browser-runner": "^9.0.5", |
@@ -300,5 +300,5 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) | ||
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3> | ||
<p><a href="#"><img src="https://images.opencollective.com/guest-bf377e88/avatar.png" alt="Eli Schleifer" height="96"></a> <a href="https://opensource.siemens.com"><img src="https://avatars.githubusercontent.com/u/624020?v=4" alt="Siemens" height="96"></a></p><h3>Silver Sponsors</h3> | ||
<p><a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a> <a href="https://opensource.siemens.com"><img src="https://avatars.githubusercontent.com/u/624020?v=4" alt="Siemens" height="96"></a></p><h3>Silver Sponsors</h3> | ||
<p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" 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> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3> | ||
<p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" 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" 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://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" 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://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p> | ||
<p><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" 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://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" 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://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p> | ||
<!--sponsorsend--> | ||
@@ -305,0 +305,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
3288062
416
78623
63
+ Added@eslint/plugin-kit@^0.1.0
+ Added@eslint/js@9.10.0(transitive)
+ Added@eslint/plugin-kit@0.1.0(transitive)
- Removedlevn@^0.4.1
- Removed@eslint/js@9.9.1(transitive)
Updated@eslint/js@9.10.0