├── .github
└── dependabot.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.yaml
├── LICENSE
├── README.md
├── babel.config.json
├── dist
└── cjs
│ ├── index.cjs
│ ├── package.json.cjs
│ └── rules
│ └── no-unused-deps.cjs
├── eslint.config.mjs
├── examples
├── eslint.config.mjs
├── index.js
├── package-lock.json
└── package.json
├── index.ts
├── package-lock.json
├── package.json
├── rollup.config.mjs
├── rules
└── no-unused-deps.ts
├── tests
└── rules
│ └── no-unused-deps.js
├── tsconfig.json
└── types
├── index.d.ts
└── rules
└── no-unused-deps.d.ts
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: 'npm'
9 | directory: '/'
10 | versioning-strategy: increase
11 | schedule:
12 | interval: 'daily'
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # builds
5 | build
6 |
7 | # tests
8 | coverage
9 |
10 | # logs
11 | logs
12 | *.log
13 |
14 | # misc
15 | .DS_Store
16 | .npm
17 | .env
18 | .eslintcache
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | types/
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | trailingComma: none
2 | singleQuote: true
3 | printWidth: 90
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Zheng Song
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## eslint-plugin-react-hooks-addons
2 |
3 | > ESLint rule to check unused and potentially unnecessary dependencies in React Hooks.
4 |
5 | [](https://www.npmjs.com/package/eslint-plugin-react-hooks-addons)
6 | [](https://www.npmjs.com/package/eslint-plugin-react-hooks-addons)
7 |
8 | ## Why?
9 |
10 | [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) is awesome for linting the dependency array of React Hooks. But it doesn't do one thing: unused dependencies in the `useEffect` or `useLayoutEffect` hooks are not reported. Unused variables in `useEffect`'s dependency array are perfectly valid in some use cases. However, they might be unnecessary in some other cases which cause the effect hook to run unintentionally.
11 |
12 | Take the following code as an example:
13 |
14 | ```js
15 | const [user1, setUser1] = useState();
16 | const [user2, setUser2] = useState();
17 |
18 | useEffect(() => {
19 | fetch(`someUrl/${user1}`).then(/* ... */);
20 | fetch(`someUrl/${user2}`).then(/* ... */);
21 | }, [user1, user2]);
22 | ```
23 |
24 | Next day you update the code and remove the second `fetch` but forget to remove `user2` from the dependency array:
25 |
26 | ```js
27 | const [user1, setUser1] = useState();
28 | const [user2, setUser2] = useState();
29 |
30 | useEffect(() => {
31 | fetch(`someUrl/${user1}`).then(/* ... */);
32 | }, [user1, user2]);
33 | ```
34 |
35 | Then the `useEffect` will run whenever `user1` or `user2` changes, which is probably not your intention. Similar errors occur more frequently when the hook callback function is large and there is a long dependency array. This eslint plugin checks and reports this kind of error.
36 |
37 | **What if I have a value which is not used in the hook function scope but I want the effect hook to run whenever that value has changed?**
38 |
39 |
40 | You could prepend a `/* effect dep */` comment to the value in dependency array then it will be skipped during linting. It brings an addition benefit: the value is explicitly marked as effectful so that other people coming across the code will understand it's not a programmatic error.
41 |
42 | ```diff
43 | useEffect(() => {
44 | fetch(`someUrl/${user1}`).then(/* ... */);
45 | - }, [user1, user2]);
46 | + }, [user1, /* effect dep */ user2]);
47 | ```
48 |
49 | ## Install
50 |
51 | with npm
52 |
53 | ```bash
54 | npm install -D eslint-plugin-react-hooks-addons
55 | ```
56 |
57 | or with Yarn
58 |
59 | ```bash
60 | yarn add -D eslint-plugin-react-hooks-addons
61 | ```
62 |
63 | ## Usage
64 |
65 | ### Flat config
66 |
67 | ```js
68 | import reactHooksAddons from 'eslint-plugin-react-hooks-addons';
69 |
70 | export default [
71 | reactHooksAddons.configs.recommended
72 | // other configs...
73 | ];
74 | ```
75 |
76 | Or, use a custom configuration:
77 |
78 | ```js
79 | import reactHooksAddons from 'eslint-plugin-react-hooks-addons';
80 |
81 | export default [
82 | // other configs...
83 | {
84 | plugins: {
85 | 'react-hooks-addons': reactHooksAddons
86 | },
87 | rules: {
88 | 'react-hooks-addons/no-unused-deps': 'warn'
89 | }
90 | }
91 | ];
92 | ```
93 |
94 | ### Legacy config
95 |
96 | ```json
97 | {
98 | "extends": ["plugin:react-hooks-addons/recommended-legacy"]
99 | }
100 | ```
101 |
102 | Or, use a custom configuration:
103 |
104 | ```json
105 | {
106 | "plugins": ["react-hooks-addons"],
107 | "rules": {
108 | "react-hooks-addons/no-unused-deps": "warn"
109 | }
110 | }
111 | ```
112 |
113 | ### Effect deps
114 |
115 | Explicitly mark a dependency as effectful with `/* effect dep */` comment:
116 |
117 | ```js
118 | useEffect(() => {
119 | // ...
120 | }, [unusedVar, /* effect dep */ effectVar]);
121 | ```
122 |
123 | Then only the `unusedVar` will be reported as an unused dependency.
124 |
125 | ### Options
126 |
127 | #### `effectComment`
128 |
129 | You can use a different comment to mark dependencies as effectful:
130 |
131 | ```json
132 | "rules": {
133 | "react-hooks-addons/no-unused-deps": [
134 | "warn",
135 | {
136 | "effectComment": "effectful"
137 | }
138 | ]
139 | }
140 | ```
141 |
142 | #### `additionalHooks`
143 |
144 | The rule checks `useEffect` and `useLayoutEffect` hooks by default. It can be configured to check dependencies of custom hooks with the `additionalHooks` option. This option accepts a `pattern` key which is a regex pattern. If you set the `replace` key to `true`, it would replace the default hooks.
145 |
146 | ```json
147 | "rules": {
148 | "react-hooks-addons/no-unused-deps": [
149 | "warn",
150 | {
151 | "additionalHooks": {
152 | "pattern": "useMyCustomHook|useMyOtherCustomHook",
153 | "replace": true
154 | }
155 | }
156 | ]
157 | }
158 | ```
159 |
160 | _Note: this eslint plugin is supposed to work in tandem with [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks), as it doesn't check things that have already been reported by that plugin._
161 |
162 | ## License
163 |
164 | [MIT](https://github.com/szhsin/eslint-plugin-react-hooks-addons/blob/master/LICENSE) Licensed.
165 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "assumptions": {
3 | "noDocumentAll": true
4 | },
5 | "presets": [
6 | [
7 | "@babel/preset-env",
8 | {
9 | "targets": {
10 | "node": "8"
11 | },
12 | "include": ["@babel/plugin-transform-nullish-coalescing-operator"]
13 | }
14 | ],
15 | "@babel/preset-typescript"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/dist/cjs/index.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _package = require('./package.json.cjs');
4 | var noUnusedDeps = require('./rules/no-unused-deps.cjs');
5 |
6 | const rules = {
7 | 'react-hooks-addons/no-unused-deps': 'error'
8 | };
9 | const plugin = {
10 | meta: {
11 | name: _package.name,
12 | version: _package.version
13 | },
14 | configs: {
15 | recommended: {},
16 | 'recommended-legacy': {}
17 | },
18 | rules: {
19 | 'no-unused-deps': noUnusedDeps
20 | }
21 | };
22 | plugin.configs.recommended = {
23 | plugins: {
24 | 'react-hooks-addons': plugin
25 | },
26 | rules
27 | };
28 | plugin.configs['recommended-legacy'] = {
29 | plugins: ['react-hooks-addons'],
30 | rules
31 | };
32 |
33 | module.exports = plugin;
34 |
--------------------------------------------------------------------------------
/dist/cjs/package.json.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _package = {
4 | name: "eslint-plugin-react-hooks-addons",
5 | version: "0.5.0"};
6 |
7 | module.exports = _package;
8 |
--------------------------------------------------------------------------------
/dist/cjs/rules/no-unused-deps.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @fileoverview Rule to check unused dependencies in React Hooks
5 | * @author Zheng Song
6 | */
7 |
8 | const reactNamespace = 'React';
9 | const hookNames = ['useEffect', 'useLayoutEffect'];
10 | const matchHooks = (name, {
11 | pattern,
12 | replace
13 | } = {}) => {
14 | let match = false;
15 | if (pattern) {
16 | match = new RegExp(pattern).test(name);
17 | if (replace) return match;
18 | }
19 | return match || hookNames.includes(name);
20 | };
21 | const rule = {
22 | meta: {
23 | type: 'problem',
24 | schema: [{
25 | type: 'object',
26 | properties: {
27 | effectComment: {
28 | type: 'string'
29 | },
30 | additionalHooks: {
31 | type: 'object',
32 | properties: {
33 | pattern: {
34 | type: 'string'
35 | },
36 | replace: {
37 | type: 'boolean'
38 | }
39 | },
40 | required: ['pattern'],
41 | additionalProperties: false
42 | }
43 | },
44 | additionalProperties: false
45 | }],
46 | messages: {
47 | unused: 'React Hook {{ hook }} has unused dependencies: {{ unusedDeps }}. They might cause the Hook to run unintentionally. Either exclude them or prepend /* {{ effectComment }} */ comments to make the intention explicit.'
48 | }
49 | },
50 | create(context) {
51 | var _context$sourceCode;
52 | const {
53 | effectComment = 'effect dep',
54 | additionalHooks
55 | } = context.options[0] || {};
56 | const sourceCode = (_context$sourceCode = context.sourceCode) != null ? _context$sourceCode : context.getSourceCode();
57 | const nodeListener = node => {
58 | const {
59 | parent
60 | } = node;
61 | if (parent.type !== 'CallExpression' || parent.arguments.length < 2 || parent.arguments[1].type !== 'ArrayExpression') {
62 | return;
63 | }
64 | const {
65 | callee
66 | } = parent;
67 | let hookName;
68 | switch (callee.type) {
69 | case 'Identifier':
70 | hookName = callee.name;
71 | break;
72 | case 'MemberExpression':
73 | if (callee.object.type === 'Identifier' && callee.object.name === reactNamespace && callee.property.type === 'Identifier') {
74 | hookName = callee.property.name;
75 | }
76 | break;
77 | }
78 | if (!hookName || !matchHooks(hookName, additionalHooks)) return;
79 | const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope();
80 | const through = scope.through.map(r => r.identifier.name);
81 | const depArray = parent.arguments[1];
82 | const deps = depArray.elements.filter(e => (e == null ? void 0 : e.type) === 'Identifier');
83 | const unusedDeps = [];
84 | for (const dep of deps) {
85 | if (through.includes(dep.name)) continue;
86 | if (sourceCode.getCommentsBefore(dep).some(({
87 | value
88 | }) => value.includes(effectComment))) {
89 | continue;
90 | }
91 | unusedDeps.push(dep.name);
92 | }
93 | if (unusedDeps.length > 0) {
94 | context.report({
95 | node: depArray,
96 | messageId: 'unused',
97 | data: {
98 | hook: hookName,
99 | unusedDeps: unusedDeps.map(dep => `'${dep}'`).join(', '),
100 | effectComment
101 | }
102 | });
103 | }
104 | };
105 | return {
106 | 'ArrowFunctionExpression,FunctionExpression': nodeListener
107 | };
108 | }
109 | };
110 |
111 | module.exports = rule;
112 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import tseslint from 'typescript-eslint';
4 | import eslint from '@eslint/js';
5 | import globals from 'globals';
6 | import prettier from 'eslint-config-prettier';
7 |
8 | export default tseslint.config(
9 | eslint.configs.recommended,
10 | tseslint.configs.recommended,
11 | prettier,
12 | {
13 | ignores: ['types/', 'dist/']
14 | },
15 | {
16 | languageOptions: {
17 | ecmaVersion: 'latest',
18 | globals: {
19 | ...globals.node,
20 | ...globals.browser
21 | }
22 | },
23 | rules: {
24 | '@typescript-eslint/no-require-imports': 'off'
25 | }
26 | }
27 | );
28 |
--------------------------------------------------------------------------------
/examples/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import eslint from '@eslint/js';
4 | import globals from 'globals';
5 | import prettier from 'eslint-config-prettier';
6 | import reactHooks from 'eslint-plugin-react-hooks';
7 | import reactHooksAddons from 'eslint-plugin-react-hooks-addons';
8 |
9 | /**
10 | * @type {import('eslint').Linter.Config[]}
11 | */
12 | export default [
13 | eslint.configs.recommended,
14 | reactHooksAddons.configs.recommended,
15 | prettier,
16 | {
17 | languageOptions: {
18 | ecmaVersion: 'latest',
19 | globals: {
20 | ...globals.browser
21 | }
22 | },
23 | plugins: {
24 | 'react-hooks': reactHooks
25 | },
26 | rules: {
27 | 'react-hooks/rules-of-hooks': 'error',
28 | 'react-hooks/exhaustive-deps': 'error'
29 | }
30 | }
31 | ];
32 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | export function App() {
4 | const [usedVar, setUsedVar] = useState('');
5 | const unusedVar = 'unused';
6 | const effectVar = 'effect';
7 | const shadowedVar = 'shadowed';
8 |
9 | useEffect(() => {
10 | setUsedVar('used');
11 | const shadowedVar = `no-unused-deps example ${usedVar}`;
12 | document.title = shadowedVar;
13 | }, [unusedVar, /* effect dep */ effectVar, shadowedVar]);
14 |
15 | React.useEffect(() => {
16 | setUsedVar('used');
17 | const shadowedVar = `no-unused-deps example ${usedVar}`;
18 | document.title = shadowedVar;
19 | }, [unusedVar, /* effect dep */ effectVar, shadowedVar]);
20 |
21 | return null;
22 | }
23 |
--------------------------------------------------------------------------------
/examples/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "name": "examples",
8 | "devDependencies": {
9 | "eslint-plugin-react-hooks": "^5.2.0",
10 | "eslint-plugin-react-hooks-addons": "file:.."
11 | }
12 | },
13 | "..": {
14 | "version": "0.5.0",
15 | "dev": true,
16 | "license": "MIT",
17 | "devDependencies": {
18 | "@babel/cli": "^7.27.0",
19 | "@babel/core": "^7.26.10",
20 | "@babel/preset-env": "^7.26.9",
21 | "@babel/preset-typescript": "^7.27.0",
22 | "@rollup/plugin-babel": "^6.0.4",
23 | "@rollup/plugin-json": "^6.1.0",
24 | "@rollup/plugin-node-resolve": "^16.0.1",
25 | "eslint": "^9.24.0",
26 | "eslint-config-prettier": "^10.1.2",
27 | "globals": "^16.0.0",
28 | "npm-run-all": "^4.1.5",
29 | "prettier": "^3.5.3",
30 | "rollup": "^4.40.0",
31 | "typescript": "^5.8.3",
32 | "typescript-eslint": "^8.30.1"
33 | },
34 | "peerDependencies": {
35 | "eslint": ">=3.0.0"
36 | }
37 | },
38 | "node_modules/@eslint-community/eslint-utils": {
39 | "version": "4.6.1",
40 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz",
41 | "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
42 | "dev": true,
43 | "license": "MIT",
44 | "peer": true,
45 | "dependencies": {
46 | "eslint-visitor-keys": "^3.4.3"
47 | },
48 | "engines": {
49 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
50 | },
51 | "funding": {
52 | "url": "https://opencollective.com/eslint"
53 | },
54 | "peerDependencies": {
55 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
56 | }
57 | },
58 | "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
59 | "version": "3.4.3",
60 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
61 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
62 | "dev": true,
63 | "license": "Apache-2.0",
64 | "peer": true,
65 | "engines": {
66 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
67 | },
68 | "funding": {
69 | "url": "https://opencollective.com/eslint"
70 | }
71 | },
72 | "node_modules/@eslint-community/regexpp": {
73 | "version": "4.12.1",
74 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
75 | "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
76 | "dev": true,
77 | "license": "MIT",
78 | "peer": true,
79 | "engines": {
80 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
81 | }
82 | },
83 | "node_modules/@eslint/config-array": {
84 | "version": "0.20.0",
85 | "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
86 | "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
87 | "dev": true,
88 | "license": "Apache-2.0",
89 | "peer": true,
90 | "dependencies": {
91 | "@eslint/object-schema": "^2.1.6",
92 | "debug": "^4.3.1",
93 | "minimatch": "^3.1.2"
94 | },
95 | "engines": {
96 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
97 | }
98 | },
99 | "node_modules/@eslint/config-helpers": {
100 | "version": "0.2.1",
101 | "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
102 | "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
103 | "dev": true,
104 | "license": "Apache-2.0",
105 | "peer": true,
106 | "engines": {
107 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
108 | }
109 | },
110 | "node_modules/@eslint/core": {
111 | "version": "0.13.0",
112 | "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
113 | "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
114 | "dev": true,
115 | "license": "Apache-2.0",
116 | "peer": true,
117 | "dependencies": {
118 | "@types/json-schema": "^7.0.15"
119 | },
120 | "engines": {
121 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
122 | }
123 | },
124 | "node_modules/@eslint/eslintrc": {
125 | "version": "3.3.1",
126 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
127 | "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
128 | "dev": true,
129 | "license": "MIT",
130 | "peer": true,
131 | "dependencies": {
132 | "ajv": "^6.12.4",
133 | "debug": "^4.3.2",
134 | "espree": "^10.0.1",
135 | "globals": "^14.0.0",
136 | "ignore": "^5.2.0",
137 | "import-fresh": "^3.2.1",
138 | "js-yaml": "^4.1.0",
139 | "minimatch": "^3.1.2",
140 | "strip-json-comments": "^3.1.1"
141 | },
142 | "engines": {
143 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
144 | },
145 | "funding": {
146 | "url": "https://opencollective.com/eslint"
147 | }
148 | },
149 | "node_modules/@eslint/js": {
150 | "version": "9.25.0",
151 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz",
152 | "integrity": "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w==",
153 | "dev": true,
154 | "license": "MIT",
155 | "peer": true,
156 | "engines": {
157 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
158 | }
159 | },
160 | "node_modules/@eslint/object-schema": {
161 | "version": "2.1.6",
162 | "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
163 | "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
164 | "dev": true,
165 | "license": "Apache-2.0",
166 | "peer": true,
167 | "engines": {
168 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
169 | }
170 | },
171 | "node_modules/@eslint/plugin-kit": {
172 | "version": "0.2.8",
173 | "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
174 | "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
175 | "dev": true,
176 | "license": "Apache-2.0",
177 | "peer": true,
178 | "dependencies": {
179 | "@eslint/core": "^0.13.0",
180 | "levn": "^0.4.1"
181 | },
182 | "engines": {
183 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
184 | }
185 | },
186 | "node_modules/@humanfs/core": {
187 | "version": "0.19.1",
188 | "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
189 | "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
190 | "dev": true,
191 | "license": "Apache-2.0",
192 | "peer": true,
193 | "engines": {
194 | "node": ">=18.18.0"
195 | }
196 | },
197 | "node_modules/@humanfs/node": {
198 | "version": "0.16.6",
199 | "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
200 | "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
201 | "dev": true,
202 | "license": "Apache-2.0",
203 | "peer": true,
204 | "dependencies": {
205 | "@humanfs/core": "^0.19.1",
206 | "@humanwhocodes/retry": "^0.3.0"
207 | },
208 | "engines": {
209 | "node": ">=18.18.0"
210 | }
211 | },
212 | "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
213 | "version": "0.3.1",
214 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
215 | "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
216 | "dev": true,
217 | "license": "Apache-2.0",
218 | "peer": true,
219 | "engines": {
220 | "node": ">=18.18"
221 | },
222 | "funding": {
223 | "type": "github",
224 | "url": "https://github.com/sponsors/nzakas"
225 | }
226 | },
227 | "node_modules/@humanwhocodes/module-importer": {
228 | "version": "1.0.1",
229 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
230 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
231 | "dev": true,
232 | "license": "Apache-2.0",
233 | "peer": true,
234 | "engines": {
235 | "node": ">=12.22"
236 | },
237 | "funding": {
238 | "type": "github",
239 | "url": "https://github.com/sponsors/nzakas"
240 | }
241 | },
242 | "node_modules/@humanwhocodes/retry": {
243 | "version": "0.4.2",
244 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
245 | "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
246 | "dev": true,
247 | "license": "Apache-2.0",
248 | "peer": true,
249 | "engines": {
250 | "node": ">=18.18"
251 | },
252 | "funding": {
253 | "type": "github",
254 | "url": "https://github.com/sponsors/nzakas"
255 | }
256 | },
257 | "node_modules/@types/estree": {
258 | "version": "1.0.7",
259 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
260 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
261 | "dev": true,
262 | "license": "MIT",
263 | "peer": true
264 | },
265 | "node_modules/@types/json-schema": {
266 | "version": "7.0.15",
267 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
268 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
269 | "dev": true,
270 | "license": "MIT",
271 | "peer": true
272 | },
273 | "node_modules/acorn": {
274 | "version": "8.14.1",
275 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
276 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
277 | "dev": true,
278 | "license": "MIT",
279 | "peer": true,
280 | "bin": {
281 | "acorn": "bin/acorn"
282 | },
283 | "engines": {
284 | "node": ">=0.4.0"
285 | }
286 | },
287 | "node_modules/acorn-jsx": {
288 | "version": "5.3.2",
289 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
290 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
291 | "dev": true,
292 | "license": "MIT",
293 | "peer": true,
294 | "peerDependencies": {
295 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
296 | }
297 | },
298 | "node_modules/ajv": {
299 | "version": "6.12.6",
300 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
301 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
302 | "dev": true,
303 | "license": "MIT",
304 | "peer": true,
305 | "dependencies": {
306 | "fast-deep-equal": "^3.1.1",
307 | "fast-json-stable-stringify": "^2.0.0",
308 | "json-schema-traverse": "^0.4.1",
309 | "uri-js": "^4.2.2"
310 | },
311 | "funding": {
312 | "type": "github",
313 | "url": "https://github.com/sponsors/epoberezkin"
314 | }
315 | },
316 | "node_modules/ansi-styles": {
317 | "version": "4.3.0",
318 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
319 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
320 | "dev": true,
321 | "license": "MIT",
322 | "peer": true,
323 | "dependencies": {
324 | "color-convert": "^2.0.1"
325 | },
326 | "engines": {
327 | "node": ">=8"
328 | },
329 | "funding": {
330 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
331 | }
332 | },
333 | "node_modules/argparse": {
334 | "version": "2.0.1",
335 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
336 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
337 | "dev": true,
338 | "license": "Python-2.0",
339 | "peer": true
340 | },
341 | "node_modules/balanced-match": {
342 | "version": "1.0.2",
343 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
344 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
345 | "dev": true,
346 | "license": "MIT",
347 | "peer": true
348 | },
349 | "node_modules/brace-expansion": {
350 | "version": "1.1.11",
351 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
352 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
353 | "dev": true,
354 | "license": "MIT",
355 | "peer": true,
356 | "dependencies": {
357 | "balanced-match": "^1.0.0",
358 | "concat-map": "0.0.1"
359 | }
360 | },
361 | "node_modules/callsites": {
362 | "version": "3.1.0",
363 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
364 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
365 | "dev": true,
366 | "license": "MIT",
367 | "peer": true,
368 | "engines": {
369 | "node": ">=6"
370 | }
371 | },
372 | "node_modules/chalk": {
373 | "version": "4.1.2",
374 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
375 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
376 | "dev": true,
377 | "license": "MIT",
378 | "peer": true,
379 | "dependencies": {
380 | "ansi-styles": "^4.1.0",
381 | "supports-color": "^7.1.0"
382 | },
383 | "engines": {
384 | "node": ">=10"
385 | },
386 | "funding": {
387 | "url": "https://github.com/chalk/chalk?sponsor=1"
388 | }
389 | },
390 | "node_modules/color-convert": {
391 | "version": "2.0.1",
392 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
393 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
394 | "dev": true,
395 | "license": "MIT",
396 | "peer": true,
397 | "dependencies": {
398 | "color-name": "~1.1.4"
399 | },
400 | "engines": {
401 | "node": ">=7.0.0"
402 | }
403 | },
404 | "node_modules/color-name": {
405 | "version": "1.1.4",
406 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
407 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
408 | "dev": true,
409 | "license": "MIT",
410 | "peer": true
411 | },
412 | "node_modules/concat-map": {
413 | "version": "0.0.1",
414 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
415 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
416 | "dev": true,
417 | "license": "MIT",
418 | "peer": true
419 | },
420 | "node_modules/cross-spawn": {
421 | "version": "7.0.6",
422 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
423 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
424 | "dev": true,
425 | "license": "MIT",
426 | "peer": true,
427 | "dependencies": {
428 | "path-key": "^3.1.0",
429 | "shebang-command": "^2.0.0",
430 | "which": "^2.0.1"
431 | },
432 | "engines": {
433 | "node": ">= 8"
434 | }
435 | },
436 | "node_modules/debug": {
437 | "version": "4.4.0",
438 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
439 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
440 | "dev": true,
441 | "license": "MIT",
442 | "peer": true,
443 | "dependencies": {
444 | "ms": "^2.1.3"
445 | },
446 | "engines": {
447 | "node": ">=6.0"
448 | },
449 | "peerDependenciesMeta": {
450 | "supports-color": {
451 | "optional": true
452 | }
453 | }
454 | },
455 | "node_modules/deep-is": {
456 | "version": "0.1.4",
457 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
458 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
459 | "dev": true,
460 | "license": "MIT",
461 | "peer": true
462 | },
463 | "node_modules/escape-string-regexp": {
464 | "version": "4.0.0",
465 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
466 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
467 | "dev": true,
468 | "license": "MIT",
469 | "peer": true,
470 | "engines": {
471 | "node": ">=10"
472 | },
473 | "funding": {
474 | "url": "https://github.com/sponsors/sindresorhus"
475 | }
476 | },
477 | "node_modules/eslint": {
478 | "version": "9.25.0",
479 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.0.tgz",
480 | "integrity": "sha512-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA==",
481 | "dev": true,
482 | "license": "MIT",
483 | "peer": true,
484 | "dependencies": {
485 | "@eslint-community/eslint-utils": "^4.2.0",
486 | "@eslint-community/regexpp": "^4.12.1",
487 | "@eslint/config-array": "^0.20.0",
488 | "@eslint/config-helpers": "^0.2.1",
489 | "@eslint/core": "^0.13.0",
490 | "@eslint/eslintrc": "^3.3.1",
491 | "@eslint/js": "9.25.0",
492 | "@eslint/plugin-kit": "^0.2.8",
493 | "@humanfs/node": "^0.16.6",
494 | "@humanwhocodes/module-importer": "^1.0.1",
495 | "@humanwhocodes/retry": "^0.4.2",
496 | "@types/estree": "^1.0.6",
497 | "@types/json-schema": "^7.0.15",
498 | "ajv": "^6.12.4",
499 | "chalk": "^4.0.0",
500 | "cross-spawn": "^7.0.6",
501 | "debug": "^4.3.2",
502 | "escape-string-regexp": "^4.0.0",
503 | "eslint-scope": "^8.3.0",
504 | "eslint-visitor-keys": "^4.2.0",
505 | "espree": "^10.3.0",
506 | "esquery": "^1.5.0",
507 | "esutils": "^2.0.2",
508 | "fast-deep-equal": "^3.1.3",
509 | "file-entry-cache": "^8.0.0",
510 | "find-up": "^5.0.0",
511 | "glob-parent": "^6.0.2",
512 | "ignore": "^5.2.0",
513 | "imurmurhash": "^0.1.4",
514 | "is-glob": "^4.0.0",
515 | "json-stable-stringify-without-jsonify": "^1.0.1",
516 | "lodash.merge": "^4.6.2",
517 | "minimatch": "^3.1.2",
518 | "natural-compare": "^1.4.0",
519 | "optionator": "^0.9.3"
520 | },
521 | "bin": {
522 | "eslint": "bin/eslint.js"
523 | },
524 | "engines": {
525 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
526 | },
527 | "funding": {
528 | "url": "https://eslint.org/donate"
529 | },
530 | "peerDependencies": {
531 | "jiti": "*"
532 | },
533 | "peerDependenciesMeta": {
534 | "jiti": {
535 | "optional": true
536 | }
537 | }
538 | },
539 | "node_modules/eslint-plugin-react-hooks": {
540 | "version": "5.2.0",
541 | "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
542 | "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
543 | "dev": true,
544 | "license": "MIT",
545 | "engines": {
546 | "node": ">=10"
547 | },
548 | "peerDependencies": {
549 | "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
550 | }
551 | },
552 | "node_modules/eslint-plugin-react-hooks-addons": {
553 | "resolved": "..",
554 | "link": true
555 | },
556 | "node_modules/eslint-scope": {
557 | "version": "8.3.0",
558 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
559 | "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
560 | "dev": true,
561 | "license": "BSD-2-Clause",
562 | "peer": true,
563 | "dependencies": {
564 | "esrecurse": "^4.3.0",
565 | "estraverse": "^5.2.0"
566 | },
567 | "engines": {
568 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
569 | },
570 | "funding": {
571 | "url": "https://opencollective.com/eslint"
572 | }
573 | },
574 | "node_modules/eslint-visitor-keys": {
575 | "version": "4.2.0",
576 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
577 | "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
578 | "dev": true,
579 | "license": "Apache-2.0",
580 | "peer": true,
581 | "engines": {
582 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
583 | },
584 | "funding": {
585 | "url": "https://opencollective.com/eslint"
586 | }
587 | },
588 | "node_modules/espree": {
589 | "version": "10.3.0",
590 | "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
591 | "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
592 | "dev": true,
593 | "license": "BSD-2-Clause",
594 | "peer": true,
595 | "dependencies": {
596 | "acorn": "^8.14.0",
597 | "acorn-jsx": "^5.3.2",
598 | "eslint-visitor-keys": "^4.2.0"
599 | },
600 | "engines": {
601 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
602 | },
603 | "funding": {
604 | "url": "https://opencollective.com/eslint"
605 | }
606 | },
607 | "node_modules/esquery": {
608 | "version": "1.6.0",
609 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
610 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
611 | "dev": true,
612 | "license": "BSD-3-Clause",
613 | "peer": true,
614 | "dependencies": {
615 | "estraverse": "^5.1.0"
616 | },
617 | "engines": {
618 | "node": ">=0.10"
619 | }
620 | },
621 | "node_modules/esrecurse": {
622 | "version": "4.3.0",
623 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
624 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
625 | "dev": true,
626 | "license": "BSD-2-Clause",
627 | "peer": true,
628 | "dependencies": {
629 | "estraverse": "^5.2.0"
630 | },
631 | "engines": {
632 | "node": ">=4.0"
633 | }
634 | },
635 | "node_modules/estraverse": {
636 | "version": "5.3.0",
637 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
638 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
639 | "dev": true,
640 | "license": "BSD-2-Clause",
641 | "peer": true,
642 | "engines": {
643 | "node": ">=4.0"
644 | }
645 | },
646 | "node_modules/esutils": {
647 | "version": "2.0.3",
648 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
649 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
650 | "dev": true,
651 | "license": "BSD-2-Clause",
652 | "peer": true,
653 | "engines": {
654 | "node": ">=0.10.0"
655 | }
656 | },
657 | "node_modules/fast-deep-equal": {
658 | "version": "3.1.3",
659 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
660 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
661 | "dev": true,
662 | "license": "MIT",
663 | "peer": true
664 | },
665 | "node_modules/fast-json-stable-stringify": {
666 | "version": "2.1.0",
667 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
668 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
669 | "dev": true,
670 | "license": "MIT",
671 | "peer": true
672 | },
673 | "node_modules/fast-levenshtein": {
674 | "version": "2.0.6",
675 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
676 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
677 | "dev": true,
678 | "license": "MIT",
679 | "peer": true
680 | },
681 | "node_modules/file-entry-cache": {
682 | "version": "8.0.0",
683 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
684 | "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
685 | "dev": true,
686 | "license": "MIT",
687 | "peer": true,
688 | "dependencies": {
689 | "flat-cache": "^4.0.0"
690 | },
691 | "engines": {
692 | "node": ">=16.0.0"
693 | }
694 | },
695 | "node_modules/find-up": {
696 | "version": "5.0.0",
697 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
698 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
699 | "dev": true,
700 | "license": "MIT",
701 | "peer": true,
702 | "dependencies": {
703 | "locate-path": "^6.0.0",
704 | "path-exists": "^4.0.0"
705 | },
706 | "engines": {
707 | "node": ">=10"
708 | },
709 | "funding": {
710 | "url": "https://github.com/sponsors/sindresorhus"
711 | }
712 | },
713 | "node_modules/flat-cache": {
714 | "version": "4.0.1",
715 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
716 | "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
717 | "dev": true,
718 | "license": "MIT",
719 | "peer": true,
720 | "dependencies": {
721 | "flatted": "^3.2.9",
722 | "keyv": "^4.5.4"
723 | },
724 | "engines": {
725 | "node": ">=16"
726 | }
727 | },
728 | "node_modules/flatted": {
729 | "version": "3.3.3",
730 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
731 | "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
732 | "dev": true,
733 | "license": "ISC",
734 | "peer": true
735 | },
736 | "node_modules/glob-parent": {
737 | "version": "6.0.2",
738 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
739 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
740 | "dev": true,
741 | "license": "ISC",
742 | "peer": true,
743 | "dependencies": {
744 | "is-glob": "^4.0.3"
745 | },
746 | "engines": {
747 | "node": ">=10.13.0"
748 | }
749 | },
750 | "node_modules/globals": {
751 | "version": "14.0.0",
752 | "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
753 | "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
754 | "dev": true,
755 | "license": "MIT",
756 | "peer": true,
757 | "engines": {
758 | "node": ">=18"
759 | },
760 | "funding": {
761 | "url": "https://github.com/sponsors/sindresorhus"
762 | }
763 | },
764 | "node_modules/has-flag": {
765 | "version": "4.0.0",
766 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
767 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
768 | "dev": true,
769 | "license": "MIT",
770 | "peer": true,
771 | "engines": {
772 | "node": ">=8"
773 | }
774 | },
775 | "node_modules/ignore": {
776 | "version": "5.3.2",
777 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
778 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
779 | "dev": true,
780 | "license": "MIT",
781 | "peer": true,
782 | "engines": {
783 | "node": ">= 4"
784 | }
785 | },
786 | "node_modules/import-fresh": {
787 | "version": "3.3.1",
788 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
789 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
790 | "dev": true,
791 | "license": "MIT",
792 | "peer": true,
793 | "dependencies": {
794 | "parent-module": "^1.0.0",
795 | "resolve-from": "^4.0.0"
796 | },
797 | "engines": {
798 | "node": ">=6"
799 | },
800 | "funding": {
801 | "url": "https://github.com/sponsors/sindresorhus"
802 | }
803 | },
804 | "node_modules/imurmurhash": {
805 | "version": "0.1.4",
806 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
807 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
808 | "dev": true,
809 | "license": "MIT",
810 | "peer": true,
811 | "engines": {
812 | "node": ">=0.8.19"
813 | }
814 | },
815 | "node_modules/is-extglob": {
816 | "version": "2.1.1",
817 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
818 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
819 | "dev": true,
820 | "license": "MIT",
821 | "peer": true,
822 | "engines": {
823 | "node": ">=0.10.0"
824 | }
825 | },
826 | "node_modules/is-glob": {
827 | "version": "4.0.3",
828 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
829 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
830 | "dev": true,
831 | "license": "MIT",
832 | "peer": true,
833 | "dependencies": {
834 | "is-extglob": "^2.1.1"
835 | },
836 | "engines": {
837 | "node": ">=0.10.0"
838 | }
839 | },
840 | "node_modules/isexe": {
841 | "version": "2.0.0",
842 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
843 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
844 | "dev": true,
845 | "license": "ISC",
846 | "peer": true
847 | },
848 | "node_modules/js-yaml": {
849 | "version": "4.1.0",
850 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
851 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
852 | "dev": true,
853 | "license": "MIT",
854 | "peer": true,
855 | "dependencies": {
856 | "argparse": "^2.0.1"
857 | },
858 | "bin": {
859 | "js-yaml": "bin/js-yaml.js"
860 | }
861 | },
862 | "node_modules/json-buffer": {
863 | "version": "3.0.1",
864 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
865 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
866 | "dev": true,
867 | "license": "MIT",
868 | "peer": true
869 | },
870 | "node_modules/json-schema-traverse": {
871 | "version": "0.4.1",
872 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
873 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
874 | "dev": true,
875 | "license": "MIT",
876 | "peer": true
877 | },
878 | "node_modules/json-stable-stringify-without-jsonify": {
879 | "version": "1.0.1",
880 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
881 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
882 | "dev": true,
883 | "license": "MIT",
884 | "peer": true
885 | },
886 | "node_modules/keyv": {
887 | "version": "4.5.4",
888 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
889 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
890 | "dev": true,
891 | "license": "MIT",
892 | "peer": true,
893 | "dependencies": {
894 | "json-buffer": "3.0.1"
895 | }
896 | },
897 | "node_modules/levn": {
898 | "version": "0.4.1",
899 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
900 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
901 | "dev": true,
902 | "license": "MIT",
903 | "peer": true,
904 | "dependencies": {
905 | "prelude-ls": "^1.2.1",
906 | "type-check": "~0.4.0"
907 | },
908 | "engines": {
909 | "node": ">= 0.8.0"
910 | }
911 | },
912 | "node_modules/locate-path": {
913 | "version": "6.0.0",
914 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
915 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
916 | "dev": true,
917 | "license": "MIT",
918 | "peer": true,
919 | "dependencies": {
920 | "p-locate": "^5.0.0"
921 | },
922 | "engines": {
923 | "node": ">=10"
924 | },
925 | "funding": {
926 | "url": "https://github.com/sponsors/sindresorhus"
927 | }
928 | },
929 | "node_modules/lodash.merge": {
930 | "version": "4.6.2",
931 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
932 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
933 | "dev": true,
934 | "license": "MIT",
935 | "peer": true
936 | },
937 | "node_modules/minimatch": {
938 | "version": "3.1.2",
939 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
940 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
941 | "dev": true,
942 | "license": "ISC",
943 | "peer": true,
944 | "dependencies": {
945 | "brace-expansion": "^1.1.7"
946 | },
947 | "engines": {
948 | "node": "*"
949 | }
950 | },
951 | "node_modules/ms": {
952 | "version": "2.1.3",
953 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
954 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
955 | "dev": true,
956 | "license": "MIT",
957 | "peer": true
958 | },
959 | "node_modules/natural-compare": {
960 | "version": "1.4.0",
961 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
962 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
963 | "dev": true,
964 | "license": "MIT",
965 | "peer": true
966 | },
967 | "node_modules/optionator": {
968 | "version": "0.9.4",
969 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
970 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
971 | "dev": true,
972 | "license": "MIT",
973 | "peer": true,
974 | "dependencies": {
975 | "deep-is": "^0.1.3",
976 | "fast-levenshtein": "^2.0.6",
977 | "levn": "^0.4.1",
978 | "prelude-ls": "^1.2.1",
979 | "type-check": "^0.4.0",
980 | "word-wrap": "^1.2.5"
981 | },
982 | "engines": {
983 | "node": ">= 0.8.0"
984 | }
985 | },
986 | "node_modules/p-limit": {
987 | "version": "3.1.0",
988 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
989 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
990 | "dev": true,
991 | "license": "MIT",
992 | "peer": true,
993 | "dependencies": {
994 | "yocto-queue": "^0.1.0"
995 | },
996 | "engines": {
997 | "node": ">=10"
998 | },
999 | "funding": {
1000 | "url": "https://github.com/sponsors/sindresorhus"
1001 | }
1002 | },
1003 | "node_modules/p-locate": {
1004 | "version": "5.0.0",
1005 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
1006 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
1007 | "dev": true,
1008 | "license": "MIT",
1009 | "peer": true,
1010 | "dependencies": {
1011 | "p-limit": "^3.0.2"
1012 | },
1013 | "engines": {
1014 | "node": ">=10"
1015 | },
1016 | "funding": {
1017 | "url": "https://github.com/sponsors/sindresorhus"
1018 | }
1019 | },
1020 | "node_modules/parent-module": {
1021 | "version": "1.0.1",
1022 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
1023 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
1024 | "dev": true,
1025 | "license": "MIT",
1026 | "peer": true,
1027 | "dependencies": {
1028 | "callsites": "^3.0.0"
1029 | },
1030 | "engines": {
1031 | "node": ">=6"
1032 | }
1033 | },
1034 | "node_modules/path-exists": {
1035 | "version": "4.0.0",
1036 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
1037 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
1038 | "dev": true,
1039 | "license": "MIT",
1040 | "peer": true,
1041 | "engines": {
1042 | "node": ">=8"
1043 | }
1044 | },
1045 | "node_modules/path-key": {
1046 | "version": "3.1.1",
1047 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1048 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1049 | "dev": true,
1050 | "license": "MIT",
1051 | "peer": true,
1052 | "engines": {
1053 | "node": ">=8"
1054 | }
1055 | },
1056 | "node_modules/prelude-ls": {
1057 | "version": "1.2.1",
1058 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
1059 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
1060 | "dev": true,
1061 | "license": "MIT",
1062 | "peer": true,
1063 | "engines": {
1064 | "node": ">= 0.8.0"
1065 | }
1066 | },
1067 | "node_modules/punycode": {
1068 | "version": "2.3.1",
1069 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1070 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1071 | "dev": true,
1072 | "license": "MIT",
1073 | "peer": true,
1074 | "engines": {
1075 | "node": ">=6"
1076 | }
1077 | },
1078 | "node_modules/resolve-from": {
1079 | "version": "4.0.0",
1080 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1081 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1082 | "dev": true,
1083 | "license": "MIT",
1084 | "peer": true,
1085 | "engines": {
1086 | "node": ">=4"
1087 | }
1088 | },
1089 | "node_modules/shebang-command": {
1090 | "version": "2.0.0",
1091 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1092 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1093 | "dev": true,
1094 | "license": "MIT",
1095 | "peer": true,
1096 | "dependencies": {
1097 | "shebang-regex": "^3.0.0"
1098 | },
1099 | "engines": {
1100 | "node": ">=8"
1101 | }
1102 | },
1103 | "node_modules/shebang-regex": {
1104 | "version": "3.0.0",
1105 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1106 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1107 | "dev": true,
1108 | "license": "MIT",
1109 | "peer": true,
1110 | "engines": {
1111 | "node": ">=8"
1112 | }
1113 | },
1114 | "node_modules/strip-json-comments": {
1115 | "version": "3.1.1",
1116 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
1117 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
1118 | "dev": true,
1119 | "license": "MIT",
1120 | "peer": true,
1121 | "engines": {
1122 | "node": ">=8"
1123 | },
1124 | "funding": {
1125 | "url": "https://github.com/sponsors/sindresorhus"
1126 | }
1127 | },
1128 | "node_modules/supports-color": {
1129 | "version": "7.2.0",
1130 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
1131 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
1132 | "dev": true,
1133 | "license": "MIT",
1134 | "peer": true,
1135 | "dependencies": {
1136 | "has-flag": "^4.0.0"
1137 | },
1138 | "engines": {
1139 | "node": ">=8"
1140 | }
1141 | },
1142 | "node_modules/type-check": {
1143 | "version": "0.4.0",
1144 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
1145 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
1146 | "dev": true,
1147 | "license": "MIT",
1148 | "peer": true,
1149 | "dependencies": {
1150 | "prelude-ls": "^1.2.1"
1151 | },
1152 | "engines": {
1153 | "node": ">= 0.8.0"
1154 | }
1155 | },
1156 | "node_modules/uri-js": {
1157 | "version": "4.4.1",
1158 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1159 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1160 | "dev": true,
1161 | "license": "BSD-2-Clause",
1162 | "peer": true,
1163 | "dependencies": {
1164 | "punycode": "^2.1.0"
1165 | }
1166 | },
1167 | "node_modules/which": {
1168 | "version": "2.0.2",
1169 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1170 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1171 | "dev": true,
1172 | "license": "ISC",
1173 | "peer": true,
1174 | "dependencies": {
1175 | "isexe": "^2.0.0"
1176 | },
1177 | "bin": {
1178 | "node-which": "bin/node-which"
1179 | },
1180 | "engines": {
1181 | "node": ">= 8"
1182 | }
1183 | },
1184 | "node_modules/word-wrap": {
1185 | "version": "1.2.5",
1186 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
1187 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
1188 | "dev": true,
1189 | "license": "MIT",
1190 | "peer": true,
1191 | "engines": {
1192 | "node": ">=0.10.0"
1193 | }
1194 | },
1195 | "node_modules/yocto-queue": {
1196 | "version": "0.1.0",
1197 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
1198 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
1199 | "dev": true,
1200 | "license": "MIT",
1201 | "peer": true,
1202 | "engines": {
1203 | "node": ">=10"
1204 | },
1205 | "funding": {
1206 | "url": "https://github.com/sponsors/sindresorhus"
1207 | }
1208 | }
1209 | }
1210 | }
1211 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples",
3 | "devDependencies": {
4 | "eslint-plugin-react-hooks": "^5.2.0",
5 | "eslint-plugin-react-hooks-addons": "file:.."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import type { Linter } from 'eslint';
2 | import _package from './package.json';
3 | import noUnusedDeps from './rules/no-unused-deps';
4 |
5 | const rules: Linter.RulesRecord = {
6 | 'react-hooks-addons/no-unused-deps': 'error'
7 | };
8 |
9 | const plugin = {
10 | meta: {
11 | name: _package.name,
12 | version: _package.version
13 | },
14 | configs: {
15 | recommended: {} as Linter.Config,
16 | 'recommended-legacy': {} as Linter.LegacyConfig
17 | },
18 | rules: {
19 | 'no-unused-deps': noUnusedDeps
20 | }
21 | };
22 |
23 | plugin.configs.recommended = {
24 | plugins: {
25 | 'react-hooks-addons': plugin
26 | },
27 | rules
28 | };
29 |
30 | plugin.configs['recommended-legacy'] = {
31 | plugins: ['react-hooks-addons'],
32 | rules
33 | };
34 |
35 | export default plugin;
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-plugin-react-hooks-addons",
3 | "version": "0.5.0",
4 | "author": "Zheng Song",
5 | "license": "MIT",
6 | "description": "ESLint rule to check unused and potentially unnecessary dependencies in React Hooks.",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/szhsin/eslint-plugin-react-hooks-addons.git"
10 | },
11 | "homepage": "https://github.com/szhsin/eslint-plugin-react-hooks-addons",
12 | "main": "./dist/cjs/index.cjs",
13 | "types": "./types/index.d.ts",
14 | "sideEffects": false,
15 | "files": [
16 | "dist/",
17 | "types/"
18 | ],
19 | "keywords": [
20 | "eslint",
21 | "eslint-plugin",
22 | "eslintplugin",
23 | "react",
24 | "hook"
25 | ],
26 | "scripts": {
27 | "start": "run-p watch \"types -- --watch\"",
28 | "bundle": "rollup -c",
29 | "watch": "rollup -c -w",
30 | "clean": "rm -Rf ./dist ./types",
31 | "types": "tsc",
32 | "test": "node tests/rules/*.js",
33 | "pret": "prettier -c .",
34 | "pret:fix": "prettier -w .",
35 | "lint": "eslint .",
36 | "eg": "cd examples && eslint .",
37 | "build": "run-s pret clean types lint bundle"
38 | },
39 | "peerDependencies": {
40 | "eslint": ">=3.0.0"
41 | },
42 | "devDependencies": {
43 | "@babel/cli": "^7.27.2",
44 | "@babel/core": "^7.27.1",
45 | "@babel/preset-env": "^7.27.2",
46 | "@babel/preset-typescript": "^7.27.1",
47 | "@rollup/plugin-babel": "^6.0.4",
48 | "@rollup/plugin-json": "^6.1.0",
49 | "@rollup/plugin-node-resolve": "^16.0.1",
50 | "deplift": "^1.0.0",
51 | "eslint": "^9.27.0",
52 | "eslint-config-prettier": "^10.1.5",
53 | "globals": "^16.1.0",
54 | "npm-run-all": "^4.1.5",
55 | "prettier": "^3.5.3",
56 | "rollup": "^4.41.1",
57 | "typescript": "^5.8.3",
58 | "typescript-eslint": "^8.32.1"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import { nodeResolve } from '@rollup/plugin-node-resolve';
4 | import { babel } from '@rollup/plugin-babel';
5 | import json from '@rollup/plugin-json';
6 |
7 | /**
8 | * @type {import('rollup').RollupOptions}
9 | */
10 | export default {
11 | plugins: [
12 | nodeResolve({ extensions: ['.ts', '.js'] }),
13 | json({ namedExports: false }),
14 | babel({
15 | babelHelpers: 'bundled',
16 | extensions: ['.ts', '.js']
17 | })
18 | ],
19 | treeshake: {
20 | moduleSideEffects: false,
21 | propertyReadSideEffects: false
22 | },
23 | input: './index.ts',
24 | output: [
25 | {
26 | dir: 'dist/cjs',
27 | format: 'cjs',
28 | interop: 'default',
29 | entryFileNames: '[name].cjs',
30 | preserveModules: true
31 | }
32 | ]
33 | };
34 |
--------------------------------------------------------------------------------
/rules/no-unused-deps.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Rule to check unused dependencies in React Hooks
3 | * @author Zheng Song
4 | */
5 |
6 | import type { Rule } from 'eslint';
7 |
8 | type RuleOption = {
9 | effectComment: string;
10 | additionalHooks: { pattern?: string; replace?: string };
11 | };
12 |
13 | const reactNamespace = 'React';
14 | const hookNames = ['useEffect', 'useLayoutEffect'];
15 |
16 | const matchHooks = (
17 | name: string,
18 | { pattern, replace }: RuleOption['additionalHooks'] = {}
19 | ) => {
20 | let match = false;
21 | if (pattern) {
22 | match = new RegExp(pattern).test(name);
23 | if (replace) return match;
24 | }
25 |
26 | return match || hookNames.includes(name);
27 | };
28 |
29 | const rule: Rule.RuleModule = {
30 | meta: {
31 | type: 'problem',
32 | schema: [
33 | {
34 | type: 'object',
35 | properties: {
36 | effectComment: {
37 | type: 'string'
38 | },
39 | additionalHooks: {
40 | type: 'object',
41 | properties: {
42 | pattern: {
43 | type: 'string'
44 | },
45 | replace: {
46 | type: 'boolean'
47 | }
48 | },
49 | required: ['pattern'],
50 | additionalProperties: false
51 | }
52 | },
53 | additionalProperties: false
54 | }
55 | ],
56 | messages: {
57 | unused:
58 | 'React Hook {{ hook }} has unused dependencies: {{ unusedDeps }}. They might cause the Hook to run unintentionally. Either exclude them or prepend /* {{ effectComment }} */ comments to make the intention explicit.'
59 | }
60 | },
61 |
62 | create(context) {
63 | const { effectComment = 'effect dep', additionalHooks } =
64 | (context.options[0] as RuleOption) || {};
65 | const sourceCode = context.sourceCode ?? context.getSourceCode();
66 |
67 | const nodeListener: Rule.NodeListener['FunctionExpression'] = (node) => {
68 | const { parent } = node;
69 | if (
70 | parent.type !== 'CallExpression' ||
71 | parent.arguments.length < 2 ||
72 | parent.arguments[1].type !== 'ArrayExpression'
73 | ) {
74 | return;
75 | }
76 |
77 | const { callee } = parent;
78 | let hookName: string | undefined;
79 | switch (callee.type) {
80 | case 'Identifier':
81 | hookName = callee.name;
82 | break;
83 | case 'MemberExpression':
84 | if (
85 | callee.object.type === 'Identifier' &&
86 | callee.object.name === reactNamespace &&
87 | callee.property.type === 'Identifier'
88 | ) {
89 | hookName = callee.property.name;
90 | }
91 | break;
92 | }
93 |
94 | if (!hookName || !matchHooks(hookName, additionalHooks)) return;
95 |
96 | const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope();
97 | const through = scope.through.map((r) => r.identifier.name);
98 | const depArray = parent.arguments[1];
99 | const deps = depArray.elements.filter((e) => e?.type === 'Identifier');
100 | const unusedDeps = [];
101 | for (const dep of deps) {
102 | if (through.includes(dep.name)) continue;
103 | if (
104 | sourceCode
105 | .getCommentsBefore(dep)
106 | .some(({ value }) => value.includes(effectComment))
107 | ) {
108 | continue;
109 | }
110 |
111 | unusedDeps.push(dep.name);
112 | }
113 |
114 | if (unusedDeps.length > 0) {
115 | context.report({
116 | node: depArray,
117 | messageId: 'unused',
118 | data: {
119 | hook: hookName,
120 | unusedDeps: unusedDeps.map((dep) => `'${dep}'`).join(', '),
121 | effectComment
122 | }
123 | });
124 | }
125 | };
126 |
127 | return {
128 | 'ArrowFunctionExpression,FunctionExpression': nodeListener
129 | };
130 | }
131 | };
132 |
133 | export default rule;
134 |
--------------------------------------------------------------------------------
/tests/rules/no-unused-deps.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | 'use strict';
3 |
4 | const { RuleTester } = require('eslint');
5 | const rule = require('../../dist/cjs/rules/no-unused-deps.cjs');
6 |
7 | const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 'latest' } });
8 |
9 | const getError = (
10 | unusedDeps,
11 | { hook = 'useEffect', effectComment = 'effect dep' } = {}
12 | ) => ({
13 | messageId: 'unused',
14 | data: { unusedDeps, hook, effectComment },
15 | type: 'ArrayExpression'
16 | });
17 |
18 | ruleTester.run('no-unused-deps', /** @type {import('eslint').Rule.RuleModule} */ (rule), {
19 | valid: [
20 | `
21 | useEffect(() => {
22 | document.title = usedVar;
23 | }, [usedVar]);
24 | `,
25 | `
26 | useEffect(() => {
27 | document.title = usedVar;
28 | }, [usedVar, /* effect dep */ effectVar]);
29 | `,
30 | `
31 | React.useEffect(() => {
32 | document.title = usedVar;
33 | }, [usedVar, /* effect dep */ effectVar]);
34 | `,
35 | `
36 | R.useEffect(() => {
37 | document.title = usedVar;
38 | }, [usedVar, effectVar]);
39 | `
40 | ],
41 |
42 | invalid: [
43 | {
44 | code: `
45 | useLayoutEffect(() => {
46 | document.title = usedVar;
47 | }, [usedVar, unusedVar]);
48 | `,
49 | errors: [getError("'unusedVar'", { hook: 'useLayoutEffect' })]
50 | },
51 | {
52 | code: `
53 | React.useLayoutEffect(() => {
54 | document.title = usedVar;
55 | }, [usedVar, unusedVar]);
56 | `,
57 | errors: [getError("'unusedVar'", { hook: 'useLayoutEffect' })]
58 | },
59 | {
60 | code: `
61 | useEffect(() => {
62 | document.title = usedVar;
63 | }, [usedVar, unusedVar, /* effect dep */ effectVar]);
64 | `,
65 | errors: [getError("'unusedVar'")]
66 | },
67 | {
68 | code: `
69 | useEffect(() => {
70 | document.title = usedVar;
71 | }, [usedVar, unusedVar, /* effectful */ effectVar, /* effect dep */ ineffectVar]);
72 | `,
73 | options: [{ effectComment: 'effectful' }],
74 | errors: [getError("'unusedVar', 'ineffectVar'", { effectComment: 'effectful' })]
75 | },
76 | {
77 | code: `
78 | useEffect(() => {
79 | const shadowedVar = usedVar;
80 | document.title = shadowedVar;
81 | }, [usedVar, unusedVar, /* effect dep */ effectVar, shadowedVar]);
82 | `,
83 | errors: [getError("'unusedVar', 'shadowedVar'")]
84 | },
85 | {
86 | code: `
87 | useEffect(() => {
88 | const nested = () => {
89 | document.title = usedVar;
90 | };
91 | nested();
92 | }, [usedVar, unusedVar]);
93 | `,
94 | errors: [getError("'unusedVar'")]
95 | },
96 | {
97 | code: `
98 | useEffect(() => {
99 | document.title = usedVar;
100 | }, [usedVar, unusedVar]);
101 |
102 | useLayoutEffect(() => {
103 | document.title = usedVar;
104 | }, [usedVar, unusedVar]);
105 |
106 | useMyCustomHook(() => {
107 | document.title = usedVar;
108 | }, [usedVar, unusedVar]);
109 | `,
110 | options: [{ additionalHooks: { pattern: 'useMyCustomHook|useMyOtherCustomHook' } }],
111 | errors: [
112 | getError("'unusedVar'"),
113 | getError("'unusedVar'", { hook: 'useLayoutEffect' }),
114 | getError("'unusedVar'", { hook: 'useMyCustomHook' })
115 | ]
116 | },
117 | {
118 | code: `
119 | useEffect(() => {
120 | document.title = usedVar;
121 | }, [usedVar, unusedVar]);
122 |
123 | useLayoutEffect(() => {
124 | document.title = usedVar;
125 | }, [usedVar, unusedVar]);
126 |
127 | useMyOtherCustomHook(() => {
128 | document.title = usedVar;
129 | }, [usedVar, unusedVar]);
130 | `,
131 | options: [
132 | {
133 | additionalHooks: {
134 | pattern: 'useMyCustomHook|useMyOtherCustomHook',
135 | replace: true
136 | }
137 | }
138 | ],
139 | errors: [getError("'unusedVar'", { hook: 'useMyOtherCustomHook' })]
140 | }
141 | ]
142 | });
143 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "emitDeclarationOnly": true,
4 | "declaration": true,
5 | "declarationDir": "./types",
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": true,
8 | "moduleResolution": "bundler",
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "target": "esnext",
12 | "esModuleInterop": true
13 | },
14 | "exclude": ["examples/", "types/"]
15 | }
16 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { Linter } from 'eslint';
2 | declare const plugin: {
3 | meta: {
4 | name: string;
5 | version: string;
6 | };
7 | configs: {
8 | recommended: Linter.Config;
9 | 'recommended-legacy': Linter.LegacyConfig;
10 | };
11 | rules: {
12 | 'no-unused-deps': import("eslint").Rule.RuleModule;
13 | };
14 | };
15 | export default plugin;
16 |
--------------------------------------------------------------------------------
/types/rules/no-unused-deps.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Rule to check unused dependencies in React Hooks
3 | * @author Zheng Song
4 | */
5 | import type { Rule } from 'eslint';
6 | declare const rule: Rule.RuleModule;
7 | export default rule;
8 |
--------------------------------------------------------------------------------