diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 64f5619..0ba73b4 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -67,6 +67,7 @@ "@next/eslint-plugin-next": "15.0.3", "@typescript-eslint/utils": "8.15.0", "eslint-config-prettier": "9.1.0", + "eslint-plugin-import": "2.31.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-react": "7.37.2", diff --git a/packages/eslint-config/src/configs/base.ts b/packages/eslint-config/src/configs/base.ts index 03cc15f..f0c7e64 100644 --- a/packages/eslint-config/src/configs/base.ts +++ b/packages/eslint-config/src/configs/base.ts @@ -3,10 +3,11 @@ import globals from 'globals'; import { type FlatConfigArray } from '@/types'; -import { importSortPluginConfig } from '@/plugins'; +import { importPluginConfig, importSortPluginConfig } from '@/plugins'; export default [ eslint.configs.recommended, + importPluginConfig, importSortPluginConfig, { name: 'commencis/base', diff --git a/packages/eslint-config/src/plugins/importPlugin.ts b/packages/eslint-config/src/plugins/importPlugin.ts new file mode 100644 index 0000000..9b42ca9 --- /dev/null +++ b/packages/eslint-config/src/plugins/importPlugin.ts @@ -0,0 +1,16 @@ +// @ts-expect-error - eslint-plugin-import is not typed +import importPlugin from 'eslint-plugin-import'; + +import { FlatConfig } from '@/types'; + +import { importRules } from '@/rules'; + +export const importPluginConfig: FlatConfig = { + name: 'commencis/plugin:import', + plugins: { + import: importPlugin, + }, + rules: { + ...importRules, + }, +}; diff --git a/packages/eslint-config/src/plugins/index.ts b/packages/eslint-config/src/plugins/index.ts index b93d7b8..f178af9 100644 --- a/packages/eslint-config/src/plugins/index.ts +++ b/packages/eslint-config/src/plugins/index.ts @@ -1,3 +1,4 @@ +export * from './importPlugin'; export * from './importSortPlugin'; export * from './jsxA11yPlugin'; export * from './nextPlugin'; diff --git a/packages/eslint-config/src/rules/importRules.ts b/packages/eslint-config/src/rules/importRules.ts new file mode 100644 index 0000000..cce0113 --- /dev/null +++ b/packages/eslint-config/src/rules/importRules.ts @@ -0,0 +1,5 @@ +import { Linter } from '@typescript-eslint/utils/ts-eslint'; + +export const importRules: Linter.RulesRecord = { + 'import/no-duplicates': ['error', { 'prefer-inline': true }], +}; diff --git a/packages/eslint-config/src/rules/importSortRules.ts b/packages/eslint-config/src/rules/importSortRules.ts index 3c58931..01c5115 100644 --- a/packages/eslint-config/src/rules/importSortRules.ts +++ b/packages/eslint-config/src/rules/importSortRules.ts @@ -1,5 +1,10 @@ import { Linter } from '@typescript-eslint/utils/ts-eslint'; +const sliceDollarSign = (str: string): string => + str.endsWith('$') ? str.slice(0, -1) : str; +const groupWithTypes = (regexes: string[]): string[] => + regexes.map((regex) => [regex, `${sliceDollarSign(regex)}.*\\u0000$`]).flat(); + export const importSortRules: Linter.RulesRecord = { 'simple-import-sort/imports': [ 'error', @@ -9,7 +14,7 @@ export const importSortRules: Linter.RulesRecord = { ['^\\u0000'], // Main frameworks & libraries - [ + groupWithTypes([ '^(react(-native|-dom)?(/.*)?)$', '^next', '^vue', @@ -17,25 +22,29 @@ export const importSortRules: Linter.RulesRecord = { '^@angular(/.*|$)', '^expo', '^node', - ], + ]), // External packages - ['^@commencis', '^@?\\w'], + groupWithTypes(['^@commencis', '^@?\\w']), // Internal common directories - ['^@?/?(config|types|interfaces|constants|helpers|utils|lib)(/.*|$)'], + groupWithTypes([ + '^@?/?(config|types|interfaces|constants|helpers|utils|lib)(/.*|$)', + ]), // Internal directories - ['^@/'], + groupWithTypes(['^@/']), // Components - ['((.*)/)?(providers|layouts|pages|modules|features|components)/?'], + groupWithTypes([ + '((.*)/)?(providers|layouts|pages|modules|features|components)/?', + ]), // Relative parent imports: '../' comes last - ['^\\.\\.(?!/?$)', '^\\.\\./?$'], + groupWithTypes(['^\\.\\.(?!/?$)', '^\\.\\./?$']), // Relative imports: './' comes last - ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], + groupWithTypes(['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$']), // Styles ['^.+\\.(s?css|(style(s)?)\\..+)$'], diff --git a/packages/eslint-config/src/rules/index.ts b/packages/eslint-config/src/rules/index.ts index 8867839..144ee8c 100644 --- a/packages/eslint-config/src/rules/index.ts +++ b/packages/eslint-config/src/rules/index.ts @@ -1,3 +1,4 @@ +export * from './importRules'; export * from './importSortRules'; export * from './nextPluginRules'; export * from './reactHooksRules'; diff --git a/packages/eslint-config/src/rules/typescriptRules.ts b/packages/eslint-config/src/rules/typescriptRules.ts index 2c6e14d..4eff9e8 100644 --- a/packages/eslint-config/src/rules/typescriptRules.ts +++ b/packages/eslint-config/src/rules/typescriptRules.ts @@ -2,6 +2,14 @@ import { Linter } from '@typescript-eslint/utils/ts-eslint'; export const typescriptRules: Linter.RulesRecord = { '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'separate-type-imports', + }, + ], + '@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/array-type': 'off', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a869a6d..be68236 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,9 @@ importers: eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-import: + specifier: 2.31.0 + version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: specifier: 6.10.2 version: 6.10.2(eslint@9.15.0(jiti@1.21.6)) @@ -871,6 +874,9 @@ packages: cpu: [x64] os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@stylistic/stylelint-config@2.0.0': resolution: {integrity: sha512-8J4YAxggy2Nzkb8KJIOLbtMXTPZ5gpKVmyhiiuKEUgCl9XFND5lM0e/ZZBMGEYZ68h5qcsS/jgg1wh235erRAw==} engines: {node: ^18.12 || >=20.9} @@ -902,6 +908,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -1084,6 +1093,10 @@ packages: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} @@ -1303,6 +1316,14 @@ packages: dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -1449,6 +1470,40 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint-plugin-jsx-a11y@6.10.2: resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} engines: {node: '>=4.0'} @@ -2040,6 +2095,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -2279,6 +2338,10 @@ packages: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} @@ -2548,6 +2611,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true @@ -2880,6 +2947,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3799,6 +3869,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.27.3': optional: true + '@rtsao/scc@1.1.0': {} + '@stylistic/stylelint-config@2.0.0(stylelint@16.10.0(typescript@5.7.2))': dependencies: '@stylistic/stylelint-plugin': 3.1.1(stylelint@16.10.0(typescript@5.7.2)) @@ -3839,6 +3911,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': {} + '@types/node@12.20.55': {} '@types/node@22.9.1': @@ -4063,6 +4137,15 @@ snapshots: es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.5 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 @@ -4283,6 +4366,10 @@ snapshots: dataloader@1.4.0: {} + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.3.7: dependencies: ms: 2.1.3 @@ -4516,6 +4603,53 @@ snapshots: dependencies: eslint: 9.15.0(jiti@1.21.6) + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint@9.15.0(jiti@1.21.6)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + eslint: 9.15.0(jiti@1.21.6) + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.15.0(jiti@1.21.6) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint@9.15.0(jiti@1.21.6)) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@9.15.0(jiti@1.21.6)): dependencies: aria-query: 5.3.2 @@ -5146,6 +5280,10 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@1.0.2: + dependencies: + minimist: 1.2.8 + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -5365,6 +5503,12 @@ snapshots: es-abstract: 1.23.5 es-object-atoms: 1.0.0 + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.5 + object.values@1.2.0: dependencies: call-bind: 1.0.7 @@ -5585,6 +5729,12 @@ snapshots: resolve-from@5.0.0: {} + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: dependencies: is-core-module: 2.15.1 @@ -5988,6 +6138,13 @@ snapshots: ts-interface-checker@0.1.13: {} + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} tsup@8.3.5(jiti@1.21.6)(postcss@8.4.49)(typescript@5.7.2)(yaml@2.5.1):