Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f7a5d1b0 | ||
|
|
99fd85ca7e | ||
|
|
b3a4e14f45 |
No files matched your search
+3
-16
@@ -1,16 +1,8 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
coverage
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
coverage
|
||||||
|
*.log
|
||||||
|
*.tsbuildinfo
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
@@ -20,8 +12,3 @@ dist-ssr
|
|||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
@@ -10,5 +10,4 @@ src/
|
|||||||
.node-version
|
.node-version
|
||||||
cspell.json
|
cspell.json
|
||||||
lefthook.yml
|
lefthook.yml
|
||||||
LICENSE
|
|
||||||
commit-message-template
|
commit-message-template
|
||||||
+8
-1
@@ -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,12 +21,12 @@ Pattern matching for TypeScript/ESM environments (F#-style, not regex).
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Node.js >= 22.6 (>= 24 recommended; `--experimental-strip-types` is unflagged on 24).
|
- Node.js >= 26 (engines field; pinned via `.node-version`).
|
||||||
|
|
||||||
## VSCode integration
|
## VSCode integration
|
||||||
|
|
||||||
- Recommended extensions: see `.vscode/extensions.json` (oxc, cspell).
|
- Recommended extensions: see `.vscode/extensions.json` (oxc, cspell).
|
||||||
- TypeScript 7 is used via `typescript.tsdk` pointing at `node_modules/typescript/lib`.
|
- TypeScript 7 is used via the `typescriptteam.native-preview` extension.
|
||||||
- oxc extension provides oxlint squiggles and oxfmt format-on-save.
|
- oxc extension provides oxlint squiggles and oxfmt format-on-save.
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|||||||
+1
-8
@@ -17,12 +17,5 @@
|
|||||||
"dbaeumer",
|
"dbaeumer",
|
||||||
"msvc"
|
"msvc"
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": ["dist", "node_modules", "coverage", "*.svg", ".gitignore"]
|
||||||
"dist",
|
|
||||||
"node_modules",
|
|
||||||
"public",
|
|
||||||
"coverage",
|
|
||||||
"*.svg",
|
|
||||||
".gitignore"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
+3
-3
@@ -5,11 +5,11 @@ pre-commit:
|
|||||||
commands:
|
commands:
|
||||||
oxlint:
|
oxlint:
|
||||||
glob: "*.{ts,tsx,js,jsx,mjs,cjs}"
|
glob: "*.{ts,tsx,js,jsx,mjs,cjs}"
|
||||||
run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}
|
run: sh -c 'LEFTHOOK_FILES="$*" npm run check:oxlint' sh {staged_files}
|
||||||
oxfmt:
|
oxfmt:
|
||||||
glob: "*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}"
|
glob: "*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}"
|
||||||
run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}
|
run: sh -c 'LEFTHOOK_FILES="$*" npm run check:oxfmt' sh {staged_files}
|
||||||
cspell:
|
cspell:
|
||||||
run: sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}
|
run: sh -c 'LEFTHOOK_FILES="$*" npm run check:cspell' sh {staged_files}
|
||||||
typecheck:
|
typecheck:
|
||||||
run: npm run check:tsc
|
run: npm run check:tsc
|
||||||
+19
-6
@@ -27,6 +27,22 @@ 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.
|
||||||
|
- Stylistic rules superseded by oxfmt (oxfmt is the canonical
|
||||||
|
formatter; these rules either conflict with its output or
|
||||||
|
duplicate features oxfmt already provides):
|
||||||
|
- `eslint/one-var` — oxfmt uses comma-joined `const`
|
||||||
|
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
|
For `*.test.ts` files additionally: `no-unused-expressions` (for
|
||||||
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import
|
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import
|
||||||
per test file in some cases), `import/no-nodejs-modules` (we use
|
per test file in some cases), `import/no-nodejs-modules` (we use
|
||||||
@@ -186,7 +202,7 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
|
|||||||
alphabetized (replaces the former `sort-package-json` tool).
|
alphabetized (replaces the former `sort-package-json` tool).
|
||||||
Built-in import sorting (enabled via `sortImports: true`) replaces
|
Built-in import sorting (enabled via `sortImports: true`) replaces
|
||||||
any external import-sort plugin.
|
any external import-sort plugin.
|
||||||
- `check:tsc`: `tsc --noEmit`.
|
- `check:tsc`: `tsc` (noEmit is set in `tsconfig.json`).
|
||||||
- `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the
|
- `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the
|
||||||
project root by default; from lefthook, only the staged files.
|
project root by default; from lefthook, only the staged files.
|
||||||
- `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here.
|
- `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here.
|
||||||
@@ -403,8 +419,6 @@ Resolves #...
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"js/ts.tsdk.path": "node_modules/typescript/lib",
|
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "oxc.oxc-vscode",
|
"editor.defaultFormatter": "oxc.oxc-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
@@ -458,6 +472,5 @@ Resolves #...
|
|||||||
(build, test, lint, typecheck, format, spell, check:outdated) are
|
(build, test, lint, typecheck, format, spell, check:outdated) are
|
||||||
run via the npm scripts in `package.json` from the integrated
|
run via the npm scripts in `package.json` from the integrated
|
||||||
terminal.
|
terminal.
|
||||||
- VSCode uses the workspace TypeScript version via
|
- VSCode uses TypeScript 7 via the `typescriptteam.native-preview`
|
||||||
`typescript.tsdk` (and the explicit `js/ts.tsdk.path`), with
|
extension, with `oxc.oxc-vscode` for formatting and linting.
|
||||||
`oxc.oxc-vscode` for formatting and linting.
|
|
||||||
+1
-1
@@ -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
@@ -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>(
|
||||||
|
|||||||
+45
-34
@@ -14,7 +14,6 @@ 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"
|
||||||
@@ -25,58 +24,70 @@ const typeMatcher = <T>(
|
|||||||
| "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;
|
||||||
|
}
|
||||||
|
if (type === "object") {
|
||||||
return (
|
return (
|
||||||
(typeof value === "object" && value !== null) ||
|
(typeof value === "object" && value !== null) ||
|
||||||
typeof value === "function"
|
typeof value === "function"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return typeof value === type;
|
return typeof value === type;
|
||||||
};
|
};
|
||||||
return { matches };
|
return { matches };
|
||||||
};
|
},
|
||||||
|
whenMatcher = <T>(
|
||||||
const whenMatcher = <T>(
|
|
||||||
predicate: (value: unknown) => value is T,
|
predicate: (value: unknown) => value is T,
|
||||||
): Matcher<T> => ({
|
): Matcher<T> => ({
|
||||||
matches: predicate,
|
matches: predicate,
|
||||||
});
|
}),
|
||||||
|
whenMatcherAny = <T>(
|
||||||
const whenMatcherAny = <T>(
|
|
||||||
predicate: (value: unknown) => boolean,
|
predicate: (value: unknown) => boolean,
|
||||||
): Matcher<T> => ({
|
): Matcher<T> => ({
|
||||||
matches: (value: unknown): value is T => predicate(value),
|
matches: (value: unknown): value is T => predicate(value),
|
||||||
});
|
}),
|
||||||
|
isNestedMatcher = (expected: unknown): expected is Matcher<unknown> =>
|
||||||
const 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;
|
|
||||||
for (const key of Object.keys(shape) as Array<keyof S>) {
|
|
||||||
if (!(key in candidate)) return false;
|
|
||||||
const expected = shape[key];
|
|
||||||
const actual = candidate[key];
|
|
||||||
if (
|
|
||||||
typeof expected === "object" &&
|
typeof expected === "object" &&
|
||||||
expected !== null &&
|
expected !== null &&
|
||||||
"matches" in expected
|
"matches" in expected,
|
||||||
) {
|
keysMatch = <S extends object>(shape: S, candidate: object): boolean => {
|
||||||
const nested = expected as unknown as Matcher<unknown>;
|
for (const key of Object.keys(shape) as (keyof S)[]) {
|
||||||
if (!nested.matches(actual)) return false;
|
if (!(key in candidate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const expected = shape[key],
|
||||||
|
actual = candidate[key as keyof object];
|
||||||
|
if (isNestedMatcher(expected)) {
|
||||||
|
if (!expected.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,
|
||||||
|
|||||||
+1
-2
@@ -3,8 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"rewriteRelativeImportExtensions": true,
|
"rewriteRelativeImportExtensions": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src"
|
||||||
"outDir": "dist"
|
|
||||||
},
|
},
|
||||||
"exclude": ["src/**/*.test.ts", "src/**/__tests__/**"]
|
"exclude": ["src/**/*.test.ts", "src/**/__tests__/**"]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user