From a7e44639dc1e525d2be110d23af8b0d01e2c0cb1 Mon Sep 17 00:00:00 2001 From: pchalupa Date: Mon, 20 Oct 2025 13:16:36 +0200 Subject: [PATCH 1/4] feat: add react native eslint config chore: add license chore: add package manager --- package-lock.json | 129 ++++++++++++++---- package.json | 1 + packages/eslint-config-react-native/LICENSE | 29 ++++ .../eslint-config-react-native/package.json | 26 ++++ 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 packages/eslint-config-react-native/LICENSE create mode 100644 packages/eslint-config-react-native/package.json diff --git a/package-lock.json b/package-lock.json index 80211c74..29bfee24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1675,6 +1675,15 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/agent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", @@ -2630,6 +2639,10 @@ "resolved": "packages/prettier-config", "link": true }, + "node_modules/@strv/react-native": { + "resolved": "packages/eslint-config-react-native", + "link": true + }, "node_modules/@strv/remark-config": { "resolved": "packages/remark-config", "link": true @@ -6861,29 +6874,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -7223,6 +7213,71 @@ "eslint": ">=6.0.0" } }, + "node_modules/eslint-config-expo": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-10.0.0.tgz", + "integrity": "sha512-/XC/DvniUWTzU7Ypb/cLDhDD4DXqEio4lug1ObD/oQ9Hcx3OVOR8Mkp4u6U4iGoZSJyIQmIk3WVHe/P1NYUXKw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-expo": "^1.0.0", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^5.1.0", + "globals": "^16.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-config-expo/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-config-expo/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-import-context": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.6.tgz", @@ -7348,6 +7403,23 @@ "eslint": ">=8" } }, + "node_modules/eslint-plugin-expo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.0.0.tgz", + "integrity": "sha512-qLtunR+cNFtC+jwYCBia5c/PJurMjSLMOV78KrEOyQK02ohZapU4dCFFnS2hfrJuw0zxfsjVkjqg3QBqi933QA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.29.1", + "@typescript-eslint/utils": "^8.29.1", + "eslint": "^9.24.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, "node_modules/eslint-plugin-graphql": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz", @@ -21173,6 +21245,17 @@ "eslint": "^9" } }, + "packages/eslint-config-react-native": { + "name": "@strv/react-native", + "version": "1.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "eslint-config-expo": "~10.0.0" + }, + "peerDependencies": { + "eslint": "^9" + } + }, "packages/eslint-config-typescript": { "name": "@strv/eslint-config-typescript", "version": "6.0.0", diff --git a/package.json b/package.json index f6ef1bb8..faad748f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "node": ">=24", "npm": ">=11" }, + "packageManager": "npm@11.6.2", "type": "module", "homepage": "https://github.com/strvcom/code-quality-tools", "keywords": [], diff --git a/packages/eslint-config-react-native/LICENSE b/packages/eslint-config-react-native/LICENSE new file mode 100644 index 00000000..d310f302 --- /dev/null +++ b/packages/eslint-config-react-native/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, STRV +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json new file mode 100644 index 00000000..26d0ee3b --- /dev/null +++ b/packages/eslint-config-react-native/package.json @@ -0,0 +1,26 @@ +{ + "name": "@strv/react-native", + "description": "STRV's ESLint config for React Native projects", + "version": "1.0.0", + "author": "Petr Chalupa ", + "keywords": ["eslint", "react-native", "config", "strv"], + "repository": { + "type": "git", + "url": "git://github.com/strvcom/code-quality-tools.git" + }, + "bugs": { + "url": "https://github.com/strvcom/code-quality-tools/issues" + }, + "license": "BSD-3-Clause", + "main": "index.mjs", + "type": "module", + "dependencies": { + "eslint-config-expo": "~10.0.0" + }, + "peerDependencies": { + "eslint": "^9" + }, + "publishConfig": { + "access": "public" + } +} From 1331df00e0ef23a9b9d230121362dcfab3d1e7d6 Mon Sep 17 00:00:00 2001 From: pchalupa Date: Mon, 20 Oct 2025 13:36:14 +0200 Subject: [PATCH 2/4] feat(eslint-config-react-native): add config chore: rename package fix: disable slow rules --- packages/eslint-config-react-native/index.mjs | 54 +++++++++++++++++++ .../eslint-config-react-native/package.json | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-config-react-native/index.mjs diff --git a/packages/eslint-config-react-native/index.mjs b/packages/eslint-config-react-native/index.mjs new file mode 100644 index 00000000..0d1b3cc0 --- /dev/null +++ b/packages/eslint-config-react-native/index.mjs @@ -0,0 +1,54 @@ +import expoConfig from 'eslint-config-expo/flat.js' +import { defineConfig, globalIgnores } from 'eslint/config' + +/** Globally ignored */ +const ignores = globalIgnores(['.expo/']) + +/** @type {import("eslint").Linter.Config} */ +const common = { + rules: { + // Very expensive check + 'import/namespace': 'off', + // Very expensive check + 'import/no-duplicates': 'off', + }, +} + +/** @type {import("eslint").Linter.Config} */ +const react = { + rules: { + 'react/jsx-sort-props': [ + 'warn', + { + multiline: 'first', + reservedFirst: ['key'], + callbacksLast: true, + shorthandLast: true, + }, + ], + 'react/display-name': 'off', + }, +} + +/** @type {import("eslint").Linter.Config} */ +const typescript = { + rules: { + // Handled by TypeScript, see https://www.typescriptlang.org/tsconfig/#noUnusedLocals + '@typescript-eslint/no-unused-vars': 'off', + // Its common in React Native to import types using require syntax, see https://reactnative.dev/docs/images#static-image-resources + '@typescript-eslint/no-require-imports': 'off', + + // '@typescript-eslint/consistent-type-imports': [ + // 'warn', + // { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + // ], + }, +} + +export default defineConfig([ + expoConfig, + ignores, + common, + typescript, + react, +]) diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index 26d0ee3b..0401319c 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -1,5 +1,5 @@ { - "name": "@strv/react-native", + "name": "@strv/eslint-config-react-native", "description": "STRV's ESLint config for React Native projects", "version": "1.0.0", "author": "Petr Chalupa ", From 3636786c75ecfbc0893eb10cff6e8432c1cb7f7d Mon Sep 17 00:00:00 2001 From: pchalupa Date: Mon, 20 Oct 2025 15:55:59 +0200 Subject: [PATCH 3/4] docs(eslint-config-react-native): add readme chore: add homepage field --- packages/eslint-config-react-native/README.md | 22 +++++++++++++++++++ .../eslint-config-react-native/package.json | 1 + 2 files changed, 23 insertions(+) create mode 100644 packages/eslint-config-react-native/README.md diff --git a/packages/eslint-config-react-native/README.md b/packages/eslint-config-react-native/README.md new file mode 100644 index 00000000..7e298049 --- /dev/null +++ b/packages/eslint-config-react-native/README.md @@ -0,0 +1,22 @@ +# @strv/eslint-config-react-native + +> ESLint config for React-Native projects + +## Installation + +```sh +npm install -D @strv/eslint-config-react-native +``` + +## Usage + +```js +// eslint.config.mjs +import reactNative from '@strv/eslint-config-react-native' + +export default reactNative +``` + +## License + +See the [LICENSE](LICENSE) file for information. diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index 0401319c..bb0b8d7f 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -11,6 +11,7 @@ "bugs": { "url": "https://github.com/strvcom/code-quality-tools/issues" }, + "homepage": "https://github.com/strvcom/code-quality-tools/tree/master/packages/eslint-config-react-native#readme", "license": "BSD-3-Clause", "main": "index.mjs", "type": "module", From 64379c0ec41caf65480975b9e182e448a4a2ed35 Mon Sep 17 00:00:00 2001 From: pchalupa Date: Mon, 20 Oct 2025 15:59:14 +0200 Subject: [PATCH 4/4] feat(eslint-config-react-native): define rules fix: add ignore chore: add ts plugin and parser fix: update files chore: bump version docs: update docs chore: deps align feat: extend by typescript config fix: use singlequote fix: format docs: add links to rules --- package-lock.json | 15 ++--- packages/eslint-config-react-native/README.md | 3 + packages/eslint-config-react-native/index.mjs | 56 +++++++++++++++---- .../eslint-config-react-native/package.json | 5 +- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29bfee24..bd3cd6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2631,6 +2631,10 @@ "resolved": "packages/eslint-config-react", "link": true }, + "node_modules/@strv/eslint-config-react-native": { + "resolved": "packages/eslint-config-react-native", + "link": true + }, "node_modules/@strv/eslint-config-typescript": { "resolved": "packages/eslint-config-typescript", "link": true @@ -2639,10 +2643,6 @@ "resolved": "packages/prettier-config", "link": true }, - "node_modules/@strv/react-native": { - "resolved": "packages/eslint-config-react-native", - "link": true - }, "node_modules/@strv/remark-config": { "resolved": "packages/remark-config", "link": true @@ -21246,11 +21246,12 @@ } }, "packages/eslint-config-react-native": { - "name": "@strv/react-native", - "version": "1.0.0", + "name": "@strv/eslint-config-react-native", + "version": "4.0.0", "license": "BSD-3-Clause", "dependencies": { - "eslint-config-expo": "~10.0.0" + "@strv/eslint-config-typescript": "^6.0.0", + "eslint-config-expo": "^10.0.0" }, "peerDependencies": { "eslint": "^9" diff --git a/packages/eslint-config-react-native/README.md b/packages/eslint-config-react-native/README.md index 7e298049..a30947f3 100644 --- a/packages/eslint-config-react-native/README.md +++ b/packages/eslint-config-react-native/README.md @@ -17,6 +17,9 @@ import reactNative from '@strv/eslint-config-react-native' export default reactNative ``` +### Tips +1. Use caching, see https://eslint.org/docs/latest/use/command-line-interface#caching + ## License See the [LICENSE](LICENSE) file for information. diff --git a/packages/eslint-config-react-native/index.mjs b/packages/eslint-config-react-native/index.mjs index 0d1b3cc0..796bed63 100644 --- a/packages/eslint-config-react-native/index.mjs +++ b/packages/eslint-config-react-native/index.mjs @@ -1,24 +1,36 @@ +import typeScriptConfig from '@strv/eslint-config-typescript' import expoConfig from 'eslint-config-expo/flat.js' import { defineConfig, globalIgnores } from 'eslint/config' /** Globally ignored */ -const ignores = globalIgnores(['.expo/']) +const ignores = globalIgnores(['.expo/', 'expo-env.d.ts']) /** @type {import("eslint").Linter.Config} */ const common = { rules: { - // Very expensive check + // Very expensive check, see https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md 'import/namespace': 'off', - // Very expensive check + // Very expensive check, see https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md 'import/no-duplicates': 'off', + // Handled by TypeScript, see https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md + 'import/no-unresolved': 'off', + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default.md + 'import/no-named-as-default': 'off', + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default-member.md + 'import/no-named-as-default-member': 'off', + // Handled by TypeScript. Enable noUnusedLocals in your tsconfig.json, see https://www.typescriptlang.org/tsconfig/#noUnusedLocals + // https://eslint.org/docs/latest/rules/no-unused-vars + 'no-unused-vars': 'off', }, } /** @type {import("eslint").Linter.Config} */ const react = { + files: ['**/*.jsx', '**/*.tsx'], rules: { + // Enforce alphabetical sorting of props for better readability, see https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 'react/jsx-sort-props': [ - 'warn', + 'error', { multiline: 'first', reservedFirst: ['key'], @@ -26,24 +38,46 @@ const react = { shorthandLast: true, }, ], + // DisplayName is not required for React Native components, see https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/display-name.md 'react/display-name': 'off', }, } /** @type {import("eslint").Linter.Config} */ -const typescript = { +const typescript = defineConfig([typeScriptConfig, { + files: ['**/*.ts', '**/*.tsx'], rules: { - // Handled by TypeScript, see https://www.typescriptlang.org/tsconfig/#noUnusedLocals + // Handled by TypeScript. Enable noUnusedLocals in your tsconfig.json, see https://www.typescriptlang.org/tsconfig/#noUnusedLocals + // https://typescript-eslint.io/rules/no-unused-vars/ '@typescript-eslint/no-unused-vars': 'off', // Its common in React Native to import types using require syntax, see https://reactnative.dev/docs/images#static-image-resources + // https://typescript-eslint.io/rules/no-require-imports/ '@typescript-eslint/no-require-imports': 'off', + // Very expensive check, see https://typescript-eslint.io/rules/promise-function-async/ + '@typescript-eslint/promise-function-async': 'off', - // '@typescript-eslint/consistent-type-imports': [ - // 'warn', - // { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, - // ], + // Allows variable shadowing in TypeScript contexts, see https://typescript-eslint.io/rules/no-shadow/ + '@typescript-eslint/no-shadow': 'off', + // Allows both interface and type definitions, see https://typescript-eslint.io/rules/consistent-type-definitions/ + '@typescript-eslint/consistent-type-definitions': 'off', + // Allows Promises in places where they might not be handled properly, see https://typescript-eslint.io/rules/no-misused-promises/ + '@typescript-eslint/no-misused-promises': 'off', + // Allows assignments of any typed values, see https://typescript-eslint.io/rules/no-unsafe-assignment/ + '@typescript-eslint/no-unsafe-assignment': 'off', + // Allows returning any typed values from functions, see https://typescript-eslint.io/rules/no-unsafe-return/ + '@typescript-eslint/no-unsafe-return': 'off', + // Allows unbound method references that may lose 'this' context, see https://typescript-eslint.io/rules/unbound-method/ + '@typescript-eslint/unbound-method': 'off', + // Allows member access on any typed values, see https://typescript-eslint.io/rules/no-unsafe-member-access/ + '@typescript-eslint/no-unsafe-member-access': 'off', + + // Enforce consistent type imports with inline style, see https://typescript-eslint.io/rules/consistent-type-imports/ + '@typescript-eslint/consistent-type-imports': [ + 'error', + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], }, -} +}]) export default defineConfig([ expoConfig, diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index bb0b8d7f..46e69cb9 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -1,7 +1,7 @@ { "name": "@strv/eslint-config-react-native", "description": "STRV's ESLint config for React Native projects", - "version": "1.0.0", + "version": "4.0.0", "author": "Petr Chalupa ", "keywords": ["eslint", "react-native", "config", "strv"], "repository": { @@ -16,7 +16,8 @@ "main": "index.mjs", "type": "module", "dependencies": { - "eslint-config-expo": "~10.0.0" + "@strv/eslint-config-typescript": "^6.0.0", + "eslint-config-expo": "^10.0.0" }, "peerDependencies": { "eslint": "^9"