♻️ Name the matcher factories getMatcher / getMatcherW
Drop the strict/widened aliases from src/index.ts so the factories are exported under their own names, and align the type tests, the autocomplete probe (which now imports the public surface from ./index.ts) and library.md with them. Also correct the widened-overload comment: the excess-property check does not apply to a generic P, so MatcherWidening's keyof guard — not the closed constraint — is what rejects keys outside T/_, as library.md already documented.
This commit is contained in:
1 parent
05fad0fcd6
commit
800c11139d
5 files changed
+69
-84
No files matched your search
+42
-42
@@ -10,15 +10,15 @@ import {
|
||||
LspSession,
|
||||
} from "#test-utils/lsp-completion.ts";
|
||||
|
||||
import { strict, widened } from "./primitive.ts";
|
||||
import { getMatcher, getMatcherW } from "./primitive.ts";
|
||||
|
||||
// ============================================================================
|
||||
// API: strict — ✔️ Exhaustive / ✔️ ReturnsStrict
|
||||
// API: getMatcher — ✔️ Exhaustive / ✔️ ReturnsStrict
|
||||
// ============================================================================
|
||||
|
||||
test("strict: exhaustive pattern infers one common return type", () => {
|
||||
test("getMatcher: exhaustive pattern infers one common return type", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b">();
|
||||
const factory = getMatcher<"a" | "b">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -41,9 +41,9 @@ test("strict: exhaustive pattern infers one common return type", () => {
|
||||
assert.equal(matcher("b"), 2);
|
||||
});
|
||||
|
||||
test("strict: dispatches on numeric literal keys", () => {
|
||||
test("getMatcher: dispatches on numeric literal keys", () => {
|
||||
// Arrange
|
||||
const factory = strict<1 | 2>();
|
||||
const factory = getMatcher<1 | 2>();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -63,9 +63,9 @@ test("strict: dispatches on numeric literal keys", () => {
|
||||
assert.equal(matcher(2), 20);
|
||||
});
|
||||
|
||||
test("strict: dispatches on mixed string and numeric keys", () => {
|
||||
test("getMatcher: dispatches on mixed string and numeric keys", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b" | 1 | 2>();
|
||||
const factory = getMatcher<"a" | "b" | 1 | 2>();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -96,12 +96,12 @@ test("strict: dispatches on mixed string and numeric keys", () => {
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// API: strict — ❌ Exhaustive (fallback) / ✔️ ReturnsStrict
|
||||
// API: getMatcher — ❌ Exhaustive (fallback) / ✔️ ReturnsStrict
|
||||
// ============================================================================
|
||||
|
||||
test("strict: a `_` fallback makes the universe keys optional", () => {
|
||||
test("getMatcher: a `_` fallback makes the universe keys optional", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b" | "c">();
|
||||
const factory = getMatcher<"a" | "b" | "c">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -125,9 +125,9 @@ test("strict: a `_` fallback makes the universe keys optional", () => {
|
||||
assert.equal(matcher("c"), 2);
|
||||
});
|
||||
|
||||
test("strict: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
test("getMatcher: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b">();
|
||||
const factory = getMatcher<"a" | "b">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -147,9 +147,9 @@ test("strict: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
assert.equal(matcher("b"), "B");
|
||||
});
|
||||
|
||||
test("strict: a `_` fallback routes mixed string and numeric gaps", () => {
|
||||
test("getMatcher: a `_` fallback routes mixed string and numeric gaps", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b" | 1 | 2>();
|
||||
const factory = getMatcher<"a" | "b" | 1 | 2>();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -176,12 +176,12 @@ test("strict: a `_` fallback routes mixed string and numeric gaps", () => {
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// API: widened — ✔️ Exhaustive / ❌ ReturnsStrict
|
||||
// API: getMatcherW — ✔️ Exhaustive / ❌ ReturnsStrict
|
||||
// ============================================================================
|
||||
|
||||
test("widened: exhaustive pattern widens to the union of handler returns", () => {
|
||||
test("getMatcherW: exhaustive pattern widens to the union of handler returns", () => {
|
||||
// Arrange
|
||||
const factory = widened<"x" | "y">();
|
||||
const factory = getMatcherW<"x" | "y">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -204,9 +204,9 @@ test("widened: exhaustive pattern widens to the union of handler returns", () =>
|
||||
assert.equal(matcher("y"), "two");
|
||||
});
|
||||
|
||||
test("widened: dispatches on mixed string and numeric keys", () => {
|
||||
test("getMatcherW: dispatches on mixed string and numeric keys", () => {
|
||||
// Arrange
|
||||
const factory = widened<"a" | "b" | 1 | 2>();
|
||||
const factory = getMatcherW<"a" | "b" | 1 | 2>();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -239,12 +239,12 @@ test("widened: dispatches on mixed string and numeric keys", () => {
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// API: widened — ❌ Exhaustive (fallback) / ❌ ReturnsStrict
|
||||
// API: getMatcherW — ❌ Exhaustive (fallback) / ❌ ReturnsStrict
|
||||
// ============================================================================
|
||||
|
||||
test("widened: a `_` fallback widens gaps into the union", () => {
|
||||
test("getMatcherW: a `_` fallback widens gaps into the union", () => {
|
||||
// Arrange
|
||||
const factory = widened<"x" | "y" | "z">();
|
||||
const factory = getMatcherW<"x" | "y" | "z">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -270,9 +270,9 @@ test("widened: a `_` fallback widens gaps into the union", () => {
|
||||
assert.equal(matcher("z"), "fallback");
|
||||
});
|
||||
|
||||
test("widened: a `_` fallback widens mixed string and numeric returns", () => {
|
||||
test("getMatcherW: a `_` fallback widens mixed string and numeric returns", () => {
|
||||
// Arrange
|
||||
const factory = widened<"a" | "b" | 1 | 2>();
|
||||
const factory = getMatcherW<"a" | "b" | 1 | 2>();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -301,9 +301,9 @@ test("widened: a `_` fallback widens mixed string and numeric returns", () => {
|
||||
assert.equal(matcher(2), "fallback");
|
||||
});
|
||||
|
||||
test("widened: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
test("getMatcherW: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
// Arrange
|
||||
const factory = widened<"x" | "y">();
|
||||
const factory = getMatcherW<"x" | "y">();
|
||||
|
||||
// Act
|
||||
const matcher = factory({
|
||||
@@ -327,9 +327,9 @@ test("widened: a `_` fallback also accepts an exhaustive pattern", () => {
|
||||
// Factory contracts — calls that must not compile
|
||||
// ============================================================================
|
||||
|
||||
test("strict factory rejects patterns outside its contract", () => {
|
||||
test("getMatcher factory rejects patterns outside its contract", () => {
|
||||
// Arrange
|
||||
const factory = strict<"a" | "b">();
|
||||
const factory = getMatcher<"a" | "b">();
|
||||
|
||||
// Act / Assert — the calls below must not compile.
|
||||
// `Parameters<typeof factory>[0]` resolves only the *last* overload, so it
|
||||
@@ -345,9 +345,9 @@ test("strict factory rejects patterns outside its contract", () => {
|
||||
factory({ a: () => 1 });
|
||||
});
|
||||
|
||||
test("widened factory rejects patterns outside its contract", () => {
|
||||
test("getMatcherW factory rejects patterns outside its contract", () => {
|
||||
// Arrange
|
||||
const factory = widened<"x" | "y">();
|
||||
const factory = getMatcherW<"x" | "y">();
|
||||
|
||||
// Act / Assert — the calls below must not compile
|
||||
// @ts-expect-error `z` is not part of the universe `"x" | "y"`
|
||||
@@ -376,14 +376,14 @@ const UNIVERSE = `"a" | "b" | "c"`;
|
||||
// documents, project membership), so they pass in any order.
|
||||
const labelsFor = (
|
||||
name: string,
|
||||
factory: "strict" | "widened",
|
||||
factory: "getMatcher" | "getMatcherW",
|
||||
body: string,
|
||||
): Promise<readonly string[]> => {
|
||||
const session = new LspSession(REPO_ROOT);
|
||||
const target: CompletionTarget = {
|
||||
file: `src/__autocomplete_${name}.ts`,
|
||||
source: [
|
||||
`import { ${factory} } from "./primitive.ts";`,
|
||||
`import { ${factory} } from "./index.ts";`,
|
||||
`const m = ${factory}<${UNIVERSE}>()({`,
|
||||
body,
|
||||
"});",
|
||||
@@ -398,10 +398,10 @@ const labelsFor = (
|
||||
|
||||
test("autocomplete: an exhaustive pattern requires the universe, `_` optional", () => {
|
||||
// Arrange
|
||||
const name = "strict_fresh";
|
||||
const name = "getMatcher_fresh";
|
||||
|
||||
// Act
|
||||
const labels = labelsFor(name, "strict", " /*COMPLETE*/");
|
||||
const labels = labelsFor(name, "getMatcher", " /*COMPLETE*/");
|
||||
|
||||
// Assert
|
||||
return labels.then((result) => {
|
||||
@@ -411,12 +411,12 @@ test("autocomplete: an exhaustive pattern requires the universe, `_` optional",
|
||||
|
||||
test("autocomplete: handled keys drop out of the popup", () => {
|
||||
// Arrange
|
||||
const name = "strict_after_key";
|
||||
const name = "getMatcher_after_key";
|
||||
|
||||
// Act
|
||||
const labels = labelsFor(
|
||||
name,
|
||||
"strict",
|
||||
"getMatcher",
|
||||
" a: () => 1,\n /*COMPLETE*/",
|
||||
);
|
||||
|
||||
@@ -428,12 +428,12 @@ test("autocomplete: handled keys drop out of the popup", () => {
|
||||
|
||||
test("autocomplete: `_` makes the remaining keys optional", () => {
|
||||
// Arrange
|
||||
const name = "strict_after_fallback";
|
||||
const name = "getMatcher_after_fallback";
|
||||
|
||||
// Act
|
||||
const labels = labelsFor(
|
||||
name,
|
||||
"strict",
|
||||
"getMatcher",
|
||||
" _: () => 0,\n /*COMPLETE*/",
|
||||
);
|
||||
|
||||
@@ -443,12 +443,12 @@ test("autocomplete: `_` makes the remaining keys optional", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("autocomplete: `widened` offers the same popup as `strict`", () => {
|
||||
test("autocomplete: `getMatcherW` offers the same popup as `getMatcher`", () => {
|
||||
// Arrange
|
||||
const name = "widened_fresh";
|
||||
const name = "getMatcherW_fresh";
|
||||
|
||||
// Act
|
||||
const labels = labelsFor(name, "widened", " /*COMPLETE*/");
|
||||
const labels = labelsFor(name, "getMatcherW", " /*COMPLETE*/");
|
||||
|
||||
// Assert
|
||||
return labels.then((result) => {
|
||||
|
||||
Reference in new issue
Block a user