🔧 Resolve oxlint warnings; defer stylistic rules to oxfmt

This commit is contained in:
tmu committed 2026-09-03 21:33:04 +00:00
1 parent b3a4e14f45
commit 99fd85ca7e
5 files changed
+99 -67

No files matched your search

+8 -1
View File
@@ -13,7 +13,14 @@
"eslint/no-undefined": "off", "eslint/no-undefined": "off",
"eslint/sort-keys": "off", "eslint/sort-keys": "off",
"eslint/id-length": "off", "eslint/id-length": "off",
"import/no-named-export": "off" "import/no-named-export": "off",
"eslint/one-var": "off",
"import/group-exports": "off",
"import/exports-last": "off",
"eslint/sort-imports": "off",
"import/consistent-type-specifier-style": "off",
"unicorn/prefer-export-from": "off",
"typescript/method-signature-style": "off"
}, },
"env": { "env": {
"builtin": true, "builtin": true,
+21 -5
View File
@@ -27,11 +27,27 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
- `eslint/sort-keys` — handler/case order is semantic, not alphabetical. - `eslint/sort-keys` — handler/case order is semantic, not alphabetical.
- `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics. - `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics.
- `import/no-named-export` — false positive on library entry re-exports. - `import/no-named-export` — false positive on library entry re-exports.
For `*.test.ts` files additionally: `no-unused-expressions` (for - Stylistic rules superseded by oxfmt (oxfmt is the canonical
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import formatter; these rules either conflict with its output or
per test file in some cases), `import/no-nodejs-modules` (we use duplicate features oxfmt already provides):
`node:test`/`node:assert`/`node:fs` intentionally), `eslint/no-magic-numbers` - `eslint/one-var` — oxfmt uses comma-joined `const`
(literals in tests are fine). declarations; oxlint wanted to split them.
- `import/group-exports` — oxfmt keeps separate `export`
statements as-is.
- `import/exports-last` — statement ordering is up to oxfmt.
- `eslint/sort-imports` — replaced by oxfmt's built-in
`sortImports` (enabled in `.oxfmtrc.json`).
- `import/consistent-type-specifier-style` — inline
`import { type X, Y }` is intentional for grouping.
- `unicorn/prefer-export-from` — conflicts with how the
barrel `src/index.ts` re-exports through `src/match.ts`.
- `typescript/method-signature-style` — method signatures
in `interface` are conventional TS ergonomics.
For `*.test.ts` files additionally: `no-unused-expressions` (for
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import
per test file in some cases), `import/no-nodejs-modules` (we use
`node:test`/`node:assert`/`node:fs` intentionally), `eslint/no-magic-numbers`
(literals in tests are fine).
- **Import sorting**: built into oxfmt (no separate plugin). - **Import sorting**: built into oxfmt (no separate plugin).
- **cspell**: Basic spelling configuration. Words dictionary in `cspell.json` - **cspell**: Basic spelling configuration. Words dictionary in `cspell.json`
covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`, covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`,
+1 -1
View File
@@ -3,7 +3,7 @@ import { test } from "node:test";
import { expectTypeOf } from "expect-type"; import { expectTypeOf } from "expect-type";
import { match, P, type Matcher } from "./index.ts"; import { type Matcher, P, match } from "./index.ts";
test("match returns a builder", () => { test("match returns a builder", () => {
const builder = match("x"); const builder = match("x");
+1 -3
View File
@@ -1,8 +1,6 @@
import { P, type Matcher, type Pattern } from "./pattern.ts"; import { P, type Matcher, type Pattern } from "./pattern.ts";
type Cases<R> = ReadonlyArray< type Cases<R> = readonly (readonly [Matcher<unknown>, (value: unknown) => R])[];
readonly [Matcher<unknown>, (value: unknown) => R]
>;
interface MatchBuilder<T, R> { interface MatchBuilder<T, R> {
with<U extends T, V>( with<U extends T, V>(
+68 -57
View File
@@ -14,69 +14,80 @@ const literalMatcher = <
): Matcher<L> => ({ ): Matcher<L> => ({
matches: (candidate): candidate is L => candidate === value, matches: (candidate): candidate is L => candidate === value,
}); });
const typeMatcher = <T>( const typeMatcher = <T>(
type: type:
| "string" | "string"
| "number" | "number"
| "boolean" | "boolean"
| "bigint" | "bigint"
| "symbol" | "symbol"
| "undefined" | "undefined"
| "object" | "object"
| "function", | "function",
): Matcher<T> => { ): Matcher<T> => {
const matches = (value: unknown): value is T => { const matches = (value: unknown): value is T => {
if (type === "undefined") return value === undefined; if (type === "undefined") {
if (type === "object") return value === undefined;
return ( }
(typeof value === "object" && value !== null) || if (type === "object") {
typeof value === "function" return (
); (typeof value === "object" && value !== null) ||
return typeof value === type; typeof value === "function"
}; );
return { matches }; }
}; return typeof value === type;
};
const whenMatcher = <T>( return { matches };
predicate: (value: unknown) => value is T, },
): Matcher<T> => ({ whenMatcher = <T>(
matches: predicate, predicate: (value: unknown) => value is T,
}); ): Matcher<T> => ({
matches: predicate,
const whenMatcherAny = <T>( }),
predicate: (value: unknown) => boolean, whenMatcherAny = <T>(
): Matcher<T> => ({ predicate: (value: unknown) => boolean,
matches: (value: unknown): value is T => predicate(value), ): Matcher<T> => ({
}); matches: (value: unknown): value is T => predicate(value),
}),
const structuralMatcher = <S extends object, T extends S>( isNestedMatcher = (expected: unknown): expected is Matcher<unknown> =>
shape: S, typeof expected === "object" &&
refine?: (value: S) => value is T, expected !== null &&
): Matcher<T> => ({ "matches" in expected,
matches: (value: unknown): value is T => { keysMatch = <S extends object>(shape: S, candidate: object): boolean => {
if (typeof value !== "object" || value === null) return false; for (const key of Object.keys(shape) as (keyof S)[]) {
const candidate = value as S; if (!(key in candidate)) {
for (const key of Object.keys(shape) as Array<keyof S>) { return false;
if (!(key in candidate)) return false; }
const expected = shape[key]; const expected = shape[key],
const actual = candidate[key]; actual = candidate[key as keyof object];
if ( if (isNestedMatcher(expected)) {
typeof expected === "object" && if (!expected.matches(actual)) {
expected !== null && return false;
"matches" in expected }
) {
const nested = expected as unknown as Matcher<unknown>;
if (!nested.matches(actual)) return false;
} else if (actual !== expected) { } else if (actual !== expected) {
return false; return false;
} }
} }
return refine return true;
? refine(candidate)
: (true as T extends S ? true : never);
}, },
}); structuralMatcher = <S extends object, T extends S>(
shape: S,
refine?: (value: S) => value is T,
): Matcher<T> => ({
matches: (value: unknown): value is T => {
if (typeof value !== "object" || value === null) {
return false;
}
const candidate = value as S;
if (!keysMatch(shape, candidate)) {
return false;
}
if (refine && !refine(candidate)) {
return false;
}
return true;
},
});
export const P = { export const P = {
literal: literalMatcher, literal: literalMatcher,