Skip to content

Commit f7f7004

Browse files
chrisradekChristopher Radek
andauthored
support nesting typekits (microsoft#7108)
Fixes microsoft#7094 The caveat to this change is that you shouldn't have typekits that use regular fields. For example - `$.foo.type` <-- if this is a regular field that contains an object, then that object will be wrapped in a Proxy, breaking object identity. However, we follow the pattern of using `get` descriptors for this use-case instead, which would not break object identity. --------- Co-authored-by: Christopher Radek <Christopher.Radek@microsoft.com>
1 parent 5c29f0a commit f7f7004

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/compiler"
5+
---
6+
7+
Adds support for nesting typekits

packages/compiler/src/experimental/typekit/index.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Program } from "../../core/program.js";
22
import { Realm } from "../realm.js";
3-
import { Typekit, TypekitNamespaceSymbol, TypekitPrototype } from "./define-kit.js";
3+
import { Typekit, TypekitPrototype } from "./define-kit.js";
44

55
export * from "./create-diagnosable.js";
66
export * from "./define-kit.js";
@@ -65,9 +65,13 @@ export function createTypekit(realm: Realm): Typekit {
6565
return proxyWrapper;
6666
}
6767

68-
// Only wrap objects marked as Typekit namespaces
69-
if (typeof value === "object" && value !== null && isTypekitNamespace(value)) {
70-
return new Proxy(value, handler); // Wrap namespace objects
68+
// Wrap objects to ensure their functions are bound correctly, avoid wrapping `get` accessors
69+
if (
70+
typeof value === "object" &&
71+
value !== null &&
72+
!Reflect.getOwnPropertyDescriptor(target, prop)?.get
73+
) {
74+
return new Proxy(value, handler);
7175
}
7276

7377
return value;
@@ -78,11 +82,6 @@ export function createTypekit(realm: Realm): Typekit {
7882
return proxy;
7983
}
8084

81-
// Helper function to check if an object is a Typekit namespace
82-
function isTypekitNamespace(obj: any): boolean {
83-
return obj && !!obj[TypekitNamespaceSymbol];
84-
}
85-
8685
// #region Default Typekit
8786

8887
const DEFAULT_REALM = Symbol.for("TypeSpec.Typekit.DEFAULT_TYPEKIT_REALM");

packages/compiler/test/experimental/typekit/define-kit.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,30 @@ it("can define a kit", async () => {
1212

1313
expect(($(program) as any).test()).toBe(true);
1414
});
15+
16+
it("supports nested kits", async () => {
17+
const { program } = await createContextMock();
18+
defineKit({
19+
__nestedTest: {
20+
get foo() {
21+
return this.program.checker.anyType;
22+
},
23+
nested: {
24+
get foo() {
25+
return this.program.checker.anyType;
26+
},
27+
bar() {
28+
return this.program.checker.anyType;
29+
},
30+
} as any,
31+
} as any,
32+
});
33+
const tk = $(program);
34+
35+
// Ensure program isn't wrapped in a proxy
36+
expect(tk.program).toBe(program);
37+
38+
expect((tk as any).__nestedTest.foo).toBe(program.checker.anyType);
39+
expect((tk as any).__nestedTest.nested.foo).toBe(program.checker.anyType);
40+
expect((tk as any).__nestedTest.nested.bar()).toBe(program.checker.anyType);
41+
});

0 commit comments

Comments
 (0)