diff --git a/.oxlintrc.json b/.oxlintrc.json index ed301f6..89e16b6 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -20,10 +20,9 @@ "eslint/sort-imports": "off", "import/consistent-type-specifier-style": "off", "unicorn/prefer-export-from": "off", - "typescript/method-signature-style": "off", - "typescript/no-unsafe-type-assertion": "off", - "typescript/no-unnecessary-type-parameters": "off" + "typescript/method-signature-style": "off" }, + "options": { "typeAware": true }, "env": { "builtin": true, "es2024": true, "node": true }, "overrides": [ { @@ -32,8 +31,7 @@ "no-unused-expressions": "off", "no-empty-file": "off", "import/no-nodejs-modules": "off", - "eslint/no-magic-numbers": "off", - "typescript/no-floating-promises": "off" + "eslint/no-magic-numbers": "off" } } ], diff --git a/README.md b/README.md index 8cce979..4a4ea58 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Pattern matching for TypeScript/ESM environments (F#-style, not regex). - **TypeScript 7** — type checker and build (`tsc`). - **node --test** + `--experimental-strip-types` — test runner (Node 22.6+, flag dropped on Node 24). - **c8** — code coverage for `test:ci`. -- **oxlint** — Rust-based linter. -- **oxlint-tsgolint** — type-aware linting via typescript-go (powers `--type-aware` in `oxlint`). Catches unsafe type assertions, unnecessary type parameters, and other type-system issues that regular oxlint can't see. +- **oxlint** — Rust-based linter. Type-aware rules are enabled via `options.typeAware: true` in `.oxlintrc.json` (powered by `oxlint-tsgolint`). +- **oxlint-tsgolint** — type-aware linting via typescript-go (declarative via `.oxlintrc.json`, no CLI flag). Catches unsafe type assertions, unnecessary type parameters, and other type-system issues that regular oxlint can't see. Source-level `oxlint-disable` directives are used to silence known false positives (e.g., `expectTypeOf()` in test files). - **oxfmt** — Rust-based formatter (Prettier-compatible). Formats JS/TS, JSON/JSONC, YAML, Markdown, MDX, and more; built-in `package.json` key sorting replaces `sort-package-json`. - **cspell** — spell checking. - **knip** — finds unused dependencies, exports, and files. Scoped via `--include dependencies,exports,files` to skip the noisy `types` category (which produces false positives for libraries whose exported types are part of the public API). diff --git a/package.json b/package.json index de6fbfc..705d747 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "check:knip": "knip --include dependencies,exports,files", "check:outdated": "check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-darwin-arm64,@oxfmt/binding-darwin-x64,@oxfmt/binding-linux-arm64-gnu,@oxfmt/binding-linux-arm64-musl,@oxfmt/binding-linux-x64-gnu,@oxfmt/binding-linux-x64-musl,@oxfmt/binding-win32-x64-msvc,@oxlint/binding-darwin-arm64,@oxlint/binding-darwin-x64,@oxlint/binding-linux-arm64-gnu,@oxlint/binding-linux-arm64-musl,@oxlint/binding-linux-x64-gnu,@oxlint/binding-linux-x64-musl,@oxlint/binding-win32-x64-msvc,@oxlint-tsgolint/darwin-arm64,@oxlint-tsgolint/darwin-x64,@oxlint-tsgolint/linux-arm64,@oxlint-tsgolint/linux-x64,@oxlint-tsgolint/win32-arm64,@oxlint-tsgolint/win32-x64", "check:oxfmt": "oxfmt --check ${LEFTHOOK_FILES:-.}", - "check:oxlint": "oxlint --type-aware ${LEFTHOOK_FILES:-src}", + "check:oxlint": "oxlint ${LEFTHOOK_FILES:-src}", "check:tsc": "tsc", "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"", "fix:oxfmt": "oxfmt ${LEFTHOOK_FILES:-.}", diff --git a/project-specs.md b/project-specs.md index 32f28df..8a2a0df 100644 --- a/project-specs.md +++ b/project-specs.md @@ -21,11 +21,14 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte - **oxlint**: Rust-based linter. Replaces ESLint. Config in `.oxlintrc.json` with `typescript`, `unicorn`, `oxc`, `import` plugins. Categories enabled as errors: `correctness`, `suspicious`, `restriction`. As warnings: `perf`, - `style`. `nursery` is off. Run with `--type-aware` to enable rules - that require TypeScript type information (powered by `oxlint-tsgolint`). + `style`. `nursery` is off. Type-aware rules are enabled via + `options.typeAware: true` in `.oxlintrc.json` (no CLI flag needed); + the linter then uses `oxlint-tsgolint` for rules that require type + information. - **oxlint-tsgolint**: TypeScript-Go-backed type-aware linter for oxlint. - Activated via `oxlint --type-aware`. Native bindings installed as - `optionalDependencies` per platform (same pattern as oxlint). + Activated by `options.typeAware: true` in `.oxlintrc.json`. Native + bindings installed as `optionalDependencies` per platform (same + pattern as oxlint). - **oxlint rules disabled by design** (in `.oxlintrc.json`): - `eslint/no-undefined` — we use `undefined` as the no-match sentinel. - `eslint/sort-keys` — handler/case order is semantic, not alphabetical. @@ -222,22 +225,25 @@ script does not belong in the standard pipeline. - `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`, `check:tsc`, `check:cspell`, `check:knip`, `check:outdated`. -- `check:oxlint`: `oxlint --type-aware ${LEFTHOOK_FILES:-src}` — lints - `src/` by default; when invoked from the lefthook pre-commit hook - with `LEFTHOOK_FILES` set to the staged-files list, lints only those - files. The `--type-aware` flag enables type-aware rules via - `oxlint-tsgolint` (TypeScript-Go-backed). This is the single source - of truth for the oxlint command and is shared between the manual - `npm run check` and the pre-commit hook. oxlint only understands - JS/TS-family languages, so config files (JSON/YAML/Markdown) are +- `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` by + default; when invoked from the lefthook pre-commit hook with + `LEFTHOOK_FILES` set to the staged-files list, lints only those + files. This is the single source of truth for the oxlint command + and is shared between the manual `npm run check` and the pre-commit + hook. Type-aware rules are activated declaratively via + `options.typeAware: true` in `.oxlintrc.json` (not via a CLI flag), + so the script command stays clean. oxlint only understands JS/TS- + family languages, so config files (JSON/YAML/Markdown) are intentionally outside its scope; they are checked only by oxfmt. - Type-aware rules disabled (with rationale in `.oxlintrc.json`): - `no-unsafe-type-assertion` and `no-unnecessary-type-parameters` - fire on legitimate generic type machinery in `keysMatch` / - `MatchBuilder` that needs type-system restructuring (out of scope - for the tool-adoption commit); `no-floating-promises` is disabled - in test files because `expectTypeOf(...)` is a sync type assertion - library that oxlint-tsgolint misidentifies. + Source-level `oxlint-disable` directives are used to silence + type-aware false positives in the generic type machinery: 4× for + `typescript/no-unsafe-type-assertion` and 1× for + `typescript/no-unnecessary-type-parameters` in `src/pattern.ts` / + `src/match.ts` (legitimate type machinery that needs restructuring, + documented in the source); 1× file-level for + `typescript/no-floating-promises` in `src/index.test.ts` + (`expectTypeOf(...)` is a sync type-assertion library that + oxlint-tsgolint misidentifies). - `check:oxfmt`: `oxfmt --check ${LEFTHOOK_FILES:-.}` — formats the whole project (`.`) by default, including JS/TS, JSON/JSONC, YAML, Markdown, MDX and other supported file types. From lefthook diff --git a/src/index.test.ts b/src/index.test.ts index 460dbeb..5a1131b 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,3 +1,4 @@ +/* oxlint-disable typescript/no-floating-promises -- expectTypeOf() is a sync type-assertion library that the type-aware linter misidentifies as a promise */ import { strict as assert } from "node:assert"; import { test } from "node:test"; diff --git a/src/match.ts b/src/match.ts index 66eac61..ae9672f 100644 --- a/src/match.ts +++ b/src/match.ts @@ -28,6 +28,7 @@ const buildMatch = (value: T, cases: Cases): MatchBuilder => { ): MatchBuilder { const nextCases: Cases = [ ...cases, + // oxlint-disable-next-line typescript/no-unsafe-type-assertion [pattern, handler as (value: unknown) => R | V], ]; return buildMatch(value, nextCases); diff --git a/src/pattern.ts b/src/pattern.ts index 9e61cce..e7b9e00 100644 --- a/src/pattern.ts +++ b/src/pattern.ts @@ -53,12 +53,16 @@ const typeMatcher = ( typeof expected === "object" && expected !== null && "matches" in expected, + // oxlint-disable-next-line typescript/no-unnecessary-type-parameters keysMatch = (shape: S, candidate: object): boolean => { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion for (const key of Object.keys(shape) as (keyof S)[]) { if (!(key in candidate)) { return false; } + // oxlint-disable-next-line typescript/no-unsafe-type-assertion const expected = shape[key], + // oxlint-disable-next-line typescript/no-unsafe-type-assertion actual = candidate[key as keyof object]; if (isNestedMatcher(expected)) { if (!expected.matches(actual)) { @@ -78,6 +82,7 @@ const typeMatcher = ( if (typeof value !== "object" || value === null) { return false; } + // oxlint-disable-next-line typescript/no-unsafe-type-assertion const candidate = value as S; if (!keysMatch(shape, candidate)) { return false;