Cleaner separation of concerns:
- options.typeAware: true in .oxlintrc.json activates type-aware
rules declaratively (equivalent to --type-aware CLI flag, but
the script command stays clean: just 'oxlint ...')
- Remove the 3 type-aware rule disables from .oxlintrc.json
- Add source-level oxlint-disable directives instead:
- 4x typescript/no-unsafe-type-assertion (pattern.ts: keysMatch
Object.keys() cast, candidate[] cast; structuralMatcher value
as S cast; match.ts: handler as ... cast in nextCases)
- 1x typescript/no-unnecessary-type-parameters (pattern.ts:
keysMatch <S extends object>)
- 1x file-level typescript/no-floating-promises in index.test.ts
(expectTypeOf() is a sync type-assertion library that the
type-aware linter misidentifies)
Disabling rules at the source (next to the line that needs the
exemption) documents intent more clearly than a global config
override, and makes the trade-off visible to anyone reading the
code. Re-enabling a rule in the future only requires removing the
inline comment, not editing a central config.
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { P, type Matcher, type Pattern } from "./pattern.ts";
|
|
|
|
type Cases<R> = readonly (readonly [Matcher<unknown>, (value: unknown) => R])[];
|
|
|
|
interface MatchBuilder<T, R> {
|
|
with<U extends T, V>(
|
|
pattern: Matcher<U>,
|
|
handler: (value: U) => V,
|
|
): MatchBuilder<T, R | V>;
|
|
exhaustive(): R;
|
|
otherwise(handler: (value: T) => R): R;
|
|
}
|
|
|
|
const buildMatch = <T, R>(value: T, cases: Cases<R>): MatchBuilder<T, R> => {
|
|
const apply = (): R | undefined => {
|
|
for (const [matcher, handler] of cases) {
|
|
if (matcher.matches(value)) {
|
|
return handler(value);
|
|
}
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const builder = {
|
|
with<U extends T, V>(
|
|
pattern: Matcher<U>,
|
|
handler: (value: U) => V,
|
|
): MatchBuilder<T, R | V> {
|
|
const nextCases: Cases<R | V> = [
|
|
...cases,
|
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
[pattern, handler as (value: unknown) => R | V],
|
|
];
|
|
return buildMatch(value, nextCases);
|
|
},
|
|
exhaustive(): R {
|
|
const result = apply();
|
|
if (result === undefined) {
|
|
throw new Error(
|
|
"tiny-pattern-ts: match.exhaustive() called with no matching case",
|
|
);
|
|
}
|
|
return result;
|
|
},
|
|
otherwise(handler: (value: T) => R): R {
|
|
for (const [matcher, run] of cases) {
|
|
if (matcher.matches(value)) {
|
|
return run(value);
|
|
}
|
|
}
|
|
return handler(value);
|
|
},
|
|
};
|
|
|
|
return builder;
|
|
};
|
|
|
|
export const match = <T>(value: T): MatchBuilder<T, never> =>
|
|
buildMatch<T, never>(value, []);
|
|
|
|
export type { Matcher, Pattern };
|
|
export { P };
|