File
Blob: types/test/generator/structure.spec.ts
| 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 | |
| 5 | import assert from 'assert'; |
| 6 | import { test } from 'node:test'; |
| 7 | import { JsgImplType_Type, Structure } from '@workerd/jsg/rtti'; |
| 8 | import { Message } from 'capnp-es'; |
| 9 | import { createStructureNode } from '../../src/generator/structure'; |
| 10 | import { printNode } from '../../src/print'; |
| 11 | |
| 12 | test('createStructureNode: method members', () => { |
| 13 | const structure = new Message().initRoot(Structure); |
| 14 | structure.name = 'Methods'; |
| 15 | structure.fullyQualifiedName = 'workerd::api::Methods'; |
| 16 | |
| 17 | const members = structure._initMembers(3); |
| 18 | |
| 19 | let method = members.get(0)._initMethod(); |
| 20 | method.name = 'one'; |
| 21 | { |
| 22 | const args = method._initArgs(3); |
| 23 | args.get(0).boolt = true; |
| 24 | args.get(1)._initPromise()._initValue().voidt = true; |
| 25 | const maybe = args.get(2)._initMaybe(); |
| 26 | maybe.name = 'jsg::Optional'; |
| 27 | maybe._initValue()._initNumber().name = 'int'; |
| 28 | } |
| 29 | method._initReturnType().voidt = true; |
| 30 | |
| 31 | method = members.get(1)._initMethod(); |
| 32 | method.name = 'two'; |
| 33 | method._initReturnType()._initJsgImpl().type = |
| 34 | JsgImplType_Type.JSG_UNIMPLEMENTED; |
| 35 | |
| 36 | method = members.get(2)._initMethod(); |
| 37 | method.name = 'three'; |
| 38 | { |
| 39 | const args = method._initArgs(1); |
| 40 | args.get(0)._initJsgImpl().type = JsgImplType_Type.JSG_VARARGS; |
| 41 | } |
| 42 | method._initReturnType().boolt = true; |
| 43 | |
| 44 | // Note method with unimplemented return is omitted |
| 45 | assert.strictEqual( |
| 46 | printNode(createStructureNode(structure, { asClass: false })), |
| 47 | `interface Methods { |
| 48 | one(param0: boolean, param1: Promise<any>, param2?: number): void; |
| 49 | three(...param0: any[]): boolean; |
| 50 | }` |
| 51 | ); |
| 52 | |
| 53 | method.static = true; |
| 54 | assert.strictEqual( |
| 55 | printNode(createStructureNode(structure, { asClass: true })), |
| 56 | `declare abstract class Methods { |
| 57 | one(param0: boolean, param1: Promise<any>, param2?: number): void; |
| 58 | static three(...param0: any[]): boolean; |
| 59 | }` |
| 60 | ); |
| 61 | }); |
| 62 | |
| 63 | test('createStructureNode: property members', () => { |
| 64 | const structure = new Message().initRoot(Structure); |
| 65 | structure.name = 'Properties'; |
| 66 | structure.fullyQualifiedName = 'workerd::api::Properties'; |
| 67 | |
| 68 | const members = structure._initMembers(8); |
| 69 | |
| 70 | let prop = members.get(0)._initProperty(); |
| 71 | prop.name = 'one'; |
| 72 | prop._initType().boolt = true; |
| 73 | |
| 74 | prop = members.get(1)._initProperty(); |
| 75 | prop.name = 'two'; |
| 76 | prop._initType()._initNumber().name = 'int'; |
| 77 | prop.readonly = true; |
| 78 | |
| 79 | prop = members.get(2)._initProperty(); |
| 80 | prop.name = 'three'; |
| 81 | { |
| 82 | const maybe = prop._initType()._initMaybe(); |
| 83 | maybe.name = 'jsg::Optional'; |
| 84 | maybe._initValue().boolt = true; |
| 85 | } |
| 86 | prop.readonly = true; |
| 87 | |
| 88 | prop = members.get(3)._initProperty(); |
| 89 | prop.name = 'four'; |
| 90 | prop._initType()._initJsgImpl().type = JsgImplType_Type.JSG_UNIMPLEMENTED; |
| 91 | prop.readonly = true; |
| 92 | |
| 93 | prop = members.get(4)._initProperty(); |
| 94 | prop.name = 'five'; |
| 95 | prop._initType().boolt = true; |
| 96 | prop.prototype = true; |
| 97 | |
| 98 | prop = members.get(5)._initProperty(); |
| 99 | prop.name = 'six'; |
| 100 | prop._initType()._initString().name = 'kj::String'; |
| 101 | prop.readonly = true; |
| 102 | prop.prototype = true; |
| 103 | |
| 104 | prop = members.get(6)._initProperty(); |
| 105 | prop.name = 'seven'; |
| 106 | { |
| 107 | const maybe = prop._initType()._initMaybe(); |
| 108 | maybe.name = 'jsg::Optional'; |
| 109 | maybe._initValue()._initNumber().name = 'short'; |
| 110 | } |
| 111 | prop.readonly = true; |
| 112 | prop.prototype = true; |
| 113 | |
| 114 | prop = members.get(7)._initProperty(); |
| 115 | prop.name = 'eight'; |
| 116 | prop._initType()._initJsgImpl().type = JsgImplType_Type.JSG_UNIMPLEMENTED; |
| 117 | prop.readonly = true; |
| 118 | prop.prototype = true; |
| 119 | |
| 120 | // Note unimplemented properties omitted |
| 121 | assert.strictEqual( |
| 122 | printNode(createStructureNode(structure, { asClass: false })), |
| 123 | `interface Properties { |
| 124 | one: boolean; |
| 125 | readonly two: number; |
| 126 | readonly three?: boolean; |
| 127 | get five(): boolean; |
| 128 | set five(value: boolean); |
| 129 | get six(): string; |
| 130 | get seven(): number | undefined; |
| 131 | }` |
| 132 | ); |
| 133 | assert.strictEqual( |
| 134 | printNode(createStructureNode(structure, { asClass: true })), |
| 135 | `declare abstract class Properties { |
| 136 | one: boolean; |
| 137 | readonly two: number; |
| 138 | readonly three?: boolean; |
| 139 | get five(): boolean; |
| 140 | set five(value: boolean); |
| 141 | get six(): string; |
| 142 | get seven(): number | undefined; |
| 143 | }` |
| 144 | ); |
| 145 | }); |
| 146 | |
| 147 | test('createStructureNode: nested type members', () => { |
| 148 | const structure = new Message().initRoot(Structure); |
| 149 | structure.name = 'Nested'; |
| 150 | structure.fullyQualifiedName = 'workerd::api::Nested'; |
| 151 | |
| 152 | const members = structure._initMembers(2); |
| 153 | |
| 154 | let nested = members.get(0)._initNested(); |
| 155 | let nestedStructure = nested._initStructure(); |
| 156 | nestedStructure.name = 'Thing'; |
| 157 | nestedStructure.fullyQualifiedName = 'workerd::api::Thing'; |
| 158 | |
| 159 | nested = members.get(1)._initNested(); |
| 160 | nestedStructure = nested._initStructure(); |
| 161 | nestedStructure.name = 'OtherThing'; |
| 162 | nestedStructure.fullyQualifiedName = 'workerd::api::OtherThing'; |
| 163 | nested.name = 'RenamedThing'; |
| 164 | |
| 165 | assert.strictEqual( |
| 166 | printNode(createStructureNode(structure, { asClass: false })), |
| 167 | `interface Nested { |
| 168 | Thing: typeof Thing; |
| 169 | RenamedThing: typeof OtherThing; |
| 170 | }` |
| 171 | ); |
| 172 | assert.strictEqual( |
| 173 | printNode(createStructureNode(structure, { asClass: true })), |
| 174 | `declare abstract class Nested { |
| 175 | Thing: typeof Thing; |
| 176 | RenamedThing: typeof OtherThing; |
| 177 | }` |
| 178 | ); |
| 179 | }); |
| 180 | |
| 181 | test('createStructureNode: constant members', () => { |
| 182 | const structure = new Message().initRoot(Structure); |
| 183 | structure.name = 'Constants'; |
| 184 | structure.fullyQualifiedName = 'workerd::api::Constants'; |
| 185 | |
| 186 | const members = structure._initMembers(1); |
| 187 | |
| 188 | const constant = members.get(0)._initConstant(); |
| 189 | constant.name = 'THING'; |
| 190 | constant.value = BigInt(42); |
| 191 | |
| 192 | assert.strictEqual( |
| 193 | printNode(createStructureNode(structure, { asClass: true })), |
| 194 | `declare abstract class Constants { |
| 195 | static readonly THING: number; |
| 196 | }` |
| 197 | ); |
| 198 | }); |
| 199 | |
| 200 | test('createStructureNode: iterator members', () => { |
| 201 | const structure = new Message().initRoot(Structure); |
| 202 | structure.name = 'Iterators'; |
| 203 | structure.fullyQualifiedName = 'workerd::api::Iterators'; |
| 204 | |
| 205 | structure.iterable = true; |
| 206 | const iterator = structure._initIterator(); |
| 207 | { |
| 208 | const args = iterator._initArgs(1); |
| 209 | const maybe = args.get(0)._initMaybe(); |
| 210 | maybe.name = 'jsg::Optional'; |
| 211 | const optionsStructure = maybe._initValue()._initStructure(); |
| 212 | optionsStructure.name = 'ThingOptions'; |
| 213 | optionsStructure.fullyQualifiedName = 'workerd::api::ThingOptions'; |
| 214 | } |
| 215 | let returnStructure = iterator._initReturnType()._initStructure(); |
| 216 | returnStructure.name = 'ThingIterator'; |
| 217 | returnStructure.fullyQualifiedName = 'workerd::api::ThingIterator'; |
| 218 | |
| 219 | structure.asyncIterable = true; |
| 220 | const asyncIterator = structure._initAsyncIterator(); |
| 221 | { |
| 222 | const args = asyncIterator._initArgs(1); |
| 223 | const maybe = args.get(0)._initMaybe(); |
| 224 | maybe.name = 'jsg::Optional'; |
| 225 | const optionsStructure = maybe._initValue()._initStructure(); |
| 226 | optionsStructure.name = 'AsyncThingOptions'; |
| 227 | optionsStructure.fullyQualifiedName = 'workerd::api::AsyncThingOptions'; |
| 228 | } |
| 229 | returnStructure = asyncIterator._initReturnType()._initStructure(); |
| 230 | returnStructure.name = 'AsyncThingIterator'; |
| 231 | returnStructure.fullyQualifiedName = 'workerd::api::AsyncThingIterator'; |
| 232 | |
| 233 | assert.strictEqual( |
| 234 | printNode(createStructureNode(structure, { asClass: false })), |
| 235 | `interface Iterators { |
| 236 | [Symbol.iterator](param0?: ThingOptions): ThingIterator; |
| 237 | [Symbol.asyncIterator](param0?: AsyncThingOptions): AsyncThingIterator; |
| 238 | }` |
| 239 | ); |
| 240 | assert.strictEqual( |
| 241 | printNode(createStructureNode(structure, { asClass: true })), |
| 242 | `declare abstract class Iterators { |
| 243 | [Symbol.iterator](param0?: ThingOptions): ThingIterator; |
| 244 | [Symbol.asyncIterator](param0?: AsyncThingOptions): AsyncThingIterator; |
| 245 | }` |
| 246 | ); |
| 247 | }); |
| 248 | |
| 249 | test('createStructureNode: constructors', () => { |
| 250 | const structure = new Message().initRoot(Structure); |
| 251 | structure.name = 'Constructor'; |
| 252 | structure.fullyQualifiedName = 'workerd::api::Constructor'; |
| 253 | |
| 254 | const members = structure._initMembers(1); |
| 255 | |
| 256 | const constructor = members.get(0)._initConstructor(); |
| 257 | { |
| 258 | const args = constructor._initArgs(4); |
| 259 | let maybe = args.get(0)._initMaybe(); |
| 260 | maybe.name = 'jsg::Optional'; |
| 261 | maybe._initValue().boolt = true; |
| 262 | args.get(1)._initString().name = 'kj::String'; |
| 263 | args.get(2)._initPromise()._initValue().voidt = true; |
| 264 | maybe = args.get(3)._initMaybe(); |
| 265 | maybe.name = 'jsg::Optional'; |
| 266 | maybe._initValue()._initNumber().name = 'int'; |
| 267 | } |
| 268 | |
| 269 | assert.strictEqual( |
| 270 | printNode(createStructureNode(structure, { asClass: true })), |
| 271 | `declare class Constructor { |
| 272 | constructor(param0: boolean | undefined, param1: string, param2: Promise<any>, param3?: number); |
| 273 | }` |
| 274 | ); |
| 275 | }); |
| 276 | |
| 277 | test('createStructureNode: extends', () => { |
| 278 | const structure = new Message().initRoot(Structure); |
| 279 | structure.name = 'Extends'; |
| 280 | structure.fullyQualifiedName = 'workerd::api::Extends'; |
| 281 | |
| 282 | const extendsStructure = structure._initExtends()._initStructure(); |
| 283 | extendsStructure.name = 'Base'; |
| 284 | extendsStructure.fullyQualifiedName = 'workerd::api::Base'; |
| 285 | |
| 286 | assert.strictEqual( |
| 287 | printNode(createStructureNode(structure, { asClass: true })), |
| 288 | `declare abstract class Extends extends Base { |
| 289 | }` |
| 290 | ); |
| 291 | }); |