github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/protocol/typescript/code.ts (about) 1 /* eslint-disable no-useless-return */ 2 // read files from vscode-languageserver-node, and generate Go rpc stubs 3 // and data definitions. (and maybe someday unmarshaling code) 4 5 // The output is 3 files, tsprotocol.go contains the type definitions 6 // while tsclient.go and tsserver.go contain the LSP API and stub. An LSP server 7 // uses both APIs. To read the code, start in this file's main() function. 8 9 // The code is rich in heuristics and special cases, some of which are to avoid 10 // extensive changes to gopls, and some of which are due to the mismatch between 11 // typescript and Go types. In particular, there is no Go equivalent to union 12 // types, so each case ought to be considered separately. The Go equivalent of A 13 // & B could frequently be struct{A;B;}, or it could be the equivalent type 14 // listing all the members of A and B. Typically the code uses the former, but 15 // especially if A and B have elements with the same name, it does a version of 16 // the latter. ClientCapabilities has to be expanded, and ServerCapabilities is 17 // expanded to make the generated code easier to read. 18 19 // for us typescript ignorati, having an import makes this file a module 20 import * as fs from 'fs'; 21 import * as ts from 'typescript'; 22 import * as u from './util'; 23 import { constName, getComments, goName, loc, strKind } from './util'; 24 25 var program: ts.Program; 26 27 function parse() { 28 // this won't complain if some fnames don't exist 29 program = ts.createProgram( 30 u.fnames, 31 { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS }); 32 program.getTypeChecker(); // finish type checking and assignment 33 } 34 35 // ----- collecting information for RPCs 36 let req = new Map<string, ts.NewExpression>(); // requests 37 let not = new Map<string, ts.NewExpression>(); // notifications 38 let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>(); // req, resp types 39 let receives = new Map<string, 'server' | 'client'>(); // who receives it 40 let rpcTypes = new Set<string>(); // types seen in the rpcs 41 42 function findRPCs(node: ts.Node) { 43 if (!ts.isModuleDeclaration(node)) { 44 return; 45 } 46 if (!ts.isIdentifier(node.name)) { 47 throw new Error( 48 `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`); 49 } 50 let reqnot = req; 51 let v = node.name.getText(); 52 if (v.endsWith('Notification')) reqnot = not; 53 else if (!v.endsWith('Request')) return; 54 55 if (!ts.isModuleBlock(node.body)) { 56 throw new Error( 57 `expected ModuleBlock got ${strKind(node.body)} at ${loc(node)}`); 58 } 59 let x: ts.ModuleBlock = node.body; 60 // The story is to expect const method = 'textDocument/implementation' 61 // const type = new ProtocolRequestType<...>(method) 62 // but the method may be an explicit string 63 let rpc: string = ''; 64 let newNode: ts.NewExpression; 65 for (let i = 0; i < x.statements.length; i++) { 66 const uu = x.statements[i]; 67 if (!ts.isVariableStatement(uu)) continue; 68 const dl: ts.VariableDeclarationList = uu.declarationList; 69 if (dl.declarations.length != 1) 70 throw new Error(`expected a single decl at ${loc(dl)}`); 71 const decl: ts.VariableDeclaration = dl.declarations[0]; 72 const name = decl.name.getText(); 73 // we want the initializers 74 if (name == 'method') { // mostly StringLiteral but NoSubstitutionTemplateLiteral in protocol.semanticTokens.ts 75 if (!ts.isStringLiteral(decl.initializer)) { 76 if (!ts.isNoSubstitutionTemplateLiteral(decl.initializer)) { 77 console.log(`81: ${decl.initializer.getText()}`); 78 throw new Error(`expect StringLiteral at ${loc(decl)} got ${strKind(decl.initializer)}`); 79 } 80 } 81 rpc = decl.initializer.getText(); 82 } 83 else if (name == 'type') { // NewExpression 84 if (!ts.isNewExpression(decl.initializer)) 85 throw new Error(`89 expected new at ${loc(decl)}`); 86 const nn: ts.NewExpression = decl.initializer; 87 newNode = nn; 88 const mtd = nn.arguments[0]; 89 if (ts.isStringLiteral(mtd)) rpc = mtd.getText(); 90 switch (nn.typeArguments.length) { 91 case 1: // exit 92 ptypes.set(rpc, [nn.typeArguments[0], null]); 93 break; 94 case 2: // notifications 95 ptypes.set(rpc, [nn.typeArguments[0], null]); 96 break; 97 case 4: // request with no parameters 98 ptypes.set(rpc, [null, nn.typeArguments[0]]); 99 break; 100 case 5: // request req, resp, partial(?) 101 ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]]); 102 break; 103 default: 104 throw new Error(`${nn.typeArguments?.length} at ${loc(nn)}`); 105 } 106 } 107 } 108 if (rpc == '') throw new Error(`112 no name found at ${loc(x)}`); 109 // remember the implied types 110 const [a, b] = ptypes.get(rpc); 111 const add = function (n: ts.Node) { 112 rpcTypes.add(goName(n.getText())); 113 }; 114 underlying(a, add); 115 underlying(b, add); 116 rpc = rpc.substring(1, rpc.length - 1); // 'exit' 117 reqnot.set(rpc, newNode); 118 } 119 120 function setReceives() { 121 // mark them all as server, then adjust the client ones. 122 // it would be nice to have some independent check on this 123 // (this logic fails if the server ever sends $/canceRequest 124 // or $/progress) 125 req.forEach((_, k) => { receives.set(k, 'server'); }); 126 not.forEach((_, k) => { receives.set(k, 'server'); }); 127 receives.set('window/showMessage', 'client'); 128 receives.set('window/showMessageRequest', 'client'); 129 receives.set('window/logMessage', 'client'); 130 receives.set('telemetry/event', 'client'); 131 receives.set('client/registerCapability', 'client'); 132 receives.set('client/unregisterCapability', 'client'); 133 receives.set('workspace/workspaceFolders', 'client'); 134 receives.set('workspace/configuration', 'client'); 135 receives.set('workspace/applyEdit', 'client'); 136 receives.set('textDocument/publishDiagnostics', 'client'); 137 receives.set('window/workDoneProgress/create', 'client'); 138 receives.set('window/showDocument', 'client'); 139 receives.set('$/progress', 'client'); 140 // a small check 141 receives.forEach((_, k) => { 142 if (!req.get(k) && !not.get(k)) throw new Error(`145 missing ${k}}`); 143 if (req.get(k) && not.get(k)) throw new Error(`146 dup ${k}`); 144 }); 145 } 146 147 type DataKind = 'module' | 'interface' | 'alias' | 'enum' | 'class'; 148 149 interface Data { 150 kind: DataKind; 151 me: ts.Node; // root node for this type 152 name: string; // Go name 153 origname: string; // their name 154 generics: ts.NodeArray<ts.TypeParameterDeclaration>; 155 as: ts.NodeArray<ts.HeritageClause>; // inheritance 156 // Interface 157 properties: ts.NodeArray<ts.PropertySignature> 158 alias: ts.TypeNode; // type alias 159 // module 160 statements: ts.NodeArray<ts.Statement>; 161 enums: ts.NodeArray<ts.EnumMember>; 162 // class 163 members: ts.NodeArray<ts.PropertyDeclaration>; 164 } 165 function newData(n: ts.Node, nm: string, k: DataKind, origname: string): Data { 166 return { 167 kind: k, 168 me: n, name: goName(nm), origname: origname, 169 generics: ts.factory.createNodeArray<ts.TypeParameterDeclaration>(), 170 as: ts.factory.createNodeArray<ts.HeritageClause>(), 171 properties: ts.factory.createNodeArray<ts.PropertySignature>(), alias: undefined, 172 statements: ts.factory.createNodeArray<ts.Statement>(), 173 enums: ts.factory.createNodeArray<ts.EnumMember>(), 174 members: ts.factory.createNodeArray<ts.PropertyDeclaration>(), 175 }; 176 } 177 178 // for debugging, produce a skeleton description 179 function strData(d: Data): string { 180 if (!d) { return 'nil'; } 181 const f = function (na: ts.NodeArray<any>): number { 182 return na.length; 183 }; 184 const nm = d.name == d.origname ? `${d.name}` : `${d.name}/${d.origname}`; 185 return `g:${f(d.generics)} a:${f(d.as)} p:${f(d.properties)} s:${f(d.statements)} e:${f(d.enums)} m:${f(d.members)} a:${d.alias !== undefined} D(${nm}) k:${d.kind}`; 186 } 187 188 let data = new Map<string, Data>(); // parsed data types 189 let seenTypes = new Map<string, Data>(); // type names we've seen 190 let extraTypes = new Map<string, string[]>(); // to avoid struct params 191 192 function setData(nm: string, d: Data) { 193 const v = data.get(nm); 194 if (!v) { 195 data.set(nm, d); 196 return; 197 } 198 // if there are multiple definitions of the same name, decide what to do. 199 // For now the choices are only aliases and modules 200 // alias is preferred unless the constant values are needed 201 if (nm === 'PrepareSupportDefaultBehavior') { 202 // want the alias, as we're going to change the type and can't afford a constant 203 if (d.kind === 'alias') data.set(nm, d); 204 else if (v.kind == 'alias') data.set(nm, v); 205 else throw new Error(`208 ${d.kind} ${v.kind}`); 206 return; 207 } 208 if (nm === 'CodeActionKind') { 209 // want the module, need the constants 210 if (d.kind === 'module') data.set(nm, d); 211 else if (v.kind === 'module') data.set(nm, v); 212 else throw new Error(`215 ${d.kind} ${v.kind}`); 213 } 214 if (v.kind === 'alias' && d.kind !== 'alias') return; 215 if (d.kind === 'alias' && v.kind !== 'alias') { 216 data.set(nm, d); 217 return; 218 } 219 if (v.kind === 'alias' && d.kind === 'alias') return; 220 // protocol/src/common/protocol.foldingRange.ts 44: 1 (39: 2) and 221 // types/src/main.ts 397: 1 (392: 2) 222 // for FoldingRangeKind 223 if (d.me.getText() === v.me.getText()) return; 224 // error messages for an unexpected case 225 console.log(`228 ${strData(v)} ${loc(v.me)} for`); 226 console.log(`229 ${v.me.getText().replace(/\n/g, '\\n')}`); 227 console.log(`230 ${strData(d)} ${loc(d.me)}`); 228 console.log(`231 ${d.me.getText().replace(/\n/g, '\\n')}`); 229 throw new Error(`232 setData found ${v.kind} for ${d.kind}`); 230 } 231 232 // look at top level data definitions 233 function genTypes(node: ts.Node) { 234 // Ignore top-level items that can't produce output 235 if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) || 236 ts.isImportDeclaration(node) || ts.isVariableStatement(node) || 237 ts.isExportDeclaration(node) || ts.isEmptyStatement(node) || 238 ts.isExportAssignment(node) || ts.isImportEqualsDeclaration(node) || 239 ts.isBlock(node) || node.kind == ts.SyntaxKind.EndOfFileToken) { 240 return; 241 } 242 if (ts.isInterfaceDeclaration(node)) { 243 const v: ts.InterfaceDeclaration = node; 244 // need to check the members, many of which are disruptive 245 let mems: ts.PropertySignature[] = []; 246 const f = function (t: ts.TypeElement) { 247 if (ts.isPropertySignature(t)) { 248 mems.push(t); 249 } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) { 250 return; 251 } else if (ts.isIndexSignatureDeclaration(t)) { 252 // probably safe to ignore these 253 // [key: string]: boolean | number | string | undefined; 254 // and InitializeResult: [custom: string]: any;] 255 } else 256 throw new Error(`259 unexpected ${strKind(t)}`); 257 }; 258 v.members.forEach(f); 259 if (mems.length == 0 && !v.heritageClauses && 260 v.name.getText() != 'InitializedParams') { 261 return; // Don't seem to need any of these [Logger, PipTransport, ...] 262 } 263 // Found one we want 264 let x = newData(v, goName(v.name.getText()), 'interface', v.name.getText()); 265 x.properties = ts.factory.createNodeArray<ts.PropertySignature>(mems); 266 if (v.typeParameters) x.generics = v.typeParameters; 267 if (v.heritageClauses) x.as = v.heritageClauses; 268 if (x.generics.length > 1) { // Unneeded 269 // Item interface Item<K, V>... 270 return; 271 } 272 if (data.has(x.name)) { // modifying one we've seen 273 x = dataChoose(x, data.get(x.name)); 274 } 275 setData(x.name, x); 276 } else if (ts.isTypeAliasDeclaration(node)) { 277 const v: ts.TypeAliasDeclaration = node; 278 let x = newData(v, v.name.getText(), 'alias', v.name.getText()); 279 x.alias = v.type; 280 // if type is a union of constants, we (mostly) don't want it 281 // (at the top level) 282 // Unfortunately this is false for TraceValues 283 if (ts.isUnionTypeNode(v.type) && 284 v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) { 285 if (x.name != 'TraceValues') return; 286 } 287 if (v.typeParameters) { 288 x.generics = v.typeParameters; 289 } 290 if (data.has(x.name)) x = dataChoose(x, data.get(x.name)); 291 if (x.generics.length > 1) { 292 return; 293 } 294 setData(x.name, x); 295 } else if (ts.isModuleDeclaration(node)) { 296 const v: ts.ModuleDeclaration = node; 297 if (!ts.isModuleBlock(v.body)) { 298 throw new Error(`${loc(v)} not ModuleBlock, but ${strKind(v.body)}`); 299 } 300 const b: ts.ModuleBlock = v.body; 301 var s: ts.Statement[] = []; 302 // we don't want most of these 303 const fx = function (x: ts.Statement) { 304 if (ts.isFunctionDeclaration(x)) { 305 return; 306 } 307 if (ts.isTypeAliasDeclaration(x) || ts.isModuleDeclaration(x)) { 308 return; 309 } 310 if (!ts.isVariableStatement(x)) 311 throw new Error( 312 `315 expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`); 313 if (hasNewExpression(x)) { 314 return; 315 } 316 s.push(x); 317 }; 318 b.statements.forEach(fx); 319 if (s.length == 0) { 320 return; 321 } 322 let m = newData(node, v.name.getText(), 'module', v.name.getText()); 323 m.statements = ts.factory.createNodeArray<ts.Statement>(s); 324 if (data.has(m.name)) m = dataChoose(m, data.get(m.name)); 325 setData(m.name, m); 326 } else if (ts.isEnumDeclaration(node)) { 327 const nm = node.name.getText(); 328 let v = newData(node, nm, 'enum', node.name.getText()); 329 v.enums = node.members; 330 if (data.has(nm)) { 331 v = dataChoose(v, data.get(nm)); 332 } 333 setData(nm, v); 334 } else if (ts.isClassDeclaration(node)) { 335 const v: ts.ClassDeclaration = node; 336 var d: ts.PropertyDeclaration[] = []; 337 const wanted = function (c: ts.ClassElement): string { 338 if (ts.isConstructorDeclaration(c)) { 339 return ''; 340 } 341 if (ts.isMethodDeclaration(c)) { 342 return ''; 343 } 344 if (ts.isGetAccessor(c)) { 345 return ''; 346 } 347 if (ts.isSetAccessor(c)) { 348 return ''; 349 } 350 if (ts.isPropertyDeclaration(c)) { 351 d.push(c); 352 return strKind(c); 353 } 354 throw new Error(`Class decl ${strKind(c)} `); 355 }; 356 v.members.forEach((c) => wanted(c)); 357 if (d.length == 0) { 358 return; 359 } // don't need it 360 let c = newData(v, v.name.getText(), 'class', v.name.getText()); 361 c.members = ts.factory.createNodeArray<ts.PropertyDeclaration>(d); 362 if (v.typeParameters) { 363 c.generics = v.typeParameters; 364 } 365 if (c.generics.length > 1) { 366 return; 367 } 368 if (v.heritageClauses) { 369 c.as = v.heritageClauses; 370 } 371 if (data.has(c.name)) 372 throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`); 373 setData(c.name, c); 374 } else { 375 throw new Error(`378 unexpected ${strKind(node)} ${loc(node)} `); 376 } 377 } 378 379 // Typescript can accumulate, but this chooses one or the other 380 function dataChoose(a: Data, b: Data): Data { 381 // maybe they are textually identical? (e.g., FoldingRangeKind) 382 const [at, bt] = [a.me.getText(), b.me.getText()]; 383 if (at == bt) { 384 return a; 385 } 386 switch (a.name) { 387 case 'InitializeError': 388 case 'CompletionItemTag': 389 case 'SymbolTag': 390 case 'CodeActionKind': 391 case 'Integer': 392 case 'Uinteger': 393 case 'Decimal': 394 // want the Module, if anything 395 return a.statements.length > 0 ? a : b; 396 case 'CancellationToken': 397 case 'CancellationStrategy': 398 // want the Interface 399 return a.properties.length > 0 ? a : b; 400 case 'TextDocumentContentChangeEvent': // almost the same 401 case 'TokenFormat': 402 case 'PrepareSupportDefaultBehavior': 403 return a; 404 } 405 console.log( 406 `409 ${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`); 407 throw new Error(`410 Fix dataChoose for ${a.name}`); 408 } 409 410 // is a node an ancestor of a NewExpression 411 function hasNewExpression(n: ts.Node): boolean { 412 let ans = false; 413 n.forEachChild((n: ts.Node) => { 414 if (ts.isNewExpression(n)) ans = true; 415 }); 416 return ans; 417 } 418 419 function checkOnce() { 420 // Data for all the rpc types? 421 rpcTypes.forEach(s => { 422 if (!data.has(s)) throw new Error(`checkOnce, ${s}?`); 423 }); 424 } 425 426 // helper function to find underlying types 427 // eslint-disable-next-line no-unused-vars 428 function underlying(n: ts.Node | undefined, f: (n: ts.Node) => void) { 429 if (!n) return; 430 const ff = function (n: ts.Node) { 431 underlying(n, f); 432 }; 433 if (ts.isIdentifier(n)) { 434 f(n); 435 } else if ( 436 n.kind == ts.SyntaxKind.StringKeyword || 437 n.kind == ts.SyntaxKind.NumberKeyword || 438 n.kind == ts.SyntaxKind.AnyKeyword || 439 n.kind == ts.SyntaxKind.UnknownKeyword || 440 n.kind == ts.SyntaxKind.NullKeyword || 441 n.kind == ts.SyntaxKind.BooleanKeyword || 442 n.kind == ts.SyntaxKind.ObjectKeyword || 443 n.kind == ts.SyntaxKind.VoidKeyword) { 444 // nothing to do 445 } else if (ts.isTypeReferenceNode(n)) { 446 f(n.typeName); 447 } else if (ts.isArrayTypeNode(n)) { 448 underlying(n.elementType, f); 449 } else if (ts.isHeritageClause(n)) { 450 n.types.forEach(ff); 451 } else if (ts.isExpressionWithTypeArguments(n)) { 452 underlying(n.expression, f); 453 } else if (ts.isPropertySignature(n)) { 454 underlying(n.type, f); 455 } else if (ts.isTypeLiteralNode(n)) { 456 n.members.forEach(ff); 457 } else if (ts.isUnionTypeNode(n) || ts.isIntersectionTypeNode(n)) { 458 n.types.forEach(ff); 459 } else if (ts.isIndexSignatureDeclaration(n)) { 460 underlying(n.type, f); 461 } else if (ts.isParenthesizedTypeNode(n)) { 462 underlying(n.type, f); 463 } else if ( 464 ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) || 465 ts.isTupleTypeNode(n)) { 466 // we only see these in moreTypes, but they are handled elsewhere 467 } else if (ts.isEnumMember(n)) { 468 if (ts.isStringLiteral(n.initializer)) return; 469 throw new Error(`472 EnumMember ${strKind(n.initializer)} ${n.name.getText()}`); 470 } else { 471 throw new Error(`474 saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`); 472 } 473 } 474 475 // find all the types implied by seenTypes. 476 // Simplest way to the transitive closure is to stabilize the size of seenTypes 477 // but it is slow 478 function moreTypes() { 479 const extra = function (s: string) { 480 if (!data.has(s)) throw new Error(`moreTypes needs ${s}`); 481 seenTypes.set(s, data.get(s)); 482 }; 483 rpcTypes.forEach(extra); // all the types needed by the rpcs 484 // needed in enums.go (or elsewhere) 485 extra('InitializeError'); 486 extra('WatchKind'); 487 extra('FoldingRangeKind'); 488 // not sure why these weren't picked up 489 extra('DidChangeWatchedFilesRegistrationOptions'); 490 extra('WorkDoneProgressBegin'); 491 extra('WorkDoneProgressReport'); 492 extra('WorkDoneProgressEnd'); 493 let old = 0; 494 do { 495 old = seenTypes.size; 496 497 const m = new Map<string, Data>(); 498 const add = function (n: ts.Node) { 499 const nm = goName(n.getText()); 500 if (seenTypes.has(nm) || m.has(nm)) return; 501 if (data.get(nm)) { 502 m.set(nm, data.get(nm)); 503 } 504 }; 505 // expect all the heritage clauses have single Identifiers 506 const h = function (n: ts.Node) { 507 underlying(n, add); 508 }; 509 const f = function (x: ts.NodeArray<ts.Node>) { 510 x.forEach(h); 511 }; 512 seenTypes.forEach((d: Data) => d && f(d.as)); 513 // find the types in the properties 514 seenTypes.forEach((d: Data) => d && f(d.properties)); 515 // and in the alias and in the statements and in the enums 516 seenTypes.forEach((d: Data) => d && underlying(d.alias, add)); 517 seenTypes.forEach((d: Data) => d && f(d.statements)); 518 seenTypes.forEach((d: Data) => d && f(d.enums)); 519 m.forEach((d, k) => seenTypes.set(k, d)); 520 } 521 while (seenTypes.size != old) 522 ; 523 } 524 525 function cleanData() { // middle pass 526 // seenTypes contains all the top-level types. 527 seenTypes.forEach((d) => { 528 if (d.kind == 'alias') mergeAlias(d); 529 }); 530 } 531 532 function sameType(a: ts.TypeNode, b: ts.TypeNode): boolean { 533 if (a.kind !== b.kind) return false; 534 if (a.kind === ts.SyntaxKind.BooleanKeyword) return true; 535 if (a.kind === ts.SyntaxKind.StringKeyword) return true; 536 if (ts.isTypeReferenceNode(a) && ts.isTypeReferenceNode(b) && 537 a.typeName.getText() === b.typeName.getText()) return true; 538 if (ts.isArrayTypeNode(a) && ts.isArrayTypeNode(b)) return sameType(a.elementType, b.elementType); 539 if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) { 540 if (a.members.length !== b.members.length) return false; 541 if (a.members.length === 1) return a.members[0].name.getText() === b.members[0].name.getText(); 542 if (loc(a) === loc(b)) return true; 543 } 544 throw new Error(`544 sameType? ${strKind(a)} ${strKind(b)} ${a.getText()}`); 545 } 546 type CreateMutable<Type> = { 547 -readonly [Property in keyof Type]: Type[Property]; 548 }; 549 type propMap = Map<string, ts.PropertySignature>; 550 function propMapSet(pm: propMap, name: string, v: ts.PropertySignature) { 551 if (!pm.get(name)) { 552 try { getComments(v); } catch (e) { console.log(`552 ${name} ${e}`); } 553 pm.set(name, v); 554 return; 555 } 556 const a = pm.get(name).type; 557 const b = v.type; 558 if (sameType(a, b)) { 559 return; 560 } 561 if (ts.isTypeReferenceNode(a) && ts.isTypeLiteralNode(b)) { 562 const x = mergeTypeRefLit(a, b); 563 const fake: CreateMutable<ts.PropertySignature> = v; 564 fake['type'] = x; 565 check(fake as ts.PropertySignature, '565'); 566 pm.set(name, fake as ts.PropertySignature); 567 return; 568 } 569 if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) { 570 const x = mergeTypeLitLit(a, b); 571 const fake: CreateMutable<ts.PropertySignature> = v; 572 fake['type'] = x; 573 check(fake as ts.PropertySignature, '578'); 574 pm.set(name, fake as ts.PropertySignature); 575 return; 576 } 577 console.log(`577 ${pm.get(name).getText()}\n${v.getText()}`); 578 throw new Error(`578 should merge ${strKind(a)} and ${strKind(b)} for ${name}`); 579 } 580 function addToProperties(pm: propMap, tn: ts.TypeNode | undefined, prefix = '') { 581 if (!tn) return; 582 if (ts.isTypeReferenceNode(tn)) { 583 const d = seenTypes.get(goName(tn.typeName.getText())); 584 if (tn.typeName.getText() === 'T') return; 585 if (!d) throw new Error(`584 ${tn.typeName.getText()} not found`); 586 if (d.properties.length === 0 && d.alias === undefined) return; 587 if (d.alias !== undefined) { 588 if (ts.isIntersectionTypeNode(d.alias)) { 589 d.alias.types.forEach((tn) => addToProperties(pm, tn, prefix)); // prefix? 590 return; 591 } 592 } 593 d.properties.forEach((ps) => { 594 const name = `${prefix}.${ps.name.getText()}`; 595 propMapSet(pm, name, ps); 596 addToProperties(pm, ps.type, name); 597 }); 598 } else if (strKind(tn) === 'TypeLiteral') { 599 if (!ts.isTypeLiteralNode(tn)) new Error(`598 ${strKind(tn)}`); 600 tn.forEachChild((child: ts.Node) => { 601 if (!ts.isPropertySignature(child)) throw new Error(`600 ${strKind(child)}`); 602 const name = `${prefix}.${child.name.getText()}`; 603 propMapSet(pm, name, child); 604 addToProperties(pm, child.type, name); 605 }); 606 } 607 } 608 function deepProperties(d: Data): propMap | undefined { 609 let properties: propMap = new Map<string, ts.PropertySignature>(); 610 if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return undefined; 611 d.alias.types.forEach((ts) => addToProperties(properties, ts)); 612 return properties; 613 } 614 615 function mergeAlias(d: Data) { 616 const props = deepProperties(d); 617 if (!props) return; // nothing merged 618 // now each element of props should have length 1 619 // change d to merged, toss its alias field, fill in its properties 620 const v: ts.PropertySignature[] = []; 621 props.forEach((ps, nm) => { 622 const xlen = nm.split('.').length; 623 if (xlen !== 2) return; // not top-level 624 v.push(ps); 625 }); 626 d.kind = 'interface'; 627 d.alias = undefined; 628 d.properties = ts.factory.createNodeArray(v); 629 } 630 631 function mergeTypeLitLit(a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode { 632 const v = new Map<string, ts.TypeElement>(); // avoid duplicates 633 a.members.forEach((te) => v.set(te.name.getText(), te)); 634 b.members.forEach((te) => v.set(te.name.getText(), te)); 635 const x: ts.TypeElement[] = []; 636 v.forEach((te) => x.push(te)); 637 const fake: CreateMutable<ts.TypeLiteralNode> = a; 638 fake['members'] = ts.factory.createNodeArray(x); 639 check(fake as ts.TypeLiteralNode, '643'); 640 return fake as ts.TypeLiteralNode; 641 } 642 643 function mergeTypeRefLit(a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode { 644 const d = seenTypes.get(goName(a.typeName.getText())); 645 if (!d) throw new Error(`644 name ${a.typeName.getText()} not found`); 646 const typ = d.me; 647 if (!ts.isInterfaceDeclaration(typ)) throw new Error(`646 got ${strKind(typ)} not InterfaceDecl`); 648 const v = new Map<string, ts.TypeElement>(); // avoid duplicates 649 typ.members.forEach((te) => v.set(te.name.getText(), te)); 650 b.members.forEach((te) => v.set(te.name.getText(), te)); 651 const x: ts.TypeElement[] = []; 652 v.forEach((te) => x.push(te)); 653 654 const w = ts.factory.createNodeArray(x); 655 const fk: CreateMutable<ts.TypeLiteralNode> = b; 656 fk['members'] = w; 657 (fk['members'] as { pos: number })['pos'] = b.members.pos; 658 (fk['members'] as { end: number })['end'] = b.members.end; 659 check(fk as ts.TypeLiteralNode, '662'); 660 return fk as ts.TypeLiteralNode; 661 } 662 663 // check that constructed nodes still have associated text 664 function check(n: ts.Node, loc: string) { 665 try { getComments(n); } catch (e) { console.log(`check at ${loc} ${e}`); } 666 try { n.getText(); } catch (e) { console.log(`text check at ${loc}`); } 667 } 668 669 let typesOut = new Array<string>(); 670 let constsOut = new Array<string>(); 671 672 // generate Go types 673 function toGo(d: Data, nm: string) { 674 if (!d) return; // this is probably a generic T 675 if (d.name.startsWith('Inner') || d.name === 'WindowClientCapabilities') return; // removed by alias processing 676 if (d.name === 'Integer' || d.name === 'Uinteger') return; // unneeded 677 switch (d.kind) { 678 case 'alias': 679 goTypeAlias(d, nm); break; 680 case 'module': goModule(d, nm); break; 681 case 'enum': goEnum(d, nm); break; 682 case 'interface': goInterface(d, nm); break; 683 default: 684 throw new Error( 685 `672: more cases in toGo ${nm} ${d.kind}`); 686 } 687 } 688 689 // these fields need a * and are not covered by the code 690 // that calls isStructType. 691 var starred: [string, string][] = [ 692 ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'], 693 ['CodeAction', 'disabled'], 694 ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'], 695 ['Diagnostic', 'codeDescription'] 696 ]; 697 698 // generate Go code for an interface 699 function goInterface(d: Data, nm: string) { 700 let ans = `type ${goName(nm)} struct {\n`; 701 702 // generate the code for each member 703 const g = function (n: ts.PropertySignature) { 704 if (!ts.isPropertySignature(n)) 705 throw new Error(`expected PropertySignature got ${strKind(n)} `); 706 ans = ans.concat(getComments(n)); 707 const json = u.JSON(n); 708 let gt = goType(n.type, n.name.getText()); 709 if (gt == d.name) gt = '*' + gt; // avoid recursive types (SelectionRange) 710 // there are several cases where a * is needed 711 // (putting * in front of too many things breaks uses of CodeActionKind) 712 starred.forEach(([a, b]) => { 713 if (d.name == a && n.name.getText() == b) { 714 gt = '*' + gt; 715 } 716 }); 717 ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n'); 718 }; 719 d.properties.forEach(g); 720 // heritage clauses become embedded types 721 // check they are all Identifiers 722 const f = function (n: ts.ExpressionWithTypeArguments) { 723 if (!ts.isIdentifier(n.expression)) 724 throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `); 725 if (n.expression.getText() === 'Omit') return; // Type modification type 726 ans = ans.concat(goName(n.expression.getText()), '\n'); 727 }; 728 d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f)); 729 ans = ans.concat('}\n'); 730 typesOut.push(getComments(d.me)); 731 typesOut.push(ans); 732 } 733 734 // generate Go code for a module (const declarations) 735 // Generates type definitions, and named constants 736 function goModule(d: Data, nm: string) { 737 if (d.generics.length > 0 || d.as.length > 0) { 738 throw new Error(`743 goModule: unexpected for ${nm} 739 `); 740 } 741 // all the statements should be export const <id>: value 742 // or value = value 743 // They are VariableStatements with x.declarationList having a single 744 // VariableDeclaration 745 let isNumeric = false; 746 const f = function (n: ts.Statement, i: number) { 747 if (!ts.isVariableStatement(n)) { 748 throw new Error(`753 ${nm} ${i} expected VariableStatement, 749 got ${strKind(n)}`); 750 } 751 const c = getComments(n); 752 const v = n.declarationList.declarations[0]; // only one 753 754 if (!v.initializer) 755 throw new Error(`760 no initializer ${nm} ${i} ${v.name.getText()}`); 756 isNumeric = strKind(v.initializer) == 'NumericLiteral'; 757 if (c != '') constsOut.push(c); // no point if there are no comments 758 // There are duplicates. 759 const cname = constName(goName(v.name.getText()), nm); 760 let val = v.initializer.getText(); 761 val = val.split('\'').join('"'); // useless work for numbers 762 constsOut.push(`${cname} ${nm} = ${val}`); 763 }; 764 d.statements.forEach(f); 765 typesOut.push(getComments(d.me)); 766 // Or should they be type aliases? 767 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`); 768 } 769 770 // generate Go code for an enum. Both types and named constants 771 function goEnum(d: Data, nm: string) { 772 let isNumeric = false; 773 const f = function (v: ts.EnumMember, j: number) { // same as goModule 774 if (!v.initializer) 775 throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`); 776 isNumeric = strKind(v.initializer) == 'NumericLiteral'; 777 const c = getComments(v); 778 const cname = constName(goName(v.name.getText()), nm); 779 let val = v.initializer.getText(); 780 val = val.split('\'').join('"'); // replace quotes. useless work for numbers 781 constsOut.push(`${c}${cname} ${nm} = ${val}`); 782 }; 783 d.enums.forEach(f); 784 typesOut.push(getComments(d.me)); 785 // Or should they be type aliases? 786 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`); 787 } 788 789 // generate code for a type alias 790 function goTypeAlias(d: Data, nm: string) { 791 if (d.as.length != 0 || d.generics.length != 0) { 792 if (nm != 'ServerCapabilities') 793 throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`); 794 } 795 typesOut.push(getComments(d.me)); 796 // d.alias doesn't seem to have comments 797 let aliasStr = goName(nm) == 'DocumentURI' ? ' ' : ' = '; 798 if (nm == 'PrepareSupportDefaultBehavior') { 799 // code-insiders is sending a bool, not a number. PJW: check this after Feb/2021 800 // (and gopls never looks at it anyway) 801 typesOut.push(`type ${goName(nm)}${aliasStr}interface{}\n`); 802 return; 803 } 804 typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`); 805 } 806 807 // return a go type and maybe an assocated javascript tag 808 function goType(n: ts.TypeNode | undefined, nm: string): string { 809 if (!n) throw new Error(`goType undefined for ${nm}`); 810 if (n.getText() == 'T') return 'interface{}'; // should check it's generic 811 if (ts.isTypeReferenceNode(n)) { 812 // DocumentDiagnosticReportKind.unChanged (or .new) value is "new" or "unChanged" 813 if (n.getText().startsWith('DocumentDiagnostic')) return 'string'; 814 switch (n.getText()) { 815 case 'integer': return 'int32'; 816 case 'uinteger': return 'uint32'; 817 default: return goName(n.typeName.getText()); // avoid <T> 818 } 819 } else if (ts.isUnionTypeNode(n)) { 820 return goUnionType(n, nm); 821 } else if (ts.isIntersectionTypeNode(n)) { 822 return goIntersectionType(n, nm); 823 } else if (strKind(n) == 'StringKeyword') { 824 return 'string'; 825 } else if (strKind(n) == 'NumberKeyword') { 826 return 'float64'; 827 } else if (strKind(n) == 'BooleanKeyword') { 828 return 'bool'; 829 } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') { 830 return 'interface{}'; 831 } else if (strKind(n) == 'NullKeyword') { 832 return 'nil'; 833 } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') { 834 return 'void'; 835 } else if (strKind(n) == 'ObjectKeyword') { 836 return 'interface{}'; 837 } else if (ts.isArrayTypeNode(n)) { 838 if (nm === 'arguments') { 839 // Command and ExecuteCommandParams 840 return '[]json.RawMessage'; 841 } 842 return `[]${goType(n.elementType, nm)}`; 843 } else if (ts.isParenthesizedTypeNode(n)) { 844 return goType(n.type, nm); 845 } else if (ts.isLiteralTypeNode(n)) { 846 return strKind(n.literal) == 'StringLiteral' ? 'string' : 'float64'; 847 } else if (ts.isTypeLiteralNode(n)) { 848 // these are anonymous structs 849 const v = goTypeLiteral(n, nm); 850 return v; 851 } else if (ts.isTupleTypeNode(n)) { 852 if (n.getText() == '[number, number]') return '[]float64'; 853 throw new Error(`goType unexpected Tuple ${n.getText()}`); 854 } 855 throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`); 856 } 857 858 // The choice is uniform interface{}, or some heuristically assigned choice, 859 // or some better sytematic idea I haven't thought of. Using interface{} 860 // is, in practice, impossibly complex in the existing code. 861 function goUnionType(n: ts.UnionTypeNode, nm: string): string { 862 let help = `/*${n.getText()}*/`; // show the original as a comment 863 // There are some bad cases with newlines: 864 // range?: boolean | {\n }; 865 // full?: boolean | {\n /**\n * The server supports deltas for full documents.\n */\n delta?: boolean;\n } 866 // These are handled specially: 867 if (nm == 'range') help = help.replace(/\n/, ''); 868 if (nm == 'full' && help.indexOf('\n') != -1) { 869 help = '/*boolean | <elided struct>*/'; 870 } 871 // handle all the special cases 872 switch (n.types.length) { 873 case 2: { 874 const a = strKind(n.types[0]); 875 const b = strKind(n.types[1]); 876 if (a == 'NumberKeyword' && b == 'StringKeyword') { // ID 877 return `interface{} ${help}`; 878 } 879 // for null, b is not useful (LiternalType) 880 if (n.types[1].getText() === 'null') { 881 if (nm == 'textDocument/codeAction') { 882 // (Command | CodeAction)[] | null 883 return `[]CodeAction ${help}`; 884 } 885 let v = goType(n.types[0], 'a'); 886 return `${v} ${help}`; 887 } 888 if (a == 'BooleanKeyword') { // usually want bool 889 if (nm == 'codeActionProvider') return `interface{} ${help}`; 890 if (nm == 'renameProvider') return `interface{} ${help}`; 891 if (nm == 'full') return `interface{} ${help}`; // there's a struct 892 if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`; 893 return `${goType(n.types[0], 'b')} ${help}`; 894 } 895 if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`; 896 if (help.includes('InsertReplaceEdit') && n.types[0].getText() == 'TextEdit') { 897 return `*TextEdit ${help}`; 898 } 899 if (a == 'TypeReference') { 900 if (nm == 'edits') return `${goType(n.types[0], '901')} ${help}`; 901 if (a == b) return `interface{} ${help}`; 902 if (nm == 'code') return `interface{} ${help}`; 903 if (nm == 'editRange') return `${goType(n.types[0], '904')} ${help}`; 904 if (nm === 'location') return `${goType(n.types[0], '905')} ${help}`; 905 } 906 if (a == 'StringKeyword') return `string ${help}`; 907 if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') { 908 return `${goType(n.types[0], nm)}`; 909 } 910 if (a == 'TypeLiteral' && b === 'TypeLiteral') { 911 // DocumentDiagnosticReport 912 // the first one includes the second one 913 return `${goType(n.types[0], '9d')}`; 914 } 915 throw new Error(`911 ${nm}: a:${a}/${goType(n.types[0], '9a')} b:${b}/${goType(n.types[1], '9b')} ${loc(n)}`); 916 } 917 case 3: { 918 const aa = strKind(n.types[0]); 919 const bb = strKind(n.types[1]); 920 const cc = strKind(n.types[2]); 921 if (nm === 'workspace/symbol') return `${goType(n.types[0], '930')} ${help}`; 922 if (nm == 'DocumentFilter' || nm == 'NotebookDocumentFilter' || nm == 'TextDocumentFilter') { 923 // not really a union. the first is enough, up to a missing 924 // omitempty but avoid repetitious comments 925 return `${goType(n.types[0], 'g')}`; 926 } 927 if (nm == 'textDocument/documentSymbol') { 928 return `[]interface{} ${help}`; 929 } 930 if (aa == 'TypeReference' && bb == 'ArrayType' && (cc == 'NullKeyword' || cc === 'LiteralType')) { 931 return `${goType(n.types[0], 'd')} ${help}`; 932 } 933 if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') { 934 // should check that this is Hover.Contents 935 return `${goType(n.types[0], 'e')} ${help}`; 936 } 937 if (aa == 'ArrayType' && bb == 'TypeReference' && (cc == 'NullKeyword' || cc === 'LiteralType')) { 938 // check this is nm == 'textDocument/completion' 939 return `${goType(n.types[1], 'f')} ${help}`; 940 } 941 if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`; 942 // keep this for diagnosing unexpected interface{} results 943 // console.log(`931, interface{} for ${aa}/${goType(n.types[0], 'g')},${bb}/${goType(n.types[1], 'h')},${cc}/${goType(n.types[2], 'i')} ${nm}`); 944 break; 945 } 946 case 4: 947 if (nm == 'documentChanges') return `TextDocumentEdit ${help} `; 948 if (nm == 'textDocument/prepareRename') { 949 // these names have to be made unique 950 const genName = `${goName("prepareRename")}${extraTypes.size}Gn`; 951 extraTypes.set(genName, [`Range Range \`json:"range"\` 952 Placeholder string \`json:"placeholder"\``]); 953 return `${genName} ${help} `; 954 } 955 break; 956 case 8: // LSPany 957 break; 958 default: 959 throw new Error(`957 goUnionType len=${n.types.length} nm=${nm} ${n.getText()}`); 960 } 961 962 // Result will be interface{} with a comment 963 let isLiteral = true; 964 let literal = 'string'; 965 let res = 'interface{} /* '; 966 n.types.forEach((v: ts.TypeNode, i: number) => { 967 // might get an interface inside: 968 // (Command | CodeAction)[] | null 969 let m = goType(v, nm); 970 if (m.indexOf('interface') != -1) { 971 // avoid nested comments 972 m = m.split(' ')[0]; 973 } 974 m = m.split('\n').join('; '); // sloppy: struct{; 975 res = res.concat(`${i == 0 ? '' : ' | '}`, m); 976 if (!ts.isLiteralTypeNode(v)) isLiteral = false; 977 else literal = strKind(v.literal) == 'StringLiteral' ? 'string' : 'number'; 978 }); 979 if (!isLiteral) { 980 return res + '*/'; 981 } 982 // I don't think we get here 983 // trace?: 'off' | 'messages' | 'verbose' should get string 984 return `${literal} /* ${n.getText()} */`; 985 } 986 987 // some of the intersection types A&B are ok as struct{A;B;} and some 988 // could be expanded, and ClientCapabilites has to be expanded, 989 // at least for workspace. It's possible to check algorithmically, 990 // but much simpler just to check explicitly. 991 function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string { 992 if (nm == 'ClientCapabilities') return expandIntersection(n); 993 //if (nm == 'ServerCapabilities') return expandIntersection(n); // save for later consideration 994 let inner = ''; 995 n.types.forEach( 996 (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n'); }); 997 return `struct{ \n${inner}} `; 998 } 999 1000 // for each of the intersected types, extract its components (each will 1001 // have a Data with properties) extract the properties, and keep track 1002 // of them by name. The names that occur once can be output. The names 1003 // that occur more than once need to be combined. 1004 function expandIntersection(n: ts.IntersectionTypeNode): string { 1005 const bad = function (n: ts.Node, s: string) { 1006 return new Error(`expandIntersection ${strKind(n)} ${s}`); 1007 }; 1008 let props = new Map<string, ts.PropertySignature[]>(); 1009 for (const tp of n.types) { 1010 if (!ts.isTypeReferenceNode(tp)) throw bad(tp, 'A'); 1011 const d = data.get(goName(tp.typeName.getText())); 1012 for (const p of d.properties) { 1013 if (!ts.isPropertySignature(p)) throw bad(p, 'B'); 1014 let v = props.get(p.name.getText()) || []; 1015 v.push(p); 1016 props.set(p.name.getText(), v); 1017 } 1018 } 1019 let ans = 'struct {\n'; 1020 for (const [k, v] of Array.from(props)) { 1021 if (v.length == 1) { 1022 const a = v[0]; 1023 ans = ans.concat(getComments(a)); 1024 ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`); 1025 continue; 1026 } 1027 ans = ans.concat(`${goName(k)} struct {\n`); 1028 for (let i = 0; i < v.length; i++) { 1029 const a = v[i]; 1030 if (ts.isTypeReferenceNode(a.type)) { 1031 ans = ans.concat(getComments(a)); 1032 ans = ans.concat(goName(a.type.typeName.getText()), '\n'); 1033 } else if (ts.isTypeLiteralNode(a.type)) { 1034 if (a.type.members.length != 1) throw bad(a.type, 'C'); 1035 const b = a.type.members[0]; 1036 if (!ts.isPropertySignature(b)) throw bad(b, 'D'); 1037 ans = ans.concat(getComments(b)); 1038 ans = ans.concat( 1039 goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n'); 1040 } else { 1041 throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`); 1042 } 1043 } 1044 ans = ans.concat('}\n'); 1045 } 1046 ans = ans.concat('}\n'); 1047 return ans; 1048 } 1049 1050 // Does it make sense to use a pointer? 1051 function isStructType(te: ts.TypeNode): boolean { 1052 switch (strKind(te)) { 1053 case 'UnionType': // really need to know which type will be chosen 1054 case 'BooleanKeyword': 1055 case 'StringKeyword': 1056 case 'ArrayType': 1057 return false; 1058 case 'TypeLiteral': return false; // true makes for difficult compound constants 1059 // but think more carefully to understands why starred is needed. 1060 case 'TypeReference': { 1061 if (!ts.isTypeReferenceNode(te)) throw new Error(`1047 impossible ${strKind(te)}`); 1062 const d = seenTypes.get(goName(te.typeName.getText())); 1063 if (d === undefined || d.properties.length == 0) return false; 1064 if (d.properties.length > 1) return true; 1065 // alias or interface with a single property (The alias is Uinteger, which we ignore later) 1066 if (d.alias) return false; 1067 const x = d.properties[0].type; 1068 return isStructType(x); 1069 } 1070 default: throw new Error(`1055 indirectable> ${strKind(te)}`); 1071 } 1072 } 1073 1074 function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string { 1075 let ans: string[] = []; // in case we generate a new extra type 1076 let res = 'struct{\n'; // the actual answer usually 1077 const g = function (nx: ts.TypeElement) { 1078 // add the json, as in goInterface(). Strange inside union types. 1079 if (ts.isPropertySignature(nx)) { 1080 let json = u.JSON(nx); 1081 let typ = goType(nx.type, nx.name.getText()); 1082 // }/*\n*/`json:v` is not legal, the comment is a newline 1083 if (typ.includes('\n') && typ.indexOf('*/') === typ.length - 2) { 1084 typ = typ.replace(/\n\t*/g, ' '); 1085 } 1086 const v = getComments(nx) || ''; 1087 starred.forEach(([a, b]) => { 1088 if (a != nm || b != typ.toLowerCase()) return; 1089 typ = '*' + typ; 1090 json = json.substring(0, json.length - 2) + ',omitempty"`'; 1091 }); 1092 if (typ[0] !== '*' && isStructType(nx.type)) typ = '*' + typ; 1093 res = res.concat(`${v} ${goName(nx.name.getText())} ${typ}`, json, '\n'); 1094 ans.push(`${v}${goName(nx.name.getText())} ${typ} ${json}\n`); 1095 } else if (ts.isIndexSignatureDeclaration(nx)) { 1096 const comment = nx.getText().replace(/[/]/g, ''); 1097 if (nx.getText() == '[uri: string]: TextEdit[];') { 1098 res = 'map[string][]TextEdit'; 1099 } else if (nx.getText().startsWith('[id: ChangeAnnotationIdentifier]')) { 1100 res = 'map[string]ChangeAnnotationIdentifier'; 1101 } else if (nx.getText().startsWith('[uri: string')) { 1102 res = 'map[string]interface{}'; 1103 } else if (nx.getText().startsWith('[uri: DocumentUri')) { 1104 res = 'map[DocumentURI][]TextEdit'; 1105 } else if (nx.getText().startsWith('[key: string')) { 1106 res = 'map[string]interface{}'; 1107 } else { 1108 throw new Error(`1100 handle ${nx.getText()} ${loc(nx)}`); 1109 } 1110 res += ` /*${comment}*/`; 1111 ans.push(res); 1112 return; 1113 } else 1114 throw new Error(`TypeLiteral had ${strKind(nx)}`); 1115 }; 1116 n.members.forEach(g); 1117 // for some the generated type is wanted, for others it's not needed 1118 if (!nm.startsWith('workspace')) { 1119 if (res.startsWith('struct')) return res + '}'; // map[] is special 1120 return res; 1121 } 1122 // these names have to be made unique 1123 const genName = `${goName(nm)}${extraTypes.size}Gn`; 1124 extraTypes.set(genName, ans); 1125 return genName; 1126 } 1127 1128 // print all the types and constants and extra types 1129 function outputTypes() { 1130 // generate go types alphabeticaly 1131 let v = Array.from(seenTypes.keys()); 1132 v.sort(); 1133 v.forEach((x) => toGo(seenTypes.get(x), x)); 1134 u.prgo(u.computeHeader(true)); 1135 u.prgo('import "encoding/json"\n\n'); 1136 typesOut.forEach((s) => { 1137 u.prgo(s); 1138 // it's more convenient not to have to think about trailing newlines 1139 // when generating types, but doc comments can't have an extra \n 1140 if (s.indexOf('/**') < 0) u.prgo('\n'); 1141 }); 1142 u.prgo('\nconst (\n'); 1143 constsOut.forEach((s) => { 1144 u.prgo(s); 1145 u.prgo('\n'); 1146 }); 1147 u.prgo(')\n'); 1148 u.prgo('// Types created to name formal parameters and embedded structs\n'); 1149 extraTypes.forEach((v, k) => { 1150 u.prgo(` type ${k} struct {\n`); 1151 v.forEach((s) => { 1152 u.prgo(s); 1153 u.prgo('\n'); 1154 }); 1155 u.prgo('}\n'); 1156 }); 1157 } 1158 1159 // client and server ------------------ 1160 1161 interface side { 1162 methods: string[]; 1163 cases: string[]; 1164 calls: string[]; 1165 name: string; // client or server 1166 goName: string; // Client or Server 1167 outputFile?: string; 1168 fd?: number 1169 } 1170 let client: side = { 1171 methods: [], 1172 cases: [], 1173 calls: [], 1174 name: 'client', 1175 goName: 'Client', 1176 }; 1177 let server: side = { 1178 methods: [], 1179 cases: [], 1180 calls: [], 1181 name: 'server', 1182 goName: 'Server', 1183 }; 1184 1185 // commonly used output 1186 const notNil = `if len(r.Params()) > 0 { 1187 return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) 1188 }`; 1189 1190 // Go code for notifications. Side is client or server, m is the request 1191 // method 1192 function goNot(side: side, m: string) { 1193 if (m == '$/cancelRequest') return; // handled specially in protocol.go 1194 const n = not.get(m); 1195 const a = goType(n.typeArguments[0], m); 1196 const nm = methodName(m); 1197 side.methods.push(sig(nm, a, '')); 1198 const caseHdr = ` case "${m}": // notif`; 1199 let case1 = notNil; 1200 if (a != '' && a != 'void') { 1201 case1 = `var params ${a} 1202 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1203 return true, sendParseError(ctx, reply, err) 1204 } 1205 err:= ${side.name}.${nm}(ctx, ¶ms) 1206 return true, reply(ctx, nil, err)`; 1207 } else { 1208 case1 = `err := ${side.name}.${nm}(ctx) 1209 return true, reply(ctx, nil, err)`; 1210 } 1211 side.cases.push(`${caseHdr}\n${case1}`); 1212 1213 const arg3 = a == '' || a == 'void' ? 'nil' : 'params'; 1214 side.calls.push(` 1215 func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} { 1216 return s.sender.Notify(ctx, "${m}", ${arg3}) 1217 }`); 1218 } 1219 1220 // Go code for requests. 1221 function goReq(side: side, m: string) { 1222 const n = req.get(m); 1223 const nm = methodName(m); 1224 let a = goType(n.typeArguments[0], m); 1225 let b = goType(n.typeArguments[1], m); 1226 if (n.getText().includes('Type0')) { 1227 b = a; 1228 a = ''; // workspace/workspaceFolders and shutdown 1229 } 1230 u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `); 1231 side.methods.push(sig(nm, a, b)); 1232 1233 const caseHdr = `case "${m}": // req`; 1234 let case1 = notNil; 1235 if (a != '') { 1236 if (extraTypes.has('Param' + nm)) a = 'Param' + nm; 1237 case1 = `var params ${a} 1238 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1239 return true, sendParseError(ctx, reply, err) 1240 }`; 1241 if (a === 'ParamInitialize') { 1242 case1 = `var params ${a} 1243 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1244 if _, ok := err.(*json.UnmarshalTypeError); !ok { 1245 return true, sendParseError(ctx, reply, err) 1246 } 1247 }`; 1248 } 1249 } 1250 const arg2 = a == '' ? '' : ', ¶ms'; 1251 // if case2 is not explicitly typed string, typescript makes it a union of strings 1252 let case2: string = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil { 1253 event.Error(ctx, "", err) 1254 }`; 1255 if (b != '' && b != 'void') { 1256 case2 = `resp, err := ${side.name}.${nm}(ctx${arg2}) 1257 return true, reply(ctx, resp, err)`; 1258 } else { // response is nil 1259 case2 = `err := ${side.name}.${nm}(ctx${arg2}) 1260 return true, reply(ctx, nil, err)`; 1261 } 1262 1263 side.cases.push(`${caseHdr}\n${case1}\n${case2}`); 1264 1265 const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`; 1266 let callBody = `return s.sender.Call(ctx, "${m}", nil, nil)\n}`; 1267 if (b != '' && b != 'void') { 1268 const p2 = a == '' ? 'nil' : 'params'; 1269 const returnType = indirect(b) ? `*${b}` : b; 1270 callBody = `var result ${returnType} 1271 if err := s.sender.Call(ctx, "${m}", ${p2}, &result); err != nil { 1272 return nil, err 1273 } 1274 return result, nil 1275 }`; 1276 } else if (a != '') { 1277 callBody = `return s.sender.Call(ctx, "${m}", params, nil) // Call, not Notify 1278 }`; 1279 } 1280 side.calls.push(`${callHdr}\n${callBody}\n`); 1281 } 1282 1283 // make sure method names are unique 1284 let seenNames = new Set<string>(); 1285 function methodName(m: string): string { 1286 let i = m.indexOf('/'); 1287 let s = m.substring(i + 1); 1288 let x = s[0].toUpperCase() + s.substring(1); 1289 for (let j = x.indexOf('/'); j >= 0; j = x.indexOf('/')) { 1290 let suffix = x.substring(j + 1); 1291 suffix = suffix[0].toUpperCase() + suffix.substring(1); 1292 let prefix = x.substring(0, j); 1293 x = prefix + suffix; 1294 } 1295 if (seenNames.has(x)) { 1296 // various Resolve and Diagnostic 1297 x += m[0].toUpperCase() + m.substring(1, i); 1298 } 1299 seenNames.add(x); 1300 return x; 1301 } 1302 1303 // used in sig and in goReq 1304 function indirect(s: string): boolean { 1305 if (s == '' || s == 'void') return false; 1306 const skip = (x: string) => s.startsWith(x); 1307 if (skip('[]') || skip('interface') || skip('Declaration') || 1308 skip('Definition') || skip('DocumentSelector')) 1309 return false; 1310 return true; 1311 } 1312 1313 // Go signatures for methods. 1314 function sig(nm: string, a: string, b: string, names?: boolean): string { 1315 if (a.indexOf('struct') != -1) { 1316 const v = a.split('\n'); 1317 extraTypes.set(`Param${nm}`, v.slice(1, v.length - 1)); 1318 a = 'Param' + nm; 1319 } 1320 if (a == 'void') 1321 a = ''; 1322 else if (a != '') { 1323 if (names) 1324 a = ', params *' + a; 1325 else 1326 a = ', *' + a; 1327 } 1328 let ret = 'error'; 1329 if (b != '' && b != 'void') { 1330 // avoid * when it is senseless 1331 if (indirect(b)) b = '*' + b; 1332 ret = `(${b}, error)`; 1333 } 1334 let start = `${nm}(`; 1335 if (names) { 1336 start = start + 'ctx '; 1337 } 1338 return `${start}context.Context${a}) ${ret}`; 1339 } 1340 1341 // write the request/notification code 1342 function output(side: side) { 1343 // make sure the output file exists 1344 if (!side.outputFile) { 1345 side.outputFile = `ts${side.name}.go`; 1346 side.fd = fs.openSync(side.outputFile, 'w'); 1347 } 1348 const f = function (s: string) { 1349 fs.writeSync(side.fd!, s); 1350 fs.writeSync(side.fd!, '\n'); 1351 }; 1352 f(u.computeHeader(false)); 1353 f(` 1354 import ( 1355 "context" 1356 "encoding/json" 1357 1358 "golang.org/x/tools/internal/jsonrpc2" 1359 errors "golang.org/x/xerrors" 1360 ) 1361 `); 1362 const a = side.name[0].toUpperCase() + side.name.substring(1); 1363 f(`type ${a} interface {`); 1364 side.methods.forEach((v) => { f(v); }); 1365 f('}\n'); 1366 f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { 1367 switch r.Method() {`); 1368 side.cases.forEach((v) => { f(v); }); 1369 f(` 1370 default: 1371 return false, nil 1372 } 1373 }`); 1374 side.calls.forEach((v) => { f(v); }); 1375 } 1376 1377 // Handling of non-standard requests, so we can add gopls-specific calls. 1378 function nonstandardRequests() { 1379 server.methods.push( 1380 'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)'); 1381 server.calls.push( 1382 `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { 1383 var result interface{} 1384 if err := s.sender.Call(ctx, method, params, &result); err != nil { 1385 return nil, err 1386 } 1387 return result, nil 1388 } 1389 `); 1390 } 1391 1392 // ----- remember it's a scripting language 1393 function main() { 1394 if (u.gitHash != u.git()) { 1395 throw new Error( 1396 `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`); 1397 } 1398 u.createOutputFiles(); 1399 parse(); 1400 u.printAST(program); 1401 // find the Requests and Nofificatations 1402 for (const sourceFile of program.getSourceFiles()) { 1403 if (!sourceFile.isDeclarationFile) { 1404 ts.forEachChild(sourceFile, findRPCs); 1405 } 1406 } 1407 // separate RPCs into client and server 1408 setReceives(); 1409 // visit every sourceFile collecting top-level type definitions 1410 for (const sourceFile of program.getSourceFiles()) { 1411 if (!sourceFile.isDeclarationFile) { 1412 ts.forEachChild(sourceFile, genTypes); 1413 } 1414 } 1415 // check that each thing occurs exactly once, and put pointers into 1416 // seenTypes 1417 checkOnce(); 1418 // for each of Client and Server there are 3 parts to the output: 1419 // 1. type X interface {methods} 1420 // 2. func (h *serverHandler) Deliver(...) { switch r.method } 1421 // 3. func (x *xDispatcher) Method(ctx, parm) 1422 not.forEach( // notifications 1423 (v, k) => { 1424 receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k); 1425 }); 1426 req.forEach( // requests 1427 (v, k) => { 1428 receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k); 1429 }); 1430 nonstandardRequests(); 1431 // find all the types implied by seenTypes and rpcs to try to avoid 1432 // generating types that aren't used 1433 moreTypes(); 1434 // do merging 1435 cleanData(); 1436 // and print the Go code 1437 outputTypes(); 1438 console.log(`seen ${seenTypes.size + extraTypes.size}`); 1439 output(client); 1440 output(server); 1441 } 1442 1443 main();