1import * as fs from 'fs'; 2import * as ts from 'typescript'; 3 4// Need a general strategy for union types. This code tries (heuristically) 5// to choose one, but sometimes defaults (heuristically) to interface{} 6interface Const { 7 typeName: string // repeated in each const 8 goType: string 9 me: ts.Node 10 name: string // constant's name 11 value: string // constant's value 12} 13let Consts: Const[] = []; 14let seenConstTypes = new Map<string, boolean>(); 15 16interface Struct { 17 me: ts.Node 18 name: string 19 embeds?: string[] 20 fields?: Field[]; 21 extends?: string[] 22} 23let Structs: Struct[] = []; 24 25interface Field { 26 me: ts.Node 27 id: ts.Identifier 28 goName: string 29 optional: boolean 30 goType: string 31 json: string 32 gostuff?: string 33 substruct?: Field[] // embedded struct from TypeLiteral 34} 35 36interface Type { 37 me: ts.Node 38 goName: string 39 goType: string 40 stuff: string 41} 42let Types: Type[] = []; 43 44// Used in printing the AST 45let seenThings = new Map<string, number>(); 46function seenAdd(x: string) { 47 seenThings[x] = (seenThings[x] === undefined ? 1 : seenThings[x] + 1) 48} 49 50let dir = process.env['HOME']; 51const srcDir = '/vscode-languageserver-node' 52let fnames = [ 53 `${srcDir}/protocol/src/protocol.ts`, `${srcDir}/types/src/main.ts`, 54 `${srcDir}/jsonrpc/src/main.ts` 55]; 56let gitHash = 'fda16d6b63ba0fbdbd21d437ea810685528a0018'; 57let outFname = 'tsprotocol.go'; 58let fda: number, fdb: number, fde: number; // file descriptors 59 60function createOutputFiles() { 61 fda = fs.openSync('/tmp/ts-a', 'w') // dump of AST 62 fdb = fs.openSync('/tmp/ts-b', 'w') // unused, for debugging 63 fde = fs.openSync(outFname, 'w') // generated Go 64} 65function pra(s: string) { 66 return (fs.writeSync(fda, s)) 67} 68function prb(s: string) { 69 return (fs.writeSync(fdb, s)) 70} 71function prgo(s: string) { 72 return (fs.writeSync(fde, s)) 73} 74 75// struct names that don't need to go in the output 76let dontEmit = new Map<string, boolean>(); 77 78function generate(files: string[], options: ts.CompilerOptions): void { 79 let program = ts.createProgram(files, options); 80 program.getTypeChecker(); // used for side-effects 81 82 // dump the ast, for debugging 83 for (const sourceFile of program.getSourceFiles()) { 84 if (!sourceFile.isDeclarationFile) { 85 // walk the tree to do stuff 86 ts.forEachChild(sourceFile, describe); 87 } 88 } 89 pra('\n') 90 for (const key of Object.keys(seenThings).sort()) { 91 pra(`${key}: ${seenThings[key]}\n`) 92 } 93 94 // visit every sourceFile in the program, generating types 95 for (const sourceFile of program.getSourceFiles()) { 96 if (!sourceFile.isDeclarationFile) { 97 ts.forEachChild(sourceFile, genTypes) 98 } 99 } 100 return; 101 102 function genTypes(node: ts.Node) { 103 // Ignore top-level items that produce no output 104 if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) || 105 ts.isImportDeclaration(node) || ts.isVariableStatement(node) || 106 ts.isExportDeclaration(node) || ts.isEmptyStatement(node) || 107 node.kind == ts.SyntaxKind.EndOfFileToken) { 108 return; 109 } 110 if (ts.isInterfaceDeclaration(node)) { 111 doInterface(node) 112 return; 113 } else if (ts.isTypeAliasDeclaration(node)) { 114 doTypeAlias(node) 115 } else if (ts.isModuleDeclaration(node)) { 116 doModuleDeclaration(node) 117 } else if (ts.isEnumDeclaration(node)) { 118 doEnumDecl(node) 119 } else if (ts.isClassDeclaration(node)) { 120 doClassDeclaration(node) 121 } else { 122 throw new Error(`unexpected ${strKind(node)} ${loc(node)}`) 123 } 124 } 125 126 function doClassDeclaration(node: ts.ClassDeclaration) { 127 let id: ts.Identifier = node.name; 128 let props = new Array<ts.PropertyDeclaration>() 129 let extend: ts.HeritageClause; 130 let bad = false 131 node.forEachChild((n: ts.Node) => { 132 if (ts.isIdentifier(n)) { 133 return 134 } 135 if (ts.isPropertyDeclaration(n)) { 136 props.push(n); 137 return 138 } 139 if (n.kind == ts.SyntaxKind.ExportKeyword) { 140 return 141 } 142 if (n.kind == ts.SyntaxKind.Constructor || ts.isMethodDeclaration(n) || 143 ts.isGetAccessor(n) || ts.isSetAccessor(n) || 144 ts.isTypeParameterDeclaration(n)) { 145 bad = true; 146 return 147 } 148 if (ts.isHeritageClause(n)) { 149 return 150 } 151 if (n.kind == ts.SyntaxKind.AbstractKeyword) { 152 bad = true; // we think all of these are useless, but are unsure 153 return; 154 } 155 throw new Error(`doClass ${loc(n)} ${kinds(n)}`) 156 }) 157 if (bad) { 158 // the class is not useful for Go. 159 return 160 } 161 let fields: Field[] = []; 162 for (const pr of props) { 163 fields.push(fromPropDecl(pr)) 164 } 165 let ans = { 166 me: node, 167 name: toGoName(getText(id)), 168 extends: heritageStrs(extend), 169 fields: fields 170 }; 171 Structs.push(ans) 172 } 173 174 // called only from doClassDeclaration 175 function fromPropDecl(node: ts.PropertyDeclaration): Field { 176 let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name); 177 let opt = node.questionToken != undefined; 178 let typ: ts.Node = node.type; 179 const computed = computeType(typ); 180 let goType = computed.goType 181 let ans = { 182 me: node, 183 id: id, 184 goName: toGoName(getText(id)), 185 optional: opt, 186 goType: goType, 187 json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\``, 188 substruct: computed.fields 189 }; 190 return ans 191 } 192 193 function doInterface(node: ts.InterfaceDeclaration) { 194 // name: Identifier; 195 // typeParameters?: NodeArray<TypeParameterDeclaration>; 196 // heritageClauses?: NodeArray<HeritageClause>; 197 // members: NodeArray<TypeElement>; 198 199 // find the Identifier from children 200 // process the PropertySignature children 201 // the members might have generic info, but so do the children 202 let id: ts.Identifier = node.name 203 let extend: ts.HeritageClause 204 let generid: ts.Identifier 205 let properties = new Array<ts.PropertySignature>() 206 let index: ts.IndexSignatureDeclaration // generate some sort of map 207 let bad = false; // maybe we don't care about this one at all 208 node.forEachChild((n: ts.Node) => { 209 if (n.kind == ts.SyntaxKind.ExportKeyword || ts.isMethodSignature(n)) { 210 // ignore 211 } else if (ts.isIdentifier(n)) { 212 } else if (ts.isHeritageClause(n)) { 213 extend = n; 214 } else if (ts.isTypeParameterDeclaration(n)) { 215 // Act as if this is <T = any> 216 generid = n.name; 217 } else if (ts.isPropertySignature(n)) { 218 properties.push(n); 219 } else if (ts.isIndexSignatureDeclaration(n)) { 220 if (index !== undefined) { 221 throw new Error(`${loc(n)} multiple index expressions`) 222 } 223 index = n 224 } else if (n.kind == ts.SyntaxKind.CallSignature) { 225 bad = true; 226 } else { 227 throw new Error(`${loc(n)} doInterface ${strKind(n)} `) 228 } 229 }) 230 if (bad) return; 231 let fields: Field[] = []; 232 for (const p of properties) { 233 fields.push(genProp(p, generid)) 234 } 235 if (index != undefined) { 236 fields.push(fromIndexSignature(index)) 237 } 238 const ans = { 239 me: node, 240 name: toGoName(getText(id)), 241 extends: heritageStrs(extend), 242 fields: fields 243 }; 244 245 Structs.push(ans) 246 } 247 248 function heritageStrs(node: ts.HeritageClause): string[] { 249 // ExpressionWithTypeArguments+, and each is an Identifier 250 let ans: string[] = []; 251 if (node == undefined) { 252 return ans 253 } 254 let x: ts.ExpressionWithTypeArguments[] = [] 255 node.forEachChild((n: ts.Node) => { 256 if (ts.isExpressionWithTypeArguments(n)) x.push(n) 257 }) 258 for (const p of x) { 259 p.forEachChild((n: ts.Node) => { 260 if (ts.isIdentifier(n)) { 261 ans.push(toGoName(getText(n))); 262 return; 263 } 264 if (ts.isTypeReferenceNode(n)) { 265 // don't want these, ignore them 266 return; 267 } 268 throw new Error(`expected Identifier ${loc(n)} ${kinds(p)} `) 269 }) 270 } 271 return ans 272 } 273 274 // optional gen is the contents of <T> 275 function genProp(node: ts.PropertySignature, gen: ts.Identifier): Field { 276 let id: ts.Identifier 277 let thing: ts.Node 278 let opt = false 279 node.forEachChild((n: ts.Node) => { 280 if (ts.isIdentifier(n)) { 281 id = n 282 } else if (n.kind == ts.SyntaxKind.QuestionToken) { 283 opt = true 284 } else if (n.kind == ts.SyntaxKind.ReadonlyKeyword) { 285 return 286 } else { 287 if (thing !== undefined) { 288 throw new Error(`${loc(n)} weird`) 289 } 290 thing = n 291 } 292 }) 293 let goName = toGoName(id.text) 294 let { goType, gostuff, optional, fields } = computeType(thing) 295 // Generics 296 if (gen && gen.text == goType) goType = 'interface{}'; 297 opt = opt || optional; 298 let ans = { 299 me: node, 300 id: id, 301 goName: goName, 302 optional: opt, 303 goType: goType, 304 gostuff: gostuff, 305 substruct: fields, 306 json: `\`json:"${id.text}${opt ? ',omitempty' : ''}"\`` 307 }; 308 // These are not structs, so '*' would be wrong. 309 switch (goType) { 310 case 'CompletionItemKind': 311 case 'TextDocumentSyncKind': 312 case 'CodeActionKind': 313 case 'FailureHandlingKind': // string 314 case 'InsertTextFormat': // float64 315 case 'DiagnosticSeverity': 316 ans.optional = false 317 } 318 return ans 319 } 320 321 function doModuleDeclaration(node: ts.ModuleDeclaration) { 322 // Export Identifier ModuleBlock 323 let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name); 324 // Don't want FunctionDeclarations 325 // some of the VariableStatement are consts, and want their comments 326 // and each VariableStatement is Export, VariableDeclarationList 327 // and each VariableDeclarationList is a single VariableDeclaration 328 let v: ts.VariableDeclaration[] = []; 329 function f(n: ts.Node) { 330 if (ts.isVariableDeclaration(n)) { 331 v.push(n); 332 return 333 } 334 if (ts.isFunctionDeclaration(n)) { 335 return 336 } 337 n.forEachChild(f) 338 } 339 f(node) 340 for (const vx of v) { 341 if (hasNewExpression(vx)) { 342 return 343 } 344 buildConst(getText(id), vx) 345 } 346 } 347 348 function buildConst(tname: string, node: ts.VariableDeclaration): Const { 349 // node is Identifier, optional-goo, (FirstLiteralToken|StringLiteral) 350 let id: ts.Identifier = (ts.isIdentifier(node.name) && node.name); 351 let str: string 352 let first: string 353 node.forEachChild((n: ts.Node) => { 354 if (ts.isStringLiteral(n)) { 355 str = getText(n) 356 } else if (n.kind == ts.SyntaxKind.NumericLiteral) { 357 first = getText(n) 358 } 359 }) 360 if (str == undefined && first == undefined) { 361 return 362 } // various 363 const ty = (str != undefined) ? 'string' : 'float64' 364 const val = (str != undefined) ? str.replace(/'/g, '"') : first 365 const name = toGoName(getText(id)) 366 const c = { 367 typeName: tname, 368 goType: ty, 369 me: node.parent.parent, 370 name: name, 371 value: val 372 }; 373 Consts.push(c) 374 return c 375 } 376 377 // is node an ancestor of a NewExpression 378 function hasNewExpression(n: ts.Node): boolean { 379 let ans = false; 380 n.forEachChild((n: ts.Node) => { 381 if (ts.isNewExpression(n)) ans = true; 382 }) 383 return ans 384 } 385 386 function doEnumDecl(node: ts.EnumDeclaration) { 387 // Generates Consts. Identifier EnumMember+ 388 // EnumMember: Identifier StringLiteral 389 let id: ts.Identifier = node.name 390 let mems = node.members 391 let theType = 'string'; 392 for (const m of mems) { 393 let name: string 394 let value: string 395 m.forEachChild((n: ts.Node) => { 396 if (ts.isIdentifier(n)) { 397 name = getText(n) 398 } else if (ts.isStringLiteral(n)) { 399 value = getText(n).replace(/'/g, '"') 400 } else if (ts.isNumericLiteral(n)) { 401 value = getText(n); 402 theType = 'float64'; 403 } else { 404 throw new Error(`in doEnumDecl ${strKind(n)} ${loc(n)}`) 405 } 406 }) 407 let ans = { 408 typeName: getText(id), 409 goType: theType, 410 me: m, 411 name: name, 412 value: value 413 }; 414 Consts.push(ans) 415 } 416 } 417 418 // top-level TypeAlias 419 function doTypeAlias(node: ts.TypeAliasDeclaration) { 420 // these are all Export Identifier alias 421 let id: ts.Identifier = node.name; 422 let alias: ts.TypeNode = node.type; 423 let ans = { 424 me: node, 425 id: id, 426 goName: toGoName(getText(id)), 427 goType: '?', // filled in later in this function 428 stuff: '' 429 }; 430 if (ts.isUnionTypeNode(alias)) { 431 ans.goType = weirdUnionType(alias) 432 if (ans.goType == undefined) { 433 // these are mostly redundant; maybe sort them out later 434 return 435 } 436 if (ans.goType == 'interface{}') { 437 // we didn't know what to do, so explain the choice 438 ans.stuff = `// ` + getText(alias) 439 } 440 Types.push(ans) 441 return 442 } 443 if (ts.isIntersectionTypeNode(alias)) { // a Struct, not a Type 444 let embeds: string[] = [] 445 alias.forEachChild((n: ts.Node) => { 446 if (ts.isTypeReferenceNode(n)) { 447 const s = toGoName(computeType(n).goType) 448 embeds.push(s) 449 // It's here just for embedding, and not used independently, maybe 450 // PJW! 451 // dontEmit.set(s, true); // PJW: do we need this? 452 } else 453 throw new Error(`expected TypeRef ${strKind(n)} ${loc(n)}`) 454 }) 455 let ans = { me: node, name: toGoName(getText(id)), embeds: embeds }; 456 Structs.push(ans) 457 return 458 } 459 if (ts.isArrayTypeNode(alias)) { // []DocumentFilter 460 ans.goType = '[]DocumentFilter'; 461 Types.push(ans) 462 return 463 } 464 if (ts.isLiteralTypeNode(alias)) { 465 return // type A = 1, so nope 466 } 467 if (ts.isTypeLiteralNode(alias)) { 468 return; // type A = {...} 469 } 470 if (ts.isTypeReferenceNode(alias)) { 471 ans.goType = computeType(alias).goType 472 if (ans.goType.match(/und/) != null) throw new Error('396') 473 Types.push(ans) // type A B 474 return 475 } 476 if (alias.kind == ts.SyntaxKind.StringKeyword) { // type A string 477 ans.goType = 'string'; 478 Types.push(ans); 479 return 480 } 481 throw new Error( 482 `in doTypeAlias ${loc(alias)} ${kinds(node)}: ${strKind(alias)}\n`) 483 } 484 485 // string, or number, or DocumentFilter 486 function weirdUnionType(node: ts.UnionTypeNode): string { 487 let bad = false; 488 let aNumber = false; 489 let aString = false; 490 let tl: ts.TypeLiteralNode[] = [] 491 node.forEachChild((n: ts.Node) => { 492 if (ts.isTypeLiteralNode(n)) { 493 tl.push(n); 494 return; 495 } 496 if (ts.isLiteralTypeNode(n)) { 497 n.literal.kind == ts.SyntaxKind.NumericLiteral ? aNumber = true : 498 aString = true; 499 return; 500 } 501 if (n.kind == ts.SyntaxKind.NumberKeyword || 502 n.kind == ts.SyntaxKind.StringKeyword) { 503 n.kind == ts.SyntaxKind.NumberKeyword ? aNumber = true : aString = true; 504 return 505 } 506 bad = true 507 }) 508 if (bad) return; // none of these are useful (so far) 509 if (aNumber) { 510 if (aString) return 'interface{}'; 511 return 'float64'; 512 } 513 if (aString) return 'string'; 514 let x = computeType(tl[0]) 515 x.fields[0].json = x.fields[0].json.replace(/"`/, ',omitempty"`') 516 let out: string[] = []; 517 for (const f of x.fields) { 518 out.push(strField(f)) 519 } 520 out.push('}\n') 521 let ans = 'struct {\n'.concat(...out); 522 return ans 523 } 524 525 // complex and filled with heuristics 526 function computeType(node: ts.Node): { goType: string, gostuff?: string, optional?: boolean, fields?: Field[] } { 527 switch (node.kind) { 528 case ts.SyntaxKind.AnyKeyword: 529 case ts.SyntaxKind.ObjectKeyword: 530 return { goType: 'interface{}' }; 531 case ts.SyntaxKind.BooleanKeyword: 532 return { goType: 'bool' }; 533 case ts.SyntaxKind.NumberKeyword: 534 return { goType: 'float64' }; 535 case ts.SyntaxKind.StringKeyword: 536 return { goType: 'string' }; 537 case ts.SyntaxKind.NullKeyword: 538 case ts.SyntaxKind.UndefinedKeyword: 539 return { goType: 'nil' }; 540 } 541 if (ts.isArrayTypeNode(node)) { 542 let { goType, gostuff, optional } = computeType(node.elementType) 543 return ({ goType: '[]' + goType, gostuff: gostuff, optional: optional }) 544 } else if (ts.isTypeReferenceNode(node)) { 545 // typeArguments?: NodeArray<TypeNode>;typeName: EntityName; 546 // typeArguments won't show up in the generated Go 547 // EntityName: Identifier|QualifiedName 548 let tn: ts.EntityName = node.typeName; 549 if (ts.isQualifiedName(tn)) { 550 throw new Error(`qualified name at ${loc(node)}`); 551 } else if (ts.isIdentifier(tn)) { 552 return { goType: toGoName(tn.text) }; 553 } else { 554 throw new Error( 555 `expected identifier got ${strKind(node.typeName)} at ${loc(tn)}`) 556 } 557 } else if (ts.isLiteralTypeNode(node)) { 558 // string|float64 (are there other possibilities?) 559 // as of 20190908: only see string 560 const txt = getText(node); 561 let typ = 'float64' 562 if (txt.charAt(0) == '\'') { 563 typ = 'string' 564 } 565 return { goType: typ, gostuff: getText(node) }; 566 } else if (ts.isTypeLiteralNode(node)) { 567 // {[uri:string]: TextEdit[];} -> map[string][]TextEdit 568 let x: Field[] = []; 569 let indexCnt = 0 570 node.forEachChild((n: ts.Node) => { 571 if (ts.isPropertySignature(n)) { 572 x.push(genProp(n, undefined)) 573 return 574 } else if (ts.isIndexSignatureDeclaration(n)) { 575 indexCnt++ 576 x.push(fromIndexSignature(n)) 577 return 578 } 579 throw new Error(`${loc(n)} gotype ${strKind(n)}, not expected`) 580 }); 581 if (indexCnt > 0) { 582 if (indexCnt != 1 || x.length != 1) 583 throw new Error(`undexpected Index ${loc(x[0].me)}`) 584 // instead of {map...} just the map 585 return ({ goType: x[0].goType, gostuff: x[0].gostuff }) 586 } 587 return ({ goType: 'embedded!', fields: x }) 588 } else if (ts.isUnionTypeNode(node)) { 589 // The major heuristics 590 let x = new Array<{ goType: string, gostuff?: string, optiona?: boolean }>() 591 node.forEachChild((n: ts.Node) => { x.push(computeType(n)) }) 592 if (x.length == 2 && x[1].goType == 'nil') { 593 // Foo | null, or Foo | undefined 594 return x[0] // make it optional somehow? TODO 595 } 596 if (x[0].goType == 'bool') { // take it, mostly 597 if (x[1].goType == 'RenameOptions' || 598 x[1].goType == 'CodeActionOptions') { 599 return ({ goType: 'interface{}', gostuff: getText(node) }) 600 } 601 return ({ goType: 'bool', gostuff: getText(node) }) 602 } 603 // these are special cases from looking at the source 604 let gostuff = getText(node); 605 if (x[0].goType == `"off"` || x[0].goType == 'string') { 606 return ({ goType: 'string', gostuff: gostuff }) 607 } 608 if (x[0].goType == 'TextDocumentSyncOptions') { 609 // TextDocumentSyncOptions | TextDocumentSyncKind 610 return ({ goType: 'interface{}', gostuff: gostuff }) 611 } 612 if (x[0].goType == 'float64' && x[1].goType == 'string') { 613 return { 614 goType: 'interface{}', gostuff: gostuff 615 } 616 } 617 if (x[0].goType == 'MarkupContent' && x[1].goType == 'MarkedString') { 618 return { 619 goType: 'MarkupContent', gostuff: gostuff 620 } 621 } 622 if (x[0].goType == 'RequestMessage' && x[1].goType == 'ResponseMessage') { 623 return { 624 goType: 'interface{}', gostuff: gostuff 625 } 626 } 627 // Fail loudly 628 console.log(`UnionType ${loc(node)}`) 629 for (const v of x) { 630 console.log(`${v.goType}`) 631 } 632 throw new Error('in UnionType, weird') 633 } else if (ts.isParenthesizedTypeNode(node)) { 634 // check that this is (TextDocumentEdit | CreateFile | RenameFile | 635 // DeleteFile) TODO(pjw) IT IS NOT! FIX THIS! ALSO: 636 // (variousOptions & StaticFegistrationOptions) 637 return { 638 goType: 'TextDocumentEdit', gostuff: getText(node) 639 } 640 } else if (ts.isTupleTypeNode(node)) { 641 // in string | [number, number]. TODO(pjw): check it really is 642 return { 643 goType: 'string', gostuff: getText(node) 644 } 645 } else if (ts.isFunctionTypeNode(node)) { 646 // we don't want these members; mark them 647 return { 648 goType: 'bad', gostuff: getText(node) 649 } 650 } 651 throw new Error(`computeType unknown ${strKind(node)} at ${loc(node)}`) 652 } 653 654 function fromIndexSignature(node: ts.IndexSignatureDeclaration): Field { 655 let parm: ts.ParameterDeclaration 656 let at: ts.Node 657 node.forEachChild((n: ts.Node) => { 658 if (ts.isParameter(n)) { 659 parm = n 660 } else if ( 661 ts.isArrayTypeNode(n) || n.kind == ts.SyntaxKind.AnyKeyword || 662 ts.isUnionTypeNode(n)) { 663 at = n 664 } else 665 throw new Error(`fromIndexSig ${strKind(n)} ${loc(n)}`) 666 }) 667 let goType = computeType(at).goType 668 let id: ts.Identifier 669 parm.forEachChild((n: ts.Node) => { 670 if (ts.isIdentifier(n)) { 671 id = n 672 } else if (n.kind != ts.SyntaxKind.StringKeyword) { 673 throw new Error(`fromIndexSig expected string, ${strKind(n)} ${loc(n)}`) 674 } 675 }) 676 goType = `map[string]${goType}` 677 return { 678 me: node, goName: toGoName(id.text), id: null, goType: goType, 679 optional: false, json: `\`json:"${id.text}"\``, 680 gostuff: `${getText(node)}` 681 } 682 } 683 684 function toGoName(s: string): string { 685 let ans = s 686 if (s.charAt(0) == '_') { 687 ans = 'Inner' + s.substring(1) 688 } 689 else { ans = s.substring(0, 1).toUpperCase() + s.substring(1) }; 690 ans = ans.replace(/Uri$/, 'URI') 691 ans = ans.replace(/Id$/, 'ID') 692 return ans 693 } 694 695 // find the text of a node 696 function getText(node: ts.Node): string { 697 let sf = node.getSourceFile(); 698 let start = node.getStart(sf) 699 let end = node.getEnd() 700 return sf.text.substring(start, end) 701 } 702 // return a string of the kinds of the immediate descendants 703 function kinds(n: ts.Node): string { 704 let res = 'Seen ' + strKind(n); 705 function f(n: ts.Node): void { res += ' ' + strKind(n) }; 706 ts.forEachChild(n, f) 707 return res 708 } 709 710 function strKind(n: ts.Node): string { 711 const x = ts.SyntaxKind[n.kind]; 712 // some of these have two names 713 switch (x) { 714 default: 715 return x; 716 case 'FirstAssignment': 717 return 'EqualsToken'; 718 case 'FirstBinaryOperator': 719 return 'LessThanToken'; 720 case 'FirstCompoundAssignment': 721 return 'PlusEqualsToken'; 722 case 'FirstContextualKeyword': 723 return 'AbstractKeyword'; 724 case 'FirstLiteralToken': 725 return 'NumericLiteral'; 726 case 'FirstNode': 727 return 'QualifiedName'; 728 case 'FirstTemplateToken': 729 return 'NoSubstitutionTemplateLiteral'; 730 case 'LastTemplateToken': 731 return 'TemplateTail'; 732 case 'FirstTypeNode': 733 return 'TypePredicate'; 734 } 735 } 736 737 function describe(node: ts.Node) { 738 if (node === undefined) { 739 return 740 } 741 let indent = ''; 742 743 function f(n: ts.Node) { 744 seenAdd(kinds(n)) 745 if (ts.isIdentifier(n)) { 746 pra(`${indent} ${loc(n)} ${strKind(n)} ${n.text}\n`) 747 } 748 else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) { 749 pra(`${indent} ${loc(n)} ${strKind(n)}\n`) 750 } 751 else if (ts.isTypeLiteralNode(n)) { 752 let m = n.members 753 pra(`${indent} ${loc(n)} ${strKind(n)} ${m.length}\n`) 754 } 755 else { pra(`${indent} ${loc(n)} ${strKind(n)}\n`) }; 756 indent += ' ' 757 ts.forEachChild(n, f) 758 indent = indent.slice(0, indent.length - 2) 759 } 760 f(node) 761 } 762} 763 764function getComments(node: ts.Node): string { 765 const sf = node.getSourceFile(); 766 const start = node.getStart(sf, false) 767 const starta = node.getStart(sf, true) 768 const x = sf.text.substring(starta, start) 769 return x 770} 771 772function loc(node: ts.Node): string { 773 const sf = node.getSourceFile(); 774 const start = node.getStart() 775 const x = sf.getLineAndCharacterOfPosition(start) 776 const full = node.getFullStart() 777 const y = sf.getLineAndCharacterOfPosition(full) 778 let fn = sf.fileName 779 const n = fn.search(/-node./) 780 fn = fn.substring(n + 6) 781 return `${fn} ${x.line + 1}:${x.character + 1} (${y.line + 1}:${ 782 y.character + 1})` 783} 784 785function emitTypes() { 786 seenConstTypes.set('MessageQueue', true); // skip 787 for (const t of Types) { 788 if (seenConstTypes.get(t.goName)) continue; 789 if (t.goName == 'CodeActionKind') continue; // consts better choice 790 if (t.goType === undefined) continue; 791 let stuff = (t.stuff == undefined) ? '' : t.stuff; 792 prgo(`// ${t.goName} is a type\n`) 793 prgo(`${getComments(t.me)}`) 794 prgo(`type ${t.goName} = ${t.goType}${stuff}\n`) 795 seenConstTypes.set(t.goName, true); 796 } 797} 798 799let byName = new Map<string, Struct>(); 800function emitStructs() { 801 dontEmit.set('Thenable', true); 802 dontEmit.set('EmitterOptions', true); 803 dontEmit.set('MessageReader', true); 804 dontEmit.set('MessageWriter', true); 805 dontEmit.set('CancellationToken', true); 806 dontEmit.set('PipeTransport', true); 807 dontEmit.set('SocketTransport', true); 808 dontEmit.set('Item', true); 809 dontEmit.set('Event', true); 810 dontEmit.set('Logger', true); 811 dontEmit.set('Disposable', true); 812 dontEmit.set('PartialMessageInfo', true); 813 dontEmit.set('MessageConnection', true); 814 dontEmit.set('ResponsePromise', true); 815 dontEmit.set('ResponseMessage', true); 816 dontEmit.set('ErrorMessage', true); 817 dontEmit.set('NotificationMessage', true); 818 dontEmit.set('RequestHandlerElement', true); 819 dontEmit.set('RequestMessage', true); 820 dontEmit.set('NotificationHandlerElement', true); 821 dontEmit.set('Message', true); // duplicate of jsonrpc2:wire.go 822 dontEmit.set('LSPLogMessage', true); 823 dontEmit.set('InnerEM', true); 824 dontEmit.set('ResponseErrorLiteral', true); 825 dontEmit.set('TraceOptions', true); 826 dontEmit.set('MessageType', true); // want the enum 827 // backwards compatibility, done in requests.ts: 828 dontEmit.set('CancelParams', true); 829 830 for (const str of Structs) { 831 byName.set(str.name, str) 832 } 833 let seenName = new Map<string, boolean>() 834 for (const str of Structs) { 835 if (str.name == 'InitializeError') { 836 // only want its consts, not the struct 837 continue 838 } 839 if (seenName.get(str.name) || dontEmit.get(str.name)) { 840 continue 841 } 842 let noopt = false; 843 seenName.set(str.name, true) 844 prgo(genComments(str.name, getComments(str.me))) 845 prgo(`type ${str.name} struct {\n`) 846 // if it has fields, generate them 847 if (str.fields != undefined) { 848 for (const f of str.fields) { 849 prgo(strField(f, noopt)) 850 } 851 } 852 if (str.extends) { 853 // ResourceOperation just repeats the Kind field 854 for (const s of str.extends) { 855 if (s != 'ResourceOperation') 856 prgo(`\t${s}\n`) // what this type extends. 857 } 858 } else if (str.embeds) { 859 prb(`embeds: ${str.name}\n`); 860 noopt = (str.name == 'ClientCapabilities'); 861 // embedded struct. the hard case is from intersection types, 862 // where fields with the same name have to be combined into 863 // a single struct 864 let fields = new Map<string, Field[]>(); 865 for (const e of str.embeds) { 866 const nm = byName.get(e); 867 if (nm.embeds) throw new Error(`${nm.name} is an embedded embed`); 868 // each of these fields might be a something that needs amalgamating 869 for (const f of nm.fields) { 870 let x = fields.get(f.goName); 871 if (x === undefined) x = []; 872 x.push(f); 873 fields.set(f.goName, x); 874 } 875 } 876 fields.forEach((val, key) => { 877 if (val.length > 1) { 878 // merge the fields with the same name 879 prgo(strField(val[0], noopt, val)); 880 } else { 881 prgo(strField(val[0], noopt)); 882 } 883 }); 884 } 885 prgo(`}\n`); 886 } 887} 888 889function genComments(name: string, maybe: string): string { 890 if (maybe == '') return `\n\t// ${name} is\n`; 891 if (maybe.indexOf('/**') == 0) { 892 return maybe.replace('/**', `\n/*${name} defined:`) 893 } 894 throw new Error(`weird comment ${maybe.indexOf('/**')}`) 895} 896 897// Turn a Field into an output string 898// flds is for merging 899function strField(f: Field, noopt?: boolean, flds?: Field[]): string { 900 let ans: string[] = []; 901 let opt = (!noopt && f.optional) ? '*' : '' 902 switch (f.goType.charAt(0)) { 903 case 's': // string 904 case 'b': // bool 905 case 'f': // float64 906 case 'i': // interface{} 907 case '[': // []foo 908 opt = '' 909 } 910 let stuff = (f.gostuff == undefined) ? '' : ` // ${f.gostuff}` 911 ans.push(genComments(f.goName, getComments(f.me))) 912 if (flds === undefined && f.substruct == undefined) { 913 ans.push(`\t${f.goName} ${opt}${f.goType} ${f.json}${stuff}\n`) 914 } 915 else if (flds !== undefined) { 916 // The logic that got us here is imprecise, so it is possible that 917 // the fields are really all the same, and don't need to be 918 // combined into a struct. 919 let simple = true; 920 for (const ff of flds) { 921 if (ff.substruct !== undefined || byName.get(ff.goType) !== undefined) { 922 simple = false 923 break 924 } 925 } 926 if (simple) { 927 // should check that the ffs are really all the same 928 return strField(flds[0], noopt) 929 } 930 ans.push(`\t${f.goName} ${opt}struct{\n`); 931 for (const ff of flds) { 932 if (ff.substruct !== undefined) { 933 for (const x of ff.substruct) { 934 ans.push(strField(x, noopt)) 935 } 936 } else if (byName.get(ff.goType) !== undefined) { 937 const st = byName.get(ff.goType); 938 for (let i = 0; i < st.fields.length; i++) { 939 ans.push(strField(st.fields[i], noopt)) 940 } 941 } else { 942 ans.push(strField(ff, noopt)); 943 } 944 } 945 ans.push(`\t} ${f.json}${stuff}\n`); 946 } 947 else { 948 ans.push(`\t${f.goName} ${opt}struct {\n`) 949 for (const x of f.substruct) { 950 ans.push(strField(x, noopt)) 951 } 952 ans.push(`\t} ${f.json}${stuff}\n`) 953 } 954 return (''.concat(...ans)) 955} 956 957function emitConsts() { 958 // need the consts too! Generate modifying prefixes and suffixes to ensure 959 // consts are unique. (Go consts are package-level, but Typescript's are 960 // not.) Use suffixes to minimize changes to gopls. 961 let pref = new Map<string, string>([ 962 ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch'] 963 ]) // typeName->prefix 964 let suff = new Map<string, string>([ 965 ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat'] 966 ]) 967 for (const c of Consts) { 968 if (seenConstTypes.get(c.typeName)) { 969 continue 970 } 971 seenConstTypes.set(c.typeName, true); 972 if (pref.get(c.typeName) == undefined) { 973 pref.set(c.typeName, '') // initialize to empty value 974 } 975 if (suff.get(c.typeName) == undefined) { 976 suff.set(c.typeName, '') 977 } 978 prgo(`// ${c.typeName} defines constants\n`) 979 prgo(`type ${c.typeName} ${c.goType}\n`) 980 } 981 prgo('const (\n') 982 let seenConsts = new Map<string, boolean>() // to avoid duplicates 983 for (const c of Consts) { 984 const x = `${pref.get(c.typeName)}${c.name}${suff.get(c.typeName)}` 985 if (seenConsts.get(x)) { 986 continue 987 } 988 seenConsts.set(x, true) 989 if (c.value === undefined) continue; // didn't figure it out 990 if (x.startsWith('undefined')) continue; // what's going on here? 991 prgo(genComments(x, getComments(c.me))) 992 prgo(`\t${x} ${c.typeName} = ${c.value}\n`) 993 } 994 prgo(')\n') 995} 996 997function emitHeader(files: string[]) { 998 let lastMod = 0 999 let lastDate: Date 1000 for (const f of files) { 1001 const st = fs.statSync(f) 1002 if (st.mtimeMs > lastMod) { 1003 lastMod = st.mtimeMs 1004 lastDate = st.mtime 1005 } 1006 } 1007 let a = fs.readFileSync(`${dir}${srcDir}/.git/refs/heads/master`); 1008 prgo(`// Package protocol contains data types and code for LSP jsonrpcs\n`) 1009 prgo(`// generated automatically from vscode-languageserver-node\n`) 1010 prgo(`// commit: ${gitHash}\n`) 1011 prgo(`// last fetched ${lastDate}\n`) 1012 prgo('package protocol\n\n') 1013 prgo(`// Code generated (see typescript/README.md) DO NOT EDIT.\n`); 1014}; 1015 1016function git(): string { 1017 let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString(); 1018 // ref: refs/heads/foo, or a hash like cc12d1a1c7df935012cdef5d085cdba04a7c8ebe 1019 if (a.charAt(a.length - 1) == '\n') { 1020 a = a.substring(0, a.length - 1); 1021 } 1022 if (a.length == 40) { 1023 return a // a hash 1024 } 1025 if (a.substring(0, 5) == 'ref: ') { 1026 const fname = `${dir}${srcDir}/.git/` + a.substring(5); 1027 let b = fs.readFileSync(fname).toString() 1028 if (b.length == 41) { 1029 return b.substring(0, 40); 1030 } 1031 } 1032 throw new Error("failed to find the git commit hash") 1033} 1034 1035// ad hoc argument parsing: [-d dir] [-o outputfile], and order matters 1036function main() { 1037 if (gitHash != git()) { 1038 throw new Error(`git hash mismatch, wanted\n${gitHash} but source is at\n${git()}`) 1039 } 1040 let args = process.argv.slice(2) // effective command line 1041 if (args.length > 0) { 1042 let j = 0; 1043 if (args[j] == '-d') { 1044 dir = args[j + 1] 1045 j += 2 1046 } 1047 if (args[j] == '-o') { 1048 outFname = args[j + 1] 1049 j += 2 1050 } 1051 if (j != args.length) throw new Error(`incomprehensible args ${args}`) 1052 } 1053 let files: string[] = []; 1054 for (let i = 0; i < fnames.length; i++) { 1055 files.push(`${dir}${fnames[i]}`) 1056 } 1057 createOutputFiles() 1058 generate( 1059 files, { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS }); 1060 emitHeader(files) 1061 emitStructs() 1062 emitConsts() 1063 emitTypes() 1064} 1065 1066main() 1067