From 15abd48d726f239253b71bd1bf0cc2969592a104 Mon Sep 17 00:00:00 2001 From: Holden Whitehead Date: Fri, 15 Mar 2024 15:15:41 -0700 Subject: [PATCH] feat: update to flat configs (#11) --- .github/workflows/release.yaml | 6 +- .github/workflows/validate.yaml | 9 +- .node-version | 1 + eslint.config.mjs | 5 + package.json | 3 +- packages/eslint-config-js/.eslintrc.json | 67 ----------- packages/eslint-config-js/README.md | 52 +++++++-- packages/eslint-config-js/index.js | 75 +++++++++++- packages/eslint-config-js/package.json | 14 +-- packages/eslint-config-js/standard.js | 31 +++++ packages/eslint-config-react/.eslintrc.json | 115 ------------------- packages/eslint-config-react/README.md | 52 +++++++-- packages/eslint-config-react/index.js | 120 +++++++++++++++++++- packages/eslint-config-react/package.json | 4 - 14 files changed, 326 insertions(+), 228 deletions(-) create mode 100644 .node-version create mode 100644 eslint.config.mjs delete mode 100644 packages/eslint-config-js/.eslintrc.json create mode 100644 packages/eslint-config-js/standard.js delete mode 100644 packages/eslint-config-react/.eslintrc.json diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7bf3fe9..8e1d421 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,16 +11,16 @@ jobs: runs-on: ubuntu-latest needs: validate steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.GH_PAT }} fetch-depth: 0 - run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://registry.npmjs.org' - run: npm install --package-lock=false - run: npm run version:bump -- --yes diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 88dcd96..f8424db 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -3,15 +3,12 @@ on: [pull_request, workflow_call] jobs: validate: runs-on: ubuntu-latest - strategy: - matrix: - node: ['16', '18'] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: + node-version: '20.x' registry-url: 'https://registry.npmjs.org/' - node-version: ${{ matrix.node }} - run: npm install - run: npm run bootstrap - run: npm run validate diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..2dbbe00 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20.11.1 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..5d5ddaa --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,5 @@ +import configJs from '@autotelic/eslint-config-js' + +export default [ + configJs +] diff --git a/package.json b/package.json index f0aeb2a..1444441 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "scripts": { "bootstrap": "lerna bootstrap --strict", "clean": "lerna clean -y && lerna exec -- rm -f package-lock.json && rm -rf package-lock.json node_modules", - "validate": "lerna run lint", + "validate": "eslint .", "version:bump": "lerna version --conventional-commits --create-release github", "version:release": "lerna publish from-package" }, @@ -11,6 +11,7 @@ "packages/*" ], "devDependencies": { + "eslint": "^8.57.0", "lerna": "^6.5.1" } } diff --git a/packages/eslint-config-js/.eslintrc.json b/packages/eslint-config-js/.eslintrc.json deleted file mode 100644 index db18c0d..0000000 --- a/packages/eslint-config-js/.eslintrc.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "extends": ["standard"], - "plugins": ["import", "n"], - "rules": { - "array-bracket-newline": ["error", "consistent"], - "arrow-body-style": ["error", "as-needed"], - "complexity": ["error", { - "max": 15 - }], - "default-param-last": ["error"], - "func-names": ["error", "as-needed"], - "func-style": ["error", "declaration", { - "allowArrowFunctions": true - }], - "function-call-argument-newline": ["error", "consistent"], - "implicit-arrow-linebreak": ["error", "beside"], - "max-len": ["error", { - "code": 110, - "tabWidth": 2, - "ignoreComments": true, - "ignoreTrailingComments": false, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreUrls": true, - "ignoreRegExpLiterals": true - }], - "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 3 }], - "no-bitwise": "error", - "no-confusing-arrow": "error", - "no-else-return": "error", - "no-multi-assign": "error", - "no-nested-ternary": "error", - "no-return-await": "error", - "object-shorthand": ["error", "always"], - "require-atomic-updates": ["error", { - "allowProperties": true - }], - "import/default": "error", - "import/named": "error", - "import/no-cycle": ["error", { - "ignoreExternal": true - }], - "import/no-self-import": "error", - "import/order": ["error", { - "alphabetize": { - "order": "asc", - "caseInsensitive": true - }, - "newlines-between": "always" - }], - "import/no-unresolved": ["error", { - "commonjs": true - }], - "n/no-unsupported-features/es-builtins": "error", - "n/no-unsupported-features/es-syntax": "error", - "n/no-unsupported-features/node-builtins": "error", - "n/prefer-global/buffer": ["error", "always"], - "n/prefer-global/console": ["error", "always"], - "n/prefer-global/process": ["error", "always"], - "n/prefer-global/text-decoder": ["error", "always"], - "n/prefer-global/text-encoder": ["error", "always"], - "n/prefer-global/url": ["error", "always"], - "n/prefer-global/url-search-params": ["error", "always"], - "n/prefer-promises/dns": "error", - "n/prefer-promises/fs": "error" - } -} diff --git a/packages/eslint-config-js/README.md b/packages/eslint-config-js/README.md index 364fc7d..e7b44d9 100644 --- a/packages/eslint-config-js/README.md +++ b/packages/eslint-config-js/README.md @@ -4,8 +4,6 @@ ### Usage -Thanks to [`@rushstack/eslint-patch`](https://github.com/microsoft/rushstack/tree/main/eslint/eslint-patch#readme), this package has no peer dependencies other than `eslint`. To use `@autotelic/eslint-config-js` in your project, just run one of the following install commands and add the `.eslintrc.json` suggested below. - #### NPM ```sh @@ -18,17 +16,51 @@ npm i --save-dev eslint @autotelic/eslint-config-js yarn add --dev eslint @autotelic/eslint-config-js ``` -#### `.eslintrc.json` +#### `eslint.config.js` + +```js +const configJs = require('@autotelic/eslint-config-js') + +module.exports = [ + configJs, + { + // ...Additional config + } +] + +``` + +#### `eslint.config.mjs` + +```js +import configJs from '@autotelic/eslint-config-js' + +export default [ + configJs, + { + // ...Additional config + } +] + +``` + +#### Configuring Node Version + +By default the Node version is set to `^20.x`, if a different or a more specific version is preferred it can be configured as follows: + +```js +// eslint.config.js +const configJs = require('@autotelic/eslint-config-js') -```json -{ - "extends": ["@autotelic/eslint-config-js"], - "settings": { - "node": { - "version": "^18.x", // Any Node version >= 16 +module.exports = [ + configJs, + { + settings: { + node: { + version: '20.11.1' } } -} +}] ``` ### About diff --git a/packages/eslint-config-js/index.js b/packages/eslint-config-js/index.js index 0687b2b..fea97e8 100644 --- a/packages/eslint-config-js/index.js +++ b/packages/eslint-config-js/index.js @@ -1,3 +1,74 @@ -require('@rushstack/eslint-patch/modern-module-resolution') +const standard = require('./standard') -module.exports = require('./.eslintrc.json') +module.exports = { + ...standard, + settings: { + node: { + version: '^20.x' + } + }, + rules: { + ...standard.rules, + 'array-bracket-newline': ['error', 'consistent'], + 'arrow-body-style': ['error', 'as-needed'], + complexity: ['error', { + max: 15 + }], + 'default-param-last': ['error'], + 'func-names': ['error', 'as-needed'], + 'func-style': ['error', 'declaration', { + allowArrowFunctions: true + }], + 'function-call-argument-newline': ['error', 'consistent'], + 'implicit-arrow-linebreak': ['error', 'beside'], + 'max-len': ['error', { + code: 110, + tabWidth: 2, + ignoreComments: true, + ignoreTrailingComments: false, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreUrls: true, + ignoreRegExpLiterals: true + }], + 'newline-per-chained-call': ['error', { ignoreChainWithDepth: 3 }], + 'no-bitwise': 'error', + 'no-confusing-arrow': 'error', + 'no-else-return': 'error', + 'no-multi-assign': 'error', + 'no-nested-ternary': 'error', + 'no-return-await': 'error', + 'object-shorthand': ['error', 'always'], + 'require-atomic-updates': ['error', { + allowProperties: true + }], + 'import/default': 'error', + 'import/named': 'error', + 'import/no-cycle': ['error', { + ignoreExternal: true + }], + 'import/no-self-import': 'error', + 'import/order': ['error', { + alphabetize: { + order: 'asc', + caseInsensitive: true + }, + 'newlines-between': 'always' + }], + 'import/no-unresolved': ['error', { + commonjs: true + }], + 'n/no-unsupported-features/es-builtins': 'error', + 'n/no-unsupported-features/es-syntax': 'error', + 'n/no-unsupported-features/node-builtins': 'error', + 'n/prefer-global/buffer': ['error', 'always'], + 'n/prefer-global/console': ['error', 'always'], + 'n/prefer-global/process': ['error', 'always'], + 'n/prefer-global/text-decoder': ['error', 'always'], + 'n/prefer-global/text-encoder': ['error', 'always'], + 'n/prefer-global/url': ['error', 'always'], + 'n/prefer-global/url-search-params': ['error', 'always'], + 'n/prefer-promises/dns': 'error', + 'n/prefer-promises/fs': 'error' + } +} diff --git a/packages/eslint-config-js/package.json b/packages/eslint-config-js/package.json index 6c93950..f1e42b4 100644 --- a/packages/eslint-config-js/package.json +++ b/packages/eslint-config-js/package.json @@ -3,9 +3,7 @@ "version": "0.3.0", "description": "base eslint config for js apps", "main": "index.js", - "files": [ - ".eslintrc.json" - ], + "files": ["standard.js"], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint ." @@ -27,11 +25,11 @@ "homepage": "https://github.com/autotelic/lint-configs/tree/main/packages/eslint-config-js", "license": "MIT", "dependencies": { - "@rushstack/eslint-patch": "^1.2.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.6.1", - "eslint-plugin-promise": "^6.1.1" + "eslint-config-standard": "17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", + "globals": "^14.0.0" }, "devDependencies": { "eslint": "^8.36.0" diff --git a/packages/eslint-config-js/standard.js b/packages/eslint-config-js/standard.js new file mode 100644 index 0000000..f10f6f4 --- /dev/null +++ b/packages/eslint-config-js/standard.js @@ -0,0 +1,31 @@ +const standard = require('eslint-config-standard') +const pluginImport = require('eslint-plugin-import') +const pluginN = require('eslint-plugin-n') +const pluginPromise = require('eslint-plugin-promise') +const globals = require('globals') + +// TODO(HW13): Get `languageOptions` and `plugins` directly from eslint-config-standard +// as soon as they publish their flat config +// -> https://github.com/standard/eslint-config-standard/issues/351 +module.exports = { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { jsx: true } + }, + globals: { + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly' + } + }, + plugins: { + n: pluginN, + promise: pluginPromise, + import: pluginImport + }, + rules: standard.rules +} diff --git a/packages/eslint-config-react/.eslintrc.json b/packages/eslint-config-react/.eslintrc.json deleted file mode 100644 index b92bc7b..0000000 --- a/packages/eslint-config-react/.eslintrc.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "extends": ["@autotelic/eslint-config-js"], - "parserOptions": { - "ecmaFeatures": { - "jsx": true - } - }, - "plugins": ["react", "react-hooks"], - "settings": { - "react": { - "version": "detect" - } - }, - "rules": { - "import/order": ["error", { - "alphabetize": { - "order": "asc", - "caseInsensitive": true - }, - "groups": ["builtin", "external", "parent", "sibling", "index"], - "newlines-between": "always", - "pathGroups": [{ - "group": "builtin", - "pattern": "react", - "position": "before" - }], - "pathGroupsExcludedImportTypes": ["builtin"] - }], - "react/function-component-definition": ["error", { - "namedComponents": ["function-declaration", "arrow-function"] - }], - "react/jsx-boolean-value": ["error", "never"], - "react/jsx-closing-bracket-location": "error", - "react/jsx-closing-tag-location": "error", - "react/jsx-curly-brace-presence": ["error", { - "props": "never", - "children": "never", - "propElementValues": "always" - }], - "react/jsx-curly-newline": ["error", "consistent"], - "react/jsx-curly-spacing": ["error", { "when": "never", "children": true }], - "react/jsx-equals-spacing": ["error", "never"], - "react/jsx-first-prop-new-line": ["error", "multiline"], - "react/jsx-fragments": ["error", "syntax"], - "react/jsx-indent": ["error", 2, { "checkAttributes": true, "indentLogicalExpressions": true }], - "react/jsx-indent-props": ["error", 2], - "react/jsx-key": "error", - "react/jsx-max-props-per-line": ["error", { "maximum": { "single": 5, "multi": 1 } }], - "react/jsx-newline": ["error", { "prevent": true }], - "react/jsx-no-bind": ["error", { - "allowArrowFunctions": true, - "allowBind": false, - "ignoreRefs": true - }], - "react/jsx-no-comment-textnodes": "error", - "react/jsx-no-constructed-context-values": "error", - "react/jsx-no-duplicate-props": "error", - "react/jsx-no-leaked-render": ["error", { "validStrategies": ["ternary"] }], - "react/jsx-no-undef": ["error", { "allowGlobals": true }], - "react/jsx-no-useless-fragment": ["error", { "allowExpressions": true }], - "react/jsx-one-expression-per-line": ["error", { "allow": "literal" }], - "react/jsx-pascal-case": "error", - "react/jsx-sort-props": ["error", { - "callbacksLast": true, - "shorthandLast": true, - "multiline": "last", - "ignoreCase": true, - "reservedFirst": true - }], - "react/jsx-tag-spacing": ["error", { - "closingSlash": "never", - "beforeSelfClosing": "always", - "afterOpening": "never", - "beforeClosing": "never" - }], - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "react/jsx-wrap-multilines": ["error", { - "declaration": "parens-new-line", - "assignment": "parens-new-line", - "return": "parens-new-line", - "arrow": "parens-new-line", - "condition": "parens-new-line", - "logical": "parens-new-line", - "prop": "parens-new-line" - }], - "react/no-deprecated": "warn", - "react/no-did-update-set-state": "error", - "react/no-direct-mutation-state": "error", - "react/no-find-dom-node": "error", - "react/no-invalid-html-attribute": "error", - "react/no-is-mounted": "error", - "react/no-object-type-as-default-prop": "error", - "react/no-string-refs": "error", - "react/no-this-in-sfc": "error", - "react/no-unescaped-entities": "error", - "react/no-unknown-property": "error", - "react/no-unstable-nested-components": "error", - "react/no-unused-class-component-methods": "error", - "react/no-unused-state": "error", - "react/no-will-update-set-state": "error", - "react/prefer-es6-class": "error", - "react/prefer-stateless-function": "error", - "react/require-render-return": "error", - "react/self-closing-comp": ["error", { - "component": true, - "html": true - }], - "react/sort-comp": "error", - "react/style-prop-object": "error", - "react/void-dom-elements-no-children": "error", - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "error" - } -} diff --git a/packages/eslint-config-react/README.md b/packages/eslint-config-react/README.md index 0c3d1a6..1df5ea5 100644 --- a/packages/eslint-config-react/README.md +++ b/packages/eslint-config-react/README.md @@ -4,8 +4,6 @@ ### Usage -Thanks to [`@rushstack/eslint-patch`](https://github.com/microsoft/rushstack/tree/main/eslint/eslint-patch#readme), this package has no peer dependencies other than `eslint`. To use `@autotelic/eslint-config-react` in your project, just run one of the following install commands and add the `.eslintrc.json` suggested below. - #### NPM ```sh @@ -18,17 +16,51 @@ npm i --save-dev eslint @autotelic/eslint-config-react yarn add --dev eslint @autotelic/eslint-config-react ``` -#### `.eslintrc.json` +#### `eslint.config.js` + +```js +const configReact = require('@autotelic/eslint-config-react') + +module.exports = [ + configReact, + { + // ...Additional config + } +] + +``` + +#### `eslint.config.mjs` + +```js +import configReact from '@autotelic/eslint-config-react' + +export default [ + configReact, + { + // ...Additional config + } +] + +``` + +#### Configuring Node Version + +By default the Node version is set to `^20.x`, if a different or a more specific version is preferred it can be configured as follows: + +```js +// eslint.config.js +const configReact = require('@autotelic/eslint-config-react') -```json -{ - "extends": ["@autotelic/eslint-config-react"], - "settings": { - "node": { - "version": "^18.x", // Any Node version >= 16 +module.exports = [ + configReact, + { + settings: { + node: { + version: '20.11.1' } } -} +}] ``` ### About diff --git a/packages/eslint-config-react/index.js b/packages/eslint-config-react/index.js index 0687b2b..85d3ef1 100644 --- a/packages/eslint-config-react/index.js +++ b/packages/eslint-config-react/index.js @@ -1,3 +1,119 @@ -require('@rushstack/eslint-patch/modern-module-resolution') +const configJs = require('@autotelic/eslint-config-js') +const pluginReact = require('eslint-plugin-react') +const pluginReactHooks = require('eslint-plugin-react-hooks') -module.exports = require('./.eslintrc.json') +module.exports = { + ...configJs, + settings: { + react: { + version: 'detect' + } + }, + plugins: { + ...configJs.plugins, + react: pluginReact, + 'react-hooks': pluginReactHooks + }, + rules: { + ...configJs.rules, + 'import/order': ['error', { + alphabetize: { + order: 'asc', + caseInsensitive: true + }, + groups: ['builtin', 'external', 'parent', 'sibling', 'index'], + 'newlines-between': 'always', + pathGroups: [{ + group: 'builtin', + pattern: 'react', + position: 'before' + }], + pathGroupsExcludedImportTypes: ['builtin'] + }], + 'react/function-component-definition': ['error', { + namedComponents: ['function-declaration', 'arrow-function'] + }], + 'react/jsx-boolean-value': ['error', 'never'], + 'react/jsx-closing-bracket-location': 'error', + 'react/jsx-closing-tag-location': 'error', + 'react/jsx-curly-brace-presence': ['error', { + props: 'never', + children: 'never', + propElementValues: 'always' + }], + 'react/jsx-curly-newline': ['error', 'consistent'], + 'react/jsx-curly-spacing': ['error', { when: 'never', children: true }], + 'react/jsx-equals-spacing': ['error', 'never'], + 'react/jsx-first-prop-new-line': ['error', 'multiline'], + 'react/jsx-fragments': ['error', 'syntax'], + 'react/jsx-indent': ['error', 2, { checkAttributes: true, indentLogicalExpressions: true }], + 'react/jsx-indent-props': ['error', 2], + 'react/jsx-key': 'error', + 'react/jsx-max-props-per-line': ['error', { maximum: { single: 5, multi: 1 } }], + 'react/jsx-newline': ['error', { prevent: true }], + 'react/jsx-no-bind': ['error', { + allowArrowFunctions: true, + allowBind: false, + ignoreRefs: true + }], + 'react/jsx-no-comment-textnodes': 'error', + 'react/jsx-no-constructed-context-values': 'error', + 'react/jsx-no-duplicate-props': 'error', + 'react/jsx-no-leaked-render': ['error', { validStrategies: ['ternary'] }], + 'react/jsx-no-undef': ['error', { allowGlobals: true }], + 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], + 'react/jsx-one-expression-per-line': ['error', { allow: 'literal' }], + 'react/jsx-pascal-case': 'error', + 'react/jsx-sort-props': ['error', { + callbacksLast: true, + shorthandLast: true, + multiline: 'last', + ignoreCase: true, + reservedFirst: true + }], + 'react/jsx-tag-spacing': ['error', { + closingSlash: 'never', + beforeSelfClosing: 'always', + afterOpening: 'never', + beforeClosing: 'never' + }], + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + 'react/jsx-wrap-multilines': ['error', { + declaration: 'parens-new-line', + assignment: 'parens-new-line', + return: 'parens-new-line', + arrow: 'parens-new-line', + condition: 'parens-new-line', + logical: 'parens-new-line', + prop: 'parens-new-line' + }], + 'react/no-deprecated': 'warn', + 'react/no-did-update-set-state': 'error', + 'react/no-direct-mutation-state': 'error', + 'react/no-find-dom-node': 'error', + 'react/no-invalid-html-attribute': 'error', + 'react/no-is-mounted': 'error', + 'react/no-object-type-as-default-prop': 'error', + 'react/no-string-refs': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-unescaped-entities': 'error', + 'react/no-unknown-property': 'error', + 'react/no-unstable-nested-components': 'error', + 'react/no-unused-class-component-methods': 'error', + 'react/no-unused-state': 'error', + 'react/no-will-update-set-state': 'error', + 'react/prefer-es6-class': 'error', + 'react/prefer-stateless-function': 'error', + 'react/require-render-return': 'error', + 'react/self-closing-comp': ['error', { + component: true, + html: true + }], + 'react/sort-comp': 'error', + 'react/style-prop-object': 'error', + 'react/void-dom-elements-no-children': 'error', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error' + } +} diff --git a/packages/eslint-config-react/package.json b/packages/eslint-config-react/package.json index fc4690d..a5292c5 100644 --- a/packages/eslint-config-react/package.json +++ b/packages/eslint-config-react/package.json @@ -3,9 +3,6 @@ "version": "0.3.0", "description": "base eslint config for react apps", "main": "index.js", - "files": [ - ".eslintrc.json" - ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint ." @@ -30,7 +27,6 @@ "license": "MIT", "dependencies": { "@autotelic/eslint-config-js": "^0.3.0", - "@rushstack/eslint-patch": "^1.2.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0" },