diff --git a/README.md b/README.md index 7896ede..d2e14ad 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,14 @@ export default jimmyDotCodes({ }); ``` +Or you can enable auto detection to enable rules based on a project's dependencies + +```js +import jimmyDotCodes from "@jimmy.codes/eslint-config"; + +export default jimmyDotCodes({ autoDetect: true }); +``` + #### TypeScript You can also change the project location which can be helpful for monorepos: diff --git a/eslint.config.mjs b/eslint.config.mjs index 9196c75..731749f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,17 +9,4 @@ const jiti = JITI(import.meta.url); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const jimmyDotCodes = jiti("./src").default; -export default jimmyDotCodes({ - typescript: true, - react: { - utilities: ["@tanstack/query"], - }, - testing: { - utilities: ["testing-library"], - }, - overrides: [ - { - ignores: ["fixtures"], - }, - ], -}); +export default jimmyDotCodes({ autoDetect: true }); diff --git a/package.json b/package.json index 90d618d..5912c83 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "eslint-plugin-simple-import-sort": "^12.0.0", "eslint-plugin-testing-library": "^6.2.0", "globals": "^15.0.0", + "local-pkg": "0.5.0", "typescript-eslint": "^7.6.0" }, "devDependencies": { @@ -74,9 +75,12 @@ "@semantic-release/git": "10.0.1", "@semantic-release/npm": "12.0.0", "@semantic-release/release-notes-generator": "13.0.0", + "@tanstack/react-query": "5.29.0", + "@testing-library/react": "15.0.0", "@types/eslint": "8.56.9", "@types/eslint__js": "8.42.3", "@types/node": "20.12.7", + "@types/react": "18.2.75", "@vitest/coverage-v8": "1.5.0", "clean-pkg-json": "1.2.0", "commitlint": "19.2.1", @@ -87,6 +91,8 @@ "lefthook": "1.6.10", "pkgroll": "2.0.2", "prettier": "3.2.5", + "react": "18.2.0", + "react-dom": "18.2.0", "semantic-release": "23.0.8", "typescript": "5.4.5", "vitest": "1.5.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fe5a36..9926624 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ dependencies: globals: specifier: ^15.0.0 version: 15.0.0 + local-pkg: + specifier: 0.5.0 + version: 0.5.0 typescript-eslint: specifier: ^7.6.0 version: 7.6.0(eslint@8.57.0)(typescript@5.4.5) @@ -82,6 +85,12 @@ devDependencies: '@semantic-release/release-notes-generator': specifier: 13.0.0 version: 13.0.0(semantic-release@23.0.8) + '@tanstack/react-query': + specifier: 5.29.0 + version: 5.29.0(react@18.2.0) + '@testing-library/react': + specifier: 15.0.0 + version: 15.0.0(react-dom@18.2.0)(react@18.2.0) '@types/eslint': specifier: 8.56.9 version: 8.56.9 @@ -91,6 +100,9 @@ devDependencies: '@types/node': specifier: 20.12.7 version: 20.12.7 + '@types/react': + specifier: 18.2.75 + version: 18.2.75 '@vitest/coverage-v8': specifier: 1.5.0 version: 1.5.0(vitest@1.5.0) @@ -121,6 +133,12 @@ devDependencies: prettier: specifier: 3.2.5 version: 3.2.5 + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) semantic-release: specifier: 23.0.8 version: 23.0.8(typescript@5.4.5) @@ -186,7 +204,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: false /@babel/types@7.24.0: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} @@ -1238,6 +1255,51 @@ packages: - typescript dev: false + /@tanstack/query-core@5.29.0: + resolution: {integrity: sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww==} + dev: true + + /@tanstack/react-query@5.29.0(react@18.2.0): + resolution: {integrity: sha512-yxlhHB73jaBla6h5B6zPaGmQjokkzAhMHN4veotkPNiQ3Ac/mCxgABRZPsJJrgCTvhpcncBZcDBFxaR2B37vug==} + peerDependencies: + react: ^18.0.0 + dependencies: + '@tanstack/query-core': 5.29.0 + react: 18.2.0 + dev: true + + /@testing-library/dom@10.0.0: + resolution: {integrity: sha512-PmJPnogldqoVFf+EwbHvbBJ98MmqASV8kLrBYgsDNxQcFMeIS7JFL48sfyXvuMtgmWO/wMhh25odr+8VhDmn4g==} + engines: {node: '>=18'} + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/runtime': 7.24.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + + /@testing-library/react@15.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Nb2Sq8MoSvGVdOGGppRqXeafLtvXCOwiOQtcbqCfkpVNZZNUqIjeSOwrfJ59zBfmZbAn8PitnWEzUKD1YwMrCg==} + engines: {node: '>=18'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.24.4 + '@testing-library/dom': 10.0.0 + '@types/react-dom': 18.2.25 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@types/aria-query@5.0.4: + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + dev: true + /@types/conventional-commits-parser@5.0.0: resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} dependencies: @@ -1274,6 +1336,23 @@ packages: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + dev: true + + /@types/react-dom@18.2.25: + resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==} + dependencies: + '@types/react': 18.2.75 + dev: true + + /@types/react@18.2.75: + resolution: {integrity: sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==} + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + dev: true + /@types/resolve@1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true @@ -1725,7 +1804,6 @@ packages: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 - dev: false /array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} @@ -2211,6 +2289,10 @@ packages: type-fest: 1.4.0 dev: true + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: false @@ -2327,7 +2409,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: false /destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} @@ -2357,6 +2438,10 @@ packages: dependencies: esutils: 2.0.3 + /dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dev: true + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -3817,7 +3902,6 @@ packages: /jsonc-parser@3.2.1: resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - dev: true /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -3974,7 +4058,6 @@ packages: dependencies: mlly: 1.6.1 pkg-types: 1.0.3 - dev: true /locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} @@ -4061,7 +4144,6 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: false /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -4080,6 +4162,11 @@ packages: dependencies: yallist: 4.0.0 + /lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: true + /magic-string@0.30.9: resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} engines: {node: '>=12'} @@ -4201,7 +4288,6 @@ packages: pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.5.3 - dev: true /mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} @@ -4634,7 +4720,6 @@ packages: /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -4667,7 +4752,6 @@ packages: jsonc-parser: 3.2.1 mlly: 1.6.1 pathe: 1.1.2 - dev: true /pkgroll@2.0.2(typescript@5.4.5): resolution: {integrity: sha512-gRQ293zs67H2nN/CKwdLnlxCKpE5qi5rJwVvDUqep8qOLs57PlTl3MAXmLlFztwI2Iv6mP9k5BSlhBjzFJmerg==} @@ -4770,6 +4854,15 @@ packages: hasBin: true dev: true + /pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + /pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4816,14 +4909,35 @@ packages: strip-json-comments: 2.0.1 dev: true + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: true + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false + /react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: true + /read-package-up@11.0.0: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} @@ -4888,7 +5002,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: false /regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} @@ -5017,6 +5130,12 @@ packages: es-errors: 1.3.0 is-regex: 1.1.4 + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: true + /semantic-release@23.0.8(typescript@5.4.5): resolution: {integrity: sha512-yZkuWcTTfh5h/DrR4Q4QvJSARJdb6wjwn/sN0qKMYEkvwaVFek8YWfrgtL8oWaRdl0fLte0Y1wWMzLbwoaII1g==} engines: {node: '>=20.8.1'} @@ -5584,7 +5703,6 @@ packages: /ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - dev: true /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} diff --git a/src/configs/commonjs.ts b/src/configs/commonjs.ts new file mode 100644 index 0000000..1fdc16a --- /dev/null +++ b/src/configs/commonjs.ts @@ -0,0 +1,15 @@ +import globals from "globals"; + +import { GLOB_CJS } from "../constants"; + +export const commonjsConfig = () => { + return [ + { + name: "jimmy.codes/commonjs", + files: [GLOB_CJS], + languageOptions: { + globals: globals.commonjs, + }, + }, + ]; +}; diff --git a/src/configs/react.ts b/src/configs/react.ts index 426d793..789652f 100644 --- a/src/configs/react.ts +++ b/src/configs/react.ts @@ -6,10 +6,17 @@ import * as reactRefresh from "eslint-plugin-react-refresh"; import globals from "globals"; import { GLOB_JSX, GLOB_TSX } from "../constants"; +import { hasReactQuery } from "../has-dep"; import { reactRules } from "../rules/react"; import { type ReactOptions } from "../types"; -const reactConfig = ({ utilities = [] }: ReactOptions = {}) => { +const reactConfig = ( + { utilities = [] }: ReactOptions = {}, + autoDetect = false, +) => { + const includeReactQuery = + utilities.includes("@tanstack/query") || (autoDetect && hasReactQuery()); + return [ { name: "jimmy.codes/react", @@ -40,7 +47,7 @@ const reactConfig = ({ utilities = [] }: ReactOptions = {}) => { }, rules: reactRules, }, - ...(utilities.includes("@tanstack/query") + ...(includeReactQuery ? [ { name: "jimmy.codes/react/query", diff --git a/src/configs/testing.ts b/src/configs/testing.ts index 5c645a7..a17be38 100644 --- a/src/configs/testing.ts +++ b/src/configs/testing.ts @@ -1,21 +1,28 @@ import jest from "eslint-plugin-jest"; import { ALLOWED_VITEST_FUNCS, GLOB_E2E, GLOB_TESTS } from "../constants"; +import { hasJest, hasTestingLibrary, hasVitest } from "../has-dep"; import { jestRules } from "../rules/jest"; import { type TestingOptions } from "../types"; import testingLibraryConfig from "./testing-library"; -const testingConfig = ({ - framework = "vitest", - utilities, -}: TestingOptions = {}) => { +const testingConfig = ( + { framework = "vitest", utilities }: TestingOptions = {}, + autoDetect = false, +) => { + const isVitest = autoDetect ? hasVitest() : framework === "vitest"; + const isJest = framework === "jest" || (autoDetect && hasJest()); + const includeTestingLibrary = + !!utilities?.includes("testing-library") || + (autoDetect && hasTestingLibrary()); + return [ { name: "jimmy.codes/testing", files: GLOB_TESTS, ...jest.configs["flat/recommended"], }, - ...(framework === "vitest" + ...(isVitest ? [ { name: "jimmy.codes/testing/vitest", @@ -33,14 +40,17 @@ const testingConfig = ({ }, }, ] - : [ + : []), + ...(isJest + ? [ { name: "jimmy.codes/testing/jest", files: GLOB_TESTS, ...jest.configs["flat/recommended"], rules: jestRules, }, - ]), + ] + : []), { name: "jimmy.codes/testing/disabled", files: GLOB_E2E, @@ -50,7 +60,7 @@ const testingConfig = ({ "jest/require-hook": "off", }, }, - ...(utilities?.includes("testing-library") ? testingLibraryConfig() : []), + ...(includeTestingLibrary ? testingLibraryConfig() : []), ]; }; diff --git a/src/constants.ts b/src/constants.ts index 4b49071..3d5d797 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -61,3 +61,5 @@ export const GLOB_E2E = [ export const GLOB_CJS = "**/*.cjs"; export const ALLOWED_VITEST_FUNCS = ["vi.mock"]; + +export const TESTING_LIBRARY_FAMILY = ["@testing-library/react"]; diff --git a/src/factory.spec.ts b/src/factory.spec.ts index e2f7ad6..77eff56 100644 --- a/src/factory.spec.ts +++ b/src/factory.spec.ts @@ -1,5 +1,9 @@ +import { isPackageExists } from "local-pkg"; + import { jimmyDotCodes } from "./factory"; +vi.mock("local-pkg"); + describe("jimmyDotCodes", () => { it("should create default configuration", () => { expect(jimmyDotCodes()).toMatchSnapshot(); @@ -98,4 +102,128 @@ describe("jimmyDotCodes", () => { ]), ); }); + + describe("autoDetect", () => { + it("should include typescript when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + return name === "typescript"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "jimmy.codes/typescript" }), + expect.not.objectContaining({ name: "jimmy.codes/testing" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.not.objectContaining({ name: "jimmy.codes/react" }), + expect.not.objectContaining({ name: "jimmy.codes/react/query" }), + expect.not.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + + it("should include react when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + return name === "react"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.not.objectContaining({ name: "jimmy.codes/typescript" }), + expect.not.objectContaining({ name: "jimmy.codes/testing" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.objectContaining({ name: "jimmy.codes/react" }), + expect.not.objectContaining({ name: "jimmy.codes/react/query" }), + expect.not.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + + it("should include react-query when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + // eslint-disable-next-line jest/no-conditional-in-test + return name === "react" || name === "@tanstack/react-query"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.not.objectContaining({ name: "jimmy.codes/typescript" }), + expect.not.objectContaining({ name: "jimmy.codes/testing" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.objectContaining({ name: "jimmy.codes/react" }), + expect.objectContaining({ name: "jimmy.codes/react/query" }), + expect.not.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + + it("should include vitest when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + return name === "vitest"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.not.objectContaining({ name: "jimmy.codes/typescript" }), + expect.objectContaining({ name: "jimmy.codes/testing" }), + expect.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.not.objectContaining({ name: "jimmy.codes/react" }), + expect.not.objectContaining({ name: "jimmy.codes/react/query" }), + expect.not.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + + it("should include jest when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + return name === "jest"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.not.objectContaining({ name: "jimmy.codes/typescript" }), + expect.objectContaining({ name: "jimmy.codes/testing" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.not.objectContaining({ name: "jimmy.codes/react" }), + expect.not.objectContaining({ name: "jimmy.codes/react/query" }), + expect.not.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + + it("should include test-library when auto detection is enabled", () => { + vi.mocked(isPackageExists).mockImplementation((name) => { + // eslint-disable-next-line jest/no-conditional-in-test + return name === "@testing-library/react" || name === "vitest"; + }); + + expect(jimmyDotCodes({ autoDetect: true })).toStrictEqual( + expect.arrayContaining([ + expect.not.objectContaining({ name: "jimmy.codes/typescript" }), + expect.objectContaining({ name: "jimmy.codes/testing" }), + expect.objectContaining({ name: "jimmy.codes/testing/vitest" }), + expect.not.objectContaining({ name: "jimmy.codes/testing/jest" }), + expect.not.objectContaining({ name: "jimmy.codes/react" }), + expect.not.objectContaining({ name: "jimmy.codes/react/query" }), + expect.objectContaining({ + name: "jimmy.codes/testing/testing-library", + }), + ]), + ); + }); + }); }); diff --git a/src/factory.ts b/src/factory.ts index 22edaf9..5a75de4 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,11 +1,12 @@ import eslintConfigPrettier from "eslint-config-prettier"; -import globals from "globals"; +import { commonjsConfig } from "./configs/commonjs"; import importsConfig from "./configs/imports"; import reactConfig from "./configs/react"; import testingConfig from "./configs/testing"; import typescriptConfig from "./configs/typescript"; -import { GLOB_CJS, GLOB_IGNORES } from "./constants"; +import { GLOB_IGNORES } from "./constants"; +import { hasReact, hasTesting, hasTypescript } from "./has-dep"; import { baseRules } from "./rules/base"; import { type Options } from "./types"; import { @@ -19,21 +20,24 @@ export const jimmyDotCodes = ({ react = false, testing = false, overrides = [], + autoDetect = false, }: Options = {}) => { + const isTypescriptEnabled = typescript || (autoDetect && hasTypescript()); + const isReactEnabled = react || (autoDetect && hasReact()); + const isTestingEnabled = testing || (autoDetect && hasTesting()); + return [ { name: "jimmy.codes/base", rules: baseRules }, - ...importsConfig({ typescript }), - ...(typescript ? typescriptConfig(getTypescriptOptions(typescript)) : []), - ...(react ? reactConfig(getReactOptions(react)) : []), - ...(testing ? testingConfig(getTestingOptions(testing)) : []), + ...importsConfig({ typescript: isTypescriptEnabled }), + ...(isTypescriptEnabled + ? typescriptConfig(getTypescriptOptions(typescript)) + : []), + ...(isReactEnabled ? reactConfig(getReactOptions(react), autoDetect) : []), + ...(isTestingEnabled + ? testingConfig(getTestingOptions(testing), autoDetect) + : []), { name: "jimmy.codes/disabled", ...eslintConfigPrettier }, - { - name: "jimmy.codes/commonjs", - files: [GLOB_CJS], - languageOptions: { - globals: globals.commonjs, - }, - }, + ...commonjsConfig(), { ignores: GLOB_IGNORES, }, diff --git a/src/has-dep.ts b/src/has-dep.ts new file mode 100644 index 0000000..df4658b --- /dev/null +++ b/src/has-dep.ts @@ -0,0 +1,33 @@ +import { isPackageExists } from "local-pkg"; + +import { TESTING_LIBRARY_FAMILY } from "./constants"; + +export const hasTypescript = () => { + return isPackageExists("typescript"); +}; + +export const hasReact = () => { + return isPackageExists("react"); +}; + +export const hasVitest = () => { + return isPackageExists("vitest"); +}; + +export const hasJest = () => { + return isPackageExists("jest"); +}; + +export const hasTesting = () => { + return hasVitest() || hasJest(); +}; + +export const hasTestingLibrary = () => { + return TESTING_LIBRARY_FAMILY.some((pkg) => { + return isPackageExists(pkg); + }); +}; + +export const hasReactQuery = () => { + return isPackageExists("@tanstack/react-query"); +}; diff --git a/src/types.ts b/src/types.ts index 3ae09d4..5ce254c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,4 +64,9 @@ export interface Options { * @default [] */ overrides?: FlatConfigItem[]; + /** + * Is auto detection enabled? + * @default false + */ + autoDetect?: boolean; } diff --git a/tsconfig.json b/tsconfig.json index f3eaedc..ddd0dce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,5 @@ "jsx": "react-jsx", "types": ["vitest/globals"] - }, - "exclude": ["./fixtures/**/*.*"] + } }