Skip to content
Draft
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
14 changes: 14 additions & 0 deletions apps/docs/src/content/docs/en/eslint-config/advanced.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ export default pleaseai({
})
```

### Canonical plugin names

Some linters, such as Bun's `bun lint`, match plugins by their registered name and run renamed plugins in JavaScript instead of natively. Set `canonicalPluginNames: true` to register `@typescript-eslint` and `n` under their original names. The other renames stay:

```ts title="eslint.config.ts"
import pleaseai from '@pleaseai/eslint-config'

export default pleaseai({
canonicalPluginNames: true,
})
```

Rule IDs change to `@typescript-eslint/*` and `n/*`. `ts/*` and `node/*` keys in config `rules` are renamed for you, but `eslint-disable` comments that use them must be updated by hand.

## View Enabled Rules

Use [`@eslint/config-inspector`](https://github.com/eslint/config-inspector) to visualise the fully-resolved config in a browser — every enabled rule, every plugin, every file glob:
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/src/content/docs/ko/eslint-config/advanced.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ export default pleaseai({
})
```

### 원래 플러그인 이름 사용

Bun의 `bun lint`처럼 등록된 이름으로 플러그인을 매칭하는 린터는 이름이 바뀐 플러그인을 네이티브가 아닌 JavaScript로 실행합니다. `canonicalPluginNames: true`를 설정하면 `@typescript-eslint`와 `n`이 원래 이름으로 등록됩니다. 나머지 이름 변경은 그대로 유지됩니다:

```ts title="eslint.config.ts"
import pleaseai from '@pleaseai/eslint-config'

export default pleaseai({
canonicalPluginNames: true,
})
```

규칙 ID는 `@typescript-eslint/*`와 `n/*`로 바뀝니다. 설정 `rules`의 `ts/*`, `node/*` 키는 자동으로 변환되지만, 이를 사용하는 `eslint-disable` 주석은 직접 수정해야 합니다.

## 활성화된 규칙 보기

[`@eslint/config-inspector`](https://github.com/eslint/config-inspector)를 사용하면 완전히 해결된 설정을 브라우저에서 시각화할 수 있습니다 — 활성화된 모든 규칙, 모든 플러그인, 모든 파일 glob까지:
Expand Down
15 changes: 15 additions & 0 deletions packages/eslint-config/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,21 @@ npx @eslint/config-inspector
| `style/*` | `@stylistic/*` | [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic) |
| `test/*` | `vitest/*` | [@vitest/eslint-plugin](https://github.com/vitest-dev/eslint-plugin-vitest) |

### 원래 플러그인 이름 사용

Bun의 `bun lint`처럼 등록된 이름으로 플러그인을 매칭하는 린터는 이름이 바뀐 플러그인을 네이티브가 아닌 JavaScript로 실행합니다. `canonicalPluginNames: true`를 설정하면 `@typescript-eslint`와 `n`이 원래 이름으로 등록됩니다. 나머지 이름 변경은 그대로 유지됩니다:

```ts
// eslint.config.ts
import pleaseai from '@pleaseai/eslint-config'

export default pleaseai({
canonicalPluginNames: true,
})
```

규칙 ID는 `@typescript-eslint/*`와 `n/*`로 바뀝니다. 설정 `rules`의 `ts/*`, `node/*` 키는 자동으로 변환되지만, 이를 사용하는 `eslint-disable` 주석은 직접 수정해야 합니다.

## 버전 정책

`@pleaseai/eslint-config`는 [Semantic Versioning](https://semver.org/)을 따르지만, 여러 가지가 얽힌 스타일 프리셋이기 때문에 **규칙 변경은 breaking change로 취급하지 않습니다**. 아래 표는 `@antfu/eslint-config`에서 상속받은 정책과 동일합니다.
Expand Down
15 changes: 15 additions & 0 deletions packages/eslint-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,21 @@ This config inherits `@antfu/eslint-config`'s plugin renaming for a consistent D
| `style/*` | `@stylistic/*` | [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic) |
| `test/*` | `vitest/*` | [@vitest/eslint-plugin](https://github.com/vitest-dev/eslint-plugin-vitest) |

### Canonical plugin names

Some linters, such as Bun's `bun lint`, match plugins by their registered name and run renamed plugins in JavaScript instead of natively. Set `canonicalPluginNames: true` to register `@typescript-eslint` and `n` under their original names. The other renames stay:

```ts
// eslint.config.ts
import pleaseai from '@pleaseai/eslint-config'

export default pleaseai({
canonicalPluginNames: true,
})
```

Rule IDs change to `@typescript-eslint/*` and `n/*`. `ts/*` and `node/*` keys in config `rules` are renamed for you, but `eslint-disable` comments that use them must be updated by hand.

## Versioning Policy

`@pleaseai/eslint-config` follows [Semantic Versioning](https://semver.org/), but because it's an opinionated style preset with many moving parts, **rule changes are not treated as breaking changes**. The tables below match the policy inherited from `@antfu/eslint-config`.
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"dist"
],
"scripts": {
"build": "tsdown"
"build": "tsdown",
"test": "bun test"
},
"peerDependencies": {
"eslint": "^10.4.0"
Expand Down
45 changes: 41 additions & 4 deletions packages/eslint-config/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import type { OptionsConfig, TypedFlatConfigItem } from '@antfu/eslint-config'
import antfu from '@antfu/eslint-config'
import antfu, { defaultPluginRenaming } from '@antfu/eslint-config'

export interface PleaseaiOptions extends OptionsConfig {
/**
* Register `@typescript-eslint` and `n` under their canonical names instead
* of antfu's `ts` and `node`, so linters that match plugins by name (e.g.
* `bun lint`) can run their rules natively. Rule IDs change accordingly
* (`ts/*` → `@typescript-eslint/*`, `node/*` → `n/*`); `ts/*` and `node/*`
* keys in config `rules` are renamed too, but inline disable comments are not.
*
* @default false
*/
canonicalPluginNames?: boolean
}

