├── .gitignore
├── test
├── fixtures
│ ├── eslint.config.test.mjs
│ ├── eslint.config.test.cjs
│ ├── component
│ │ ├── invalid.js
│ │ └── valid.js
│ └── hooks
│ │ ├── invalid.js
│ │ └── valid.js
├── index.test.mjs
└── __snapshots__
│ └── index.test.mjs.snap
├── .editorconfig
├── eslint.config.js
├── README.md
├── .github
└── workflows
│ └── nodejs.yml
├── LICENSE
├── package.json
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/test/fixtures/eslint.config.test.mjs:
--------------------------------------------------------------------------------
1 | import preact from 'eslint-config-preact';
2 |
3 | export default [
4 | ...preact
5 | ];
6 |
--------------------------------------------------------------------------------
/test/fixtures/eslint.config.test.cjs:
--------------------------------------------------------------------------------
1 | const {default: preact} = require('eslint-config-preact');
2 |
3 | module.exports = [
4 | ...preact
5 | ];
6 |
--------------------------------------------------------------------------------
/test/fixtures/component/invalid.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'preact';
2 |
3 | export class A extends Component {
4 | constructor() {
5 | super();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/component/valid.js:
--------------------------------------------------------------------------------
1 | import { h, Component } from 'preact';
2 |
3 | export class A extends Component {
4 | handleClick = () => {};
5 |
6 | renderBlah() {}
7 |
8 | render() {
9 | return (
10 |
hello
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [{*.md,*.json,.*rc,*.yml}]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/test/fixtures/hooks/invalid.js:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect } from 'preact/hooks';
2 |
3 | export function Foo () {
4 | const [value, setValue] = useState(0);
5 | const increment = useCallback(() => setValue(value + 1));
6 |
7 | useEffect(() => {
8 | console.log(value);
9 | }, []);
10 |
11 | return ;
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/hooks/valid.js:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect } from 'preact/hooks';
2 |
3 | export function Foo () {
4 | const [value, setValue] = useState(0);
5 | const increment = useCallback(() => setValue(v => v + 1), [setValue]);
6 |
7 | useEffect(() => {
8 | console.log(value);
9 | }, [value]);
10 |
11 | return ;
12 | }
13 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import eslintJS from '@eslint/js';
2 | import globals from 'globals';
3 |
4 | export default [
5 | {
6 | ignores: [
7 | "dist/**/*",
8 | "test/fixtures/**",
9 | "test/__snapshots__/**"
10 | ]
11 | },
12 | eslintJS.configs.recommended,
13 | {
14 | languageOptions: {
15 | globals: {
16 | ...globals.node
17 | }
18 | },
19 | rules: {
20 | "no-empty": "off"
21 | }
22 | }
23 | ];
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eslint-config-preact
2 |
3 | An unopinionated baseline ESLint configuration for Preact and Preact CLI codebases.
4 |
5 | It helps you avoid bugs, and lets you know when there's a more optimal way to do things.
6 |
7 | ✅ **What's included:** sensible defaults for modern JS, JSX, Preact, Jest and Mocha.
8 |
9 | ⛔️ **What's not included:** no stylistic or subjective rules are provided.
10 |
11 | ## Installation
12 |
13 | Install eslint and this config:
14 |
15 | ```
16 | npm i -D eslint eslint-config-preact
17 | ```
18 |
19 | Now in your `eslint.config.js`:
20 |
21 | ```ts
22 | import preact from 'eslint-config-preact';
23 | export default [
24 | ...preact
25 | ];
26 | ```
27 |
28 | Or in CommonJS `eslint.config.cjs`:
29 |
30 | ```ts
31 | const {default: preact} = require('eslint-config-preact');
32 |
33 | module.exports = [
34 | ...preact
35 | ];
36 | ```
37 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 |
8 | jobs:
9 | build:
10 | name: ESLint ${{ matrix.eslint }} on Node ${{ matrix.node }}
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | node:
16 | - 20
17 | - 23
18 | eslint:
19 | - 8
20 | - local
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node ${{ matrix.node }}
25 | uses: actions/setup-node@v1
26 | with:
27 | node-version: ${{ matrix.node }}
28 | - run: npm install
29 | - name: Install ESLint ${{ matrix.eslint }}
30 | if: ${{ matrix.eslint != 'local' }}
31 | run: npm install eslint@${{ matrix.eslint }} --no-save
32 | - run: npm run build --if-present
33 | - run: npm test
34 | env:
35 | CI: true
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-present The Preact Authors
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-preact",
3 | "version": "2.0.0",
4 | "type": "module",
5 | "description": "Unopinionated base ESLint configuration for Preact and Preact CLI projects.",
6 | "keywords": [
7 | "eslint",
8 | "eslint-config"
9 | ],
10 | "repository": "preactjs/eslint-config-preact",
11 | "license": "MIT",
12 | "author": "The Preact Authors (https://preactjs.com)",
13 | "main": "./index.js",
14 | "exports": {
15 | ".": {
16 | "default": "./index.js"
17 | }
18 | },
19 | "files": [
20 | "index.js"
21 | ],
22 | "scripts": {
23 | "lint": "eslint .",
24 | "test": "npm run lint && vitest run",
25 | "prepare": "npm test",
26 | "release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
27 | },
28 | "dependencies": {
29 | "@babel/core": "^7.13.16",
30 | "@babel/eslint-parser": "^7.27.5",
31 | "@babel/plugin-syntax-class-properties": "^7.12.13",
32 | "@babel/plugin-syntax-jsx": "^7.12.13",
33 | "@eslint/js": "^9.29.0",
34 | "eslint-plugin-compat": "^6.0.2",
35 | "eslint-plugin-react": "^7.37.5",
36 | "eslint-plugin-react-hooks": "^5.2.0",
37 | "globals": "^16.2.0"
38 | },
39 | "devDependencies": {
40 | "@types/eslint": "^9.6.1",
41 | "@types/node": "^24.0.4",
42 | "eslint": "^9.30.0",
43 | "vitest": "^3.2.4"
44 | },
45 | "peerDependencies": {
46 | "eslint": "^8.57.1 || ^9.0.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/index.test.mjs:
--------------------------------------------------------------------------------
1 | import { describe, test, expect, beforeAll } from 'vitest';
2 | import eslint from 'eslint';
3 | import path from 'path';
4 | import fs from 'fs';
5 |
6 | const configs = [
7 | 'eslint.config.test.mjs',
8 | 'eslint.config.test.cjs'
9 | ];
10 | const FIXTURES_PATH = path.resolve(__dirname, 'fixtures');
11 | const FIXTURES = fs.readdirSync(FIXTURES_PATH, {withFileTypes: true})
12 | .filter((dir) => dir.isDirectory())
13 | .map((dir) => dir.name);
14 |
15 | async function lint (file, cli) {
16 | const lintResults = await cli.lintFiles(file);
17 | const formatter = await cli.loadFormatter('json');
18 | const report = JSON.parse(formatter.format(lintResults));
19 | return {
20 | report: report.map((r) => ({
21 | ...r,
22 | messages: r.messages.map((m) => ({
23 | ...m,
24 | suggestions: undefined, // since ESLint 8 doesn't return these
25 | })),
26 | filePath: ''
27 | })),
28 | ...lintResults[0],
29 | };
30 | }
31 |
32 | for (const config of configs) {
33 | describe(`config: ${config}`, () => {
34 | let cli;
35 |
36 | beforeAll(async () => {
37 | const isESLintRC = config.endsWith('.json');
38 | const ESLint = await eslint.loadESLint({
39 | useFlatConfig: !isESLintRC,
40 | });
41 | cli = new ESLint({
42 | cwd: FIXTURES_PATH,
43 | fix: false,
44 | cache: false,
45 | errorOnUnmatchedPattern: true,
46 | overrideConfigFile: path.join(FIXTURES_PATH, config),
47 | ...(isESLintRC ? {useEslintrc: false} : {})
48 | });
49 | });
50 |
51 | for (const dir of FIXTURES) {
52 | const p = f => path.join(dir, f);
53 |
54 | describe(`Fixture: ${dir}`, () => {
55 | test('valid', async () => {
56 | const report = await lint(p('valid.js'), cli);
57 | expect(report).toHaveProperty('errorCount', 0);
58 | expect(report).toHaveProperty('warningCount', 0);
59 | expect(report.report).toMatchSnapshot(`fixtures/${dir}/valid.js`);
60 | });
61 | test('invalid', async () => {
62 | const report = await lint(p('invalid.js'), cli);
63 | expect(report.errorCount + report.warningCount).toBeGreaterThan(0);
64 | expect(report.report).toMatchSnapshot(`fixtures/${dir}/invalid.js`);
65 | });
66 | });
67 | }
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import parser from '@babel/eslint-parser';
2 | import eslintJS from '@eslint/js';
3 | import compat from 'eslint-plugin-compat';
4 | import react from 'eslint-plugin-react';
5 | import reactHooks from 'eslint-plugin-react-hooks';
6 | import knownGlobals from 'globals';
7 |
8 | const configs = [
9 | eslintJS.configs.recommended,
10 | {
11 | languageOptions: {
12 | // TODO: this is really only required for class property initializer methods, which are seeing declining usage.
13 | // At some point, we should un-ship the custom parser and let ESLint use esprima.
14 | parser,
15 | // Currently ignored due to the custom parser.
16 | parserOptions: {
17 | ecmaVersion: 2020,
18 | sourceType: 'module',
19 | ecmaFeatures: {
20 | modules: true,
21 | impliedStrict: true,
22 | jsx: true
23 | },
24 | requireConfigFile: false,
25 | babelOptions: {
26 | plugins: [
27 | '@babel/plugin-syntax-class-properties',
28 | '@babel/plugin-syntax-jsx'
29 | ]
30 | }
31 | },
32 | globals: {
33 | ...knownGlobals.browser,
34 | ...knownGlobals.es2015,
35 | ...knownGlobals.node,
36 | expect: true,
37 | browser: true,
38 | global: true
39 | }
40 | },
41 | plugins: {
42 | compat,
43 | react,
44 | 'react-hooks': reactHooks
45 | },
46 | settings: {
47 | // Preact CLI provides these defaults
48 | targets: ['last 2 versions'],
49 | polyfills: ['fetch', 'Promise'],
50 | react: {
51 | // eslint-plugin-preact interprets this as "h.createElement",
52 | // however we only care about marking h() as being a used variable.
53 | pragma: 'h',
54 | // We use "react 16.0" to avoid pushing folks to UNSAFE_ methods.
55 | version: '16.0'
56 | }
57 | },
58 | rules: {
59 | /**
60 | * Preact / JSX rules
61 | */
62 | 'react/no-deprecated': 2,
63 | 'react/react-in-jsx-scope': 0, // handled this automatically
64 | 'react/display-name': [1, { ignoreTranspilerName: false }],
65 | 'react/jsx-no-bind': [1, {
66 | ignoreRefs: true,
67 | allowFunctions: true,
68 | allowArrowFunctions: true
69 | }],
70 | 'react/jsx-no-comment-textnodes': 2,
71 | 'react/jsx-no-duplicate-props': 2,
72 | 'react/jsx-no-target-blank': 2,
73 | 'react/jsx-no-undef': 2,
74 | 'react/jsx-uses-react': 2, // debatable
75 | 'react/jsx-uses-vars': 2,
76 | 'react/jsx-key': [2, { checkFragmentShorthand: true }],
77 | 'react/self-closing-comp': 2,
78 | 'react/prefer-es6-class': 2,
79 | 'react/prefer-stateless-function': 1,
80 | 'react/require-render-return': 2,
81 | 'react/no-danger': 1,
82 | // Legacy APIs not supported in Preact:
83 | 'react/no-did-mount-set-state': 2,
84 | 'react/no-did-update-set-state': 2,
85 | 'react/no-find-dom-node': 2,
86 | 'react/no-is-mounted': 2,
87 | 'react/no-string-refs': 2,
88 |
89 | /**
90 | * Hooks
91 | */
92 | 'react-hooks/rules-of-hooks': 2,
93 | 'react-hooks/exhaustive-deps': 1,
94 |
95 | /**
96 | * General JavaScript error avoidance
97 | */
98 | 'constructor-super': 2,
99 | 'no-caller': 2,
100 | 'no-const-assign': 2,
101 | 'no-delete-var': 2,
102 | 'no-dupe-class-members': 2,
103 | 'no-dupe-keys': 2,
104 | 'no-duplicate-imports': 2,
105 | 'no-else-return': 1,
106 | 'no-empty-pattern': 0,
107 | 'no-empty': 0,
108 | 'no-iterator': 2,
109 | 'no-lonely-if': 2,
110 | 'no-multi-str': 1,
111 | 'no-new-wrappers': 2,
112 | 'no-proto': 2,
113 | 'no-redeclare': 2,
114 | 'no-shadow-restricted-names': 2,
115 | 'no-shadow': 0,
116 | 'no-this-before-super': 2,
117 | 'no-undef-init': 2,
118 | 'no-unneeded-ternary': 2,
119 | 'no-unused-vars': [2, {
120 | args: 'after-used',
121 | ignoreRestSiblings: true
122 | }],
123 | 'no-useless-call': 1,
124 | 'no-useless-computed-key': 1,
125 | 'no-useless-concat': 1,
126 | 'no-useless-constructor': 1,
127 | 'no-useless-escape': 1,
128 | 'no-useless-rename': 1,
129 | 'no-var': 1,
130 | 'no-with': 2,
131 |
132 | /**
133 | * General JavaScript stylistic rules (disabled)
134 | */
135 | strict: [2, 'never'], // assume type=module output (cli default)
136 | 'object-shorthand': 1,
137 | 'prefer-arrow-callback': 1,
138 | 'prefer-rest-params': 1,
139 | 'prefer-spread': 1,
140 | 'prefer-template': 1,
141 | quotes: [0, 'single', {
142 | avoidEscape: true,
143 | allowTemplateLiterals: true
144 | }],
145 | radix: 1, // parseInt(x, 10)
146 | 'unicode-bom': 2,
147 | 'valid-jsdoc': 0
148 | }
149 | }
150 | ];
151 |
152 | export default configs;
153 |
--------------------------------------------------------------------------------
/test/__snapshots__/index.test.mjs.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`config: eslint.config.test.cjs > Fixture: component > invalid > fixtures/component/invalid.js 1`] = `
4 | [
5 | {
6 | "errorCount": 0,
7 | "fatalErrorCount": 0,
8 | "filePath": "",
9 | "fixableErrorCount": 0,
10 | "fixableWarningCount": 0,
11 | "messages": [
12 | {
13 | "column": 8,
14 | "endColumn": 2,
15 | "endLine": 7,
16 | "line": 3,
17 | "message": "Component should be written as a pure function",
18 | "messageId": "componentShouldBePure",
19 | "nodeType": "ClassDeclaration",
20 | "ruleId": "react/prefer-stateless-function",
21 | "severity": 1,
22 | "suggestions": undefined,
23 | },
24 | {
25 | "column": 2,
26 | "endColumn": 3,
27 | "endLine": 6,
28 | "line": 4,
29 | "message": "Useless constructor.",
30 | "messageId": "noUselessConstructor",
31 | "nodeType": "MethodDefinition",
32 | "ruleId": "no-useless-constructor",
33 | "severity": 1,
34 | "suggestions": undefined,
35 | },
36 | ],
37 | "source": "import { Component } from 'preact';
38 |
39 | export class A extends Component {
40 | constructor() {
41 | super();
42 | }
43 | }
44 | ",
45 | "suppressedMessages": [],
46 | "usedDeprecatedRules": [],
47 | "warningCount": 2,
48 | },
49 | ]
50 | `;
51 |
52 | exports[`config: eslint.config.test.cjs > Fixture: component > valid > fixtures/component/valid.js 1`] = `
53 | [
54 | {
55 | "errorCount": 0,
56 | "fatalErrorCount": 0,
57 | "filePath": "",
58 | "fixableErrorCount": 0,
59 | "fixableWarningCount": 0,
60 | "messages": [],
61 | "suppressedMessages": [],
62 | "usedDeprecatedRules": [],
63 | "warningCount": 0,
64 | },
65 | ]
66 | `;
67 |
68 | exports[`config: eslint.config.test.cjs > Fixture: hooks > invalid > fixtures/hooks/invalid.js 1`] = `
69 | [
70 | {
71 | "errorCount": 0,
72 | "fatalErrorCount": 0,
73 | "filePath": "",
74 | "fixableErrorCount": 0,
75 | "fixableWarningCount": 0,
76 | "messages": [
77 | {
78 | "column": 20,
79 | "endColumn": 31,
80 | "endLine": 5,
81 | "line": 5,
82 | "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?",
83 | "nodeType": "Identifier",
84 | "ruleId": "react-hooks/exhaustive-deps",
85 | "severity": 1,
86 | "suggestions": undefined,
87 | },
88 | {
89 | "column": 5,
90 | "endColumn": 7,
91 | "endLine": 9,
92 | "line": 9,
93 | "message": "React Hook useEffect has a missing dependency: 'value'. Either include it or remove the dependency array.",
94 | "nodeType": "ArrayExpression",
95 | "ruleId": "react-hooks/exhaustive-deps",
96 | "severity": 1,
97 | "suggestions": undefined,
98 | },
99 | ],
100 | "source": "import { useState, useCallback, useEffect } from 'preact/hooks';
101 |
102 | export function Foo () {
103 | const [value, setValue] = useState(0);
104 | const increment = useCallback(() => setValue(value + 1));
105 |
106 | useEffect(() => {
107 | console.log(value);
108 | }, []);
109 |
110 | return ;
111 | }
112 | ",
113 | "suppressedMessages": [],
114 | "usedDeprecatedRules": [],
115 | "warningCount": 2,
116 | },
117 | ]
118 | `;
119 |
120 | exports[`config: eslint.config.test.cjs > Fixture: hooks > valid > fixtures/hooks/valid.js 1`] = `
121 | [
122 | {
123 | "errorCount": 0,
124 | "fatalErrorCount": 0,
125 | "filePath": "",
126 | "fixableErrorCount": 0,
127 | "fixableWarningCount": 0,
128 | "messages": [],
129 | "suppressedMessages": [],
130 | "usedDeprecatedRules": [],
131 | "warningCount": 0,
132 | },
133 | ]
134 | `;
135 |
136 | exports[`config: eslint.config.test.mjs > Fixture: component > invalid > fixtures/component/invalid.js 1`] = `
137 | [
138 | {
139 | "errorCount": 0,
140 | "fatalErrorCount": 0,
141 | "filePath": "",
142 | "fixableErrorCount": 0,
143 | "fixableWarningCount": 0,
144 | "messages": [
145 | {
146 | "column": 8,
147 | "endColumn": 2,
148 | "endLine": 7,
149 | "line": 3,
150 | "message": "Component should be written as a pure function",
151 | "messageId": "componentShouldBePure",
152 | "nodeType": "ClassDeclaration",
153 | "ruleId": "react/prefer-stateless-function",
154 | "severity": 1,
155 | "suggestions": undefined,
156 | },
157 | {
158 | "column": 2,
159 | "endColumn": 3,
160 | "endLine": 6,
161 | "line": 4,
162 | "message": "Useless constructor.",
163 | "messageId": "noUselessConstructor",
164 | "nodeType": "MethodDefinition",
165 | "ruleId": "no-useless-constructor",
166 | "severity": 1,
167 | "suggestions": undefined,
168 | },
169 | ],
170 | "source": "import { Component } from 'preact';
171 |
172 | export class A extends Component {
173 | constructor() {
174 | super();
175 | }
176 | }
177 | ",
178 | "suppressedMessages": [],
179 | "usedDeprecatedRules": [],
180 | "warningCount": 2,
181 | },
182 | ]
183 | `;
184 |
185 | exports[`config: eslint.config.test.mjs > Fixture: component > valid > fixtures/component/valid.js 1`] = `
186 | [
187 | {
188 | "errorCount": 0,
189 | "fatalErrorCount": 0,
190 | "filePath": "",
191 | "fixableErrorCount": 0,
192 | "fixableWarningCount": 0,
193 | "messages": [],
194 | "suppressedMessages": [],
195 | "usedDeprecatedRules": [],
196 | "warningCount": 0,
197 | },
198 | ]
199 | `;
200 |
201 | exports[`config: eslint.config.test.mjs > Fixture: hooks > invalid > fixtures/hooks/invalid.js 1`] = `
202 | [
203 | {
204 | "errorCount": 0,
205 | "fatalErrorCount": 0,
206 | "filePath": "",
207 | "fixableErrorCount": 0,
208 | "fixableWarningCount": 0,
209 | "messages": [
210 | {
211 | "column": 20,
212 | "endColumn": 31,
213 | "endLine": 5,
214 | "line": 5,
215 | "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?",
216 | "nodeType": "Identifier",
217 | "ruleId": "react-hooks/exhaustive-deps",
218 | "severity": 1,
219 | "suggestions": undefined,
220 | },
221 | {
222 | "column": 5,
223 | "endColumn": 7,
224 | "endLine": 9,
225 | "line": 9,
226 | "message": "React Hook useEffect has a missing dependency: 'value'. Either include it or remove the dependency array.",
227 | "nodeType": "ArrayExpression",
228 | "ruleId": "react-hooks/exhaustive-deps",
229 | "severity": 1,
230 | "suggestions": undefined,
231 | },
232 | ],
233 | "source": "import { useState, useCallback, useEffect } from 'preact/hooks';
234 |
235 | export function Foo () {
236 | const [value, setValue] = useState(0);
237 | const increment = useCallback(() => setValue(value + 1));
238 |
239 | useEffect(() => {
240 | console.log(value);
241 | }, []);
242 |
243 | return ;
244 | }
245 | ",
246 | "suppressedMessages": [],
247 | "usedDeprecatedRules": [],
248 | "warningCount": 2,
249 | },
250 | ]
251 | `;
252 |
253 | exports[`config: eslint.config.test.mjs > Fixture: hooks > valid > fixtures/hooks/valid.js 1`] = `
254 | [
255 | {
256 | "errorCount": 0,
257 | "fatalErrorCount": 0,
258 | "filePath": "",
259 | "fixableErrorCount": 0,
260 | "fixableWarningCount": 0,
261 | "messages": [],
262 | "suppressedMessages": [],
263 | "usedDeprecatedRules": [],
264 | "warningCount": 0,
265 | },
266 | ]
267 | `;
268 |
--------------------------------------------------------------------------------