import ts from "typescript"; import { describe, expect, it } from "vitest"; import { getProductionLayerMatches, getSourceImportBoundaryDiagnostics, getSourceImportGraphCycleDiagnostic, } from "./testing/module-import-boundary-test-support"; import { getModuleReferences } from "./testing/module-import-syntax-test-support"; const compilerSourceExtensions = [ ".ts", ".tsx", ".mts", ".cts", ".d.ts", ".d.mts", ".d.cts", ] as const; const executableSourceExtensions = [".ts", ".tsx", ".mts", ".cts"] as const; const directiveSource = [ '/// ', '/// ', '/// ', '/// ', ].join("\n"); describe("capability module TypeScript reference inventory", () => { it.each(compilerSourceExtensions)( "inventories compiler directives in fixture%s", (extension) => { expect(getModuleReferences(`fixture${extension}`, directiveSource)).toEqual([ { kind: "specifier", referenceKind: "path", specifier: "./contract/contribution.ts", }, { kind: "specifier", referenceKind: "types", specifier: "react", }, { kind: "specifier", referenceKind: "lib", specifier: "es2023", }, { kind: "specifier", referenceKind: "amd", specifier: "./contract/module-definition", }, ]); }, ); it.each(executableSourceExtensions)( "inventories supported require calls in fixture%s", (extension) => { const specifier = "./contract/capability"; for (const source of [ `require("${specifier}");`, `module.require("${specifier}");`, `require.resolve("${specifier}");`, ]) { expect(getModuleReferences(`fixture${extension}`, source)).toEqual([ { kind: "specifier", referenceKind: "module", specifier }, ]); } }, ); it.each([ ["module.require(runtimeOwner);", "nonliteral module.require()"], ["require.resolve(runtimeOwner);", "nonliteral require.resolve()"], [ 'loader.require("./contract/capability");', "unrecognized require-like call", ], [ 'module["require"]("./contract/capability");', "unrecognized require-like call", ], [ 'require["resolve"]("./contract/capability");', "unrecognized require-like call", ], ])("fails closed for %s", (source, diagnostic) => { expect(getModuleReferences("fixture.cts", source)).toEqual([ { diagnostic, kind: "nonliteral" }, ]); }); it.each([ ["const load = require;", "unrecognized require-like reference"], ["const load = module.require;", "unrecognized require-like reference"], ["const resolve = require.resolve;", "unrecognized require-like reference"], ])("fails closed when taking %s", (source, diagnostic) => { expect(getModuleReferences("fixture.cts", source)).toEqual([ { diagnostic, kind: "nonliteral" }, ]); }); }); function boundaryDiagnostics( source: string, fileName = "capability.ts", compilerOptions?: ts.CompilerOptions, ) { return getSourceImportBoundaryDiagnostics({ compilerOptions, importerModulePath: `contract/${fileName}`, importerRuntimePath: `modules/contract/${fileName}`, source, }); } describe("capability module TypeScript reference resolution", () => { it.each([ "const view = content;", "const view = ;", "const view = <>content;", "const view = <>;", "/** @jsxImportSource react */\nconst view = ;", ])("rejects implicit JSX runtime syntax from %s", (source) => { expect(boundaryDiagnostics(source, "capability.tsx")).toEqual([ "contract/capability.tsx -> JSX syntax is forbidden in capability production modules", ]); }); it("rejects JSX under the ReactJSXDev compiler option", () => { expect( boundaryDiagnostics( "const view = ;", "capability.tsx", { jsx: ts.JsxEmit.ReactJSXDev }, ), ).toEqual([ "contract/capability.tsx -> JSX syntax is forbidden in capability production modules", ]); }); it("keeps JSX-free TSX classified and test support out of production", () => { expect( boundaryDiagnostics("export const capability = true;", "capability.tsx"), ).toEqual([]); expect(getProductionLayerMatches("contract/capability.tsx")).toEqual([ "contract", ]); for (const testSupportPath of [ "contract/capability.test.tsx", "testing/jsx-fixture.d.ts", "fixtures/jsx-fixture.d.mts", "test-support/jsx-fixture.d.cts", ]) { expect(getProductionLayerMatches(testSupportPath)).toEqual([]); } }); it.each([ ['/// ', "capability.ts"], [ '/// ', "capability.ts", ], ['module.require("../../react/index");', "capability.cts"], ['require.resolve("../../react/index");', "capability.cts"], ])("rejects a forbidden runtime reference from %s", (source, fileName) => { expect(boundaryDiagnostics(source, fileName)).toEqual([ `contract/${fileName} -> react/index.ts`, ]); }); it.each([ ['/// ', "types:react"], ['/// ', "lib:es2023"], ['/// ', "path:./missing.d.ts"], [ '/// ', "amd:./missing", ], ])("reports an explicit directive diagnostic for %s", (source, target) => { expect(boundaryDiagnostics(source)).toEqual([ `contract/capability.ts -> ${target}`, ]); }); it.each([ ["module.require(runtimeOwner);", "nonliteral module.require()"], ["require.resolve(runtimeOwner);", "nonliteral require.resolve()"], [ 'module["require"]("../../react/index");', "unrecognized require-like call", ], ])("reports an explicit require diagnostic for %s", (source, target) => { expect(boundaryDiagnostics(source, "capability.cts")).toEqual([ `contract/capability.cts -> ${target}`, ]); }); it.each([ '/// ', '/// ', 'module.require("./contribution");', 'require.resolve("./contribution");', ])("allows internal contract reference %s", (source) => { expect(boundaryDiagnostics(source)).toEqual([]); }); it("includes compiler reference edges in cycle analysis", () => { expect( getSourceImportGraphCycleDiagnostic( new Map([ [ "contract/capability.ts", '/// ', ], [ "contract/module-definition.ts", '/// ', ], ]), ), ).toBe( "Capability module import cycle: contract/capability.ts -> contract/module-definition.ts -> contract/capability.ts.", ); }); });