@jchiam/eslint-config 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ name: Node.js ${{ matrix.node-version }}
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ node-version: [20, 22, 24]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+
20
+ - name: Set up Node.js
21
+ uses: actions/setup-node@v6
22
+ with:
23
+ node-version: ${{ matrix.node-version }}
24
+ cache: npm
25
+
26
+ - name: Install dependencies
27
+ run: npm ci
28
+
29
+ - name: Validate configs load
30
+ run: |
31
+ node --input-type=module --eval "
32
+ import recommended from './recommended.js';
33
+ import react from './react.js';
34
+ if (!Array.isArray(recommended)) throw new Error('recommended.js must export an array');
35
+ if (!Array.isArray(react)) throw new Error('react.js must export an array');
36
+ console.log('recommended.js: ok (' + recommended.length + ' config objects)');
37
+ console.log('react.js: ok (' + react.length + ' config objects)');
38
+ "
@@ -0,0 +1,26 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+
16
+ - name: Set up Node.js
17
+ uses: actions/setup-node@v6
18
+ with:
19
+ node-version: 24
20
+ registry-url: https://registry.npmjs.org
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Publish to npm
26
+ run: npm publish --access public --provenance
package/README.md CHANGED
@@ -2,48 +2,124 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@jchiam/eslint-config.svg)](https://npmjs.org/package/@jchiam/eslint-config)
4
4
 
5
- This is a shareable eslint config.
5
+ My personal shareable ESLint config. Targets TypeScript projects, with an optional React extension.
6
+
7
+ ## Requirements
8
+
9
+ - ESLint 9 (ESLint 10 not yet supported due to `eslint-plugin-import` peer dep constraints)
10
+ - `typescript-eslint` ^8
11
+ - `eslint-plugin-import` ^2.29
12
+
13
+ React projects additionally need:
14
+ - `eslint-plugin-react` ^7.32
15
+ - `eslint-plugin-react-hooks` ^5
6
16
 
7
17
  ## Usage
8
18
 
19
+ ```sh
20
+ npm i -D @jchiam/eslint-config eslint typescript-eslint eslint-plugin-import
9
21
  ```
10
- npm i -D @jchiam/eslint-config
22
+
23
+ Create `eslint.config.js` in your project root:
24
+
25
+ ```js
26
+ // eslint.config.js — TypeScript project
27
+ import jchiamConfig from '@jchiam/eslint-config';
28
+
29
+ export default [...jchiamConfig];
11
30
  ```
12
31
 
13
- Add `.eslintrc` to project.
14
- ```javascript
15
- {
16
- "extends": [
17
- "@jchiam/eslint-config/recommended",
18
- "@jchiam/eslint-config/react"
19
- ]
20
- "rules": {}
21
- }
32
+ ```js
33
+ // eslint.config.js — React + TypeScript project
34
+ import jchiamConfig from '@jchiam/eslint-config';
35
+ import jchiamReact from '@jchiam/eslint-config/react';
36
+
37
+ export default [...jchiamConfig, ...jchiamReact];
22
38
  ```
23
39
 
24
- Override rules as needed in local `.eslintrc`.
40
+ Override rules by appending a config object to the array:
41
+
42
+ ```js
43
+ import jchiamConfig from '@jchiam/eslint-config';
44
+
45
+ export default [
46
+ ...jchiamConfig,
47
+ {
48
+ rules: {
49
+ 'prefer-const': 'warn', // override to warn instead of error
50
+ },
51
+ },
52
+ ];
53
+ ```
54
+
55
+ ## What's included
56
+
57
+ ### `recommended.js`
58
+
59
+ - [`eslint:recommended`](https://eslint.org/docs/rules/) — ESLint core rules
60
+ - [`typescript-eslint`](https://typescript-eslint.io/) recommended rules
61
+ - [`eslint-plugin-import`](https://github.com/import-js/eslint-plugin-import) recommended + TypeScript resolver
62
+ - [`@stylistic/eslint-plugin`](https://eslint.style/) for formatting (indent, spacing, quotes, semi, etc.)
63
+ - Additional opinionated rules for best practices and ES6+
64
+
65
+ ### `react.js`
66
+
67
+ Extends `recommended.js` intent with:
68
+ - [`eslint-plugin-react`](https://github.com/jsx-eslint/eslint-plugin-react) recommended (with `react/prop-types` off for TypeScript)
69
+ - [`eslint-plugin-react-hooks`](https://github.com/facebook/react) rules of hooks + exhaustive deps
25
70
 
26
71
  ## Breaking Changes
27
72
 
73
+ ### v4 to v5
74
+
75
+ Migrated to [ESLint flat config](https://eslint.org/docs/latest/use/configure/configuration-files) (required for ESLint 9+). The `.eslintrc` format is no longer supported.
76
+
77
+ **Note:** delete your existing `node_modules` and `package-lock.json` before reinstalling — the old lockfile pins conflicting major versions and will cause resolution errors.
78
+
79
+ Install the new peer dependencies:
80
+
81
+ ```sh
82
+ npm i -D typescript-eslint
83
+ ```
84
+
85
+ Remove the old peer dependencies (now bundled or renamed):
86
+
87
+ ```sh
88
+ npm uninstall @typescript-eslint/eslint-plugin @typescript-eslint/parser
89
+ ```
90
+
91
+ Config format changes from `.eslintrc.js`:
92
+
93
+ ```js
94
+ // Before (.eslintrc.js)
95
+ module.exports = {
96
+ extends: ['@jchiam/eslint-config/recommended'],
97
+ };
98
+ ```
99
+
100
+ To `eslint.config.js`:
101
+
102
+ ```js
103
+ // After
104
+ import jchiamConfig from '@jchiam/eslint-config';
105
+ export default [...jchiamConfig];
106
+ ```
107
+
108
+ **Rule changes:**
109
+ - Formatting rules moved from ESLint core to `@stylistic/eslint-plugin` (same rules, prefixed with `@stylistic/`)
110
+ - `no-shadow` / `no-use-before-define` replaced by their `@typescript-eslint/*` equivalents (TS-aware, no false positives on type declarations)
111
+ - `no-new-object` renamed to `no-object-constructor`
112
+ - `vars-on-top` removed (redundant — `no-var` is enforced and `@typescript-eslint/no-use-before-define` covers the intent)
113
+ - `@typescript-eslint/indent` removed (was deprecated and broken; `@stylistic/indent` is used instead)
114
+
28
115
  ### v3 to v4
29
116
 
30
- The original config file has been split from `index.js` to `recommended.js` and `react.js`. Extending both config files is equivalent to the original usage. This was done to allow for non-React projects to extend only the base config file to avoid any React-related warnings from ESLint.
31
-
32
- From:
33
- ```javascript
34
- {
35
- "extends": "@jchiam",
36
- "rules": {}
37
- }
38
- ```
39
-
40
- To:
41
- ```javascript
42
- {
43
- "extends": [
44
- "@jchiam/eslint-config/recommended",
45
- "@jchiam/eslint-config/react"
46
- ]
47
- "rules": {}
48
- }
117
+ The original config file was split from `index.js` into `recommended.js` and `react.js` to allow non-React projects to use only the base config.
118
+
119
+ ```js
120
+ // Before
121
+ { "extends": "@jchiam" }
122
+
123
+ // After
124
+ { "extends": ["@jchiam/eslint-config/recommended", "@jchiam/eslint-config/react"] }
49
125
  ```
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@jchiam/eslint-config",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "my personal ESLint rules",
5
+ "type": "module",
5
6
  "main": "recommended.js",
6
7
  "scripts": {
7
8
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -11,16 +12,36 @@
11
12
  "url": "git+https://github.com/jchiam/eslint-config.git"
12
13
  },
13
14
  "author": "Jonathan Chiam",
15
+ "dependencies": {
16
+ "@eslint/js": "^9.0.0",
17
+ "@stylistic/eslint-plugin": "^5.0.0",
18
+ "globals": "^17.0.0"
19
+ },
14
20
  "peerDependencies": {
15
- "@typescript-eslint/eslint-plugin": "^5.0.0",
16
- "eslint-plugin-import": "^2.0.0",
17
- "eslint-plugin-jest": "^26.0.0",
18
- "eslint-plugin-react": "^7.0.0",
19
- "eslint-plugin-react-hooks": "^4.0.0"
21
+ "eslint": "^9.0.0 || ^10.0.0",
22
+ "typescript-eslint": "^8.0.0",
23
+ "eslint-plugin-import": "^2.29.0",
24
+ "eslint-plugin-jest": "^29.0.0",
25
+ "eslint-plugin-react": "^7.32.0",
26
+ "eslint-plugin-react-hooks": "^5.0.0"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "eslint-plugin-jest": {
30
+ "optional": true
31
+ },
32
+ "eslint-plugin-react": {
33
+ "optional": true
34
+ },
35
+ "eslint-plugin-react-hooks": {
36
+ "optional": true
37
+ }
20
38
  },
21
39
  "devDependencies": {
22
- "@typescript-eslint/parser": "^5.33.1",
23
- "eslint": "^8.22.0",
24
- "typescript": "^4.7.4"
40
+ "eslint": "^9.0.0",
41
+ "eslint-plugin-import": "^2.32.0",
42
+ "eslint-plugin-react": "^7.37.5",
43
+ "eslint-plugin-react-hooks": "^7.0.0",
44
+ "typescript": "^5.0.0",
45
+ "typescript-eslint": "^8.0.0"
25
46
  }
26
47
  }
package/react.js CHANGED
@@ -1,36 +1,37 @@
1
- module.exports = {
2
- "parser": "@typescript-eslint/parser",
3
- "extends": [
4
- "plugin:react/recommended",
5
- "plugin:import/react",
6
- ],
7
- "plugins": ["react-hooks"],
8
- "env": {
9
- "es6": true,
10
- "node": true,
11
- "browser": true
12
- },
13
- "parserOptions": {
14
- "ecmaVersion": 2018,
15
- "ecmaFeatures": {
16
- "jsx": true
17
- }
18
- },
19
- "rules": {
20
- "react/prop-types": "off",
21
- "react-hooks/rules-of-hooks": "error",
22
- "react-hooks/exhaustive-deps": "warn",
23
- "jsx-quotes": "error"
24
- },
25
- "settings": {
26
- "import/resolver": {
27
- "node": {
28
- "extensions": [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
29
- "moduleDirectory": ["node_modules", "src"]
30
- }
1
+ import reactPlugin from 'eslint-plugin-react';
2
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
3
+ import stylistic from '@stylistic/eslint-plugin';
4
+
5
+ export default [
6
+ reactPlugin.configs.flat.recommended,
7
+ {
8
+ plugins: {
9
+ '@stylistic': stylistic,
10
+ 'react-hooks': reactHooksPlugin,
11
+ },
12
+ languageOptions: {
13
+ parserOptions: {
14
+ ecmaFeatures: {
15
+ jsx: true,
16
+ },
17
+ },
31
18
  },
32
- "react": {
33
- "version": "detect"
34
- }
35
- }
36
- };
19
+ rules: {
20
+ 'react/prop-types': 'off',
21
+ 'react-hooks/rules-of-hooks': 'error',
22
+ 'react-hooks/exhaustive-deps': 'warn',
23
+ '@stylistic/jsx-quotes': 'error',
24
+ },
25
+ settings: {
26
+ 'import/resolver': {
27
+ node: {
28
+ extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts'],
29
+ moduleDirectory: ['node_modules', 'src'],
30
+ },
31
+ },
32
+ react: {
33
+ version: 'detect',
34
+ },
35
+ },
36
+ },
37
+ ];
package/recommended.js CHANGED
@@ -1,92 +1,111 @@
1
- module.exports = {
2
- "parser": "@typescript-eslint/parser",
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:import/recommended",
6
- "plugin:import/typescript",
7
- "plugin:@typescript-eslint/recommended"
8
- ],
9
- "env": {
10
- "es6": true,
11
- "node": true,
12
- "browser": true
1
+ import js from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+ import importPlugin from 'eslint-plugin-import';
4
+ import stylistic from '@stylistic/eslint-plugin';
5
+ import globals from 'globals';
6
+
7
+ export default [
8
+ js.configs.recommended,
9
+ ...tseslint.configs.recommended,
10
+ importPlugin.flatConfigs.recommended,
11
+ importPlugin.flatConfigs.typescript,
12
+ {
13
+ plugins: {
14
+ '@stylistic': stylistic,
15
+ },
16
+ languageOptions: {
17
+ globals: {
18
+ ...globals.es2020,
19
+ ...globals.node,
20
+ ...globals.browser,
21
+ },
22
+ parserOptions: {
23
+ ecmaVersion: 'latest',
24
+ },
25
+ },
26
+ rules: {
27
+ // TypeScript rules
28
+ '@typescript-eslint/array-type': ['error', { default: 'generic' }],
29
+ '@typescript-eslint/explicit-function-return-type': 'off',
30
+ '@typescript-eslint/no-explicit-any': 'off',
31
+ '@typescript-eslint/no-shadow': 'error',
32
+ '@typescript-eslint/no-use-before-define': 'error',
33
+
34
+ // Disable base rules superseded by TypeScript equivalents
35
+ 'no-shadow': 'off',
36
+ 'no-use-before-define': 'off',
37
+
38
+ // Best practice rules
39
+ 'camelcase': 'error',
40
+ 'eqeqeq': ['error', 'smart'],
41
+ 'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
42
+ 'max-depth': 'warn',
43
+ 'max-lines': ['warn', { skipBlankLines: true, skipComments: true }],
44
+ 'no-bitwise': 'error',
45
+ 'no-continue': 'error',
46
+ 'no-else-return': 'warn',
47
+ 'no-lonely-if': 'error',
48
+ 'no-nested-ternary': 'error',
49
+ 'no-object-constructor': 'error',
50
+ 'no-unneeded-ternary': 'error',
51
+ 'operator-assignment': 'error',
52
+ 'prefer-object-spread': 'error',
53
+
54
+ // ES6+ rules
55
+ 'no-duplicate-imports': 'error',
56
+ 'no-useless-computed-key': 'error',
57
+ 'no-var': 'error',
58
+ 'object-shorthand': 'error',
59
+ 'prefer-const': 'error',
60
+ 'prefer-destructuring': ['error', { array: false, object: true }],
61
+ 'prefer-numeric-literals': 'error',
62
+ 'prefer-template': 'error',
63
+
64
+ // Formatting (via @stylistic/eslint-plugin)
65
+ '@stylistic/array-bracket-spacing': 'error',
66
+ '@stylistic/arrow-parens': ['error', 'as-needed'],
67
+ '@stylistic/arrow-spacing': 'error',
68
+ '@stylistic/block-spacing': 'error',
69
+ '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
70
+ '@stylistic/comma-dangle': 'error',
71
+ '@stylistic/comma-spacing': 'error',
72
+ '@stylistic/comma-style': 'error',
73
+ '@stylistic/computed-property-spacing': 'error',
74
+ '@stylistic/eol-last': 'error',
75
+ '@stylistic/func-call-spacing': 'error',
76
+ '@stylistic/indent': ['error', 2, { SwitchCase: 1 }],
77
+ '@stylistic/key-spacing': 'error',
78
+ '@stylistic/keyword-spacing': 'error',
79
+ '@stylistic/lines-around-comment': ['error', { beforeBlockComment: true, allowBlockStart: true }],
80
+ '@stylistic/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
81
+ '@stylistic/newline-per-chained-call': ['error', { ignoreChainWithDepth: 2 }],
82
+ '@stylistic/no-multi-spaces': ['error', { ignoreEOLComments: true }],
83
+ '@stylistic/no-multiple-empty-lines': 'error',
84
+ '@stylistic/no-tabs': 'error',
85
+ '@stylistic/no-trailing-spaces': 'error',
86
+ '@stylistic/no-whitespace-before-property': 'error',
87
+ '@stylistic/nonblock-statement-body-position': 'error',
88
+ '@stylistic/object-curly-spacing': ['error', 'always'],
89
+ '@stylistic/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }],
90
+ '@stylistic/operator-linebreak': ['error', 'after'],
91
+ '@stylistic/quote-props': ['error', 'consistent-as-needed'],
92
+ '@stylistic/quotes': ['error', 'single'],
93
+ '@stylistic/rest-spread-spacing': 'error',
94
+ '@stylistic/semi': 'error',
95
+ '@stylistic/semi-style': 'error',
96
+ '@stylistic/space-before-blocks': 'error',
97
+ '@stylistic/space-before-function-paren': ['error', { anonymous: 'always', named: 'never', asyncArrow: 'always' }],
98
+ '@stylistic/space-in-parens': 'error',
99
+ '@stylistic/switch-colon-spacing': 'error',
100
+ '@stylistic/template-curly-spacing': 'error',
101
+ },
102
+ settings: {
103
+ 'import/resolver': {
104
+ node: {
105
+ extensions: ['.js', '.ts', '.d.ts'],
106
+ moduleDirectory: ['node_modules', 'src'],
107
+ },
108
+ },
109
+ },
13
110
  },
14
- "parserOptions": {
15
- "ecmaVersion": 2018
16
- },
17
- "rules": {
18
- "@typescript-eslint/array-type": ["error", { "default": "generic" }],
19
- "@typescript-eslint/explicit-function-return-type": "off",
20
- "@typescript-eslint/indent": ["error", 2, { "SwitchCase": 1 }],
21
- "@typescript-eslint/no-explicit-any": "off",
22
- "eqeqeq": ["error", "smart"],
23
- "no-else-return": "warn",
24
- "no-multi-spaces": ["error", { "ignoreEOLComments": true }],
25
- "vars-on-top": "warn",
26
- "no-shadow": "error",
27
- "no-use-before-define": "error",
28
- "array-bracket-spacing": "error",
29
- "block-spacing": "error",
30
- "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
31
- "camelcase": "error",
32
- "comma-dangle": "error",
33
- "comma-spacing": "error",
34
- "comma-style": "error",
35
- "computed-property-spacing": "error",
36
- "eol-last": "error",
37
- "func-call-spacing": "error",
38
- "func-style": ["error", "declaration", { "allowArrowFunctions": true }],
39
- "indent": ["error", 2, { "SwitchCase": 1 }],
40
- "key-spacing": "error",
41
- "keyword-spacing": "error",
42
- "lines-around-comment": ["error", { "beforeBlockComment": true, "allowBlockStart": true }],
43
- "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
44
- "max-depth": "warn",
45
- "max-lines": ["warn", { "skipBlankLines": true, "skipComments": true }],
46
- "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
47
- "no-bitwise": "error",
48
- "no-continue": "error",
49
- "no-lonely-if": "error",
50
- "no-multiple-empty-lines": "error",
51
- "no-nested-ternary": "error",
52
- "no-new-object": "error",
53
- "no-tabs": "error",
54
- "no-trailing-spaces": "error",
55
- "no-unneeded-ternary": "error",
56
- "no-whitespace-before-property": "error",
57
- "nonblock-statement-body-position": "error",
58
- "object-curly-spacing": ["error", "always"],
59
- "object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
60
- "operator-assignment": "error",
61
- "operator-linebreak": ["error", "after"],
62
- "prefer-object-spread": "error",
63
- "quote-props": ["error", "consistent-as-needed"],
64
- "quotes": ["error", "single"],
65
- "semi": "error",
66
- "semi-style": "error",
67
- "space-before-blocks": "error",
68
- "space-before-function-paren": ["error", { "anonymous": "always", "named": "never", "asyncArrow": "always" }],
69
- "space-in-parens": "error",
70
- "switch-colon-spacing": "error",
71
- "arrow-parens": ["error", "as-needed"],
72
- "arrow-spacing": "error",
73
- "no-duplicate-imports": "error",
74
- "no-useless-computed-key": "error",
75
- "no-var": "error",
76
- "object-shorthand": "error",
77
- "prefer-const": "error",
78
- "prefer-destructuring": ["error", { "array": false, "object": true }],
79
- "prefer-numeric-literals": "error",
80
- "prefer-template": "error",
81
- "rest-spread-spacing": "error",
82
- "template-curly-spacing": "error"
83
- },
84
- "settings": {
85
- "import/resolver": {
86
- "node": {
87
- "extensions": [".js", ".ts", ".d.ts"],
88
- "moduleDirectory": ["node_modules", "src"]
89
- }
90
- }
91
- }
92
- };
111
+ ];