diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts index a1021572602..e73e7acbc70 100644 --- a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts @@ -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']); @@ -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 => { 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); @@ -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}`); } } @@ -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`', diff --git a/packages/spec/src/ui/app.test.ts b/packages/spec/src/ui/app.test.ts index 8379d8118c6..2b185dd573a 100644 --- a/packages/spec/src/ui/app.test.ts +++ b/packages/spec/src/ui/app.test.ts @@ -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, @@ -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, diff --git a/packages/spec/src/ui/view.test.ts b/packages/spec/src/ui/view.test.ts index 40cc4997348..6878fd207d2 100644 --- a/packages/spec/src/ui/view.test.ts +++ b/packages/spec/src/ui/view.test.ts @@ -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: ''` axis these view // schemas deliberately leave open — imported so the two-door split is asserted // from the side that can see both. @@ -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], @@ -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); @@ -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();