Skip to content
File

Blob: types/test/generator/index.spec.ts

typescript253 lines
1// Copyright (c) 2022-2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5import assert from 'assert';
6import { test } from 'node:test';
7import {
8 Member_Nested,
9 Structure,
10 StructureGroups,
11 StructureGroups_StructureGroup,
12 Type,
13} from '@workerd/jsg/rtti';
14import { Message } from 'capnp-es';
15import { generateDefinitions } from '../../src/generator';
16import { printNodeList } from '../../src/print';
17 
18// Initializes a structure group containing `targets` targets to reference.
19// Returns a function to point a type at an identified target.
20function initAsReferencableTypesGroup(
21 group: StructureGroups_StructureGroup,
22 targets: number
23): (id: number, type: Type | Member_Nested) => void {
24 group.name = 'referenced';
25 const structures = group._initStructures(targets);
26 for (let i = 0; i < targets; i++) {
27 const structure = structures.get(i);
28 structure.name = `Thing${i}`;
29 structure.fullyQualifiedName = `workerd::api::Thing${i}`;
30 }
31 return function initAsReferencedType(id, type) {
32 assert(0 <= id && id < targets);
33 const structure = type._initStructure();
34 structure.name = `Thing${id}`;
35 structure.fullyQualifiedName = `workerd::api::Thing${id}`;
36 };
37}
38 
39test('generateDefinitions: only includes referenced types from roots', () => {
40 const root = new Message().initRoot(StructureGroups);
41 const groups = root._initGroups(2);
42 
43 // Generate group containing definitions for referencing by next group
44 const initAsReferencedType = initAsReferencableTypesGroup(groups.get(0), 24);
45 
46 // Generate group containing definitions with each possible type of visitable
47 // type
48 const group = groups.get(1);
49 group.name = 'definitions';
50 const structures = group._initStructures(4);
51 
52 const root1 = structures.get(0);
53 root1.name = 'Root1';
54 root1.fullyQualifiedName = 'workerd::api::Root1';
55 root1.tsRoot = true;
56 {
57 const members = root1._initMembers(7);
58 
59 let prop = members.get(0)._initProperty();
60 prop.name = 'promise';
61 initAsReferencedType(0, prop._initType()._initPromise()._initValue());
62 
63 prop = members.get(1)._initProperty();
64 prop.name = 'structure';
65 initAsReferencedType(1, prop._initType());
66 
67 prop = members.get(2)._initProperty();
68 prop.name = 'array';
69 const array = prop._initType()._initArray();
70 array.name = 'kj::Array';
71 initAsReferencedType(2, array._initElement());
72 
73 prop = members.get(3)._initProperty();
74 prop.name = 'maybe';
75 const maybe = prop._initType()._initMaybe();
76 maybe.name = 'jsg::Optional';
77 initAsReferencedType(3, maybe._initValue());
78 
79 prop = members.get(4)._initProperty();
80 prop.name = 'dict';
81 const dict = prop._initType()._initDict();
82 initAsReferencedType(4, dict._initKey());
83 initAsReferencedType(5, dict._initValue());
84 
85 prop = members.get(5)._initProperty();
86 prop.name = 'variants';
87 const variants = prop._initType()._initOneOf()._initVariants(3);
88 initAsReferencedType(6, variants.get(0));
89 initAsReferencedType(7, variants.get(1));
90 initAsReferencedType(8, variants.get(2));
91 
92 prop = members.get(6)._initProperty();
93 prop.name = 'function';
94 const func = prop._initType()._initFunction();
95 initAsReferencedType(9, func._initArgs(1).get(0));
96 initAsReferencedType(10, func._initReturnType());
97 }
98 
99 const nested = structures.get(1);
100 function initAsNestedStructure(structure: Structure): void {
101 structure.name = 'Nested';
102 structure.fullyQualifiedName = 'workerd::api::Nested';
103 const members = structure._initMembers(1);
104 const prop = members.get(0)._initProperty();
105 prop.name = 'nestedProp';
106 initAsReferencedType(11, prop._initType());
107 }
108 initAsNestedStructure(nested);
109 
110 const root2 = structures.get(2);
111 root2.name = 'Root2';
112 root2.fullyQualifiedName = 'workerd::api::Root2';
113 root2.tsRoot = true;
114 {
115 const members = root2._initMembers(3);
116 
117 const method = members.get(0)._initMethod();
118 method.name = 'method';
119 initAsReferencedType(12, method._initArgs(1).get(0));
120 initAsReferencedType(13, method._initReturnType());
121 
122 const nested = members.get(1)._initNested();
123 nested.name = 'Nested';
124 initAsNestedStructure(nested._initStructure());
125 
126 const constructor = members.get(2)._initConstructor();
127 initAsReferencedType(14, constructor._initArgs(1).get(0));
128 }
129 const iterator = root2._initIterator();
130 initAsReferencedType(15, iterator._initArgs(1).get(0));
131 initAsReferencedType(16, iterator._initReturnType());
132 const asyncIterator = root2._initAsyncIterator();
133 initAsReferencedType(17, asyncIterator._initArgs(1).get(0));
134 initAsReferencedType(18, asyncIterator._initReturnType());
135 initAsReferencedType(19, root2._initExtends());
136 
137 // Types referenced by non-roots shouldn't be included
138 const nonRoot = structures.get(3);
139 nonRoot.name = 'NonRoot';
140 nonRoot.fullyQualifiedName = 'workerd::api::NonRoot';
141 const members = nonRoot._initMembers(1);
142 const prop = members.get(0)._initProperty();
143 prop.name = 'nonRootProp';
144 initAsReferencedType(20, prop._initType());
145 
146 const referencedInterfaces = Array.from(Array(19))
147 .map((_, i) => `interface Thing${i} {\n}`)
148 .join('\n');
149 const { nodes } = generateDefinitions(root);
150 assert.strictEqual(
151 printNodeList(nodes),
152 `${referencedInterfaces}
153declare abstract class Thing19 {
154}
155interface Root1 {
156 promise: Promise<Thing0>;
157 structure: Thing1;
158 array: Thing2[];
159 maybe?: Thing3;
160 dict: Record<Thing4, Thing5>;
161 variants: Thing6 | Thing7 | Thing8;
162 function: (param0: Thing9) => Thing10;
163}
164declare abstract class Nested {
165 nestedProp: Thing11;
166}
167declare class Root2 extends Thing19 {
168 constructor(param0: Thing14);
169 method(param0: Thing12): Thing13;
170 Nested: typeof Nested;
171 [Symbol.iterator](param0: Thing15): Thing16;
172 [Symbol.asyncIterator](param0: Thing17): Thing18;
173}
174`
175 );
176});
177 
178test('generateDefinitions: only generates classes if required', () => {
179 const root = new Message().initRoot(StructureGroups);
180 const groups = root._initGroups(2);
181 
182 // Generate group containing definitions for referencing by next group
183 const initAsReferencedType = initAsReferencableTypesGroup(groups.get(0), 2);
184 
185 // Generate group containing definitions with each possible class requirement
186 const group = groups.get(1);
187 group.name = 'definitions';
188 const structures = group._initStructures(4);
189 
190 const root1 = structures.get(0);
191 root1.name = 'Root1';
192 root1.fullyQualifiedName = 'workerd::api::Root1';
193 root1.tsRoot = true;
194 // Thing0 should be a class as it's a nested type
195 {
196 const members = root1._initMembers(1);
197 const nested = members.get(0)._initNested();
198 nested.name = 'Thing0';
199 initAsReferencedType(0, nested);
200 }
201 
202 const root2 = structures.get(1);
203 root2.name = 'Root2';
204 root2.fullyQualifiedName = 'workerd::api::Root2';
205 root2.tsRoot = true;
206 {
207 const members = root2._initMembers(1);
208 // ExportedHandler should be a class as it's constructible
209 members.get(0)._initConstructor();
210 }
211 
212 const root3 = structures.get(2);
213 root3.name = 'Root3';
214 root3.fullyQualifiedName = 'workerd::api::Root3';
215 root3.tsRoot = true;
216 {
217 const members = root3._initMembers(1);
218 const method = members.get(0)._initMethod();
219 method.name = 'method';
220 // DurableObjectNamespace should be a class as it contains static methods
221 method.static = true;
222 method._initReturnType().voidt = true;
223 }
224 
225 const root4 = structures.get(3);
226 root4.name = 'Root4';
227 root4.fullyQualifiedName = 'workerd::api::Root4';
228 root4.tsRoot = true;
229 // Thing1 should be a class as its inherited
230 initAsReferencedType(1, root4._initExtends());
231 
232 const { nodes } = generateDefinitions(root);
233 assert.strictEqual(
234 printNodeList(nodes),
235 `declare abstract class Thing0 {
236}
237declare abstract class Thing1 {
238}
239interface Root1 {
240 Thing0: typeof Thing0;
241}
242declare class Root2 {
243 constructor();
244}
245declare abstract class Root3 {
246 static method(): void;
247}
248interface Root4 extends Thing1 {
249}
250`
251 );
252});