const canonicalPluginRenaming: Record<string, string> = {
ts: '@typescript-eslint',
node: 'n',
}

export function pleaseai(
options: OptionsConfig = {},
options: PleaseaiOptions = {},
...userConfigs: TypedFlatConfigItem[]
): ReturnType<typeof antfu> {
return antfu(
const { canonicalPluginNames = false, ...antfuOptions } = options
const autoRenamePlugins = antfuOptions.autoRenamePlugins ?? true

const composer = antfu(
{
lessOpinionated: true,
stylistic: {
Expand All @@ -19,10 +40,26 @@ export function pleaseai(
'antfu/top-level-function': 'error',
'test/prefer-lowercase-title': 'off',
},
...options,
...antfuOptions,
// Renames apply in a single pass, so antfu's `@typescript-eslint` → `ts`
// and `n` → `node` entries must be dropped rather than reversed on top.
...(canonicalPluginNames && { autoRenamePlugins: false }),
},
...userConfigs,
)

if (!canonicalPluginNames) {
return composer
}

const renames: Record<string, string> = autoRenamePlugins
? Object.fromEntries(
Object.entries(defaultPluginRenaming)
.filter(([, to]) => !(to in canonicalPluginRenaming)),
)
: {}

return composer.renamePlugins({ ...renames, ...canonicalPluginRenaming })
}

export * from '@antfu/eslint-config'
Expand Down
50 changes: 50 additions & 0 deletions packages/eslint-config/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { PleaseaiOptions, TypedFlatConfigItem } from '../src/index.js'
import { describe, expect, test } from 'bun:test'
import { pleaseai } from '../src/index.js'

const userConfig: TypedFlatConfigItem = {
files: ['**/*.ts'],
rules: {
'ts/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': 'warn',
'node/no-sync': 'error',
},
}

async function resolve(options: PleaseaiOptions) {
const configs = await pleaseai(options, userConfig)
return {
plugins: new Set(configs.flatMap(c => Object.keys(c.plugins ?? {}))),
rules: Object.assign({}, ...configs.map(c => c.rules ?? {})) as Record<string, unknown>,
}
}

describe('canonicalPluginNames', () => {
test('keeps antfu plugin names by default', async () => {
const { plugins, rules } = await resolve({})
expect(plugins).toContain('ts')
expect(plugins).toContain('node')
expect(plugins).not.toContain('@typescript-eslint')
expect(plugins).not.toContain('n')
expect(rules['ts/no-unused-vars']).toBe('warn')
})

test('registers @typescript-eslint and n under canonical names', async () => {
const { plugins, rules } = await resolve({ canonicalPluginNames: true })
expect(plugins).toContain('@typescript-eslint')
expect(plugins).toContain('n')
expect(plugins).not.toContain('ts')
expect(plugins).not.toContain('node')
expect(Object.keys(rules).filter(r => r.startsWith('ts/') || r.startsWith('node/'))).toEqual([])
expect(rules['@typescript-eslint/no-explicit-any']).toBe('error')
expect(rules['@typescript-eslint/no-unused-vars']).toBe('warn')
expect(rules['n/no-sync']).toBe('error')
})

test('keeps the other antfu renames', async () => {
const { plugins } = await resolve({ canonicalPluginNames: true })
expect(plugins).toContain('style')
expect(plugins).toContain('import')
expect(plugins).not.toContain('@stylistic')
})
})
Loading