diff --git a/packages/eslint-plugin-react-hooks/babel.config.js b/packages/eslint-plugin-react-hooks/babel.config.js new file mode 100644 index 0000000000000..3b947a7163bcb --- /dev/null +++ b/packages/eslint-plugin-react-hooks/babel.config.js @@ -0,0 +1,8 @@ +/** + * This file is purely being used for local jest runs, and doesn't participate in the build process. + */ +'use strict'; + +module.exports = { + extends: '../../babel.config-ts.js', +}; diff --git a/packages/eslint-plugin-react-hooks/index.js b/packages/eslint-plugin-react-hooks/index.js index 754dc9f9c7f23..ce26a10c31518 100644 --- a/packages/eslint-plugin-react-hooks/index.js +++ b/packages/eslint-plugin-react-hooks/index.js @@ -1,8 +1 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export * from './src/index'; +module.exports = require('./src/index.ts'); diff --git a/packages/eslint-plugin-react-hooks/jest.config.js b/packages/eslint-plugin-react-hooks/jest.config.js new file mode 100644 index 0000000000000..a7b91c3ef1cbe --- /dev/null +++ b/packages/eslint-plugin-react-hooks/jest.config.js @@ -0,0 +1,8 @@ +'use strict'; + +process.env.NODE_ENV = 'development'; + +module.exports = { + setupFiles: [require.resolve('../../scripts/jest/setupEnvironment.js')], + moduleFileExtensions: ['ts', 'js', 'json'], +}; diff --git a/packages/eslint-plugin-react-hooks/npm/index.d.ts b/packages/eslint-plugin-react-hooks/npm/index.d.ts new file mode 100644 index 0000000000000..62ca164ec2173 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/npm/index.d.ts @@ -0,0 +1 @@ +export * from './cjs/eslint-plugin-react-hooks'; diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index b45609362faac..d3717b9dee61c 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -10,8 +10,9 @@ "files": [ "LICENSE", "README.md", + "cjs", "index.js", - "cjs" + "index.d.ts" ], "keywords": [ "eslint", @@ -19,10 +20,16 @@ "eslintplugin", "react" ], + "scripts": { + "test": "jest", + "typecheck": "tsc --noEmit" + }, "license": "MIT", "bugs": { "url": "https://github.com/facebook/react/issues" }, + "main": "./index.js", + "types": "./index.d.ts", "engines": { "node": ">=10" }, @@ -32,6 +39,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.11.4", + "@babel/preset-typescript": "^7.26.0", "@tsconfig/strictest": "^2.0.5", "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", @@ -45,7 +53,6 @@ "eslint-v7": "npm:eslint@^7.7.0", "eslint-v9": "npm:eslint@^9.0.0", "jest": "^29.5.0", - "tsup": "^8.3.5", "typescript": "^5.4.3" } } diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts similarity index 78% rename from packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js rename to packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts index b2818327ff9e4..9f3d4d4db9004 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts @@ -4,12 +4,41 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - /* eslint-disable no-for-of-loops/no-for-of-loops */ +import type {Rule, Scope} from 'eslint'; +import type { + ArrayExpression, + ArrowFunctionExpression, + CallExpression, + Expression, + FunctionDeclaration, + FunctionExpression, + Identifier, + Node, + Pattern, + PrivateIdentifier, + Super, + VariableDeclarator, +} from 'estree'; + +type DeclaredDependency = { + key: string; + node: Node; +}; -'use strict'; +type Dependency = { + isStable: boolean; + references: Scope.Reference[]; +}; + +type DependencyTreeNode = { + isUsed: boolean; // True if used in code + isSatisfiedRecursively: boolean; // True if specified in deps + isSubtreeUsed: boolean; // True if something deeper is used by code + children: Map; // Nodes for properties +}; -export default { +const rule = { meta: { type: 'suggestion', docs: { @@ -36,7 +65,7 @@ export default { }, ], }, - create(context) { + create(context: Rule.RuleContext) { // Parse the `additionalHooks` regex. const additionalHooks = context.options && @@ -45,7 +74,7 @@ export default { ? new RegExp(context.options[0].additionalHooks) : undefined; - const enableDangerousAutofixThisMayCauseInfiniteLoops = + const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean = (context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) || @@ -56,11 +85,15 @@ export default { enableDangerousAutofixThisMayCauseInfiniteLoops, }; - function reportProblem(problem) { + function reportProblem(problem: Rule.ReportDescriptor) { if (enableDangerousAutofixThisMayCauseInfiniteLoops) { // Used to enable legacy behavior. Dangerous. // Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension). - if (Array.isArray(problem.suggest) && problem.suggest.length > 0) { + if ( + Array.isArray(problem.suggest) && + problem.suggest.length > 0 && + problem.suggest[0] + ) { problem.fix = problem.suggest[0].fix; } } @@ -68,15 +101,15 @@ export default { } /** - * SourceCode#getText that also works down to ESLint 3.0.0 + * SourceCode that also works down to ESLint 3.0.0 */ - const getSource = - typeof context.getSource === 'function' - ? node => { - return context.getSource(node); + const getSourceCode = + typeof context.getSourceCode === 'function' + ? () => { + return context.getSourceCode(); } - : node => { - return context.sourceCode.getText(node); + : () => { + return context.sourceCode; }; /** * SourceCode#getScope that also works down to ESLint 3.0.0 @@ -86,24 +119,34 @@ export default { ? () => { return context.getScope(); } - : node => { + : (node: Node) => { return context.sourceCode.getScope(node); }; - const scopeManager = context.getSourceCode().scopeManager; + const scopeManager = getSourceCode().scopeManager; // Should be shared between visitors. - const setStateCallSites = new WeakMap(); - const stateVariables = new WeakSet(); - const stableKnownValueCache = new WeakMap(); - const functionWithoutCapturedValueCache = new WeakMap(); - const useEffectEventVariables = new WeakSet(); - function memoizeWithWeakMap(fn, map) { - return function (arg) { + const setStateCallSites = new WeakMap< + Expression | Super, + Pattern | null | undefined + >(); + const stateVariables = new WeakSet(); + const stableKnownValueCache = new WeakMap(); + const functionWithoutCapturedValueCache = new WeakMap< + Scope.Variable, + boolean + >(); + const useEffectEventVariables = new WeakSet(); + + function memoizeWithWeakMap( + fn: (resolved: Scope.Variable) => boolean, + map: WeakMap, + ) { + return function (arg: Scope.Variable): boolean { if (map.has(arg)) { // to verify cache hits: // console.log(arg.name) - return map.get(arg); + return map.get(arg)!; } const result = fn(arg); map.set(arg, result); @@ -114,12 +157,12 @@ export default { * Visitor for both function expressions and arrow function expressions. */ function visitFunctionWithDependencies( - node, - declaredDependenciesNode, - reactiveHook, - reactiveHookName, - isEffect, - ) { + node: ArrowFunctionExpression | FunctionDeclaration | FunctionExpression, + declaredDependenciesNode: Node | undefined, + reactiveHook: Node, + reactiveHookName: string, + isEffect: boolean, + ): void { if (isEffect && node.async) { reportProblem({ node: node, @@ -140,6 +183,9 @@ export default { // Get the current scope. const scope = scopeManager.acquire(node); + if (!scope) { + return; + } // Find all our "pure scopes". On every re-render of a component these // pure scopes may have changes to the variables declared within. So all @@ -150,7 +196,7 @@ export default { // scope. We can't enforce this in a lint so we trust that all variables // declared outside of pure scope are indeed frozen. const pureScopes = new Set(); - let componentScope = null; + let componentScope: Scope.Scope | null = null; { let currentScope = scope.upper; while (currentScope) { @@ -186,7 +232,7 @@ export default { // const onStuff = useEffectEvent(() => {}) // ^^^ true for this reference // False for everything else. - function isStableKnownHookValue(resolved) { + function isStableKnownHookValue(resolved: Scope.Variable): boolean { if (!isArray(resolved.defs)) { return false; } @@ -195,10 +241,11 @@ export default { return false; } // Look for `let stuff = ...` - if (def.node.type !== 'VariableDeclarator') { + const defNode: VariableDeclarator = def.node; + if (defNode.type !== 'VariableDeclarator') { return false; } - let init = def.node.init; + let init = defNode.init; if (init == null) { return false; } @@ -207,8 +254,8 @@ export default { } // Detect primitive constants // const foo = 42 - let declaration = def.node.parent; - if (declaration == null) { + let declaration = defNode.parent; + if (declaration == null && componentScope) { // This might happen if variable is declared after the callback. // In that case ESLint won't set up .parent refs. // So we'll set them up manually. @@ -219,6 +266,8 @@ export default { } } if ( + declaration && + 'kind' in declaration && declaration.kind === 'const' && init.type === 'Literal' && (typeof init.value === 'string' || @@ -233,10 +282,11 @@ export default { if (init.type !== 'CallExpression') { return false; } - let callee = init.callee; + let callee: Expression | PrivateIdentifier | Super = init.callee; // Step into `= React.something` initializer. if ( callee.type === 'MemberExpression' && + 'name' in callee.object && callee.object.name === 'React' && callee.property != null && !callee.computed @@ -246,7 +296,8 @@ export default { if (callee.type !== 'Identifier') { return false; } - const id = def.node.id; + const definitionNode: VariableDeclarator = def.node; + const id = definitionNode.id; const {name} = callee; if (name === 'useRef' && id.type === 'Identifier') { // useRef() return value is stable. @@ -256,6 +307,7 @@ export default { id.type === 'Identifier' ) { for (const ref of resolved.references) { + // @ts-expect-error These types are not compatible (Reference and Identifier) if (ref !== id) { useEffectEventVariables.add(ref.identifier); } @@ -278,17 +330,14 @@ export default { if (name === 'useState') { const references = resolved.references; let writeCount = 0; - for (let i = 0; i < references.length; i++) { - if (references[i].isWrite()) { + for (const reference of references) { + if (reference.isWrite()) { writeCount++; } if (writeCount > 1) { return false; } - setStateCallSites.set( - references[i].identifier, - id.elements[0], - ); + setStateCallSites.set(reference.identifier, id.elements[0]); } } // Setter is stable. @@ -296,8 +345,8 @@ export default { } else if (id.elements[0] === resolved.identifiers[0]) { if (name === 'useState') { const references = resolved.references; - for (let i = 0; i < references.length; i++) { - stateVariables.add(references[i].identifier); + for (const reference of references) { + stateVariables.add(reference.identifier); } } // State variable itself is dynamic. @@ -323,7 +372,9 @@ export default { } // Some are just functions that don't reference anything dynamic. - function isFunctionWithoutCapturedValues(resolved) { + function isFunctionWithoutCapturedValues( + resolved: Scope.Variable, + ): boolean { if (!isArray(resolved.defs)) { return false; } @@ -336,12 +387,10 @@ export default { } // Search the direct component subscopes for // top-level function definitions matching this reference. - const fnNode = def.node; - const childScopes = componentScope.childScopes; + const fnNode: Node = def.node; + const childScopes = componentScope?.childScopes || []; let fnScope = null; - let i; - for (i = 0; i < childScopes.length; i++) { - const childScope = childScopes[i]; + for (const childScope of childScopes) { const childScopeBlock = childScope.block; if ( // function handleChange() {} @@ -362,8 +411,7 @@ export default { } // Does this function capture any values // that are in pure scopes (aka render)? - for (i = 0; i < fnScope.through.length; i++) { - const ref = fnScope.through[i]; + for (const ref of fnScope.through) { if (ref.resolved == null) { continue; } @@ -392,15 +440,21 @@ export default { ); // These are usually mistaken. Collect them. - const currentRefsInEffectCleanup = new Map(); + const currentRefsInEffectCleanup = new Map< + string, + { + reference: Scope.Reference; + dependencyNode: Identifier; + } + >(); // Is this reference inside a cleanup function for this effect node? // We can check by traversing scopes upwards from the reference, and checking // if the last "return () => " we encounter is located directly inside the effect. - function isInsideEffectCleanup(reference) { - let curScope = reference.from; + function isInsideEffectCleanup(reference: Scope.Reference): boolean { + let curScope: Scope.Scope | null = reference.from; let isInReturnedFunction = false; - while (curScope.block !== node) { + while (curScope && curScope.block !== node) { if (curScope.type === 'function') { isInReturnedFunction = curScope.block.parent != null && @@ -413,11 +467,11 @@ export default { // Get dependencies from all our resolved references in pure scopes. // Key is dependency string, value is whether it's stable. - const dependencies = new Map(); - const optionalChains = new Map(); + const dependencies = new Map(); + const optionalChains = new Map(); gatherDependenciesRecursively(scope); - function gatherDependenciesRecursively(currentScope) { + function gatherDependenciesRecursively(currentScope: Scope.Scope): void { for (const reference of currentScope.references) { // If this reference is not resolved or it is not declared in a pure // scope then we don't care about this reference. @@ -434,6 +488,9 @@ export default { node, reference.identifier, ); + if (referenceNode == null) { + continue; + } const dependencyNode = getDependency(referenceNode); const dependency = analyzePropertyChain( dependencyNode, @@ -446,8 +503,8 @@ export default { isEffect && // ... and this look like accessing .current... dependencyNode.type === 'Identifier' && - (dependencyNode.parent.type === 'MemberExpression' || - dependencyNode.parent.type === 'OptionalMemberExpression') && + (dependencyNode.parent?.type === 'MemberExpression' || + dependencyNode.parent?.type === 'OptionalMemberExpression') && !dependencyNode.parent.computed && dependencyNode.parent.property.type === 'Identifier' && dependencyNode.parent.property.name === 'current' && @@ -461,8 +518,8 @@ export default { } if ( - dependencyNode.parent.type === 'TSTypeQuery' || - dependencyNode.parent.type === 'TSTypeReference' + dependencyNode.parent?.type === 'TSTypeQuery' || + dependencyNode.parent?.type === 'TSTypeReference' ) { continue; } @@ -472,10 +529,11 @@ export default { continue; } // Ignore references to the function itself as it's not defined yet. - if (def.node != null && def.node.init === node.parent) { + if (def.node && def.node.init === node.parent) { continue; } // Ignore Flow type parameters + // @ts-expect-error We don't have flow types if (def.type === 'TypeParameter') { continue; } @@ -492,7 +550,7 @@ export default { references: [reference], }); } else { - dependencies.get(dependency).references.push(reference); + dependencies.get(dependency)?.references.push(reference); } } @@ -504,12 +562,12 @@ export default { // Warn about accessing .current in cleanup effects. currentRefsInEffectCleanup.forEach( ({reference, dependencyNode}, dependency) => { - const references = reference.resolved.references; + const references = reference.resolved?.references || []; // Is React managing this ref or us? // Let's see if we can find a .current assignment. let foundCurrentAssignment = false; - for (let i = 0; i < references.length; i++) { - const {identifier} = references[i]; + for (const reference of references) { + const {identifier} = reference; const {parent} = identifier; if ( parent != null && @@ -520,7 +578,7 @@ export default { parent.property.type === 'Identifier' && parent.property.name === 'current' && // ref.current = - parent.parent.type === 'AssignmentExpression' && + parent.parent?.type === 'AssignmentExpression' && parent.parent.left === parent ) { foundCurrentAssignment = true; @@ -532,6 +590,7 @@ export default { return; } reportProblem({ + // @ts-expect-error We can do better here (dependencyNode.parent has not been type narrowed) node: dependencyNode.parent.property, message: `The ref value '${dependency}.current' will likely have ` + @@ -545,8 +604,8 @@ export default { // Warn about assigning to variables in the outer scope. // Those are usually bugs. - const staleAssignments = new Set(); - function reportStaleAssignment(writeExpr, key) { + const staleAssignments = new Set(); + function reportStaleAssignment(writeExpr: Node, key: string): void { if (staleAssignments.has(key)) { return; } @@ -555,16 +614,16 @@ export default { node: writeExpr, message: `Assignments to the '${key}' variable from inside React Hook ` + - `${getSource(reactiveHook)} will be lost after each ` + + `${getSourceCode().getText(reactiveHook)} will be lost after each ` + `render. To preserve the value over time, store it in a useRef ` + `Hook and keep the mutable value in the '.current' property. ` + `Otherwise, you can move this variable directly inside ` + - `${getSource(reactiveHook)}.`, + `${getSourceCode().getText(reactiveHook)}.`, }); } // Remember which deps are stable and report bad usage first. - const stableDependencies = new Set(); + const stableDependencies = new Set(); dependencies.forEach(({isStable, references}, key) => { if (isStable) { stableDependencies.add(key); @@ -584,8 +643,8 @@ export default { if (!declaredDependenciesNode) { // Check if there are any top-level setState() calls. // Those tend to lead to infinite loops. - let setStateInsideEffectWithoutDeps = null; - dependencies.forEach(({isStable, references}, key) => { + let setStateInsideEffectWithoutDeps: string | null = null; + dependencies.forEach(({references}, key) => { if (setStateInsideEffectWithoutDeps) { return; } @@ -600,11 +659,11 @@ export default { return; } - let fnScope = reference.from; - while (fnScope.type !== 'function') { + let fnScope: Scope.Scope | null = reference.from; + while (fnScope && fnScope.type !== 'function') { fnScope = fnScope.upper; } - const isDirectlyInsideEffect = fnScope.block === node; + const isDirectlyInsideEffect = fnScope?.block === node; if (isDirectlyInsideEffect) { // TODO: we could potentially ignore early returns. setStateInsideEffectWithoutDeps = key; @@ -616,7 +675,7 @@ export default { dependencies, declaredDependencies: [], stableDependencies, - externalDependencies: new Set(), + externalDependencies: new Set(), isEffect: true, }); reportProblem({ @@ -645,8 +704,8 @@ export default { return; } - const declaredDependencies = []; - const externalDependencies = new Set(); + const declaredDependencies: DeclaredDependency[] = []; + const externalDependencies = new Set(); const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression'; const isTSAsArrayExpression = @@ -660,7 +719,7 @@ export default { reportProblem({ node: declaredDependenciesNode, message: - `React Hook ${getSource(reactiveHook)} was passed a ` + + `React Hook ${getSourceCode().getText(reactiveHook)} was passed a ` + 'dependency list that is not an array literal. This means we ' + "can't statically verify whether you've passed the correct " + 'dependencies.', @@ -670,108 +729,117 @@ export default { ? declaredDependenciesNode.expression : declaredDependenciesNode; - arrayExpression.elements.forEach(declaredDependencyNode => { - // Skip elided elements. - if (declaredDependencyNode === null) { - return; - } - // If we see a spread element then add a special warning. - if (declaredDependencyNode.type === 'SpreadElement') { - reportProblem({ - node: declaredDependencyNode, - message: - `React Hook ${getSource(reactiveHook)} has a spread ` + - "element in its dependency array. This means we can't " + - "statically verify whether you've passed the " + - 'correct dependencies.', - }); - return; - } - if (useEffectEventVariables.has(declaredDependencyNode)) { - reportProblem({ - node: declaredDependencyNode, - message: - 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + - `Remove \`${getSource( - declaredDependencyNode, - )}\` from the list.`, - suggest: [ - { - desc: `Remove the dependency \`${getSource( + (arrayExpression as ArrayExpression).elements.forEach( + declaredDependencyNode => { + // Skip elided elements. + if (declaredDependencyNode === null) { + return; + } + // If we see a spread element then add a special warning. + if (declaredDependencyNode.type === 'SpreadElement') { + reportProblem({ + node: declaredDependencyNode, + message: + `React Hook ${getSourceCode().getText(reactiveHook)} has a spread ` + + "element in its dependency array. This means we can't " + + "statically verify whether you've passed the " + + 'correct dependencies.', + }); + return; + } + if (useEffectEventVariables.has(declaredDependencyNode)) { + reportProblem({ + node: declaredDependencyNode, + message: + 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + + `Remove \`${getSourceCode().getText( declaredDependencyNode, - )}\``, - fix(fixer) { - return fixer.removeRange(declaredDependencyNode.range); + )}\` from the list.`, + suggest: [ + { + desc: `Remove the dependency \`${getSourceCode().getText( + declaredDependencyNode, + )}\``, + fix(fixer) { + return fixer.removeRange(declaredDependencyNode.range!); + }, }, - }, - ], - }); - } - // Try to normalize the declared dependency. If we can't then an error - // will be thrown. We will catch that error and report an error. - let declaredDependency; - try { - declaredDependency = analyzePropertyChain( - declaredDependencyNode, - null, - ); - } catch (error) { - if (/Unsupported node type/.test(error.message)) { - if (declaredDependencyNode.type === 'Literal') { - if (dependencies.has(declaredDependencyNode.value)) { - reportProblem({ - node: declaredDependencyNode, - message: - `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + - `because it never changes. ` + - `Did you mean to include ${declaredDependencyNode.value} in the array instead?`, - }); + ], + }); + } + // Try to normalize the declared dependency. If we can't then an error + // will be thrown. We will catch that error and report an error. + let declaredDependency; + try { + declaredDependency = analyzePropertyChain( + declaredDependencyNode, + null, + ); + } catch (error: unknown) { + if ( + error instanceof Error && + /Unsupported node type/.test(error.message) + ) { + if (declaredDependencyNode.type === 'Literal') { + if ( + declaredDependencyNode.value && + dependencies.has(declaredDependencyNode.value as string) + ) { + reportProblem({ + node: declaredDependencyNode, + message: + `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + + `because it never changes. ` + + `Did you mean to include ${declaredDependencyNode.value} in the array instead?`, + }); + } else { + reportProblem({ + node: declaredDependencyNode, + message: + `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + + 'because it never changes. You can safely remove it.', + }); + } } else { reportProblem({ node: declaredDependencyNode, message: - `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + - 'because it never changes. You can safely remove it.', + `React Hook ${getSourceCode().getText(reactiveHook)} has a ` + + `complex expression in the dependency array. ` + + 'Extract it to a separate variable so it can be statically checked.', }); } + + return; } else { - reportProblem({ - node: declaredDependencyNode, - message: - `React Hook ${getSource(reactiveHook)} has a ` + - `complex expression in the dependency array. ` + - 'Extract it to a separate variable so it can be statically checked.', - }); + throw error; } - - return; - } else { - throw error; } - } - let maybeID = declaredDependencyNode; - while ( - maybeID.type === 'MemberExpression' || - maybeID.type === 'OptionalMemberExpression' || - maybeID.type === 'ChainExpression' - ) { - maybeID = maybeID.object || maybeID.expression.object; - } - const isDeclaredInComponent = !componentScope.through.some( - ref => ref.identifier === maybeID, - ); + let maybeID = declaredDependencyNode; + while ( + maybeID.type === 'MemberExpression' || + maybeID.type === 'OptionalMemberExpression' || + maybeID.type === 'ChainExpression' + ) { + // @ts-expect-error This can be done better + maybeID = maybeID.object || maybeID.expression.object; + } + const isDeclaredInComponent = !componentScope.through.some( + ref => ref.identifier === maybeID, + ); - // Add the dependency to our declared dependency map. - declaredDependencies.push({ - key: declaredDependency, - node: declaredDependencyNode, - }); + // Add the dependency to our declared dependency map. + declaredDependencies.push({ + key: declaredDependency, + node: declaredDependencyNode, + }); - if (!isDeclaredInComponent) { - externalDependencies.add(declaredDependency); - } - }); + if (!isDeclaredInComponent) { + externalDependencies.add(declaredDependency); + } + }, + ); } const { @@ -824,10 +892,10 @@ export default { const message = `The '${construction.name.name}' ${depType} ${causation} the dependencies of ` + - `${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc.start.line}) ` + + `${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc?.start.line}) ` + `change on every render. ${advice}`; - let suggest; + let suggest: Rule.ReportDescriptor['suggest']; // Only handle the simple case of variable assignments. // Wrapping function declarations can mess up hoisting. if ( @@ -848,12 +916,12 @@ export default { : ['useCallback(', ')']; return [ // TODO: also add an import? - fixer.insertTextBefore(construction.node.init, before), + fixer.insertTextBefore(construction.node.init!, before), // TODO: ideally we'd gather deps here but it would require // restructuring the rule code. This will cause a new lint // error to appear immediately for useCallback. Note we're // not adding [] because would that changes semantics. - fixer.insertTextAfter(construction.node.init, after), + fixer.insertTextAfter(construction.node.init!, after), ]; }, }, @@ -889,7 +957,7 @@ export default { } // Alphabetize the suggestions, but only if deps were already alphabetized. - function areDeclaredDepsAlphabetized() { + function areDeclaredDepsAlphabetized(): boolean { if (declaredDependencies.length === 0) { return true; } @@ -905,7 +973,7 @@ export default { // This function is the last step before printing a dependency, so now is a good time to // check whether any members in our path are always used as optional-only. In that case, // we will use ?. instead of . to concatenate those parts of the path. - function formatDependency(path) { + function formatDependency(path: string): string { const members = path.split('.'); let finalPath = ''; for (let i = 0; i < members.length; i++) { @@ -919,7 +987,12 @@ export default { return finalPath; } - function getWarningMessage(deps, singlePrefix, label, fixVerb) { + function getWarningMessage( + deps: Set, + singlePrefix: string, + label: string, + fixVerb: string, + ): string | null { if (deps.size === 0) { return null; } @@ -942,7 +1015,7 @@ export default { let extraWarning = ''; if (unnecessaryDependencies.size > 0) { - let badRef = null; + let badRef: string | null = null; Array.from(unnecessaryDependencies.keys()).forEach(key => { if (badRef !== null) { return; @@ -956,7 +1029,7 @@ export default { ` Mutable values like '${badRef}' aren't valid dependencies ` + "because mutating them doesn't re-render the component."; } else if (externalDependencies.size > 0) { - const dep = Array.from(externalDependencies)[0]; + const dep = Array.from(externalDependencies)[0]!; // Don't show this warning for things that likely just got moved *inside* the callback // because in that case they're clearly not referring to globals. if (!scope.set.has(dep)) { @@ -980,8 +1053,7 @@ export default { return; } let isPropsOnlyUsedInMembers = true; - for (let i = 0; i < refs.length; i++) { - const ref = refs[i]; + for (const ref of refs) { const id = fastFindReferenceWithParent( componentScope.block, ref.identifier, @@ -1008,14 +1080,14 @@ export default { ` However, 'props' will change when *any* prop changes, so the ` + `preferred fix is to destructure the 'props' object outside of ` + `the ${reactiveHookName} call and refer to those specific props ` + - `inside ${getSource(reactiveHook)}.`; + `inside ${getSourceCode().getText(reactiveHook)}.`; } } if (!extraWarning && missingDependencies.size > 0) { // See if the user is trying to avoid specifying a callable prop. // This usually means they're unaware of useCallback. - let missingCallbackDep = null; + let missingCallbackDep: string | null = null; missingDependencies.forEach(missingDep => { if (missingCallbackDep) { return; @@ -1023,19 +1095,22 @@ export default { // Is this a variable from top scope? const topScopeRef = componentScope.set.get(missingDep); const usedDep = dependencies.get(missingDep); - if (usedDep.references[0].resolved !== topScopeRef) { + if ( + !usedDep?.references || + usedDep?.references[0]?.resolved !== topScopeRef + ) { return; } // Is this a destructured prop? - const def = topScopeRef.defs[0]; + const def = topScopeRef?.defs[0]; if (def == null || def.name == null || def.type !== 'Parameter') { return; } // Was it called in at least one case? Then it's a function. let isFunctionCall = false; - let id; - for (let i = 0; i < usedDep.references.length; i++) { - id = usedDep.references[i].identifier; + let id: Identifier | undefined; + for (const reference of usedDep.references) { + id = reference.identifier; if ( id != null && id.parent != null && @@ -1064,17 +1139,21 @@ export default { } if (!extraWarning && missingDependencies.size > 0) { - let setStateRecommendation = null; - missingDependencies.forEach(missingDep => { + let setStateRecommendation: { + missingDep: string; + setter: string; + form: 'reducer' | 'updater' | 'inlineReducer'; + } | null = null; + for (const missingDep of missingDependencies) { if (setStateRecommendation !== null) { - return; + break; } - const usedDep = dependencies.get(missingDep); + const usedDep = dependencies.get(missingDep)!; const references = usedDep.references; let id; let maybeCall; - for (let i = 0; i < references.length; i++) { - id = references[i].identifier; + for (const reference of references) { + id = reference.identifier; maybeCall = id.parent; // Try to see if we have setState(someExpr(missingDep)). while (maybeCall != null && maybeCall !== componentScope.block) { @@ -1083,22 +1162,27 @@ export default { maybeCall.callee, ); if (correspondingStateVariable != null) { - if (correspondingStateVariable.name === missingDep) { + if ( + 'name' in correspondingStateVariable && + correspondingStateVariable.name === missingDep + ) { // setCount(count + 1) setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee ? maybeCall.callee.name : '', form: 'updater', }; } else if (stateVariables.has(id)) { // setCount(count + increment) setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee ? maybeCall.callee.name : '', form: 'reducer', }; } else { - const resolved = references[i].resolved; + const resolved = reference.resolved; if (resolved != null) { // If it's a parameter *and* a missing dep, // it must be a prop or something inside a prop. @@ -1107,7 +1191,10 @@ export default { if (def != null && def.type === 'Parameter') { setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee + ? maybeCall.callee.name + : '', form: 'inlineReducer', }; } @@ -1122,7 +1209,7 @@ export default { break; } } - }); + } if (setStateRecommendation !== null) { switch (setStateRecommendation.form) { case 'reducer': @@ -1158,7 +1245,7 @@ export default { reportProblem({ node: declaredDependenciesNode, message: - `React Hook ${getSource(reactiveHook)} has ` + + `React Hook ${getSourceCode().getText(reactiveHook)} has ` + // To avoid a long message, show the next actionable item. (getWarningMessage(missingDependencies, 'a', 'missing', 'include') || getWarningMessage( @@ -1191,7 +1278,7 @@ export default { }); } - function visitCallExpression(node) { + function visitCallExpression(node: CallExpression): void { const callbackIndex = getReactiveHookCallbackIndex(node.callee, options); if (callbackIndex === -1) { // Not a React Hook call that needs deps. @@ -1199,7 +1286,9 @@ export default { } let callback = node.arguments[callbackIndex]; const reactiveHook = node.callee; - const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name; + const nodeWithoutNamespace = getNodeWithoutReactNamespace(reactiveHook); + const reactiveHookName = + 'name' in nodeWithoutNamespace ? nodeWithoutNamespace.name : ''; const maybeNode = node.arguments[callbackIndex + 1]; const declaredDependenciesNode = maybeNode && @@ -1268,6 +1357,7 @@ export default { // The function passed as a callback is not written inline. // But perhaps it's in the dependencies array? if ( + 'elements' in declaredDependenciesNode && declaredDependenciesNode.elements && declaredDependenciesNode.elements.some( el => el && el.type === 'Identifier' && el.name === callback.name, @@ -1368,7 +1458,7 @@ export default { CallExpression: visitCallExpression, }; }, -}; +} satisfies Rule.RuleModule; // The meat of the logic. function collectRecommendations({ @@ -1377,6 +1467,12 @@ function collectRecommendations({ stableDependencies, externalDependencies, isEffect, +}: { + dependencies: Map; + declaredDependencies: DeclaredDependency[]; + stableDependencies: Set; + externalDependencies: Set; + isEffect: boolean; }) { // Our primary data structure. // It is a logical representation of property chains: @@ -1388,7 +1484,7 @@ function collectRecommendations({ // and the nodes that were *declared* as deps. Then we will // traverse it to learn which deps are missing or unnecessary. const depTree = createDepTree(); - function createDepTree() { + function createDepTree(): DependencyTreeNode { return { isUsed: false, // True if used in code isSatisfiedRecursively: false, // True if specified in deps @@ -1419,7 +1515,10 @@ function collectRecommendations({ }); // Tree manipulation helpers. - function getOrCreateNodeByPath(rootNode, path) { + function getOrCreateNodeByPath( + rootNode: DependencyTreeNode, + path: string, + ): DependencyTreeNode { const keys = path.split('.'); let node = rootNode; for (const key of keys) { @@ -1432,7 +1531,11 @@ function collectRecommendations({ } return node; } - function markAllParentsByPath(rootNode, path, fn) { + function markAllParentsByPath( + rootNode: DependencyTreeNode, + path: string, + fn: (node: DependencyTreeNode) => void, + ): void { const keys = path.split('.'); let node = rootNode; for (const key of keys) { @@ -1446,15 +1549,20 @@ function collectRecommendations({ } // Now we can learn which dependencies are missing or necessary. - const missingDependencies = new Set(); - const satisfyingDependencies = new Set(); + const missingDependencies = new Set(); + const satisfyingDependencies = new Set(); scanTreeRecursively( depTree, missingDependencies, satisfyingDependencies, key => key, ); - function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) { + function scanTreeRecursively( + node: DependencyTreeNode, + missingPaths: Set, + satisfyingPaths: Set, + keyToPath: (key: string) => string, + ): void { node.children.forEach((child, key) => { const path = keyToPath(key); if (child.isSatisfiedRecursively) { @@ -1484,9 +1592,9 @@ function collectRecommendations({ } // Collect suggestions in the order they were originally specified. - const suggestedDependencies = []; - const unnecessaryDependencies = new Set(); - const duplicateDependencies = new Set(); + const suggestedDependencies: string[] = []; + const unnecessaryDependencies = new Set(); + const duplicateDependencies = new Set(); declaredDependencies.forEach(({key}) => { // Does this declared dep satisfy a real need? if (satisfyingDependencies.has(key)) { @@ -1532,7 +1640,7 @@ function collectRecommendations({ // If the node will result in constructing a referentially unique value, return // its human readable type name, else return null. -function getConstructionExpressionType(node) { +function getConstructionExpressionType(node: Node): string | null { switch (node.type) { case 'ObjectExpression': return 'object'; @@ -1590,6 +1698,11 @@ function scanForConstructions({ declaredDependenciesNode, componentScope, scope, +}: { + declaredDependencies: DeclaredDependency[]; + declaredDependenciesNode: Node; + componentScope: Scope.Scope; + scope: Scope.Scope; }) { const constructions = declaredDependencies .map(({key}) => { @@ -1616,7 +1729,7 @@ function scanForConstructions({ const constantExpressionType = getConstructionExpressionType( node.node.init, ); - if (constantExpressionType != null) { + if (constantExpressionType) { return [ref, constantExpressionType]; } } @@ -1634,12 +1747,11 @@ function scanForConstructions({ } return null; }) - .filter(Boolean); + .filter(Boolean) as [Scope.Variable, string][]; - function isUsedOutsideOfHook(ref) { + function isUsedOutsideOfHook(ref: Scope.Variable): boolean { let foundWriteExpr = false; - for (let i = 0; i < ref.references.length; i++) { - const reference = ref.references[i]; + for (const reference of ref.references) { if (reference.writeExpr) { if (foundWriteExpr) { // Two writes to the same function. @@ -1650,7 +1762,7 @@ function scanForConstructions({ continue; } } - let currentScope = reference.from; + let currentScope: Scope.Scope | null = reference.from; while (currentScope !== scope && currentScope != null) { currentScope = currentScope.upper; } @@ -1666,7 +1778,7 @@ function scanForConstructions({ } return constructions.map(([ref, depType]) => ({ - construction: ref.defs[0], + construction: ref.defs[0] as Scope.Definition, depType, isUsedOutsideOfHook: isUsedOutsideOfHook(ref), })); @@ -1679,11 +1791,13 @@ function scanForConstructions({ * props.foo.(bar) => (props).foo.bar * props.foo.bar.(baz) => (props).foo.bar.baz */ -function getDependency(node) { +function getDependency(node: Node): Node { if ( + node.parent && (node.parent.type === 'MemberExpression' || node.parent.type === 'OptionalMemberExpression') && node.parent.object === node && + 'name' in node.parent.property && node.parent.property.name !== 'current' && !node.parent.computed && !( @@ -1713,9 +1827,13 @@ function getDependency(node) { * It just means there is an optional member somewhere inside. * This particular node might still represent a required member, so check .optional field. */ -function markNode(node, optionalChains, result) { +function markNode( + node: Node, + optionalChains: Map | null, + result: string, +): void { if (optionalChains) { - if (node.optional) { + if ('optional' in node && node.optional) { // We only want to consider it optional if *all* usages were optional. if (!optionalChains.has(result)) { // Mark as (maybe) optional. If there's a required usage, this will be overridden. @@ -1735,7 +1853,10 @@ function markNode(node, optionalChains, result) { * foo.bar(.)baz -> 'foo.bar.baz' * Otherwise throw. */ -function analyzePropertyChain(node, optionalChains) { +function analyzePropertyChain( + node: Node, + optionalChains: Map | null, +): string { if (node.type === 'Identifier' || node.type === 'JSXIdentifier') { const result = node.name; if (optionalChains) { @@ -1755,7 +1876,10 @@ function analyzePropertyChain(node, optionalChains) { const result = `${object}.${property}`; markNode(node, optionalChains, result); return result; - } else if (node.type === 'ChainExpression' && !node.computed) { + } else if ( + node.type === 'ChainExpression' && + (!('computed' in node) || !node.computed) + ) { const expression = node.expression; if (expression.type === 'CallExpression') { @@ -1772,7 +1896,9 @@ function analyzePropertyChain(node, optionalChains) { } } -function getNodeWithoutReactNamespace(node, options) { +function getNodeWithoutReactNamespace( + node: Expression | Super, +): Expression | Identifier | Super { if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' && @@ -1790,7 +1916,13 @@ function getNodeWithoutReactNamespace(node, options) { // 0 for useEffect/useMemo/useCallback(fn). // 1 for useImperativeHandle(ref, fn). // For additionally configured Hooks, assume that they're like useEffect (0). -function getReactiveHookCallbackIndex(calleeNode, options) { +function getReactiveHookCallbackIndex( + calleeNode: Expression | Super, + options?: { + additionalHooks: RegExp | undefined; + enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean; + }, +): 0 | -1 | 1 { const node = getNodeWithoutReactNamespace(calleeNode); if (node.type !== 'Identifier') { return -1; @@ -1812,8 +1944,11 @@ function getReactiveHookCallbackIndex(calleeNode, options) { let name; try { name = analyzePropertyChain(node, null); - } catch (error) { - if (/Unsupported node type/.test(error.message)) { + } catch (error: unknown) { + if ( + error instanceof Error && + /Unsupported node type/.test(error.message) + ) { return 0; } else { throw error; @@ -1836,12 +1971,12 @@ function getReactiveHookCallbackIndex(calleeNode, options) { * - optimized by only searching nodes with a range surrounding our target node * - agnostic to AST node types, it looks for `{ type: string, ... }` */ -function fastFindReferenceWithParent(start, target) { +function fastFindReferenceWithParent(start: Node, target: Node): Node | null { const queue = [start]; - let item = null; + let item: Node; while (queue.length) { - item = queue.shift(); + item = queue.shift() as Node; if (isSameIdentifier(item, target)) { return item; @@ -1872,7 +2007,7 @@ function fastFindReferenceWithParent(start, target) { return null; } -function joinEnglish(arr) { +function joinEnglish(arr: string[]): string { let s = ''; for (let i = 0; i < arr.length; i++) { s += arr[i]; @@ -1887,39 +2022,49 @@ function joinEnglish(arr) { return s; } -function isNodeLike(val) { +function isNodeLike(val: unknown): boolean { return ( typeof val === 'object' && val !== null && !Array.isArray(val) && + 'type' in val && typeof val.type === 'string' ); } -function isSameIdentifier(a, b) { +function isSameIdentifier(a: Node, b: Node): boolean { return ( (a.type === 'Identifier' || a.type === 'JSXIdentifier') && a.type === b.type && a.name === b.name && + !!a.range && + !!b.range && a.range[0] === b.range[0] && a.range[1] === b.range[1] ); } -function isAncestorNodeOf(a, b) { - return a.range[0] <= b.range[0] && a.range[1] >= b.range[1]; +function isAncestorNodeOf(a: Node, b: Node): boolean { + return ( + !!a.range && + !!b.range && + a.range[0] <= b.range[0] && + a.range[1] >= b.range[1] + ); } -function isUseEffectEventIdentifier(node) { +function isUseEffectEventIdentifier(node: Node): boolean { if (__EXPERIMENTAL__) { return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } -function getUnknownDependenciesMessage(reactiveHookName) { +function getUnknownDependenciesMessage(reactiveHookName: string): string { return ( `React Hook ${reactiveHookName} received a function whose dependencies ` + `are unknown. Pass an inline function instead.` ); } + +export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts similarity index 85% rename from packages/eslint-plugin-react-hooks/src/RulesOfHooks.js rename to packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts index fc340519e233e..24870d71614ea 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts @@ -4,18 +4,16 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -/* global BigInt */ /* eslint-disable no-for-of-loops/no-for-of-loops */ -'use strict'; +import type {Rule, Scope} from 'eslint'; +import type {CallExpression, DoWhileStatement, Node} from 'estree'; /** * Catch all identifiers that begin with "use" followed by an uppercase Latin * character to exclude identifiers like "user". */ - -function isHookName(s) { +function isHookName(s: string): boolean { return s === 'use' || /^use[A-Z0-9]/.test(s); } @@ -23,8 +21,7 @@ function isHookName(s) { * We consider hooks to be a hook name identifier or a member expression * containing a hook name. */ - -function isHook(node) { +function isHook(node: Node): boolean { if (node.type === 'Identifier') { return isHookName(node.name); } else if ( @@ -44,16 +41,17 @@ function isHook(node) { * Checks if the node is a React component name. React component names must * always start with an uppercase letter. */ - -function isComponentName(node) { +function isComponentName(node: Node): boolean { return node.type === 'Identifier' && /^[A-Z]/.test(node.name); } -function isReactFunction(node, functionName) { +function isReactFunction(node: Node, functionName: string): boolean { return ( - node.name === functionName || + ('name' in node && node.name === functionName) || (node.type === 'MemberExpression' && + 'name' in node.object && node.object.name === 'React' && + 'name' in node.property && node.property.name === functionName) ); } @@ -62,10 +60,10 @@ function isReactFunction(node, functionName) { * Checks if the node is a callback argument of forwardRef. This render function * should follow the rules of hooks. */ - -function isForwardRefCallback(node) { +function isForwardRefCallback(node: Node): boolean { return !!( node.parent && + 'callee' in node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'forwardRef') ); @@ -75,16 +73,16 @@ function isForwardRefCallback(node) { * Checks if the node is a callback argument of React.memo. This anonymous * functional component should follow the rules of hooks. */ - -function isMemoCallback(node) { +function isMemoCallback(node: Node): boolean { return !!( node.parent && + 'callee' in node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'memo') ); } -function isInsideComponentOrHook(node) { +function isInsideComponentOrHook(node: Node | undefined): boolean { while (node) { const functionName = getFunctionName(node); if (functionName) { @@ -100,7 +98,7 @@ function isInsideComponentOrHook(node) { return false; } -function isInsideDoWhileLoop(node) { +function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement { while (node) { if (node.type === 'DoWhileStatement') { return true; @@ -110,18 +108,18 @@ function isInsideDoWhileLoop(node) { return false; } -function isUseEffectEventIdentifier(node) { +function isUseEffectEventIdentifier(node: Node): boolean { if (__EXPERIMENTAL__) { return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } -function isUseIdentifier(node) { +function isUseIdentifier(node: Node): boolean { return isReactFunction(node, 'use'); } -export default { +const rule = { meta: { type: 'problem', docs: { @@ -130,25 +128,28 @@ export default { url: 'https://reactjs.org/docs/hooks-rules.html', }, }, - create(context) { - let lastEffect = null; - const codePathReactHooksMapStack = []; - const codePathSegmentStack = []; + create(context: Rule.RuleContext) { + let lastEffect: CallExpression | null = null; + const codePathReactHooksMapStack: Map[] = []; + const codePathSegmentStack: Rule.CodePathSegment[] = []; const useEffectEventFunctions = new WeakSet(); // For a given scope, iterate through the references and add all useEffectEvent definitions. We can // do this in non-Program nodes because we can rely on the assumption that useEffectEvent functions // can only be declared within a component or hook at its top level. - function recordAllUseEffectEventFunctions(scope) { + function recordAllUseEffectEventFunctions(scope: Scope.Scope): void { for (const reference of scope.references) { const parent = reference.identifier.parent; if ( - parent.type === 'VariableDeclarator' && + parent?.type === 'VariableDeclarator' && parent.init && parent.init.type === 'CallExpression' && parent.init.callee && isUseEffectEventIdentifier(parent.init.callee) ) { + if (reference.resolved === null) { + throw new Error('Unexpected null reference.resolved'); + } for (const ref of reference.resolved.references) { if (ref !== reference) { useEffectEventFunctions.add(ref.identifier); @@ -159,26 +160,26 @@ export default { } /** - * SourceCode#getText that also works down to ESLint 3.0.0 + * SourceCode that also works down to ESLint 3.0.0 */ - const getSource = - typeof context.getSource === 'function' - ? node => { - return context.getSource(node); + const getSourceCode = + typeof context.getSourceCode === 'function' + ? () => { + return context.getSourceCode(); } - : node => { - return context.sourceCode.getText(node); + : () => { + return context.sourceCode; }; /** * SourceCode#getScope that also works down to ESLint 3.0.0 */ const getScope = typeof context.getScope === 'function' - ? () => { + ? (): Scope.Scope => { return context.getScope(); } - : node => { - return context.sourceCode.getScope(node); + : (node: Node): Scope.Scope => { + return getSourceCode().getScope(node); }; return { @@ -187,7 +188,10 @@ export default { onCodePathSegmentEnd: () => codePathSegmentStack.pop(), // Maintain code path stack as we traverse. - onCodePathStart: () => codePathReactHooksMapStack.push(new Map()), + onCodePathStart: () => + codePathReactHooksMapStack.push( + new Map(), + ), // Process our code path. // @@ -195,8 +199,10 @@ export default { // segment and reachable from every final segment. onCodePathEnd(codePath, codePathNode) { const reactHooksMap = codePathReactHooksMapStack.pop(); - if (reactHooksMap.size === 0) { + if (reactHooksMap?.size === 0) { return; + } else if (typeof reactHooksMap === 'undefined') { + throw new Error('Unexpected undefined reactHooksMap'); } // All of the segments which are cyclic are recorded in this set. @@ -223,11 +229,13 @@ export default { * * Populates `cyclic` with cyclic segments. */ - - function countPathsFromStart(segment, pathHistory) { + function countPathsFromStart( + segment: Rule.CodePathSegment, + pathHistory?: Set, + ): bigint { const {cache} = countPathsFromStart; let paths = cache.get(segment.id); - const pathList = new Set(pathHistory); + const pathList = new Set(pathHistory); // If `pathList` includes the current segment then we've found a cycle! // We need to fill `cyclic` with all segments inside cycle @@ -295,7 +303,10 @@ export default { * Populates `cyclic` with cyclic segments. */ - function countPathsToEnd(segment, pathHistory) { + function countPathsToEnd( + segment: Rule.CodePathSegment, + pathHistory?: Set, + ): bigint { const {cache} = countPathsToEnd; let paths = cache.get(segment.id); const pathList = new Set(pathHistory); @@ -359,7 +370,9 @@ export default { * so we would return that. */ - function shortestPathLengthToStart(segment) { + function shortestPathLengthToStart( + segment: Rule.CodePathSegment, + ): number { const {cache} = shortestPathLengthToStart; let length = cache.get(segment.id); @@ -392,9 +405,9 @@ export default { return length; } - countPathsFromStart.cache = new Map(); - countPathsToEnd.cache = new Map(); - shortestPathLengthToStart.cache = new Map(); + countPathsFromStart.cache = new Map(); + countPathsToEnd.cache = new Map(); + shortestPathLengthToStart.cache = new Map(); // Count all code paths to the end of our component/hook. Also primes // the `countPathsToEnd` cache. @@ -502,7 +515,7 @@ export default { context.report({ node: hook, message: - `React Hook "${getSource(hook)}" may be executed ` + + `React Hook "${getSourceCode().getText(hook)}" may be executed ` + 'more than once. Possibly because it is called in a loop. ' + 'React Hooks must be called in the exact same order in ' + 'every component render.', @@ -516,12 +529,13 @@ export default { // called in. if (isDirectlyInsideComponentOrHook) { // Report an error if the hook is called inside an async function. + // @ts-expect-error the above check hasn't properly type-narrowed `codePathNode` (async doesn't exist on Node) const isAsyncFunction = codePathNode.async; if (isAsyncFunction) { context.report({ node: hook, message: - `React Hook "${getSource(hook)}" cannot be ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be ` + 'called in an async function.', }); } @@ -537,7 +551,7 @@ export default { !isInsideDoWhileLoop(hook) // wrapping do/while loops are checked separately. ) { const message = - `React Hook "${getSource(hook)}" is called ` + + `React Hook "${getSourceCode().getText(hook)}" is called ` + 'conditionally. React Hooks must be called in the exact ' + 'same order in every component render.' + (possiblyHasEarlyReturn @@ -549,21 +563,22 @@ export default { } else if ( codePathNode.parent && (codePathNode.parent.type === 'MethodDefinition' || + // @ts-expect-error `ClassProperty` was removed from typescript-estree in https://github.com/typescript-eslint/typescript-eslint/pull/3806 codePathNode.parent.type === 'ClassProperty' || codePathNode.parent.type === 'PropertyDefinition') && codePathNode.parent.value === codePathNode ) { // Custom message for hooks inside a class const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'in a class component. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); } else if (codePathFunctionName) { // Custom message if we found an invalid function name. const message = - `React Hook "${getSource(hook)}" is called in ` + - `function "${getSource(codePathFunctionName)}" ` + + `React Hook "${getSourceCode().getText(hook)}" is called in ` + + `function "${getSourceCode().getText(codePathFunctionName)}" ` + 'that is neither a React function component nor a custom ' + 'React Hook function.' + ' React component names must start with an uppercase letter.' + @@ -572,7 +587,7 @@ export default { } else if (codePathNode.type === 'Program') { // These are dangerous if you have inline requires enabled. const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'at the top level. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); @@ -585,7 +600,7 @@ export default { // `use(...)` can be called in callbacks. if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) { const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'inside a callback. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); @@ -638,7 +653,7 @@ export default { context.report({ node, message: - `\`${getSource( + `\`${getSourceCode().getText( node, )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` + 'the same component. They cannot be assigned to variables or passed down.', @@ -667,7 +682,7 @@ export default { }, }; }, -}; +} satisfies Rule.RuleModule; /** * Gets the static name of a function AST node. For function declarations it is @@ -677,7 +692,7 @@ export default { * same AST nodes with some exceptions to better fit our use case. */ -function getFunctionName(node) { +function getFunctionName(node: Node) { if ( node.type === 'FunctionDeclaration' || (node.type === 'FunctionExpression' && node.id) @@ -693,20 +708,20 @@ function getFunctionName(node) { node.type === 'ArrowFunctionExpression' ) { if ( - node.parent.type === 'VariableDeclarator' && + node.parent?.type === 'VariableDeclarator' && node.parent.init === node ) { // const useHook = () => {}; return node.parent.id; } else if ( - node.parent.type === 'AssignmentExpression' && + node.parent?.type === 'AssignmentExpression' && node.parent.right === node && node.parent.operator === '=' ) { // useHook = () => {}; return node.parent.left; } else if ( - node.parent.type === 'Property' && + node.parent?.type === 'Property' && node.parent.value === node && !node.parent.computed ) { @@ -721,8 +736,9 @@ function getFunctionName(node) { // class {useHook = () => {}} // class {useHook() {}} } else if ( - node.parent.type === 'AssignmentPattern' && + node.parent?.type === 'AssignmentPattern' && node.parent.right === node && + // @ts-expect-error Property computed does not exist on type `AssignmentPattern`. !node.parent.computed ) { // const {useHook = () => {}} = {}; @@ -742,7 +758,8 @@ function getFunctionName(node) { /** * Convenience function for peeking the last item in a stack. */ - -function last(array) { - return array[array.length - 1]; +function last(array: T[]): T { + return array[array.length - 1] as T; } + +export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/index.js b/packages/eslint-plugin-react-hooks/src/index.js deleted file mode 100644 index 0ebfbc91a5a8d..0000000000000 --- a/packages/eslint-plugin-react-hooks/src/index.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -import RulesOfHooks from './RulesOfHooks'; -import ExhaustiveDeps from './ExhaustiveDeps'; - -// All rules -export const rules = { - 'rules-of-hooks': RulesOfHooks, - 'exhaustive-deps': ExhaustiveDeps, -}; - -// Config rules -const configRules = { - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', -}; - -// Legacy config -const legacyRecommendedConfig = { - plugins: ['react-hooks'], - rules: configRules, -}; - -// Base plugin object -const reactHooksPlugin = { - meta: {name: 'eslint-plugin-react-hooks'}, - rules, -}; - -// Flat config -const flatRecommendedConfig = { - name: 'react-hooks/recommended', - plugins: {'react-hooks': reactHooksPlugin}, - rules: configRules, -}; - -export const configs = { - /** Legacy recommended config, to be used with rc-based configurations */ - 'recommended-legacy': legacyRecommendedConfig, - - /** Latest recommended config, to be used with flat configurations */ - 'recommended-latest': flatRecommendedConfig, - - /** - * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. - * This is deprecated and in v6, it will switch to alias the flat recommended config. - */ - recommended: legacyRecommendedConfig, -}; - -export default { - ...reactHooksPlugin, - configs, -}; diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts new file mode 100644 index 0000000000000..42b55320fcd89 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import RulesOfHooks from './RulesOfHooks'; +import ExhaustiveDeps from './ExhaustiveDeps'; +import type {ESLint, Linter, Rule} from 'eslint'; + +// All rules +const rules = { + 'rules-of-hooks': RulesOfHooks, + 'exhaustive-deps': ExhaustiveDeps, +} satisfies Record; + +// Config rules +const configRules = { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', +} satisfies Linter.RulesRecord; + +// Legacy config +const legacyRecommendedConfig = { + plugins: ['react-hooks'], + rules: configRules, +} satisfies Linter.LegacyConfig; + +// Plugin object +const plugin = { + // TODO: Make this more dynamic to populate version from package.json. + // This can be done by injecting at build time, since importing the package.json isn't an option in Meta + meta: {name: 'eslint-plugin-react-hooks'}, + rules, + configs: { + /** Legacy recommended config, to be used with rc-based configurations */ + 'recommended-legacy': legacyRecommendedConfig, + + /** + * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. + * This is deprecated and in v6, it will switch to alias the flat recommended config. + */ + recommended: legacyRecommendedConfig, + + /** Latest recommended config, to be used with flat configurations */ + 'recommended-latest': { + name: 'react-hooks/recommended', + plugins: { + get 'react-hooks'(): ESLint.Plugin { + return plugin; + }, + }, + rules: configRules, + }, + }, +} satisfies ESLint.Plugin; + +const configs = plugin.configs; +const meta = plugin.meta; +export {configs, meta, rules}; + +// TODO: If the plugin is ever updated to be pure ESM and drops support for rc-based configs, then it should be exporting the plugin as default +// instead of individual named exports. +// export default plugin; diff --git a/packages/eslint-plugin-react-hooks/src/types/estree.d.ts b/packages/eslint-plugin-react-hooks/src/types/estree.d.ts index 10337ef7d6bf0..f266369704885 100644 --- a/packages/eslint-plugin-react-hooks/src/types/estree.d.ts +++ b/packages/eslint-plugin-react-hooks/src/types/estree.d.ts @@ -1,3 +1,5 @@ +import {Expression, Identifier, Node} from 'estree-jsx'; + /** * This file augments the `estree` types to include types that are not built-in to `estree` or `estree-jsx`. * This is necessary because the `estree` types are used by ESLint, and ESLint does not natively support diff --git a/packages/eslint-plugin-react-hooks/tsconfig.json b/packages/eslint-plugin-react-hooks/tsconfig.json index db8a4893649ea..e67b840d3fd7a 100644 --- a/packages/eslint-plugin-react-hooks/tsconfig.json +++ b/packages/eslint-plugin-react-hooks/tsconfig.json @@ -6,7 +6,6 @@ "moduleResolution": "Bundler", "lib": ["ES2020"], "rootDir": ".", - "noEmit": true, "sourceMap": false, "types": ["estree-jsx", "node"] }, diff --git a/packages/eslint-plugin-react-hooks/tsup.config.ts b/packages/eslint-plugin-react-hooks/tsup.config.ts deleted file mode 100644 index e275d0495e26c..0000000000000 --- a/packages/eslint-plugin-react-hooks/tsup.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {defineConfig} from 'tsup'; - -export default defineConfig({ - clean: true, - dts: true, - entry: ['src/index.ts'], - format: ['cjs'], - outDir: 'build', -}); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index c66cff82c5ac2..0a378c4f31bf7 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1184,17 +1184,19 @@ const bundles = [ /******* ESLint Plugin for Hooks *******/ { - // TODO: it's awkward to create a bundle for this but if we don't, the package - // won't get copied. We also can't create just DEV bundle because it contains a - // NODE_ENV check inside. We should probably tweak our build process to allow - // "raw" packages that don't get bundled. - bundleTypes: [NODE_DEV, NODE_PROD], + // TODO: we're building this from typescript source now, but there's really + // no reason to have both dev and prod for this package. It's + // currently required in order for the package to be copied over correctly. + // So, it would be worth improving that flow. + name: 'eslint-plugin-react-hooks', + bundleTypes: [NODE_DEV, NODE_PROD, CJS_DTS], moduleType: ISOMORPHIC, - entry: 'eslint-plugin-react-hooks', + entry: 'eslint-plugin-react-hooks/src/index.ts', global: 'ESLintPluginReactHooks', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false, externals: [], + tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json', }, /******* React Fresh *******/ diff --git a/yarn.lock b/yarn.lock index 44d590625111c..ba71ba7abe323 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2403,131 +2403,6 @@ opn "5.3.0" react "^16.13.1" -"@esbuild/aix-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461" - integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA== - -"@esbuild/android-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894" - integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg== - -"@esbuild/android-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3" - integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q== - -"@esbuild/android-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb" - integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw== - -"@esbuild/darwin-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936" - integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA== - -"@esbuild/darwin-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9" - integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA== - -"@esbuild/freebsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00" - integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg== - -"@esbuild/freebsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f" - integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q== - -"@esbuild/linux-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43" - integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg== - -"@esbuild/linux-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736" - integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA== - -"@esbuild/linux-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5" - integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw== - -"@esbuild/linux-loong64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc" - integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ== - -"@esbuild/linux-mips64el@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb" - integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw== - -"@esbuild/linux-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412" - integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw== - -"@esbuild/linux-riscv64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694" - integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q== - -"@esbuild/linux-s390x@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577" - integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw== - -"@esbuild/linux-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f" - integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q== - -"@esbuild/netbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6" - integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw== - -"@esbuild/netbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40" - integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw== - -"@esbuild/openbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f" - integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A== - -"@esbuild/openbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205" - integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA== - -"@esbuild/sunos-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6" - integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig== - -"@esbuild/win32-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85" - integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ== - -"@esbuild/win32-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2" - integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA== - -"@esbuild/win32-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b" - integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg== - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -2924,15 +2799,6 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -3396,101 +3262,6 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz#e554185b1afa5509a7a4040d15ec0c3b4435ded1" - integrity sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw== - -"@rollup/rollup-android-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz#b1ee64bb413b2feba39803b0a1bebf2a9f3d70e1" - integrity sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg== - -"@rollup/rollup-darwin-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz#bfdce3e07a345dd1bd628f3b796050f39629d7f0" - integrity sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA== - -"@rollup/rollup-darwin-x64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz#781a94a537c57bdf0a500e47a25ab5985e5e8dff" - integrity sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA== - -"@rollup/rollup-freebsd-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz#7a028357cbd12c5869c446ad18177c89f3405102" - integrity sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA== - -"@rollup/rollup-freebsd-x64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz#f24836a6371cccc4408db74f0fd986dacf098950" - integrity sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q== - -"@rollup/rollup-linux-arm-gnueabihf@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz#95f27e96f0eb9b9ae9887739a8b6dffc90c1237f" - integrity sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ== - -"@rollup/rollup-linux-arm-musleabihf@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz#677b34fba9d070877736c3fe8b02aacb5e142d97" - integrity sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw== - -"@rollup/rollup-linux-arm64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz#32d3d19dedde54e91574a098f22ea43a09cf63dd" - integrity sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA== - -"@rollup/rollup-linux-arm64-musl@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz#a58dff44a18696df65ed8c0ad68a2945cf900484" - integrity sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw== - -"@rollup/rollup-linux-loongarch64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz#a7488ab078233111e8aeb370d1ecf107ec7e1716" - integrity sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz#e9b9c0d6bd248a92b2d6ec01ebf99c62ae1f2e9a" - integrity sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w== - -"@rollup/rollup-linux-riscv64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz#0df84ce2bea48ee686fb55060d76ab47aff45c4c" - integrity sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw== - -"@rollup/rollup-linux-s390x-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz#73df374c57d036856e33dbd2715138922e91e452" - integrity sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw== - -"@rollup/rollup-linux-x64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz#f27af0b55f0cdd84e182e6cd44a6d03da0458149" - integrity sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA== - -"@rollup/rollup-linux-x64-musl@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz#c7981ad5cfb8c3cd5d643d33ca54e4d2802b9201" - integrity sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ== - -"@rollup/rollup-win32-arm64-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz#06cedc0ef3cbf1cbd8abcf587090712e40ae6941" - integrity sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw== - -"@rollup/rollup-win32-ia32-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz#90b39b977b14961a769be6ea61238e7fc668dd4d" - integrity sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg== - -"@rollup/rollup-win32-x64-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz#6531d61e7141091eaab0461ee8e0380c10e4ca57" - integrity sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w== - "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -3680,7 +3451,7 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -5760,13 +5531,6 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" -bundle-require@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" - integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== - dependencies: - load-tsconfig "^0.2.3" - bunyan@1.8.15: version "1.8.15" resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" @@ -5787,11 +5551,6 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -6093,13 +5852,6 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chokidar@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -6368,7 +6120,7 @@ commander@^2.18.0, commander@^2.20.0, commander@^2.6.0, commander@^2.8.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.0, commander@^4.0.1: +commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -6511,11 +6263,6 @@ connect-history-api-fallback@^2.0.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== -consola@^3.2.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" - integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== - console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -7039,13 +6786,6 @@ debug@^4.3.4, debug@~4.3.1: dependencies: ms "2.1.2" -debug@^4.3.7: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - decamelize@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-6.0.0.tgz#8cad4d916fde5c41a264a43d0ecc56fe3d31749e" @@ -7744,37 +7484,6 @@ es6-error@4.1.1, es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.24.0: - version "0.24.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" - integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA== - optionalDependencies: - "@esbuild/aix-ppc64" "0.24.2" - "@esbuild/android-arm" "0.24.2" - "@esbuild/android-arm64" "0.24.2" - "@esbuild/android-x64" "0.24.2" - "@esbuild/darwin-arm64" "0.24.2" - "@esbuild/darwin-x64" "0.24.2" - "@esbuild/freebsd-arm64" "0.24.2" - "@esbuild/freebsd-x64" "0.24.2" - "@esbuild/linux-arm" "0.24.2" - "@esbuild/linux-arm64" "0.24.2" - "@esbuild/linux-ia32" "0.24.2" - "@esbuild/linux-loong64" "0.24.2" - "@esbuild/linux-mips64el" "0.24.2" - "@esbuild/linux-ppc64" "0.24.2" - "@esbuild/linux-riscv64" "0.24.2" - "@esbuild/linux-s390x" "0.24.2" - "@esbuild/linux-x64" "0.24.2" - "@esbuild/netbsd-arm64" "0.24.2" - "@esbuild/netbsd-x64" "0.24.2" - "@esbuild/openbsd-arm64" "0.24.2" - "@esbuild/openbsd-x64" "0.24.2" - "@esbuild/sunos-x64" "0.24.2" - "@esbuild/win32-arm64" "0.24.2" - "@esbuild/win32-ia32" "0.24.2" - "@esbuild/win32-x64" "0.24.2" - escalade@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -8649,11 +8358,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.4.2: - version "6.4.3" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" - integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -9292,18 +8996,6 @@ glob@^10.2.5: minipass "^5.0.0 || ^6.0.2" path-scurry "^1.7.0" -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -11489,11 +11181,6 @@ jose@5.4.1: resolved "https://registry.yarnpkg.com/jose/-/jose-5.4.1.tgz#b471ee3963920ba5452fd1b1398c8ba72a7b2fcf" integrity sha512-U6QajmpV/nhL9SyfAewo000fkiRQ+Yd2H0lBxJJ9apjpOgkOcBQJWOrMo917lxLptdS/n/o/xPzMkXhF46K8hQ== -joycon@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" - integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== - jpeg-js@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -11880,11 +11567,6 @@ lighthouse-logger@^1.0.0: debug "^2.6.8" marky "^1.2.0" -lilconfig@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" - integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -11906,11 +11588,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-tsconfig@^0.2.3: - version "0.2.5" - resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" - integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== - loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -12047,11 +11724,6 @@ lodash.omitby@4.6.0: resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E= -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -12572,7 +12244,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1, ms@^2.1.2, ms@^2.1.3: +ms@2.1.3, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -12604,7 +12276,7 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -mz@2.7.0, mz@^2.7.0: +mz@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== @@ -13646,11 +13318,6 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -13821,13 +13488,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -postcss-load-config@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" - integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== - dependencies: - lilconfig "^3.1.1" - postcss-modules-extract-imports@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" @@ -14556,11 +14216,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -15003,34 +14658,6 @@ rollup@^3.29.5: optionalDependencies: fsevents "~2.3.2" -rollup@^4.24.0: - version "4.34.7" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f" - integrity sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.34.7" - "@rollup/rollup-android-arm64" "4.34.7" - "@rollup/rollup-darwin-arm64" "4.34.7" - "@rollup/rollup-darwin-x64" "4.34.7" - "@rollup/rollup-freebsd-arm64" "4.34.7" - "@rollup/rollup-freebsd-x64" "4.34.7" - "@rollup/rollup-linux-arm-gnueabihf" "4.34.7" - "@rollup/rollup-linux-arm-musleabihf" "4.34.7" - "@rollup/rollup-linux-arm64-gnu" "4.34.7" - "@rollup/rollup-linux-arm64-musl" "4.34.7" - "@rollup/rollup-linux-loongarch64-gnu" "4.34.7" - "@rollup/rollup-linux-powerpc64le-gnu" "4.34.7" - "@rollup/rollup-linux-riscv64-gnu" "4.34.7" - "@rollup/rollup-linux-s390x-gnu" "4.34.7" - "@rollup/rollup-linux-x64-gnu" "4.34.7" - "@rollup/rollup-linux-x64-musl" "4.34.7" - "@rollup/rollup-win32-arm64-msvc" "4.34.7" - "@rollup/rollup-win32-ia32-msvc" "4.34.7" - "@rollup/rollup-win32-x64-msvc" "4.34.7" - fsevents "~2.3.2" - rrweb-cssom@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" @@ -15671,13 +15298,6 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.8.0-beta.0: - version "0.8.0-beta.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" - integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== - dependencies: - whatwg-url "^7.0.0" - source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -16136,19 +15756,6 @@ style-loader@^1.2.1: loader-utils "^2.0.0" schema-utils "^2.6.6" -sucrase@^3.35.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" - integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - glob "^10.3.10" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -16437,19 +16044,6 @@ tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinyexec@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" - integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== - -tinyglobby@^0.2.9: - version "0.2.10" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" - integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew== - dependencies: - fdir "^6.4.2" - picomatch "^4.0.2" - titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" @@ -16556,13 +16150,6 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== - dependencies: - punycode "^2.1.0" - tr46@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" @@ -16580,11 +16167,6 @@ traverse-chain@~0.1.0: resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" integrity sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE= -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -16602,11 +16184,6 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - ts-node@8.9.1: version "8.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" @@ -16633,28 +16210,6 @@ tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tsup@^8.3.5: - version "8.3.6" - resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.3.6.tgz#a10eb2dc27f84b510a0f00341ab75cad03d13a88" - integrity sha512-XkVtlDV/58S9Ye0JxUUTcrQk4S+EqlOHKzg6Roa62rdjL1nGWNUstG0xgI4vanHdfIpjP448J8vlN0oK6XOJ5g== - dependencies: - bundle-require "^5.0.0" - cac "^6.7.14" - chokidar "^4.0.1" - consola "^3.2.3" - debug "^4.3.7" - esbuild "^0.24.0" - joycon "^3.1.1" - picocolors "^1.1.1" - postcss-load-config "^6.0.1" - resolve-from "^5.0.0" - rollup "^4.24.0" - source-map "0.8.0-beta.0" - sucrase "^3.35.0" - tinyexec "^0.3.1" - tinyglobby "^0.2.9" - tree-kill "^1.2.2" - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -17287,11 +16842,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -17462,15 +17012,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - when@3.7.7: version "3.7.7" resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718"