diff --git a/.changeset/code-style-cli-oxfmt-readme.md b/.changeset/code-style-cli-oxfmt-readme.md new file mode 100644 index 0000000..f98a086 --- /dev/null +++ b/.changeset/code-style-cli-oxfmt-readme.md @@ -0,0 +1,7 @@ +--- +'@naverpay/code-style-cli': patch +--- + +code-style-cli README의 잘못된 oxfmt 설명 삭제 + +oxfmt 가이드에서 "별도 config 패키지가 없습니다"라는 잘못된 문장을 제거했습니다. (`@naverpay/oxfmt-config` 패키지가 존재) diff --git a/.changeset/oxlint-config-jsplugins.md b/.changeset/oxlint-config-jsplugins.md new file mode 100644 index 0000000..b76eb6a --- /dev/null +++ b/.changeset/oxlint-config-jsplugins.md @@ -0,0 +1,12 @@ +--- +'@naverpay/oxlint-config': minor +--- + +[#164] oxlint-config 프리셋에 jsPlugins 룰 추가 (unused-imports, @naverpay/prevent-default-import) + +oxlint 네이티브에 없는 룰을 문자열 배열 형식의 `jsPlugins`로 추가했습니다 (`oxlint@>=1.0` 호환 유지). 필요한 플러그인은 패키지 `dependencies`로 함께 설치됩니다. + +- node 프리셋: `eslint-plugin-unused-imports` → `unused-imports/no-unused-imports` +- react 프리셋: `@naverpay/eslint-plugin` → `@naverpay/prevent-default-import` (node의 unused-imports는 `extends`로 병합 상속) + +주의: `jsPlugins`는 experimental 단계라 실행 시 경고가 출력되고 IDE(oxc LSP)에서는 표시되지 않습니다. `import/order`·react 옵션 오버라이드는 네임스페이스 예약(`import`/`react`) 때문에 문자열 형식으로 불가하여 객체 형식(oxlint 상향 필요)과 함께 후속 작업으로 분리합니다. diff --git a/.changeset/oxlint-config-react-preset.md b/.changeset/oxlint-config-react-preset.md new file mode 100644 index 0000000..74b794a --- /dev/null +++ b/.changeset/oxlint-config-react-preset.md @@ -0,0 +1,13 @@ +--- +'@naverpay/oxlint-config': minor +--- + +[#164] oxlint-config 에 `react` 프리셋 추가 + +`node` 프리셋을 확장하는 `react/.oxlintrc.json` 프리셋을 신설했습니다. `@naverpay/eslint-config` 의 react 프리셋을 기준으로, oxlint 가 **네이티브로 지원하는 룰만** 미러링했습니다 (jsPlugins 미사용 → 새 의존성 없음, IDE(oxc LSP) 정상 동작, experimental 경고 없음). + +- `plugins: ["react", "jsx-a11y"]` 선언 (두 플러그인은 기본 비활성 → 선언 시 correctness 룰 활성화) +- 명시 룰: `curly`, `no-restricted-imports`(lodash), `react/rules-of-hooks`, `react/exhaustive-deps`, `react/jsx-handler-names`, `jsx-a11y/{alt-text,label-has-associated-control}` +- 소비: `extends: ["./node_modules/@naverpay/oxlint-config/react/.oxlintrc.json"]` + +> `import/order`, `unused-imports`, `@naverpay/*` 커스텀 룰 등 네이티브 미지원분은 jsPlugins 가 필요해 이번 범위에서 제외했습니다 (#164 후속). diff --git a/.changeset/oxlint-config-readme-extends.md b/.changeset/oxlint-config-readme-extends.md new file mode 100644 index 0000000..19ae532 --- /dev/null +++ b/.changeset/oxlint-config-readme-extends.md @@ -0,0 +1,13 @@ +--- +'@naverpay/oxlint-config': patch +--- + +oxlint-config README에 `extends` 상속 한계 및 소비자 필수 항목 안내 추가 + +`card-apply-web`에서 카나리(`0.1.0-canary.260701-d925f10`) react 프리셋을 검증한 결과, oxlint의 `extends`는 `rules`/`plugins`/`jsPlugins`만 병합하고 `env`/`globals`/`categories`는 **상속하지 않는다**는 것을 확인했습니다 (oxlint 1.71.0/1.72.0, [oxc#20087](https://github.com/oxc-project/oxc/issues/20087)). + +- `categories`가 상속되지 않아 oxlint 기본 `correctness`가 켜져 `unicorn/*` 룰이 의도치 않게 발화 +- `env`가 상속되지 않아 `no-undef`가 `require`/`module`/`process`/`__dirname` 등을 false positive로 잡음 +- 프리셋에 `globals`를 직접 추가해도 extends로 상속되지 않아 해결 불가 (검증 완료) + +따라서 소비자는 최상위 config에 `env`/`categories`(필요시 `globals`)를 직접 작성해야 합니다. README의 Node.js/React 예시에 해당 항목을 포함하고, 한계를 명시하는 Note를 추가했습니다. diff --git a/packages/code-style-cli/README.md b/packages/code-style-cli/README.md index cb04e15..f15a39c 100644 --- a/packages/code-style-cli/README.md +++ b/packages/code-style-cli/README.md @@ -53,7 +53,7 @@ CLI는 기본 설정 파일만 생성합니다. 추가 설정이 필요한 경 > > **Note:** oxfmt는 현재 **alpha** 단계입니다. VSCode Extension 지원도 experimental 상태입니다. -oxfmt는 현재 `extends` 옵션을 지원하지 않아 별도 config 패키지가 없습니다. CLI에서 네이버페이 권장 설정이 포함된 `.oxfmtrc.json`을 생성합니다. +CLI에서 네이버페이 권장 설정이 포함된 `.oxfmtrc.json`을 생성합니다. ### 설정 diff --git a/packages/oxlint-config/CLAUDE.md b/packages/oxlint-config/CLAUDE.md index 13597c8..c38ef2d 100644 --- a/packages/oxlint-config/CLAUDE.md +++ b/packages/oxlint-config/CLAUDE.md @@ -1,7 +1,8 @@ # CLAUDE.md — @naverpay/oxlint-config NaverPay's [oxlint](https://oxc.rs) config (Rust-based, ESLint-compatible linter). **Config-only** — -ships the `node/` directory (`node/.oxlintrc.json`). No build, no tests. Peer dep: `oxlint@>=1.0.0`. +ships the `node/` and `react/` preset directories (`/.oxlintrc.json`). No build, no tests. +Peer dep: `oxlint@>=1.0.0`. ## Consumption gotcha — path resolution @@ -15,7 +16,8 @@ specifier. Consumers therefore reference the full path, not the package name: } ``` -This is why the package exposes a real directory (`files: ["node"]`) instead of `exports`/`main`. +This is why the package exposes real directories (`files: ["node", "react"]`) instead of +`exports`/`main`. ## Ruleset @@ -23,4 +25,38 @@ This is why the package exposes a real directory (`files: ["node"]`) instead of `no-console`, `no-param-reassign`, `no-unused-vars` with `^_` ignore patterns) plus a set of `@typescript-eslint/*` rules. An `overrides` block for `**/*.{ts,tsx}` disables the JS-only `no-unused-vars`/`no-undef`/`no-unused-expressions` and switches to their `@typescript-eslint` -equivalents. Edit this file to change rules; add a changeset. +equivalents. + +`react/.oxlintrc.json` `extends` the node preset (relative path `../node/.oxlintrc.json`), adds +`env.browser`, declares `plugins: ["react", "jsx-a11y"]` (both are **off by default** — declaring +them activates their `correctness` rules), and layers NaverPay's explicit React choices: `curly`, +`no-restricted-imports` (lodash), `react/rules-of-hooks`, `react/exhaustive-deps`, +`react/jsx-handler-names`, and `jsx-a11y/{alt-text,label-has-associated-control}`. + +## jsPlugins (experimental) + +For rules oxlint has no native (Rust) implementation, the presets load ESLint-compatible plugins +via `jsPlugins` (string-array form, so it works on `oxlint@>=1.0`). These plugins ship as package +`dependencies` so they resolve for consumers: + +- node preset — `eslint-plugin-unused-imports` → `unused-imports/no-unused-imports` +- react preset — `@naverpay/eslint-plugin` → `@naverpay/prevent-default-import` + +`jsPlugins` arrays **merge across `extends`**, so the react preset inherits the node preset's +`unused-imports`. Two caveats, inherent to oxlint's JS-plugin support (not the format): + +- oxlint prints an **experimental warning** on every run. +- JS plugins are **not supported in the language server** (no IDE/oxc-LSP diagnostics for them). + +**String-format limitation.** JS plugins can't be renamed, so plugins whose `meta.name` collides +with a reserved native namespace **fail to load** (`import`, `react` are reserved). That blocks +`import/order` (`eslint-plugin-import`) and `eslint-plugin-react` option overrides — those need the +object form `{name, specifier}`, which requires a newer oxlint and drops `1.x` back-compat. They are +deferred to a follow-up. Note the scoped `@naverpay/eslint-plugin` registers under namespace +`@naverpay` (first path segment), so its rules are keyed `@naverpay/`. + +## Adding rules + +oxlint **silently ignores** unknown rule names — verify native support with `oxlint --rules` before +adding a native rule, or add a jsPlugin (mind the reserved-namespace limitation above). Edit the +preset files to change rules; add a changeset. diff --git a/packages/oxlint-config/README.md b/packages/oxlint-config/README.md index cb246d2..cb801a7 100644 --- a/packages/oxlint-config/README.md +++ b/packages/oxlint-config/README.md @@ -16,12 +16,38 @@ npm install @naverpay/oxlint-config oxlint -D 프로젝트 루트에 `.oxlintrc.json` 파일을 생성하고 아래와 같이 설정합니다. +> **⚠️ 중요 — `extends`는 `rules`/`plugins`/`jsPlugins`만 상속합니다.** +> +> oxlint의 `extends`는 ESLint와 달리 `env`/`globals`/`categories`를 **상속하지 않습니다**. (oxlint 1.72.0 기준 확인, [oxc#20087](https://github.com/oxc-project/oxc/issues/20087) 참고) +> +> 따라서 아래 항목은 소비자의 **최상위** config에 직접 작성해야 합니다: +> +> - `env` — `no-undef`가 참조할 전역 변수 주입 (`node`, `browser`, `commonjs` 등). 프리셋에 선언된 `env`는 `extends`로 넘어오지 않습니다. +> - `categories` — oxlint 기본 `correctness` 카테고리를 끄지 않으면 프리셋에 없는 `unicorn/*` 룰이 의도치 않게 발화합니다. `categories: { "correctness": "off" }`를 명시하세요. +> +> 프리셋이 활성화하는 룰(`rules`)과 `jsPlugins`는 정상적으로 상속됩니다. + ### Node.js 프로젝트 ```json { "$schema": "./node_modules/oxlint/configuration_schema.json", - "extends": ["./node_modules/@naverpay/oxlint-config/node/.oxlintrc.json"] + "extends": ["./node_modules/@naverpay/oxlint-config/node/.oxlintrc.json"], + "env": { "node": true, "commonjs": true, "es2023": true }, + "categories": { "correctness": "off" } +} +``` + +### React 프로젝트 + +`react` 프리셋은 `node` 프리셋을 확장하고 React / JSX 접근성(a11y) 룰을 추가로 활성화합니다. + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "extends": ["./node_modules/@naverpay/oxlint-config/react/.oxlintrc.json"], + "env": { "node": true, "browser": true, "commonjs": true, "es2023": true }, + "categories": { "correctness": "off" } } ``` @@ -31,10 +57,16 @@ npm install @naverpay/oxlint-config oxlint -D { "$schema": "./node_modules/oxlint/configuration_schema.json", "extends": ["./node_modules/@naverpay/oxlint-config/node/.oxlintrc.json"], + "env": { "node": true, "commonjs": true, "es2023": true }, + "categories": { "correctness": "off" }, "ignorePatterns": ["dist", "node_modules"] } ``` +**Tip:** `no-undef`가 `.js`/`.cjs` 파일에서 전역(`require`, `process` 등)을 잡는다면 `env.node`/`env.commonjs`가 최상위에 선언되어 있는지 확인하세요. TypeScript(`.ts`/`.tsx`)에서는 프리셋의 `overrides`가 `no-undef`를 끄므로 영향이 없습니다. + +> **Note:** oxlint 네이티브에 없는 일부 룰(`unused-imports/no-unused-imports`, `@naverpay/prevent-default-import`)은 `jsPlugins`로 제공되며, 필요한 플러그인은 이 패키지의 의존성으로 함께 설치됩니다. `jsPlugins`는 아직 **experimental** 단계라 lint 실행 시 경고가 출력되고, IDE(oxc language server)에서는 해당 룰이 표시되지 않습니다. + ## CLI package.json에 스크립트를 추가하여 lint 검사를 할 수 있습니다. diff --git a/packages/oxlint-config/node/.oxlintrc.json b/packages/oxlint-config/node/.oxlintrc.json index 0cae8a3..d018249 100644 --- a/packages/oxlint-config/node/.oxlintrc.json +++ b/packages/oxlint-config/node/.oxlintrc.json @@ -4,6 +4,7 @@ "commonjs": true, "es2023": true }, + "jsPlugins": ["eslint-plugin-unused-imports"], "rules": { "eqeqeq": ["error", "smart"], "no-console": "error", @@ -11,6 +12,8 @@ "no-unused-vars": ["error", {"ignoreRestSiblings": true, "argsIgnorePattern": "^_", "varsIgnorePattern": "^_"}], "no-undef": "error", + "unused-imports/no-unused-imports": "error", + "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", diff --git a/packages/oxlint-config/package.json b/packages/oxlint-config/package.json index 1b67712..9136122 100644 --- a/packages/oxlint-config/package.json +++ b/packages/oxlint-config/package.json @@ -19,8 +19,13 @@ "author": "@NaverPayDev/frontend", "type": "module", "files": [ - "node" + "node", + "react" ], + "dependencies": { + "@naverpay/eslint-plugin": "workspace:*", + "eslint-plugin-unused-imports": "^4.1.4" + }, "devDependencies": { "oxlint": "^1.31.0" }, diff --git a/packages/oxlint-config/react/.oxlintrc.json b/packages/oxlint-config/react/.oxlintrc.json new file mode 100644 index 0000000..7d19745 --- /dev/null +++ b/packages/oxlint-config/react/.oxlintrc.json @@ -0,0 +1,40 @@ +{ + "extends": ["../node/.oxlintrc.json"], + "env": { + "browser": true + }, + "plugins": ["react", "jsx-a11y"], + "jsPlugins": ["@naverpay/eslint-plugin"], + "rules": { + "curly": ["error", "all"], + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "lodash", + "message": "lodash는 CommonJS로 작성되어 있어 트리쉐이킹이 되지 않아 번들 사이즈를 크게 합니다. lodash/* 형식으로 import 해주세요." + } + ] + } + ], + + "@naverpay/prevent-default-import": ["error", {"packages": ["react"]}], + + "react/rules-of-hooks": "error", + "react/exhaustive-deps": "error", + "react/jsx-handler-names": [ + "warn", + { + "eventHandlerPrefix": "(on|handle)", + "eventHandlerPropPrefix": "(on|handle)", + "checkLocalVariables": true, + "checkInlineFunction": false + } + ], + + "jsx-a11y/alt-text": "error", + "jsx-a11y/label-has-associated-control": "error", + "jsx-a11y/click-events-have-key-events": "off" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c816b06..c277316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ importers: version: 56.0.1(eslint@9.17.0) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0) + version: 4.1.4(@typescript-eslint/eslint-plugin@8.61.1(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0) eslint-plugin-yml: specifier: ^1.16.0 version: 1.16.0(eslint@9.17.0) @@ -178,6 +178,13 @@ importers: version: 4.0.17(sass-embedded@1.85.1)(terser@5.36.0) packages/oxlint-config: + dependencies: + '@naverpay/eslint-plugin': + specifier: workspace:* + version: link:../eslint-plugin + eslint-plugin-unused-imports: + specifier: ^4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@8.61.1(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0) devDependencies: oxlint: specifier: ^1.31.0 @@ -7490,7 +7497,7 @@ snapshots: semver: 7.7.1 strip-indent: 3.0.0 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.61.1(eslint@9.17.0)(typescript@6.0.3))(eslint@9.17.0): dependencies: eslint: 9.17.0 optionalDependencies: