Refactor projections test to make sure we convert (#1509)
This commit is contained in:
Родитель
5ee584b3cb
Коммит
3c208033fd
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/compiler",
|
||||
"comment": "Fix issue with referencing spread properties or enum member depending on the order of declaration",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/compiler"
|
||||
}
|
|
@ -2472,100 +2472,108 @@ export function createChecker(program: Program): Checker {
|
|||
}
|
||||
|
||||
function bindAllMembers(node: Node) {
|
||||
const bound = new Set<Sym>();
|
||||
if (node.symbol) {
|
||||
bindMembers(node, node.symbol);
|
||||
}
|
||||
visitChildren(node, (child) => {
|
||||
bindAllMembers(child);
|
||||
});
|
||||
}
|
||||
|
||||
function bindMembers(node: Node, containerSym: Sym) {
|
||||
let containerMembers: Mutable<SymbolTable>;
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ModelStatement:
|
||||
if (node.extends && node.extends.kind === SyntaxKind.TypeReference) {
|
||||
resolveAndCopyMembers(node.extends);
|
||||
}
|
||||
if (node.is && node.is.kind === SyntaxKind.TypeReference) {
|
||||
resolveAndCopyMembers(node.is);
|
||||
}
|
||||
for (const prop of node.properties) {
|
||||
if (prop.kind === SyntaxKind.ModelSpreadProperty) {
|
||||
resolveAndCopyMembers(prop.target);
|
||||
} else {
|
||||
const name = prop.id.kind === SyntaxKind.Identifier ? prop.id.sv : prop.id.value;
|
||||
bindMember(name, prop, SymbolFlags.ModelProperty);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.EnumStatement:
|
||||
for (const member of node.members.values()) {
|
||||
if (member.kind === SyntaxKind.EnumSpreadMember) {
|
||||
resolveAndCopyMembers(member.target);
|
||||
} else {
|
||||
const name = member.id.kind === SyntaxKind.Identifier ? member.id.sv : member.id.value;
|
||||
bindMember(name, member, SymbolFlags.EnumMember);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.InterfaceStatement:
|
||||
for (const member of node.operations.values()) {
|
||||
bindMember(member.id.sv, member, SymbolFlags.InterfaceMember | SymbolFlags.Operation);
|
||||
}
|
||||
if (node.extends) {
|
||||
for (const ext of node.extends) {
|
||||
resolveAndCopyMembers(ext);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.UnionStatement:
|
||||
for (const variant of node.options.values()) {
|
||||
const name = variant.id.kind === SyntaxKind.Identifier ? variant.id.sv : variant.id.value;
|
||||
bindMember(name, variant, SymbolFlags.UnionVariant);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
function resolveAndCopyMembers(node: TypeReferenceNode) {
|
||||
let ref = resolveTypeReferenceSym(node, undefined);
|
||||
if (ref && ref.flags & SymbolFlags.Alias) {
|
||||
ref = resolveAliasedSymbol(ref);
|
||||
function bindMembers(node: Node, containerSym: Sym) {
|
||||
if (bound.has(containerSym)) {
|
||||
return;
|
||||
}
|
||||
if (ref && ref.members) {
|
||||
copyMembers(ref.members);
|
||||
}
|
||||
}
|
||||
bound.add(containerSym);
|
||||
let containerMembers: Mutable<SymbolTable>;
|
||||
|
||||
function resolveAliasedSymbol(ref: Sym): Sym | undefined {
|
||||
const node = ref.declarations[0] as AliasStatementNode;
|
||||
switch (node.value.kind) {
|
||||
case SyntaxKind.MemberExpression:
|
||||
case SyntaxKind.TypeReference:
|
||||
case SyntaxKind.Identifier:
|
||||
const resolvedSym = resolveTypeReferenceSym(node.value, undefined);
|
||||
if (resolvedSym && resolvedSym.flags & SymbolFlags.Alias) {
|
||||
return resolveAliasedSymbol(resolvedSym);
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ModelStatement:
|
||||
if (node.extends && node.extends.kind === SyntaxKind.TypeReference) {
|
||||
resolveAndCopyMembers(node.extends);
|
||||
}
|
||||
return resolvedSym;
|
||||
default:
|
||||
return undefined;
|
||||
if (node.is && node.is.kind === SyntaxKind.TypeReference) {
|
||||
resolveAndCopyMembers(node.is);
|
||||
}
|
||||
for (const prop of node.properties) {
|
||||
if (prop.kind === SyntaxKind.ModelSpreadProperty) {
|
||||
resolveAndCopyMembers(prop.target);
|
||||
} else {
|
||||
const name = prop.id.kind === SyntaxKind.Identifier ? prop.id.sv : prop.id.value;
|
||||
bindMember(name, prop, SymbolFlags.ModelProperty);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.EnumStatement:
|
||||
for (const member of node.members.values()) {
|
||||
if (member.kind === SyntaxKind.EnumSpreadMember) {
|
||||
resolveAndCopyMembers(member.target);
|
||||
} else {
|
||||
const name =
|
||||
member.id.kind === SyntaxKind.Identifier ? member.id.sv : member.id.value;
|
||||
bindMember(name, member, SymbolFlags.EnumMember);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.InterfaceStatement:
|
||||
for (const member of node.operations.values()) {
|
||||
bindMember(member.id.sv, member, SymbolFlags.InterfaceMember | SymbolFlags.Operation);
|
||||
}
|
||||
if (node.extends) {
|
||||
for (const ext of node.extends) {
|
||||
resolveAndCopyMembers(ext);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.UnionStatement:
|
||||
for (const variant of node.options.values()) {
|
||||
const name =
|
||||
variant.id.kind === SyntaxKind.Identifier ? variant.id.sv : variant.id.value;
|
||||
bindMember(name, variant, SymbolFlags.UnionVariant);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function copyMembers(table: SymbolTable) {
|
||||
const members = augmentedSymbolTables.get(table) ?? table;
|
||||
for (const member of members.values()) {
|
||||
bindMember(member.name, member.declarations[0], member.flags);
|
||||
function resolveAndCopyMembers(node: TypeReferenceNode) {
|
||||
let ref = resolveTypeReferenceSym(node, undefined);
|
||||
if (ref && ref.flags & SymbolFlags.Alias) {
|
||||
ref = resolveAliasedSymbol(ref);
|
||||
}
|
||||
if (ref && ref.members) {
|
||||
bindMembers(ref.declarations[0], ref);
|
||||
copyMembers(ref.members);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindMember(name: string, node: Node, kind: SymbolFlags) {
|
||||
const sym = createSymbol(node, name, kind, containerSym);
|
||||
compilerAssert(containerSym.members, "containerSym.members is undefined");
|
||||
containerMembers ??= getOrCreateAugmentedSymbolTable(containerSym.members);
|
||||
containerMembers.set(name, sym);
|
||||
function resolveAliasedSymbol(ref: Sym): Sym | undefined {
|
||||
const node = ref.declarations[0] as AliasStatementNode;
|
||||
switch (node.value.kind) {
|
||||
case SyntaxKind.MemberExpression:
|
||||
case SyntaxKind.TypeReference:
|
||||
case SyntaxKind.Identifier:
|
||||
const resolvedSym = resolveTypeReferenceSym(node.value, undefined);
|
||||
if (resolvedSym && resolvedSym.flags & SymbolFlags.Alias) {
|
||||
return resolveAliasedSymbol(resolvedSym);
|
||||
}
|
||||
return resolvedSym;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function copyMembers(table: SymbolTable) {
|
||||
const members = augmentedSymbolTables.get(table) ?? table;
|
||||
for (const member of members.values()) {
|
||||
bindMember(member.name, member.declarations[0], member.flags);
|
||||
}
|
||||
}
|
||||
|
||||
function bindMember(name: string, node: Node, kind: SymbolFlags) {
|
||||
const sym = createSymbol(node, name, kind, containerSym);
|
||||
compilerAssert(containerSym.members, "containerSym.members is undefined");
|
||||
containerMembers ??= getOrCreateAugmentedSymbolTable(containerSym.members);
|
||||
containerMembers.set(name, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -389,13 +389,14 @@ export function createProjector(
|
|||
returnType,
|
||||
});
|
||||
|
||||
if (op.interface) {
|
||||
projectedOp.interface = projectedInterfaceScope();
|
||||
} else if (op.namespace) {
|
||||
if (op.namespace) {
|
||||
projectedOp.namespace = projectedNamespaceScope();
|
||||
}
|
||||
|
||||
finishTypeForProgram(projectedProgram, projectedOp);
|
||||
if (op.interface) {
|
||||
projectedOp.interface = projectType(op.interface) as Interface;
|
||||
}
|
||||
return applyProjection(op, projectedOp);
|
||||
}
|
||||
|
||||
|
@ -453,9 +454,8 @@ export function createProjector(
|
|||
decorators: projectedDecs,
|
||||
});
|
||||
|
||||
const parentUnion = projectType(variant.union) as Union;
|
||||
projectedVariant.union = parentUnion;
|
||||
finishTypeForProgram(projectedProgram, projectedVariant);
|
||||
projectedVariant.union = projectType(variant.union) as Union;
|
||||
return projectedVariant;
|
||||
}
|
||||
|
||||
|
@ -498,9 +498,8 @@ export function createProjector(
|
|||
const projectedMember = shallowClone(e, {
|
||||
decorators,
|
||||
});
|
||||
const parentEnum = projectType(e.enum) as Enum;
|
||||
projectedMember.enum = parentEnum;
|
||||
finishTypeForProgram(projectedProgram, projectedMember);
|
||||
projectedMember.enum = projectType(e.enum) as Enum;
|
||||
return projectedMember;
|
||||
}
|
||||
|
||||
|
@ -563,25 +562,6 @@ export function createProjector(
|
|||
return projectType(ns) as Namespace;
|
||||
}
|
||||
|
||||
function interfaceScope(): Interface | undefined {
|
||||
for (let i = scope.length - 1; i >= 0; i--) {
|
||||
if ("interface" in scope[i]) {
|
||||
return (scope[i] as any).interface;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function projectedInterfaceScope(): Interface | undefined {
|
||||
const iface = interfaceScope();
|
||||
if (!iface) return iface;
|
||||
if (!projectedTypes.has(iface)) {
|
||||
throw new Error(`Interface "${iface.name}" should have been projected already`);
|
||||
}
|
||||
return projectType(iface) as Interface;
|
||||
}
|
||||
|
||||
function applyProjection(baseType: Type, projectedType: Type): Type {
|
||||
const inScopeProjections = getInScopeProjections();
|
||||
for (const projectionApplication of inScopeProjections) {
|
||||
|
@ -607,9 +587,6 @@ export function createProjector(
|
|||
if ("namespace" in type && type.namespace !== undefined) {
|
||||
scopeProps.namespace = projectedNamespaceScope();
|
||||
}
|
||||
if ("interface" in type && type.interface !== undefined) {
|
||||
scopeProps.interface = projectedInterfaceScope();
|
||||
}
|
||||
|
||||
const clone = checker.createType({
|
||||
...type,
|
||||
|
|
|
@ -44,7 +44,7 @@ describe("compiler: references", () => {
|
|||
ref: "MyModel.x",
|
||||
}));
|
||||
|
||||
describe("spread property", () =>
|
||||
describe("spread property from model defined before", () =>
|
||||
itCanReference({
|
||||
code: `
|
||||
model Spreadable {
|
||||
|
@ -59,6 +59,21 @@ describe("compiler: references", () => {
|
|||
resolveTarget: (target: Model) => target.properties.get("y"),
|
||||
}));
|
||||
|
||||
describe("spread property from model defined after", () =>
|
||||
itCanReference({
|
||||
code: `
|
||||
@test("target") model MyModel {
|
||||
x: string;
|
||||
... Spreadable;
|
||||
}
|
||||
|
||||
model Spreadable {
|
||||
y: string;
|
||||
}`,
|
||||
ref: "MyModel.y",
|
||||
resolveTarget: (target: Model) => target.properties.get("y"),
|
||||
}));
|
||||
|
||||
describe("spread property via alias", () =>
|
||||
itCanReference({
|
||||
code: `
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
EnumMember,
|
||||
Interface,
|
||||
Model,
|
||||
ModelProperty,
|
||||
Namespace,
|
||||
NumericLiteral,
|
||||
Operation,
|
||||
|
@ -20,7 +19,7 @@ import {
|
|||
import { getDoc } from "../../lib/decorators.js";
|
||||
import { createTestHost, TestHost } from "../../testing/index.js";
|
||||
|
||||
describe("compiler: projections", () => {
|
||||
describe("compiler: projections: logic", () => {
|
||||
let testHost: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -213,70 +212,6 @@ describe("compiler: projections", () => {
|
|||
});
|
||||
|
||||
describe("models", () => {
|
||||
it("link projected model to projected properties", async () => {
|
||||
const code = `
|
||||
@test model Foo {
|
||||
name: string;
|
||||
}
|
||||
#suppress "projections-are-experimental"
|
||||
projection model#test {to {}}`;
|
||||
const result = (await testProjection(code)) as Model;
|
||||
ok(result.projectionBase);
|
||||
strictEqual(result.properties.get("name")?.model, result);
|
||||
});
|
||||
|
||||
it("link projected property with sourceProperty", async () => {
|
||||
const code = `
|
||||
@test model Foo {
|
||||
...Bar
|
||||
}
|
||||
|
||||
model Bar {
|
||||
name: string;
|
||||
}
|
||||
|
||||
#suppress "projections-are-experimental"
|
||||
projection Foo#test {to {}}`;
|
||||
const Foo = (await testProjection(code)) as Model;
|
||||
ok(Foo.projectionBase);
|
||||
const sourceProperty = Foo.properties.get("name")?.sourceProperty;
|
||||
ok(sourceProperty);
|
||||
strictEqual(sourceProperty, Foo.namespace!.models.get("Bar")?.properties.get("name"));
|
||||
strictEqual(sourceProperty.model, Foo.namespace!.models.get("Bar"));
|
||||
});
|
||||
|
||||
it("project all properties first", async () => {
|
||||
const keySym = Symbol("key");
|
||||
testHost.addJsFile("lib.js", {
|
||||
$tagProp({ program }: DecoratorContext, t: ModelProperty) {
|
||||
program.stateSet(keySym).add(t);
|
||||
},
|
||||
$tagModel({ program }: DecoratorContext, t: Model) {
|
||||
for (const prop of t.properties.values()) {
|
||||
ok(
|
||||
program.stateSet(keySym).has(prop),
|
||||
`Prop ${prop.name} should have run @key decorator by this time.`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const code = `
|
||||
import "./lib.js";
|
||||
|
||||
@test @tagModel model Foo {
|
||||
...Bar;
|
||||
}
|
||||
|
||||
model Bar {
|
||||
@tagProp name: string;
|
||||
}
|
||||
|
||||
#suppress "projections-are-experimental"
|
||||
projection Foo#test {to {}}`;
|
||||
await testProjection(code);
|
||||
});
|
||||
|
||||
it("works for versioning", async () => {
|
||||
const addedOnKey = Symbol("addedOn");
|
||||
const removedOnKey = Symbol("removedOn");
|
||||
|
@ -362,46 +297,6 @@ describe("compiler: projections", () => {
|
|||
strictEqual(resultNested2.properties.size, 2);
|
||||
});
|
||||
|
||||
it("runs decorator on property before model", async () => {
|
||||
const collection: Type[] = [];
|
||||
testHost.addJsFile("./ref.js", {
|
||||
$ref: (_: DecoratorContext, target: Type) => collection.push(target),
|
||||
});
|
||||
|
||||
const code = `
|
||||
import "./ref.js";
|
||||
|
||||
@test model Bar {
|
||||
b: Foo.b;
|
||||
}
|
||||
|
||||
@ref
|
||||
@test model Foo {
|
||||
@ref
|
||||
b: string;
|
||||
}
|
||||
#suppress "projections-are-experimental"
|
||||
projection model#test {to {}}`;
|
||||
await testProjection(code);
|
||||
|
||||
strictEqual(collection.length, 4);
|
||||
strictEqual(collection[2].kind, "ModelProperty");
|
||||
strictEqual(collection[3].kind, "Model");
|
||||
});
|
||||
|
||||
it("project property with type referencing sibling", async () => {
|
||||
const code = `
|
||||
@test model Foo {
|
||||
a: Foo.b;
|
||||
b: string;
|
||||
}
|
||||
#suppress "projections-are-experimental"
|
||||
projection model#test {to {}}`;
|
||||
const result = (await testProjection(code)) as Model;
|
||||
ok(result.projectionBase);
|
||||
strictEqual(result.properties.get("a")?.type, result.properties.get("b"));
|
||||
});
|
||||
|
||||
it("can recursively apply projections to nested models", async () => {
|
||||
const code = `
|
||||
@test model Foo {
|
||||
|
@ -617,18 +512,6 @@ describe("compiler: projections", () => {
|
|||
strictEqual((variant.type as Model).name, typeName);
|
||||
}
|
||||
|
||||
it("link projected model to projected properties", async () => {
|
||||
const code = `
|
||||
@test union Foo {
|
||||
one: {};
|
||||
}
|
||||
#suppress "projections-are-experimental"
|
||||
projection model#test {to {}}`;
|
||||
const result = (await testProjection(code)) as Union;
|
||||
ok(result.projectionBase);
|
||||
strictEqual(result.variants.get("one")?.union, result);
|
||||
});
|
||||
|
||||
it("can rename itself", async () => {
|
||||
const code = `
|
||||
${unionCode}
|
||||
|
@ -645,6 +528,7 @@ describe("compiler: projections", () => {
|
|||
strictEqual(result.name, "Bar");
|
||||
strictEqual(result.namespace!.unions.get("Bar"), result);
|
||||
});
|
||||
|
||||
it("can rename variants", async () => {
|
||||
const code = defaultCode(`
|
||||
self::variants::forEach((v) => {
|
||||
|
@ -755,18 +639,6 @@ describe("compiler: projections", () => {
|
|||
${projectionCode(body)}
|
||||
`;
|
||||
|
||||
it("link projected interfaces to its projected operations", async () => {
|
||||
const code = `
|
||||
@test interface Foo {
|
||||
op test(): string;
|
||||
}
|
||||
#suppress "projections-are-experimental"
|
||||
projection interface#test {to {}}`;
|
||||
const result = (await testProjection(code)) as Interface;
|
||||
ok(result.projectionBase);
|
||||
strictEqual(result.operations.get("test")?.interface, result);
|
||||
});
|
||||
|
||||
it("can rename itself", async () => {
|
||||
const code = `
|
||||
${interfaceCode}
|
||||
|
@ -821,14 +693,6 @@ describe("compiler: projections", () => {
|
|||
${projectionCode(body)}
|
||||
`;
|
||||
|
||||
it("link projected enum to projected members", async () => {
|
||||
const code = defaultCode("");
|
||||
const result = (await testProjection(code)) as Enum;
|
||||
ok(result.projectionBase);
|
||||
strictEqual(result.members.get("one")?.enum, result);
|
||||
strictEqual(result.members.get("two")?.enum, result);
|
||||
});
|
||||
|
||||
it("can rename itself", async () => {
|
||||
const code = `
|
||||
${enumCode}
|
|
@ -0,0 +1,293 @@
|
|||
import { deepStrictEqual, ok, strictEqual } from "assert";
|
||||
import { DecoratorContext, getTypeName, Namespace, Type } from "../../core/index.js";
|
||||
import { createProjector } from "../../core/projector.js";
|
||||
import { createTestHost, createTestRunner } from "../../testing/test-host.js";
|
||||
import { BasicTestRunner, TestHost } from "../../testing/types.js";
|
||||
|
||||
/**
|
||||
* This test suite checks that projected types are reconstructed just fine.
|
||||
*/
|
||||
describe("compiler: projector: Identity", () => {
|
||||
let host: TestHost;
|
||||
let runner: BasicTestRunner;
|
||||
|
||||
beforeEach(async () => {
|
||||
host = await createTestHost();
|
||||
runner = await createTestRunner(host);
|
||||
});
|
||||
|
||||
type IdentifyProjectResult<T extends Type> = {
|
||||
type: T;
|
||||
globalNamespace: Namespace;
|
||||
|
||||
originalType: T;
|
||||
|
||||
/**
|
||||
* Types collected with the `@collect` decorator in the order they were run.
|
||||
*/
|
||||
trackedTypes: Type[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Project the given code without any projection implementation.
|
||||
*/
|
||||
async function projectWithNoChange<K extends Type["kind"], T extends Type & { kind: K }>(
|
||||
code: string,
|
||||
kind?: K
|
||||
): Promise<IdentifyProjectResult<T>> {
|
||||
const projections = [{ arguments: [], projectionName: "noop" }];
|
||||
|
||||
const trackedTypes: Type[] = [];
|
||||
host.addJsFile("./track.js", {
|
||||
$track: (_: DecoratorContext, target: Type) => trackedTypes.push(target),
|
||||
});
|
||||
|
||||
const { target } = await runner.compile(`
|
||||
import "./track.js";
|
||||
${code}`);
|
||||
|
||||
while (trackedTypes.length > 0) {
|
||||
trackedTypes.pop();
|
||||
}
|
||||
|
||||
ok(target, `Expected to have found a test type tagged with target. Add @test("target")`);
|
||||
if (kind) {
|
||||
strictEqual(target.kind, kind);
|
||||
}
|
||||
const projector = createProjector(runner.program, projections).projector;
|
||||
const projectedType = projector.projectedTypes.get(target);
|
||||
ok(projectedType, `Type ${getTypeName(target)} should have been projected`);
|
||||
if (kind) {
|
||||
strictEqual(projectedType.kind, kind);
|
||||
}
|
||||
strictEqual(projectedType.projectionBase, target);
|
||||
strictEqual(projectedType.projectionSource, target);
|
||||
|
||||
return {
|
||||
type: projectedType as T,
|
||||
globalNamespace: projector.projectedGlobalNamespace!,
|
||||
originalType: target as T,
|
||||
trackedTypes,
|
||||
};
|
||||
}
|
||||
|
||||
type TestDecoratorOrderOptions = {
|
||||
name: string;
|
||||
code: string;
|
||||
ref?: string;
|
||||
expectedTypes: [Type["kind"], string][];
|
||||
};
|
||||
|
||||
function describeDecoratorOrder({ name, code, ref, expectedTypes }: TestDecoratorOrderOptions) {
|
||||
if (ref === undefined) {
|
||||
it(name, async () => {
|
||||
const emptyCode = `@test("target") model Empty {}`;
|
||||
const result = await projectWithNoChange(`${emptyCode}\n${code}`);
|
||||
expectTrackedTypes(result.trackedTypes, expectedTypes);
|
||||
});
|
||||
} else {
|
||||
const refCode = `
|
||||
@test("target") model Referencing {
|
||||
b: ${ref};
|
||||
}
|
||||
`;
|
||||
describe(name, () => {
|
||||
it("referenced before", async () => {
|
||||
const result = await projectWithNoChange(`${refCode}\n${code}`);
|
||||
expectTrackedTypes(result.trackedTypes, expectedTypes);
|
||||
});
|
||||
|
||||
it("referenced after", async () => {
|
||||
const result = await projectWithNoChange(`${code}\n${refCode}`);
|
||||
expectTrackedTypes(result.trackedTypes, expectedTypes);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function expectTrackedTypes(trackedTypes: Type[], expectedTypes: [Type["kind"], string][]) {
|
||||
deepStrictEqual(
|
||||
trackedTypes.map((x) => [x.kind, getTypeName(x)]),
|
||||
expectedTypes
|
||||
);
|
||||
}
|
||||
|
||||
describe("models", () => {
|
||||
it("link projected model to projected properties", async () => {
|
||||
const projectResult = await projectWithNoChange(
|
||||
`
|
||||
@test("target") model Foo {
|
||||
name: string;
|
||||
}
|
||||
`,
|
||||
"Model"
|
||||
);
|
||||
strictEqual(projectResult.type.properties.get("name")?.model, projectResult.type);
|
||||
});
|
||||
|
||||
it("link projected property with sourceProperty", async () => {
|
||||
const code = `
|
||||
@test("target") model Foo {
|
||||
...Spreadable
|
||||
}
|
||||
|
||||
model Spreadable {
|
||||
name: string;
|
||||
}
|
||||
`;
|
||||
const projectResult = await projectWithNoChange(code, "Model");
|
||||
const sourceProperty = projectResult.type.properties.get("name")?.sourceProperty;
|
||||
ok(sourceProperty);
|
||||
const Spreadable = projectResult.globalNamespace.models.get("Spreadable")!;
|
||||
strictEqual(sourceProperty, Spreadable.properties.get("name"));
|
||||
});
|
||||
|
||||
it("project property with type referencing sibling", async () => {
|
||||
const code = `
|
||||
@test("target") model Foo {
|
||||
a: Foo.b;
|
||||
b: string;
|
||||
}`;
|
||||
const result = await projectWithNoChange(code, "Model");
|
||||
strictEqual(result.type.properties.get("a")?.type, result.type.properties.get("b"));
|
||||
});
|
||||
|
||||
describe("runs decorator on property before model", () => {
|
||||
describeDecoratorOrder({
|
||||
name: "simple",
|
||||
code: `
|
||||
@track model Foo {
|
||||
@track a: string
|
||||
}`,
|
||||
ref: "Foo.a",
|
||||
expectedTypes: [
|
||||
["ModelProperty", "Foo.a"],
|
||||
["Model", "Foo"],
|
||||
],
|
||||
});
|
||||
|
||||
describeDecoratorOrder({
|
||||
name: "with spread properties",
|
||||
code: `
|
||||
@track model Foo {
|
||||
...Spreadable;
|
||||
}
|
||||
|
||||
model Spreadable {
|
||||
@track name: string;
|
||||
}`,
|
||||
ref: "Foo.name",
|
||||
expectedTypes: [
|
||||
["ModelProperty", "Spreadable.name"],
|
||||
["ModelProperty", "Foo.name"],
|
||||
["Model", "Foo"],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unions", () => {
|
||||
it("link projected unions to projected variants", async () => {
|
||||
const projectResult = await projectWithNoChange(
|
||||
`
|
||||
@test("target") union Foo {
|
||||
one: {};
|
||||
}
|
||||
`,
|
||||
"Union"
|
||||
);
|
||||
strictEqual(projectResult.type.variants.get("one")?.union, projectResult.type);
|
||||
});
|
||||
|
||||
describe("runs decorator on variants before unions", () => {
|
||||
describeDecoratorOrder({
|
||||
name: "simple",
|
||||
code: `
|
||||
@track union Foo {
|
||||
@track one: {}
|
||||
}`,
|
||||
ref: "Foo.one",
|
||||
expectedTypes: [
|
||||
["UnionVariant", "{}"],
|
||||
["Union", "Foo"],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("enum", () => {
|
||||
it("link projected enum to the projected enum member", async () => {
|
||||
const projectResult = await projectWithNoChange(
|
||||
`
|
||||
@test("target") enum Foo {
|
||||
one,
|
||||
}
|
||||
`,
|
||||
"Enum"
|
||||
);
|
||||
strictEqual(projectResult.type.members.get("one")?.enum, projectResult.type);
|
||||
});
|
||||
|
||||
describe("runs decorator on variants before unions", () => {
|
||||
describeDecoratorOrder({
|
||||
name: "simple",
|
||||
code: `
|
||||
@track enum Foo {
|
||||
@track one,
|
||||
}`,
|
||||
ref: "Foo.one",
|
||||
expectedTypes: [
|
||||
["EnumMember", "Foo.one"],
|
||||
["Enum", "Foo"],
|
||||
],
|
||||
});
|
||||
|
||||
describeDecoratorOrder({
|
||||
name: "with spread members",
|
||||
code: `
|
||||
@track enum Foo {
|
||||
...Spreadable;
|
||||
}
|
||||
|
||||
enum Spreadable {
|
||||
@track one,
|
||||
}`,
|
||||
ref: "Foo.one",
|
||||
expectedTypes: [
|
||||
["EnumMember", "Foo.one"],
|
||||
["Enum", "Foo"],
|
||||
["EnumMember", "Spreadable.one"],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("interface", () => {
|
||||
it("link projected interface to the projected operation member", async () => {
|
||||
const projectResult = await projectWithNoChange(
|
||||
`
|
||||
@test("target") interface Foo {
|
||||
one(): void;
|
||||
}
|
||||
`,
|
||||
"Interface"
|
||||
);
|
||||
strictEqual(projectResult.type.operations.get("one")?.interface, projectResult.type);
|
||||
});
|
||||
|
||||
describe("runs decorator on variants before unions", () => {
|
||||
describeDecoratorOrder({
|
||||
name: "simple",
|
||||
code: `
|
||||
@track interface Foo {
|
||||
@track one(): void;
|
||||
}`,
|
||||
ref: "Foo.one",
|
||||
expectedTypes: [
|
||||
["Operation", "one"],
|
||||
["Interface", "Foo"],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче