├── .editorconfig ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test ├── __snapshots__ └── index.test.js.snap ├── fixtures ├── .eslintrc ├── component │ ├── invalid.js │ └── valid.js └── hooks │ ├── invalid.js │ └── valid.js └── index.test.js /.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 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Use Node.js ${{ matrix.node-version }} 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: '20.x' 16 | - run: npm install 17 | - run: npm run build --if-present 18 | - run: npm test 19 | env: 20 | CI: true 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 `package.json`: 20 | 21 | ```` 22 | { 23 | "eslintConfig": { 24 | "extends": "preact" 25 | } 26 | } 27 | ```` 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // TODO: this is really only required for class property initializer methods, which are seeing declining usage. 3 | // At some point, we should un-ship the custom parser and let ESLint use esprima. 4 | parser: require.resolve('@babel/eslint-parser'), 5 | 6 | // Currently ignored due to the custom parser. 7 | parserOptions: { 8 | ecmaVersion: 2020, 9 | sourceType: 'module', 10 | ecmaFeatures: { 11 | modules: true, 12 | impliedStrict: true, 13 | jsx: true 14 | }, 15 | requireConfigFile: false, 16 | babelOptions: { 17 | plugins: [ 18 | '@babel/plugin-syntax-class-properties', 19 | '@babel/plugin-syntax-jsx' 20 | ] 21 | } 22 | }, 23 | 24 | // We don't use plugin:react/recommended here to avoid React-specific rules. 25 | extends: [ 26 | 'eslint:recommended' 27 | ], 28 | 29 | plugins: [ 30 | 'compat', 31 | 'react', 32 | 'react-hooks' 33 | ], 34 | 35 | env: { 36 | browser: true, 37 | es6: true, 38 | node: true 39 | }, 40 | 41 | globals: { 42 | expect: true, 43 | browser: true, 44 | global: true 45 | }, 46 | 47 | settings: { 48 | // Preact CLI provides these defaults 49 | targets: ['last 2 versions'], 50 | polyfills: ['fetch', 'Promise'], 51 | react: { 52 | // eslint-plugin-preact interprets this as "h.createElement", 53 | // however we only care about marking h() as being a used variable. 54 | pragma: 'h', 55 | // We use "react 16.0" to avoid pushing folks to UNSAFE_ methods. 56 | version: '16.0' 57 | } 58 | }, 59 | 60 | rules: { 61 | /** 62 | * Preact / JSX rules 63 | */ 64 | 'react/no-deprecated': 2, 65 | 'react/react-in-jsx-scope': 0, // handled this automatically 66 | 'react/display-name': [1, { ignoreTranspilerName: false }], 67 | 'react/jsx-no-bind': [1, { 68 | ignoreRefs: true, 69 | allowFunctions: true, 70 | allowArrowFunctions: true 71 | }], 72 | 'react/jsx-no-comment-textnodes': 2, 73 | 'react/jsx-no-duplicate-props': 2, 74 | 'react/jsx-no-target-blank': 2, 75 | 'react/jsx-no-undef': 2, 76 | 'react/jsx-tag-spacing': [2, { beforeSelfClosing: 'always' }], 77 | 'react/jsx-uses-react': 2, // debatable 78 | 'react/jsx-uses-vars': 2, 79 | 'react/jsx-key': [2, { checkFragmentShorthand: true }], 80 | 'react/self-closing-comp': 2, 81 | 'react/prefer-es6-class': 2, 82 | 'react/prefer-stateless-function': 1, 83 | 'react/require-render-return': 2, 84 | 'react/no-danger': 1, 85 | // Legacy APIs not supported in Preact: 86 | 'react/no-did-mount-set-state': 2, 87 | 'react/no-did-update-set-state': 2, 88 | 'react/no-find-dom-node': 2, 89 | 'react/no-is-mounted': 2, 90 | 'react/no-string-refs': 2, 91 | 92 | /** 93 | * Hooks 94 | */ 95 | 'react-hooks/rules-of-hooks': 2, 96 | 'react-hooks/exhaustive-deps': 1, 97 | 98 | /** 99 | * General JavaScript error avoidance 100 | */ 101 | 'constructor-super': 2, 102 | 'no-caller': 2, 103 | 'no-const-assign': 2, 104 | 'no-delete-var': 2, 105 | 'no-dupe-class-members': 2, 106 | 'no-dupe-keys': 2, 107 | 'no-duplicate-imports': 2, 108 | 'no-else-return': 1, 109 | 'no-empty-pattern': 0, 110 | 'no-empty': 0, 111 | 'no-extra-parens': 0, 112 | 'no-iterator': 2, 113 | 'no-lonely-if': 2, 114 | 'no-mixed-spaces-and-tabs': [1, 'smart-tabs'], 115 | 'no-multi-str': 1, 116 | 'no-new-wrappers': 2, 117 | 'no-proto': 2, 118 | 'no-redeclare': 2, 119 | 'no-shadow-restricted-names': 2, 120 | 'no-shadow': 0, 121 | 'no-spaced-func': 2, 122 | 'no-this-before-super': 2, 123 | 'no-undef-init': 2, 124 | 'no-unneeded-ternary': 2, 125 | 'no-unused-vars': [2, { 126 | args: 'after-used', 127 | ignoreRestSiblings: true 128 | }], 129 | 'no-useless-call': 1, 130 | 'no-useless-computed-key': 1, 131 | 'no-useless-concat': 1, 132 | 'no-useless-constructor': 1, 133 | 'no-useless-escape': 1, 134 | 'no-useless-rename': 1, 135 | 'no-var': 1, 136 | 'no-with': 2, 137 | 138 | /** 139 | * General JavaScript stylistic rules (disabled) 140 | */ 141 | semi: 0, 142 | strict: [2, 'never'], // assume type=module output (cli default) 143 | 'object-curly-spacing': [0, 'always'], 144 | 'rest-spread-spacing': 0, 145 | 'space-before-function-paren': [0, 'always'], 146 | 'space-in-parens': [0, 'never'], 147 | 'object-shorthand': 1, 148 | 'prefer-arrow-callback': 1, 149 | 'prefer-rest-params': 1, 150 | 'prefer-spread': 1, 151 | 'prefer-template': 1, 152 | quotes: [0, 'single', { 153 | avoidEscape: true, 154 | allowTemplateLiterals: true 155 | }], 156 | 'quote-props': [2, 'as-needed'], 157 | radix: 1, // parseInt(x, 10) 158 | 'unicode-bom': 2, 159 | 'valid-jsdoc': 0 160 | } 161 | }; 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-preact", 3 | "version": "1.5.0", 4 | "description": "Unopinionated base ESLint configuration for Preact and Preact CLI projects.", 5 | "keywords": [ 6 | "eslint", 7 | "eslint-config" 8 | ], 9 | "repository": "preactjs/eslint-config-preact", 10 | "license": "MIT", 11 | "author": "The Preact Authors (https://preactjs.com)", 12 | "main": "dist/eslint-config-preact.js", 13 | "module": "dist/eslint-config-preact.module.js", 14 | "exports": { 15 | ".": { 16 | "import": "./dist/eslint-config-preact.module.js", 17 | "require": "./dist/eslint-config-preact.js" 18 | } 19 | }, 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "test": "eslint index.js && npm run -s build && jest", 25 | "build": "microbundle -f es,cjs --target node --no-sourcemap index.js", 26 | "prepare": "npm run -s build && npm test", 27 | "release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" 28 | }, 29 | "eslintConfig": { 30 | "extends": "eslint:recommended", 31 | "parserOptions": { 32 | "ecmaVersion": 2020, 33 | "sourceType": "module" 34 | }, 35 | "env": { 36 | "node": true, 37 | "jest": true 38 | }, 39 | "rules": { 40 | "no-empty": 0 41 | }, 42 | "ignorePatterns": [ 43 | "test/fixtures/**", 44 | "test/__snapshots__/**" 45 | ] 46 | }, 47 | "dependencies": { 48 | "@babel/core": "^7.13.16", 49 | "@babel/eslint-parser": "^7.13.14", 50 | "@babel/plugin-syntax-class-properties": "^7.12.13", 51 | "@babel/plugin-syntax-jsx": "^7.12.13", 52 | "eslint-plugin-compat": "^4.0.0", 53 | "eslint-plugin-react": "^7.27.0", 54 | "eslint-plugin-react-hooks": "^4.3.0" 55 | }, 56 | "devDependencies": { 57 | "@types/eslint": "^8.2.0", 58 | "@types/jest": "^27.0.2", 59 | "@types/node": "^12.20.37", 60 | "eslint": "^8.2.0", 61 | "jest": "^27.3.1", 62 | "microbundle": "^0.14.1" 63 | }, 64 | "peerDependencies": { 65 | "eslint": "6.x || 7.x || 8.x" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Fixture: component invalid: fixtures/component/invalid.js 1`] = ` 4 | " --- 5 | message: Component should be written as a pure function 6 | severity: warning 7 | data: 8 | line: 3 9 | column: 8 10 | ruleId: react/prefer-stateless-function 11 | messages: 12 | - message: Useless constructor. 13 | severity: warning 14 | data: 15 | line: 4 16 | column: 2 17 | ruleId: no-useless-constructor 18 | ... 19 | " 20 | `; 21 | 22 | exports[`Fixture: component valid: fixtures/component/valid.js 1`] = `""`; 23 | 24 | exports[`Fixture: hooks invalid: fixtures/hooks/invalid.js 1`] = ` 25 | " --- 26 | message: >- 27 | React Hook useCallback does nothing when called with only one argument. Did 28 | you forget to pass an array of dependencies? 29 | severity: warning 30 | data: 31 | line: 5 32 | column: 20 33 | ruleId: react-hooks/exhaustive-deps 34 | messages: 35 | - message: >- 36 | React Hook useEffect has a missing dependency: 'value'. Either include it 37 | or remove the dependency array. 38 | severity: warning 39 | data: 40 | line: 9 41 | column: 5 42 | ruleId: react-hooks/exhaustive-deps 43 | ... 44 | " 45 | `; 46 | 47 | exports[`Fixture: hooks valid: fixtures/hooks/valid.js 1`] = `""`; 48 | -------------------------------------------------------------------------------- /test/fixtures/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../..", 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /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 |