├── .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 | --------------------------------------------------------------------------------