Skip to content
Draft
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
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
---

compiler - Add typekit for interfaces (`$.interface`)
2 changes: 2 additions & 0 deletions packages/compiler/src/typekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export {
EnumKit,
EnumMemberDescriptor,
EnumMemberKit,
InterfaceDescriptor,
InterfaceKit,
IntrinsicKit,
LiteralKit,
ModelDescriptor,
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/typekit/kits/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./builtin.js";
export * from "./entity.js";
export * from "./enum-member.js";
export * from "./enum.js";
export * from "./interface.js";
export * from "./intrinsic.js";
export * from "./literal.js";
export * from "./model-property.js";
Expand Down
81 changes: 81 additions & 0 deletions packages/compiler/src/typekit/kits/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { Entity, Interface, Operation } from "../../core/types.js";
import { createRekeyableMap } from "../../utils/misc.js";
import { defineKit } from "../define-kit.js";
import { decoratorApplication, DecoratorArgs } from "../utils.js";

/**
* A descriptor for creating an interface.
*/
export interface InterfaceDescriptor {
/**
* The name of the interface declaration.
*/
name: string;

/**
* Decorators to apply to the interface.
*/
decorators?: DecoratorArgs[];

/**
* The operations of the interface.
*/
operations?: Operation[];
}

/**
* Utilities for working with interfaces.
* @typekit interface
*/
export interface InterfaceKit {
/**
* Create an interface type.
*
* @param desc The descriptor of the interface.
*/
create(desc: InterfaceDescriptor): Interface;

/**
* Check if the given `type` is an interface.
*
* @param type The type to check.
*/
is(type: Entity): type is Interface;
}

interface TypekitExtension {
/**
* Utilities for working with interfaces.
*/
interface: InterfaceKit;
}

declare module "../define-kit.js" {
interface Typekit extends TypekitExtension {}
}

defineKit<TypekitExtension>({
interface: {
create(desc) {
const iface: Interface = this.program.checker.createType({
kind: "Interface",
name: desc.name,
decorators: decoratorApplication(this, desc.decorators),
operations: createRekeyableMap(),
sourceInterfaces: [],
});

for (const op of desc.operations ?? []) {
op.interface = iface;
iface.operations.set(op.name, op);
}

this.program.checker.finishType(iface);
return iface;
},

is(type) {
return type.entityKind === "Type" && type.kind === "Interface";
},
},
});
62 changes: 62 additions & 0 deletions packages/compiler/test/typekit/interface.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expect, it } from "vitest";
import { $ } from "../../src/typekit/index.js";
import { createContextMock, getTypes } from "./utils.js";

it("can check if a type is an Interface", async () => {
const {
Foo,
context: { program },
} = await getTypes(
`
interface Foo {};
`,
["Foo"],
);

expect($(program).interface.is(Foo)).toBe(true);
});

it("returns false when the type is not an interface", async () => {
const {
Foo,
context: { program },
} = await getTypes(
`
model Foo {};
`,
["Foo"],
);

expect($(program).interface.is(Foo)).toBe(false);
expect($(program).interface.is($(program).value.create("foo"))).toBe(false);
});

it("creates a new Interface", async () => {
const { program } = await createContextMock();
const iface = $(program).interface.create({
name: "Foo",
});

expect($(program).interface.is(iface)).toBe(true);
expect(iface.name).toBe("Foo");
expect(iface.operations.size).toBe(0);
});

it("creates an Interface with operations", async () => {
const { program } = await createContextMock();

const op = $(program).operation.create({
name: "myOp",
parameters: [],
returnType: $(program).intrinsic.void,
});

const iface = $(program).interface.create({
name: "Foo",
operations: [op],
});

expect(iface.operations.size).toBe(1);
expect(iface.operations.get("myOp")).toBe(op);
expect(op.interface).toBe(iface);
});
Loading