Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads
const IN_RADIUS = (rel: string) =>
(rel.startsWith('packages/') && rel.endsWith('.ts'))
|| /^examples\/[^/]+\/src\/.+\.ts$/.test(rel);
/**
* Hand-renamed readers the walk must see as code references under the NEW
* name (anti-vacuity, asserted below). Only these files are asked the new
* name: the assertion is containment of exactly this list, so no other file
* can change its verdict, and parsing every other file that mentions the new
* name was about a third of the walk's parsing.
*/
const NEW_READERS = [
'packages/metadata-protocol/src/protocol.ts',
'packages/metadata-protocol/src/sys-metadata-repository.ts',
'packages/spec/src/kernel/metadata-plugin.zod.ts',
];
/** Build, SCM and cache state — not authored sources. */
const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']);

Expand All @@ -58,28 +70,36 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads
&& ts.isIdentifier(node.parent.initializer.expression)
&& node.parent.initializer.expression.text === 'retiredKey';

/** Every CODE reference to `name` in `text`, as `line:col` — identifiers and quoted keys, never comments or prose strings. */
const codeReferences = (fileName: string, text: string, name: string): string[] => {
/**
* Every CODE reference to each of `names` in `text`, as `line:col` per name —
* identifiers and quoted keys, never comments or prose strings. One parse
* serves every name: the walk below can ask two names of the same file, and
* the files holding both are the largest in the radius (megabytes each).
*/
const codeReferencesByName = (fileName: string, text: string, names: readonly string[]): Map<string, string[]> => {
const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const found: string[] = [];
const at = (node: ts.Node) => {
const found = new Map(names.map((name) => [name, [] as string[]]));
const at = (name: string, node: ts.Node) => {
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
found.push(`${line + 1}:${character + 1}`);
found.get(name)!.push(`${line + 1}:${character + 1}`);
};
const visit = (node: ts.Node) => {
if (ts.isIdentifier(node) && node.text === name && !isTombstoneDeclaration(node)) at(node);
else if (ts.isStringLiteral(node) && node.text === name && node.parent) {
if (ts.isIdentifier(node) && found.has(node.text) && !isTombstoneDeclaration(node)) at(node.text, node);
else if (ts.isStringLiteral(node) && found.has(node.text) && node.parent) {
const p = node.parent;
// A quoted key or an element access is a reference; any other string is prose.
if ((ts.isPropertyAssignment(p) && p.name === node)
|| (ts.isElementAccessExpression(p) && p.argumentExpression === node)
|| (ts.isPropertySignature(p) && p.name === node)) at(node);
|| (ts.isPropertySignature(p) && p.name === node)) at(node.text, node);
}
ts.forEachChild(node, visit);
};
visit(sf);
return found;
};
/** Every CODE reference to `name` in `text`, as `line:col`. */
const codeReferences = (fileName: string, text: string, name: string): string[] =>
codeReferencesByName(fileName, text, [name]).get(name)!;

it('the matcher sees a code reference and ignores a mention (anti-vacuity)', () => {
const refs = (src: string) => codeReferences('probe.ts', src, OLD);
Expand Down Expand Up @@ -122,9 +142,11 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') continue;
throw err;
}
if (text.includes(NEW) && codeReferences(rel, text, NEW).length > 0) newReaders.add(rel);
if (!text.includes(OLD)) continue;
for (const where of codeReferences(rel, text, OLD)) {
const names = [OLD, ...(NEW_READERS.includes(rel) ? [NEW] : [])].filter((name) => text.includes(name));
if (names.length === 0) continue;
const refs = codeReferencesByName(rel, text, names);
if ((refs.get(NEW)?.length ?? 0) > 0) newReaders.add(rel);
for (const where of refs.get(OLD) ?? []) {
(rel === REFUSAL_PIN ? refusalPinReferences : offenders).push(`${rel}:${where}`);
}
}
Expand All @@ -140,11 +162,7 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads
// Live positive control: the refusal pin's deliberate `allowOrgOverride: true`
// and `: false` are real code references, and the walk found them.
expect(refusalPinReferences.length).toBeGreaterThanOrEqual(2);
expect([...newReaders]).toEqual(expect.arrayContaining([
'packages/metadata-protocol/src/protocol.ts',
'packages/metadata-protocol/src/sys-metadata-repository.ts',
'packages/spec/src/kernel/metadata-plugin.zod.ts',
]));
expect([...newReaders]).toEqual(expect.arrayContaining(NEW_READERS));
expect(
offenders,
'a code reference to `allowOrgOverride` reads `undefined` against the tombstone and closes every type — read `allowEnvironmentOverlay`',
Expand Down
12 changes: 7 additions & 5 deletions packages/spec/src/ui/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { describe, it, expect } from 'vitest';
// Loaded here, at module top, not inside the compiler-API case below: a
// clocked window measures behaviour, never loading, and `typescript` alone is
// about 300 ms of module load.
import ts from 'typescript';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
AppSchema,
AppBrandingSchema,
Expand Down Expand Up @@ -1587,11 +1593,7 @@ describe('unknown keys are rejected, not stripped', () => {
// compiler API and reads its members — the same symbol-identity measurement
// `check:dual-source-exports` makes, over `src/`, so it runs in `pnpm test`
// without a build.
it('the retired gates are gone from the TYPE, not only from the parse (compiler API)', async () => {
const ts = (await import('typescript')).default;
const { resolve, dirname } = await import('node:path');
const { fileURLToPath } = await import('node:url');

it('the retired gates are gone from the TYPE, not only from the parse (compiler API)', () => {
const entry = resolve(dirname(fileURLToPath(import.meta.url)), 'app.zod.ts');
const program = ts.createProgram([entry], {
module: ts.ModuleKind.ESNext,
Expand Down
27 changes: 12 additions & 15 deletions packages/spec/src/ui/view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ import {
originOf,
} from '../../scripts/lib/export-origins-testkit';

// The three published entries the dual-source pins below compare. Loaded
// here, at module top, and not by an `await import()` inside a case: a clocked
// window measures behaviour, never loading, and these three loads were most
// of this file's test time.
import * as sharedEntry from '../shared/index';
import * as uiEntry from '../ui/index';
import * as apiEntry from '../api/index';

// [#16577] The door that carries the `type: '<layout>'` axis these view
// schemas deliberately leave open — imported so the two-door split is asserted
// from the side that can see both.
Expand Down Expand Up @@ -4050,20 +4058,14 @@ describe('HttpMethodSubsetSchema/HttpRequestSchema backward compat', () => {
// re-exports `./shared`'s const — so the only thing that ever split was the type
// alias, which is exactly the part runtime cannot see. Hence two layers.
describe('HttpRequest is single-source across ./shared and ./ui', () => {
it('both entry points expose the very same schema declaration at runtime', async () => {
const sharedEntry = await import('../shared/index');
const uiEntry = await import('../ui/index');

it('both entry points expose the very same schema declaration at runtime', () => {
// Identity, not shape: `lazySchema` returns one Proxy per declaration site,
// so two declarations could never be `toBe`-equal however alike they look.
// A re-introduced local `HttpRequestSchema` in view.zod.ts fails here.
expect(uiEntry.HttpRequestSchema).toBe(sharedEntry.HttpRequestSchema);
});

it('the shared declaration validates identically on both entries', async () => {
const sharedEntry = await import('../shared/index');
const uiEntry = await import('../ui/index');

it('the shared declaration validates identically on both entries', () => {
for (const [entry, schema] of [
['./shared', sharedEntry.HttpRequestSchema],
['./ui', uiEntry.HttpRequestSchema],
Expand Down Expand Up @@ -4154,10 +4156,7 @@ describe('`HttpMethod` is not exported from ./ui', () => {
expect(originFileOf('./shared', 'HttpMethod')).toBe('src/shared/http.zod.ts');
});

it('keeps the two value ranges distinct: 7 for `HttpMethod`, 5 for `HttpMethodSubsetSchema`', async () => {
const sharedEntry = await import('../shared/index');
const apiEntry = await import('../api/index');

it('keeps the two value ranges distinct: 7 for `HttpMethod`, 5 for `HttpMethodSubsetSchema`', () => {
// `./api` re-exports the const, so this is one object seen twice.
expect(apiEntry.HttpMethod).toBe(sharedEntry.HttpMethod);

Expand All @@ -4173,9 +4172,7 @@ describe('`HttpMethod` is not exported from ./ui', () => {
expect(sharedEntry.HttpMethodSubsetSchema.options).not.toContain('HEAD');
});

it('rejects `HEAD` at the parse layer — the runtime the ./ui type must not out-promise', async () => {
const uiEntry = await import('../ui/index');

it('rejects `HEAD` at the parse layer — the runtime the ./ui type must not out-promise', () => {
expect(() => uiEntry.HttpMethodSubsetSchema.parse('HEAD')).toThrow();
expect(() => uiEntry.HttpMethodSubsetSchema.parse('OPTIONS')).toThrow();
expect(() => uiEntry.HttpRequestSchema.parse({ url: '/api/data', method: 'HEAD' })).toThrow();
Expand Down
Loading