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 20import * as fs from 'fs'; 21import * as ts from 'typescript'; 22import * as u from './util'; 23import { constName, getComments, goName, loc, strKind } from './util'; 24 25var program: ts.Program; 26 27function 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 36let req = new Map<string, ts.NewExpression>(); // requests 37let not = new Map<string, ts.NewExpression>(); // notifications 38let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>(); // req, resp types 39let receives = new Map<string, 'server' | 'client'>(); // who receives it 40let rpcTypes = new Set<string>(); // types seen in the rpcs 41 42function 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 120function 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 147type DataKind = 'module' | 'interface' | 'alias' | 'enum' | 'class'; 148 149interface 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} 165function 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 179function 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 188let data = new Map<string, Data>(); // parsed data types 189let seenTypes = new Map<string, Data>(); // type names we've seen 190let extraTypes = new Map<string, string[]>(); // to avoid struct params 191 192function 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 233function 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 380function 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 411function 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 419function 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 428function 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 478function 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 525function 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 532function 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 (ts.isTypeReferenceNode(a) && ts.isTypeReferenceNode(b) && 536 a.typeName.getText() === b.typeName.getText()) return true; 537 if (ts.isArrayTypeNode(a) && ts.isArrayTypeNode(b)) return sameType(a.elementType, b.elementType); 538 if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) { 539 if (a.members.length !== b.members.length) return false; 540 if (a.members.length === 1) return a.members[0].name.getText() === b.members[0].name.getText(); 541 if (loc(a) === loc(b)) return true; 542 } 543 throw new Error(`546 sameType? ${strKind(a)} ${strKind(b)}`); 544} 545type CreateMutable<Type> = { 546 -readonly [Property in keyof Type]: Type[Property]; 547}; 548type propMap = Map<string, ts.PropertySignature>; 549function propMapSet(pm: propMap, name: string, v: ts.PropertySignature) { 550 if (!pm.get(name)) { 551 try { getComments(v); } catch (e) { console.log(`552 ${name} ${e}`); } 552 pm.set(name, v); 553 return; 554 } 555 const a = pm.get(name).type; 556 const b = v.type; 557 if (sameType(a, b)) { 558 return; 559 } 560 if (ts.isTypeReferenceNode(a) && ts.isTypeLiteralNode(b)) { 561 const x = mergeTypeRefLit(a, b); 562 const fake: CreateMutable<ts.PropertySignature> = v; 563 fake['type'] = x; 564 check(fake as ts.PropertySignature, '565'); 565 pm.set(name, fake as ts.PropertySignature); 566 return; 567 } 568 if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) { 569 const x = mergeTypeLitLit(a, b); 570 const fake: CreateMutable<ts.PropertySignature> = v; 571 fake['type'] = x; 572 check(fake as ts.PropertySignature, '578'); 573 pm.set(name, fake as ts.PropertySignature); 574 return; 575 } 576 console.log(`577 ${pm.get(name).getText()}\n${v.getText()}`); 577 throw new Error(`578 should merge ${strKind(a)} and ${strKind(b)} for ${name}`); 578} 579function addToProperties(pm: propMap, tn: ts.TypeNode | undefined, prefix = '') { 580 if (!tn) return; 581 if (ts.isTypeReferenceNode(tn)) { 582 const d = seenTypes.get(goName(tn.typeName.getText())); 583 if (tn.typeName.getText() === 'T') return; 584 if (!d) throw new Error(`584 ${tn.typeName.getText()} not found`); 585 if (d.properties.length === 0 && d.alias === undefined) return; 586 if (d.alias !== undefined) { 587 if (ts.isIntersectionTypeNode(d.alias)) { 588 d.alias.types.forEach((tn) => addToProperties(pm, tn, prefix)); // prefix? 589 return; 590 } 591 } 592 d.properties.forEach((ps) => { 593 const name = `${prefix}.${ps.name.getText()}`; 594 propMapSet(pm, name, ps); 595 addToProperties(pm, ps.type, name); 596 }); 597 } else if (strKind(tn) === 'TypeLiteral') { 598 if (!ts.isTypeLiteralNode(tn)) new Error(`598 ${strKind(tn)}`); 599 tn.forEachChild((child: ts.Node) => { 600 if (!ts.isPropertySignature(child)) throw new Error(`600 ${strKind(child)}`); 601 const name = `${prefix}.${child.name.getText()}`; 602 propMapSet(pm, name, child); 603 addToProperties(pm, child.type, name); 604 }); 605 } 606} 607function deepProperties(d: Data): propMap | undefined { 608 let properties: propMap = new Map<string, ts.PropertySignature>(); 609 if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return undefined; 610 d.alias.types.forEach((ts) => addToProperties(properties, ts)); 611 return properties; 612} 613 614function mergeAlias(d: Data) { 615 const props = deepProperties(d); 616 if (!props) return; // nothing merged 617 // now each element of props should have length 1 618 // change d to merged, toss its alias field, fill in its properties 619 const v: ts.PropertySignature[] = []; 620 props.forEach((ps, nm) => { 621 const xlen = nm.split('.').length; 622 if (xlen !== 2) return; // not top-level 623 v.push(ps); 624 }); 625 d.kind = 'interface'; 626 d.alias = undefined; 627 d.properties = ts.factory.createNodeArray(v); 628} 629 630function mergeTypeLitLit(a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode { 631 const v = new Map<string, ts.TypeElement>(); // avoid duplicates 632 a.members.forEach((te) => v.set(te.name.getText(), te)); 633 b.members.forEach((te) => v.set(te.name.getText(), te)); 634 const x: ts.TypeElement[] = []; 635 v.forEach((te) => x.push(te)); 636 const fake: CreateMutable<ts.TypeLiteralNode> = a; 637 fake['members'] = ts.factory.createNodeArray(x); 638 check(fake as ts.TypeLiteralNode, '643'); 639 return fake as ts.TypeLiteralNode; 640} 641 642function mergeTypeRefLit(a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode { 643 const d = seenTypes.get(goName(a.typeName.getText())); 644 if (!d) throw new Error(`644 name ${a.typeName.getText()} not found`); 645 const typ = d.me; 646 if (!ts.isInterfaceDeclaration(typ)) throw new Error(`646 got ${strKind(typ)} not InterfaceDecl`); 647 const v = new Map<string, ts.TypeElement>(); // avoid duplicates 648 typ.members.forEach((te) => v.set(te.name.getText(), te)); 649 b.members.forEach((te) => v.set(te.name.getText(), te)); 650 const x: ts.TypeElement[] = []; 651 v.forEach((te) => x.push(te)); 652 653 const w = ts.factory.createNodeArray(x); 654 const fk: CreateMutable<ts.TypeLiteralNode> = b; 655 fk['members'] = w; 656 (fk['members'] as { pos: number })['pos'] = b.members.pos; 657 (fk['members'] as { end: number })['end'] = b.members.end; 658 check(fk as ts.TypeLiteralNode, '662'); 659 return fk as ts.TypeLiteralNode; 660} 661 662// check that constructed nodes still have associated text 663function check(n: ts.Node, loc: string) { 664 try { getComments(n); } catch (e) { console.log(`check at ${loc} ${e}`); } 665 try { n.getText(); } catch (e) { console.log(`text check at ${loc}`); } 666} 667 668let typesOut = new Array<string>(); 669let constsOut = new Array<string>(); 670 671// generate Go types 672function toGo(d: Data, nm: string) { 673 if (!d) return; // this is probably a generic T 674 if (d.name.startsWith('Inner') || d.name === 'WindowClientCapabilities') return; // removed by alias processing 675 if (d.name === 'Integer' || d.name === 'Uinteger') return; // unneeded 676 switch (d.kind) { 677 case 'alias': 678 goTypeAlias(d, nm); break; 679 case 'module': goModule(d, nm); break; 680 case 'enum': goEnum(d, nm); break; 681 case 'interface': goInterface(d, nm); break; 682 default: 683 throw new Error( 684 `672: more cases in toGo ${nm} ${d.kind}`); 685 } 686} 687 688// these fields need a * and are not covered by the code 689// that calls isStructType. 690var starred: [string, string][] = [ 691 ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'], 692 ['CodeAction', 'disabled'], 693 ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'], 694 ['Diagnostic', 'codeDescription'] 695]; 696 697// generate Go code for an interface 698function goInterface(d: Data, nm: string) { 699 let ans = `type ${goName(nm)} struct {\n`; 700 701 // generate the code for each member 702 const g = function (n: ts.PropertySignature) { 703 if (!ts.isPropertySignature(n)) 704 throw new Error(`expected PropertySignature got ${strKind(n)} `); 705 ans = ans.concat(getComments(n)); 706 const json = u.JSON(n); 707 let gt = goType(n.type, n.name.getText()); 708 if (gt == d.name) gt = '*' + gt; // avoid recursive types (SelectionRange) 709 // there are several cases where a * is needed 710 // (putting * in front of too many things breaks uses of CodeActionKind) 711 starred.forEach(([a, b]) => { 712 if (d.name == a && n.name.getText() == b) { 713 gt = '*' + gt; 714 } 715 }); 716 ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n'); 717 }; 718 d.properties.forEach(g); 719 // heritage clauses become embedded types 720 // check they are all Identifiers 721 const f = function (n: ts.ExpressionWithTypeArguments) { 722 if (!ts.isIdentifier(n.expression)) 723 throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `); 724 ans = ans.concat(goName(n.expression.getText()), '\n'); 725 }; 726 d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f)); 727 ans = ans.concat('}\n'); 728 typesOut.push(getComments(d.me)); 729 typesOut.push(ans); 730} 731 732// generate Go code for a module (const declarations) 733// Generates type definitions, and named constants 734function goModule(d: Data, nm: string) { 735 if (d.generics.length > 0 || d.as.length > 0) { 736 throw new Error(`743 goModule: unexpected for ${nm} 737 `); 738 } 739 // all the statements should be export const <id>: value 740 // or value = value 741 // They are VariableStatements with x.declarationList having a single 742 // VariableDeclaration 743 let isNumeric = false; 744 const f = function (n: ts.Statement, i: number) { 745 if (!ts.isVariableStatement(n)) { 746 throw new Error(`753 ${nm} ${i} expected VariableStatement, 747 got ${strKind(n)}`); 748 } 749 const c = getComments(n); 750 const v = n.declarationList.declarations[0]; // only one 751 752 if (!v.initializer) 753 throw new Error(`760 no initializer ${nm} ${i} ${v.name.getText()}`); 754 isNumeric = strKind(v.initializer) == 'NumericLiteral'; 755 if (c != '') constsOut.push(c); // no point if there are no comments 756 // There are duplicates. 757 const cname = constName(goName(v.name.getText()), nm); 758 let val = v.initializer.getText(); 759 val = val.split('\'').join('"'); // useless work for numbers 760 constsOut.push(`${cname} ${nm} = ${val}`); 761 }; 762 d.statements.forEach(f); 763 typesOut.push(getComments(d.me)); 764 // Or should they be type aliases? 765 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`); 766} 767 768// generate Go code for an enum. Both types and named constants 769function goEnum(d: Data, nm: string) { 770 let isNumeric = false; 771 const f = function (v: ts.EnumMember, j: number) { // same as goModule 772 if (!v.initializer) 773 throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`); 774 isNumeric = strKind(v.initializer) == 'NumericLiteral'; 775 const c = getComments(v); 776 const cname = constName(goName(v.name.getText()), nm); 777 let val = v.initializer.getText(); 778 val = val.split('\'').join('"'); // replace quotes. useless work for numbers 779 constsOut.push(`${c}${cname} ${nm} = ${val}`); 780 }; 781 d.enums.forEach(f); 782 typesOut.push(getComments(d.me)); 783 // Or should they be type aliases? 784 typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`); 785} 786 787// generate code for a type alias 788function goTypeAlias(d: Data, nm: string) { 789 if (d.as.length != 0 || d.generics.length != 0) { 790 if (nm != 'ServerCapabilities') 791 throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`); 792 } 793 typesOut.push(getComments(d.me)); 794 // d.alias doesn't seem to have comments 795 let aliasStr = goName(nm) == 'DocumentURI' ? ' ' : ' = '; 796 if (nm == 'PrepareSupportDefaultBehavior') { 797 // code-insiders is sending a bool, not a number. PJW: check this after Feb/2021 798 // (and gopls never looks at it anyway) 799 typesOut.push(`type ${goName(nm)}${aliasStr}interface{}\n`); 800 return; 801 } 802 typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`); 803} 804 805// return a go type and maybe an assocated javascript tag 806function goType(n: ts.TypeNode | undefined, nm: string): string { 807 if (!n) throw new Error(`goType undefined for ${nm}`); 808 if (n.getText() == 'T') return 'interface{}'; // should check it's generic 809 if (ts.isTypeReferenceNode(n)) { 810 // DocumentDiagnosticReportKind.unChanged (or .new) value is "new" or "unChanged" 811 if (n.getText().startsWith('DocumentDiagnostic')) return 'string'; 812 switch (n.getText()) { 813 case 'integer': return 'int32'; 814 case 'uinteger': return 'uint32'; 815 default: return goName(n.typeName.getText()); // avoid <T> 816 } 817 } else if (ts.isUnionTypeNode(n)) { 818 return goUnionType(n, nm); 819 } else if (ts.isIntersectionTypeNode(n)) { 820 return goIntersectionType(n, nm); 821 } else if (strKind(n) == 'StringKeyword') { 822 return 'string'; 823 } else if (strKind(n) == 'NumberKeyword') { 824 return 'float64'; 825 } else if (strKind(n) == 'BooleanKeyword') { 826 return 'bool'; 827 } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') { 828 return 'interface{}'; 829 } else if (strKind(n) == 'NullKeyword') { 830 return 'nil'; 831 } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') { 832 return 'void'; 833 } else if (strKind(n) == 'ObjectKeyword') { 834 return 'interface{}'; 835 } else if (ts.isArrayTypeNode(n)) { 836 if (nm === 'arguments') { 837 // Command and ExecuteCommandParams 838 return '[]json.RawMessage'; 839 } 840 return `[]${goType(n.elementType, nm)}`; 841 } else if (ts.isParenthesizedTypeNode(n)) { 842 return goType(n.type, nm); 843 } else if (ts.isLiteralTypeNode(n)) { 844 return strKind(n.literal) == 'StringLiteral' ? 'string' : 'float64'; 845 } else if (ts.isTypeLiteralNode(n)) { 846 // these are anonymous structs 847 const v = goTypeLiteral(n, nm); 848 return v; 849 } else if (ts.isTupleTypeNode(n)) { 850 if (n.getText() == '[number, number]') return '[]float64'; 851 throw new Error(`goType unexpected Tuple ${n.getText()}`); 852 } 853 throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`); 854} 855 856// The choice is uniform interface{}, or some heuristically assigned choice, 857// or some better sytematic idea I haven't thought of. Using interface{} 858// is, in practice, impossibly complex in the existing code. 859function goUnionType(n: ts.UnionTypeNode, nm: string): string { 860 let help = `/*${n.getText()}*/`; // show the original as a comment 861 // There are some bad cases with newlines: 862 // range?: boolean | {\n }; 863 // full?: boolean | {\n /**\n * The server supports deltas for full documents.\n */\n delta?: boolean;\n } 864 // These are handled specially: 865 if (nm == 'range') help = help.replace(/\n/, ''); 866 if (nm == 'full' && help.indexOf('\n') != -1) { 867 help = '/*boolean | <elided struct>*/'; 868 } 869 // handle all the special cases 870 switch (n.types.length) { 871 case 2: { 872 const a = strKind(n.types[0]); 873 const b = strKind(n.types[1]); 874 if (a == 'NumberKeyword' && b == 'StringKeyword') { // ID 875 return `interface{} ${help}`; 876 } 877 if (b == 'NullKeyword' || n.types[1].getText() === 'null') { 878 // PJW: fix this. it looks like 'null' is now being parsed as LiteralType 879 // and check the other keyword cases 880 if (nm == 'textDocument/codeAction') { 881 // (Command | CodeAction)[] | null 882 return `[]CodeAction ${help}`; 883 } 884 let v = goType(n.types[0], 'a'); 885 return `${v} ${help}`; 886 } 887 if (a == 'BooleanKeyword') { // usually want bool 888 if (nm == 'codeActionProvider') return `interface{} ${help}`; 889 if (nm == 'renameProvider') return `interface{} ${help}`; 890 if (nm == 'full') return `interface{} ${help}`; // there's a struct 891 if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`; 892 return `${goType(n.types[0], 'b')} ${help}`; 893 } 894 if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`; 895 if (help.includes('InsertReplaceEdit') && n.types[0].getText() == 'TextEdit') { 896 return `*TextEdit ${help}`; 897 } 898 if (a == 'TypeReference') { 899 if (nm == 'edits') return `${goType(n.types[0], '715')} ${help}`; 900 if (a == b) return `interface{} ${help}`; 901 if (nm == 'code') return `interface{} ${help}`; 902 } 903 if (a == 'StringKeyword') return `string ${help}`; 904 if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') { 905 return `${goType(n.types[0], nm)}`; 906 } 907 if (a == 'TypeLiteral' && b === 'TypeLiteral') { 908 // DocumentDiagnosticReport 909 // the first one includes the second one 910 return `${goType(n.types[0], '9d')}`; 911 } 912 throw new Error(`911 ${nm}: a:${a}/${goType(n.types[0], '9a')} b:${b}/${goType(n.types[1], '9b')} ${loc(n)}`); 913 } 914 case 3: { 915 const aa = strKind(n.types[0]); 916 const bb = strKind(n.types[1]); 917 const cc = strKind(n.types[2]); 918 if (nm == 'DocumentFilter') { 919 // not really a union. the first is enough, up to a missing 920 // omitempty but avoid repetitious comments 921 return `${goType(n.types[0], 'g')}`; 922 } 923 if (nm == 'textDocument/documentSymbol') { 924 return `[]interface{} ${help}`; 925 } 926 if (aa == 'TypeReference' && bb == 'ArrayType' && (cc == 'NullKeyword' || cc === 'LiteralType')) { 927 return `${goType(n.types[0], 'd')} ${help}`; 928 } 929 if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') { 930 // should check that this is Hover.Contents 931 return `${goType(n.types[0], 'e')} ${help}`; 932 } 933 if (aa == 'ArrayType' && bb == 'TypeReference' && (cc == 'NullKeyword' || cc === 'LiteralType')) { 934 // check this is nm == 'textDocument/completion' 935 return `${goType(n.types[1], 'f')} ${help}`; 936 } 937 if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`; 938 // keep this for diagnosing unexpected interface{} results 939 // console.log(`931, interface{} for ${aa}/${goType(n.types[0], 'g')},${bb}/${goType(n.types[1], 'h')},${cc}/${goType(n.types[2], 'i')} ${nm}`); 940 break; 941 } 942 case 4: 943 if (nm == 'documentChanges') return `TextDocumentEdit ${help} `; 944 if (nm == 'textDocument/prepareRename') return `Range ${help} `; 945 // eslint-disable-next-line no-fallthrough 946 default: 947 throw new Error(`goUnionType len=${n.types.length} nm=${nm}`); 948 } 949 950 // Result will be interface{} with a comment 951 let isLiteral = true; 952 let literal = 'string'; 953 let res = 'interface{} /* '; 954 n.types.forEach((v: ts.TypeNode, i: number) => { 955 // might get an interface inside: 956 // (Command | CodeAction)[] | null 957 let m = goType(v, nm); 958 if (m.indexOf('interface') != -1) { 959 // avoid nested comments 960 m = m.split(' ')[0]; 961 } 962 m = m.split('\n').join('; '); // sloppy: struct{; 963 res = res.concat(`${i == 0 ? '' : ' | '}`, m); 964 if (!ts.isLiteralTypeNode(v)) isLiteral = false; 965 else literal = strKind(v.literal) == 'StringLiteral' ? 'string' : 'number'; 966 }); 967 if (!isLiteral) { 968 return res + '*/'; 969 } 970 // I don't think we get here 971 // trace?: 'off' | 'messages' | 'verbose' should get string 972 return `${literal} /* ${n.getText()} */`; 973} 974 975// some of the intersection types A&B are ok as struct{A;B;} and some 976// could be expanded, and ClientCapabilites has to be expanded, 977// at least for workspace. It's possible to check algorithmically, 978// but much simpler just to check explicitly. 979function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string { 980 if (nm == 'ClientCapabilities') return expandIntersection(n); 981 //if (nm == 'ServerCapabilities') return expandIntersection(n); // save for later consideration 982 let inner = ''; 983 n.types.forEach( 984 (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n'); }); 985 return `struct{ \n${inner}} `; 986} 987 988// for each of the intersected types, extract its components (each will 989// have a Data with properties) extract the properties, and keep track 990// of them by name. The names that occur once can be output. The names 991// that occur more than once need to be combined. 992function expandIntersection(n: ts.IntersectionTypeNode): string { 993 const bad = function (n: ts.Node, s: string) { 994 return new Error(`expandIntersection ${strKind(n)} ${s}`); 995 }; 996 let props = new Map<string, ts.PropertySignature[]>(); 997 for (const tp of n.types) { 998 if (!ts.isTypeReferenceNode(tp)) throw bad(tp, 'A'); 999 const d = data.get(goName(tp.typeName.getText())); 1000 for (const p of d.properties) { 1001 if (!ts.isPropertySignature(p)) throw bad(p, 'B'); 1002 let v = props.get(p.name.getText()) || []; 1003 v.push(p); 1004 props.set(p.name.getText(), v); 1005 } 1006 } 1007 let ans = 'struct {\n'; 1008 for (const [k, v] of Array.from(props)) { 1009 if (v.length == 1) { 1010 const a = v[0]; 1011 ans = ans.concat(getComments(a)); 1012 ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`); 1013 continue; 1014 } 1015 ans = ans.concat(`${goName(k)} struct {\n`); 1016 for (let i = 0; i < v.length; i++) { 1017 const a = v[i]; 1018 if (ts.isTypeReferenceNode(a.type)) { 1019 ans = ans.concat(getComments(a)); 1020 ans = ans.concat(goName(a.type.typeName.getText()), '\n'); 1021 } else if (ts.isTypeLiteralNode(a.type)) { 1022 if (a.type.members.length != 1) throw bad(a.type, 'C'); 1023 const b = a.type.members[0]; 1024 if (!ts.isPropertySignature(b)) throw bad(b, 'D'); 1025 ans = ans.concat(getComments(b)); 1026 ans = ans.concat( 1027 goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n'); 1028 } else { 1029 throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`); 1030 } 1031 } 1032 ans = ans.concat('}\n'); 1033 } 1034 ans = ans.concat('}\n'); 1035 return ans; 1036} 1037 1038// Does it make sense to use a pointer? 1039function isStructType(te: ts.TypeNode): boolean { 1040 switch (strKind(te)) { 1041 case 'UnionType': // really need to know which type will be chosen 1042 case 'BooleanKeyword': 1043 case 'StringKeyword': 1044 case 'ArrayType': 1045 return false; 1046 case 'TypeLiteral': return false; // true makes for difficult compound constants 1047 // but think more carefully to understands why starred is needed. 1048 case 'TypeReference': { 1049 if (!ts.isTypeReferenceNode(te)) throw new Error(`1047 impossible ${strKind(te)}`); 1050 const d = seenTypes.get(goName(te.typeName.getText())); 1051 if (d === undefined) return false; 1052 if (d.properties.length > 1) return true; 1053 // alias or interface with a single property (The alias is Uinteger, which we ignore later) 1054 if (d.alias) return false; 1055 const x = d.properties[0].type; 1056 return isStructType(x); 1057 } 1058 default: throw new Error(`1055 indirectable> ${strKind(te)}`); 1059 } 1060} 1061 1062function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string { 1063 let ans: string[] = []; // in case we generate a new extra type 1064 let res = 'struct{\n'; // the actual answer usually 1065 const g = function (nx: ts.TypeElement) { 1066 // add the json, as in goInterface(). Strange inside union types. 1067 if (ts.isPropertySignature(nx)) { 1068 let json = u.JSON(nx); 1069 let typ = goType(nx.type, nx.name.getText()); 1070 const v = getComments(nx) || ''; 1071 starred.forEach(([a, b]) => { 1072 if (a != nm || b != typ.toLowerCase()) return; 1073 typ = '*' + typ; 1074 json = json.substring(0, json.length - 2) + ',omitempty"`'; 1075 }); 1076 if (typ[0] !== '*' && isStructType(nx.type)) typ = '*' + typ; 1077 res = res.concat(`${v} ${goName(nx.name.getText())} ${typ}`, json, '\n'); 1078 ans.push(`${v}${goName(nx.name.getText())} ${typ} ${json}\n`); 1079 } else if (ts.isIndexSignatureDeclaration(nx)) { 1080 const comment = nx.getText().replace(/[/]/g, ''); 1081 if (nx.getText() == '[uri: string]: TextEdit[];') { 1082 res = 'map[string][]TextEdit'; 1083 } else if (nx.getText() == '[id: string /* ChangeAnnotationIdentifier */]: ChangeAnnotation;') { 1084 res = 'map[string]ChangeAnnotationIdentifier'; 1085 } else if (nx.getText().startsWith('[uri: string')) { 1086 res = 'map[string]interface{}'; 1087 } else { 1088 throw new Error(`1088 handle ${nx.getText()} ${loc(nx)}`); 1089 } 1090 res += ` /*${comment}*/`; 1091 ans.push(res); 1092 return; 1093 } else 1094 throw new Error(`TypeLiteral had ${strKind(nx)}`); 1095 }; 1096 n.members.forEach(g); 1097 // for some the generated type is wanted, for others it's not needed 1098 if (!nm.startsWith('workspace')) { 1099 if (res.startsWith('struct')) return res + '}'; // map[] is special 1100 return res; 1101 } 1102 // these names have to be made unique 1103 const genName = `${goName(nm)}${extraTypes.size}Gn`; 1104 extraTypes.set(genName, ans); 1105 return genName; 1106} 1107 1108// print all the types and constants and extra types 1109function outputTypes() { 1110 // generate go types alphabeticaly 1111 let v = Array.from(seenTypes.keys()); 1112 v.sort(); 1113 v.forEach((x) => toGo(seenTypes.get(x), x)); 1114 u.prgo(u.computeHeader(true)); 1115 u.prgo('import "encoding/json"\n\n'); 1116 typesOut.forEach((s) => { 1117 u.prgo(s); 1118 // it's more convenient not to have to think about trailing newlines 1119 // when generating types, but doc comments can't have an extra \n 1120 if (s.indexOf('/**') < 0) u.prgo('\n'); 1121 }); 1122 u.prgo('\nconst (\n'); 1123 constsOut.forEach((s) => { 1124 u.prgo(s); 1125 u.prgo('\n'); 1126 }); 1127 u.prgo(')\n'); 1128 u.prgo('// Types created to name formal parameters and embedded structs\n'); 1129 extraTypes.forEach((v, k) => { 1130 u.prgo(` type ${k} struct {\n`); 1131 v.forEach((s) => { 1132 u.prgo(s); 1133 u.prgo('\n'); 1134 }); 1135 u.prgo('}\n'); 1136 }); 1137} 1138 1139// client and server ------------------ 1140 1141interface side { 1142 methods: string[]; 1143 cases: string[]; 1144 calls: string[]; 1145 name: string; // client or server 1146 goName: string; // Client or Server 1147 outputFile?: string; 1148 fd?: number 1149} 1150let client: side = { 1151 methods: [], 1152 cases: [], 1153 calls: [], 1154 name: 'client', 1155 goName: 'Client', 1156}; 1157let server: side = { 1158 methods: [], 1159 cases: [], 1160 calls: [], 1161 name: 'server', 1162 goName: 'Server', 1163}; 1164 1165// commonly used output 1166const notNil = `if len(r.Params()) > 0 { 1167 return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) 1168}`; 1169 1170// Go code for notifications. Side is client or server, m is the request 1171// method 1172function goNot(side: side, m: string) { 1173 if (m == '$/cancelRequest') return; // handled specially in protocol.go 1174 const n = not.get(m); 1175 const a = goType(n.typeArguments[0], m); 1176 const nm = methodName(m); 1177 side.methods.push(sig(nm, a, '')); 1178 const caseHdr = ` case "${m}": // notif`; 1179 let case1 = notNil; 1180 if (a != '' && a != 'void') { 1181 case1 = `var params ${a} 1182 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1183 return true, sendParseError(ctx, reply, err) 1184 } 1185 err:= ${side.name}.${nm}(ctx, ¶ms) 1186 return true, reply(ctx, nil, err)`; 1187 } else { 1188 case1 = `err := ${side.name}.${nm}(ctx) 1189 return true, reply(ctx, nil, err)`; 1190 } 1191 side.cases.push(`${caseHdr}\n${case1}`); 1192 1193 const arg3 = a == '' || a == 'void' ? 'nil' : 'params'; 1194 side.calls.push(` 1195 func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} { 1196 return s.sender.Notify(ctx, "${m}", ${arg3}) 1197 }`); 1198} 1199 1200// Go code for requests. 1201function goReq(side: side, m: string) { 1202 const n = req.get(m); 1203 const nm = methodName(m); 1204 let a = goType(n.typeArguments[0], m); 1205 let b = goType(n.typeArguments[1], m); 1206 if (n.getText().includes('Type0')) { 1207 b = a; 1208 a = ''; // workspace/workspaceFolders and shutdown 1209 } 1210 u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `); 1211 side.methods.push(sig(nm, a, b)); 1212 1213 const caseHdr = `case "${m}": // req`; 1214 let case1 = notNil; 1215 if (a != '') { 1216 if (extraTypes.has('Param' + nm)) a = 'Param' + nm; 1217 case1 = `var params ${a} 1218 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1219 return true, sendParseError(ctx, reply, err) 1220 }`; 1221 if (a === 'ParamInitialize') { 1222 case1 = `var params ${a} 1223 if err := json.Unmarshal(r.Params(), ¶ms); err != nil { 1224 if _, ok := err.(*json.UnmarshalTypeError); !ok { 1225 return true, sendParseError(ctx, reply, err) 1226 } 1227 }`; 1228 } 1229 } 1230 const arg2 = a == '' ? '' : ', ¶ms'; 1231 // if case2 is not explicitly typed string, typescript makes it a union of strings 1232 let case2: string = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil { 1233 event.Error(ctx, "", err) 1234 }`; 1235 if (b != '' && b != 'void') { 1236 case2 = `resp, err := ${side.name}.${nm}(ctx${arg2}) 1237 return true, reply(ctx, resp, err)`; 1238 } else { // response is nil 1239 case2 = `err := ${side.name}.${nm}(ctx${arg2}) 1240 return true, reply(ctx, nil, err)`; 1241 } 1242 1243 side.cases.push(`${caseHdr}\n${case1}\n${case2}`); 1244 1245 const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`; 1246 let callBody = `return s.sender.Call(ctx, "${m}", nil, nil)\n}`; 1247 if (b != '' && b != 'void') { 1248 const p2 = a == '' ? 'nil' : 'params'; 1249 const returnType = indirect(b) ? `*${b}` : b; 1250 callBody = `var result ${returnType} 1251 if err := s.sender.Call(ctx, "${m}", ${p2}, &result); err != nil { 1252 return nil, err 1253 } 1254 return result, nil 1255 }`; 1256 } else if (a != '') { 1257 callBody = `return s.sender.Call(ctx, "${m}", params, nil) // Call, not Notify 1258 }`; 1259 } 1260 side.calls.push(`${callHdr}\n${callBody}\n`); 1261} 1262 1263// make sure method names are unique 1264let seenNames = new Set<string>(); 1265function methodName(m: string): string { 1266 let i = m.indexOf('/'); 1267 let s = m.substring(i + 1); 1268 let x = s[0].toUpperCase() + s.substring(1); 1269 for (let j = x.indexOf('/'); j >= 0; j = x.indexOf('/')) { 1270 let suffix = x.substring(j + 1); 1271 suffix = suffix[0].toUpperCase() + suffix.substring(1); 1272 let prefix = x.substring(0, j); 1273 x = prefix + suffix; 1274 } 1275 if (seenNames.has(x)) { 1276 // various Resolve and Diagnostic 1277 x += m[0].toUpperCase() + m.substring(1, i); 1278 } 1279 seenNames.add(x); 1280 return x; 1281} 1282 1283// used in sig and in goReq 1284function indirect(s: string): boolean { 1285 if (s == '' || s == 'void') return false; 1286 const skip = (x: string) => s.startsWith(x); 1287 if (skip('[]') || skip('interface') || skip('Declaration') || 1288 skip('Definition') || skip('DocumentSelector')) 1289 return false; 1290 return true; 1291} 1292 1293// Go signatures for methods. 1294function sig(nm: string, a: string, b: string, names?: boolean): string { 1295 if (a.indexOf('struct') != -1) { 1296 const v = a.split('\n'); 1297 extraTypes.set(`Param${nm}`, v.slice(1, v.length - 1)); 1298 a = 'Param' + nm; 1299 } 1300 if (a == 'void') 1301 a = ''; 1302 else if (a != '') { 1303 if (names) 1304 a = ', params *' + a; 1305 else 1306 a = ', *' + a; 1307 } 1308 let ret = 'error'; 1309 if (b != '' && b != 'void') { 1310 // avoid * when it is senseless 1311 if (indirect(b)) b = '*' + b; 1312 ret = `(${b}, error)`; 1313 } 1314 let start = `${nm}(`; 1315 if (names) { 1316 start = start + 'ctx '; 1317 } 1318 return `${start}context.Context${a}) ${ret}`; 1319} 1320 1321// write the request/notification code 1322function output(side: side) { 1323 // make sure the output file exists 1324 if (!side.outputFile) { 1325 side.outputFile = `ts${side.name}.go`; 1326 side.fd = fs.openSync(side.outputFile, 'w'); 1327 } 1328 const f = function (s: string) { 1329 fs.writeSync(side.fd!, s); 1330 fs.writeSync(side.fd!, '\n'); 1331 }; 1332 f(u.computeHeader(false)); 1333 f(` 1334 import ( 1335 "context" 1336 "encoding/json" 1337 1338 "golang.org/x/tools/internal/jsonrpc2" 1339 errors "golang.org/x/xerrors" 1340 ) 1341 `); 1342 const a = side.name[0].toUpperCase() + side.name.substring(1); 1343 f(`type ${a} interface {`); 1344 side.methods.forEach((v) => { f(v); }); 1345 f('}\n'); 1346 f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { 1347 switch r.Method() {`); 1348 side.cases.forEach((v) => { f(v); }); 1349 f(` 1350 default: 1351 return false, nil 1352 } 1353 }`); 1354 side.calls.forEach((v) => { f(v); }); 1355} 1356 1357// Handling of non-standard requests, so we can add gopls-specific calls. 1358function nonstandardRequests() { 1359 server.methods.push( 1360 'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)'); 1361 server.calls.push( 1362 `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { 1363 var result interface{} 1364 if err := s.sender.Call(ctx, method, params, &result); err != nil { 1365 return nil, err 1366 } 1367 return result, nil 1368 } 1369 `); 1370} 1371 1372// ----- remember it's a scripting language 1373function main() { 1374 if (u.gitHash != u.git()) { 1375 throw new Error( 1376 `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`); 1377 } 1378 u.createOutputFiles(); 1379 parse(); 1380 u.printAST(program); 1381 // find the Requests and Nofificatations 1382 for (const sourceFile of program.getSourceFiles()) { 1383 if (!sourceFile.isDeclarationFile) { 1384 ts.forEachChild(sourceFile, findRPCs); 1385 } 1386 } 1387 // separate RPCs into client and server 1388 setReceives(); 1389 // visit every sourceFile collecting top-level type definitions 1390 for (const sourceFile of program.getSourceFiles()) { 1391 if (!sourceFile.isDeclarationFile) { 1392 ts.forEachChild(sourceFile, genTypes); 1393 } 1394 } 1395 // check that each thing occurs exactly once, and put pointers into 1396 // seenTypes 1397 checkOnce(); 1398 // for each of Client and Server there are 3 parts to the output: 1399 // 1. type X interface {methods} 1400 // 2. func (h *serverHandler) Deliver(...) { switch r.method } 1401 // 3. func (x *xDispatcher) Method(ctx, parm) 1402 not.forEach( // notifications 1403 (v, k) => { 1404 receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k); 1405 }); 1406 req.forEach( // requests 1407 (v, k) => { 1408 receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k); 1409 }); 1410 nonstandardRequests(); 1411 // find all the types implied by seenTypes and rpcs to try to avoid 1412 // generating types that aren't used 1413 moreTypes(); 1414 // do merging 1415 cleanData(); 1416 // and print the Go code 1417 outputTypes(); 1418 console.log(`seen ${seenTypes.size + extraTypes.size}`); 1419 output(client); 1420 output(server); 1421} 1422 1423main(); 1424