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