diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1fedd751..1446ca6c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ name: Publish on: push: tags: - - "v*" + - 'v*' concurrency: group: publish @@ -27,7 +27,7 @@ jobs: uses: actions/setup-node@v6 with: node-version: 24 - registry-url: "https://registry.npmjs.org" + registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: yarn install --immutable - name: Build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23843ad0..8b7c5e29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - v6 pull_request: branches: - main diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000..949cf79b --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,21 @@ +{ + "arrowParens": "always", + "bracketSameLine": true, + "bracketSpacing": true, + "jsxSingleQuote": false, + "printWidth": 90, + "quoteProps": "as-needed", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": true, + "overrides": [ + { + "files": ["*.html"], + "options": { + "printWidth": 320 + } + } + ] +} diff --git a/MIGRATION-v6.md b/MIGRATION-v6.md new file mode 100644 index 00000000..d9a566df --- /dev/null +++ b/MIGRATION-v6.md @@ -0,0 +1,71 @@ +# v6 移行ガイド + +## Oxlint の導入と ESLint との役割分担 + +v6 では lint の主軸を Oxlint に切り替えます。Oxlint に実装がない領域(regexp、jsdoc の主要チェック、`import-x/no-extraneous-dependencies`、`no-restricted-syntax`)は、`@d-zero/eslint-config`で ESLint により検査します。 + +1. `@d-zero/oxlint-config`と`@d-zero/oxlint-plugin`を開発依存に追加します。 +2. `oxlint.config.mts`(TypeScript を使わないプロジェクトでは`oxlint.config.mjs`でも可)から共有設定を読み込みます。 +3. `oxlint`を lint コマンドとして実行します。 + +```ts +import config from '@d-zero/oxlint-config'; + +export default { + ...config, + jsPlugins: ['@d-zero/oxlint-plugin'], + rules: { + ...config.rules, + '@d-zero/no-click-event': 'warn', + }, +}; +``` + +`@d-zero/no-click-event`は JavaScript、TypeScript、JSX、TSX の click event handler を検出します。Vue テンプレートの`@click`と`v-on:click`は v6 から検出しません。 + +### ESLint の併用 + +`@d-zero/eslint-config`は Oxlint に実装がない領域(regexp・jsdoc の主要チェック・`import-x/no-extraneous-dependencies`・`import-x/order`・`sort-class-members`・`no-dupe-args`/`no-octal`/`no-redeclare`/`no-undef`)だけを検査します。TypeScript ファイルは対象外です(Oxlint が検査します)。 + +```js +import dz from '@d-zero/eslint-config'; + +export default [...dz.configs.base]; +``` + +DOM/Vue/React 向けの`@d-zero/no-click-event`ルールは`frontend`構成にのみ含まれます。 + +```js +import dz from '@d-zero/eslint-config'; + +export default [...dz.configs.frontend]; +``` + +## Formatter + +JS、TS、JSON、CSS、SCSS、HTML、Markdown、YAML は oxfmt を使用します。Pug と Astro は Prettier を継続使用します。Pug 対応は v7 で廃止予定です。 + +CI では`yarn lint:ci`を使います。このコマンドはファイルを書き換えず、違反があれば失敗します。ローカルでの自動修正は`yarn lint:fix`を使います。 + +oxfmt のフォーマットルールはこのリポジトリの`.oxfmtrc.json`にのみ定義しており、`@d-zero/oxfmt-config`のような配布パッケージは現時点で提供していません。他リポジトリで同じルールを再現したい場合は`.oxfmtrc.json`の内容を手動で複製してください。 + +## lint-staged + +`@d-zero/lint-staged-config`の拡張子別コマンドマッピングを Oxlint/oxfmt ベースに変更しました。pre-commit 時の挙動が変わるため、`lint-staged.config.js`を差し替えるだけのプロジェクトでも影響を受けます。 + +| 拡張子 | v5 まで | v6 から | +| ------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | +| `js` / `ts` / `cjs` / `mjs` / `cts` / `mts` | `eslint --fix` + `prettier --write` | `oxlint --fix` + `oxfmt --write` | +| `jsx` / `tsx` / `vue` / `svelte` | `eslint --fix` + `markuplint` + `prettier --write` | `oxlint --fix` + `markuplint` + `oxfmt --write` | +| `astro` | `eslint --fix` + `markuplint` + `prettier --write` | `oxlint --fix` + `markuplint` + `prettier --write` | +| `css` / `scss` | `stylelint --fix` + `prettier --write` | `stylelint --fix` + `oxfmt --write` | +| `html` | `markuplint` + `prettier --write` | `markuplint` + `oxfmt --write` | +| `json` / `yaml` / `yml` | `prettier --write` | `oxfmt --write` | +| `md` / `mdx` | `prettier --write` + `textlint` | `oxfmt --write` + `textlint` | +| `pug` | `markuplint` + `prettier --write`(変更なし) | `markuplint` + `prettier --write`(変更なし) | + +詳細な対応表は[`packages/@d-zero/lint-staged-config/src/default-mapping.ts`](./packages/@d-zero/lint-staged-config/src/default-mapping.ts)を参照してください。 + +## TypeScript 7 + +TypeScript は 7 系を使用しています。 diff --git a/README.md b/README.md index 4ac7d800..c029266c 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ ### Linter & Formatter 設定 -| パッケージ名 | 内容 | -| ---------------------------------------------------------------------- | ------------------------------------------------------------- | -| [`@d-zero/cspell-config`](./packages/%40d-zero/cspell-config/) | [_CSpell_](https://cspell.org/)の設定ファイル | -| [`@d-zero/eslint-config`](./packages/%40d-zero/eslint-config/) | [_ESLint_](https://eslint.org/)の設定ファイル | -| [`@d-zero/markuplint-config`](./packages/%40d-zero/markuplint-config/) | [_Markuplint_](https://markuplint.dev/)の設定ファイル | -| [`@d-zero/prettier-config`](./packages/%40d-zero/prettier-config/) | [_Prettier_](https://prettier.io/)の設定ファイル | -| [`@d-zero/pug-lint-config`](./packages/%40d-zero/pug-lint-config/) | [_pug-lint_](https://github.com/pugjs/pug-lint)の設定ファイル | -| [`@d-zero/stylelint-config`](./packages/%40d-zero/stylelint-config/) | [_Stylelint_](https://stylelint.io/)の設定ファイル | -| [`@d-zero/textlint-config`](./packages/%40d-zero/textlint-config/) | [_textlint_](https://textlint.github.io/)の設定ファイル | +| パッケージ名 | 内容 | +| ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| [`@d-zero/cspell-config`](./packages/%40d-zero/cspell-config/) | [_CSpell_](https://cspell.org/)の設定ファイル | +| [`@d-zero/oxlint-config`](./packages/%40d-zero/oxlint-config/) | [_Oxlint_](https://oxc.rs/docs/guide/usage/linter.html)の設定ファイル | +| [`@d-zero/eslint-config`](./packages/%40d-zero/eslint-config/) | [_ESLint_](https://eslint.org/)の設定ファイル(Oxlintが未対応の領域を補う) | +| [`@d-zero/markuplint-config`](./packages/%40d-zero/markuplint-config/) | [_Markuplint_](https://markuplint.dev/)の設定ファイル | +| [`@d-zero/prettier-config`](./packages/%40d-zero/prettier-config/) | [_Prettier_](https://prettier.io/)の設定ファイル | +| [`@d-zero/pug-lint-config`](./packages/%40d-zero/pug-lint-config/) | [_pug-lint_](https://github.com/pugjs/pug-lint)の設定ファイル | +| [`@d-zero/stylelint-config`](./packages/%40d-zero/stylelint-config/) | [_Stylelint_](https://stylelint.io/)の設定ファイル | +| [`@d-zero/textlint-config`](./packages/%40d-zero/textlint-config/) | [_textlint_](https://textlint.github.io/)の設定ファイル | ### ツール & ユーティリティ @@ -26,7 +27,8 @@ | パッケージ名 | 内容 | | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`@d-zero/eslint-plugin`](./packages/%40d-zero/eslint-plugin/) | [`@d-zero/eslint-config`](./packages/%40d-zero/eslint-config/)に設定されているディーゼロ独自のESLintルール | +| [`@d-zero/oxlint-plugin`](./packages/%40d-zero/oxlint-plugin/) | [`@d-zero/oxlint-config`](./packages/%40d-zero/oxlint-config/)で利用するディーゼロ独自のOxlintルール | +| [`@d-zero/eslint-plugin`](./packages/%40d-zero/eslint-plugin/) | [`@d-zero/eslint-config`](./packages/%40d-zero/eslint-config/)で利用するディーゼロ独自のESLintルール | | [`@d-zero/stylelint-rules`](./packages/%40d-zero/stylelint-rules/) | [`@d-zero/stylelint-config`](./packages/%40d-zero/stylelint-config/)に設定されているディーゼロ独自のStylelintルール | | [`@d-zero/csstree-scss-syntax`](./packages/%40d-zero/csstree-scss-syntax/) | [`@d-zero/stylelint-rules`](./packages/%40d-zero/stylelint-rules/)内で使用されている[CSSTree](https://github.com/csstree/csstree)用の[SCSS](https://sass-lang.com/documentation/syntax/#scss)パーサープラグイン | @@ -40,15 +42,17 @@ - このバージョンは[Renovate](https://www.mend.io/renovate/)によってアップデートされます。 - [Commitizen](https://github.com/commitizen/cz-cli)を利用してコミットメッセージを作ります。メッセージは[_commitlint_](https://commitlint.js.org/)によってチェックされます。 - [actionlint](https://github.com/rhysd/actionlint)によってGitHub Actionsの設定ファイルを検証しています。 +- [TypeScript](https://www.typescriptlang.org/)は7系を使用しています。 ### メンテ用コマンド -| コマンド | 内容 | -| --------------- | -------------------------------------------------------------------------------------- | -| `npm run build` | 各パッケージのビルドを行います | -| `npm run lint` | リポジトリ内のファイルのリント・自動フォーマット・型チェック・スペルチェックを行います | -| `npm run test` | テスト(*Vitest*を実行します) | -| `npm run co` | Gitコミットを*Commitizen*経由で実行します | +| コマンド | 内容 | +| --------------- | -------------------------------------------------------------------------------- | +| `npm run build` | 各パッケージのビルドを行います | +| `yarn lint` | リポジトリ内のファイルを変更せずに、リント・フォーマット・型・スペルを検査します | +| `yarn lint:fix` | oxlint/oxfmt/Prettier(Pug・Astro)で自動修正します | +| `npm run test` | テスト(*Vitest*を実行します) | +| `npm run co` | Gitコミットを*Commitizen*経由で実行します | --- diff --git a/cspell.json b/cspell.json index 74c03e27..992f8715 100644 --- a/cspell.json +++ b/cspell.json @@ -10,7 +10,12 @@ // ESLint plugin "dzero", - "TSES" + "TSES", + + // Oxc tools + "oxfmt", + "oxfmtrc", + "oxlint" ], "overrides": [ { diff --git a/eslint.config.js b/eslint.config.js index dbf3b35c..d7b4dd42 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,14 +4,9 @@ import dz from '@d-zero/eslint-config'; * @type {import('eslint').ESLint.ConfigData[]} */ export default [ - ...dz.configs.standard, + ...dz.configs.base, { - rules: { - '@typescript-eslint/ban-ts-comment': 0, - }, - }, - { - files: ['*.mjs', '**/*.spec.{js,mjs,ts}'], + files: ['*.mjs', '**/*.spec.{js,mjs}'], rules: { 'import-x/no-extraneous-dependencies': 0, }, @@ -19,14 +14,14 @@ export default [ { files: [ '.textlintrc.js', - '**/cz-config/**/*', - '**/pug-lint-config/**/*', - '**/stylelint-config/**/*', - '**/textlint-config/**/*', + '**/cz-config/**', + '**/pug-lint-config/**', + '**/stylelint-config/**', + '**/textlint-config/**', ], ...dz.configs.commonjs, }, { - ignores: ['**/lib/**/*'], + ignores: ['**/lib/**/*', '**/dist/**', 'test/fixtures/**', '**/*.json'], }, ]; diff --git a/oxlint.config.mts b/oxlint.config.mts new file mode 100644 index 00000000..ec2d2737 --- /dev/null +++ b/oxlint.config.mts @@ -0,0 +1,34 @@ +import config from '@d-zero/oxlint-config'; + +export default { + ...config, + jsPlugins: ['@d-zero/oxlint-plugin'], + rules: { + ...config.rules, + '@d-zero/no-click-event': 'warn', + }, + overrides: [ + { + files: [ + '.textlintrc.js', + '**/cz-config/**', + '**/pug-lint-config/**', + '**/stylelint-config/**', + '**/textlint-config/**', + ], + rules: { + 'typescript/no-require-imports': 'off', + 'typescript/no-var-requires': 'off', + 'unicorn/prefer-module': 'off', + }, + }, + { + // `null` is Stylelint's own API for explicitly disabling a rule; it isn't a stray + // literal to clean up. + files: ['**/stylelint-config/**'], + rules: { + 'unicorn/no-null': 'off', + }, + }, + ], +}; diff --git a/package.json b/package.json index 878496a5..282403e5 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,19 @@ "scripts": { "test": "vitest run", "build": "lerna run build", - "lint": "run-s lint:dedupe lint:tsc lint:eslint lint:prettier lint:textlint lint:cspell lint:secretlint lint:actionlint", - "lint:ci": "run-s lint:dedupe lint:tsc lint:eslint lint:prettier lint:textlint lint:cspell lint:secretlint", + "lint": "run-s lint:dedupe lint:tsc lint:oxlint lint:eslint lint:oxfmt lint:prettier lint:textlint lint:cspell lint:secretlint lint:actionlint", + "lint:ci": "run-s lint:dedupe lint:tsc lint:oxlint lint:eslint lint:oxfmt lint:prettier lint:textlint lint:cspell lint:secretlint", "lint:dedupe": "yarn dedupe --check", + "lint:fix": "run-s lint:oxlint:fix lint:eslint:fix lint:oxfmt:fix lint:prettier:fix", "lint:cspell": "npx cspell --no-progress --show-suggestions \"{*,packages/@d-zero/*/{*,src/**/*}}/\"", - "lint:eslint": "npx eslint --fix \"{*,packages/@d-zero/*/{*,src/**/*}}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}\"", - "lint:prettier": "npx prettier --write \"{*,**/*}.{md,mdc,json,js,cjs,mjs,jsx,ts,cts,mts,tsx}\"", + "lint:oxlint": "oxlint --ignore-pattern \"test/fixtures/**\" .", + "lint:oxlint:fix": "oxlint --fix --ignore-pattern \"test/fixtures/**\" .", + "lint:eslint": "eslint .", + "lint:eslint:fix": "eslint --fix .", + "lint:oxfmt": "oxfmt --check \"{*,**/*}.{md,json,js,cjs,mjs,jsx,ts,cts,mts,tsx,yml,yaml,css,scss,html}\" \"!test/fixtures/**\" \"!.agents/**\"", + "lint:oxfmt:fix": "oxfmt --write \"{*,**/*}.{md,json,js,cjs,mjs,jsx,ts,cts,mts,tsx,yml,yaml,css,scss,html}\" \"!test/fixtures/**\" \"!.agents/**\"", + "lint:prettier": "prettier --check \"{*,**/*}.{pug,astro}\"", + "lint:prettier:fix": "prettier --write \"{*,**/*}.{pug,astro}\"", "lint:textlint": "npx textlint \"./{*,packages/@d-zero/*/{*,src/**/*}}.{md,mdc}\"", "lint:tsc": "npx tsc", "lint:secretlint": "npx secretlint \"**/*\"", @@ -32,16 +39,21 @@ "update": "yarn upgrade-interactive" }, "devDependencies": { + "@d-zero/eslint-config": "6.0.0-alpha.2", "@d-zero/tsconfig": "2.0.0", "@secretlint/secretlint-rule-preset-recommend": "13.0.4", "@types/node": "24.13.3", + "eslint": "9.39.5", "execa": "10.0.1", "husky": "9.1.7", "lerna": "10.0.0", "npm-run-all2": "9.0.3", + "oxfmt": "0.62.0", + "oxlint": "1.77.0", "prettier": "3.9.6", + "prettier-plugin-astro": "0.14.1", "secretlint": "13.0.4", - "typescript": "5.9.3", + "typescript": "7.0.2", "vitest": "4.1.10" }, "resolutions": { diff --git a/packages/@d-zero/commitlint-config/get-cz-config.js b/packages/@d-zero/commitlint-config/get-cz-config.js index 657f27f5..368f089e 100644 --- a/packages/@d-zero/commitlint-config/get-cz-config.js +++ b/packages/@d-zero/commitlint-config/get-cz-config.js @@ -2,7 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; /** - * @returns {Promise} + * @returns {Promise} */ export async function getCZConfig() { const cwd = process.cwd(); @@ -12,12 +12,12 @@ export async function getCZConfig() { const czConfigPath = packageJson?.config?.['cz-customizable']?.config; if (!czConfigPath) { - return null; + return; } - const modPath = czConfigPath.replace(/^(?:\.\/)?node_modules\//, ''); + const modulePath = czConfigPath.replace(/^(?:\.\/)?node_modules\//, ''); - const czConfig = await import(modPath).catch(() => null); + const czConfig = await import(modulePath).catch(() => {}); - return czConfig?.default ?? czConfig ?? null; + return czConfig?.default ?? czConfig; } diff --git a/packages/@d-zero/cspell-config/package.json b/packages/@d-zero/cspell-config/package.json index 012a5bb3..3681dda7 100644 --- a/packages/@d-zero/cspell-config/package.json +++ b/packages/@d-zero/cspell-config/package.json @@ -2,24 +2,24 @@ "name": "@d-zero/cspell-config", "version": "6.0.0-alpha.2", "description": "Configurations of CSpell", + "license": "MIT", + "author": "D-ZERO Co., Ltd.", "repository": { "type": "git", "url": "https://github.com/d-zero-dev/linters.git", "directory": "packages/@d-zero/cspell-config" }, - "author": "D-ZERO Co., Ltd.", - "license": "MIT", - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=22.0.0" - }, "files": [ "cspell.json" ], "main": "cspell.json", + "publishConfig": { + "access": "public" + }, "dependencies": { "cspell": "10.0.1" + }, + "engines": { + "node": ">=22.0.0" } } diff --git a/packages/@d-zero/csstree-scss-syntax/src/index.ts b/packages/@d-zero/csstree-scss-syntax/src/index.ts index f9834037..e72b8bce 100644 --- a/packages/@d-zero/csstree-scss-syntax/src/index.ts +++ b/packages/@d-zero/csstree-scss-syntax/src/index.ts @@ -27,7 +27,7 @@ const forked = CSSTree.fork( switch (this.tokenType) { case DELIM: { // @ts-ignore - // eslint-disable-next-line unicorn/prefer-code-point + // oxlint-disable-next-line unicorn/prefer-code-point -- this is css-tree's TokenStream, not a string; it has no codePointAt const code = this.charCodeAt(this.tokenStart); if (code === DOLLAR_SIGN) { diff --git a/packages/@d-zero/cz-config/monorepo-scopes.js b/packages/@d-zero/cz-config/monorepo-scopes.js index ee3f2ea5..ac1949f2 100644 --- a/packages/@d-zero/cz-config/monorepo-scopes.js +++ b/packages/@d-zero/cz-config/monorepo-scopes.js @@ -3,10 +3,10 @@ const path = require('node:path'); /** * Get the list of packages in the monorepo - * @param {(string | RegExp)[]} removes - * @returns {string[]} + * @param {(string | RegExp)[]} removes - Patterns to strip from each package name + * @returns {string[]} The list of package names */ -module.exports = function (removes) { +module.exports = function getMonorepoScopes(removes) { const cwd = process.cwd(); const lernaPath = path.resolve(cwd, 'lerna.json'); if (!fs.existsSync(lernaPath)) { @@ -16,7 +16,7 @@ module.exports = function (removes) { const packages = fs.globSync(lerna.packages); return packages .map((packagePath) => { - let packageJson = null; + let packageJson; try { packageJson = fs.readFileSync( path.resolve(cwd, packagePath, 'package.json'), @@ -26,14 +26,14 @@ module.exports = function (removes) { // } if (!packageJson) { - return null; + return; } - const pkg = JSON.parse(packageJson); - let name = pkg.name; + const package_ = JSON.parse(packageJson); + let name = package_.name; for (const remove of removes) { name = name.replace(remove, ''); } return name; }) - .filter((name) => name !== null); + .filter((name) => name !== undefined); }; diff --git a/packages/@d-zero/cz-config/package.json b/packages/@d-zero/cz-config/package.json index ad7cdf6f..6c99c310 100644 --- a/packages/@d-zero/cz-config/package.json +++ b/packages/@d-zero/cz-config/package.json @@ -2,30 +2,30 @@ "name": "@d-zero/cz-config", "version": "6.0.0-alpha.2", "description": "Configurations of cz-customizable", + "license": "MIT", + "author": "D-ZERO Co., Ltd.", "repository": { "type": "git", "url": "https://github.com/d-zero-dev/linters.git", "directory": "packages/@d-zero/cz-config" }, - "author": "D-ZERO Co., Ltd.", - "license": "MIT", - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=22.0.0" - }, + "files": [ + "*.js" + ], "type": "commonjs", "exports": { ".": "./index.js", "./ui": "./ui.js", "./scopes": "./scopes.js" }, - "files": [ - "*.js" - ], + "publishConfig": { + "access": "public" + }, "dependencies": { "commitizen": "4.3.2", "cz-customizable": "7.5.4" + }, + "engines": { + "node": ">=22.0.0" } } diff --git a/packages/@d-zero/eslint-config/README.md b/packages/@d-zero/eslint-config/README.md index 7b6af609..22f023cf 100644 --- a/packages/@d-zero/eslint-config/README.md +++ b/packages/@d-zero/eslint-config/README.md @@ -44,12 +44,8 @@ export default [ 以下のプリセットが用意されています。 -| プロパティ | 型 | 説明 | -| ---------------------- | -------- | -------------------------------------------------- | -| `configs.frontend` | `Array` | フロントエンド開発用 | -| `configs.frontendNoTS` | `Array` | フロントエンド開発用(TypeScriptを利用しない場合) | -| `configs.node` | `Array` | Node.js開発用 | -| `configs.nodeNoTS` | `Array` | Node.js開発用(TypeScriptを利用しない場合) | -| `configs.standard` | `Array` | `config.node`と同じ | -| `configs.base` | `Array` | `config.nodeNoTS`と同じ | -| `configs.commonjs` | `Object` | CommonJS用単一設定 | +| プロパティ | 型 | 説明 | +| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `configs.base` | `Array` | Oxlint と併用する前提の共通設定。Oxlint に実装がない領域(regexp・jsdoc の主要チェック・`import-x/no-extraneous-dependencies`・`import-x/order`・`sort-class-members`等)のみ検査。TypeScript ファイルは対象外(Oxlint が検査) | +| `configs.frontend` | `Array` | `configs.base`に加え、DOM/Vue/React 向けの`@d-zero/no-click-event`を検査するフロントエンド開発用 | +| `configs.commonjs` | `Object` | CommonJS用単一設定 | diff --git a/packages/@d-zero/eslint-config/base.js b/packages/@d-zero/eslint-config/base.js index 8b27f8f5..4607bb4b 100644 --- a/packages/@d-zero/eslint-config/base.js +++ b/packages/@d-zero/eslint-config/base.js @@ -1,82 +1,67 @@ -import dzeroPlugin from '@d-zero/eslint-plugin'; +import oxlintConfig from '@d-zero/oxlint-config'; import js from '@eslint/js'; import comments from 'eslint-plugin-eslint-comments'; import { flatConfigs as importX } from 'eslint-plugin-import-x'; -import jsdoc from 'eslint-plugin-jsdoc'; +import { configs as jsdocConfigs } from 'eslint-plugin-jsdoc'; +import oxlintPlugin from 'eslint-plugin-oxlint'; import * as regexpPlugin from 'eslint-plugin-regexp'; import sortClassMembers from 'eslint-plugin-sort-class-members'; import eslintPluginUnicorn from 'eslint-plugin-unicorn'; import globals from 'globals'; +import { restrictedSyntax } from './restricted-syntax.js'; + /** * @type {import('eslint').Linter.Config[]} */ export const base = [ + { + ignores: ['**/*.{ts,mts,cts,tsx}'], + }, { ...js.configs.recommended, rules: { ...js.configs.recommended.rules, - 'no-console': 'warn', 'no-mixed-spaces-and-tabs': 0, - 'no-restricted-syntax': [ - 2, - { - selector: - ':matches(PropertyDefinition, MethodDefinition)[accessibility="private"]', - message: 'Use #private instead', - }, - { - selector: - ':matches(PropertyDefinition, MethodDefinition)[accessibility="public"]', - message: 'Remove public keyword', - }, - { - selector: 'MethodDefinition[key.name=/^_/]:not([accessibility="protected"])', - message: 'Add protected keyword', - }, - { - selector: 'MethodDefinition:not([key.name=/^_/])[accessibility="protected"]', - message: 'Start with `_` if you want to use protected', - }, - { - selector: - "CallExpression[callee.property.name='addEventListener'][arguments.0.value='DOMContentLoaded']", - message: - "Avoid using 'DOMContentLoaded'. Use 'defer' or 'type=module' attribute instead.", - }, - ], - 'no-unused-vars': 0, - 'no-var': 2, - 'prefer-const': 2, - 'prefer-rest-params': 2, - 'prefer-spread': 2, + 'no-restricted-syntax': [2, ...restrictedSyntax], }, }, { ...eslintPluginUnicorn.configs.recommended, + }, + { rules: { - ...eslintPluginUnicorn.configs.recommended.rules, + // Repo style choices predating the Oxlint migration — not oxlint-overlap + // suppressions, so keep these off even though both linters enable them by default. + 'unicorn/no-null': 0, + 'unicorn/prefer-ternary': 0, + 'unicorn/prevent-abbreviations': 0, + 'unicorn/no-nested-ternary': 0, 'unicorn/consistent-destructuring': 0, - 'unicorn/consistent-function-scoping': 0, - 'unicorn/no-anonymous-default-export': 0, 'unicorn/no-array-callback-reference': 0, - 'unicorn/no-nested-ternary': 0, - 'unicorn/no-null': 0, - 'unicorn/no-process-exit': 0, 'unicorn/prefer-global-this': 0, 'unicorn/prefer-query-selector': 0, + 'unicorn/consistent-function-scoping': 0, + 'unicorn/no-anonymous-default-export': 0, 'unicorn/prefer-string-raw': 0, - 'unicorn/prefer-ternary': 0, - 'unicorn/prevent-abbreviations': 0, }, }, - regexpPlugin.configs['flat/recommended'], + { + ...regexpPlugin.configs['flat/recommended'], + }, { ...importX.recommended, rules: { ...importX.recommended.rules, + // `import-x` isn't recognized by `eslint-plugin-oxlint`'s `buildFromOxlintConfig` + // (it maps oxlint's `import` scope to the `import/` prefix, not `import-x/`), so + // these overlaps with `@d-zero/oxlint-config` stay disabled manually. + 'import-x/default': 0, + 'import-x/namespace': 0, + 'import-x/no-duplicates': 0, 'import-x/no-extraneous-dependencies': 2, 'import-x/no-named-as-default': 0, + 'import-x/no-named-as-default-member': 0, 'import-x/no-unresolved': 0, 'import-x/order': [ 2, @@ -108,15 +93,7 @@ export const base = [ }, }, { - ...jsdoc.configs['flat/recommended'], - rules: { - ...jsdoc.configs['flat/recommended'].rules, - 'jsdoc/require-param-type': 0, - 'jsdoc/require-param-description': 0, - 'jsdoc/require-returns': 0, - 'jsdoc/require-returns-type': 0, - 'jsdoc/require-returns-description': 0, - }, + ...jsdocConfigs['flat/recommended'], }, { plugins: { @@ -243,19 +220,15 @@ export const base = [ }, { languageOptions: { - ecmaVersion: 2023, + ecmaVersion: 'latest', globals: { ...globals.builtin, ...globals.nodeBuiltin, }, }, }, - { - plugins: { - '@d-zero': dzeroPlugin, - }, - rules: { - '@d-zero/no-click-event': 'warn', - }, - }, + // Turns off every rule that `@d-zero/oxlint-config` already covers, generated straight from + // its config object so the two configs can't drift out of sync. `import-x/*` overlaps are + // handled manually above (see the comment there). + ...oxlintPlugin.buildFromOxlintConfig(oxlintConfig), ]; diff --git a/packages/@d-zero/eslint-config/commonjs.js b/packages/@d-zero/eslint-config/commonjs.js index b4349614..73b82046 100644 --- a/packages/@d-zero/eslint-config/commonjs.js +++ b/packages/@d-zero/eslint-config/commonjs.js @@ -4,10 +4,6 @@ import globals from 'globals'; * @type {import('eslint').Linter.Config} */ export const commonjs = { - rules: { - 'unicorn/prefer-module': 0, - '@typescript-eslint/no-require-imports': 0, - }, languageOptions: { globals: { ...globals.commonjs, diff --git a/packages/@d-zero/eslint-config/frontend.js b/packages/@d-zero/eslint-config/frontend.js index a10ae52d..7a1ba5a6 100644 --- a/packages/@d-zero/eslint-config/frontend.js +++ b/packages/@d-zero/eslint-config/frontend.js @@ -1,11 +1,15 @@ +import dzeroPlugin from '@d-zero/eslint-plugin'; import globals from 'globals'; /** * @type {import('eslint').Linter.Config} */ export const frontend = { + plugins: { + '@d-zero': dzeroPlugin, + }, rules: { - 'unicorn/prefer-top-level-await': 0, + '@d-zero/no-click-event': 'warn', }, languageOptions: { globals: { diff --git a/packages/@d-zero/eslint-config/index.js b/packages/@d-zero/eslint-config/index.js index 7dcf2c43..144bf5d1 100644 --- a/packages/@d-zero/eslint-config/index.js +++ b/packages/@d-zero/eslint-config/index.js @@ -1,19 +1,14 @@ import { base } from './base.js'; import { commonjs } from './commonjs.js'; import { frontend } from './frontend.js'; -import { ts } from './typescript.js'; /** * @type {import('eslint').ESLint.Plugin} */ export default { configs: { - standard: [...ts], base: [...base], - node: [...ts], - nodeNoTS: [...base], - frontend: [...ts, frontend], - frontendNoTS: [...base, frontend], + frontend: [...base, frontend], commonjs, }, }; diff --git a/packages/@d-zero/eslint-config/package.json b/packages/@d-zero/eslint-config/package.json index ab7dc1e4..75251ed2 100644 --- a/packages/@d-zero/eslint-config/package.json +++ b/packages/@d-zero/eslint-config/package.json @@ -21,16 +21,17 @@ }, "dependencies": { "@d-zero/eslint-plugin": "6.0.0-alpha.2", + "@d-zero/oxlint-config": "6.0.0-alpha.2", "@eslint/js": "9.39.5", "eslint": "9.39.5", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import-x": "4.17.1", "eslint-plugin-jsdoc": "64.1.0", + "eslint-plugin-oxlint": "1.77.0", "eslint-plugin-regexp": "3.1.1", "eslint-plugin-sort-class-members": "1.22.1", "eslint-plugin-unicorn": "63.0.0", - "globals": "17.11.0", - "typescript-eslint": "8.67.0" + "globals": "17.11.0" }, "engines": { "node": ">=22.0.0" diff --git a/packages/@d-zero/eslint-config/restricted-syntax.js b/packages/@d-zero/eslint-config/restricted-syntax.js new file mode 100644 index 00000000..16c9a482 --- /dev/null +++ b/packages/@d-zero/eslint-config/restricted-syntax.js @@ -0,0 +1,11 @@ +/** + * @type {[selector: string, message: string][]} + */ +export const restrictedSyntax = [ + { + selector: + "CallExpression[callee.property.name='addEventListener'][arguments.0.value='DOMContentLoaded']", + message: + "Avoid using 'DOMContentLoaded'. Use 'defer' or 'type=module' attribute instead.", + }, +]; diff --git a/packages/@d-zero/eslint-config/typescript.js b/packages/@d-zero/eslint-config/typescript.js deleted file mode 100644 index 43b00a30..00000000 --- a/packages/@d-zero/eslint-config/typescript.js +++ /dev/null @@ -1,44 +0,0 @@ -import tsESLint from 'typescript-eslint'; - -import { base } from './base.js'; - -export const ts = tsESLint.config( - base, - tsESLint.configs.recommended, - { - files: ['*.{ts,tsx}', '**/*.{ts,tsx}'], - languageOptions: { - parserOptions: { - sourceType: 'module', - project: ['./tsconfig.json'], - }, - }, - settings: { - 'import-x/parsers': { - '@typescript-eslint/parser': ['.ts'], - }, - }, - rules: { - '@typescript-eslint/adjacent-overload-signatures': 2, - '@typescript-eslint/ban-ts-comment': 0, - '@typescript-eslint/consistent-type-imports': 1, - '@typescript-eslint/member-ordering': 0, - '@typescript-eslint/no-array-constructor': 2, - '@typescript-eslint/no-explicit-any': [1, { fixToUnknown: true }], - '@typescript-eslint/no-floating-promises': 2, - '@typescript-eslint/no-namespace': [2, { allowDeclarations: true }], - '@typescript-eslint/no-unnecessary-type-assertion': 2, - '@typescript-eslint/no-unused-vars': 2, - '@typescript-eslint/no-var-requires': 2, - '@typescript-eslint/prefer-namespace-keyword': 2, - '@typescript-eslint/require-await': 2, - '@typescript-eslint/restrict-plus-operands': 0, - }, - }, - { - files: ['*.{test,spec}.{ts,mts,tsx}'], - rules: { - 'import/no-extraneous-dependencies': 0, - }, - }, -); diff --git a/packages/@d-zero/eslint-plugin/package.json b/packages/@d-zero/eslint-plugin/package.json index 1de2f650..cd2b1b90 100644 --- a/packages/@d-zero/eslint-plugin/package.json +++ b/packages/@d-zero/eslint-plugin/package.json @@ -22,12 +22,8 @@ "scripts": { "build": "tsc" }, - "dependencies": { - "@typescript-eslint/utils": "8.67.0" - }, "devDependencies": { - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/rule-tester": "8.67.0", + "@types/estree": "1.0.8", "eslint": "9.39.5", "vue-eslint-parser": "10.4.1" }, diff --git a/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.spec.ts b/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.spec.ts index 3d4da5ad..f4d5992a 100644 --- a/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.spec.ts +++ b/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.spec.ts @@ -1,9 +1,9 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; -import { afterAll, describe, it } from 'vitest'; +import { RuleTester } from 'eslint'; +import vueParser from 'vue-eslint-parser'; +import { describe, it } from 'vitest'; import rule from './index.js'; -RuleTester.afterAll = afterAll; RuleTester.describe = describe; RuleTester.it = it; @@ -99,3 +99,30 @@ ruleTester.run('no-click-event', rule, { }, ], }); + +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: vueParser, + ecmaVersion: 2023, + sourceType: 'module', + }, +}); + +vueRuleTester.run('no-click-event (Vue)', rule, { + valid: [ + '', + '', + ], + invalid: [ + // Pattern 6: Vue @click shorthand + { + code: '', + errors: [{ messageId: 'noClickEvent' }], + }, + // Pattern 6: Vue v-on:click + { + code: '', + errors: [{ messageId: 'noClickEvent' }], + }, + ], +}); diff --git a/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.ts b/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.ts index 14ba3765..302fa3cd 100644 --- a/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.ts +++ b/packages/@d-zero/eslint-plugin/src/rules/no-click-event/index.ts @@ -1,4 +1,6 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import type { Rule } from 'eslint'; +import type { AssignmentExpression, CallExpression, MemberExpression } from 'estree'; +import type { AST } from 'vue-eslint-parser'; import { createRule } from '../../utils/create-rule.js'; @@ -15,18 +17,15 @@ export default createRule({ }, schema: [], // No options }, - defaultOptions: [], - create(context) { - return { + create(context): Rule.RuleListener { + const scriptVisitor: Rule.RuleListener = { // Pattern 1: addEventListener('click', ...) - 'CallExpression[callee.property.name="addEventListener"]'( - node: TSESTree.CallExpression, - ) { + 'CallExpression[callee.property.name="addEventListener"]'(node: CallExpression) { const args = node.arguments; const firstArg = args[0]; if (firstArg && firstArg.type === 'Literal' && firstArg.value === 'click') { context.report({ - node, + node: node as unknown as Rule.Node, messageId: 'noClickEvent', }); } @@ -34,22 +33,22 @@ export default createRule({ // Pattern 2: element.onclick = ... 'AssignmentExpression[left.type="MemberExpression"][left.property.name="onclick"]'( - node: TSESTree.AssignmentExpression, + node: AssignmentExpression, ) { context.report({ - node, + node: node as unknown as Rule.Node, messageId: 'noClickEvent', }); }, // Pattern 3: jQuery .on('click', ...) 'CallExpression[callee.type="MemberExpression"][callee.property.name="on"]'( - node: TSESTree.CallExpression, + node: CallExpression, ) { const firstArg = node.arguments[0]; if (firstArg && firstArg.type === 'Literal' && firstArg.value === 'click') { context.report({ - node, + node: node as unknown as Rule.Node, messageId: 'noClickEvent', }); } @@ -57,14 +56,14 @@ export default createRule({ // Pattern 4: jQuery .click(handler) - only for jQuery objects with arguments (event handler registration) 'CallExpression[callee.type="MemberExpression"][callee.property.name="click"]'( - node: TSESTree.CallExpression, + node: CallExpression, ) { // Allow .click() without arguments (click execution, not event handler registration) if (node.arguments.length === 0) { return; } - const callee = node.callee as TSESTree.MemberExpression; + const callee = node.callee as MemberExpression; const object = callee.object; // $(...).click(handler) or jQuery(...).click(handler) @@ -74,7 +73,7 @@ export default createRule({ (object.callee.name === '$' || object.callee.name === 'jQuery') ) { context.report({ - node, + node: node as unknown as Rule.Node, messageId: 'noClickEvent', }); return; @@ -83,37 +82,51 @@ export default createRule({ // $element.click(handler) if (object.type === 'Identifier' && object.name.startsWith('$')) { context.report({ - node, + node: node as unknown as Rule.Node, messageId: 'noClickEvent', }); } }, // Pattern 5: React onClick={...} - 'JSXAttribute[name.name="onClick"]'(node: TSESTree.JSXAttribute) { + // JSXAttribute isn't part of ESTree; espree with jsx enabled produces it, but no type package covers it. + 'JSXAttribute[name.name="onClick"]'(node: unknown) { context.report({ - node, + node: node as Rule.Node, messageId: 'noClickEvent', }); }, + }; - // Pattern 6: Vue @click or v-on:click - // Note: This requires vue-eslint-parser - // eslint-disable-next-line @typescript-eslint/no-explicit-any - 'VAttribute[key.name.name="click"]'(node: any) { - context.report({ - node, - messageId: 'noClickEvent', - }); - }, + // Vue's