Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/itchy-tires-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commencis/eslint-config': major
---

feat: enable consistent type imports
3 changes: 1 addition & 2 deletions packages/eslint-config/src/plugins/importSortPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';

import { FlatConfig } from '@/types';

import { importSortRules } from '@/rules';
import type { FlatConfig } from '@/types';

export const importSortPluginConfig: FlatConfig = {
name: 'commencis/plugin:simple-import-sort',
Expand Down
127 changes: 93 additions & 34 deletions packages/eslint-config/src/rules/importSortRules.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,106 @@
import { Linter } from '@typescript-eslint/utils/ts-eslint';
import type { Linter } from '@typescript-eslint/utils/ts-eslint';

export const importSortRules: Linter.RulesRecord = {
'simple-import-sort/imports': [
'error',
{
groups: [
// Side effects
['^\\u0000'],
/**
* Turn a normal "from" regex into a “type-only” variant that matches
* the same sources *when* they’re type-only imports.
* simple-import-sort appends \u0000 to type-only import sources.
*/
function asTypeOnly(pattern: string): string {
const base = pattern.endsWith('$') ? pattern.slice(0, -1) : pattern;
return `${base}\\u0000$`;
}

// Put type-only variants first inside the same group.
function withTypeFirst(group: string[]): string[] {
return group.flatMap((pattern) => [asTypeOnly(pattern), pattern]);
}

// Helpers to generate exact and subpath regex for @/ aliases
function exact(p: string): string {
return `^@/${p}$`;
}

function subpath(p: string): string {
return `^@/${p}/.+$`;
}

// Expand an array of folder names into [exact, subpath] for each
function expandFolders(folders: string[]): string[] {
return folders.flatMap((name) => [exact(name), subpath(name)]);
}

// Main frameworks & libraries
[
'^(react(-native|-dom)?(/.*)?)$',
'^next',
'^vue',
'^nuxt',
'^@angular(/.*|$)',
'^expo',
'^node',
],
const GROUPS: Record<string, string[]> = {
// Side effects (simple-import-sort prefixes side-effects with \u0000 at the start)
SIDE_EFFECTS: ['^\\u0000'],

// External packages
['^@commencis', '^@?\\w'],
// Main frameworks & libraries
FRAMEWORKS: [
'^(react(-native|-dom)?(/.*)?)$',
'^next',
'^vue',
'^nuxt',
'^@angular(/.*|$)',
'^expo',
'^node',
],

// Internal common directories
['^@?/?(config|types|interfaces|constants|helpers|utils|lib)(/.*|$)'],
// External packages
EXTERNAL: ['^@commencis', '^@?\\w'],

// Internal directories
['^@/'],
// Internal common directories
INTERNAL_COMMON: expandFolders([
'config',
'types',
'interfaces',
'constants',
'helpers',
'utils',
'lib',
]),

// Components
['((.*)/)?(providers|layouts|pages|modules|features|components)/?'],
// Component directories
COMPONENTS: expandFolders([
'providers',
'layouts',
'pages',
'modules',
'features',
'components',
]),

// Relative parent imports: '../' comes last
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
// Internal root alias (catch-all leftover @/ imports)
INTERNAL_ROOT: [exact(''), subpath('')],

// Relative imports: './' comes last
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
// Relative parent imports then same-dir relatives
RELATIVE_PARENT: ['^\\.\\.(?!/?$)', '^\\.\\./?$'],
RELATIVE_SAME: ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],

// Styles
['^.+\\.(s?css|(style(s)?)\\..+)$'],
// Styles
STYLES: ['^.+\\.(s?css|(style(s)?)\\..+)$'],

// Static assets
['(asset(s?)|public|static|images)(/.*|$)', '^.+\\.svg$', '^.+\\.png$'],
// Assets
ASSETS: [
'(asset(s?)|public|static|images)(/.*|$)',
'^.+\\.svg$',
'^.+\\.png$',
],
};

export const importSortRules: Linter.RulesRecord = {
'simple-import-sort/imports': [
'error',
{
groups: [
GROUPS.SIDE_EFFECTS,
withTypeFirst(GROUPS.FRAMEWORKS),
withTypeFirst(GROUPS.EXTERNAL),
withTypeFirst(GROUPS.INTERNAL_COMMON),
withTypeFirst(GROUPS.COMPONENTS),
withTypeFirst(GROUPS.INTERNAL_ROOT),
withTypeFirst(GROUPS.RELATIVE_PARENT),
withTypeFirst(GROUPS.RELATIVE_SAME),
GROUPS.STYLES,
GROUPS.ASSETS,
],
},
],
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-config/src/rules/typescriptRules.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Linter } from '@typescript-eslint/utils/ts-eslint';
import type { Linter } from '@typescript-eslint/utils/ts-eslint';

export const typescriptRules: Linter.RulesRecord = {
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/array-type': 'off',

'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
Expand Down