Skip to content
File

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

typescript207 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 { BuiltinType_Type, JsgImplType_Type, Type } from '@workerd/jsg/rtti';
8import { Message } from 'capnp-es';
9import { createTypeNode } from '../../src/generator/type';
10import { printNode } from '../../src/print';
11 
12test('createTypeNode: primitive types', () => {
13 const type = new Message().initRoot(Type);
14 
15 type.unknown = true;
16 assert.strictEqual(printNode(createTypeNode(type)), 'any');
17 type.object = true;
18 assert.strictEqual(printNode(createTypeNode(type)), 'any');
19 
20 type.voidt = true;
21 assert.strictEqual(printNode(createTypeNode(type)), 'void');
22 
23 type.boolt = true;
24 assert.strictEqual(printNode(createTypeNode(type)), 'boolean');
25 
26 type._initNumber().name = 'int';
27 assert.strictEqual(printNode(createTypeNode(type)), 'number');
28 type.number.name = 'long';
29 assert.strictEqual(printNode(createTypeNode(type)), 'number | bigint');
30 
31 type._initString().name = 'kj::String';
32 assert.strictEqual(printNode(createTypeNode(type)), 'string');
33 
34 const structure = type._initStructure();
35 structure.name = 'KvNamespace';
36 structure.fullyQualifiedName = 'workerd::api::KvNamespace';
37 assert.strictEqual(printNode(createTypeNode(type)), 'KvNamespace');
38});
39 
40test('createTypeNode: builtin types', () => {
41 const type = new Message().initRoot(Type);
42 const builtin = type._initBuiltin();
43 
44 builtin.type = BuiltinType_Type.V8UINT8ARRAY;
45 assert.strictEqual(printNode(createTypeNode(type)), 'Uint8Array');
46 
47 builtin.type = BuiltinType_Type.V8ARRAY_BUFFER_VIEW;
48 assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBufferView');
49 
50 builtin.type = BuiltinType_Type.JSG_BUFFER_SOURCE;
51 assert.strictEqual(
52 printNode(createTypeNode(type)),
53 'ArrayBuffer | ArrayBufferView'
54 );
55 
56 builtin.type = BuiltinType_Type.KJ_DATE;
57 assert.strictEqual(printNode(createTypeNode(type)), 'Date');
58 
59 builtin.type = BuiltinType_Type.V8FUNCTION;
60 assert.strictEqual(printNode(createTypeNode(type)), 'Function');
61 
62 const intrinsic = type._initIntrinsic();
63 
64 intrinsic.name = 'v8::kErrorPrototype';
65 assert.strictEqual(printNode(createTypeNode(type)), 'Error');
66 
67 intrinsic.name = 'v8::kIteratorPrototype';
68 assert.strictEqual(printNode(createTypeNode(type)), 'Iterator<unknown>');
69 
70 intrinsic.name = 'v8::kAsyncIteratorPrototype';
71 assert.strictEqual(printNode(createTypeNode(type)), 'AsyncIterator<unknown>');
72});
73 
74test('createTypeNode: generic types', () => {
75 const type = new Message().initRoot(Type);
76 
77 type._initPromise()._initValue().voidt = true;
78 assert.strictEqual(printNode(createTypeNode(type)), 'Promise<void>');
79 type._initPromise()._initValue().voidt = true;
80 assert.strictEqual(
81 printNode(createTypeNode(type, true)),
82 'void | Promise<void>'
83 );
84 assert.strictEqual(
85 printNode(createTypeNode(type, true, true)),
86 'Promise<any>'
87 );
88 
89 const maybe = type._initMaybe();
90 maybe._initValue().boolt = true;
91 maybe.name = 'jsg::Optional';
92 assert.strictEqual(printNode(createTypeNode(type)), 'boolean | undefined');
93 maybe.name = 'jsg::LenientOptional';
94 assert.strictEqual(printNode(createTypeNode(type)), 'boolean | undefined');
95 maybe.name = 'kj::Maybe';
96 assert.strictEqual(printNode(createTypeNode(type)), 'boolean | null');
97 
98 const dict = type._initDict();
99 dict._initKey()._initString().name = 'kj::StringPtr';
100 dict._initValue()._initNumber().name = 'short';
101 assert.strictEqual(printNode(createTypeNode(type)), 'Record<string, number>');
102 
103 const variants = type._initOneOf()._initVariants(3);
104 variants.get(0).voidt = true;
105 variants.get(1)._initNumber().name = 'unsigned short';
106 variants.get(2)._initString().name = 'kj::String';
107 assert.strictEqual(printNode(createTypeNode(type)), 'void | number | string');
108});
109 
110test('createTypeNode: array types', () => {
111 const type = new Message().initRoot(Type);
112 const array = type._initArray();
113 
114 // Regular array
115 array.name = 'kj::Array';
116 array._initElement()._initString().name = 'kj::String';
117 assert.strictEqual(printNode(createTypeNode(type)), 'string[]');
118 assert.strictEqual(printNode(createTypeNode(type, true)), 'string[]');
119 // Iterable
120 array.name = 'jsg::Sequence';
121 array._initElement()._initString().name = 'kj::String';
122 assert.strictEqual(printNode(createTypeNode(type)), 'string[]');
123 assert.strictEqual(printNode(createTypeNode(type, true)), 'Iterable<string>');
124 
125 // Numeric arrays
126 array.name = 'kj::Array';
127 array._initElement()._initNumber().name = 'int';
128 assert.strictEqual(printNode(createTypeNode(type)), 'number[]');
129 // If element is a char, then this is a string
130 array.name = 'kj::ArrayPtr';
131 array._initElement()._initNumber().name = 'char';
132 assert.strictEqual(printNode(createTypeNode(type)), 'string');
133 // If element is a byte, then this is an ArrayBuffer, ArrayBufferView or both
134 array.name = 'kj::Array';
135 array._initElement()._initNumber().name = 'unsigned char';
136 assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBuffer');
137 assert.strictEqual(
138 printNode(createTypeNode(type, true)),
139 'ArrayBuffer | ArrayBufferView'
140 );
141 array.name = 'kj::ArrayPtr';
142 array._initElement()._initNumber().name = 'unsigned char';
143 assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBufferView');
144 assert.strictEqual(
145 printNode(createTypeNode(type, true)),
146 'ArrayBuffer | ArrayBufferView'
147 );
148});
149 
150test('createTypeNode: function types', () => {
151 const message = new Message();
152 
153 // (a: boolean, b: number | undefined, d: string, c?: any) => void
154 let type = message.initRoot(Type);
155 let func = type._initFunction();
156 let args = func._initArgs(5);
157 args.get(0).boolt = true;
158 args.get(1)._initMaybe()._initValue()._initNumber().name = 'int';
159 args.get(2)._initString().name = 'kj::String';
160 args.get(3)._initMaybe()._initValue().object = true;
161 args.get(4)._initJsgImpl().type = JsgImplType_Type.V8ISOLATE;
162 func._initReturnType().voidt = true;
163 let typeNode = createTypeNode(type);
164 assert.strictEqual(
165 printNode(typeNode),
166 '(param0: boolean, param1: number | undefined, param2: string, param3?: any) => void'
167 );
168 
169 // (a?: string, ...b: any[]) => Promise<void>
170 type = message.initRoot(Type);
171 func = type._initFunction();
172 args = func._initArgs(3);
173 args.get(0)._initJsgImpl().type = JsgImplType_Type.JSG_TYPE_HANDLER;
174 args.get(1)._initMaybe()._initValue()._initString().name = 'kj::String';
175 args.get(2)._initJsgImpl().type = JsgImplType_Type.JSG_VARARGS;
176 func._initReturnType()._initPromise()._initValue().voidt = true;
177 typeNode = createTypeNode(type);
178 assert.strictEqual(
179 printNode(typeNode),
180 '(param1?: string, ...param2: any[]) => void | Promise<void>'
181 );
182});
183 
184test('createTypeNode: implementation types', () => {
185 const type = new Message().initRoot(Type);
186 const impl = type._initJsgImpl();
187 
188 const implTypes: JsgImplType_Type[] = Object.values(type).filter(
189 (member) => typeof member === 'number'
190 ) as JsgImplType_Type[];
191 for (const implType of implTypes) {
192 // VARARGS and NAME are the only types we care about which will be tested
193 // with function types, the rest should be ignored
194 if (
195 implType === JsgImplType_Type.JSG_VARARGS ||
196 implType === JsgImplType_Type.JSG_NAME
197 ) {
198 continue;
199 }
200 impl.type = implType;
201 assert.strictEqual(printNode(createTypeNode(type)), 'never');
202 }
203 
204 impl.type = JsgImplType_Type.JSG_NAME;
205 assert.strictEqual(printNode(createTypeNode(type)), 'PropertyKey');
206});