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