eslint
Advanced tools
Comparing version 9.0.0-alpha.2 to 9.0.0-beta.0
@@ -12,3 +12,4 @@ /** | ||
const { ESLint } = require("./eslint/eslint"); | ||
const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"); | ||
const { LegacyESLint } = require("./eslint/legacy-eslint"); | ||
const { Linter } = require("./linter"); | ||
@@ -19,2 +20,24 @@ const { RuleTester } = require("./rule-tester"); | ||
//----------------------------------------------------------------------------- | ||
// Functions | ||
//----------------------------------------------------------------------------- | ||
/** | ||
* Loads the correct ESLint constructor given the options. | ||
* @param {Object} [options] The options object | ||
* @param {boolean} [options.useFlatConfig] Whether or not to use a flat config | ||
* @returns {Promise<ESLint|LegacyESLint>} The ESLint constructor | ||
*/ | ||
async function loadESLint({ useFlatConfig } = {}) { | ||
/* | ||
* Note: The v8.x version of this function also accepted a `cwd` option, but | ||
* it is not used in this implementation so we silently ignore it. | ||
*/ | ||
const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig()); | ||
return shouldESLintUseFlatConfig ? ESLint : LegacyESLint; | ||
} | ||
//----------------------------------------------------------------------------- | ||
// Exports | ||
@@ -25,2 +48,3 @@ //----------------------------------------------------------------------------- | ||
Linter, | ||
loadESLint, | ||
ESLint, | ||
@@ -27,0 +51,0 @@ RuleTester, |
@@ -569,2 +569,8 @@ /** | ||
/** | ||
* The type of configuration used by this class. | ||
* @type {string} | ||
*/ | ||
static configType = "flat"; | ||
/** | ||
* Creates a new instance of the main ESLint API. | ||
@@ -571,0 +577,0 @@ * @param {ESLintOptions} options The options for this instance. |
@@ -442,2 +442,8 @@ /** | ||
/** | ||
* The type of configuration used by this class. | ||
* @type {string} | ||
*/ | ||
static configType = "eslintrc"; | ||
/** | ||
* Creates a new instance of the main ESLint API. | ||
@@ -444,0 +450,0 @@ * @param {LegacyESLintOptions} options The options for this instance. |
"use strict"; | ||
const { Linter } = require("./linter"); | ||
const interpolate = require("./interpolate"); | ||
const { interpolate } = require("./interpolate"); | ||
const SourceCodeFixer = require("./source-code-fixer"); | ||
@@ -6,0 +6,0 @@ |
@@ -12,3 +12,18 @@ /** | ||
module.exports = (text, data) => { | ||
/** | ||
* Returns a global expression matching placeholders in messages. | ||
* @returns {RegExp} Global regular expression matching placeholders | ||
*/ | ||
function getPlaceholderMatcher() { | ||
return /\{\{([^{}]+?)\}\}/gu; | ||
} | ||
/** | ||
* Replaces {{ placeholders }} in the message with the provided data. | ||
* Does not replace placeholders not available in the data. | ||
* @param {string} text Original message with potential placeholders | ||
* @param {Record<string, string>} data Map of placeholder name to its value | ||
* @returns {string} Message with replaced placeholders | ||
*/ | ||
function interpolate(text, data) { | ||
if (!data) { | ||
@@ -18,4 +33,6 @@ return text; | ||
const matcher = getPlaceholderMatcher(); | ||
// Substitution content for any {{ }} markers. | ||
return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => { | ||
return text.replace(matcher, (fullMatch, termWithWhitespace) => { | ||
const term = termWithWhitespace.trim(); | ||
@@ -30,2 +47,7 @@ | ||
}); | ||
} | ||
module.exports = { | ||
getPlaceholderMatcher, | ||
interpolate | ||
}; |
@@ -14,3 +14,3 @@ /** | ||
const ruleFixer = require("./rule-fixer"); | ||
const interpolate = require("./interpolate"); | ||
const { interpolate } = require("./interpolate"); | ||
@@ -17,0 +17,0 @@ //------------------------------------------------------------------------------ |
@@ -20,3 +20,4 @@ /** | ||
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"), | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"), | ||
{ Linter, SourceCodeFixer } = require("../linter"), | ||
{ interpolate, getPlaceholderMatcher } = require("../linter/interpolate"), | ||
stringify = require("json-stable-stringify-without-jsonify"); | ||
@@ -308,2 +309,35 @@ | ||
/** | ||
* Extracts names of {{ placeholders }} from the reported message. | ||
* @param {string} message Reported message | ||
* @returns {string[]} Array of placeholder names | ||
*/ | ||
function getMessagePlaceholders(message) { | ||
const matcher = getPlaceholderMatcher(); | ||
return Array.from(message.matchAll(matcher), ([, name]) => name.trim()); | ||
} | ||
/** | ||
* Returns the placeholders in the reported messages but | ||
* only includes the placeholders available in the raw message and not in the provided data. | ||
* @param {string} message The reported message | ||
* @param {string} raw The raw message specified in the rule meta.messages | ||
* @param {undefined|Record<unknown, unknown>} data The passed | ||
* @returns {string[]} Missing placeholder names | ||
*/ | ||
function getUnsubstitutedMessagePlaceholders(message, raw, data = {}) { | ||
const unsubstituted = getMessagePlaceholders(message); | ||
if (unsubstituted.length === 0) { | ||
return []; | ||
} | ||
// Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property | ||
const known = getMessagePlaceholders(raw); | ||
const provided = Object.keys(data); | ||
return unsubstituted.filter(name => known.includes(name) && !provided.includes(name)); | ||
} | ||
const metaSchemaDescription = ` | ||
@@ -650,3 +684,7 @@ \t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation. | ||
if (item.filename) { | ||
if (hasOwnProperty(item, "only")) { | ||
assert.ok(typeof item.only === "boolean", "Optional test case property 'only' must be a boolean"); | ||
} | ||
if (hasOwnProperty(item, "filename")) { | ||
assert.ok(typeof item.filename === "string", "Optional test case property 'filename' must be a string"); | ||
filename = item.filename; | ||
@@ -966,2 +1004,3 @@ } | ||
assertMessageMatches(message.message, error); | ||
assert.ok(message.suggestions === void 0, `Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`); | ||
} else if (typeof error === "object" && error !== null) { | ||
@@ -999,2 +1038,14 @@ | ||
); | ||
const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( | ||
message.message, | ||
rule.meta.messages[message.messageId], | ||
error.data | ||
); | ||
assert.ok( | ||
unsubstitutedPlaceholders.length === 0, | ||
`The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.` | ||
); | ||
if (hasOwnProperty(error, "data")) { | ||
@@ -1016,9 +1067,6 @@ | ||
} | ||
} else { | ||
assert.fail("Test error must specify either a 'messageId' or 'message'."); | ||
} | ||
assert.ok( | ||
hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true, | ||
"Error must specify 'messageId' if 'data' is used." | ||
); | ||
if (error.type) { | ||
@@ -1044,72 +1092,94 @@ assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`); | ||
assert.ok(!message.suggestions || hasOwnProperty(error, "suggestions"), `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`); | ||
if (hasOwnProperty(error, "suggestions")) { | ||
// Support asserting there are no suggestions | ||
if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) { | ||
if (Array.isArray(message.suggestions) && message.suggestions.length > 0) { | ||
assert.fail(`Error should have no suggestions on error with message: "${message.message}"`); | ||
} | ||
} else { | ||
assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`); | ||
assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); | ||
const expectsSuggestions = Array.isArray(error.suggestions) ? error.suggestions.length > 0 : Boolean(error.suggestions); | ||
const hasSuggestions = message.suggestions !== void 0; | ||
error.suggestions.forEach((expectedSuggestion, index) => { | ||
assert.ok( | ||
typeof expectedSuggestion === "object" && expectedSuggestion !== null, | ||
"Test suggestion in 'suggestions' array must be an object." | ||
); | ||
Object.keys(expectedSuggestion).forEach(propertyName => { | ||
assert.ok( | ||
suggestionObjectParameters.has(propertyName), | ||
`Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` | ||
); | ||
}); | ||
if (!hasSuggestions && expectsSuggestions) { | ||
assert.ok(!error.suggestions, `Error should have suggestions on error with message: "${message.message}"`); | ||
} else if (hasSuggestions) { | ||
assert.ok(expectsSuggestions, `Error should have no suggestions on error with message: "${message.message}"`); | ||
if (typeof error.suggestions === "number") { | ||
assert.strictEqual(message.suggestions.length, error.suggestions, `Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`); | ||
} else if (Array.isArray(error.suggestions)) { | ||
assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); | ||
const actualSuggestion = message.suggestions[index]; | ||
const suggestionPrefix = `Error Suggestion at index ${index} :`; | ||
if (hasOwnProperty(expectedSuggestion, "desc")) { | ||
error.suggestions.forEach((expectedSuggestion, index) => { | ||
assert.ok( | ||
!hasOwnProperty(expectedSuggestion, "data"), | ||
`${suggestionPrefix} Test should not specify both 'desc' and 'data'.` | ||
typeof expectedSuggestion === "object" && expectedSuggestion !== null, | ||
"Test suggestion in 'suggestions' array must be an object." | ||
); | ||
assert.strictEqual( | ||
actualSuggestion.desc, | ||
expectedSuggestion.desc, | ||
`${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` | ||
); | ||
} | ||
Object.keys(expectedSuggestion).forEach(propertyName => { | ||
assert.ok( | ||
suggestionObjectParameters.has(propertyName), | ||
`Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` | ||
); | ||
}); | ||
if (hasOwnProperty(expectedSuggestion, "messageId")) { | ||
assert.ok( | ||
ruleHasMetaMessages, | ||
`${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` | ||
); | ||
assert.ok( | ||
hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), | ||
`${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` | ||
); | ||
assert.strictEqual( | ||
actualSuggestion.messageId, | ||
expectedSuggestion.messageId, | ||
`${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` | ||
); | ||
if (hasOwnProperty(expectedSuggestion, "data")) { | ||
const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; | ||
const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); | ||
const actualSuggestion = message.suggestions[index]; | ||
const suggestionPrefix = `Error Suggestion at index ${index}:`; | ||
if (hasOwnProperty(expectedSuggestion, "desc")) { | ||
assert.ok( | ||
!hasOwnProperty(expectedSuggestion, "data"), | ||
`${suggestionPrefix} Test should not specify both 'desc' and 'data'.` | ||
); | ||
assert.ok( | ||
!hasOwnProperty(expectedSuggestion, "messageId"), | ||
`${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.` | ||
); | ||
assert.strictEqual( | ||
actualSuggestion.desc, | ||
rehydratedDesc, | ||
`${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` | ||
expectedSuggestion.desc, | ||
`${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` | ||
); | ||
} else if (hasOwnProperty(expectedSuggestion, "messageId")) { | ||
assert.ok( | ||
ruleHasMetaMessages, | ||
`${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` | ||
); | ||
assert.ok( | ||
hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), | ||
`${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` | ||
); | ||
assert.strictEqual( | ||
actualSuggestion.messageId, | ||
expectedSuggestion.messageId, | ||
`${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` | ||
); | ||
const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( | ||
actualSuggestion.desc, | ||
rule.meta.messages[expectedSuggestion.messageId], | ||
expectedSuggestion.data | ||
); | ||
assert.ok( | ||
unsubstitutedPlaceholders.length === 0, | ||
`The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.` | ||
); | ||
if (hasOwnProperty(expectedSuggestion, "data")) { | ||
const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; | ||
const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); | ||
assert.strictEqual( | ||
actualSuggestion.desc, | ||
rehydratedDesc, | ||
`${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` | ||
); | ||
} | ||
} else if (hasOwnProperty(expectedSuggestion, "data")) { | ||
assert.fail( | ||
`${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` | ||
); | ||
} else { | ||
assert.fail( | ||
`${suggestionPrefix} Test must specify either 'messageId' or 'desc'.` | ||
); | ||
} | ||
} else { | ||
assert.ok( | ||
!hasOwnProperty(expectedSuggestion, "data"), | ||
`${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` | ||
); | ||
} | ||
if (hasOwnProperty(expectedSuggestion, "output")) { | ||
assert.ok(hasOwnProperty(expectedSuggestion, "output"), `${suggestionPrefix} The "output" property is required.`); | ||
const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output; | ||
@@ -1119,3 +1189,3 @@ | ||
const errorMessageInSuggestion = | ||
linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal); | ||
linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal); | ||
@@ -1130,4 +1200,7 @@ assert(!errorMessageInSuggestion, [ | ||
assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`); | ||
} | ||
}); | ||
assert.notStrictEqual(expectedSuggestion.output, item.code, `The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`); | ||
}); | ||
} else { | ||
assert.fail("Test error object property 'suggestions' should be an array or a number"); | ||
} | ||
} | ||
@@ -1152,2 +1225,3 @@ } | ||
assert.strictEqual(result.output, item.output, "Output is incorrect."); | ||
assert.notStrictEqual(item.code, item.output, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null."); | ||
} | ||
@@ -1154,0 +1228,0 @@ } else { |
@@ -95,3 +95,3 @@ /** | ||
ignoreRestSiblings: false, | ||
caughtErrors: "none" | ||
caughtErrors: "all" | ||
}; | ||
@@ -98,0 +98,0 @@ |
@@ -104,3 +104,3 @@ /** | ||
type: "boolean", | ||
default: false | ||
default: true | ||
} | ||
@@ -118,3 +118,3 @@ }, | ||
const sourceCode = context.sourceCode; | ||
const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers; | ||
const enforceForClassMembers = context.options[0]?.enforceForClassMembers ?? true; | ||
@@ -121,0 +121,0 @@ /** |
@@ -24,5 +24,13 @@ /** | ||
function isNaNIdentifier(node) { | ||
return Boolean(node) && ( | ||
astUtils.isSpecificId(node, "NaN") || | ||
astUtils.isSpecificMemberAccess(node, "Number", "NaN") | ||
if (!node) { | ||
return false; | ||
} | ||
const nodeToCheck = node.type === "SequenceExpression" | ||
? node.expressions.at(-1) | ||
: node; | ||
return ( | ||
astUtils.isSpecificId(nodeToCheck, "NaN") || | ||
astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN") | ||
); | ||
@@ -38,2 +46,3 @@ } | ||
meta: { | ||
hasSuggestions: true, | ||
type: "problem", | ||
@@ -68,3 +77,5 @@ | ||
caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.", | ||
indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN." | ||
indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.", | ||
replaceWithIsNaN: "Replace with Number.isNaN.", | ||
replaceWithCastingAndIsNaN: "Replace with Number.isNaN cast to a Number." | ||
} | ||
@@ -77,4 +88,33 @@ }, | ||
const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf; | ||
const sourceCode = context.sourceCode; | ||
const fixableOperators = new Set(["==", "===", "!=", "!=="]); | ||
const castableOperators = new Set(["==", "!="]); | ||
/** | ||
* Get a fixer for a binary expression that compares to NaN. | ||
* @param {ASTNode} node The node to fix. | ||
* @param {function(string): string} wrapValue A function that wraps the compared value with a fix. | ||
* @returns {function(Fixer): Fix} The fixer function. | ||
*/ | ||
function getBinaryExpressionFixer(node, wrapValue) { | ||
return fixer => { | ||
const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left; | ||
const shouldWrap = comparedValue.type === "SequenceExpression"; | ||
const shouldNegate = node.operator[0] === "!"; | ||
const negation = shouldNegate ? "!" : ""; | ||
let comparedValueText = sourceCode.getText(comparedValue); | ||
if (shouldWrap) { | ||
comparedValueText = `(${comparedValueText})`; | ||
} | ||
const fixedValue = wrapValue(comparedValueText); | ||
return fixer.replaceText(node, `${negation}${fixedValue}`); | ||
}; | ||
} | ||
/** | ||
* Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons. | ||
@@ -89,3 +129,28 @@ * @param {ASTNode} node The node to check. | ||
) { | ||
context.report({ node, messageId: "comparisonWithNaN" }); | ||
const suggestedFixes = []; | ||
const NaNNode = isNaNIdentifier(node.left) ? node.left : node.right; | ||
const isSequenceExpression = NaNNode.type === "SequenceExpression"; | ||
const isFixable = fixableOperators.has(node.operator) && !isSequenceExpression; | ||
const isCastable = castableOperators.has(node.operator); | ||
if (isFixable) { | ||
suggestedFixes.push({ | ||
messageId: "replaceWithIsNaN", | ||
fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`) | ||
}); | ||
if (isCastable) { | ||
suggestedFixes.push({ | ||
messageId: "replaceWithCastingAndIsNaN", | ||
fix: getBinaryExpressionFixer(node, value => `Number.isNaN(Number(${value}))`) | ||
}); | ||
} | ||
} | ||
context.report({ | ||
node, | ||
messageId: "comparisonWithNaN", | ||
suggest: suggestedFixes | ||
}); | ||
} | ||
@@ -92,0 +157,0 @@ } |
{ | ||
"name": "eslint", | ||
"version": "9.0.0-alpha.2", | ||
"version": "9.0.0-beta.0", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -68,4 +68,4 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"@eslint-community/regexpp": "^4.6.1", | ||
"@eslint/eslintrc": "^3.0.0", | ||
"@eslint/js": "9.0.0-alpha.2", | ||
"@eslint/eslintrc": "^3.0.1", | ||
"@eslint/js": "9.0.0-beta.0", | ||
"@humanwhocodes/config-array": "^0.11.14", | ||
@@ -80,8 +80,8 @@ "@humanwhocodes/module-importer": "^1.0.1", | ||
"eslint-scope": "^8.0.0", | ||
"eslint-visitor-keys": "^3.4.3", | ||
"espree": "^10.0.0", | ||
"eslint-visitor-keys": "^4.0.0", | ||
"espree": "^10.0.1", | ||
"esquery": "^1.4.2", | ||
"esutils": "^2.0.2", | ||
"fast-deep-equal": "^3.1.3", | ||
"file-entry-cache": "^6.0.1", | ||
"file-entry-cache": "^8.0.0", | ||
"find-up": "^5.0.0", | ||
@@ -141,3 +141,3 @@ "glob-parent": "^6.0.2", | ||
"markdownlint": "^0.33.0", | ||
"markdownlint-cli": "^0.38.0", | ||
"markdownlint-cli": "^0.39.0", | ||
"marked": "^4.0.8", | ||
@@ -161,3 +161,3 @@ "memfs": "^3.0.1", | ||
"semver": "^7.5.3", | ||
"shelljs": "^0.8.2", | ||
"shelljs": "^0.8.5", | ||
"sinon": "^11.0.0", | ||
@@ -164,0 +164,0 @@ "vite-plugin-commonjs": "^0.10.0", |
@@ -258,7 +258,2 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) | ||
</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" alt="Yosuke Ota's Avatar"><br /> | ||
Yosuke Ota | ||
</a> | ||
</td><td align="center" valign="top" width="11%"> | ||
<a href="https://github.com/Tanujkanti4441"> | ||
@@ -303,3 +298,3 @@ <img src="https://github.com/Tanujkanti4441.png?s=75" width="75" height="75" alt="Tanuj Kanti's Avatar"><br /> | ||
<p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/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://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" 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://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://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></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://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://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://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--> | ||
@@ -306,0 +301,0 @@ |
2983414
70230
305
+ Added@eslint/js@9.0.0-beta.0(transitive)
- Removed@eslint/js@9.0.0-alpha.2(transitive)
- Removedfile-entry-cache@6.0.1(transitive)
- Removedflat-cache@3.2.0(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedglob@7.2.3(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinherits@2.0.4(transitive)
- Removedonce@1.4.0(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedrimraf@3.0.2(transitive)
- Removedwrappy@1.0.2(transitive)
Updated@eslint/eslintrc@^3.0.1
Updated@eslint/js@9.0.0-beta.0
Updatedeslint-visitor-keys@^4.0.0
Updatedespree@^10.0.1
Updatedfile-entry-cache@^8.0.0