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