├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── crash-report.md └── workflows │ └── build_test_publish.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .yarn └── releases │ └── yarn-4.6.0.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── build.js ├── eslint.config.js ├── package.json ├── src ├── index.cjs ├── index.js ├── messages.js ├── rule.js └── util │ ├── ast.js │ └── react.js ├── test ├── chaining-state.test.js ├── config.test.js ├── deriving-state.test.js ├── empty-effect.test.js ├── initializing-state.test.js ├── parent-child-coupling.test.js ├── real-world.test.js ├── resetting-state-from-props.test.js ├── rule-tester.js ├── syntax.test.js └── using-state-as-event-handler.test.js ├── types └── index.d.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crash-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Crash report 3 | about: Report a crash in the plugin 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue 11 | 12 | ### Stack Trace 13 | 14 | The crash's stack trace. 15 | 16 | ## Context 17 | 18 | ### Versions 19 | 20 | **Plugin:** 21 | **ESLint:** 22 | **Node:** 23 | 24 | ### ESLint Config 25 | 26 | Your `.eslintrc` or `eslint.config.js`. 27 | 28 | ### Source Code 29 | 30 | If possible, please provide your entire React component that the plugin crashed on, not just the `useEffect`. 31 | -------------------------------------------------------------------------------- /.github/workflows/build_test_publish.yml: -------------------------------------------------------------------------------- 1 | name: Build, test, publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 10 | 11 | jobs: 12 | build_and_test: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | current_version: ${{ steps.version_check.outputs.current_version }} 16 | previous_version: ${{ steps.version_check.outputs.previous_version }} 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '23.9.0' 25 | cache: 'yarn' 26 | 27 | - name: Install dependencies 28 | run: yarn install --immutable 29 | 30 | - name: Lint 31 | run: yarn lint 32 | 33 | - name: Build 34 | run: yarn build 35 | 36 | - name: Test 37 | run: yarn test 38 | 39 | - name: Upload dist artifact 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: dist 43 | path: dist/ 44 | 45 | - name: Check version 46 | id: version_check 47 | run: | 48 | sudo apt-get install -y jq 49 | echo "current_version=$(jq -r .version package.json)" >> $GITHUB_OUTPUT 50 | echo "previous_version=$(yarn npm info $(jq -r .name package.json) -f version --json | jq -r .version)" >> $GITHUB_OUTPUT 51 | 52 | publish: 53 | runs-on: ubuntu-latest 54 | needs: build_and_test 55 | if: ${{ needs.build_and_test.outputs.current_version != needs.build_and_test.outputs.previous_version }} 56 | steps: 57 | - name: Checkout code 58 | uses: actions/checkout@v4 59 | 60 | - name: Setup Node.js 61 | uses: actions/setup-node@v4 62 | with: 63 | node-version: '23.9.0' 64 | cache: 'yarn' 65 | 66 | - name: Download dist artifact 67 | uses: actions/download-artifact@v4 68 | with: 69 | name: dist 70 | path: dist/ 71 | 72 | - name: Publish to npm 73 | run: yarn npm publish --access public 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/plugins 6 | !.yarn/releases 7 | !.yarn/sdks 8 | !.yarn/versions 9 | 10 | # Swap the comments on the following lines if you wish to use zero-installs 11 | # In that case, don't forget to run `yarn config set enableGlobalCache false`! 12 | # Documentation here: https://yarnpkg.com/features/caching#zero-installs 13 | 14 | #!.yarn/cache 15 | .pnp.* 16 | node_modules 17 | .aider* 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | .github 3 | .yarn 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.6.0.cjs 4 | 5 | npmAuthToken: "${NPM_TOKEN}" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nick van Dyke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESLint - React - You Might Not Need An Effect 2 | 3 | ESLint plugin to catch [unnecessary React `useEffect`s](https://react.dev/learn/you-might-not-need-an-effect) to make your code easier to follow, faster to run, and less error-prone. Highly recommended for new React developers as you learn its mental model, and even experienced developers may be surprised. 4 | 5 | ## 🚀 Setup 6 | 7 | This plugin requires ESLint >= v7.0.0 and Node >= 14. 8 | 9 | ### Installation 10 | 11 | **NPM**: 12 | 13 | ```bash 14 | npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect 15 | ``` 16 | 17 | **Yarn**: 18 | 19 | ```bash 20 | yarn add -D eslint-plugin-react-you-might-not-need-an-effect 21 | ``` 22 | 23 | ### Configuration 24 | 25 | Add the plugin's recommended config to your ESLint configuration file. 26 | 27 | #### Legacy config (`.eslintrc`) 28 | 29 | ```js 30 | { 31 | "extends": [ 32 | "plugin:react-you-might-not-need-an-effect/legacy-recommended" 33 | ], 34 | } 35 | ``` 36 | 37 | #### Flat config (`eslint.config.js`) 38 | 39 | ```js 40 | import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect"; 41 | 42 | export default [ 43 | reactYouMightNotNeedAnEffect.configs.recommended 44 | ]; 45 | ``` 46 | 47 | ### Recommended 48 | 49 | The plugin will have more information to act upon when you pass the correct dependencies to your effect — [`react-hooks/exhaustive-deps`](https://www.npmjs.com/package/eslint-plugin-react-hooks). 50 | 51 | ## 🔎 Rule: `you-might-not-need-an-effect` 52 | 53 | Determines when an effect is likely unnecessary, such as when it: 54 | 55 | - Stores derived state 56 | - Chains state updates 57 | - Initializes state 58 | - Resets all state when props change 59 | - Couples parent and child state or behavior 60 | 61 | When possible, also suggests the more idiomatic pattern. 62 | 63 | While the effect may be unnecessary, we cannot reliably determine that when it: 64 | 65 | - Uses external state 66 | - Calls external functions 67 | - Uses internal state to handle events 68 | 69 | ## ⚠️ Limitations 70 | 71 | This plugin aims to minimize false positives and accepts that some false negatives are inevitable — see the [tests](./test) for (in)valid examples. But the ways to (mis)use an effect are practically endless; if you encounter unexpected behavior or edge cases in real-world usage, please [open an issue](https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/new) with details about your scenario. Your feedback helps improve the plugin for everyone! 72 | 73 | ## 📖 Learn More 74 | 75 | - https://react.dev/reference/react/useEffect 76 | - https://react.dev/learn/you-might-not-need-an-effect 77 | - https://react.dev/learn/synchronizing-with-effects 78 | - https://react.dev/learn/separating-events-from-effects 79 | - https://react.dev/learn/lifecycle-of-reactive-effects 80 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import { build } from "esbuild"; 2 | 3 | build({ 4 | entryPoints: ["src/index.cjs"], 5 | bundle: true, 6 | sourcemap: true, 7 | format: "cjs", 8 | outfile: "dist/index.cjs", 9 | platform: "node", 10 | external: ["eslint"], 11 | }); 12 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import eslintPlugin from "eslint-plugin-eslint-plugin"; 3 | import nodePlugin from "eslint-plugin-n"; 4 | import globals from "globals"; 5 | 6 | export default [ 7 | eslint.configs.recommended, 8 | eslintPlugin.configs["flat/recommended"], 9 | nodePlugin.configs["flat/recommended"], 10 | { 11 | ignores: ["dist/**", "node_modules/**", ".yarn/**"], 12 | }, 13 | { 14 | languageOptions: { 15 | globals: { 16 | ...globals.node, 17 | }, 18 | }, 19 | }, 20 | { 21 | files: ["test/**"], 22 | languageOptions: { 23 | globals: { 24 | ...globals.mocha, 25 | }, 26 | }, 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-react-you-might-not-need-an-effect", 3 | "version": "0.1.4", 4 | "description": "ESLint rule to warn against unnecessary React useEffect hooks.", 5 | "author": "Nick van Dyke", 6 | "license": "MIT", 7 | "type": "module", 8 | "module": "./src/index.js", 9 | "main": "./dist/index.cjs", 10 | "types": "./types/index.d.ts", 11 | "files": [ 12 | "src", 13 | "types", 14 | "dist" 15 | ], 16 | "exports": { 17 | "types": "./types/index.d.ts", 18 | "import": "./src/index.js", 19 | "require": "./dist/index.cjs" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect.git" 24 | }, 25 | "scripts": { 26 | "build": "node build.js", 27 | "lint": "eslint", 28 | "test": "mocha" 29 | }, 30 | "keywords": [ 31 | "eslint", 32 | "eslintplugin", 33 | "eslint-plugin", 34 | "react" 35 | ], 36 | "engines": { 37 | "node": ">=14.0.0" 38 | }, 39 | "dependencies": { 40 | "eslint-utils": "^3.0.0", 41 | "globals": "^16.2.0" 42 | }, 43 | "devDependencies": { 44 | "@eslint/js": "^9.28.0", 45 | "esbuild": "^0.25.3", 46 | "eslint": "^9.20.1", 47 | "eslint-plugin-eslint-plugin": "^6.4.0", 48 | "eslint-plugin-n": "^17.17.0", 49 | "lint-staged": "^16.1.0", 50 | "mocha": "11.1.0", 51 | "prettier": "^3.5.3", 52 | "simple-git-hooks": "^2.13.0" 53 | }, 54 | "peerDependencies": { 55 | "eslint": ">=7.0.0" 56 | }, 57 | "packageManager": "yarn@4.6.0", 58 | "simple-git-hooks": { 59 | "pre-commit": "yarn lint-staged" 60 | }, 61 | "lint-staged": { 62 | "*.js": [ 63 | "prettier --write" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/index.cjs: -------------------------------------------------------------------------------- 1 | // `build.js` will bundle everything into CJS. 2 | // Would be nice to have it use `index.js` directly, but then `esbuild` 3 | // seems unable to structure the CJS export the way ESLint expects. 4 | // Seems we have to unwrap the default export ourselves for that. 5 | module.exports = require("./index.js").default; 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { name as ruleName, rule } from "./rule.js"; 2 | import globals from "globals"; 3 | 4 | const pluginName = "react-you-might-not-need-an-effect"; 5 | 6 | const plugin = { 7 | meta: { 8 | name: pluginName, 9 | }, 10 | configs: {}, 11 | rules: { 12 | [ruleName]: rule, 13 | }, 14 | }; 15 | 16 | Object.assign(plugin.configs, { 17 | // flat config format 18 | recommended: { 19 | files: ["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"], 20 | plugins: { 21 | // Object.assign above so we can reference `plugin` here 22 | [pluginName]: plugin, 23 | }, 24 | rules: { 25 | [pluginName + "/" + ruleName]: "warn", 26 | }, 27 | languageOptions: { 28 | globals: { 29 | // NOTE: Required so we can resolve global references to their upstream global variables 30 | ...globals.browser, 31 | }, 32 | parserOptions: { 33 | ecmaFeatures: { 34 | jsx: true, 35 | }, 36 | }, 37 | }, 38 | }, 39 | // eslintrc format 40 | "legacy-recommended": { 41 | plugins: [pluginName], 42 | rules: { 43 | [pluginName + "/" + ruleName]: "warn", 44 | }, 45 | globals: { 46 | // NOTE: Required so we can resolve global references to their upstream global variables 47 | ...globals.browser, 48 | }, 49 | parserOptions: { 50 | ecmaFeatures: { 51 | jsx: true, 52 | }, 53 | }, 54 | }, 55 | }); 56 | 57 | export default plugin; 58 | -------------------------------------------------------------------------------- /src/messages.js: -------------------------------------------------------------------------------- 1 | export const messageIds = { 2 | avoidEmptyEffect: "avoidEmptyEffect", 3 | avoidDerivedState: "avoidDerivedState", 4 | avoidInitializingState: "avoidInitializingState", 5 | avoidChainingState: "avoidChainingState", 6 | avoidParentChildCoupling: "avoidParentChildCoupling", 7 | avoidResettingStateFromProps: "avoidResettingStateFromProps", 8 | // TODO: This would be nice, but I'm not sure it can be done accurately 9 | // Maybe we can accurately warn about this when the state being reacted to is one of our own `useState`s? 10 | // Because if we have a setter then we have a callback. 11 | // But, I think that would also warn about valid uses that synchronize internal state to external state. 12 | // avoidEventHandler: "avoidEventHandler", 13 | // TODO: Possible to detect when `useSyncExternalStore` should be preferred? 14 | }; 15 | 16 | // TODO: Could include more info in messages, like the relevant node 17 | export const messages = { 18 | [messageIds.avoidEmptyEffect]: "This effect is empty and could be removed.", 19 | [messageIds.avoidDerivedState]: 20 | 'Avoid storing derived state. Compute "{{state}}" directly during render, optionally with `useMemo` if it\'s expensive.', 21 | [messageIds.avoidInitializingState]: 22 | 'Avoid initializing state in an effect. Instead, pass "{{state}}"\'s initial value to its `useState`.', 23 | [messageIds.avoidChainingState]: 24 | "Avoid chaining state changes. When possible, update all relevant state simultaneously.", 25 | [messageIds.avoidParentChildCoupling]: 26 | "Avoid coupling parent behavior or state to a child component. Instead, lift shared logic or state up to the parent.", 27 | [messageIds.avoidResettingStateFromProps]: 28 | 'Avoid resetting state from props. If "{{prop}}" is a key, pass it as `key` instead so React will reset the component.', 29 | // [messages.avoidEventHandler]: 30 | // "Avoid using state as an event handler. Instead, call the event handler directly.", 31 | }; 32 | -------------------------------------------------------------------------------- /src/rule.js: -------------------------------------------------------------------------------- 1 | import { messageIds, messages } from "./messages.js"; 2 | import { getCallExpr, getDownstreamRefs } from "./util/ast.js"; 3 | import { 4 | findPropUsedToResetAllState, 5 | isUseEffect, 6 | getUseStateNode, 7 | getEffectFnRefs, 8 | getDependenciesRefs, 9 | isStateSetter, 10 | isPropCallback, 11 | isDirectCall, 12 | getUpstreamReactVariables, 13 | isState, 14 | isProp, 15 | isHOCProp, 16 | } from "./util/react.js"; 17 | 18 | export const name = "you-might-not-need-an-effect"; 19 | 20 | export const rule = { 21 | meta: { 22 | type: "suggestion", 23 | docs: { 24 | description: "Catch unnecessary React useEffect hooks.", 25 | url: "https://react.dev/learn/you-might-not-need-an-effect", 26 | }, 27 | schema: [], 28 | messages: messages, 29 | }, 30 | create: (context) => ({ 31 | CallExpression: (node) => { 32 | if (!isUseEffect(node)) { 33 | return; 34 | } 35 | 36 | const effectFnRefs = getEffectFnRefs(context, node); 37 | const depsRefs = getDependenciesRefs(context, node); 38 | 39 | if (!effectFnRefs || !depsRefs) { 40 | return; 41 | } else if (effectFnRefs.length === 0) { 42 | // Hopefully it's obvious the effect can be removed. 43 | // More a follow-up for once they fix/remove other issues. 44 | context.report({ 45 | node, 46 | messageId: messageIds.avoidEmptyEffect, 47 | }); 48 | return; 49 | } 50 | 51 | const propUsedToResetAllState = findPropUsedToResetAllState( 52 | context, 53 | effectFnRefs, 54 | depsRefs, 55 | node, 56 | ); 57 | if (propUsedToResetAllState) { 58 | const propName = propUsedToResetAllState.identifier.name; 59 | context.report({ 60 | node: node, 61 | messageId: messageIds.avoidResettingStateFromProps, 62 | data: { prop: propName }, 63 | }); 64 | // Don't flag anything else -- confusing, and this should be fixed first. 65 | return; 66 | } 67 | 68 | effectFnRefs 69 | .filter( 70 | (ref) => 71 | isStateSetter(context, ref) || 72 | (isPropCallback(context, ref) && 73 | // Don't analyze HOC prop callbacks -- we don't have control over them to lift state or logic 74 | !isHOCProp(ref.resolved)), 75 | ) 76 | // Non-direct calls are likely inside a callback passed to an external system like `window.addEventListener`, 77 | // or a Promise chain that (probably) retrieves external data. 78 | // Note we'll still analyze derived setters because isStateSetter considers that. 79 | // Heuristic inspired by https://eslint-react.xyz/docs/rules/hooks-extra-no-direct-set-state-in-use-effect 80 | .filter((ref) => isDirectCall(ref.identifier)) 81 | .forEach((ref) => { 82 | const callExpr = getCallExpr(ref); 83 | 84 | if (isStateSetter(context, ref)) { 85 | const useStateNode = getUseStateNode(context, ref); 86 | const stateName = ( 87 | useStateNode.id.elements[0] ?? useStateNode.id.elements[1] 88 | )?.name; 89 | 90 | if (depsRefs.length === 0) { 91 | context.report({ 92 | node: callExpr, 93 | messageId: messageIds.avoidInitializingState, 94 | data: { state: stateName }, 95 | }); 96 | } 97 | 98 | // TODO: Make more readable 99 | const isArgsInternal = callExpr.arguments 100 | .flatMap((arg) => getDownstreamRefs(context, arg)) 101 | .flatMap((ref) => 102 | getUpstreamReactVariables(context, ref.identifier), 103 | ) 104 | .notEmptyEvery( 105 | (variable) => 106 | isState(variable) || 107 | (isProp(variable) && !isHOCProp(variable)), 108 | ); 109 | const isArgsExternal = callExpr.arguments 110 | .flatMap((arg) => getDownstreamRefs(context, arg)) 111 | .flatMap((ref) => 112 | getUpstreamReactVariables(context, ref.identifier), 113 | ) 114 | .some( 115 | (variable) => 116 | (!isState(variable) && !isProp(variable)) || 117 | isHOCProp(variable), 118 | ); 119 | const isDepsInternal = depsRefs 120 | .flatMap((ref) => 121 | getUpstreamReactVariables(context, ref.identifier), 122 | ) 123 | .notEmptyEvery( 124 | (variable) => 125 | isState(variable) || 126 | (isProp(variable) && !isHOCProp(variable)), 127 | ); 128 | 129 | if (isArgsInternal) { 130 | // TODO: Can also warn if this is the only call to the setter, 131 | // even if the arg is external (and not retrieved in the effect)... 132 | // Does it matter whether the args are in the deps array? 133 | // I guess so, to differentiate between derived and chain state updates? 134 | // What about internal vs in deps? Changes behavior, but meaningfully? 135 | context.report({ 136 | node: callExpr, 137 | messageId: messageIds.avoidDerivedState, 138 | data: { state: stateName }, 139 | }); 140 | } 141 | 142 | if (!isArgsInternal && !isArgsExternal && isDepsInternal) { 143 | context.report({ 144 | node: callExpr, 145 | messageId: messageIds.avoidChainingState, 146 | }); 147 | } 148 | } else if (isPropCallback(context, ref)) { 149 | // I'm pretty sure we can flag this regardless of the arguments, including none... 150 | // 151 | // Because we are either: 152 | // 1. Passing live state updates to the parent 153 | // 2. Using state as an event handler to pass final state to the parent 154 | // 155 | // Both are bad. However I'm not yet sure how we could differentiate #2 to give a better warning. 156 | // 157 | // TODO: Can we thus safely assume that state is used as an event handler when the ref is a prop? 158 | // Normally we can't warn about that because we don't know what the event handler does externally. 159 | // But when it's a prop, it's internal. 160 | // I guess it could still be valid when the dep is external state? Or in that case, 161 | // the issue is the state should be lifted to the parent? 162 | context.report({ 163 | node: callExpr, 164 | messageId: messageIds.avoidParentChildCoupling, 165 | }); 166 | } 167 | }); 168 | }, 169 | }), 170 | }; 171 | -------------------------------------------------------------------------------- /src/util/ast.js: -------------------------------------------------------------------------------- 1 | import { findVariable } from "eslint-utils"; 2 | 3 | export const traverse = (context, node, visit, visited = new Set()) => { 4 | if (visited.has(node)) { 5 | return; 6 | } 7 | 8 | visited.add(node); 9 | visit(node); 10 | 11 | (context.sourceCode.visitorKeys[node.type] || []) 12 | .map((key) => node[key]) 13 | // Some `visitorKeys` are optional, e.g. `IfStatement.alternate`. 14 | .filter(Boolean) 15 | // Can be an array, like `CallExpression.arguments` 16 | .flatMap((child) => (Array.isArray(child) ? child : [child])) 17 | // Can rarely be `null`, e.g. `ArrayPattern.elements[1]` when an element is skipped - `const [a, , b] = arr` 18 | .filter(Boolean) 19 | // Check it's a valid AST node 20 | .filter((child) => typeof child.type === "string") 21 | .forEach((child) => traverse(context, child, visit, visited)); 22 | }; 23 | 24 | const getDownstreamIdentifiers = (context, rootNode) => { 25 | const identifiers = []; 26 | traverse(context, rootNode, (node) => { 27 | if (node.type === "Identifier") { 28 | identifiers.push(node); 29 | } 30 | }); 31 | return identifiers; 32 | }; 33 | 34 | export const getUpstreamVariables = ( 35 | context, 36 | node, 37 | filter, 38 | visited = new Set(), 39 | ) => { 40 | if (visited.has(node)) { 41 | return []; 42 | } 43 | 44 | visited.add(node); 45 | 46 | const variable = findVariable(context.sourceCode.getScope(node), node); 47 | if (!variable) { 48 | // I think this only happens when: 49 | // 1. There's genuinely no variable, i.e. `node` is a literal 50 | // 2. Import statement is missing 51 | // 3. ESLint globals are misconfigured 52 | return []; 53 | } 54 | 55 | const upstreamVariables = variable.defs 56 | .filter((def) => !!def.node.init) 57 | .filter((def) => filter(def.node)) 58 | .flatMap((def) => getDownstreamIdentifiers(context, def.node.init)) 59 | .flatMap((identifier) => 60 | getUpstreamVariables(context, identifier, filter, visited), 61 | ); 62 | 63 | // Ultimately return only leaf variables 64 | return upstreamVariables.length === 0 ? [variable] : upstreamVariables; 65 | }; 66 | 67 | export const getDownstreamRefs = (context, node) => 68 | getDownstreamIdentifiers(context, node) 69 | .map((identifier) => getRef(context, identifier)) 70 | .filter(Boolean); 71 | 72 | const getRef = (context, identifier) => 73 | findVariable( 74 | context.sourceCode.getScope(identifier), 75 | identifier, 76 | )?.references.find((ref) => ref.identifier === identifier); 77 | 78 | export const getCallExpr = (ref, current = ref.identifier.parent) => { 79 | if (current.type === "CallExpression") { 80 | // We've reached the top - confirm that the ref is the (eventual) callee, as opposed to an argument. 81 | let node = ref.identifier; 82 | while (node.parent.type === "MemberExpression") { 83 | node = node.parent; 84 | } 85 | 86 | if (current.callee === node) { 87 | return current; 88 | } 89 | } 90 | 91 | if (current.type === "MemberExpression") { 92 | return getCallExpr(ref, current.parent); 93 | } 94 | 95 | return undefined; 96 | }; 97 | 98 | export const isIIFE = (node) => 99 | node.type === "CallExpression" && 100 | (node.callee.type === "ArrowFunctionExpression" || 101 | node.callee.type === "FunctionExpression"); 102 | -------------------------------------------------------------------------------- /src/util/react.js: -------------------------------------------------------------------------------- 1 | import { 2 | traverse, 3 | getUpstreamVariables, 4 | getDownstreamRefs, 5 | getCallExpr, 6 | isIIFE, 7 | } from "./ast.js"; 8 | 9 | export const isReactFunctionalComponent = (node) => 10 | (node.type === "FunctionDeclaration" || 11 | (node.type === "VariableDeclarator" && 12 | (node.init.type === "ArrowFunctionExpression" || 13 | node.init.type === "CallExpression"))) && 14 | node.id.type === "Identifier" && 15 | node.id.name[0].toUpperCase() === node.id.name[0]; 16 | 17 | // NOTE: Returns false for known pure HOCs -- `memo` and `forwardRef`. 18 | // TODO: Will not detect when they define the component normally and then export it wrapped in the HOC. 19 | // e.g. `const MyComponent = (props) => {...}; export default memo(MyComponent);` 20 | export const isReactFunctionalHOC = (node) => 21 | node.type === "VariableDeclarator" && 22 | node.init && 23 | node.init.type === "CallExpression" && 24 | node.init.callee.type === "Identifier" && 25 | !["memo", "forwardRef"].includes(node.init.callee.name) && 26 | node.init.arguments.length > 0 && 27 | (node.init.arguments[0].type === "ArrowFunctionExpression" || 28 | node.init.arguments[0].type === "FunctionExpression") && 29 | node.id.type === "Identifier" && 30 | node.id.name[0].toUpperCase() === node.id.name[0]; 31 | 32 | export const isCustomHook = (node) => 33 | (node.type === "FunctionDeclaration" || 34 | (node.type === "VariableDeclarator" && 35 | node.init && 36 | (node.init.type === "ArrowFunctionExpression" || 37 | node.init.type === "FunctionExpression"))) && 38 | node.id.type === "Identifier" && 39 | node.id.name.startsWith("use") && 40 | node.id.name[3] === node.id.name[3].toUpperCase(); 41 | 42 | export const isUseState = (node) => 43 | node.type === "VariableDeclarator" && 44 | node.init && 45 | node.init.type === "CallExpression" && 46 | node.init.callee.name === "useState" && 47 | node.id.type === "ArrayPattern" && 48 | // Not sure its usecase, but may just have the setter 49 | (node.id.elements.length === 1 || node.id.elements.length === 2) && 50 | node.id.elements.every((el) => { 51 | // Apparently skipping the state element is a valid use. 52 | // I suppose technically the state can still be read via setter callback. 53 | return !el || el.type === "Identifier"; 54 | }); 55 | 56 | export const isUseEffect = (node) => 57 | node.type === "CallExpression" && 58 | ((node.callee.type === "Identifier" && 59 | (node.callee.name === "useEffect" || 60 | node.callee.name === "useLayoutEffect")) || 61 | (node.callee.type === "MemberExpression" && 62 | node.callee.object.name === "React" && 63 | (node.callee.property.name === "useEffect" || 64 | node.callee.property.name === "useLayoutEffect"))); 65 | 66 | export const getEffectFn = (node) => { 67 | if (!isUseEffect(node) || node.arguments.length < 1) { 68 | return undefined; 69 | } 70 | 71 | const effectFn = node.arguments[0]; 72 | if ( 73 | effectFn.type !== "ArrowFunctionExpression" && 74 | effectFn.type !== "FunctionExpression" 75 | ) { 76 | return undefined; 77 | } 78 | 79 | return effectFn; 80 | }; 81 | 82 | // NOTE: When `MemberExpression` (even nested ones), a `Reference` is only the root object, not the function. 83 | export const getEffectFnRefs = (context, node) => { 84 | const effectFn = getEffectFn(node); 85 | if (!effectFn) { 86 | return null; 87 | } 88 | 89 | return getDownstreamRefs(context, effectFn); 90 | }; 91 | 92 | export function getDependenciesRefs(context, node) { 93 | if (!isUseEffect(node) || node.arguments.length < 2) { 94 | return undefined; 95 | } 96 | 97 | const depsArr = node.arguments[1]; 98 | if (depsArr.type !== "ArrayExpression") { 99 | return undefined; 100 | } 101 | 102 | return getDownstreamRefs(context, depsArr); 103 | } 104 | 105 | export const isFnRef = (ref) => getCallExpr(ref) !== undefined; 106 | 107 | // NOTE: These return true for state with CallExpressions, like `list.concat()`. 108 | // Arguably preferable, as mutating the state is functionally the same as calling the setter. 109 | // (Even though that is not recommended and should be prevented by a different rule). 110 | // And in the case of a prop, we can't differentiate state mutations from callbacks anyway. 111 | export const isStateSetter = (context, ref) => 112 | isFnRef(ref) && 113 | getUpstreamReactVariables(context, ref.identifier).notEmptyEvery((variable) => 114 | isState(variable), 115 | ); 116 | export const isPropCallback = (context, ref) => 117 | isFnRef(ref) && 118 | getUpstreamReactVariables(context, ref.identifier).notEmptyEvery((variable) => 119 | isProp(variable), 120 | ); 121 | 122 | // NOTE: Global variables (like `JSON` in `JSON.stringify()`) have an empty `defs`; fortunately `[].some() === false`. 123 | // Also, I'm not sure so far when `defs.length > 1`... haven't seen it with shadowed variables or even redeclared variables with `var`. 124 | export const isState = (variable) => 125 | variable.defs.some((def) => isUseState(def.node)); 126 | export const isProp = (variable) => 127 | variable.defs.some( 128 | (def) => 129 | def.type === "Parameter" && 130 | (isReactFunctionalComponent(getDeclNode(def.node)) || 131 | isCustomHook(getDeclNode(def.node))), 132 | ); 133 | export const isHOCProp = (variable) => 134 | variable.defs.some( 135 | (def) => 136 | def.type === "Parameter" && isReactFunctionalHOC(getDeclNode(def.node)), 137 | ); 138 | 139 | const getDeclNode = (node) => 140 | node.type === "ArrowFunctionExpression" 141 | ? node.parent.type === "CallExpression" 142 | ? node.parent.parent 143 | : node.parent 144 | : node; 145 | 146 | export const getUseStateNode = (context, ref) => { 147 | return getUpstreamReactVariables(context, ref.identifier) 148 | .find((variable) => isState(variable)) 149 | ?.defs.find((def) => isUseState(def.node))?.node; 150 | }; 151 | 152 | // Returns true if the node is called directly inside a `useEffect`. 153 | // Note IIFEs do not break the "direct" chain because they're invoked immediately, as opposed to being a callback. 154 | export const isDirectCall = (node) => { 155 | if (!node) { 156 | return false; 157 | } else if ( 158 | (node.type === "ArrowFunctionExpression" || 159 | node.type === "FunctionExpression") && 160 | !isIIFE(node.parent) 161 | ) { 162 | return isUseEffect(node.parent); 163 | } else { 164 | return isDirectCall(node.parent); 165 | } 166 | }; 167 | 168 | export const findPropUsedToResetAllState = ( 169 | context, 170 | effectFnRefs, 171 | depsRefs, 172 | useEffectNode, 173 | ) => { 174 | const stateSetterRefs = effectFnRefs.filter((ref) => 175 | isStateSetter(context, ref), 176 | ); 177 | 178 | const isAllStateReset = 179 | stateSetterRefs.length > 0 && 180 | stateSetterRefs.every((ref) => isSetStateToInitialValue(context, ref)) && 181 | stateSetterRefs.length === 182 | countUseStates(context, findContainingNode(useEffectNode)); 183 | 184 | return isAllStateReset 185 | ? depsRefs.find((ref) => isProp(ref.resolved)) 186 | : undefined; 187 | }; 188 | 189 | const isSetStateToInitialValue = (context, setterRef) => { 190 | const setStateToValue = getCallExpr(setterRef).arguments[0]; 191 | const stateInitialValue = getUseStateNode(context, setterRef).init 192 | .arguments[0]; 193 | 194 | // `useState()` (with no args) defaults to `undefined`, 195 | // so ommitting the arg is equivalent to passing `undefined`. 196 | // Technically this would false positive if they shadowed 197 | // `undefined` in only one of the scopes (only possible via `var`), 198 | // but I hope no one would do that. 199 | const isUndefined = (node) => node === undefined || node.name === "undefined"; 200 | if (isUndefined(setStateToValue) && isUndefined(stateInitialValue)) { 201 | return true; 202 | } 203 | 204 | // `sourceCode.getText()` returns the entire file when passed null/undefined - let's short circuit that 205 | if (setStateToValue === null && stateInitialValue === null) { 206 | return true; 207 | } else if ( 208 | (setStateToValue && !stateInitialValue) || 209 | (!setStateToValue && stateInitialValue) 210 | ) { 211 | return false; 212 | } 213 | 214 | return ( 215 | context.sourceCode.getText(setStateToValue) === 216 | context.sourceCode.getText(stateInitialValue) 217 | ); 218 | }; 219 | 220 | const countUseStates = (context, componentNode) => { 221 | let count = 0; 222 | 223 | traverse(context, componentNode, (node) => { 224 | if (isUseState(node)) { 225 | count++; 226 | } 227 | }); 228 | 229 | return count; 230 | }; 231 | 232 | // Returns the component or custom hook that contains the `useEffect` node. 233 | const findContainingNode = (node) => { 234 | if (!node) { 235 | return undefined; 236 | } else if ( 237 | isReactFunctionalComponent(node) || 238 | isReactFunctionalHOC(node) || 239 | isCustomHook(node) 240 | ) { 241 | return node; 242 | } else { 243 | return findContainingNode(node.parent); 244 | } 245 | }; 246 | 247 | export const getUpstreamReactVariables = (context, node) => 248 | getUpstreamVariables( 249 | context, 250 | node, 251 | // Stop at the *usage* of `useState` - don't go up to the `useState` variable. 252 | // Not needed for props - they don't go "too far". 253 | // We could remove this and check for the `useState` variable instead, 254 | // but then all our tests need to import it so we can traverse up to it. 255 | // And would need to change `getUseStateNode()` too? 256 | // TODO: Could probably organize these filters better. 257 | (node) => !isUseState(node), 258 | ).filter( 259 | (variable) => 260 | isProp(variable) || 261 | variable.defs.every((def) => def.type !== "Parameter"), 262 | ); 263 | 264 | Array.prototype.notEmptyEvery = function (predicate) { 265 | return this.length > 0 && this.every(predicate); 266 | }; 267 | -------------------------------------------------------------------------------- /test/chaining-state.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/chaining-state", { 5 | invalid: [ 6 | { 7 | // React docs recommend to first update state in render instead of an effect. 8 | // But then continue on to say that usually you can avoid the sync entirely by 9 | // more wisely choosing your state. So we'll just always warn about chained state. 10 | name: "Syncing prop changes to internal state", 11 | code: js` 12 | function List({ items }) { 13 | const [selection, setSelection] = useState(); 14 | 15 | useEffect(() => { 16 | setSelection(null); 17 | }, [items]); 18 | 19 | return ( 20 |
21 | {items.map((item) => ( 22 |
setSelection(item)}> 23 | {item.name} 24 |
25 | ))} 26 |
27 | ) 28 | } 29 | `, 30 | errors: [ 31 | { 32 | messageId: messageIds.avoidChainingState, 33 | }, 34 | ], 35 | }, 36 | { 37 | name: "Conditionally setting state from internal state", 38 | code: js` 39 | function Form() { 40 | const [error, setError] = useState(); 41 | const [result, setResult] = useState(); 42 | 43 | useEffect(() => { 44 | if (result.data) { 45 | setError(null); 46 | } 47 | }, [result]); 48 | } 49 | `, 50 | errors: [ 51 | { 52 | messageId: messageIds.avoidChainingState, 53 | }, 54 | ], 55 | }, 56 | { 57 | name: "In an otherwise valid effect", 58 | code: js` 59 | function MyComponent() { 60 | const [state, setState] = useState(); 61 | const [otherState, setOtherState] = useState('Meow'); 62 | 63 | useEffect(() => { 64 | console.log('Meow'); 65 | setState('Hello World'); 66 | }, [otherState]); 67 | } 68 | `, 69 | errors: [ 70 | { 71 | messageId: messageIds.avoidChainingState, 72 | data: { state: "state" }, 73 | }, 74 | ], 75 | }, 76 | ], 77 | }); 78 | -------------------------------------------------------------------------------- /test/config.test.js: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint"; 2 | import plugin from "../src/index.js"; 3 | import assert from "assert"; 4 | import { js } from "./rule-tester.js"; 5 | import { LegacyESLint } from "eslint/use-at-your-own-risk"; 6 | 7 | describe("Recommended config", () => { 8 | const code = js` 9 | import { useState, useEffect } from "react"; 10 | 11 | const MyComponent = () => { 12 | const [state, setState] = useState(0); 13 | const [otherState, setOtherState] = useState(0); 14 | 15 | useEffect(() => { 16 | setState(otherState * 2); 17 | }, [state]); 18 | }; 19 | `; 20 | 21 | const testCases = [ 22 | { 23 | name: "Flat", 24 | eslint: new ESLint({ 25 | // Use `overrideConfig` so it ignores the project's config 26 | overrideConfig: [plugin.configs.recommended], 27 | }), 28 | }, 29 | { 30 | name: "Legacy", 31 | eslint: new LegacyESLint({ 32 | overrideConfig: { 33 | extends: [ 34 | "plugin:react-you-might-not-need-an-effect/legacy-recommended", 35 | ], 36 | parserOptions: { 37 | // To support the syntax in the code under test 38 | ecmaVersion: 2020, 39 | sourceType: "module", 40 | }, 41 | }, 42 | }), 43 | }, 44 | ]; 45 | 46 | testCases.forEach(({ name, eslint }) => { 47 | it(name, async () => { 48 | const results = await eslint.lintText(code); 49 | 50 | assert.strictEqual(results.length, 1); 51 | assert.ok(results[0].messages); 52 | assert.ok( 53 | results[0].messages.some( 54 | (message) => 55 | message.ruleId === 56 | "react-you-might-not-need-an-effect/you-might-not-need-an-effect", 57 | ), 58 | ); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/deriving-state.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | // TODO: All these need the state setter in the deps 5 | new MyRuleTester().run("/deriving-state", { 6 | valid: [ 7 | { 8 | name: "Compute in render from internal state", 9 | code: js` 10 | function Form() { 11 | const [firstName, setFirstName] = useState('Taylor'); 12 | const [lastName, setLastName] = useState('Swift'); 13 | 14 | const fullName = firstName + ' ' + lastName; 15 | } 16 | `, 17 | }, 18 | { 19 | name: "Compute in render from props", 20 | code: js` 21 | function Form({ firstName, lastName }) { 22 | const fullName = firstName + ' ' + lastName; 23 | } 24 | `, 25 | }, 26 | { 27 | name: "From external state change", 28 | code: js` 29 | function Feed() { 30 | const { data: posts } = useQuery('/posts'); 31 | const [scrollPosition, setScrollPosition] = useState(0); 32 | 33 | useEffect(() => { 34 | setScrollPosition(0); 35 | }, [posts]); 36 | } 37 | `, 38 | }, 39 | { 40 | name: "From external state change, with multiple setter calls", 41 | code: js` 42 | function Feed() { 43 | const { data: posts } = useQuery('/posts'); 44 | const [selectedPost, setSelectedPost] = useState(); 45 | 46 | useEffect(() => { 47 | setSelectedPost(posts[0]); 48 | }, [posts]); 49 | 50 | return ( 51 |
52 | {posts.map((post) => ( 53 |
setSelectedPost(post)}> 54 | {post.title} 55 |
56 | ))} 57 |
58 | ) 59 | } 60 | `, 61 | }, 62 | { 63 | name: "Fetch external state on mount", 64 | code: js` 65 | function Todos() { 66 | const [todos, setTodos] = useState([]); 67 | 68 | useEffect(() => { 69 | fetch('/todos').then((todos) => { 70 | setTodos(todos); 71 | }); 72 | }, []); 73 | } 74 | `, 75 | }, 76 | { 77 | name: "Sync external state", 78 | // Technically we could trigger the network call in `input.onChange`, 79 | // but the use of an effect to sync state is arguably more readable and a valid use. 80 | // Especially when we already store the input's controlled state. 81 | code: js` 82 | function Search() { 83 | const [query, setQuery] = useState(); 84 | const [results, setResults] = useState(); 85 | 86 | useEffect(() => { 87 | fetch('/search').then((data) => { 88 | setResults(data); 89 | }); 90 | }, [query]); 91 | 92 | return ( 93 |
94 | setQuery(e.target.value)} 99 | /> 100 | 105 |
106 | ) 107 | } 108 | `, 109 | }, 110 | { 111 | name: "Subscribe to external state", 112 | code: js` 113 | import { subscribeToStatus } from 'library'; 114 | 115 | function Status({ topic }) { 116 | const [status, setStatus] = useState(); 117 | 118 | useEffect(() => { 119 | const unsubscribe = subscribeToStatus(topic, (status) => { 120 | setStatus(status); 121 | }); 122 | 123 | return () => unsubscribe(); 124 | }, [topic]); 125 | 126 | return
{status}
; 127 | } 128 | `, 129 | }, 130 | { 131 | name: "From derived external state with multiple calls to setter", 132 | code: js` 133 | function Form() { 134 | const name = useQuery('/name'); 135 | const [fullName, setFullName] = useState(''); 136 | 137 | useEffect(() => { 138 | const prefixedName = 'Dr. ' + name; 139 | setFullName(prefixedName) 140 | }, [name]); 141 | 142 | return ( 143 | setFullName(e.target.value)} 148 | /> 149 | ) 150 | } 151 | `, 152 | }, 153 | { 154 | name: "From external state via member function", 155 | code: js` 156 | function Counter() { 157 | const countGetter = useSomeAPI(); 158 | const [count, setCount] = useState(0); 159 | 160 | useEffect(() => { 161 | const newCount = countGetter.getCount(); 162 | setCount(newCount); 163 | }, [countGetter, setCount]); 164 | } 165 | `, 166 | }, 167 | { 168 | name: "Via pure local function", 169 | code: js` 170 | function DoubleCounter() { 171 | const [count, setCount] = useState(0); 172 | const [doubleCount, setDoubleCount] = useState(0); 173 | 174 | function calculateDoubleCount(count) { 175 | return count * 2; 176 | } 177 | 178 | useEffect(() => { 179 | setDoubleCount(calculateDoubleCount(count)); 180 | }, [count]); 181 | } 182 | `, 183 | }, 184 | { 185 | name: "Via unpure local function", 186 | code: js` 187 | function Counter() { 188 | const [count, setCount] = useState(0); 189 | 190 | function calculate(count) { 191 | return count * fetch('/multipler'); 192 | } 193 | 194 | useEffect(() => { 195 | setCount(calculate(count)); 196 | }, [count]); 197 | } 198 | `, 199 | }, 200 | { 201 | name: "From props via unpure derived setter", 202 | code: js` 203 | function DoubleCounter({ count }) { 204 | const [doubleCount, setDoubleCount] = useState(0); 205 | 206 | const derivedSetter = (count) => { 207 | const multipler = fetch('/multipler'); 208 | setDoubleCount(count); 209 | } 210 | 211 | useEffect(() => { 212 | derivedSetter(count); 213 | }, [count]); 214 | } 215 | `, 216 | }, 217 | { 218 | name: "Via pure global function", 219 | code: js` 220 | function Counter({ count }) { 221 | const [countJson, setCountJson] = useState(); 222 | 223 | useEffect(() => { 224 | setCountJson(JSON.stringify(count)); 225 | }, [count]); 226 | } 227 | `, 228 | }, 229 | { 230 | name: "Via unpure global function", 231 | code: js` 232 | function Counter({ count }) { 233 | const [multipliedCount, setMultipliedCount] = useState(); 234 | 235 | useEffect(() => { 236 | const multipler = fetch('/multipler'); 237 | setMultipliedCount(count * multipler); 238 | }, [count]); 239 | } 240 | `, 241 | }, 242 | { 243 | name: "From internal and external state", 244 | code: js` 245 | import { getPrefixFor } from 'library'; 246 | import { useState } from 'react'; 247 | 248 | function Component() { 249 | const [name, setName] = useState(); 250 | const [prefixedName, setPrefixedName] = useState(); 251 | const prefix = getPrefixFor(name); 252 | 253 | useEffect(() => { 254 | setPrefixedName(prefix + name); 255 | }, [name, prefix]) 256 | } 257 | `, 258 | }, 259 | { 260 | name: "From derived internal and external state", 261 | code: js` 262 | import { getPrefixFor } from 'library'; 263 | import { useState } from 'react'; 264 | 265 | function Component() { 266 | const [name, setName] = useState(); 267 | const [prefixedName, setPrefixedName] = useState(); 268 | const prefix = getPrefixFor(name); 269 | const newValue = prefix + name; 270 | 271 | useEffect(() => { 272 | setPrefixedName(newValue); 273 | }, [newValue]) 274 | } 275 | `, 276 | }, 277 | { 278 | name: "From external state via useCallback derived setter", 279 | code: js` 280 | import { getPrefixFor } from 'library'; 281 | import { useState } from 'react'; 282 | 283 | function Component() { 284 | const [name, setName] = useState(); 285 | const [prefixedName, setPrefixedName] = useState(); 286 | const prefix = getPrefixFor(name); 287 | 288 | const derivedSetter = useCallback((name) => { 289 | setPrefixedName(prefix + name); 290 | }, [prefix]); 291 | 292 | useEffect(() => { 293 | derivedSetter(name); 294 | }, [name, derivedSetter]) 295 | } 296 | `, 297 | }, 298 | ], 299 | invalid: [ 300 | { 301 | name: "From internal state", 302 | code: js` 303 | function Form() { 304 | const [firstName, setFirstName] = useState('Taylor'); 305 | const [lastName, setLastName] = useState('Swift'); 306 | 307 | const [fullName, setFullName] = useState(''); 308 | useEffect(() => setFullName(firstName + ' ' + lastName), [firstName, lastName]); 309 | } 310 | `, 311 | errors: [ 312 | { 313 | messageId: messageIds.avoidDerivedState, 314 | data: { state: "fullName" }, 315 | }, 316 | ], 317 | }, 318 | { 319 | name: "From derived internal state", 320 | code: js` 321 | function Form() { 322 | const [firstName, setFirstName] = useState('Taylor'); 323 | const [lastName, setLastName] = useState('Swift'); 324 | const [fullName, setFullName] = useState(''); 325 | 326 | useEffect(() => { 327 | const name = firstName + ' ' + lastName; 328 | setFullName(name) 329 | }, [firstName, lastName]); 330 | } 331 | `, 332 | errors: [ 333 | { 334 | messageId: messageIds.avoidDerivedState, 335 | data: { state: "fullName" }, 336 | }, 337 | ], 338 | }, 339 | { 340 | name: "From derived internal state outside effect", 341 | code: js` 342 | function Form() { 343 | const [firstName, setFirstName] = useState('Taylor'); 344 | const [lastName, setLastName] = useState('Swift'); 345 | const [fullName, setFullName] = useState(''); 346 | const name = firstName + ' ' + lastName; 347 | 348 | useEffect(() => { 349 | setFullName(name) 350 | }, [name]); 351 | } 352 | `, 353 | errors: [ 354 | { 355 | messageId: messageIds.avoidDerivedState, 356 | data: { state: "fullName" }, 357 | }, 358 | ], 359 | }, 360 | { 361 | name: "From props", 362 | code: js` 363 | function Form({ firstName, lastName }) { 364 | const [fullName, setFullName] = useState(''); 365 | 366 | useEffect(() => { 367 | setFullName(firstName + ' ' + lastName); 368 | }, [firstName, lastName]); 369 | } 370 | `, 371 | errors: [ 372 | { 373 | messageId: messageIds.avoidDerivedState, 374 | data: { state: "fullName" }, 375 | }, 376 | ], 377 | }, 378 | { 379 | name: "From derived prop", 380 | code: js` 381 | function Form({ firstName, lastName }) { 382 | const [fullName, setFullName] = useState(''); 383 | const prefixedName = 'Dr. ' + firstName; 384 | 385 | useEffect(() => { 386 | setFullName(prefixedName + ' ' + lastName); 387 | }, [prefixedName, lastName]); 388 | } 389 | `, 390 | errors: [ 391 | { 392 | messageId: messageIds.avoidDerivedState, 393 | data: { state: "fullName" }, 394 | }, 395 | ], 396 | }, 397 | { 398 | name: "From props via member function", 399 | code: js` 400 | function DoubleList({ list }) { 401 | const [doubleList, setDoubleList] = useState([]); 402 | 403 | useEffect(() => { 404 | setDoubleList(list.concat(list)); 405 | }, [list]); 406 | } 407 | `, 408 | errors: [ 409 | { 410 | messageId: messageIds.avoidDerivedState, 411 | data: { state: "doubleList" }, 412 | }, 413 | { 414 | // We consider `list.concat` to essentially be a prop callback 415 | messageId: messageIds.avoidParentChildCoupling, 416 | }, 417 | ], 418 | }, 419 | { 420 | name: "From internal state via member function", 421 | code: js` 422 | function DoubleList() { 423 | const [list, setList] = useState([]); 424 | const [doubleList, setDoubleList] = useState([]); 425 | 426 | useEffect(() => { 427 | setDoubleList(list.concat(list)); 428 | }, [list]); 429 | } 430 | `, 431 | errors: [ 432 | { 433 | messageId: messageIds.avoidDerivedState, 434 | data: { state: "doubleList" }, 435 | }, 436 | { 437 | // We consider `list.concat` to essentially be a state setter call 438 | messageId: messageIds.avoidDerivedState, 439 | data: { state: "list" }, 440 | }, 441 | ], 442 | }, 443 | { 444 | name: "Mutate internal state", 445 | code: js` 446 | function DoubleList() { 447 | const [list, setList] = useState([]); 448 | const [doubleList, setDoubleList] = useState([]); 449 | 450 | useEffect(() => { 451 | doubleList.push(...list); 452 | }, [list]); 453 | } 454 | `, 455 | errors: [ 456 | { 457 | // We consider `doubleList.push` to essentially be a state setter call 458 | messageId: messageIds.avoidDerivedState, 459 | data: { state: "doubleList" }, 460 | }, 461 | ], 462 | }, 463 | { 464 | name: "From external state with single setter call", 465 | todo: true, 466 | code: js` 467 | function Feed() { 468 | const { data: posts } = useQuery('/posts'); 469 | const [selectedPost, setSelectedPost] = useState(); 470 | 471 | useEffect(() => { 472 | // This is the only place that modifies the state, 473 | // thus they will always be in sync and it could be computed during render 474 | // Difficult bit is that a single state setter call is legit when the 475 | // external state is initialized inside the effect (i.e. retrieved from external system) 476 | // Hopefully 'isDirectCall' will mostly catch that now. 477 | setSelectedPost(posts[0]); 478 | }, [posts]); 479 | } 480 | `, 481 | errors: [ 482 | { 483 | messageId: messageIds.avoidDerivedState, 484 | data: { state: "selectedPost" }, 485 | }, 486 | ], 487 | }, 488 | { 489 | name: "From derived external state with single setter call", 490 | todo: true, 491 | code: js` 492 | function Form() { 493 | const name = useQuery('/name'); 494 | const [fullName, setFullName] = useState(''); 495 | 496 | useEffect(() => { 497 | const prefixedName = 'Dr. ' + name; 498 | setFullName(prefixedName) 499 | }, [name]); 500 | } 501 | `, 502 | errors: [ 503 | { 504 | messageId: messageIds.avoidDerivedState, 505 | data: { state: "fullName" }, 506 | }, 507 | ], 508 | }, 509 | { 510 | name: "From HOC prop with single setter call", 511 | todo: true, 512 | code: js` 513 | import { withRouter } from 'react-router-dom'; 514 | 515 | const MyComponent = withRouter(({ history }) => { 516 | const [location, setLocation] = useState(); 517 | 518 | useEffect(() => { 519 | setLocation(history.location); 520 | }, [history.location]); 521 | }); 522 | `, 523 | errors: [ 524 | { 525 | messageId: messageIds.avoidDerivedState, 526 | data: { state: "fullName" }, 527 | }, 528 | ], 529 | }, 530 | { 531 | name: "From props via callback setter", 532 | code: js` 533 | import { useState, useEffect } from 'react'; 534 | 535 | function CountAccumulator({ count }) { 536 | const [total, setTotal] = useState(count); 537 | 538 | useEffect(() => { 539 | setTotal((prev) => prev + count); 540 | }, [count]); 541 | } 542 | `, 543 | errors: [ 544 | { 545 | messageId: messageIds.avoidDerivedState, 546 | data: { state: "total" }, 547 | }, 548 | ], 549 | }, 550 | { 551 | name: "From props via pure derived setter", 552 | code: js` 553 | function DoubleCounter({ count }) { 554 | const [doubleCount, setDoubleCount] = useState(0); 555 | 556 | const derivedSetter = (count) => setDoubleCount(count * 2); 557 | 558 | useEffect(() => { 559 | derivedSetter(count); 560 | }, [count]); 561 | } 562 | `, 563 | errors: [ 564 | { 565 | messageId: messageIds.avoidDerivedState, 566 | data: { state: "doubleCount" }, 567 | }, 568 | ], 569 | }, 570 | { 571 | name: "From internal state via useCallback derived setter", 572 | todo: true, 573 | code: js` 574 | import { getPrefixFor } from 'library'; 575 | import { useState } from 'react'; 576 | 577 | function Component() { 578 | const [name, setName] = useState(); 579 | const [prefixedName, setPrefixedName] = useState(); 580 | const prefix = 'Dr. '; 581 | 582 | const derivedSetter = useCallback((name) => { 583 | setPrefixedName(prefix + name); 584 | }, [prefix]); 585 | 586 | useEffect(() => { 587 | derivedSetter(name); 588 | }, [name, derivedSetter]); 589 | } 590 | `, 591 | errors: [ 592 | { 593 | messageId: messageIds.avoidDerivedState, 594 | data: { state: "prefixedName" }, 595 | }, 596 | ], 597 | }, 598 | { 599 | name: "Partially update complex state from props", 600 | code: js` 601 | function Form({ firstName, lastName }) { 602 | const [formData, setFormData] = useState({ 603 | title: 'Dr.', 604 | fullName: '', 605 | }); 606 | 607 | useEffect(() => { 608 | setFormData({ 609 | ...formData, 610 | fullName: firstName + ' ' + lastName, 611 | }); 612 | }, [firstName, lastName, formData]); 613 | } 614 | `, 615 | errors: [ 616 | { 617 | messageId: messageIds.avoidDerivedState, 618 | data: { state: "formData" }, 619 | }, 620 | ], 621 | }, 622 | { 623 | name: "Partially update complex state from props via callback setter", 624 | code: js` 625 | function Form({ firstName, lastName }) { 626 | const [formData, setFormData] = useState({ 627 | title: 'Dr.', 628 | fullName: '', 629 | }); 630 | 631 | useEffect(() => { 632 | setFormData((prev) => ({ 633 | ...prev, 634 | fullName: firstName + ' ' + lastName, 635 | })); 636 | }, [firstName, lastName]); 637 | } 638 | `, 639 | errors: [ 640 | { 641 | messageId: messageIds.avoidDerivedState, 642 | data: { state: "formData" }, 643 | }, 644 | ], 645 | }, 646 | { 647 | name: "Partially update complex state from props via derived setter", 648 | code: js` 649 | function Form({ firstName, lastName }) { 650 | const [formData, setFormData] = useState({ 651 | title: 'Dr.', 652 | fullName: '', 653 | }); 654 | 655 | const setFullName = (fullName) => setFormData({ ...formData, fullName }); 656 | 657 | useEffect(() => { 658 | setFormData({ 659 | ...formData, 660 | fullName: firstName + ' ' + lastName, 661 | }); 662 | }, [firstName, lastName, formData]); 663 | } 664 | `, 665 | errors: [ 666 | { 667 | messageId: messageIds.avoidDerivedState, 668 | data: { state: "formData" }, 669 | }, 670 | ], 671 | }, 672 | { 673 | name: "Derived state in larger, otherwise legit effect", 674 | code: js` 675 | function Form() { 676 | const [firstName, setFirstName] = useState('Taylor'); 677 | const [lastName, setLastName] = useState('Swift'); 678 | const [fullName, setFullName] = useState(''); 679 | 680 | useEffect(() => { 681 | console.log(name); 682 | 683 | setFullName(firstName + ' ' + lastName); 684 | }, [firstName, lastName]); 685 | } 686 | `, 687 | errors: [ 688 | { 689 | messageId: messageIds.avoidDerivedState, 690 | data: { state: "fullName" }, 691 | }, 692 | ], 693 | }, 694 | ], 695 | }); 696 | -------------------------------------------------------------------------------- /test/empty-effect.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/empty-effect", { 5 | valid: [ 6 | { 7 | name: "Valid effect", 8 | code: js` 9 | function Component() { 10 | useEffect(() => { 11 | console.log("Meow"); 12 | }, []); 13 | } 14 | `, 15 | }, 16 | ], 17 | invalid: [ 18 | { 19 | name: "Empty effect", 20 | code: js` 21 | function Component() { 22 | useEffect(() => {}, []); 23 | } 24 | `, 25 | errors: [ 26 | { 27 | messageId: messageIds.avoidEmptyEffect, 28 | }, 29 | ], 30 | }, 31 | ], 32 | }); 33 | -------------------------------------------------------------------------------- /test/initializing-state.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/initializing-state", { 5 | valid: [ 6 | { 7 | name: "To external data", 8 | code: js` 9 | function MyComponent() { 10 | const [state, setState] = useState(); 11 | 12 | useEffect(() => { 13 | fetch("https://api.example.com/data") 14 | .then(response => response.json()) 15 | .then(data => setState(data)); 16 | }, []); 17 | } 18 | `, 19 | }, 20 | ], 21 | invalid: [ 22 | { 23 | name: "To literal", 24 | code: js` 25 | function MyComponent() { 26 | const [state, setState] = useState(); 27 | 28 | useEffect(() => { 29 | setState("Hello"); 30 | }, []); 31 | 32 | return
{state}
; 33 | } 34 | `, 35 | errors: [ 36 | { 37 | messageId: messageIds.avoidInitializingState, 38 | data: { state: "state" }, 39 | }, 40 | ], 41 | }, 42 | { 43 | name: "To internal data", 44 | code: js` 45 | function MyComponent() { 46 | const [state, setState] = useState(); 47 | const [otherState, setOtherState] = useState('Meow'); 48 | 49 | useEffect(() => { 50 | setState(otherState); 51 | }, []); 52 | } 53 | `, 54 | errors: [ 55 | { 56 | messageId: messageIds.avoidInitializingState, 57 | data: { state: "state" }, 58 | }, 59 | { 60 | messageId: messageIds.avoidDerivedState, 61 | }, 62 | ], 63 | }, 64 | { 65 | name: "In an otherwise valid effect", 66 | code: js` 67 | function MyComponent() { 68 | const [state, setState] = useState(); 69 | 70 | useEffect(() => { 71 | console.log('Meow'); 72 | setState('Hello World'); 73 | }, []); 74 | } 75 | `, 76 | errors: [ 77 | { 78 | messageId: messageIds.avoidInitializingState, 79 | data: { state: "state" }, 80 | }, 81 | ], 82 | }, 83 | ], 84 | }); 85 | -------------------------------------------------------------------------------- /test/parent-child-coupling.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/parent-child-coupling", { 5 | valid: [ 6 | { 7 | name: "Prop from library HOC used internally", 8 | code: js` 9 | import { withRouter } from 'react-router-dom'; 10 | 11 | const MyComponent = withRouter(({ history }) => { 12 | const [option, setOption] = useState(); 13 | 14 | useEffect(() => { 15 | history.push('/options/' + option); 16 | }, [option]); 17 | }); 18 | `, 19 | }, 20 | { 21 | name: "Prop from library HOC used externally", 22 | code: js` 23 | import { withRouter } from 'react-router-dom'; 24 | 25 | const MyComponent = withRouter(({ history }) => { 26 | const data = useSomeAPI(); 27 | 28 | useEffect(() => { 29 | if (data.error) { 30 | history.push('/error-page'); 31 | } 32 | }, [data]); 33 | }); 34 | `, 35 | }, 36 | ], 37 | invalid: [ 38 | { 39 | // Valid wrt this flag. 40 | // Verifies `setSelection` is not considered a prop because it's initialized with a prop. 41 | name: "Using prop in state initializer", 42 | code: js` 43 | function List({ items }) { 44 | const [selection, setSelection] = useState(items[0]); 45 | 46 | useEffect(() => { 47 | setSelection(null); 48 | }, [items]); 49 | } 50 | `, 51 | errors: [ 52 | { 53 | messageId: messageIds.avoidChainingState, 54 | }, 55 | ], 56 | }, 57 | { 58 | name: "Pass internal live state", 59 | code: js` 60 | const Child = ({ onFetched }) => { 61 | const [data, setData] = useState(); 62 | 63 | useEffect(() => { 64 | onFetched(data); 65 | }, [onFetched, data]); 66 | } 67 | `, 68 | errors: [ 69 | { 70 | messageId: messageIds.avoidParentChildCoupling, 71 | }, 72 | ], 73 | }, 74 | { 75 | name: "Pass derived internal live state", 76 | code: js` 77 | const Child = ({ onFetched }) => { 78 | const [data, setData] = useState(); 79 | 80 | useEffect(() => { 81 | const firstElement = data[0]; 82 | onFetched(firstElement); 83 | }, [onFetched, data]); 84 | } 85 | `, 86 | errors: [ 87 | { 88 | messageId: messageIds.avoidParentChildCoupling, 89 | }, 90 | ], 91 | }, 92 | { 93 | name: "Pass internal live state via derived prop", 94 | code: js` 95 | const Child = ({ onFetched }) => { 96 | const [data, setData] = useState(); 97 | // No idea why someone would do this, but hey we can catch it 98 | const onFetchedWrapper = onFetched 99 | 100 | useEffect(() => { 101 | onFetchedWrapper(data); 102 | }, [onFetched, data]); 103 | } 104 | `, 105 | errors: [ 106 | { 107 | messageId: messageIds.avoidParentChildCoupling, 108 | }, 109 | ], 110 | }, 111 | { 112 | name: "No-arg prop callback in response to internal state change", 113 | code: js` 114 | function Form({ onClose }) { 115 | const [name, setName] = useState(); 116 | const [isOpen, setIsOpen] = useState(true); 117 | 118 | useEffect(() => { 119 | onClose(); 120 | }, [isOpen]); 121 | 122 | return ( 123 | 124 | ) 125 | } 126 | `, 127 | errors: [ 128 | { 129 | messageId: messageIds.avoidParentChildCoupling, 130 | }, 131 | ], 132 | }, 133 | { 134 | name: "Pass live external state", 135 | code: js` 136 | const Child = ({ onFetched }) => { 137 | const data = useSomeAPI(); 138 | 139 | useEffect(() => { 140 | onFetched(data); 141 | }, [onFetched, data]); 142 | } 143 | `, 144 | errors: [ 145 | { 146 | messageId: messageIds.avoidParentChildCoupling, 147 | }, 148 | ], 149 | }, 150 | { 151 | name: "Pass derived live external state", 152 | code: js` 153 | const Child = ({ onFetched }) => { 154 | const data = useSomeAPI(); 155 | const firstElement = data[0]; 156 | 157 | useEffect(() => { 158 | onFetched(firstElement); 159 | }, [onFetched, firstElement]); 160 | } 161 | `, 162 | errors: [ 163 | { 164 | messageId: messageIds.avoidParentChildCoupling, 165 | }, 166 | ], 167 | }, 168 | { 169 | name: "Pass final external state", 170 | code: js` 171 | function Form({ onSubmit }) { 172 | const [name, setName] = useState(); 173 | const [dataToSubmit, setDataToSubmit] = useState(); 174 | 175 | useEffect(() => { 176 | onSubmit(dataToSubmit); 177 | }, [dataToSubmit]); 178 | 179 | return ( 180 |
181 | setName(e.target.value)} 185 | /> 186 | 187 |
188 | ) 189 | } 190 | `, 191 | errors: [ 192 | { 193 | // TODO: Ideally we catch using state as an event handler, 194 | // but not sure how to differentiate that 195 | messageId: messageIds.avoidParentChildCoupling, 196 | }, 197 | ], 198 | }, 199 | { 200 | name: "Call prop in response to prop change", 201 | code: js` 202 | function Form({ isOpen, events }) { 203 | 204 | useEffect(() => { 205 | if (!isOpen) { 206 | // NOTE: Also verifies that we consider 'events' in 'events.onClose' to be a fn ref 207 | // (It's a MemberExpression under a CallExpression) 208 | events.onClose(); 209 | } 210 | }, [isOpen]); 211 | } 212 | `, 213 | errors: [ 214 | { 215 | messageId: messageIds.avoidParentChildCoupling, 216 | }, 217 | ], 218 | }, 219 | { 220 | name: "Derive state from prop function", 221 | code: js` 222 | function FilteredPosts({ posts }) { 223 | const [filteredPosts, setFilteredPosts] = useState([]); 224 | 225 | useEffect(() => { 226 | // Resulting AST node looks like: 227 | // { 228 | // "type": "ArrayPattern", 229 | // "elements": [ 230 | // null, <-- Must handle this! 231 | // { 232 | // "type": "Identifier", 233 | // "name": "second" 234 | // } 235 | // ] 236 | // } 237 | setFilteredPosts( 238 | posts.filter((post) => post.body !== "") 239 | ); 240 | }, [posts]); 241 | } 242 | `, 243 | errors: [ 244 | { 245 | messageId: messageIds.avoidDerivedState, 246 | }, 247 | { 248 | messageId: messageIds.avoidParentChildCoupling, 249 | }, 250 | ], 251 | }, 252 | ], 253 | }); 254 | -------------------------------------------------------------------------------- /test/real-world.test.js: -------------------------------------------------------------------------------- 1 | import { messageIds } from "../src/messages.js"; 2 | import { MyRuleTester, js } from "./rule-tester.js"; 3 | 4 | // Uses taken from the real world, as opposed to contrived examples 5 | new MyRuleTester().run("/real-world", { 6 | valid: [ 7 | { 8 | name: "Managing a timer", 9 | code: js` 10 | function Timer() { 11 | const [seconds, setSeconds] = useState(0); 12 | 13 | useEffect(() => { 14 | const interval = setInterval(() => { 15 | setSeconds((s) => s + 1); 16 | }, 1000); 17 | 18 | return () => { 19 | clearInterval(interval); 20 | } 21 | }, []); 22 | 23 | return
{seconds}
; 24 | } 25 | `, 26 | }, 27 | { 28 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/11 29 | name: "Debouncing", 30 | code: js` 31 | function useDebouncedState(value, delay) { 32 | const [state, setState] = useState(value); 33 | const [debouncedState, setDebouncedState] = useState(value); 34 | 35 | useEffect(() => { 36 | const timeout = setTimeout(() => { 37 | setDebouncedState(state); 38 | }, delay); 39 | 40 | return () => { 41 | clearTimeout(timeout); 42 | }; 43 | }, [delay, state]); 44 | 45 | return [state, debouncedState, setState]; 46 | } 47 | `, 48 | }, 49 | { 50 | name: "Listening for window events", 51 | code: js` 52 | function WindowSize() { 53 | const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); 54 | 55 | useEffect(() => { 56 | const handleResize = () => { 57 | setSize({ width: window.innerWidth, height: window.innerHeight }); 58 | }; 59 | 60 | window.addEventListener('resize', handleResize); 61 | 62 | return () => { 63 | window.removeEventListener('resize', handleResize); 64 | }; 65 | }, []); 66 | 67 | return
{size.width} x {size.height}
; 68 | } 69 | `, 70 | }, 71 | { 72 | name: "Play/pausing DOM video", 73 | // Could technically play/pause the video in the `onClick` handler, 74 | // but the use of an effect to sync state is arguably more readable and a valid use. 75 | code: js` 76 | function VideoPlayer() { 77 | const [isPlaying, setIsPlaying] = useState(false); 78 | const videoRef = useRef(); 79 | 80 | useEffect(() => { 81 | if (isPlaying) { 82 | videoRef.current.play(); 83 | } else { 84 | videoRef.current.pause(); 85 | } 86 | }, [isPlaying]); 87 | 88 | return
89 |
92 | } 93 | `, 94 | }, 95 | { 96 | name: "Saving to LocalStorage", 97 | code: js` 98 | function Notes() { 99 | const [notes, setNotes] = useState(() => { 100 | const savedNotes = localStorage.getItem('notes'); 101 | return savedNotes ? JSON.parse(savedNotes) : []; 102 | }); 103 | 104 | useEffect(() => { 105 | localStorage.setItem('notes', JSON.stringify(notes)); 106 | }, [notes]); 107 | 108 | return setNotes(e.target.value)} 112 | /> 113 | } 114 | `, 115 | }, 116 | { 117 | name: "Logging/Analytics", 118 | code: js` 119 | function Nav() { 120 | const [page, setPage] = useState('home'); 121 | 122 | useEffect(() => { 123 | console.log("page viewed", page); 124 | }, [page]); 125 | 126 | return ( 127 |
128 | 129 | 130 |
{page}
131 |
132 | ) 133 | } 134 | `, 135 | }, 136 | { 137 | // This might be a code smell, but people do it 138 | name: "JSON.stringifying in deps", 139 | code: js` 140 | function Feed() { 141 | const [posts, setPosts] = useState([]); 142 | const [scrollPosition, setScrollPosition] = useState(0); 143 | 144 | useEffect(() => { 145 | setScrollPosition(0); 146 | // We can't be sure JSON.stringify is pure, so we can't warn about this. 147 | // TODO: Technically we could check against known pure functions. 148 | }, [JSON.stringify(posts)]); 149 | } 150 | `, 151 | }, 152 | { 153 | // Taken from https://github.com/linhnguyen-gt/react-native-phone-number-input/blob/b5e6dc652fa8a03609efb72607dc6866f5556ca3/src/countryPickerModal/CountryPicker.tsx 154 | name: "CountryPicker", 155 | code: js` 156 | function CountryPicker({ withEmoji }) { 157 | const { translation, getCountries } = useContext(); 158 | 159 | const [state, setState] = useState({ 160 | countries: [], 161 | selectedCountry: null, 162 | }); 163 | const setCountries = (countries) => setState({ ...state, countries }); 164 | 165 | useEffect(() => { 166 | let cancel = false; 167 | getCountries(translation) 168 | .then((countries) => (cancel ? null : setCountries(countries))) 169 | .catch(console.warn); 170 | 171 | return () => { 172 | cancel = true; 173 | }; 174 | }, 175 | // Important to the test: Leads us to find useStates to check their initializers 176 | [translation, withEmoji] 177 | ); 178 | } 179 | `, 180 | }, 181 | { 182 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/7 183 | name: "Klarna", 184 | code: js` 185 | function Klarna({ klarnaAppId }) { 186 | const [countryCode] = useState(qs.parse('countryCode=meow')); 187 | const [result, setResult] = useState(); 188 | const klarnaEnabled = useSelector('idk') && shouldKlarnaBeEnabled(countryCode); 189 | const currentLocale = getCurrentLocale(useGetCurrentLanguage()); 190 | 191 | const loadSignInWithKlarna = (klarnaAppId, klarnaEnvironment, countryCode, currentLocale) => { 192 | const klarnaResult = doSomething(); 193 | setResult(klarnaResult); 194 | }; 195 | 196 | useEffect(() => { 197 | if (klarnaEnabled) { 198 | return loadSignInWithKlarna( 199 | klarnaAppId, 200 | klarnaEnvironment, 201 | countryCode?.toUpperCase(), 202 | currentLocale, 203 | ); 204 | } 205 | }, [ 206 | countryCode, 207 | klarnaAppId, 208 | klarnaEnabled, 209 | klarnaEnvironment, 210 | currentLocale, 211 | ]); 212 | } 213 | `, 214 | }, 215 | { 216 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/10 217 | name: "navigation.setOptions", 218 | code: js` 219 | import { useNavigation } from '@react-navigation/native'; 220 | import { useState, useLayoutEffect } from 'react'; 221 | 222 | function ProfileScreen({ route }) { 223 | const navigation = useNavigation(); 224 | const [value, onChangeText] = React.useState(route.params.title); 225 | 226 | React.useLayoutEffect(() => { 227 | navigation.setOptions({ 228 | title: value === '' ? 'No title' : value, 229 | }); 230 | }, [navigation, route]); 231 | } 232 | `, 233 | }, 234 | { 235 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/9#issuecomment-2913950378 236 | name: "Keyboard state listener", 237 | code: js` 238 | import { useEffect, useState } from 'react'; 239 | import keyboardReducer from './reducers'; 240 | 241 | let globalKeyboardState = { 242 | recentlyUsed: [] 243 | }; 244 | 245 | export const keyboardStateListeners = new Set(); 246 | 247 | const setKeyboardState = (action) => { 248 | globalKeyboardState = keyboardReducer(globalKeyboardState, action); 249 | keyboardStateListeners.forEach((listener) => listener(globalKeyboardState)); 250 | }; 251 | 252 | export const useKeyboardStore = () => { 253 | const [keyboardState, setState] = useState(globalKeyboardState); 254 | 255 | useEffect(() => { 256 | const listener = () => setState(globalKeyboardState); 257 | keyboardStateListeners.add(listener); 258 | return () => { 259 | keyboardStateListeners.delete(listener); 260 | }; 261 | }, [keyboardState]); 262 | 263 | return { keyboardState, setKeyboardState }; 264 | }; 265 | 266 | useKeyboardStore.setKeyboardState = setKeyboardState; 267 | `, 268 | }, 269 | ], 270 | invalid: [ 271 | { 272 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/8 273 | name: "Meow", 274 | code: js` 275 | const ExternalAssetItemRow = memo( 276 | ({ 277 | id, 278 | title, 279 | exportIdentifier, 280 | localId, 281 | hasUpdate, 282 | isViewOnly, 283 | getMenuOptions, 284 | onUpdate, 285 | onDragStart, 286 | Icon, 287 | exitMode, 288 | }) => { 289 | const [shouldUpdate, setShouldUpdate] = useState(hasUpdate); 290 | 291 | useEffect(() => { 292 | setShouldUpdate(hasUpdate); 293 | }, [hasUpdate]); 294 | 295 | const onClickUpdate = useCallback( 296 | (event) => { 297 | event.stopPropagation(); 298 | 299 | if (isViewOnly) return; 300 | 301 | setShouldUpdate(false); 302 | }, 303 | [onUpdate, exportIdentifier, title, isViewOnly], 304 | ); 305 | 306 | const handleDragStart = useCallback( 307 | (event) => { 308 | exitMode(); 309 | onDragStart(event, exportIdentifier); 310 | }, 311 | [onDragStart, exportIdentifier], 312 | ); 313 | 314 | const getMenu = useCallback( 315 | (id) => getMenuOptions(id, exportIdentifier, title, localId), 316 | [getMenuOptions, exportIdentifier, title, localId], 317 | ); 318 | 319 | return ( 320 | 326 | 327 | ) 328 | }, 329 | ); 330 | `, 331 | errors: [ 332 | { 333 | // TODO: Because the initial state is internal, derived state would be a better flag. 334 | messageId: messageIds.avoidResettingStateFromProps, 335 | }, 336 | ], 337 | }, 338 | ], 339 | }); 340 | -------------------------------------------------------------------------------- /test/resetting-state-from-props.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/resetting-state-from-props", { 5 | invalid: [ 6 | { 7 | // Valid wrt this flag 8 | name: "Set state when a prop changes, but not to its default value", 9 | code: js` 10 | function List({ items }) { 11 | const [selection, setSelection] = useState(); 12 | 13 | useEffect(() => { 14 | setSelection(items[0]); 15 | }, [items]); 16 | } 17 | `, 18 | errors: [ 19 | { 20 | messageId: messageIds.avoidDerivedState, 21 | }, 22 | ], 23 | }, 24 | { 25 | // Valid wrt this flag 26 | name: "Reset some state when a prop changes", 27 | code: js` 28 | function ProfilePage({ userId }) { 29 | const [user, setUser] = useState(null); 30 | const [comment, setComment] = useState('type something'); 31 | const [catName, setCatName] = useState('Sparky'); 32 | 33 | useEffect(() => { 34 | setUser(null); 35 | setComment('meow') 36 | }, [userId]); 37 | } 38 | `, 39 | errors: [ 40 | { 41 | messageId: messageIds.avoidChainingState, 42 | }, 43 | { 44 | messageId: messageIds.avoidChainingState, 45 | }, 46 | ], 47 | }, 48 | { 49 | name: "Reset all state when a prop changes", 50 | code: js` 51 | function ProfilePage({ userId }) { 52 | const [user, setUser] = useState(null); 53 | const [comment, setComment] = useState('type something'); 54 | 55 | useEffect(() => { 56 | setUser(null); 57 | setComment('type something'); 58 | }, [userId]); 59 | } 60 | `, 61 | errors: [ 62 | { 63 | messageId: messageIds.avoidResettingStateFromProps, 64 | data: { prop: "userId" }, 65 | }, 66 | ], 67 | }, 68 | { 69 | name: "Reset all state to shared var when a prop changes", 70 | code: js` 71 | function ProfilePage({ userId }) { 72 | const initialState = 'meow meow' 73 | const [user, setUser] = useState(null); 74 | const [comment, setComment] = useState(initialState); 75 | 76 | useEffect(() => { 77 | setUser(null); 78 | setComment(initialState); 79 | }, [userId]); 80 | } 81 | `, 82 | errors: [ 83 | { 84 | messageId: messageIds.avoidResettingStateFromProps, 85 | data: { prop: "userId" }, 86 | }, 87 | ], 88 | }, 89 | { 90 | name: "Reset all state when a prop member changes", 91 | code: js` 92 | function ProfilePage({ user }) { 93 | const [comment, setComment] = useState('type something'); 94 | 95 | useEffect(() => { 96 | setComment('type something'); 97 | }, [user.id]); 98 | } 99 | `, 100 | errors: [ 101 | { 102 | messageId: messageIds.avoidResettingStateFromProps, 103 | // TODO: Ideally would be "user.id" 104 | data: { prop: "user" }, 105 | }, 106 | ], 107 | }, 108 | { 109 | name: "Reset all state when one of two props change", 110 | code: js` 111 | function ProfilePage({ userId, friends }) { 112 | const [comment, setComment] = useState('type something'); 113 | 114 | useEffect(() => { 115 | setComment('type something'); 116 | }, [userId, friends]); 117 | } 118 | `, 119 | errors: [ 120 | { 121 | messageId: messageIds.avoidResettingStateFromProps, 122 | data: { prop: "userId" }, 123 | }, 124 | ], 125 | }, 126 | { 127 | // These are equivalent because state initializes to `undefined` when it has no argument 128 | name: "Undefined state initializer compared to state setter with literal undefined", 129 | code: js` 130 | function List({ items }) { 131 | const [selectedItem, setSelectedItem] = useState(); 132 | 133 | useEffect(() => { 134 | setSelectedItem(undefined); 135 | }, [items]); 136 | } 137 | `, 138 | errors: [ 139 | { 140 | messageId: messageIds.avoidResettingStateFromProps, 141 | }, 142 | ], 143 | }, 144 | { 145 | // Valid wrt this flag - undefined !== null 146 | name: "Undefined state initializer compared to state setter with literal null", 147 | code: js` 148 | function List({ items }) { 149 | const [selectedItem, setSelectedItem] = useState(); 150 | 151 | useEffect(() => { 152 | setSelectedItem(null); 153 | }, [items]); 154 | } 155 | `, 156 | errors: [ 157 | { 158 | messageId: messageIds.avoidChainingState, 159 | }, 160 | ], 161 | }, 162 | ], 163 | }); 164 | -------------------------------------------------------------------------------- /test/rule-tester.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { RuleTester } from "eslint"; 3 | import plugin from "../src/index.js"; 4 | 5 | // For syntax highlighting inside code under test 6 | export const js = String.raw; 7 | 8 | export class MyRuleTester extends RuleTester { 9 | constructor(options) { 10 | super({ 11 | ...options, 12 | languageOptions: plugin.configs.recommended.languageOptions, 13 | }); 14 | } 15 | 16 | run(variation, tests) { 17 | const originalStrictEqual = assert.strictEqual; 18 | try { 19 | assert.strictEqual = (actual, expected, ...rest) => { 20 | if (typeof actual === "string" && typeof expected === "string") { 21 | return originalStrictEqual( 22 | normalizeWhitespace(actual), 23 | normalizeWhitespace(expected), 24 | ...rest, 25 | ); 26 | } 27 | return originalStrictEqual(actual, expected, ...rest); 28 | }; 29 | 30 | const { valid = [], invalid = [] } = tests; 31 | 32 | [...valid, ...invalid] 33 | .filter((test) => test.todo) 34 | .forEach((test) => { 35 | it.skip(test.name); 36 | }); 37 | 38 | const filteredTests = { 39 | valid: valid.filter((test) => !test.todo), 40 | invalid: invalid.filter((test) => !test.todo), 41 | }; 42 | 43 | super.run( 44 | plugin.meta.name + variation, 45 | plugin.rules["you-might-not-need-an-effect"], 46 | filteredTests, 47 | ); 48 | } finally { 49 | // Restore the original strictEqual function to avoid unintended effects 50 | assert.strictEqual = originalStrictEqual; 51 | } 52 | } 53 | } 54 | 55 | const normalizeWhitespace = (str) => 56 | typeof str === "string" ? str.replace(/\s+/g, " ").trim() : str; 57 | -------------------------------------------------------------------------------- /test/syntax.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | // TODO: Should maybe do away with this... it helps writing but not readable 5 | const code = ({ 6 | componentDeclaration = js`const DoubleCounter = () =>`, 7 | effectBody = js`setDoubleCount(count * 2)`, 8 | effectDeps = js`[count]`, 9 | }) => js` 10 | ${componentDeclaration} { 11 | const [count, setCount] = useState(0); 12 | const [doubleCount, setDoubleCount] = useState(0); 13 | 14 | useEffect(() => ${effectBody}, ${effectDeps}); 15 | 16 | return ( 17 |
18 |

Count: {count}

19 |

Double Count: {doubleCount}

20 |
21 | ); 22 | } 23 | `; 24 | 25 | // Syntax variations that are semantically equivalent 26 | // TODO: Could dynamically generate variations: https://mochajs.org/#dynamically-generating-tests 27 | // Could be overkill; they shouldn't affect each other (supposedly, but I guess that's the point of tests!) 28 | new MyRuleTester().run("/syntax", { 29 | valid: [ 30 | { 31 | name: "Two components with overlapping names", 32 | // Not a super realistic example 33 | code: js` 34 | function ComponentOne() { 35 | const [data, setData] = useState(); 36 | } 37 | 38 | function ComponentTwo() { 39 | const setData = (data) => { 40 | console.log(data); 41 | } 42 | 43 | useEffect(() => { 44 | setData('hello'); 45 | }, []); 46 | } 47 | `, 48 | }, 49 | { 50 | // TODO: We don't follow functions passed directly to the effect right now 51 | name: "Passing non-anonymous function to effect", 52 | code: js` 53 | function Form({ onClose }) { 54 | const [name, setName] = useState(); 55 | const [isOpen, setIsOpen] = useState(true); 56 | 57 | useEffect(onClose, [isOpen]); 58 | } 59 | `, 60 | }, 61 | { 62 | name: "Variable name shadows state name", 63 | code: js` 64 | import { getCountries } from 'library'; 65 | 66 | function CountrySelect({ translation }) { 67 | const [countries, setCountries] = useState(); 68 | 69 | useEffect(() => { 70 | // Verify that the shadowing variable is not considered a state ref 71 | const countries = getCountries(translation); 72 | setCountries(countries); 73 | }, 74 | // Important to the test: Leads us to check useState initializers, 75 | // so we can verify that we don't try to find a useState for the shadowing variable 76 | [translation] 77 | ); 78 | } 79 | `, 80 | }, 81 | { 82 | name: "Reacting to external state changes with member access in deps", 83 | code: js` 84 | function Feed() { 85 | const { data } = useQuery('/posts'); 86 | const [scrollPosition, setScrollPosition] = useState(0); 87 | 88 | useEffect(() => { 89 | setScrollPosition(0); 90 | }, [data.posts]); 91 | } 92 | `, 93 | }, 94 | { 95 | name: "Destructured array skips element in arrow function params", 96 | code: js` 97 | function FilteredPosts() { 98 | const posts = useSomeAPI(); 99 | const [filteredPosts, setFilteredPosts] = useState([]); 100 | 101 | useEffect(() => { 102 | // Resulting AST node looks like: 103 | // { 104 | // "type": "ArrayPattern", 105 | // "elements": [ 106 | // null, <-- Must handle this! 107 | // { 108 | // "type": "Identifier", 109 | // "name": "second" 110 | // } 111 | // ] 112 | // } 113 | setFilteredPosts( 114 | posts.filter(([, value]) => value !== "") 115 | ); 116 | }, [posts]); 117 | } 118 | `, 119 | }, 120 | { 121 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/16 122 | name: "External IIFE", 123 | code: js` 124 | import { useEffect, useState } from 'react'; 125 | 126 | export const App = () => { 127 | const [response, setResponse] = useState(null); 128 | 129 | const fetchYesNoApi = () => { 130 | return (async () => { 131 | try { 132 | const response = await fetch('https://yesno.wtf/api'); 133 | if (!response.ok) { 134 | throw new Error('Network error'); 135 | } 136 | const data = await response.json(); 137 | setResponse(data); 138 | } catch (err) { 139 | console.error(err); 140 | } 141 | })(); 142 | }; 143 | 144 | useEffect(() => { 145 | (async () => { 146 | await fetchYesNoApi(); 147 | })(); 148 | }, []); 149 | 150 | return ( 151 |
{response}
152 | ); 153 | }; 154 | `, 155 | }, 156 | { 157 | name: "Internal IIFE inside callback", 158 | code: js` 159 | import { useEffect, useState } from 'react'; 160 | 161 | export const MyComponent = () => { 162 | const [state, setState] = useState(); 163 | 164 | useEffect(() => { 165 | window.addEventListener('load', () => { 166 | (async () => { 167 | setState('Loaded'); 168 | })(); 169 | }); 170 | }, []); 171 | }; 172 | `, 173 | }, 174 | { 175 | name: "Internal callback inside IIFE", 176 | code: js` 177 | import { useEffect, useState } from 'react'; 178 | 179 | export const MyComponent = () => { 180 | const [state, setState] = useState(); 181 | 182 | useEffect(() => { 183 | (async () => { 184 | window.addEventListener('load', () => { 185 | setState('Loaded'); 186 | }); 187 | })(); 188 | }, []); 189 | }; 190 | `, 191 | }, 192 | ], 193 | invalid: [ 194 | { 195 | name: "Function component", 196 | code: code({ 197 | componentDeclaration: js`function DoubleCounter()`, 198 | }), 199 | errors: 1, 200 | }, 201 | { 202 | name: "Arrow function component", 203 | code: code({ 204 | componentDeclaration: js`const DoubleCounter = () =>`, 205 | }), 206 | errors: 1, 207 | }, 208 | { 209 | name: "Memoized component, with props", 210 | code: js` 211 | const DoubleCounter = memo(({ count }) => { 212 | const [doubleCount, setDoubleCount] = useState(0); 213 | 214 | useEffect(() => setDoubleCount(count), [count]); 215 | }); 216 | `, 217 | errors: [ 218 | { 219 | messageId: messageIds.avoidDerivedState, 220 | data: { state: "doubleCount" }, 221 | }, 222 | ], 223 | }, 224 | { 225 | name: "Effect one-liner body", 226 | code: code({ 227 | componentDeclaration: js`const AvoidDuplicateTest = () =>`, 228 | effectBody: js`setDoubleCount(count * 2)`, 229 | }), 230 | errors: 1, 231 | }, 232 | { 233 | name: "Effect single-statement body", 234 | code: code({ 235 | effectBody: js`{ setDoubleCount(count * 2); }`, 236 | }), 237 | errors: 1, 238 | }, 239 | { 240 | name: "Effect multi-statement body", 241 | code: code({ 242 | effectBody: js`{ setDoubleCount(count * 2); setDoubleCount(count * 2); }`, 243 | }), 244 | errors: 2, 245 | }, 246 | { 247 | name: "React.useEffect", 248 | code: js` 249 | function DoubleCounter() { 250 | const [count, setCount] = useState(0); 251 | const [doubleCount, setDoubleCount] = useState(0); 252 | 253 | React.useEffect(() => { 254 | setDoubleCount(count * 2); 255 | }, [count]); 256 | } 257 | `, 258 | errors: 1, 259 | }, 260 | { 261 | name: "useLayoutEffect", 262 | code: js` 263 | function DoubleCounter() { 264 | const [count, setCount] = useState(0); 265 | const [doubleCount, setDoubleCount] = useState(0); 266 | 267 | useLayoutEffect(() => { 268 | setDoubleCount(count * 2); 269 | }, [count]); 270 | } 271 | `, 272 | errors: 1, 273 | }, 274 | { 275 | name: "Non-destructured props", 276 | code: code({ 277 | componentDeclaration: js`function DoubleCounter(props)`, 278 | effectBody: js`setDoubleCount(props.count * 2)`, 279 | effectDeps: js`[props.count]`, 280 | }), 281 | errors: 1, 282 | }, 283 | { 284 | name: "Destructured props", 285 | code: code({ 286 | componentDeclaration: js`function DoubleCounter({ propCount })`, 287 | effectBody: js`setDoubleCount(propCount * 2)`, 288 | effectDeps: js`[propCount]`, 289 | }), 290 | errors: 1, 291 | }, 292 | { 293 | name: "Renamed destructured props", 294 | code: code({ 295 | componentDeclaration: js`function DoubleCounter({ count: countProp })`, 296 | effectBody: js`setDoubleCount(countProp * 2)`, 297 | effectDeps: js`[countProp]`, 298 | }), 299 | errors: 1, 300 | }, 301 | { 302 | name: "Doubly deep MemberExpression in effect", 303 | code: code({ 304 | componentDeclaration: js`function DoubleCounter(props)`, 305 | effectBody: js`setDoubleCount(props.nested.count * 2)`, 306 | effectDeps: js`[props.nested.count]`, 307 | }), 308 | errors: 1, 309 | }, 310 | { 311 | name: "Objects stored in state", 312 | code: js` 313 | function DoubleCounter() { 314 | const [count, setCount] = useState({ value: 0 }); 315 | const [doubleCount, setDoubleCount] = useState({ value: 0 }); 316 | 317 | useEffect(() => { 318 | setDoubleCount({ value: count.value * 2 }); 319 | }, [count]); 320 | } 321 | `, 322 | errors: 1, 323 | }, 324 | { 325 | name: "Optional chaining and nullish coalescing", 326 | code: js` 327 | function DoubleCounter({ count }) { 328 | const [doubleCount, setDoubleCount] = useState(0); 329 | 330 | useEffect(() => { 331 | setDoubleCount((count?.value ?? 1) * 2); 332 | }, [count?.value]); 333 | } 334 | `, 335 | errors: 1, 336 | }, 337 | { 338 | // `exhaustive-deps` doesn't enforce member access in the deps 339 | name: "Member access in effect body but not in deps", 340 | code: code({ 341 | componentDeclaration: js`function DoubleCounter(props)`, 342 | effectBody: js`setDoubleCount(props.count * 2)`, 343 | effectDeps: js`[props]`, 344 | }), 345 | errors: 1, 346 | }, 347 | { 348 | name: "Doubly nested scopes in effect body", 349 | code: code({ 350 | effectBody: js` 351 | { 352 | if (count > 10) { 353 | if (count > 100) { 354 | setDoubleCount(count * 4); 355 | } else { 356 | setDoubleCount(count * 2); 357 | } 358 | } else { 359 | setDoubleCount(count); 360 | } 361 | } 362 | `, 363 | }), 364 | errors: 3, 365 | }, 366 | { 367 | name: "Destructured array skips element in variable declaration", 368 | code: js` 369 | function SecondPost({ posts }) { 370 | const [secondPost, setSecondPost] = useState(); 371 | 372 | useEffect(() => { 373 | const [, second] = posts; 374 | setSecondPost(second); 375 | }, [posts]); 376 | } 377 | `, 378 | errors: 1, 379 | }, 380 | { 381 | name: "Value-less useState", 382 | code: js` 383 | import { useState } from 'react'; 384 | 385 | function AttemptCounter() { 386 | const [, setAttempts] = useState(0); 387 | const [count, setCount] = useState(0); 388 | 389 | useEffect(() => { 390 | setAttempts((prev) => { 391 | return prev + count; 392 | }); 393 | }, [count]); 394 | } 395 | `, 396 | errors: [ 397 | { 398 | messageId: messageIds.avoidDerivedState, 399 | data: { state: "setAttempts" }, 400 | }, 401 | ], 402 | }, 403 | { 404 | name: "Setter-less useState", 405 | code: js` 406 | function AttemptCounter() { 407 | const [attempts, setAttempts] = useState(0); 408 | const [count] = useState(0); 409 | 410 | useEffect(() => { 411 | setAttempts(count); 412 | }, [count]); 413 | } 414 | `, 415 | errors: [ 416 | { 417 | messageId: messageIds.avoidDerivedState, 418 | data: { state: "attempts" }, 419 | }, 420 | ], 421 | }, 422 | { 423 | name: "Custom hook with state", 424 | code: js` 425 | function useCustomHook() { 426 | const [count, setCount] = useState(0); 427 | const [doubleCount, setDoubleCount] = useState(0); 428 | 429 | useEffect(() => { 430 | setDoubleCount(count * 2); 431 | }, [count]); 432 | 433 | return state; 434 | } 435 | 436 | function Component() { 437 | const customState = useCustomHook(); 438 | } 439 | `, 440 | errors: [ 441 | { 442 | messageId: messageIds.avoidDerivedState, 443 | data: { state: "doubleCount" }, 444 | }, 445 | ], 446 | }, 447 | { 448 | name: "FunctionDeclaration custom hook with props", 449 | code: js` 450 | function useCustomHook(prop) { 451 | const [state, setState] = useState(0); 452 | 453 | useEffect(() => { 454 | setState(prop); 455 | }, [prop]); 456 | 457 | return state; 458 | } 459 | `, 460 | errors: [ 461 | { 462 | messageId: messageIds.avoidDerivedState, 463 | data: { state: "state" }, 464 | }, 465 | ], 466 | }, 467 | { 468 | name: "VariableDeclarator custom hook with object props", 469 | code: js` 470 | const useCustomHook = ({ prop }) => { 471 | const [state, setState] = useState(0); 472 | 473 | useEffect(() => { 474 | setState(prop); 475 | }, [prop]); 476 | 477 | return state; 478 | } 479 | `, 480 | errors: [ 481 | { 482 | messageId: messageIds.avoidDerivedState, 483 | data: { state: "state" }, 484 | }, 485 | ], 486 | }, 487 | { 488 | // Verifies that we don't check for upstream state and props in isolation 489 | name: "Derive from both state and props", 490 | code: js` 491 | function Component({ prop }) { 492 | const [state, setState] = useState(0); 493 | const [derived, setDerived] = useState(0); 494 | const combined = state + prop; 495 | 496 | useEffect(() => { 497 | setDerived(combined); 498 | }, [combined]); 499 | } 500 | `, 501 | errors: [ 502 | { 503 | messageId: messageIds.avoidDerivedState, 504 | data: { state: "derived" }, 505 | }, 506 | ], 507 | }, 508 | { 509 | // Effects shouldn't be called conditionally, but good to be prepared 510 | name: "Conditional useEffect", 511 | code: js` 512 | function ConditionalEffect({ key }) { 513 | const [state, setState] = useState(0); 514 | 515 | if (condition) { 516 | useEffect(() => { 517 | setState(0); 518 | }, [key]); 519 | } 520 | } 521 | `, 522 | errors: [ 523 | { 524 | messageId: messageIds.avoidResettingStateFromProps, 525 | data: { prop: "key" }, 526 | }, 527 | ], 528 | }, 529 | { 530 | // https://github.com/NickvanDyke/eslint-plugin-react-you-might-not-need-an-effect/issues/16 531 | name: "Internal IIFE", 532 | code: js` 533 | import { useEffect, useState } from 'react'; 534 | 535 | export const App = () => { 536 | const [data, setData] = useState(null); 537 | 538 | const iife = () => { 539 | return (async () => { 540 | setData('Meow'); 541 | })(); 542 | }; 543 | 544 | useEffect(() => { 545 | (async () => { 546 | await iife(); 547 | })(); 548 | }, []); 549 | }; 550 | `, 551 | errors: [ 552 | { 553 | messageId: messageIds.avoidInitializingState, 554 | }, 555 | ], 556 | }, 557 | ], 558 | }); 559 | -------------------------------------------------------------------------------- /test/using-state-as-event-handler.test.js: -------------------------------------------------------------------------------- 1 | import { MyRuleTester, js } from "./rule-tester.js"; 2 | import { messageIds } from "../src/messages.js"; 3 | 4 | new MyRuleTester().run("/using-state-as-event-handler", { 5 | invalid: [ 6 | { 7 | // TODO: How to detect this though? Not sure it's discernable from legit synchronization effects. 8 | // Maybe when the setter is only called in this one place? Meaning we could instead inline the effect. 9 | name: "Using state to handle an event", 10 | todo: true, 11 | code: js` 12 | function Form() { 13 | const [name, setName] = useState(); 14 | const [dataToSubmit, setDataToSubmit] = useState(); 15 | 16 | useEffect(() => { 17 | submitData(dataToSubmit); 18 | }, [dataToSubmit]); 19 | 20 | return ( 21 |
22 | setName(e.target.value)} 26 | /> 27 | 28 |
29 | ) 30 | } 31 | `, 32 | errors: [ 33 | { 34 | messageId: messageIds.avoidEventHandler, 35 | }, 36 | ], 37 | }, 38 | ], 39 | }); 40 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { ESLint, Linter } from "eslint"; 2 | 3 | declare const plugin: ESLint.Plugin & { 4 | configs: { 5 | recommended: Linter.Config; 6 | "legacy-recommended": Linter.LegacyConfig; 7 | }; 8 | }; 9 | export default plugin; 10 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 8 6 | cacheKey: 10c0 7 | 8 | "@esbuild/aix-ppc64@npm:0.25.3": 9 | version: 0.25.3 10 | resolution: "@esbuild/aix-ppc64@npm:0.25.3" 11 | conditions: os=aix & cpu=ppc64 12 | languageName: node 13 | linkType: hard 14 | 15 | "@esbuild/android-arm64@npm:0.25.3": 16 | version: 0.25.3 17 | resolution: "@esbuild/android-arm64@npm:0.25.3" 18 | conditions: os=android & cpu=arm64 19 | languageName: node 20 | linkType: hard 21 | 22 | "@esbuild/android-arm@npm:0.25.3": 23 | version: 0.25.3 24 | resolution: "@esbuild/android-arm@npm:0.25.3" 25 | conditions: os=android & cpu=arm 26 | languageName: node 27 | linkType: hard 28 | 29 | "@esbuild/android-x64@npm:0.25.3": 30 | version: 0.25.3 31 | resolution: "@esbuild/android-x64@npm:0.25.3" 32 | conditions: os=android & cpu=x64 33 | languageName: node 34 | linkType: hard 35 | 36 | "@esbuild/darwin-arm64@npm:0.25.3": 37 | version: 0.25.3 38 | resolution: "@esbuild/darwin-arm64@npm:0.25.3" 39 | conditions: os=darwin & cpu=arm64 40 | languageName: node 41 | linkType: hard 42 | 43 | "@esbuild/darwin-x64@npm:0.25.3": 44 | version: 0.25.3 45 | resolution: "@esbuild/darwin-x64@npm:0.25.3" 46 | conditions: os=darwin & cpu=x64 47 | languageName: node 48 | linkType: hard 49 | 50 | "@esbuild/freebsd-arm64@npm:0.25.3": 51 | version: 0.25.3 52 | resolution: "@esbuild/freebsd-arm64@npm:0.25.3" 53 | conditions: os=freebsd & cpu=arm64 54 | languageName: node 55 | linkType: hard 56 | 57 | "@esbuild/freebsd-x64@npm:0.25.3": 58 | version: 0.25.3 59 | resolution: "@esbuild/freebsd-x64@npm:0.25.3" 60 | conditions: os=freebsd & cpu=x64 61 | languageName: node 62 | linkType: hard 63 | 64 | "@esbuild/linux-arm64@npm:0.25.3": 65 | version: 0.25.3 66 | resolution: "@esbuild/linux-arm64@npm:0.25.3" 67 | conditions: os=linux & cpu=arm64 68 | languageName: node 69 | linkType: hard 70 | 71 | "@esbuild/linux-arm@npm:0.25.3": 72 | version: 0.25.3 73 | resolution: "@esbuild/linux-arm@npm:0.25.3" 74 | conditions: os=linux & cpu=arm 75 | languageName: node 76 | linkType: hard 77 | 78 | "@esbuild/linux-ia32@npm:0.25.3": 79 | version: 0.25.3 80 | resolution: "@esbuild/linux-ia32@npm:0.25.3" 81 | conditions: os=linux & cpu=ia32 82 | languageName: node 83 | linkType: hard 84 | 85 | "@esbuild/linux-loong64@npm:0.25.3": 86 | version: 0.25.3 87 | resolution: "@esbuild/linux-loong64@npm:0.25.3" 88 | conditions: os=linux & cpu=loong64 89 | languageName: node 90 | linkType: hard 91 | 92 | "@esbuild/linux-mips64el@npm:0.25.3": 93 | version: 0.25.3 94 | resolution: "@esbuild/linux-mips64el@npm:0.25.3" 95 | conditions: os=linux & cpu=mips64el 96 | languageName: node 97 | linkType: hard 98 | 99 | "@esbuild/linux-ppc64@npm:0.25.3": 100 | version: 0.25.3 101 | resolution: "@esbuild/linux-ppc64@npm:0.25.3" 102 | conditions: os=linux & cpu=ppc64 103 | languageName: node 104 | linkType: hard 105 | 106 | "@esbuild/linux-riscv64@npm:0.25.3": 107 | version: 0.25.3 108 | resolution: "@esbuild/linux-riscv64@npm:0.25.3" 109 | conditions: os=linux & cpu=riscv64 110 | languageName: node 111 | linkType: hard 112 | 113 | "@esbuild/linux-s390x@npm:0.25.3": 114 | version: 0.25.3 115 | resolution: "@esbuild/linux-s390x@npm:0.25.3" 116 | conditions: os=linux & cpu=s390x 117 | languageName: node 118 | linkType: hard 119 | 120 | "@esbuild/linux-x64@npm:0.25.3": 121 | version: 0.25.3 122 | resolution: "@esbuild/linux-x64@npm:0.25.3" 123 | conditions: os=linux & cpu=x64 124 | languageName: node 125 | linkType: hard 126 | 127 | "@esbuild/netbsd-arm64@npm:0.25.3": 128 | version: 0.25.3 129 | resolution: "@esbuild/netbsd-arm64@npm:0.25.3" 130 | conditions: os=netbsd & cpu=arm64 131 | languageName: node 132 | linkType: hard 133 | 134 | "@esbuild/netbsd-x64@npm:0.25.3": 135 | version: 0.25.3 136 | resolution: "@esbuild/netbsd-x64@npm:0.25.3" 137 | conditions: os=netbsd & cpu=x64 138 | languageName: node 139 | linkType: hard 140 | 141 | "@esbuild/openbsd-arm64@npm:0.25.3": 142 | version: 0.25.3 143 | resolution: "@esbuild/openbsd-arm64@npm:0.25.3" 144 | conditions: os=openbsd & cpu=arm64 145 | languageName: node 146 | linkType: hard 147 | 148 | "@esbuild/openbsd-x64@npm:0.25.3": 149 | version: 0.25.3 150 | resolution: "@esbuild/openbsd-x64@npm:0.25.3" 151 | conditions: os=openbsd & cpu=x64 152 | languageName: node 153 | linkType: hard 154 | 155 | "@esbuild/sunos-x64@npm:0.25.3": 156 | version: 0.25.3 157 | resolution: "@esbuild/sunos-x64@npm:0.25.3" 158 | conditions: os=sunos & cpu=x64 159 | languageName: node 160 | linkType: hard 161 | 162 | "@esbuild/win32-arm64@npm:0.25.3": 163 | version: 0.25.3 164 | resolution: "@esbuild/win32-arm64@npm:0.25.3" 165 | conditions: os=win32 & cpu=arm64 166 | languageName: node 167 | linkType: hard 168 | 169 | "@esbuild/win32-ia32@npm:0.25.3": 170 | version: 0.25.3 171 | resolution: "@esbuild/win32-ia32@npm:0.25.3" 172 | conditions: os=win32 & cpu=ia32 173 | languageName: node 174 | linkType: hard 175 | 176 | "@esbuild/win32-x64@npm:0.25.3": 177 | version: 0.25.3 178 | resolution: "@esbuild/win32-x64@npm:0.25.3" 179 | conditions: os=win32 & cpu=x64 180 | languageName: node 181 | linkType: hard 182 | 183 | "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.5.0": 184 | version: 4.7.0 185 | resolution: "@eslint-community/eslint-utils@npm:4.7.0" 186 | dependencies: 187 | eslint-visitor-keys: "npm:^3.4.3" 188 | peerDependencies: 189 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 190 | checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf 191 | languageName: node 192 | linkType: hard 193 | 194 | "@eslint-community/eslint-utils@npm:^4.2.0": 195 | version: 4.4.1 196 | resolution: "@eslint-community/eslint-utils@npm:4.4.1" 197 | dependencies: 198 | eslint-visitor-keys: "npm:^3.4.3" 199 | peerDependencies: 200 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 201 | checksum: 10c0/2aa0ac2fc50ff3f234408b10900ed4f1a0b19352f21346ad4cc3d83a1271481bdda11097baa45d484dd564c895e0762a27a8240be7a256b3ad47129e96528252 202 | languageName: node 203 | linkType: hard 204 | 205 | "@eslint-community/eslint-utils@npm:^4.4.0": 206 | version: 4.6.1 207 | resolution: "@eslint-community/eslint-utils@npm:4.6.1" 208 | dependencies: 209 | eslint-visitor-keys: "npm:^3.4.3" 210 | peerDependencies: 211 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 212 | checksum: 10c0/cdeb6f8fc33a83726357d7f736075cdbd6e79dc7ac4b00b15680f1111d0f33bda583e7fafa5937245a058cc66302dc47568bba57b251302dc74964d8e87f56d7 213 | languageName: node 214 | linkType: hard 215 | 216 | "@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.1": 217 | version: 4.12.1 218 | resolution: "@eslint-community/regexpp@npm:4.12.1" 219 | checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 220 | languageName: node 221 | linkType: hard 222 | 223 | "@eslint/config-array@npm:^0.19.0": 224 | version: 0.19.2 225 | resolution: "@eslint/config-array@npm:0.19.2" 226 | dependencies: 227 | "@eslint/object-schema": "npm:^2.1.6" 228 | debug: "npm:^4.3.1" 229 | minimatch: "npm:^3.1.2" 230 | checksum: 10c0/dd68da9abb32d336233ac4fe0db1e15a0a8d794b6e69abb9e57545d746a97f6f542496ff9db0d7e27fab1438546250d810d90b1904ac67677215b8d8e7573f3d 231 | languageName: node 232 | linkType: hard 233 | 234 | "@eslint/core@npm:^0.10.0": 235 | version: 0.10.0 236 | resolution: "@eslint/core@npm:0.10.0" 237 | dependencies: 238 | "@types/json-schema": "npm:^7.0.15" 239 | checksum: 10c0/074018075079b3ed1f14fab9d116f11a8824cdfae3e822badf7ad546962fafe717a31e61459bad8cc59cf7070dc413ea9064ddb75c114f05b05921029cde0a64 240 | languageName: node 241 | linkType: hard 242 | 243 | "@eslint/core@npm:^0.11.0": 244 | version: 0.11.0 245 | resolution: "@eslint/core@npm:0.11.0" 246 | dependencies: 247 | "@types/json-schema": "npm:^7.0.15" 248 | checksum: 10c0/1e0671d035c908175f445864a7864cf6c6a8b67a5dfba8c47b2ac91e2d3ed36e8c1f2fd81d98a73264f8677055559699d4adb0f97d86588e616fc0dc9a4b86c9 249 | languageName: node 250 | linkType: hard 251 | 252 | "@eslint/eslintrc@npm:^3.2.0": 253 | version: 3.2.0 254 | resolution: "@eslint/eslintrc@npm:3.2.0" 255 | dependencies: 256 | ajv: "npm:^6.12.4" 257 | debug: "npm:^4.3.2" 258 | espree: "npm:^10.0.1" 259 | globals: "npm:^14.0.0" 260 | ignore: "npm:^5.2.0" 261 | import-fresh: "npm:^3.2.1" 262 | js-yaml: "npm:^4.1.0" 263 | minimatch: "npm:^3.1.2" 264 | strip-json-comments: "npm:^3.1.1" 265 | checksum: 10c0/43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b 266 | languageName: node 267 | linkType: hard 268 | 269 | "@eslint/js@npm:9.20.0": 270 | version: 9.20.0 271 | resolution: "@eslint/js@npm:9.20.0" 272 | checksum: 10c0/10e7b5b9e628b5192e8fc6b0ecd27cf48322947e83e999ff60f9f9e44ac8d499138bcb9383cbfa6e51e780d53b4e76ccc2d1753b108b7173b8404fd484d37328 273 | languageName: node 274 | linkType: hard 275 | 276 | "@eslint/js@npm:^9.28.0": 277 | version: 9.28.0 278 | resolution: "@eslint/js@npm:9.28.0" 279 | checksum: 10c0/5a6759542490dd9f778993edfbc8d2f55168fd0f7336ceed20fe3870c65499d72fc0bca8d1ae00ea246b0923ea4cba2e0758a8a5507a3506ddcf41c92282abb8 280 | languageName: node 281 | linkType: hard 282 | 283 | "@eslint/object-schema@npm:^2.1.6": 284 | version: 2.1.6 285 | resolution: "@eslint/object-schema@npm:2.1.6" 286 | checksum: 10c0/b8cdb7edea5bc5f6a96173f8d768d3554a628327af536da2fc6967a93b040f2557114d98dbcdbf389d5a7b290985ad6a9ce5babc547f36fc1fde42e674d11a56 287 | languageName: node 288 | linkType: hard 289 | 290 | "@eslint/plugin-kit@npm:^0.2.5": 291 | version: 0.2.5 292 | resolution: "@eslint/plugin-kit@npm:0.2.5" 293 | dependencies: 294 | "@eslint/core": "npm:^0.10.0" 295 | levn: "npm:^0.4.1" 296 | checksum: 10c0/ba9832b8409af618cf61791805fe201dd62f3c82c783adfcec0f5cd391e68b40beaecb47b9a3209e926dbcab65135f410cae405b69a559197795793399f61176 297 | languageName: node 298 | linkType: hard 299 | 300 | "@humanfs/core@npm:^0.19.1": 301 | version: 0.19.1 302 | resolution: "@humanfs/core@npm:0.19.1" 303 | checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 304 | languageName: node 305 | linkType: hard 306 | 307 | "@humanfs/node@npm:^0.16.6": 308 | version: 0.16.6 309 | resolution: "@humanfs/node@npm:0.16.6" 310 | dependencies: 311 | "@humanfs/core": "npm:^0.19.1" 312 | "@humanwhocodes/retry": "npm:^0.3.0" 313 | checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1 314 | languageName: node 315 | linkType: hard 316 | 317 | "@humanwhocodes/module-importer@npm:^1.0.1": 318 | version: 1.0.1 319 | resolution: "@humanwhocodes/module-importer@npm:1.0.1" 320 | checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 321 | languageName: node 322 | linkType: hard 323 | 324 | "@humanwhocodes/retry@npm:^0.3.0": 325 | version: 0.3.1 326 | resolution: "@humanwhocodes/retry@npm:0.3.1" 327 | checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b 328 | languageName: node 329 | linkType: hard 330 | 331 | "@humanwhocodes/retry@npm:^0.4.1": 332 | version: 0.4.1 333 | resolution: "@humanwhocodes/retry@npm:0.4.1" 334 | checksum: 10c0/be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b 335 | languageName: node 336 | linkType: hard 337 | 338 | "@isaacs/cliui@npm:^8.0.2": 339 | version: 8.0.2 340 | resolution: "@isaacs/cliui@npm:8.0.2" 341 | dependencies: 342 | string-width: "npm:^5.1.2" 343 | string-width-cjs: "npm:string-width@^4.2.0" 344 | strip-ansi: "npm:^7.0.1" 345 | strip-ansi-cjs: "npm:strip-ansi@^6.0.1" 346 | wrap-ansi: "npm:^8.1.0" 347 | wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" 348 | checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e 349 | languageName: node 350 | linkType: hard 351 | 352 | "@isaacs/fs-minipass@npm:^4.0.0": 353 | version: 4.0.1 354 | resolution: "@isaacs/fs-minipass@npm:4.0.1" 355 | dependencies: 356 | minipass: "npm:^7.0.4" 357 | checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 358 | languageName: node 359 | linkType: hard 360 | 361 | "@npmcli/agent@npm:^3.0.0": 362 | version: 3.0.0 363 | resolution: "@npmcli/agent@npm:3.0.0" 364 | dependencies: 365 | agent-base: "npm:^7.1.0" 366 | http-proxy-agent: "npm:^7.0.0" 367 | https-proxy-agent: "npm:^7.0.1" 368 | lru-cache: "npm:^10.0.1" 369 | socks-proxy-agent: "npm:^8.0.3" 370 | checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 371 | languageName: node 372 | linkType: hard 373 | 374 | "@npmcli/fs@npm:^4.0.0": 375 | version: 4.0.0 376 | resolution: "@npmcli/fs@npm:4.0.0" 377 | dependencies: 378 | semver: "npm:^7.3.5" 379 | checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 380 | languageName: node 381 | linkType: hard 382 | 383 | "@pkgjs/parseargs@npm:^0.11.0": 384 | version: 0.11.0 385 | resolution: "@pkgjs/parseargs@npm:0.11.0" 386 | checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd 387 | languageName: node 388 | linkType: hard 389 | 390 | "@types/estree@npm:^1.0.6": 391 | version: 1.0.6 392 | resolution: "@types/estree@npm:1.0.6" 393 | checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a 394 | languageName: node 395 | linkType: hard 396 | 397 | "@types/json-schema@npm:^7.0.15": 398 | version: 7.0.15 399 | resolution: "@types/json-schema@npm:7.0.15" 400 | checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db 401 | languageName: node 402 | linkType: hard 403 | 404 | "abbrev@npm:^3.0.0": 405 | version: 3.0.1 406 | resolution: "abbrev@npm:3.0.1" 407 | checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf 408 | languageName: node 409 | linkType: hard 410 | 411 | "acorn-jsx@npm:^5.3.2": 412 | version: 5.3.2 413 | resolution: "acorn-jsx@npm:5.3.2" 414 | peerDependencies: 415 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 416 | checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 417 | languageName: node 418 | linkType: hard 419 | 420 | "acorn@npm:^8.14.0": 421 | version: 8.14.0 422 | resolution: "acorn@npm:8.14.0" 423 | bin: 424 | acorn: bin/acorn 425 | checksum: 10c0/6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7 426 | languageName: node 427 | linkType: hard 428 | 429 | "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": 430 | version: 7.1.3 431 | resolution: "agent-base@npm:7.1.3" 432 | checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 433 | languageName: node 434 | linkType: hard 435 | 436 | "ajv@npm:^6.12.4": 437 | version: 6.12.6 438 | resolution: "ajv@npm:6.12.6" 439 | dependencies: 440 | fast-deep-equal: "npm:^3.1.1" 441 | fast-json-stable-stringify: "npm:^2.0.0" 442 | json-schema-traverse: "npm:^0.4.1" 443 | uri-js: "npm:^4.2.2" 444 | checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 445 | languageName: node 446 | linkType: hard 447 | 448 | "ansi-colors@npm:^4.1.3": 449 | version: 4.1.3 450 | resolution: "ansi-colors@npm:4.1.3" 451 | checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 452 | languageName: node 453 | linkType: hard 454 | 455 | "ansi-escapes@npm:^7.0.0": 456 | version: 7.0.0 457 | resolution: "ansi-escapes@npm:7.0.0" 458 | dependencies: 459 | environment: "npm:^1.0.0" 460 | checksum: 10c0/86e51e36fabef18c9c004af0a280573e828900641cea35134a124d2715e0c5a473494ab4ce396614505da77638ae290ff72dd8002d9747d2ee53f5d6bbe336be 461 | languageName: node 462 | linkType: hard 463 | 464 | "ansi-regex@npm:^5.0.1": 465 | version: 5.0.1 466 | resolution: "ansi-regex@npm:5.0.1" 467 | checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 468 | languageName: node 469 | linkType: hard 470 | 471 | "ansi-regex@npm:^6.0.1": 472 | version: 6.1.0 473 | resolution: "ansi-regex@npm:6.1.0" 474 | checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc 475 | languageName: node 476 | linkType: hard 477 | 478 | "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": 479 | version: 4.3.0 480 | resolution: "ansi-styles@npm:4.3.0" 481 | dependencies: 482 | color-convert: "npm:^2.0.1" 483 | checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 484 | languageName: node 485 | linkType: hard 486 | 487 | "ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": 488 | version: 6.2.1 489 | resolution: "ansi-styles@npm:6.2.1" 490 | checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c 491 | languageName: node 492 | linkType: hard 493 | 494 | "anymatch@npm:~3.1.2": 495 | version: 3.1.3 496 | resolution: "anymatch@npm:3.1.3" 497 | dependencies: 498 | normalize-path: "npm:^3.0.0" 499 | picomatch: "npm:^2.0.4" 500 | checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac 501 | languageName: node 502 | linkType: hard 503 | 504 | "argparse@npm:^2.0.1": 505 | version: 2.0.1 506 | resolution: "argparse@npm:2.0.1" 507 | checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e 508 | languageName: node 509 | linkType: hard 510 | 511 | "balanced-match@npm:^1.0.0": 512 | version: 1.0.2 513 | resolution: "balanced-match@npm:1.0.2" 514 | checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee 515 | languageName: node 516 | linkType: hard 517 | 518 | "binary-extensions@npm:^2.0.0": 519 | version: 2.3.0 520 | resolution: "binary-extensions@npm:2.3.0" 521 | checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 522 | languageName: node 523 | linkType: hard 524 | 525 | "brace-expansion@npm:^1.1.7": 526 | version: 1.1.11 527 | resolution: "brace-expansion@npm:1.1.11" 528 | dependencies: 529 | balanced-match: "npm:^1.0.0" 530 | concat-map: "npm:0.0.1" 531 | checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 532 | languageName: node 533 | linkType: hard 534 | 535 | "brace-expansion@npm:^2.0.1": 536 | version: 2.0.1 537 | resolution: "brace-expansion@npm:2.0.1" 538 | dependencies: 539 | balanced-match: "npm:^1.0.0" 540 | checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f 541 | languageName: node 542 | linkType: hard 543 | 544 | "braces@npm:^3.0.3, braces@npm:~3.0.2": 545 | version: 3.0.3 546 | resolution: "braces@npm:3.0.3" 547 | dependencies: 548 | fill-range: "npm:^7.1.1" 549 | checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 550 | languageName: node 551 | linkType: hard 552 | 553 | "browser-stdout@npm:^1.3.1": 554 | version: 1.3.1 555 | resolution: "browser-stdout@npm:1.3.1" 556 | checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 557 | languageName: node 558 | linkType: hard 559 | 560 | "cacache@npm:^19.0.1": 561 | version: 19.0.1 562 | resolution: "cacache@npm:19.0.1" 563 | dependencies: 564 | "@npmcli/fs": "npm:^4.0.0" 565 | fs-minipass: "npm:^3.0.0" 566 | glob: "npm:^10.2.2" 567 | lru-cache: "npm:^10.0.1" 568 | minipass: "npm:^7.0.3" 569 | minipass-collect: "npm:^2.0.1" 570 | minipass-flush: "npm:^1.0.5" 571 | minipass-pipeline: "npm:^1.2.4" 572 | p-map: "npm:^7.0.2" 573 | ssri: "npm:^12.0.0" 574 | tar: "npm:^7.4.3" 575 | unique-filename: "npm:^4.0.0" 576 | checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c 577 | languageName: node 578 | linkType: hard 579 | 580 | "callsites@npm:^3.0.0": 581 | version: 3.1.0 582 | resolution: "callsites@npm:3.1.0" 583 | checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 584 | languageName: node 585 | linkType: hard 586 | 587 | "camelcase@npm:^6.0.0": 588 | version: 6.3.0 589 | resolution: "camelcase@npm:6.3.0" 590 | checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 591 | languageName: node 592 | linkType: hard 593 | 594 | "chalk@npm:^4.0.0, chalk@npm:^4.1.0": 595 | version: 4.1.2 596 | resolution: "chalk@npm:4.1.2" 597 | dependencies: 598 | ansi-styles: "npm:^4.1.0" 599 | supports-color: "npm:^7.1.0" 600 | checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 601 | languageName: node 602 | linkType: hard 603 | 604 | "chalk@npm:^5.4.1": 605 | version: 5.4.1 606 | resolution: "chalk@npm:5.4.1" 607 | checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef 608 | languageName: node 609 | linkType: hard 610 | 611 | "chokidar@npm:^3.5.3": 612 | version: 3.6.0 613 | resolution: "chokidar@npm:3.6.0" 614 | dependencies: 615 | anymatch: "npm:~3.1.2" 616 | braces: "npm:~3.0.2" 617 | fsevents: "npm:~2.3.2" 618 | glob-parent: "npm:~5.1.2" 619 | is-binary-path: "npm:~2.1.0" 620 | is-glob: "npm:~4.0.1" 621 | normalize-path: "npm:~3.0.0" 622 | readdirp: "npm:~3.6.0" 623 | dependenciesMeta: 624 | fsevents: 625 | optional: true 626 | checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 627 | languageName: node 628 | linkType: hard 629 | 630 | "chownr@npm:^3.0.0": 631 | version: 3.0.0 632 | resolution: "chownr@npm:3.0.0" 633 | checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 634 | languageName: node 635 | linkType: hard 636 | 637 | "cli-cursor@npm:^5.0.0": 638 | version: 5.0.0 639 | resolution: "cli-cursor@npm:5.0.0" 640 | dependencies: 641 | restore-cursor: "npm:^5.0.0" 642 | checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 643 | languageName: node 644 | linkType: hard 645 | 646 | "cli-truncate@npm:^4.0.0": 647 | version: 4.0.0 648 | resolution: "cli-truncate@npm:4.0.0" 649 | dependencies: 650 | slice-ansi: "npm:^5.0.0" 651 | string-width: "npm:^7.0.0" 652 | checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c 653 | languageName: node 654 | linkType: hard 655 | 656 | "cliui@npm:^8.0.1": 657 | version: 8.0.1 658 | resolution: "cliui@npm:8.0.1" 659 | dependencies: 660 | string-width: "npm:^4.2.0" 661 | strip-ansi: "npm:^6.0.1" 662 | wrap-ansi: "npm:^7.0.0" 663 | checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 664 | languageName: node 665 | linkType: hard 666 | 667 | "color-convert@npm:^2.0.1": 668 | version: 2.0.1 669 | resolution: "color-convert@npm:2.0.1" 670 | dependencies: 671 | color-name: "npm:~1.1.4" 672 | checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 673 | languageName: node 674 | linkType: hard 675 | 676 | "color-name@npm:~1.1.4": 677 | version: 1.1.4 678 | resolution: "color-name@npm:1.1.4" 679 | checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 680 | languageName: node 681 | linkType: hard 682 | 683 | "colorette@npm:^2.0.20": 684 | version: 2.0.20 685 | resolution: "colorette@npm:2.0.20" 686 | checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 687 | languageName: node 688 | linkType: hard 689 | 690 | "commander@npm:^14.0.0": 691 | version: 14.0.0 692 | resolution: "commander@npm:14.0.0" 693 | checksum: 10c0/73c4babfa558077868d84522b11ef56834165d472b9e86a634cd4c3ae7fc72d59af6377d8878e06bd570fe8f3161eced3cbe383c38f7093272bb65bd242b595b 694 | languageName: node 695 | linkType: hard 696 | 697 | "concat-map@npm:0.0.1": 698 | version: 0.0.1 699 | resolution: "concat-map@npm:0.0.1" 700 | checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f 701 | languageName: node 702 | linkType: hard 703 | 704 | "cross-spawn@npm:^7.0.6": 705 | version: 7.0.6 706 | resolution: "cross-spawn@npm:7.0.6" 707 | dependencies: 708 | path-key: "npm:^3.1.0" 709 | shebang-command: "npm:^2.0.0" 710 | which: "npm:^2.0.1" 711 | checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 712 | languageName: node 713 | linkType: hard 714 | 715 | "debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5": 716 | version: 4.4.0 717 | resolution: "debug@npm:4.4.0" 718 | dependencies: 719 | ms: "npm:^2.1.3" 720 | peerDependenciesMeta: 721 | supports-color: 722 | optional: true 723 | checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de 724 | languageName: node 725 | linkType: hard 726 | 727 | "debug@npm:^4.4.1": 728 | version: 4.4.1 729 | resolution: "debug@npm:4.4.1" 730 | dependencies: 731 | ms: "npm:^2.1.3" 732 | peerDependenciesMeta: 733 | supports-color: 734 | optional: true 735 | checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 736 | languageName: node 737 | linkType: hard 738 | 739 | "decamelize@npm:^4.0.0": 740 | version: 4.0.0 741 | resolution: "decamelize@npm:4.0.0" 742 | checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d 743 | languageName: node 744 | linkType: hard 745 | 746 | "deep-is@npm:^0.1.3": 747 | version: 0.1.4 748 | resolution: "deep-is@npm:0.1.4" 749 | checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c 750 | languageName: node 751 | linkType: hard 752 | 753 | "diff@npm:^5.2.0": 754 | version: 5.2.0 755 | resolution: "diff@npm:5.2.0" 756 | checksum: 10c0/aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4 757 | languageName: node 758 | linkType: hard 759 | 760 | "eastasianwidth@npm:^0.2.0": 761 | version: 0.2.0 762 | resolution: "eastasianwidth@npm:0.2.0" 763 | checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 764 | languageName: node 765 | linkType: hard 766 | 767 | "emoji-regex@npm:^10.3.0": 768 | version: 10.4.0 769 | resolution: "emoji-regex@npm:10.4.0" 770 | checksum: 10c0/a3fcedfc58bfcce21a05a5f36a529d81e88d602100145fcca3dc6f795e3c8acc4fc18fe773fbf9b6d6e9371205edb3afa2668ec3473fa2aa7fd47d2a9d46482d 771 | languageName: node 772 | linkType: hard 773 | 774 | "emoji-regex@npm:^8.0.0": 775 | version: 8.0.0 776 | resolution: "emoji-regex@npm:8.0.0" 777 | checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 778 | languageName: node 779 | linkType: hard 780 | 781 | "emoji-regex@npm:^9.2.2": 782 | version: 9.2.2 783 | resolution: "emoji-regex@npm:9.2.2" 784 | checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 785 | languageName: node 786 | linkType: hard 787 | 788 | "encoding@npm:^0.1.13": 789 | version: 0.1.13 790 | resolution: "encoding@npm:0.1.13" 791 | dependencies: 792 | iconv-lite: "npm:^0.6.2" 793 | checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 794 | languageName: node 795 | linkType: hard 796 | 797 | "enhanced-resolve@npm:^5.17.1": 798 | version: 5.18.1 799 | resolution: "enhanced-resolve@npm:5.18.1" 800 | dependencies: 801 | graceful-fs: "npm:^4.2.4" 802 | tapable: "npm:^2.2.0" 803 | checksum: 10c0/4cffd9b125225184e2abed9fdf0ed3dbd2224c873b165d0838fd066cde32e0918626cba2f1f4bf6860762f13a7e2364fd89a82b99566be2873d813573ac71846 804 | languageName: node 805 | linkType: hard 806 | 807 | "env-paths@npm:^2.2.0": 808 | version: 2.2.1 809 | resolution: "env-paths@npm:2.2.1" 810 | checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 811 | languageName: node 812 | linkType: hard 813 | 814 | "environment@npm:^1.0.0": 815 | version: 1.1.0 816 | resolution: "environment@npm:1.1.0" 817 | checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d 818 | languageName: node 819 | linkType: hard 820 | 821 | "err-code@npm:^2.0.2": 822 | version: 2.0.3 823 | resolution: "err-code@npm:2.0.3" 824 | checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 825 | languageName: node 826 | linkType: hard 827 | 828 | "esbuild@npm:^0.25.3": 829 | version: 0.25.3 830 | resolution: "esbuild@npm:0.25.3" 831 | dependencies: 832 | "@esbuild/aix-ppc64": "npm:0.25.3" 833 | "@esbuild/android-arm": "npm:0.25.3" 834 | "@esbuild/android-arm64": "npm:0.25.3" 835 | "@esbuild/android-x64": "npm:0.25.3" 836 | "@esbuild/darwin-arm64": "npm:0.25.3" 837 | "@esbuild/darwin-x64": "npm:0.25.3" 838 | "@esbuild/freebsd-arm64": "npm:0.25.3" 839 | "@esbuild/freebsd-x64": "npm:0.25.3" 840 | "@esbuild/linux-arm": "npm:0.25.3" 841 | "@esbuild/linux-arm64": "npm:0.25.3" 842 | "@esbuild/linux-ia32": "npm:0.25.3" 843 | "@esbuild/linux-loong64": "npm:0.25.3" 844 | "@esbuild/linux-mips64el": "npm:0.25.3" 845 | "@esbuild/linux-ppc64": "npm:0.25.3" 846 | "@esbuild/linux-riscv64": "npm:0.25.3" 847 | "@esbuild/linux-s390x": "npm:0.25.3" 848 | "@esbuild/linux-x64": "npm:0.25.3" 849 | "@esbuild/netbsd-arm64": "npm:0.25.3" 850 | "@esbuild/netbsd-x64": "npm:0.25.3" 851 | "@esbuild/openbsd-arm64": "npm:0.25.3" 852 | "@esbuild/openbsd-x64": "npm:0.25.3" 853 | "@esbuild/sunos-x64": "npm:0.25.3" 854 | "@esbuild/win32-arm64": "npm:0.25.3" 855 | "@esbuild/win32-ia32": "npm:0.25.3" 856 | "@esbuild/win32-x64": "npm:0.25.3" 857 | dependenciesMeta: 858 | "@esbuild/aix-ppc64": 859 | optional: true 860 | "@esbuild/android-arm": 861 | optional: true 862 | "@esbuild/android-arm64": 863 | optional: true 864 | "@esbuild/android-x64": 865 | optional: true 866 | "@esbuild/darwin-arm64": 867 | optional: true 868 | "@esbuild/darwin-x64": 869 | optional: true 870 | "@esbuild/freebsd-arm64": 871 | optional: true 872 | "@esbuild/freebsd-x64": 873 | optional: true 874 | "@esbuild/linux-arm": 875 | optional: true 876 | "@esbuild/linux-arm64": 877 | optional: true 878 | "@esbuild/linux-ia32": 879 | optional: true 880 | "@esbuild/linux-loong64": 881 | optional: true 882 | "@esbuild/linux-mips64el": 883 | optional: true 884 | "@esbuild/linux-ppc64": 885 | optional: true 886 | "@esbuild/linux-riscv64": 887 | optional: true 888 | "@esbuild/linux-s390x": 889 | optional: true 890 | "@esbuild/linux-x64": 891 | optional: true 892 | "@esbuild/netbsd-arm64": 893 | optional: true 894 | "@esbuild/netbsd-x64": 895 | optional: true 896 | "@esbuild/openbsd-arm64": 897 | optional: true 898 | "@esbuild/openbsd-x64": 899 | optional: true 900 | "@esbuild/sunos-x64": 901 | optional: true 902 | "@esbuild/win32-arm64": 903 | optional: true 904 | "@esbuild/win32-ia32": 905 | optional: true 906 | "@esbuild/win32-x64": 907 | optional: true 908 | bin: 909 | esbuild: bin/esbuild 910 | checksum: 10c0/127aff654310ede4e2eb232a7b1d8823f5b5d69222caf17aa7f172574a5b6b75f71ce78c6d8a40030421d7c75b784dc640de0fb1b87b7ea77ab2a1c832fa8df8 911 | languageName: node 912 | linkType: hard 913 | 914 | "escalade@npm:^3.1.1": 915 | version: 3.2.0 916 | resolution: "escalade@npm:3.2.0" 917 | checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 918 | languageName: node 919 | linkType: hard 920 | 921 | "escape-string-regexp@npm:^4.0.0": 922 | version: 4.0.0 923 | resolution: "escape-string-regexp@npm:4.0.0" 924 | checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 925 | languageName: node 926 | linkType: hard 927 | 928 | "eslint-compat-utils@npm:^0.5.1": 929 | version: 0.5.1 930 | resolution: "eslint-compat-utils@npm:0.5.1" 931 | dependencies: 932 | semver: "npm:^7.5.4" 933 | peerDependencies: 934 | eslint: ">=6.0.0" 935 | checksum: 10c0/325e815205fab70ebcd379f6d4b5d44c7d791bb8dfe0c9888233f30ebabd9418422595b53a781b946c768d9244d858540e5e6129a6b3dd6d606f467d599edc6c 936 | languageName: node 937 | linkType: hard 938 | 939 | "eslint-plugin-es-x@npm:^7.8.0": 940 | version: 7.8.0 941 | resolution: "eslint-plugin-es-x@npm:7.8.0" 942 | dependencies: 943 | "@eslint-community/eslint-utils": "npm:^4.1.2" 944 | "@eslint-community/regexpp": "npm:^4.11.0" 945 | eslint-compat-utils: "npm:^0.5.1" 946 | peerDependencies: 947 | eslint: ">=8" 948 | checksum: 10c0/002fda8c029bc5da41e24e7ac11654062831d675fc4f5f20d0de460e24bf1e05cd559000678ef3e46c48641190f4fc07ae3d57aa5e8b085ef5f67e5f63742614 949 | languageName: node 950 | linkType: hard 951 | 952 | "eslint-plugin-eslint-plugin@npm:^6.4.0": 953 | version: 6.4.0 954 | resolution: "eslint-plugin-eslint-plugin@npm:6.4.0" 955 | dependencies: 956 | "@eslint-community/eslint-utils": "npm:^4.4.0" 957 | estraverse: "npm:^5.3.0" 958 | peerDependencies: 959 | eslint: ">=8.23.0" 960 | checksum: 10c0/c34a976d70e3d8ef91f6adb7ff6a89d149531c8624da07b2ef2696c44d4a0184c4c5baf8a63e18f53fd13468b2b491c72bcbb1832178e96a18bce54ad385dca5 961 | languageName: node 962 | linkType: hard 963 | 964 | "eslint-plugin-n@npm:^17.17.0": 965 | version: 17.17.0 966 | resolution: "eslint-plugin-n@npm:17.17.0" 967 | dependencies: 968 | "@eslint-community/eslint-utils": "npm:^4.5.0" 969 | enhanced-resolve: "npm:^5.17.1" 970 | eslint-plugin-es-x: "npm:^7.8.0" 971 | get-tsconfig: "npm:^4.8.1" 972 | globals: "npm:^15.11.0" 973 | ignore: "npm:^5.3.2" 974 | minimatch: "npm:^9.0.5" 975 | semver: "npm:^7.6.3" 976 | peerDependencies: 977 | eslint: ">=8.23.0" 978 | checksum: 10c0/ac6b2e2bbdc8f49a84be1bf1add8a412093a56fe95e8820610ecd5185fa00a348197a06fe3fe36080c09dc5d5a8f0f4f543cb3cf193265ace3fd071a79a07e88 979 | languageName: node 980 | linkType: hard 981 | 982 | "eslint-plugin-react-you-might-not-need-an-effect@workspace:.": 983 | version: 0.0.0-use.local 984 | resolution: "eslint-plugin-react-you-might-not-need-an-effect@workspace:." 985 | dependencies: 986 | "@eslint/js": "npm:^9.28.0" 987 | esbuild: "npm:^0.25.3" 988 | eslint: "npm:^9.20.1" 989 | eslint-plugin-eslint-plugin: "npm:^6.4.0" 990 | eslint-plugin-n: "npm:^17.17.0" 991 | eslint-utils: "npm:^3.0.0" 992 | globals: "npm:^16.2.0" 993 | lint-staged: "npm:^16.1.0" 994 | mocha: "npm:11.1.0" 995 | prettier: "npm:^3.5.3" 996 | simple-git-hooks: "npm:^2.13.0" 997 | peerDependencies: 998 | eslint: ">=7.0.0" 999 | languageName: unknown 1000 | linkType: soft 1001 | 1002 | "eslint-scope@npm:^8.2.0": 1003 | version: 8.2.0 1004 | resolution: "eslint-scope@npm:8.2.0" 1005 | dependencies: 1006 | esrecurse: "npm:^4.3.0" 1007 | estraverse: "npm:^5.2.0" 1008 | checksum: 10c0/8d2d58e2136d548ac7e0099b1a90d9fab56f990d86eb518de1247a7066d38c908be2f3df477a79cf60d70b30ba18735d6c6e70e9914dca2ee515a729975d70d6 1009 | languageName: node 1010 | linkType: hard 1011 | 1012 | "eslint-utils@npm:^3.0.0": 1013 | version: 3.0.0 1014 | resolution: "eslint-utils@npm:3.0.0" 1015 | dependencies: 1016 | eslint-visitor-keys: "npm:^2.0.0" 1017 | peerDependencies: 1018 | eslint: ">=5" 1019 | checksum: 10c0/45aa2b63667a8d9b474c98c28af908d0a592bed1a4568f3145cd49fb5d9510f545327ec95561625290313fe126e6d7bdfe3fdbdb6f432689fab6b9497d3bfb52 1020 | languageName: node 1021 | linkType: hard 1022 | 1023 | "eslint-visitor-keys@npm:^2.0.0": 1024 | version: 2.1.0 1025 | resolution: "eslint-visitor-keys@npm:2.1.0" 1026 | checksum: 10c0/9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 1027 | languageName: node 1028 | linkType: hard 1029 | 1030 | "eslint-visitor-keys@npm:^3.4.3": 1031 | version: 3.4.3 1032 | resolution: "eslint-visitor-keys@npm:3.4.3" 1033 | checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 1034 | languageName: node 1035 | linkType: hard 1036 | 1037 | "eslint-visitor-keys@npm:^4.2.0": 1038 | version: 4.2.0 1039 | resolution: "eslint-visitor-keys@npm:4.2.0" 1040 | checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269 1041 | languageName: node 1042 | linkType: hard 1043 | 1044 | "eslint@npm:^9.20.1": 1045 | version: 9.20.1 1046 | resolution: "eslint@npm:9.20.1" 1047 | dependencies: 1048 | "@eslint-community/eslint-utils": "npm:^4.2.0" 1049 | "@eslint-community/regexpp": "npm:^4.12.1" 1050 | "@eslint/config-array": "npm:^0.19.0" 1051 | "@eslint/core": "npm:^0.11.0" 1052 | "@eslint/eslintrc": "npm:^3.2.0" 1053 | "@eslint/js": "npm:9.20.0" 1054 | "@eslint/plugin-kit": "npm:^0.2.5" 1055 | "@humanfs/node": "npm:^0.16.6" 1056 | "@humanwhocodes/module-importer": "npm:^1.0.1" 1057 | "@humanwhocodes/retry": "npm:^0.4.1" 1058 | "@types/estree": "npm:^1.0.6" 1059 | "@types/json-schema": "npm:^7.0.15" 1060 | ajv: "npm:^6.12.4" 1061 | chalk: "npm:^4.0.0" 1062 | cross-spawn: "npm:^7.0.6" 1063 | debug: "npm:^4.3.2" 1064 | escape-string-regexp: "npm:^4.0.0" 1065 | eslint-scope: "npm:^8.2.0" 1066 | eslint-visitor-keys: "npm:^4.2.0" 1067 | espree: "npm:^10.3.0" 1068 | esquery: "npm:^1.5.0" 1069 | esutils: "npm:^2.0.2" 1070 | fast-deep-equal: "npm:^3.1.3" 1071 | file-entry-cache: "npm:^8.0.0" 1072 | find-up: "npm:^5.0.0" 1073 | glob-parent: "npm:^6.0.2" 1074 | ignore: "npm:^5.2.0" 1075 | imurmurhash: "npm:^0.1.4" 1076 | is-glob: "npm:^4.0.0" 1077 | json-stable-stringify-without-jsonify: "npm:^1.0.1" 1078 | lodash.merge: "npm:^4.6.2" 1079 | minimatch: "npm:^3.1.2" 1080 | natural-compare: "npm:^1.4.0" 1081 | optionator: "npm:^0.9.3" 1082 | peerDependencies: 1083 | jiti: "*" 1084 | peerDependenciesMeta: 1085 | jiti: 1086 | optional: true 1087 | bin: 1088 | eslint: bin/eslint.js 1089 | checksum: 10c0/056789dd5a00897730376f8c0a191e22840e97b7276916068ec096341cb2ec3a918c8bd474bf94ccd7b457ad9fbc16e5c521a993c7cc6ebcf241933e2fd378b0 1090 | languageName: node 1091 | linkType: hard 1092 | 1093 | "espree@npm:^10.0.1, espree@npm:^10.3.0": 1094 | version: 10.3.0 1095 | resolution: "espree@npm:10.3.0" 1096 | dependencies: 1097 | acorn: "npm:^8.14.0" 1098 | acorn-jsx: "npm:^5.3.2" 1099 | eslint-visitor-keys: "npm:^4.2.0" 1100 | checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462 1101 | languageName: node 1102 | linkType: hard 1103 | 1104 | "esquery@npm:^1.5.0": 1105 | version: 1.6.0 1106 | resolution: "esquery@npm:1.6.0" 1107 | dependencies: 1108 | estraverse: "npm:^5.1.0" 1109 | checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 1110 | languageName: node 1111 | linkType: hard 1112 | 1113 | "esrecurse@npm:^4.3.0": 1114 | version: 4.3.0 1115 | resolution: "esrecurse@npm:4.3.0" 1116 | dependencies: 1117 | estraverse: "npm:^5.2.0" 1118 | checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 1119 | languageName: node 1120 | linkType: hard 1121 | 1122 | "estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": 1123 | version: 5.3.0 1124 | resolution: "estraverse@npm:5.3.0" 1125 | checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 1126 | languageName: node 1127 | linkType: hard 1128 | 1129 | "esutils@npm:^2.0.2": 1130 | version: 2.0.3 1131 | resolution: "esutils@npm:2.0.3" 1132 | checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 1133 | languageName: node 1134 | linkType: hard 1135 | 1136 | "eventemitter3@npm:^5.0.1": 1137 | version: 5.0.1 1138 | resolution: "eventemitter3@npm:5.0.1" 1139 | checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 1140 | languageName: node 1141 | linkType: hard 1142 | 1143 | "exponential-backoff@npm:^3.1.1": 1144 | version: 3.1.2 1145 | resolution: "exponential-backoff@npm:3.1.2" 1146 | checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 1147 | languageName: node 1148 | linkType: hard 1149 | 1150 | "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": 1151 | version: 3.1.3 1152 | resolution: "fast-deep-equal@npm:3.1.3" 1153 | checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 1154 | languageName: node 1155 | linkType: hard 1156 | 1157 | "fast-json-stable-stringify@npm:^2.0.0": 1158 | version: 2.1.0 1159 | resolution: "fast-json-stable-stringify@npm:2.1.0" 1160 | checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b 1161 | languageName: node 1162 | linkType: hard 1163 | 1164 | "fast-levenshtein@npm:^2.0.6": 1165 | version: 2.0.6 1166 | resolution: "fast-levenshtein@npm:2.0.6" 1167 | checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 1168 | languageName: node 1169 | linkType: hard 1170 | 1171 | "fdir@npm:^6.4.4": 1172 | version: 6.4.4 1173 | resolution: "fdir@npm:6.4.4" 1174 | peerDependencies: 1175 | picomatch: ^3 || ^4 1176 | peerDependenciesMeta: 1177 | picomatch: 1178 | optional: true 1179 | checksum: 10c0/6ccc33be16945ee7bc841e1b4178c0b4cf18d3804894cb482aa514651c962a162f96da7ffc6ebfaf0df311689fb70091b04dd6caffe28d56b9ebdc0e7ccadfdd 1180 | languageName: node 1181 | linkType: hard 1182 | 1183 | "file-entry-cache@npm:^8.0.0": 1184 | version: 8.0.0 1185 | resolution: "file-entry-cache@npm:8.0.0" 1186 | dependencies: 1187 | flat-cache: "npm:^4.0.0" 1188 | checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 1189 | languageName: node 1190 | linkType: hard 1191 | 1192 | "fill-range@npm:^7.1.1": 1193 | version: 7.1.1 1194 | resolution: "fill-range@npm:7.1.1" 1195 | dependencies: 1196 | to-regex-range: "npm:^5.0.1" 1197 | checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 1198 | languageName: node 1199 | linkType: hard 1200 | 1201 | "find-up@npm:^5.0.0": 1202 | version: 5.0.0 1203 | resolution: "find-up@npm:5.0.0" 1204 | dependencies: 1205 | locate-path: "npm:^6.0.0" 1206 | path-exists: "npm:^4.0.0" 1207 | checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a 1208 | languageName: node 1209 | linkType: hard 1210 | 1211 | "flat-cache@npm:^4.0.0": 1212 | version: 4.0.1 1213 | resolution: "flat-cache@npm:4.0.1" 1214 | dependencies: 1215 | flatted: "npm:^3.2.9" 1216 | keyv: "npm:^4.5.4" 1217 | checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc 1218 | languageName: node 1219 | linkType: hard 1220 | 1221 | "flat@npm:^5.0.2": 1222 | version: 5.0.2 1223 | resolution: "flat@npm:5.0.2" 1224 | bin: 1225 | flat: cli.js 1226 | checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe 1227 | languageName: node 1228 | linkType: hard 1229 | 1230 | "flatted@npm:^3.2.9": 1231 | version: 3.3.2 1232 | resolution: "flatted@npm:3.3.2" 1233 | checksum: 10c0/24cc735e74d593b6c767fe04f2ef369abe15b62f6906158079b9874bdb3ee5ae7110bb75042e70cd3f99d409d766f357caf78d5ecee9780206f5fdc5edbad334 1234 | languageName: node 1235 | linkType: hard 1236 | 1237 | "foreground-child@npm:^3.1.0": 1238 | version: 3.3.1 1239 | resolution: "foreground-child@npm:3.3.1" 1240 | dependencies: 1241 | cross-spawn: "npm:^7.0.6" 1242 | signal-exit: "npm:^4.0.1" 1243 | checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 1244 | languageName: node 1245 | linkType: hard 1246 | 1247 | "fs-minipass@npm:^3.0.0": 1248 | version: 3.0.3 1249 | resolution: "fs-minipass@npm:3.0.3" 1250 | dependencies: 1251 | minipass: "npm:^7.0.3" 1252 | checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 1253 | languageName: node 1254 | linkType: hard 1255 | 1256 | "fsevents@npm:~2.3.2": 1257 | version: 2.3.3 1258 | resolution: "fsevents@npm:2.3.3" 1259 | dependencies: 1260 | node-gyp: "npm:latest" 1261 | checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 1262 | conditions: os=darwin 1263 | languageName: node 1264 | linkType: hard 1265 | 1266 | "fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": 1267 | version: 2.3.3 1268 | resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" 1269 | dependencies: 1270 | node-gyp: "npm:latest" 1271 | conditions: os=darwin 1272 | languageName: node 1273 | linkType: hard 1274 | 1275 | "get-caller-file@npm:^2.0.5": 1276 | version: 2.0.5 1277 | resolution: "get-caller-file@npm:2.0.5" 1278 | checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde 1279 | languageName: node 1280 | linkType: hard 1281 | 1282 | "get-east-asian-width@npm:^1.0.0": 1283 | version: 1.3.0 1284 | resolution: "get-east-asian-width@npm:1.3.0" 1285 | checksum: 10c0/1a049ba697e0f9a4d5514c4623781c5246982bdb61082da6b5ae6c33d838e52ce6726407df285cdbb27ec1908b333cf2820989bd3e986e37bb20979437fdf34b 1286 | languageName: node 1287 | linkType: hard 1288 | 1289 | "get-tsconfig@npm:^4.8.1": 1290 | version: 4.10.0 1291 | resolution: "get-tsconfig@npm:4.10.0" 1292 | dependencies: 1293 | resolve-pkg-maps: "npm:^1.0.0" 1294 | checksum: 10c0/c9b5572c5118923c491c04285c73bd55b19e214992af957c502a3be0fc0043bb421386ffd45ca3433c0a7fba81221ca300479e8393960acf15d0ed4563f38a86 1295 | languageName: node 1296 | linkType: hard 1297 | 1298 | "glob-parent@npm:^6.0.2": 1299 | version: 6.0.2 1300 | resolution: "glob-parent@npm:6.0.2" 1301 | dependencies: 1302 | is-glob: "npm:^4.0.3" 1303 | checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 1304 | languageName: node 1305 | linkType: hard 1306 | 1307 | "glob-parent@npm:~5.1.2": 1308 | version: 5.1.2 1309 | resolution: "glob-parent@npm:5.1.2" 1310 | dependencies: 1311 | is-glob: "npm:^4.0.1" 1312 | checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee 1313 | languageName: node 1314 | linkType: hard 1315 | 1316 | "glob@npm:^10.2.2, glob@npm:^10.4.5": 1317 | version: 10.4.5 1318 | resolution: "glob@npm:10.4.5" 1319 | dependencies: 1320 | foreground-child: "npm:^3.1.0" 1321 | jackspeak: "npm:^3.1.2" 1322 | minimatch: "npm:^9.0.4" 1323 | minipass: "npm:^7.1.2" 1324 | package-json-from-dist: "npm:^1.0.0" 1325 | path-scurry: "npm:^1.11.1" 1326 | bin: 1327 | glob: dist/esm/bin.mjs 1328 | checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e 1329 | languageName: node 1330 | linkType: hard 1331 | 1332 | "globals@npm:^14.0.0": 1333 | version: 14.0.0 1334 | resolution: "globals@npm:14.0.0" 1335 | checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d 1336 | languageName: node 1337 | linkType: hard 1338 | 1339 | "globals@npm:^15.11.0": 1340 | version: 15.15.0 1341 | resolution: "globals@npm:15.15.0" 1342 | checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe 1343 | languageName: node 1344 | linkType: hard 1345 | 1346 | "globals@npm:^16.2.0": 1347 | version: 16.2.0 1348 | resolution: "globals@npm:16.2.0" 1349 | checksum: 10c0/c2b3ea163faa6f8a38076b471b12f4bda891f7df7f7d2e8294fb4801d735a51a73431bf4c1696c5bf5dbca5e0a0db894698acfcbd3068730c6b12eef185dea25 1350 | languageName: node 1351 | linkType: hard 1352 | 1353 | "graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": 1354 | version: 4.2.11 1355 | resolution: "graceful-fs@npm:4.2.11" 1356 | checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 1357 | languageName: node 1358 | linkType: hard 1359 | 1360 | "has-flag@npm:^4.0.0": 1361 | version: 4.0.0 1362 | resolution: "has-flag@npm:4.0.0" 1363 | checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 1364 | languageName: node 1365 | linkType: hard 1366 | 1367 | "he@npm:^1.2.0": 1368 | version: 1.2.0 1369 | resolution: "he@npm:1.2.0" 1370 | bin: 1371 | he: bin/he 1372 | checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 1373 | languageName: node 1374 | linkType: hard 1375 | 1376 | "http-cache-semantics@npm:^4.1.1": 1377 | version: 4.1.1 1378 | resolution: "http-cache-semantics@npm:4.1.1" 1379 | checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc 1380 | languageName: node 1381 | linkType: hard 1382 | 1383 | "http-proxy-agent@npm:^7.0.0": 1384 | version: 7.0.2 1385 | resolution: "http-proxy-agent@npm:7.0.2" 1386 | dependencies: 1387 | agent-base: "npm:^7.1.0" 1388 | debug: "npm:^4.3.4" 1389 | checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 1390 | languageName: node 1391 | linkType: hard 1392 | 1393 | "https-proxy-agent@npm:^7.0.1": 1394 | version: 7.0.6 1395 | resolution: "https-proxy-agent@npm:7.0.6" 1396 | dependencies: 1397 | agent-base: "npm:^7.1.2" 1398 | debug: "npm:4" 1399 | checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac 1400 | languageName: node 1401 | linkType: hard 1402 | 1403 | "iconv-lite@npm:^0.6.2": 1404 | version: 0.6.3 1405 | resolution: "iconv-lite@npm:0.6.3" 1406 | dependencies: 1407 | safer-buffer: "npm:>= 2.1.2 < 3.0.0" 1408 | checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 1409 | languageName: node 1410 | linkType: hard 1411 | 1412 | "ignore@npm:^5.2.0, ignore@npm:^5.3.2": 1413 | version: 5.3.2 1414 | resolution: "ignore@npm:5.3.2" 1415 | checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 1416 | languageName: node 1417 | linkType: hard 1418 | 1419 | "import-fresh@npm:^3.2.1": 1420 | version: 3.3.1 1421 | resolution: "import-fresh@npm:3.3.1" 1422 | dependencies: 1423 | parent-module: "npm:^1.0.0" 1424 | resolve-from: "npm:^4.0.0" 1425 | checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec 1426 | languageName: node 1427 | linkType: hard 1428 | 1429 | "imurmurhash@npm:^0.1.4": 1430 | version: 0.1.4 1431 | resolution: "imurmurhash@npm:0.1.4" 1432 | checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 1433 | languageName: node 1434 | linkType: hard 1435 | 1436 | "ip-address@npm:^9.0.5": 1437 | version: 9.0.5 1438 | resolution: "ip-address@npm:9.0.5" 1439 | dependencies: 1440 | jsbn: "npm:1.1.0" 1441 | sprintf-js: "npm:^1.1.3" 1442 | checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc 1443 | languageName: node 1444 | linkType: hard 1445 | 1446 | "is-binary-path@npm:~2.1.0": 1447 | version: 2.1.0 1448 | resolution: "is-binary-path@npm:2.1.0" 1449 | dependencies: 1450 | binary-extensions: "npm:^2.0.0" 1451 | checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 1452 | languageName: node 1453 | linkType: hard 1454 | 1455 | "is-extglob@npm:^2.1.1": 1456 | version: 2.1.1 1457 | resolution: "is-extglob@npm:2.1.1" 1458 | checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 1459 | languageName: node 1460 | linkType: hard 1461 | 1462 | "is-fullwidth-code-point@npm:^3.0.0": 1463 | version: 3.0.0 1464 | resolution: "is-fullwidth-code-point@npm:3.0.0" 1465 | checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc 1466 | languageName: node 1467 | linkType: hard 1468 | 1469 | "is-fullwidth-code-point@npm:^4.0.0": 1470 | version: 4.0.0 1471 | resolution: "is-fullwidth-code-point@npm:4.0.0" 1472 | checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8 1473 | languageName: node 1474 | linkType: hard 1475 | 1476 | "is-fullwidth-code-point@npm:^5.0.0": 1477 | version: 5.0.0 1478 | resolution: "is-fullwidth-code-point@npm:5.0.0" 1479 | dependencies: 1480 | get-east-asian-width: "npm:^1.0.0" 1481 | checksum: 10c0/cd591b27d43d76b05fa65ed03eddce57a16e1eca0b7797ff7255de97019bcaf0219acfc0c4f7af13319e13541f2a53c0ace476f442b13267b9a6a7568f2b65c8 1482 | languageName: node 1483 | linkType: hard 1484 | 1485 | "is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": 1486 | version: 4.0.3 1487 | resolution: "is-glob@npm:4.0.3" 1488 | dependencies: 1489 | is-extglob: "npm:^2.1.1" 1490 | checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a 1491 | languageName: node 1492 | linkType: hard 1493 | 1494 | "is-number@npm:^7.0.0": 1495 | version: 7.0.0 1496 | resolution: "is-number@npm:7.0.0" 1497 | checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 1498 | languageName: node 1499 | linkType: hard 1500 | 1501 | "is-plain-obj@npm:^2.1.0": 1502 | version: 2.1.0 1503 | resolution: "is-plain-obj@npm:2.1.0" 1504 | checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 1505 | languageName: node 1506 | linkType: hard 1507 | 1508 | "is-unicode-supported@npm:^0.1.0": 1509 | version: 0.1.0 1510 | resolution: "is-unicode-supported@npm:0.1.0" 1511 | checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 1512 | languageName: node 1513 | linkType: hard 1514 | 1515 | "isexe@npm:^2.0.0": 1516 | version: 2.0.0 1517 | resolution: "isexe@npm:2.0.0" 1518 | checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d 1519 | languageName: node 1520 | linkType: hard 1521 | 1522 | "isexe@npm:^3.1.1": 1523 | version: 3.1.1 1524 | resolution: "isexe@npm:3.1.1" 1525 | checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 1526 | languageName: node 1527 | linkType: hard 1528 | 1529 | "jackspeak@npm:^3.1.2": 1530 | version: 3.4.3 1531 | resolution: "jackspeak@npm:3.4.3" 1532 | dependencies: 1533 | "@isaacs/cliui": "npm:^8.0.2" 1534 | "@pkgjs/parseargs": "npm:^0.11.0" 1535 | dependenciesMeta: 1536 | "@pkgjs/parseargs": 1537 | optional: true 1538 | checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 1539 | languageName: node 1540 | linkType: hard 1541 | 1542 | "js-yaml@npm:^4.1.0": 1543 | version: 4.1.0 1544 | resolution: "js-yaml@npm:4.1.0" 1545 | dependencies: 1546 | argparse: "npm:^2.0.1" 1547 | bin: 1548 | js-yaml: bin/js-yaml.js 1549 | checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f 1550 | languageName: node 1551 | linkType: hard 1552 | 1553 | "jsbn@npm:1.1.0": 1554 | version: 1.1.0 1555 | resolution: "jsbn@npm:1.1.0" 1556 | checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 1557 | languageName: node 1558 | linkType: hard 1559 | 1560 | "json-buffer@npm:3.0.1": 1561 | version: 3.0.1 1562 | resolution: "json-buffer@npm:3.0.1" 1563 | checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 1564 | languageName: node 1565 | linkType: hard 1566 | 1567 | "json-schema-traverse@npm:^0.4.1": 1568 | version: 0.4.1 1569 | resolution: "json-schema-traverse@npm:0.4.1" 1570 | checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce 1571 | languageName: node 1572 | linkType: hard 1573 | 1574 | "json-stable-stringify-without-jsonify@npm:^1.0.1": 1575 | version: 1.0.1 1576 | resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" 1577 | checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 1578 | languageName: node 1579 | linkType: hard 1580 | 1581 | "keyv@npm:^4.5.4": 1582 | version: 4.5.4 1583 | resolution: "keyv@npm:4.5.4" 1584 | dependencies: 1585 | json-buffer: "npm:3.0.1" 1586 | checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e 1587 | languageName: node 1588 | linkType: hard 1589 | 1590 | "levn@npm:^0.4.1": 1591 | version: 0.4.1 1592 | resolution: "levn@npm:0.4.1" 1593 | dependencies: 1594 | prelude-ls: "npm:^1.2.1" 1595 | type-check: "npm:~0.4.0" 1596 | checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e 1597 | languageName: node 1598 | linkType: hard 1599 | 1600 | "lilconfig@npm:^3.1.3": 1601 | version: 3.1.3 1602 | resolution: "lilconfig@npm:3.1.3" 1603 | checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc 1604 | languageName: node 1605 | linkType: hard 1606 | 1607 | "lint-staged@npm:^16.1.0": 1608 | version: 16.1.0 1609 | resolution: "lint-staged@npm:16.1.0" 1610 | dependencies: 1611 | chalk: "npm:^5.4.1" 1612 | commander: "npm:^14.0.0" 1613 | debug: "npm:^4.4.1" 1614 | lilconfig: "npm:^3.1.3" 1615 | listr2: "npm:^8.3.3" 1616 | micromatch: "npm:^4.0.8" 1617 | nano-spawn: "npm:^1.0.2" 1618 | pidtree: "npm:^0.6.0" 1619 | string-argv: "npm:^0.3.2" 1620 | yaml: "npm:^2.8.0" 1621 | bin: 1622 | lint-staged: bin/lint-staged.js 1623 | checksum: 10c0/5cc33d61ec2c682e488eb3fcea5c153ce486623b80314f2c56af438ad78d73c7fcd3e7c911d273ac740bd34f1e030d35d4fb92d8e476984150c0c59724ac7fa4 1624 | languageName: node 1625 | linkType: hard 1626 | 1627 | "listr2@npm:^8.3.3": 1628 | version: 8.3.3 1629 | resolution: "listr2@npm:8.3.3" 1630 | dependencies: 1631 | cli-truncate: "npm:^4.0.0" 1632 | colorette: "npm:^2.0.20" 1633 | eventemitter3: "npm:^5.0.1" 1634 | log-update: "npm:^6.1.0" 1635 | rfdc: "npm:^1.4.1" 1636 | wrap-ansi: "npm:^9.0.0" 1637 | checksum: 10c0/0792f8a7fd482fa516e21689e012e07081cab3653172ca606090622cfa0024c784a1eba8095a17948a0e9a4aa98a80f7c9c90f78a0dd35173d6802f9cc123a82 1638 | languageName: node 1639 | linkType: hard 1640 | 1641 | "locate-path@npm:^6.0.0": 1642 | version: 6.0.0 1643 | resolution: "locate-path@npm:6.0.0" 1644 | dependencies: 1645 | p-locate: "npm:^5.0.0" 1646 | checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 1647 | languageName: node 1648 | linkType: hard 1649 | 1650 | "lodash.merge@npm:^4.6.2": 1651 | version: 4.6.2 1652 | resolution: "lodash.merge@npm:4.6.2" 1653 | checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 1654 | languageName: node 1655 | linkType: hard 1656 | 1657 | "log-symbols@npm:^4.1.0": 1658 | version: 4.1.0 1659 | resolution: "log-symbols@npm:4.1.0" 1660 | dependencies: 1661 | chalk: "npm:^4.1.0" 1662 | is-unicode-supported: "npm:^0.1.0" 1663 | checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 1664 | languageName: node 1665 | linkType: hard 1666 | 1667 | "log-update@npm:^6.1.0": 1668 | version: 6.1.0 1669 | resolution: "log-update@npm:6.1.0" 1670 | dependencies: 1671 | ansi-escapes: "npm:^7.0.0" 1672 | cli-cursor: "npm:^5.0.0" 1673 | slice-ansi: "npm:^7.1.0" 1674 | strip-ansi: "npm:^7.1.0" 1675 | wrap-ansi: "npm:^9.0.0" 1676 | checksum: 10c0/4b350c0a83d7753fea34dcac6cd797d1dc9603291565de009baa4aa91c0447eab0d3815a05c8ec9ac04fdfffb43c82adcdb03ec1fceafd8518e1a8c1cff4ff89 1677 | languageName: node 1678 | linkType: hard 1679 | 1680 | "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": 1681 | version: 10.4.3 1682 | resolution: "lru-cache@npm:10.4.3" 1683 | checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb 1684 | languageName: node 1685 | linkType: hard 1686 | 1687 | "make-fetch-happen@npm:^14.0.3": 1688 | version: 14.0.3 1689 | resolution: "make-fetch-happen@npm:14.0.3" 1690 | dependencies: 1691 | "@npmcli/agent": "npm:^3.0.0" 1692 | cacache: "npm:^19.0.1" 1693 | http-cache-semantics: "npm:^4.1.1" 1694 | minipass: "npm:^7.0.2" 1695 | minipass-fetch: "npm:^4.0.0" 1696 | minipass-flush: "npm:^1.0.5" 1697 | minipass-pipeline: "npm:^1.2.4" 1698 | negotiator: "npm:^1.0.0" 1699 | proc-log: "npm:^5.0.0" 1700 | promise-retry: "npm:^2.0.1" 1701 | ssri: "npm:^12.0.0" 1702 | checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 1703 | languageName: node 1704 | linkType: hard 1705 | 1706 | "micromatch@npm:^4.0.8": 1707 | version: 4.0.8 1708 | resolution: "micromatch@npm:4.0.8" 1709 | dependencies: 1710 | braces: "npm:^3.0.3" 1711 | picomatch: "npm:^2.3.1" 1712 | checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 1713 | languageName: node 1714 | linkType: hard 1715 | 1716 | "mimic-function@npm:^5.0.0": 1717 | version: 5.0.1 1718 | resolution: "mimic-function@npm:5.0.1" 1719 | checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d 1720 | languageName: node 1721 | linkType: hard 1722 | 1723 | "minimatch@npm:^3.1.2": 1724 | version: 3.1.2 1725 | resolution: "minimatch@npm:3.1.2" 1726 | dependencies: 1727 | brace-expansion: "npm:^1.1.7" 1728 | checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 1729 | languageName: node 1730 | linkType: hard 1731 | 1732 | "minimatch@npm:^5.1.6": 1733 | version: 5.1.6 1734 | resolution: "minimatch@npm:5.1.6" 1735 | dependencies: 1736 | brace-expansion: "npm:^2.0.1" 1737 | checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 1738 | languageName: node 1739 | linkType: hard 1740 | 1741 | "minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": 1742 | version: 9.0.5 1743 | resolution: "minimatch@npm:9.0.5" 1744 | dependencies: 1745 | brace-expansion: "npm:^2.0.1" 1746 | checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed 1747 | languageName: node 1748 | linkType: hard 1749 | 1750 | "minipass-collect@npm:^2.0.1": 1751 | version: 2.0.1 1752 | resolution: "minipass-collect@npm:2.0.1" 1753 | dependencies: 1754 | minipass: "npm:^7.0.3" 1755 | checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e 1756 | languageName: node 1757 | linkType: hard 1758 | 1759 | "minipass-fetch@npm:^4.0.0": 1760 | version: 4.0.1 1761 | resolution: "minipass-fetch@npm:4.0.1" 1762 | dependencies: 1763 | encoding: "npm:^0.1.13" 1764 | minipass: "npm:^7.0.3" 1765 | minipass-sized: "npm:^1.0.3" 1766 | minizlib: "npm:^3.0.1" 1767 | dependenciesMeta: 1768 | encoding: 1769 | optional: true 1770 | checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c 1771 | languageName: node 1772 | linkType: hard 1773 | 1774 | "minipass-flush@npm:^1.0.5": 1775 | version: 1.0.5 1776 | resolution: "minipass-flush@npm:1.0.5" 1777 | dependencies: 1778 | minipass: "npm:^3.0.0" 1779 | checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd 1780 | languageName: node 1781 | linkType: hard 1782 | 1783 | "minipass-pipeline@npm:^1.2.4": 1784 | version: 1.2.4 1785 | resolution: "minipass-pipeline@npm:1.2.4" 1786 | dependencies: 1787 | minipass: "npm:^3.0.0" 1788 | checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 1789 | languageName: node 1790 | linkType: hard 1791 | 1792 | "minipass-sized@npm:^1.0.3": 1793 | version: 1.0.3 1794 | resolution: "minipass-sized@npm:1.0.3" 1795 | dependencies: 1796 | minipass: "npm:^3.0.0" 1797 | checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb 1798 | languageName: node 1799 | linkType: hard 1800 | 1801 | "minipass@npm:^3.0.0": 1802 | version: 3.3.6 1803 | resolution: "minipass@npm:3.3.6" 1804 | dependencies: 1805 | yallist: "npm:^4.0.0" 1806 | checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c 1807 | languageName: node 1808 | linkType: hard 1809 | 1810 | "minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": 1811 | version: 7.1.2 1812 | resolution: "minipass@npm:7.1.2" 1813 | checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 1814 | languageName: node 1815 | linkType: hard 1816 | 1817 | "minizlib@npm:^3.0.1": 1818 | version: 3.0.2 1819 | resolution: "minizlib@npm:3.0.2" 1820 | dependencies: 1821 | minipass: "npm:^7.1.2" 1822 | checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 1823 | languageName: node 1824 | linkType: hard 1825 | 1826 | "mkdirp@npm:^3.0.1": 1827 | version: 3.0.1 1828 | resolution: "mkdirp@npm:3.0.1" 1829 | bin: 1830 | mkdirp: dist/cjs/src/bin.js 1831 | checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d 1832 | languageName: node 1833 | linkType: hard 1834 | 1835 | "mocha@npm:11.1.0": 1836 | version: 11.1.0 1837 | resolution: "mocha@npm:11.1.0" 1838 | dependencies: 1839 | ansi-colors: "npm:^4.1.3" 1840 | browser-stdout: "npm:^1.3.1" 1841 | chokidar: "npm:^3.5.3" 1842 | debug: "npm:^4.3.5" 1843 | diff: "npm:^5.2.0" 1844 | escape-string-regexp: "npm:^4.0.0" 1845 | find-up: "npm:^5.0.0" 1846 | glob: "npm:^10.4.5" 1847 | he: "npm:^1.2.0" 1848 | js-yaml: "npm:^4.1.0" 1849 | log-symbols: "npm:^4.1.0" 1850 | minimatch: "npm:^5.1.6" 1851 | ms: "npm:^2.1.3" 1852 | serialize-javascript: "npm:^6.0.2" 1853 | strip-json-comments: "npm:^3.1.1" 1854 | supports-color: "npm:^8.1.1" 1855 | workerpool: "npm:^6.5.1" 1856 | yargs: "npm:^17.7.2" 1857 | yargs-parser: "npm:^21.1.1" 1858 | yargs-unparser: "npm:^2.0.0" 1859 | bin: 1860 | _mocha: bin/_mocha 1861 | mocha: bin/mocha.js 1862 | checksum: 10c0/46e063fb014bef8c7f290094325ee2666ef9f63a918573f5278781631d4b3d04e45abe35f776307ff19e837bc2b42e4f2a7c60c53b69517539890636cf8e49ec 1863 | languageName: node 1864 | linkType: hard 1865 | 1866 | "ms@npm:^2.1.3": 1867 | version: 2.1.3 1868 | resolution: "ms@npm:2.1.3" 1869 | checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 1870 | languageName: node 1871 | linkType: hard 1872 | 1873 | "nano-spawn@npm:^1.0.2": 1874 | version: 1.0.2 1875 | resolution: "nano-spawn@npm:1.0.2" 1876 | checksum: 10c0/d8cec78f127a44aa5e38be01746b3d963a8dcf8b00b4a05bf259b5369af2225b8c7dc9d12517050b90234e5c3eeea4ece5d18a5f9c6c3462b56f9f595f07e632 1877 | languageName: node 1878 | linkType: hard 1879 | 1880 | "natural-compare@npm:^1.4.0": 1881 | version: 1.4.0 1882 | resolution: "natural-compare@npm:1.4.0" 1883 | checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 1884 | languageName: node 1885 | linkType: hard 1886 | 1887 | "negotiator@npm:^1.0.0": 1888 | version: 1.0.0 1889 | resolution: "negotiator@npm:1.0.0" 1890 | checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b 1891 | languageName: node 1892 | linkType: hard 1893 | 1894 | "node-gyp@npm:latest": 1895 | version: 11.2.0 1896 | resolution: "node-gyp@npm:11.2.0" 1897 | dependencies: 1898 | env-paths: "npm:^2.2.0" 1899 | exponential-backoff: "npm:^3.1.1" 1900 | graceful-fs: "npm:^4.2.6" 1901 | make-fetch-happen: "npm:^14.0.3" 1902 | nopt: "npm:^8.0.0" 1903 | proc-log: "npm:^5.0.0" 1904 | semver: "npm:^7.3.5" 1905 | tar: "npm:^7.4.3" 1906 | tinyglobby: "npm:^0.2.12" 1907 | which: "npm:^5.0.0" 1908 | bin: 1909 | node-gyp: bin/node-gyp.js 1910 | checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9 1911 | languageName: node 1912 | linkType: hard 1913 | 1914 | "nopt@npm:^8.0.0": 1915 | version: 8.1.0 1916 | resolution: "nopt@npm:8.1.0" 1917 | dependencies: 1918 | abbrev: "npm:^3.0.0" 1919 | bin: 1920 | nopt: bin/nopt.js 1921 | checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef 1922 | languageName: node 1923 | linkType: hard 1924 | 1925 | "normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": 1926 | version: 3.0.0 1927 | resolution: "normalize-path@npm:3.0.0" 1928 | checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 1929 | languageName: node 1930 | linkType: hard 1931 | 1932 | "onetime@npm:^7.0.0": 1933 | version: 7.0.0 1934 | resolution: "onetime@npm:7.0.0" 1935 | dependencies: 1936 | mimic-function: "npm:^5.0.0" 1937 | checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 1938 | languageName: node 1939 | linkType: hard 1940 | 1941 | "optionator@npm:^0.9.3": 1942 | version: 0.9.4 1943 | resolution: "optionator@npm:0.9.4" 1944 | dependencies: 1945 | deep-is: "npm:^0.1.3" 1946 | fast-levenshtein: "npm:^2.0.6" 1947 | levn: "npm:^0.4.1" 1948 | prelude-ls: "npm:^1.2.1" 1949 | type-check: "npm:^0.4.0" 1950 | word-wrap: "npm:^1.2.5" 1951 | checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 1952 | languageName: node 1953 | linkType: hard 1954 | 1955 | "p-limit@npm:^3.0.2": 1956 | version: 3.1.0 1957 | resolution: "p-limit@npm:3.1.0" 1958 | dependencies: 1959 | yocto-queue: "npm:^0.1.0" 1960 | checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a 1961 | languageName: node 1962 | linkType: hard 1963 | 1964 | "p-locate@npm:^5.0.0": 1965 | version: 5.0.0 1966 | resolution: "p-locate@npm:5.0.0" 1967 | dependencies: 1968 | p-limit: "npm:^3.0.2" 1969 | checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a 1970 | languageName: node 1971 | linkType: hard 1972 | 1973 | "p-map@npm:^7.0.2": 1974 | version: 7.0.3 1975 | resolution: "p-map@npm:7.0.3" 1976 | checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c 1977 | languageName: node 1978 | linkType: hard 1979 | 1980 | "package-json-from-dist@npm:^1.0.0": 1981 | version: 1.0.1 1982 | resolution: "package-json-from-dist@npm:1.0.1" 1983 | checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b 1984 | languageName: node 1985 | linkType: hard 1986 | 1987 | "parent-module@npm:^1.0.0": 1988 | version: 1.0.1 1989 | resolution: "parent-module@npm:1.0.1" 1990 | dependencies: 1991 | callsites: "npm:^3.0.0" 1992 | checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 1993 | languageName: node 1994 | linkType: hard 1995 | 1996 | "path-exists@npm:^4.0.0": 1997 | version: 4.0.0 1998 | resolution: "path-exists@npm:4.0.0" 1999 | checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b 2000 | languageName: node 2001 | linkType: hard 2002 | 2003 | "path-key@npm:^3.1.0": 2004 | version: 3.1.1 2005 | resolution: "path-key@npm:3.1.1" 2006 | checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c 2007 | languageName: node 2008 | linkType: hard 2009 | 2010 | "path-scurry@npm:^1.11.1": 2011 | version: 1.11.1 2012 | resolution: "path-scurry@npm:1.11.1" 2013 | dependencies: 2014 | lru-cache: "npm:^10.2.0" 2015 | minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" 2016 | checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d 2017 | languageName: node 2018 | linkType: hard 2019 | 2020 | "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": 2021 | version: 2.3.1 2022 | resolution: "picomatch@npm:2.3.1" 2023 | checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be 2024 | languageName: node 2025 | linkType: hard 2026 | 2027 | "picomatch@npm:^4.0.2": 2028 | version: 4.0.2 2029 | resolution: "picomatch@npm:4.0.2" 2030 | checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc 2031 | languageName: node 2032 | linkType: hard 2033 | 2034 | "pidtree@npm:^0.6.0": 2035 | version: 0.6.0 2036 | resolution: "pidtree@npm:0.6.0" 2037 | bin: 2038 | pidtree: bin/pidtree.js 2039 | checksum: 10c0/0829ec4e9209e230f74ebf4265f5ccc9ebfb488334b525cb13f86ff801dca44b362c41252cd43ae4d7653a10a5c6ab3be39d2c79064d6895e0d78dc50a5ed6e9 2040 | languageName: node 2041 | linkType: hard 2042 | 2043 | "prelude-ls@npm:^1.2.1": 2044 | version: 1.2.1 2045 | resolution: "prelude-ls@npm:1.2.1" 2046 | checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd 2047 | languageName: node 2048 | linkType: hard 2049 | 2050 | "prettier@npm:^3.5.3": 2051 | version: 3.5.3 2052 | resolution: "prettier@npm:3.5.3" 2053 | bin: 2054 | prettier: bin/prettier.cjs 2055 | checksum: 10c0/3880cb90b9dc0635819ab52ff571518c35bd7f15a6e80a2054c05dbc8a3aa6e74f135519e91197de63705bcb38388ded7e7230e2178432a1468005406238b877 2056 | languageName: node 2057 | linkType: hard 2058 | 2059 | "proc-log@npm:^5.0.0": 2060 | version: 5.0.0 2061 | resolution: "proc-log@npm:5.0.0" 2062 | checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 2063 | languageName: node 2064 | linkType: hard 2065 | 2066 | "promise-retry@npm:^2.0.1": 2067 | version: 2.0.1 2068 | resolution: "promise-retry@npm:2.0.1" 2069 | dependencies: 2070 | err-code: "npm:^2.0.2" 2071 | retry: "npm:^0.12.0" 2072 | checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 2073 | languageName: node 2074 | linkType: hard 2075 | 2076 | "punycode@npm:^2.1.0": 2077 | version: 2.3.1 2078 | resolution: "punycode@npm:2.3.1" 2079 | checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 2080 | languageName: node 2081 | linkType: hard 2082 | 2083 | "randombytes@npm:^2.1.0": 2084 | version: 2.1.0 2085 | resolution: "randombytes@npm:2.1.0" 2086 | dependencies: 2087 | safe-buffer: "npm:^5.1.0" 2088 | checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 2089 | languageName: node 2090 | linkType: hard 2091 | 2092 | "readdirp@npm:~3.6.0": 2093 | version: 3.6.0 2094 | resolution: "readdirp@npm:3.6.0" 2095 | dependencies: 2096 | picomatch: "npm:^2.2.1" 2097 | checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b 2098 | languageName: node 2099 | linkType: hard 2100 | 2101 | "require-directory@npm:^2.1.1": 2102 | version: 2.1.1 2103 | resolution: "require-directory@npm:2.1.1" 2104 | checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 2105 | languageName: node 2106 | linkType: hard 2107 | 2108 | "resolve-from@npm:^4.0.0": 2109 | version: 4.0.0 2110 | resolution: "resolve-from@npm:4.0.0" 2111 | checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 2112 | languageName: node 2113 | linkType: hard 2114 | 2115 | "resolve-pkg-maps@npm:^1.0.0": 2116 | version: 1.0.0 2117 | resolution: "resolve-pkg-maps@npm:1.0.0" 2118 | checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab 2119 | languageName: node 2120 | linkType: hard 2121 | 2122 | "restore-cursor@npm:^5.0.0": 2123 | version: 5.1.0 2124 | resolution: "restore-cursor@npm:5.1.0" 2125 | dependencies: 2126 | onetime: "npm:^7.0.0" 2127 | signal-exit: "npm:^4.1.0" 2128 | checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 2129 | languageName: node 2130 | linkType: hard 2131 | 2132 | "retry@npm:^0.12.0": 2133 | version: 0.12.0 2134 | resolution: "retry@npm:0.12.0" 2135 | checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe 2136 | languageName: node 2137 | linkType: hard 2138 | 2139 | "rfdc@npm:^1.4.1": 2140 | version: 1.4.1 2141 | resolution: "rfdc@npm:1.4.1" 2142 | checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 2143 | languageName: node 2144 | linkType: hard 2145 | 2146 | "safe-buffer@npm:^5.1.0": 2147 | version: 5.2.1 2148 | resolution: "safe-buffer@npm:5.2.1" 2149 | checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 2150 | languageName: node 2151 | linkType: hard 2152 | 2153 | "safer-buffer@npm:>= 2.1.2 < 3.0.0": 2154 | version: 2.1.2 2155 | resolution: "safer-buffer@npm:2.1.2" 2156 | checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 2157 | languageName: node 2158 | linkType: hard 2159 | 2160 | "semver@npm:^7.3.5, semver@npm:^7.5.4, semver@npm:^7.6.3": 2161 | version: 7.7.1 2162 | resolution: "semver@npm:7.7.1" 2163 | bin: 2164 | semver: bin/semver.js 2165 | checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 2166 | languageName: node 2167 | linkType: hard 2168 | 2169 | "serialize-javascript@npm:^6.0.2": 2170 | version: 6.0.2 2171 | resolution: "serialize-javascript@npm:6.0.2" 2172 | dependencies: 2173 | randombytes: "npm:^2.1.0" 2174 | checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 2175 | languageName: node 2176 | linkType: hard 2177 | 2178 | "shebang-command@npm:^2.0.0": 2179 | version: 2.0.0 2180 | resolution: "shebang-command@npm:2.0.0" 2181 | dependencies: 2182 | shebang-regex: "npm:^3.0.0" 2183 | checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e 2184 | languageName: node 2185 | linkType: hard 2186 | 2187 | "shebang-regex@npm:^3.0.0": 2188 | version: 3.0.0 2189 | resolution: "shebang-regex@npm:3.0.0" 2190 | checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 2191 | languageName: node 2192 | linkType: hard 2193 | 2194 | "signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": 2195 | version: 4.1.0 2196 | resolution: "signal-exit@npm:4.1.0" 2197 | checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 2198 | languageName: node 2199 | linkType: hard 2200 | 2201 | "simple-git-hooks@npm:^2.13.0": 2202 | version: 2.13.0 2203 | resolution: "simple-git-hooks@npm:2.13.0" 2204 | bin: 2205 | simple-git-hooks: cli.js 2206 | checksum: 10c0/aebd7c36711d6805c1ecb26a53203c259d0587fdd214a650217dccd57a42fd808075e3f92826c5d4838b3e68870f5561820072a5989f039cb7659742421d3ceb 2207 | languageName: node 2208 | linkType: hard 2209 | 2210 | "slice-ansi@npm:^5.0.0": 2211 | version: 5.0.0 2212 | resolution: "slice-ansi@npm:5.0.0" 2213 | dependencies: 2214 | ansi-styles: "npm:^6.0.0" 2215 | is-fullwidth-code-point: "npm:^4.0.0" 2216 | checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f 2217 | languageName: node 2218 | linkType: hard 2219 | 2220 | "slice-ansi@npm:^7.1.0": 2221 | version: 7.1.0 2222 | resolution: "slice-ansi@npm:7.1.0" 2223 | dependencies: 2224 | ansi-styles: "npm:^6.2.1" 2225 | is-fullwidth-code-point: "npm:^5.0.0" 2226 | checksum: 10c0/631c971d4abf56cf880f034d43fcc44ff883624867bf11ecbd538c47343911d734a4656d7bc02362b40b89d765652a7f935595441e519b59e2ad3f4d5d6fe7ca 2227 | languageName: node 2228 | linkType: hard 2229 | 2230 | "smart-buffer@npm:^4.2.0": 2231 | version: 4.2.0 2232 | resolution: "smart-buffer@npm:4.2.0" 2233 | checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 2234 | languageName: node 2235 | linkType: hard 2236 | 2237 | "socks-proxy-agent@npm:^8.0.3": 2238 | version: 8.0.5 2239 | resolution: "socks-proxy-agent@npm:8.0.5" 2240 | dependencies: 2241 | agent-base: "npm:^7.1.2" 2242 | debug: "npm:^4.3.4" 2243 | socks: "npm:^2.8.3" 2244 | checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 2245 | languageName: node 2246 | linkType: hard 2247 | 2248 | "socks@npm:^2.8.3": 2249 | version: 2.8.4 2250 | resolution: "socks@npm:2.8.4" 2251 | dependencies: 2252 | ip-address: "npm:^9.0.5" 2253 | smart-buffer: "npm:^4.2.0" 2254 | checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5 2255 | languageName: node 2256 | linkType: hard 2257 | 2258 | "sprintf-js@npm:^1.1.3": 2259 | version: 1.1.3 2260 | resolution: "sprintf-js@npm:1.1.3" 2261 | checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec 2262 | languageName: node 2263 | linkType: hard 2264 | 2265 | "ssri@npm:^12.0.0": 2266 | version: 12.0.0 2267 | resolution: "ssri@npm:12.0.0" 2268 | dependencies: 2269 | minipass: "npm:^7.0.3" 2270 | checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d 2271 | languageName: node 2272 | linkType: hard 2273 | 2274 | "string-argv@npm:^0.3.2": 2275 | version: 0.3.2 2276 | resolution: "string-argv@npm:0.3.2" 2277 | checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 2278 | languageName: node 2279 | linkType: hard 2280 | 2281 | "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": 2282 | version: 4.2.3 2283 | resolution: "string-width@npm:4.2.3" 2284 | dependencies: 2285 | emoji-regex: "npm:^8.0.0" 2286 | is-fullwidth-code-point: "npm:^3.0.0" 2287 | strip-ansi: "npm:^6.0.1" 2288 | checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b 2289 | languageName: node 2290 | linkType: hard 2291 | 2292 | "string-width@npm:^5.0.1, string-width@npm:^5.1.2": 2293 | version: 5.1.2 2294 | resolution: "string-width@npm:5.1.2" 2295 | dependencies: 2296 | eastasianwidth: "npm:^0.2.0" 2297 | emoji-regex: "npm:^9.2.2" 2298 | strip-ansi: "npm:^7.0.1" 2299 | checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca 2300 | languageName: node 2301 | linkType: hard 2302 | 2303 | "string-width@npm:^7.0.0": 2304 | version: 7.2.0 2305 | resolution: "string-width@npm:7.2.0" 2306 | dependencies: 2307 | emoji-regex: "npm:^10.3.0" 2308 | get-east-asian-width: "npm:^1.0.0" 2309 | strip-ansi: "npm:^7.1.0" 2310 | checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 2311 | languageName: node 2312 | linkType: hard 2313 | 2314 | "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": 2315 | version: 6.0.1 2316 | resolution: "strip-ansi@npm:6.0.1" 2317 | dependencies: 2318 | ansi-regex: "npm:^5.0.1" 2319 | checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 2320 | languageName: node 2321 | linkType: hard 2322 | 2323 | "strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": 2324 | version: 7.1.0 2325 | resolution: "strip-ansi@npm:7.1.0" 2326 | dependencies: 2327 | ansi-regex: "npm:^6.0.1" 2328 | checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 2329 | languageName: node 2330 | linkType: hard 2331 | 2332 | "strip-json-comments@npm:^3.1.1": 2333 | version: 3.1.1 2334 | resolution: "strip-json-comments@npm:3.1.1" 2335 | checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd 2336 | languageName: node 2337 | linkType: hard 2338 | 2339 | "supports-color@npm:^7.1.0": 2340 | version: 7.2.0 2341 | resolution: "supports-color@npm:7.2.0" 2342 | dependencies: 2343 | has-flag: "npm:^4.0.0" 2344 | checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 2345 | languageName: node 2346 | linkType: hard 2347 | 2348 | "supports-color@npm:^8.1.1": 2349 | version: 8.1.1 2350 | resolution: "supports-color@npm:8.1.1" 2351 | dependencies: 2352 | has-flag: "npm:^4.0.0" 2353 | checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 2354 | languageName: node 2355 | linkType: hard 2356 | 2357 | "tapable@npm:^2.2.0": 2358 | version: 2.2.1 2359 | resolution: "tapable@npm:2.2.1" 2360 | checksum: 10c0/bc40e6efe1e554d075469cedaba69a30eeb373552aaf41caeaaa45bf56ffacc2674261b106245bd566b35d8f3329b52d838e851ee0a852120acae26e622925c9 2361 | languageName: node 2362 | linkType: hard 2363 | 2364 | "tar@npm:^7.4.3": 2365 | version: 7.4.3 2366 | resolution: "tar@npm:7.4.3" 2367 | dependencies: 2368 | "@isaacs/fs-minipass": "npm:^4.0.0" 2369 | chownr: "npm:^3.0.0" 2370 | minipass: "npm:^7.1.2" 2371 | minizlib: "npm:^3.0.1" 2372 | mkdirp: "npm:^3.0.1" 2373 | yallist: "npm:^5.0.0" 2374 | checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d 2375 | languageName: node 2376 | linkType: hard 2377 | 2378 | "tinyglobby@npm:^0.2.12": 2379 | version: 0.2.13 2380 | resolution: "tinyglobby@npm:0.2.13" 2381 | dependencies: 2382 | fdir: "npm:^6.4.4" 2383 | picomatch: "npm:^4.0.2" 2384 | checksum: 10c0/ef07dfaa7b26936601d3f6d999f7928a4d1c6234c5eb36896bb88681947c0d459b7ebe797022400e555fe4b894db06e922b95d0ce60cb05fd827a0a66326b18c 2385 | languageName: node 2386 | linkType: hard 2387 | 2388 | "to-regex-range@npm:^5.0.1": 2389 | version: 5.0.1 2390 | resolution: "to-regex-range@npm:5.0.1" 2391 | dependencies: 2392 | is-number: "npm:^7.0.0" 2393 | checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 2394 | languageName: node 2395 | linkType: hard 2396 | 2397 | "type-check@npm:^0.4.0, type-check@npm:~0.4.0": 2398 | version: 0.4.0 2399 | resolution: "type-check@npm:0.4.0" 2400 | dependencies: 2401 | prelude-ls: "npm:^1.2.1" 2402 | checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 2403 | languageName: node 2404 | linkType: hard 2405 | 2406 | "unique-filename@npm:^4.0.0": 2407 | version: 4.0.0 2408 | resolution: "unique-filename@npm:4.0.0" 2409 | dependencies: 2410 | unique-slug: "npm:^5.0.0" 2411 | checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc 2412 | languageName: node 2413 | linkType: hard 2414 | 2415 | "unique-slug@npm:^5.0.0": 2416 | version: 5.0.0 2417 | resolution: "unique-slug@npm:5.0.0" 2418 | dependencies: 2419 | imurmurhash: "npm:^0.1.4" 2420 | checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 2421 | languageName: node 2422 | linkType: hard 2423 | 2424 | "uri-js@npm:^4.2.2": 2425 | version: 4.4.1 2426 | resolution: "uri-js@npm:4.4.1" 2427 | dependencies: 2428 | punycode: "npm:^2.1.0" 2429 | checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c 2430 | languageName: node 2431 | linkType: hard 2432 | 2433 | "which@npm:^2.0.1": 2434 | version: 2.0.2 2435 | resolution: "which@npm:2.0.2" 2436 | dependencies: 2437 | isexe: "npm:^2.0.0" 2438 | bin: 2439 | node-which: ./bin/node-which 2440 | checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f 2441 | languageName: node 2442 | linkType: hard 2443 | 2444 | "which@npm:^5.0.0": 2445 | version: 5.0.0 2446 | resolution: "which@npm:5.0.0" 2447 | dependencies: 2448 | isexe: "npm:^3.1.1" 2449 | bin: 2450 | node-which: bin/which.js 2451 | checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b 2452 | languageName: node 2453 | linkType: hard 2454 | 2455 | "word-wrap@npm:^1.2.5": 2456 | version: 1.2.5 2457 | resolution: "word-wrap@npm:1.2.5" 2458 | checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 2459 | languageName: node 2460 | linkType: hard 2461 | 2462 | "workerpool@npm:^6.5.1": 2463 | version: 6.5.1 2464 | resolution: "workerpool@npm:6.5.1" 2465 | checksum: 10c0/58e8e969782292cb3a7bfba823f1179a7615250a0cefb4841d5166234db1880a3d0fe83a31dd8d648329ec92c2d0cd1890ad9ec9e53674bb36ca43e9753cdeac 2466 | languageName: node 2467 | linkType: hard 2468 | 2469 | "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": 2470 | version: 7.0.0 2471 | resolution: "wrap-ansi@npm:7.0.0" 2472 | dependencies: 2473 | ansi-styles: "npm:^4.0.0" 2474 | string-width: "npm:^4.1.0" 2475 | strip-ansi: "npm:^6.0.0" 2476 | checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da 2477 | languageName: node 2478 | linkType: hard 2479 | 2480 | "wrap-ansi@npm:^8.1.0": 2481 | version: 8.1.0 2482 | resolution: "wrap-ansi@npm:8.1.0" 2483 | dependencies: 2484 | ansi-styles: "npm:^6.1.0" 2485 | string-width: "npm:^5.0.1" 2486 | strip-ansi: "npm:^7.0.1" 2487 | checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 2488 | languageName: node 2489 | linkType: hard 2490 | 2491 | "wrap-ansi@npm:^9.0.0": 2492 | version: 9.0.0 2493 | resolution: "wrap-ansi@npm:9.0.0" 2494 | dependencies: 2495 | ansi-styles: "npm:^6.2.1" 2496 | string-width: "npm:^7.0.0" 2497 | strip-ansi: "npm:^7.1.0" 2498 | checksum: 10c0/a139b818da9573677548dd463bd626a5a5286271211eb6e4e82f34a4f643191d74e6d4a9bb0a3c26ec90e6f904f679e0569674ac099ea12378a8b98e20706066 2499 | languageName: node 2500 | linkType: hard 2501 | 2502 | "y18n@npm:^5.0.5": 2503 | version: 5.0.8 2504 | resolution: "y18n@npm:5.0.8" 2505 | checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 2506 | languageName: node 2507 | linkType: hard 2508 | 2509 | "yallist@npm:^4.0.0": 2510 | version: 4.0.0 2511 | resolution: "yallist@npm:4.0.0" 2512 | checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a 2513 | languageName: node 2514 | linkType: hard 2515 | 2516 | "yallist@npm:^5.0.0": 2517 | version: 5.0.0 2518 | resolution: "yallist@npm:5.0.0" 2519 | checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 2520 | languageName: node 2521 | linkType: hard 2522 | 2523 | "yaml@npm:^2.8.0": 2524 | version: 2.8.0 2525 | resolution: "yaml@npm:2.8.0" 2526 | bin: 2527 | yaml: bin.mjs 2528 | checksum: 10c0/f6f7310cf7264a8107e72c1376f4de37389945d2fb4656f8060eca83f01d2d703f9d1b925dd8f39852a57034fafefde6225409ddd9f22aebfda16c6141b71858 2529 | languageName: node 2530 | linkType: hard 2531 | 2532 | "yargs-parser@npm:^21.1.1": 2533 | version: 21.1.1 2534 | resolution: "yargs-parser@npm:21.1.1" 2535 | checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 2536 | languageName: node 2537 | linkType: hard 2538 | 2539 | "yargs-unparser@npm:^2.0.0": 2540 | version: 2.0.0 2541 | resolution: "yargs-unparser@npm:2.0.0" 2542 | dependencies: 2543 | camelcase: "npm:^6.0.0" 2544 | decamelize: "npm:^4.0.0" 2545 | flat: "npm:^5.0.2" 2546 | is-plain-obj: "npm:^2.1.0" 2547 | checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 2548 | languageName: node 2549 | linkType: hard 2550 | 2551 | "yargs@npm:^17.7.2": 2552 | version: 17.7.2 2553 | resolution: "yargs@npm:17.7.2" 2554 | dependencies: 2555 | cliui: "npm:^8.0.1" 2556 | escalade: "npm:^3.1.1" 2557 | get-caller-file: "npm:^2.0.5" 2558 | require-directory: "npm:^2.1.1" 2559 | string-width: "npm:^4.2.3" 2560 | y18n: "npm:^5.0.5" 2561 | yargs-parser: "npm:^21.1.1" 2562 | checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 2563 | languageName: node 2564 | linkType: hard 2565 | 2566 | "yocto-queue@npm:^0.1.0": 2567 | version: 0.1.0 2568 | resolution: "yocto-queue@npm:0.1.0" 2569 | checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f 2570 | languageName: node 2571 | linkType: hard 2572 | --------------------------------------------------------------------------------