├── .dprint.jsonc ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── bun.lockb ├── index.ts ├── output ├── binder.go ├── checker.go ├── diagnostics │ └── diagnostics_generated.go ├── emitter.go ├── expressionToTypeNode.go ├── parser.go ├── program.go ├── scanner.go ├── types.go ├── utilities.go └── utilitiesPublic.go ├── package.json ├── processDiagnosticMessages.mjs └── tsconfig.json /.dprint.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | // If updating this, also update the config in dtsBundler.mjs. 3 | "indentWidth": 4, 4 | "lineWidth": 120, 5 | "newLineKind": "auto", 6 | "useTabs": false, 7 | "typescript": { 8 | "semiColons": "always", 9 | "quoteStyle": "preferDouble", 10 | "quoteProps": "consistent", 11 | "useBraces": "whenNotSingleLine", 12 | "bracePosition": "sameLineUnlessHanging", 13 | "singleBodyPosition": "sameLine", 14 | "nextControlFlowPosition": "nextLine", // Stroustrup style braces. 15 | "trailingCommas": "onlyMultiLine", 16 | "preferHanging": false, 17 | "operatorPosition": "maintain", 18 | 19 | "arrowFunction.useParentheses": "preferNone", 20 | "conditionalExpression.linePerExpression": false, // Keep our "match/case"-ish conditionals. 21 | "functionExpression.spaceAfterFunctionKeyword": true, 22 | "constructorType.spaceAfterNewKeyword": true, 23 | "constructSignature.spaceAfterNewKeyword": true, 24 | 25 | "module.sortImportDeclarations": "caseInsensitive", 26 | "module.sortExportDeclarations": "caseInsensitive", 27 | "exportDeclaration.sortNamedExports": "caseInsensitive", 28 | "importDeclaration.sortNamedImports": "caseInsensitive" 29 | }, 30 | "prettier": { 31 | "newLineKind": "lf", 32 | "associations": [ 33 | "**/*.{yaml,yml}" 34 | ], 35 | "yml.tabWidth": 2, 36 | "yaml.tabWidth": 2, 37 | "yml.singleQuote": true, 38 | "yaml.singleQuote": true 39 | }, 40 | "json": { 41 | // This would be good to do in known-JSONC files, but VS Code warns on trailing commas. 42 | "trailingCommas": "never" 43 | }, 44 | "excludes": [ 45 | "**/.git", 46 | "**/node_modules", 47 | "**/*-lock.json" 48 | ], 49 | // Note: if adding new languages, make sure settings.template.json is updated too. 50 | // Also, if updating typescript, update the one in package.json. 51 | "plugins": [ 52 | "https://plugins.dprint.dev/typescript-0.91.6.wasm", 53 | "https://plugins.dprint.dev/json-0.19.3.wasm", 54 | "https://plugins.dprint.dev/prettier-0.46.1.json@e5bd083088a8dfc6e5ce2d3c9bee81489b065bd5345ef55b59f5d96627928b7a" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go.txt linguist-language=TypeScript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript][typescriptreact][javascript][javascriptreact][json][jsonc][yaml][github-actions-workflow]": { 3 | "editor.defaultFormatter": "dprint.dprint", 4 | "editor.formatOnSave": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | 39 | You must cause any modified files to carry prominent notices stating that You changed the files; and 40 | 41 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 42 | 43 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-to-go 2 | 3 | This is a tool to convert TypeScript to Go; it's super hacky and only works for basic syntactic transformation of the TypeScript compiler itself. 4 | 5 | To run: 6 | 7 | ```bash 8 | bun run index.ts 9 | ``` 10 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakebailey/ts-to-go/8eed632491af8aee984117d84d90761a842e68f2/bun.lockb -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import cp from "child_process"; 3 | import CodeBlockWriter from "code-block-writer"; 4 | import path from "path"; 5 | import prettyMilliseconds from "pretty-ms"; 6 | import { 7 | ArrowFunction, 8 | AsExpression, 9 | BinaryExpression, 10 | Block, 11 | BodyableNode, 12 | type CaseOrDefaultClause, 13 | CommentStatement, 14 | ConditionalExpression, 15 | ConstructorDeclaration, 16 | Expression, 17 | ExpressionStatement, 18 | FunctionDeclaration, 19 | FunctionExpression, 20 | IfStatement, 21 | MethodDeclaration, 22 | Node, 23 | Project, 24 | Statement, 25 | ts, 26 | Type, 27 | TypeNode, 28 | TypeParameteredNode, 29 | } from "ts-morph"; 30 | import which from "which"; 31 | 32 | const root = "/home/jabaile/work/ts-native-pin/src/compiler"; 33 | function pathFor(s: string) { 34 | return path.join(root, s); 35 | } 36 | 37 | const tsCommit = cp.execFileSync(which.sync("git"), ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim(); 38 | 39 | const project = new Project({ 40 | tsConfigFilePath: pathFor("tsconfig.json"), 41 | }); 42 | 43 | const checker = project.getTypeChecker(); 44 | 45 | async function convert(filename: string, output: string, mainStruct?: string) { 46 | const start = performance.now(); 47 | 48 | const createFunction = mainStruct ? `create${mainStruct}` : undefined; 49 | let methodReceiver: string | undefined; 50 | if (mainStruct) { 51 | switch (mainStruct) { 52 | case "TypeChecker": 53 | mainStruct = "Checker"; 54 | methodReceiver = "c"; 55 | break; 56 | case "Binder": 57 | methodReceiver = "b"; 58 | break; 59 | case "Scanner": 60 | methodReceiver = "scanner"; 61 | break; 62 | case "Printer": 63 | methodReceiver = "printer"; 64 | break; 65 | case "SyntacticTypeNodeBuilder": 66 | methodReceiver = "stnb"; 67 | break; 68 | default: 69 | throw new Error(`Unknown main struct: ${mainStruct}`); 70 | } 71 | } 72 | 73 | console.log(`Converting ${filename} to ${output}...`); 74 | 75 | const sourceFile = project.getSourceFileOrThrow(pathFor(filename)); 76 | 77 | const writer = new CodeBlockWriter({ 78 | useTabs: true, 79 | }); 80 | 81 | writer.writeLine(`// Code generated by ts-to-go at ${tsCommit}. DO NOT EDIT.`); 82 | writer.newLine(); 83 | writer.writeLine("package output"); 84 | writer.newLine(); 85 | 86 | // writer.writeLine("func __COND__[C comparable, T any](cond C, a T, b T) T {"); 87 | // writer.indent(() => { 88 | // writer.writeLine("var zero C"); 89 | // writer.writeLine("if cond != zero {"); 90 | // writer.indent(() => { 91 | // writer.writeLine("return a"); 92 | // }); 93 | // writer.writeLine("}"); 94 | // writer.writeLine("return b"); 95 | // }); 96 | // writer.writeLine("}"); 97 | // writer.newLine(); 98 | 99 | function asComment(text: string) { 100 | text = text.replaceAll("*/", "* /"); 101 | text = text.replace(/(\r?\n\s*)+/gm, " "); 102 | assert(!text.includes("\n")); 103 | return `/* ${text} */`; 104 | } 105 | 106 | const todoCounts = new Map(); 107 | 108 | function writeTodoNode(node: Node) { 109 | let nodeKind = "Node"; 110 | if (Node.isBinaryExpression(node)) { 111 | const token = node.getOperatorToken().getKindName(); 112 | nodeKind = token; 113 | } 114 | else if (Node.isTypeNode(node)) { 115 | nodeKind = "TypeNode"; 116 | } 117 | else if (ts.isExpression(node.compilerNode)) { 118 | nodeKind = "Expression"; 119 | } 120 | const kindName = node.getKindName(); 121 | const todoName = `${nodeKind} ${kindName}`; 122 | const count = todoCounts.get(todoName) || 0; 123 | todoCounts.set(todoName, count + 1); 124 | writer.write(asComment(`TODO(TS-TO-GO) ${todoName}: ${node.getText()}`)); 125 | } 126 | 127 | function isIdentifier(text: string) { 128 | return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(text); 129 | } 130 | 131 | function isInterfaceTypeName(name: string) { 132 | switch (name) { 133 | case "Node": 134 | case "*Node": 135 | case "*ast.Node": 136 | case "Declaration": 137 | case "Expression": 138 | case "Statement": 139 | case "Type": 140 | case "*Type": 141 | case "*ast.Type": 142 | case "CancellationToken": 143 | case "any": 144 | case "unknown": 145 | case "void": 146 | case "Symbol": 147 | case "*ast.Symbol": 148 | return true; 149 | default: 150 | return false; 151 | } 152 | } 153 | 154 | function typeStringToGo(text: string): string { 155 | text = text.replace(/import\([^)]+\)\./g, ""); 156 | text = text.trim(); 157 | 158 | switch (text) { 159 | case "__String": 160 | return "string"; 161 | case "boolean": 162 | return "bool"; 163 | case "void": 164 | return "void"; 165 | case "object": 166 | return "any"; 167 | case "{}": 168 | return "any"; 169 | case "unknown": 170 | return "any"; 171 | case "Symbol": 172 | return "*ast.Symbol"; 173 | case "Node": 174 | return "*ast.Node"; 175 | case "Type": 176 | return "*Type"; 177 | } 178 | 179 | if (isIdentifier(text)) { 180 | return text; 181 | } 182 | 183 | const orUndefined = " | undefined"; 184 | if (!text.startsWith("*") && text.endsWith(orUndefined)) { 185 | text = text.slice(0, -orUndefined.length); 186 | const goType = typeStringToGo(text); 187 | if (isInterfaceTypeName(goType)) { 188 | return goType; 189 | } 190 | assert(goType); 191 | return "*" + goType; 192 | } 193 | 194 | const bracketBracket = "[]"; 195 | if (text.endsWith(bracketBracket)) { 196 | text = text.slice(0, -bracketBracket.length); 197 | return `[]${typeStringToGo(text)}`; 198 | } 199 | 200 | const readonly = "readonly "; 201 | if (text.startsWith(readonly)) { 202 | text = text.slice(readonly.length); 203 | return typeStringToGo(text); 204 | } 205 | 206 | const set = "Set<"; 207 | if (text.startsWith(set)) { 208 | text = text.slice(set.length, -1); 209 | return `Set[${typeStringToGo(text)}]`; 210 | } 211 | 212 | const map = "Map<"; 213 | if (text.startsWith(map)) { 214 | text = text.slice(map.length, -1); 215 | const [key, value] = text.split(", ", 2); 216 | return `Map[${typeStringToGo(key)}, ${typeStringToGo(value)}]`; 217 | } 218 | 219 | const nodeArray = "NodeArray<"; 220 | if (text.startsWith(nodeArray)) { 221 | text = text.slice(nodeArray.length, -1); 222 | return `NodeArray[${typeStringToGo(text)}]`; 223 | } 224 | 225 | const todoName = "Type"; 226 | const count = todoCounts.get(todoName) || 0; 227 | todoCounts.set(todoName, count + 1); 228 | return `${asComment("TODO(TS-TO-GO) inferred type " + text)} any`; 229 | } 230 | 231 | function writeType(host: Node, node: Type, isReturn?: boolean): void { 232 | const text = typeStringToGo(node.getText(host)); 233 | if (isReturn && text === "void") { 234 | return; 235 | } 236 | writer.write(text); 237 | } 238 | 239 | function visitTypeNode(type: TypeNode): void { 240 | // In Go, there's never a reason to parenthesize a type. 241 | while (Node.isParenthesizedTypeNode(type)) { 242 | type = type.getTypeNode(); 243 | } 244 | 245 | if (Node.isFunctionTypeNode(type)) { 246 | writer.write("func("); 247 | const params = type.getParameters(); 248 | for (let i = 0; i < params.length; i++) { 249 | const param = params[i]; 250 | writer.write(getNameOfNamed(param)); 251 | writer.write(" "); 252 | visitTypeNode(param.getTypeNodeOrThrow()); 253 | writer.conditionalWrite(i < params.length - 1, ", "); 254 | } 255 | writer.write(") "); 256 | const ret = type.getReturnTypeNodeOrThrow(); 257 | visitTypeNode(ret); 258 | } 259 | else if (Node.isBooleanKeyword(type)) { 260 | writer.write("bool"); 261 | } 262 | else if (Node.isStringKeyword(type)) { 263 | writer.write("string"); 264 | } 265 | else if (Node.isNumberKeyword(type)) { 266 | writer.write("number"); 267 | } 268 | else if (Node.isNeverKeyword(type)) { 269 | writer.write("never"); 270 | } 271 | else if (Node.isObjectKeyword(type)) { 272 | writer.write("any"); 273 | } 274 | else if (Node.isUndefinedKeyword(type)) { 275 | writer.write("undefined"); 276 | } 277 | else if (type.getText() === "void") { 278 | const parent = type.getParentIfKind(ts.SyntaxKind.FunctionType); 279 | if (parent?.getReturnTypeNode() !== type) { 280 | writer.write("void"); 281 | } 282 | } 283 | else if (Node.isArrayTypeNode(type)) { 284 | writer.write("[]"); 285 | visitTypeNode(type.getElementTypeNode()); 286 | } 287 | else if (Node.isTypeOperatorTypeNode(type)) { 288 | visitTypeNode(type.getTypeNode()); 289 | } 290 | else if (Node.isTypeReference(type)) { 291 | const name = type.getTypeName(); 292 | if (Node.isIdentifier(name)) { 293 | writer.write(typeStringToGo(sanitizeName(name.getText()))); 294 | } 295 | else { 296 | writeTodoNode(name); 297 | writer.write(` any`); 298 | } 299 | const typeArguments = type.getTypeArguments(); 300 | if (typeArguments.length > 0) { 301 | writer.write("["); 302 | for (let i = 0; i < typeArguments.length; i++) { 303 | visitTypeNode(typeArguments[i]); 304 | writer.conditionalWrite(i < typeArguments.length - 1, ", "); 305 | } 306 | writer.write("]"); 307 | } 308 | } 309 | else if (Node.isUnionTypeNode(type)) { 310 | if (type.getTypeNodes().length === 2) { 311 | let [a, b] = type.getTypeNodes(); 312 | if (Node.isUndefinedKeyword(a)) { 313 | [a, b] = [b, a]; 314 | } 315 | if (Node.isUndefinedKeyword(b)) { 316 | if (!isInterfaceTypeName(a.getText())) { 317 | writer.write("*"); 318 | } 319 | visitTypeNode(a); 320 | return; 321 | } 322 | } 323 | writer.write("Union["); 324 | for (const unionType of type.getTypeNodes()) { 325 | visitTypeNode(unionType); 326 | writer.write(", "); 327 | } 328 | writer.write("]"); 329 | } 330 | else if (Node.isAnyKeyword(type) || type.getText() === "unknown") { 331 | writer.write("any"); 332 | } 333 | else if (Node.isIntersectionTypeNode(type)) { 334 | writer.write("Intersection["); 335 | for (const intersectionType of type.getTypeNodes()) { 336 | visitTypeNode(intersectionType); 337 | writer.write(", "); 338 | } 339 | writer.write("]"); 340 | } 341 | else { 342 | writeTodoNode(type); 343 | writer.write(` any`); 344 | } 345 | } 346 | 347 | function writeConditionalExpression(node: ConditionalExpression, sideEffect: () => void) { 348 | if (Node.isConditionalExpression(node.getWhenTrue()) || Node.isConditionalExpression(node.getWhenFalse())) { 349 | writeConditionalExpressionAsSwitchCase(node, sideEffect); 350 | } 351 | else { 352 | writeConditionalExpressionAsIfElse(node, sideEffect); 353 | } 354 | } 355 | 356 | function writeConditionalExpressionAsSwitchCase(node: ConditionalExpression, sideEffect: () => void) { 357 | writer.newLineIfLastNot(); 358 | writer.write("switch {"); 359 | 360 | let ladder: Expression = node; 361 | while (Node.isConditionalExpression(ladder)) { 362 | const cond = ladder.getCondition(); 363 | const whenTrue = ladder.getWhenTrue(); 364 | const whenFalse = ladder.getWhenFalse(); 365 | 366 | writer.newLine(); 367 | writer.write("case "); 368 | visitExpression(cond, undefined, true); 369 | writer.write(":"); 370 | writer.indent(() => { 371 | if (Node.isConditionalExpression(whenTrue)) { 372 | writeConditionalExpression(whenTrue, sideEffect); 373 | } 374 | else { 375 | sideEffect(); 376 | visitExpression(whenTrue); 377 | } 378 | }); 379 | 380 | ladder = whenFalse; 381 | } 382 | 383 | writer.newLine(); 384 | writer.write("default:"); 385 | writer.indent(() => { 386 | sideEffect(); 387 | visitExpression(ladder); 388 | }); 389 | writer.newLine(); 390 | writer.write("}"); 391 | return; 392 | } 393 | 394 | function writeConditionalExpressionAsIfElse(node: ConditionalExpression, sideEffect: () => void) { 395 | const cond = node.getCondition(); 396 | const whenTrue = node.getWhenTrue(); 397 | const whenFalse = node.getWhenFalse(); 398 | 399 | writer.newLineIfLastNot(); 400 | writer.write("if "); 401 | visitExpression(cond, undefined, true); 402 | writer.write(" {"); 403 | writer.indent(() => { 404 | sideEffect(); 405 | visitExpression(whenTrue); 406 | }); 407 | writer.write("} else {"); 408 | writer.indent(() => { 409 | sideEffect(); 410 | visitExpression(whenFalse); 411 | }); 412 | writer.write("}"); 413 | } 414 | 415 | function isVariableForMainStruct(node: Node) { 416 | if (!createFunction) return false; 417 | const block = node.getFirstAncestorByKind(ts.SyntaxKind.Block); 418 | return block?.getParentIfKind(ts.SyntaxKind.FunctionDeclaration)?.getName() === createFunction; 419 | } 420 | 421 | function isNode(type: Type) { 422 | return type.getProperty("kind") && type.getProperty("parent"); 423 | } 424 | 425 | function isType(type: Type) { 426 | return type.getProperty("flags") && type.getProperty("symbol") && type.getProperty("checker"); 427 | } 428 | 429 | function isSymbol(type: Type) { 430 | return type.getProperty("flags") && type.getProperty("escapedName") && type.getProperty("valueDeclaration"); 431 | } 432 | 433 | function isElidedAsExpression(node: AsExpression) { 434 | const type = node.getTypeNodeOrThrow(); 435 | switch (type.getText()) { 436 | case "const": 437 | case "unknown": 438 | case "any": 439 | return true; 440 | } 441 | return false; 442 | } 443 | 444 | function visitExpression( 445 | node: Expression, 446 | inStatement?: boolean, 447 | needBool?: boolean, 448 | ): void { 449 | const text = node.getText(); 450 | 451 | if (Node.isRegularExpressionLiteral(node)) { 452 | const re = node.getLiteralValue(); 453 | let source = re.source.replaceAll("`", "\\`"); 454 | 455 | for (const flag of re.flags.split("")) { 456 | switch (flag) { 457 | case "i": 458 | source = `(?i)${source}`; 459 | break; 460 | default: 461 | writeTodoNode(node); 462 | writer.write(` TODO`); 463 | return; 464 | } 465 | } 466 | 467 | writer.write(`regexp.MustParse(\`${source}\`)`); 468 | } 469 | else if (Node.isLiteralExpression(node)) { 470 | if (Node.isStringLiteral(node)) { 471 | writer.write(JSON.stringify(node.getLiteralValue())); 472 | } 473 | else { 474 | writer.write(text); 475 | } 476 | } 477 | else if (Node.isAsExpression(node)) { 478 | if (isElidedAsExpression(node)) { 479 | visitExpression(node.getExpression(), undefined, needBool); 480 | return; 481 | } 482 | 483 | const type = node.getTypeNodeOrThrow(); 484 | 485 | if (Node.isTypeReference(type) && !Node.isStringLiteral(node.getExpression())) { 486 | const entity = type.getTypeName(); 487 | const sym = entity.getSymbolOrThrow(); 488 | const decl = sym.getValueDeclaration() ?? sym.getDeclarations()[0]; 489 | const isTypeVar = decl?.getKind() === ts.SyntaxKind.TypeParameter; 490 | 491 | if (Node.isIdentifier(entity) && !isTypeVar) { 492 | const type = entity.getType(); 493 | 494 | if (isNode(type) || isType(type)) { 495 | const sym = entity.getSymbolOrThrow(); 496 | const nodeName = sym.getName(); 497 | visitExpression(node.getExpression()); 498 | writer.write(`.As${nodeName}()`); 499 | return; 500 | } 501 | } 502 | } 503 | 504 | visitExpression(node.getExpression()); 505 | if (Node.isTypeReference(type) && !Node.isStringLiteral(node.getExpression())) { 506 | const entity = type.getTypeName(); 507 | if (Node.isIdentifier(entity)) { 508 | writer.write(".("); 509 | visitTypeNode(type); 510 | writer.write(")"); 511 | return; 512 | } 513 | } 514 | writer.write(` ${asComment("as " + node.getTypeNodeOrThrow().getText())}`); 515 | } 516 | else if (Node.isNonNullExpression(node)) { 517 | visitExpression(node.getExpression()); 518 | // writer.write("/* TODO(TS-TO-GO): was ! */"); 519 | } 520 | else if (Node.isIdentifier(node) || text === "this") { 521 | if (text === "undefined") { 522 | writer.write("nil"); 523 | } 524 | else { 525 | if (text === "type" && canRename(node, "t")) { 526 | writer.write("t"); 527 | return; 528 | } 529 | 530 | const id = sanitizeName(node.getText()); 531 | if (structFields.has(id)) { 532 | const nodeSym = node.getSymbolOrThrow(); 533 | const decl = nodeSym.getValueDeclaration() ?? nodeSym.getDeclarations()[0]; 534 | if (isVariableForMainStruct(decl)) { 535 | writer.write(`${methodReceiver}.${id}`); 536 | } 537 | else { 538 | writer.write(id); 539 | } 540 | } 541 | else { 542 | writer.write(id); 543 | } 544 | } 545 | } 546 | else if (Node.isCallExpression(node)) { 547 | const expression = node.getExpression(); 548 | 549 | if (Node.isPropertyAccessExpression(expression)) { 550 | const name = expression.getNameNode(); 551 | const expr = expression.getExpression(); 552 | if (Node.isStringLiteral(expr)) { 553 | switch (name.getText()) { 554 | case "padStart": 555 | writer.write("PAD_START("); 556 | visitExpression(expr); 557 | if (node.getArguments().length === 1) { 558 | writer.write(", "); 559 | const arg = node.getArguments()[0]; 560 | assert(Node.isExpression(arg)); 561 | visitExpression(arg); 562 | } 563 | writer.write(")"); 564 | return; 565 | default: 566 | throw new Error(`Unexpected string property access: ${name.getText()}`); 567 | } 568 | } 569 | else if (Node.isIdentifier(expr) && expr.getText() === "Math") { 570 | switch (name.getText()) { 571 | case "max": { 572 | writer.write("max("); 573 | const args = node.getArguments(); 574 | for (let i = 0; i < args.length; i++) { 575 | const arg = args[i]; 576 | assert(Node.isExpression(arg)); 577 | visitExpression(arg); 578 | writer.conditionalWrite(i < args.length - 1, ", "); 579 | } 580 | writer.write(")"); 581 | return; 582 | } 583 | case "min": { 584 | writer.write("min("); 585 | const args = node.getArguments(); 586 | for (let i = 0; i < args.length; i++) { 587 | const arg = args[i]; 588 | assert(Node.isExpression(arg)); 589 | visitExpression(arg); 590 | writer.conditionalWrite(i < args.length - 1, ", "); 591 | } 592 | writer.write(")"); 593 | return; 594 | } 595 | } 596 | } 597 | } 598 | 599 | 600 | if (Node.isIdentifier(expression)) { 601 | const newName = { 602 | "some": "core.Some", 603 | "filter": "core.Filter", 604 | "concatenate": "core.Concatenate", 605 | "map": "core.Map", 606 | "sameMap": "core.SameMap", 607 | "every": "core.Every", 608 | "find": "core.Find", 609 | "findIndex": "core.FindIndex", 610 | "findLast": "core.FindLast", 611 | "findLastIndex": "core.FindLastIndex", 612 | "first": "core.FirstOrNil", 613 | "last": "core.LastOrNil", 614 | "countWhere": "core.CountWhere", 615 | "memoize": "core.Memoize", 616 | "replaceElement": "core.ReplaceElement", 617 | "insertSorted": "core.InsertSorted", 618 | "appendIfUnique": "core.AppendIfUnique", 619 | }[expression.getText()]; 620 | if (newName) { 621 | writer.write(newName); 622 | } else { 623 | visitExpression(expression); 624 | } 625 | } else { 626 | visitExpression(expression); 627 | } 628 | 629 | writer.write("("); 630 | const args = node.getArguments(); 631 | for (let i = 0; i < args.length; i++) { 632 | const expr = args[i]; 633 | assert(Node.isExpression(expr)); 634 | visitExpression(expr); 635 | const comment = expr.getPreviousSibling()?.getTrailingCommentRanges().at(0)?.getText(); 636 | if (comment?.startsWith("/*")) { 637 | writer.write(comment); 638 | } 639 | writer.conditionalWrite(i < args.length - 1, ", "); 640 | } 641 | writer.write(")"); 642 | } 643 | else if (Node.isPrefixUnaryExpression(node)) { 644 | let token = ts.tokenToString(node.getOperatorToken())!; 645 | if (token === "~") { 646 | token = "^"; 647 | } 648 | 649 | if (token === "!") { 650 | let operand: Expression = node.getOperand(); 651 | let addParens = false; 652 | if ( 653 | Node.isPrefixUnaryExpression(operand) && 654 | operand.getOperatorToken() === ts.SyntaxKind.ExclamationToken 655 | ) { 656 | // Remove !! 657 | operand = operand.getOperand(); 658 | if (Node.isParenthesizedExpression(operand)) { 659 | operand = operand.getExpression(); 660 | } 661 | } 662 | else { 663 | writer.write("!"); 664 | if (isConvertibleToBool(operand.getType())) { 665 | addParens = true; 666 | } 667 | } 668 | if (addParens) writer.write("("); 669 | visitExpression(operand, undefined, true); 670 | if (addParens) writer.write(")"); 671 | return; 672 | } 673 | else if (inStatement && (token === "++" || token === "--")) { 674 | visitExpression(node.getOperand()); 675 | writer.write(token); 676 | } 677 | else if (token === "++" || token === "--") { 678 | writeTodoNode(node); 679 | writer.write(` TODO`); 680 | } 681 | else { 682 | writer.write(token); 683 | visitExpression(node.getOperand()); 684 | } 685 | } 686 | else if (Node.isParenthesizedExpression(node)) { 687 | let expr = node.getExpression(); 688 | let elide = false; 689 | 690 | if (Node.isAsExpression(expr) && isElidedAsExpression(expr)) { 691 | expr = expr.getExpression(); 692 | elide = true; 693 | } 694 | 695 | if ( 696 | elide 697 | || Node.isIdentifier(expr) 698 | || Node.isPropertyAccessExpression(expr) 699 | ) { 700 | visitExpression(expr, undefined, needBool); 701 | return; 702 | } 703 | 704 | writer.write("("); 705 | visitExpression(expr, undefined, needBool); 706 | writer.write(")"); 707 | return; 708 | } 709 | else if (Node.isBinaryExpression(node)) { 710 | writeBinaryExpression(node, inStatement, needBool); 711 | 712 | if (needBool) { 713 | switch (node.getOperatorToken().getKind()) { 714 | case ts.SyntaxKind.AmpersandAmpersandToken: 715 | case ts.SyntaxKind.BarBarToken: 716 | return; // handled in writeBinaryExpression 717 | } 718 | } 719 | } 720 | else if (Node.isTrueLiteral(node)) { 721 | writer.write("true"); 722 | } 723 | else if (Node.isFalseLiteral(node)) { 724 | writer.write("false"); 725 | } 726 | else if (Node.isPropertyAccessExpression(node)) { 727 | // Check for enum accesses first 728 | const expression = node.getExpression(); 729 | if (Node.isIdentifier(expression)) { 730 | const type = expression.getType(); 731 | if (type.isEnum()) { 732 | const enumName = fixEnumName(expression.getText()); 733 | writer.write(`${enumName}${getNameOfNamed(node)}`); 734 | return; 735 | } 736 | } 737 | 738 | const expr = node.getExpression(); 739 | const name = node.getNameNode(); 740 | if (Node.isStringLiteral(node.getExpression())) { 741 | switch (name.getText()) { 742 | case "length": 743 | writer.write("len("); 744 | visitExpression(expr); 745 | writer.write(")"); 746 | return; 747 | default: 748 | throw new Error(`Unexpected string property access: ${name.getText()}`); 749 | } 750 | } 751 | 752 | visitExpression(expr); 753 | if (node.hasQuestionDotToken()) { 754 | writer.write("/* ? */"); 755 | } 756 | 757 | let rightName = sanitizeName(name.getText()); 758 | 759 | const leftType = expr.getType(); 760 | if (isNode(leftType) || isSymbol(leftType)) { 761 | rightName = rightName[0].toUpperCase() + rightName.slice(1); 762 | } 763 | 764 | writer.write(`.${rightName}`); 765 | } 766 | else if (Node.isElementAccessExpression(node)) { 767 | visitExpression(node.getExpression()); 768 | writer.write("["); 769 | visitExpression(node.getArgumentExpressionOrThrow()); 770 | writer.write("]"); 771 | } 772 | else if (Node.isArrowFunction(node) || Node.isFunctionExpression(node)) { 773 | writer.write("func"); 774 | if (Node.isFunctionExpression(node)) { 775 | const name = node.getName(); 776 | if (name) { 777 | writer.write(` ${asComment(node.getName()!)}`); 778 | } 779 | if (node.isGenerator()) { 780 | writer.write(` ${asComment("generator")}`); 781 | } 782 | } 783 | 784 | writeFunctionParametersAndReturn(node); 785 | writer.write(" {"); 786 | writer.indent(() => { 787 | const body = node.getBody(); 788 | if (Node.isBlock(body)) { 789 | visitBlock(body); 790 | } 791 | else { 792 | assert(Node.isExpression(body)); 793 | if (Node.isConditionalExpression(body)) { 794 | writeConditionalExpression(body, () => writer.write("return ")); 795 | } 796 | else { 797 | writer.write("return "); 798 | visitExpression(body); 799 | } 800 | } 801 | }); 802 | writer.write("}"); 803 | } 804 | else if (Node.isNewExpression(node)) { 805 | const expression = node.getExpression(); 806 | if (Node.isIdentifier(expression)) { 807 | const name = sanitizeName(expression.getText()); 808 | writer.write(`New${name}`); 809 | const typeArguments = node.getTypeArguments(); 810 | if (typeArguments.length > 0) { 811 | writer.write("["); 812 | for (let i = 0; i < typeArguments.length; i++) { 813 | visitTypeNode(typeArguments[i]); 814 | writer.conditionalWrite(i < typeArguments.length - 1, ", "); 815 | } 816 | writer.write("]"); 817 | } 818 | writer.write("("); 819 | const args = node.getArguments(); 820 | for (let i = 0; i < args.length; i++) { 821 | const arg = args[i]; 822 | assert(Node.isExpression(arg)); 823 | visitExpression(arg); 824 | writer.conditionalWrite(i < args.length - 1, ", "); 825 | } 826 | writer.write(")"); 827 | } 828 | else { 829 | writeTodoNode(node); 830 | writer.write(` TODO`); 831 | } 832 | } 833 | else if (Node.isVoidExpression(node)) { 834 | visitExpression(node.getExpression()); 835 | return; 836 | } 837 | else if (Node.isYieldExpression(node)) { 838 | writer.write("yield("); 839 | visitExpression(node.getExpressionOrThrow()); 840 | writer.write(")"); 841 | return; 842 | } 843 | else if (Node.isPostfixUnaryExpression(node)) { 844 | const tokenStr = ts.tokenToString(node.getOperatorToken()); 845 | if (tokenStr) { 846 | if (!inStatement && (tokenStr === "++" || tokenStr === "--")) { 847 | writeTodoNode(node); 848 | writer.write(` TODO`); 849 | return; 850 | } 851 | 852 | visitExpression(node.getOperand()); 853 | writer.write(tokenStr); 854 | return; 855 | } 856 | writeTodoNode(node); 857 | writer.write(` TODO`); 858 | } 859 | else if (Node.isSpreadElement(node)) { 860 | visitExpression(node.getExpression()); 861 | writer.write("..."); 862 | return; 863 | } 864 | else if (Node.isArrayLiteralExpression(node)) { 865 | writer.write("[]"); 866 | const type = node.getType().getArrayElementType(); 867 | if (type) { 868 | writer.write(" "); 869 | writeType(node, type); 870 | } 871 | else { 872 | writer.write("any"); 873 | } 874 | 875 | writer.write("{"); 876 | const elements = node.getElements(); 877 | for (let i = 0; i < elements.length; i++) { 878 | const element = elements[i]; 879 | if (Node.isSpreadElement(element)) { 880 | writeTodoNode(element); 881 | } 882 | else { 883 | visitExpression(element); 884 | writer.conditionalWrite(i < elements.length - 1, ", "); 885 | } 886 | } 887 | writer.write("}"); 888 | } 889 | else if (Node.isObjectLiteralExpression(node)) { 890 | const type = node.getContextualType(); 891 | let typeName = type?.getText(node); 892 | if (typeName && !typeName.startsWith("{")) { 893 | typeName = typeStringToGo(typeName); 894 | if (typeName.startsWith("*")) { 895 | typeName = "&" + typeName.slice(1); 896 | } 897 | } 898 | else { 899 | typeName = undefined; 900 | } 901 | 902 | if (typeName) { 903 | writer.write(`${typeName}{`); 904 | } 905 | else { 906 | writer.write("map[any]any{ /* TODO(TS-TO-GO): was object literal */"); 907 | } 908 | 909 | writer.indent(() => { 910 | const quote = typeName ? "" : '"'; 911 | 912 | const properties = node.getProperties(); 913 | for (const prop of properties) { 914 | writeLeadingComments(prop); 915 | if (Node.isShorthandPropertyAssignment(prop)) { 916 | writer.write(`${quote}${getNameOfNamed(prop)}${quote}: ${getNameOfNamed(prop)}`); 917 | writer.write(","); 918 | } 919 | else if (Node.isPropertyAssignment(prop)) { 920 | writer.write(`${quote}${getNameOfNamed(prop)}${quote}: `); 921 | visitExpression(prop.getInitializerOrThrow()); 922 | writer.write(","); 923 | } 924 | else if (Node.isMethodDeclaration(prop)) { 925 | writer.write(`${quote}${getNameOfNamed(prop)}${quote}: func`); 926 | writeTypeParameters(prop); 927 | writeFunctionParametersAndReturn(prop); 928 | writeBody(prop); 929 | writer.write(","); 930 | } 931 | else { 932 | writeTodoNode(prop); 933 | } 934 | writeTrailingComments(prop); 935 | writer.newLineIfLastNot(); 936 | } 937 | }); 938 | writer.write("}"); 939 | } 940 | else if (Node.isConditionalExpression(node)) { 941 | const cond = node.getCondition(); 942 | const whenTrue = node.getWhenTrue(); 943 | const whenFalse = node.getWhenFalse(); 944 | // TODO: this causes side effects; should these be funcs? 945 | writer.write("ifElse("); 946 | visitExpression(cond, undefined, true); 947 | writer.write(", "); 948 | visitExpression(whenTrue, undefined, needBool); 949 | writer.write(", "); 950 | visitExpression(whenFalse, undefined, needBool); 951 | writer.write(")"); 952 | } 953 | else if (Node.isTemplateExpression(node)) { 954 | writer.write("__TEMPLATE__("); 955 | const head = node.getHead().getLiteralText(); 956 | if (head) { 957 | writer.write(JSON.stringify(head)); 958 | } 959 | 960 | const spans = node.getTemplateSpans(); 961 | 962 | for (let i = 0; i < spans.length; i++) { 963 | const span = spans[i]; 964 | if (i === 0 && head) { 965 | writer.write(", "); 966 | } 967 | 968 | visitExpression(span.getExpression()); 969 | const literal = span.getLiteral().getLiteralText(); 970 | if (literal) { 971 | writer.write(", "); 972 | writer.write(JSON.stringify(literal)); 973 | } 974 | if (i < spans.length - 1) { 975 | writer.write(", "); 976 | } 977 | } 978 | writer.write(")"); 979 | } 980 | else { 981 | writeTodoNode(node); 982 | writer.write(` TODO`); 983 | } 984 | 985 | if (needBool) { 986 | // keep in sync with isConvertibleToBool 987 | const type = node.getType(); 988 | // if type is union with undefined, add != nil 989 | if ( 990 | type.isUnion() && type.isNullable() && 991 | !type.getUnionTypes().some(t => 992 | t.isString() || t.isStringLiteral() 993 | || t.isBoolean() || t.isBooleanLiteral() 994 | || t.isNumber() || t.isNumberLiteral() 995 | || t.isEnum() || t.isEnumLiteral() 996 | ) 997 | ) { 998 | writer.write(" != nil"); 999 | } 1000 | else if (type.isString() || type.isStringLiteral()) { 1001 | writer.write(' != ""'); 1002 | } 1003 | else if (type.isNumber() || type.isNumberLiteral()) { 1004 | writer.write(" != 0"); 1005 | } 1006 | else if (type.isEnum() || type.isEnumLiteral()) { 1007 | writer.write(" != 0"); 1008 | } 1009 | } 1010 | } 1011 | 1012 | function isConvertibleToBool(type: Type) { 1013 | if ( 1014 | type.isUnion() && type.isNullable() && 1015 | !type.getUnionTypes().some(t => 1016 | t.isString() || t.isStringLiteral() 1017 | || t.isBoolean() || t.isBooleanLiteral() 1018 | || t.isNumber() || t.isNumberLiteral() 1019 | || t.isEnum() || t.isEnumLiteral() 1020 | ) 1021 | ) { 1022 | return true; 1023 | } 1024 | return type.isString() || type.isStringLiteral() 1025 | || type.isNumber() || type.isNumberLiteral() 1026 | || type.isEnum() || type.isEnumLiteral(); 1027 | } 1028 | 1029 | function isAssignmentOperator(kind: ts.SyntaxKind): boolean { 1030 | switch (kind) { 1031 | case ts.SyntaxKind.EqualsToken: 1032 | case ts.SyntaxKind.BarEqualsToken: 1033 | case ts.SyntaxKind.AmpersandEqualsToken: 1034 | case ts.SyntaxKind.PlusEqualsToken: 1035 | case ts.SyntaxKind.MinusEqualsToken: 1036 | case ts.SyntaxKind.AsteriskEqualsToken: 1037 | case ts.SyntaxKind.SlashEqualsToken: 1038 | case ts.SyntaxKind.PercentEqualsToken: 1039 | case ts.SyntaxKind.CaretEqualsToken: 1040 | case ts.SyntaxKind.LessThanLessThanEqualsToken: 1041 | case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: 1042 | case ts.SyntaxKind.BarBarEqualsToken: 1043 | return true; 1044 | default: 1045 | return false; 1046 | } 1047 | } 1048 | 1049 | function writeBinaryExpression(node: BinaryExpression, isStatement?: boolean, needBool?: boolean) { 1050 | const op = node.getOperatorToken(); 1051 | const left = node.getLeft(); 1052 | let right = node.getRight(); 1053 | 1054 | if (needBool) { 1055 | switch (op.getKind()) { 1056 | case ts.SyntaxKind.AmpersandAmpersandToken: 1057 | case ts.SyntaxKind.BarBarToken: 1058 | needBool = true; 1059 | break; 1060 | default: 1061 | needBool = false; 1062 | } 1063 | } 1064 | 1065 | let tok: string | undefined; 1066 | switch (op.getKind()) { 1067 | case ts.SyntaxKind.QuestionQuestionToken: 1068 | writer.write("ifNotNilElse("); 1069 | visitExpression(left); 1070 | writer.write(", "); 1071 | visitExpression(right); 1072 | writer.write(")"); 1073 | return; 1074 | case ts.SyntaxKind.AmpersandAmpersandToken: 1075 | case ts.SyntaxKind.BarBarToken: 1076 | case ts.SyntaxKind.LessThanEqualsToken: 1077 | case ts.SyntaxKind.LessThanToken: 1078 | case ts.SyntaxKind.GreaterThanEqualsToken: 1079 | case ts.SyntaxKind.GreaterThanToken: 1080 | case ts.SyntaxKind.MinusToken: 1081 | case ts.SyntaxKind.PlusToken: 1082 | case ts.SyntaxKind.AsteriskToken: 1083 | case ts.SyntaxKind.SlashToken: 1084 | case ts.SyntaxKind.AsteriskAsteriskToken: 1085 | case ts.SyntaxKind.CaretToken: 1086 | case ts.SyntaxKind.PercentToken: 1087 | case ts.SyntaxKind.AmpersandToken: 1088 | case ts.SyntaxKind.BarToken: 1089 | tok = ts.tokenToString(op.getKind())!; 1090 | break; 1091 | case ts.SyntaxKind.EqualsEqualsEqualsToken: 1092 | tok = "=="; 1093 | break; 1094 | case ts.SyntaxKind.ExclamationEqualsEqualsToken: 1095 | tok = "!="; 1096 | break; 1097 | default: 1098 | if (op.getKind() === ts.SyntaxKind.AmpersandEqualsToken) { 1099 | if (Node.isPrefixUnaryExpression(right) && right.getOperatorToken() === ts.SyntaxKind.TildeToken) { 1100 | tok = "&^="; 1101 | right = right.getOperand(); 1102 | } 1103 | } 1104 | 1105 | if (isStatement && op.getKind() === ts.SyntaxKind.QuestionQuestionEqualsToken) { 1106 | // convert a ??= b into if a == nil { a = b } 1107 | writer.write("if "); 1108 | visitExpression(left); 1109 | writer.write(" == nil { "); 1110 | visitExpression(left); 1111 | writer.write(" = "); 1112 | visitExpression(right); 1113 | writer.write(" }"); 1114 | return; 1115 | } 1116 | 1117 | if (isStatement && isAssignmentOperator(op.getKind())) { 1118 | tok ??= ts.tokenToString(op.getKind())!; 1119 | 1120 | if (Node.isConditionalExpression(right)) { 1121 | writeConditionalExpression(right, () => { 1122 | visitExpression(left); 1123 | if (tok === "||=") { 1124 | writer.write(" = "); 1125 | visitExpression(left); 1126 | writer.write(" || "); 1127 | } 1128 | else if (tok === "&&=") { 1129 | writer.write(" = "); 1130 | visitExpression(left); 1131 | writer.write(" && "); 1132 | } 1133 | else { 1134 | writer.write(` ${tok} `); 1135 | } 1136 | }); 1137 | return; 1138 | } 1139 | 1140 | break; 1141 | } 1142 | 1143 | writeTodoNode(node); 1144 | writer.write(` TODO`); 1145 | return; 1146 | } 1147 | 1148 | assert(tok); 1149 | 1150 | visitExpression(left, undefined, needBool); 1151 | if (tok === "||=") { 1152 | writer.write(" = "); 1153 | visitExpression(left); 1154 | writer.write(" || "); 1155 | } 1156 | else if (tok === "&&=") { 1157 | writer.write(" = "); 1158 | visitExpression(left); 1159 | writer.write(" && "); 1160 | } 1161 | else { 1162 | writer.write(` ${tok} `); 1163 | } 1164 | visitExpression(right, undefined, needBool); 1165 | } 1166 | 1167 | const referencesCache = new Map(); 1168 | function findReferencesAsNodes(node: Node): Node[] { 1169 | if (!Node.isReferenceFindable(node)) { 1170 | return []; 1171 | } 1172 | 1173 | if (referencesCache.has(node)) { 1174 | return referencesCache.get(node)!; 1175 | } 1176 | 1177 | const references = node.findReferencesAsNodes(); 1178 | referencesCache.set(node, references); 1179 | return references; 1180 | } 1181 | 1182 | function canRename(node: Node, name: string) { 1183 | const symbol = node.getSymbol(); 1184 | if (!symbol) return false; 1185 | 1186 | const declarations = symbol.getDeclarations(); 1187 | for (const decl of declarations) { 1188 | for (const ref of findReferencesAsNodes(decl)) { 1189 | const other = checker.resolveName(name, ref, ts.SymbolFlags.Value, true); 1190 | if (other && other !== symbol) { 1191 | return false; 1192 | } 1193 | } 1194 | } 1195 | 1196 | return true; 1197 | } 1198 | 1199 | function sanitizeName(name: string | undefined) { 1200 | switch (name) { 1201 | case "break": 1202 | case "case": 1203 | case "chan": 1204 | case "const": 1205 | case "continue": 1206 | case "default": 1207 | case "defer": 1208 | case "else": 1209 | case "fallthrough": 1210 | case "for": 1211 | case "func": 1212 | case "go": 1213 | case "goto": 1214 | case "if": 1215 | case "import": 1216 | case "interface": 1217 | case "map": 1218 | case "package": 1219 | case "range": 1220 | case "return": 1221 | case "select": 1222 | case "struct": 1223 | case "switch": 1224 | case "type": 1225 | case "var": 1226 | name = `${name}_`; 1227 | break; 1228 | default: 1229 | name = name || "TODO"; 1230 | break; 1231 | } 1232 | 1233 | name = name.replaceAll("$", "_DOLLAR_"); 1234 | 1235 | return name; 1236 | } 1237 | 1238 | function getNameOfNamed(node: Node & { getName(): string | undefined; }) { 1239 | const unsanitized = node.getName(); 1240 | if (unsanitized === "type" && canRename(node, "t")) { 1241 | return "t"; 1242 | } 1243 | const name = sanitizeName(node.getName()); 1244 | if (/^[a-zA-Z0-9_]+$/.test(name)) { 1245 | return name; 1246 | } 1247 | return `TODO_IDENTIFIER`; 1248 | } 1249 | 1250 | const enumNames = new Map([ 1251 | ["SyntaxKind", "ast.Kind"], 1252 | ["CheckFlags", "ast.CheckFlags"], 1253 | ["ModifierFlags", "ast.ModifierFlags"], 1254 | ["NodeFlags", "ast.NodeFlags"], 1255 | ["SymbolFlags", "ast.SymbolFlags"], 1256 | ]); 1257 | 1258 | function fixEnumName(originalName: string): string { 1259 | const enumName = enumNames.get(originalName); 1260 | return enumName ?? originalName; 1261 | } 1262 | 1263 | function visitIfStatement(node: IfStatement) { 1264 | writer.write("if "); 1265 | visitExpression(node.getExpression(), undefined, true); 1266 | 1267 | writer.write(" {"); 1268 | writer.indent(() => { 1269 | const thenStatement = node.getThenStatement(); 1270 | if (thenStatement) { 1271 | if (Node.isBlock(thenStatement)) { 1272 | visitBlock(thenStatement); 1273 | } 1274 | else if (Node.isStatement(thenStatement)) { 1275 | visitStatement(thenStatement); 1276 | } 1277 | else { 1278 | writeTodoNode(node); 1279 | } 1280 | } 1281 | writer.newLineIfLastNot(); 1282 | }); 1283 | 1284 | const elseStatement = node.getElseStatement(); 1285 | if (elseStatement) { 1286 | writer.write("} else "); 1287 | if (Node.isIfStatement(elseStatement)) { 1288 | return visitIfStatement(elseStatement); 1289 | } 1290 | writer.write("{"); 1291 | writer.indent(() => { 1292 | if (Node.isBlock(elseStatement)) { 1293 | visitBlock(elseStatement); 1294 | } 1295 | else if (Node.isStatement(elseStatement)) { 1296 | visitStatement(elseStatement); 1297 | } 1298 | else { 1299 | writeTodoNode(node); 1300 | } 1301 | }); 1302 | writer.write("}"); 1303 | } 1304 | else { 1305 | writer.write("}"); 1306 | } 1307 | } 1308 | function visitExpressionStatement(node: ExpressionStatement) { 1309 | const expression = node.getExpression(); 1310 | visitExpression(expression, true); 1311 | writer.newLine(); 1312 | } 1313 | 1314 | function writeTypeParameters(node: TypeParameteredNode) { 1315 | const typeParameters = node.getTypeParameters(); 1316 | if (typeParameters.length > 0) { 1317 | writer.write("["); 1318 | 1319 | for (let i = 0; i < typeParameters.length; i++) { 1320 | const typeParameter = typeParameters[i]; 1321 | writer.write(getNameOfNamed(typeParameter)); 1322 | const constraint = typeParameter.getConstraint(); 1323 | if (constraint) { 1324 | writer.write(" "); 1325 | visitTypeNode(constraint); 1326 | } 1327 | else { 1328 | writer.write(" any"); 1329 | } 1330 | writer.conditionalWrite(i < typeParameters.length - 1, ", "); 1331 | } 1332 | 1333 | writer.write("]"); 1334 | } 1335 | } 1336 | 1337 | function writeFunctionParametersAndReturn( 1338 | node: FunctionDeclaration | ArrowFunction | MethodDeclaration | FunctionExpression | ConstructorDeclaration, 1339 | ) { 1340 | writer.write("("); 1341 | const params = node.getParameters(); 1342 | for (let i = 0; i < params.length; i++) { 1343 | const param = params[i]; 1344 | writer.write(getNameOfNamed(param)); 1345 | writer.write(" "); 1346 | const paramType = param.getTypeNode(); 1347 | if (paramType) { 1348 | visitTypeNode(paramType); 1349 | } 1350 | else { 1351 | writeType(node, param.getType()); 1352 | } 1353 | const initializer = param.getInitializer(); 1354 | if (initializer) { 1355 | writer.write(` ${asComment(" = " + initializer.getText())}`); 1356 | } 1357 | writer.conditionalWrite(i < params.length - 1, ", "); 1358 | } 1359 | writer.write(")"); 1360 | const ret = node.getReturnType(); 1361 | if (!ret.isVoid()) { 1362 | writer.write(" "); 1363 | const retNode = node.getReturnTypeNode(); 1364 | if (retNode) { 1365 | if (Node.isTypePredicate(retNode)) { 1366 | if (retNode.hasAssertsModifier()) { 1367 | visitTypeNode(retNode); 1368 | } 1369 | else { 1370 | writer.write("bool"); 1371 | } 1372 | } 1373 | else { 1374 | visitTypeNode(retNode); 1375 | } 1376 | } 1377 | else { 1378 | writeType(node, ret, true); 1379 | } 1380 | } 1381 | } 1382 | 1383 | function writeBody(node: BodyableNode) { 1384 | writer.write(" {"); 1385 | writer.indent(() => { 1386 | const body = node.getBodyOrThrow(); 1387 | assert(Node.isBlock(body)); 1388 | visitBlock(body); 1389 | }); 1390 | writer.write("}"); 1391 | } 1392 | 1393 | function visitBlock(node: Block) { 1394 | node.getStatementsWithComments().forEach(visitStatement); 1395 | } 1396 | 1397 | function writeLeadingComments(node: Node) { 1398 | for (const range of node.getLeadingCommentRanges()) { 1399 | if (range.compilerObject.hasTrailingNewLine) { 1400 | writer.writeLine(range.getText()); 1401 | } 1402 | else { 1403 | writer.write(range.getText()); 1404 | } 1405 | } 1406 | } 1407 | 1408 | function writeTrailingComments(node: Node) { 1409 | for (const range of node.getTrailingCommentRanges()) { 1410 | if (range.compilerObject.hasTrailingNewLine) { 1411 | writer.writeLine(range.getText()); 1412 | } 1413 | else { 1414 | writer.write(range.getText()); 1415 | } 1416 | } 1417 | } 1418 | 1419 | let mainStructScanState: "before" | "after" | (FunctionDeclaration | CommentStatement)[] = "before"; 1420 | 1421 | function visitStatement(node: Statement) { 1422 | let isStructMethod = false; 1423 | if (isVariableForMainStruct(node)) { 1424 | if (Node.isFunctionDeclaration(node) || Node.isCommentStatement(node)) { 1425 | if (mainStructScanState === "before") { 1426 | // Do nothing 1427 | } 1428 | else if (mainStructScanState === "after") { 1429 | isStructMethod = true; 1430 | } 1431 | else { 1432 | mainStructScanState.push(node); 1433 | return; 1434 | } 1435 | } 1436 | else if (Node.isReturnStatement(node)) { 1437 | mainStructScanState = []; 1438 | } 1439 | } 1440 | 1441 | writer.newLineIfLastNot(); 1442 | if (!node.getPreviousSibling()?.getKindName()?.endsWith("Trivia")) { 1443 | writeLeadingComments(node); 1444 | } 1445 | 1446 | visitStatement2(node, isStructMethod); 1447 | 1448 | if (!node.getNextSibling()?.getKindName()?.endsWith("Trivia")) { 1449 | writeTrailingComments(node); 1450 | } 1451 | } 1452 | 1453 | function visitStatement2(node: Statement, isStructMethod: boolean) { 1454 | writer.newLineIfLastNot(); 1455 | 1456 | // If more than one newline, add another newline. 1457 | const prev = node.getPreviousSibling()?.getEnd(); 1458 | if (prev !== undefined) { 1459 | // This is much faster than asking ts-morph for the line numbers. 1460 | const text = sourceFile.getText(); 1461 | const first = text.indexOf("\n", prev); 1462 | if (first !== -1) { 1463 | const second = text.indexOf("\n", first + 1); 1464 | if (second !== -1 && second < node.getStart()) { 1465 | writer.newLine(); 1466 | } 1467 | } 1468 | } 1469 | 1470 | const isGlobal = node.getParentIf(p => Node.isSourceFile(p) || Node.isModuleBlock(p)) !== undefined; 1471 | 1472 | if (Node.isImportDeclaration(node)) { 1473 | return; 1474 | } 1475 | 1476 | if (Node.isTypeAliasDeclaration(node)) { 1477 | writer.write(`type ${getNameOfNamed(node)}`); 1478 | writeTypeParameters(node); 1479 | writer.write(" "); 1480 | visitTypeNode(node.getTypeNodeOrThrow()); 1481 | 1482 | writer.newLineIfLastNot(); 1483 | return; 1484 | } 1485 | 1486 | if (Node.isFunctionDeclaration(node)) { 1487 | if (!node.hasBody()) { 1488 | writer.write(asComment(`OVERLOAD: ${node.getText()}`)); 1489 | return; 1490 | } 1491 | 1492 | const isGlobal = node.getParentIf(p => Node.isSourceFile(p) || Node.isModuleBlock(p)) !== undefined; 1493 | 1494 | if (isStructMethod) { 1495 | writer.write(`func (${methodReceiver} *${mainStruct}) ${getNameOfNamed(node)}`); 1496 | } 1497 | else if (!isGlobal) { 1498 | writer.write(`${getNameOfNamed(node)} := func`); 1499 | if (node.isGenerator()) { 1500 | writer.write("/* generator */"); 1501 | } 1502 | } 1503 | else { 1504 | writer.write(`func `); 1505 | if (node.isGenerator()) { 1506 | writer.write("/* generator */"); 1507 | } 1508 | writer.write(`${getNameOfNamed(node)}`); 1509 | } 1510 | 1511 | writeFunctionParametersAndReturn(node); 1512 | writeBody(node); 1513 | 1514 | writer.newLine(); 1515 | writer.newLine(); 1516 | 1517 | if (createFunction && node.getName() === createFunction) { 1518 | const funcs = mainStructScanState; 1519 | assert(Array.isArray(funcs)); 1520 | mainStructScanState = "after"; 1521 | // for (const f of funcs) { 1522 | // console.log(f.getText()); 1523 | // } 1524 | funcs.forEach(visitStatement); 1525 | } 1526 | 1527 | return; 1528 | } 1529 | 1530 | if (Node.isInterfaceDeclaration(node)) { 1531 | writer.write(`type ${getNameOfNamed(node)}`); 1532 | writeTypeParameters(node); 1533 | writer.write(" struct {"); 1534 | writer.indent(() => { 1535 | const members = node.getMembers(); 1536 | for (const member of members) { 1537 | if (Node.isPropertySignature(member)) { 1538 | writer.write(`${getNameOfNamed(member)} `); 1539 | visitTypeNode(member.getTypeNodeOrThrow()); 1540 | writer.newLine(); 1541 | } 1542 | else { 1543 | writeTodoNode(node); 1544 | } 1545 | } 1546 | }); 1547 | writer.write("}"); 1548 | 1549 | writer.newLineIfLastNot(); 1550 | return; 1551 | } 1552 | 1553 | if (Node.isVariableStatement(node)) { 1554 | const isGlobal = node.getParentIf(p => Node.isSourceFile(p) || Node.isModuleBlock(p)) !== undefined; 1555 | 1556 | const declarations = node.getDeclarations(); 1557 | 1558 | for (const declaration of declarations) { 1559 | const typeNode = declaration.getTypeNode(); 1560 | const initializer = declaration.getInitializer(); 1561 | 1562 | if (isVariableForMainStruct(declaration)) { 1563 | if (initializer) { 1564 | writer.write(`tc.${getNameOfNamed(declaration)} = `); 1565 | visitExpression(initializer); 1566 | } 1567 | continue; 1568 | } 1569 | 1570 | if (!isGlobal && Node.isConditionalExpression(initializer)) { 1571 | writer.write(`var ${getNameOfNamed(declaration)} `); 1572 | if (typeNode) { 1573 | visitTypeNode(typeNode); 1574 | } 1575 | else { 1576 | writeType(declaration, declaration.getType()); 1577 | } 1578 | writeConditionalExpression( 1579 | initializer, 1580 | () => writer.write(`${getNameOfNamed(declaration)} = `), 1581 | ); 1582 | } 1583 | else if (isGlobal) { 1584 | writer.write(`var ${getNameOfNamed(declaration)}`); 1585 | if (typeNode) { 1586 | writer.write(" "); 1587 | visitTypeNode(typeNode); 1588 | } 1589 | if (initializer) { 1590 | writer.write(" = "); 1591 | visitExpression(initializer); 1592 | } 1593 | } 1594 | else { 1595 | if (typeNode) { 1596 | writer.write(`var ${getNameOfNamed(declaration)} `); 1597 | visitTypeNode(typeNode); 1598 | if (initializer) { 1599 | writer.write(" = "); 1600 | visitExpression(initializer); 1601 | } 1602 | } 1603 | else if (initializer) { 1604 | writer.write(`${getNameOfNamed(declaration)} := `); 1605 | visitExpression(initializer); 1606 | } 1607 | else { 1608 | // No annotation or inferred type, comes from something later... 1609 | writer.write(`var ${getNameOfNamed(declaration)} TODO`); 1610 | } 1611 | } 1612 | writer.newLineIfLastNot(); 1613 | } 1614 | 1615 | writer.newLineIfLastNot(); 1616 | return; 1617 | } 1618 | 1619 | if (Node.isEnumDeclaration(node)) { 1620 | let enumName = getNameOfNamed(node); 1621 | const newEnumName = fixEnumName(enumName); 1622 | 1623 | if (enumName !== newEnumName) { 1624 | writer.writeLine(`// Should be ${newEnumName}`); 1625 | enumName = newEnumName.split(".")[1]; 1626 | } 1627 | 1628 | writer.write(`type ${enumName}`); 1629 | if (node.getMembers()[0].getInitializer()?.getKindName() === "StringLiteral") { 1630 | writer.write(` string`); 1631 | } 1632 | else { 1633 | writer.write(` int32`); 1634 | } 1635 | 1636 | writer.writeLine(`const (`); 1637 | writer.indent(() => { 1638 | const members = node.getMembers(); 1639 | const nameMapping = new Map(); 1640 | for (const member of members) { 1641 | const memberName = getNameOfNamed(member); 1642 | nameMapping.set(memberName, `${enumName}${memberName}`); 1643 | } 1644 | const replacers = [...nameMapping.entries()].sort((a, b) => b[0].length - a[0].length).map(([name, value]) => { 1645 | const regexp = new RegExp(`\\b${name}`, "g") 1646 | return (s: string) => { 1647 | return s.replace(regexp, value); 1648 | } 1649 | }); 1650 | 1651 | for (let i = 0; i < members.length; i++) { 1652 | const member = members[i]; 1653 | writeLeadingComments(member); 1654 | const memberName = nameMapping.get(getNameOfNamed(member))!; 1655 | writer.write(`${memberName} `); 1656 | const initializer = member.getInitializer(); 1657 | if (i === 0 && !initializer) { 1658 | writer.write(`${enumName} = iota`); 1659 | } 1660 | else if (initializer) { 1661 | let initializerText = initializer.getText(); 1662 | 1663 | for (const replacer of replacers) { 1664 | initializerText = replacer(initializerText); 1665 | } 1666 | 1667 | initializerText = initializerText.replaceAll("~", "^"); 1668 | initializerText = initializerText.replaceAll(/\r?\n/g, ""); 1669 | 1670 | writer.write(`${enumName} = ${initializerText}`); 1671 | } 1672 | writer.newLineIfLastNot(); 1673 | writeTrailingComments(member); 1674 | writer.newLineIfLastNot(); 1675 | } 1676 | }); 1677 | writer.writeLine(")"); 1678 | 1679 | writer.newLineIfLastNot(); 1680 | return; 1681 | } 1682 | 1683 | if (Node.isModuleDeclaration(node)) { 1684 | // TODO: add namespace name as prefix to each variable declared inside 1685 | // TODO: error on namespace merging; check which things namespace can merge with? 1686 | 1687 | if (node.getName() === "JsxNames") { 1688 | // Special case for JsxNames only 1689 | const exports = new Map(); 1690 | 1691 | for (const statement of node.getStatements()) { 1692 | assert(Node.isVariableStatement(statement), statement.getKindName()); 1693 | assert(statement.isExported()); 1694 | 1695 | const declaration = statement.getDeclarationList().getDeclarations()[0]; 1696 | 1697 | const name = getNameOfNamed(declaration); 1698 | const initializer = declaration.getInitializerOrThrow(); 1699 | assert(Node.isAsExpression(initializer), initializer.getKindName()); 1700 | const left = initializer.getExpression(); 1701 | assert(Node.isStringLiteral(left), left.getKindName()); 1702 | exports.set(name, left.getLiteralText()); 1703 | } 1704 | 1705 | writer.write("var JsxNames = struct {"); 1706 | writer.indent(() => { 1707 | for (const [name, value] of exports) { 1708 | writer.write(`${name} __String`); 1709 | writer.newLine(); 1710 | } 1711 | }); 1712 | writer.write("} {"); 1713 | writer.indent(() => { 1714 | for (const [name, value] of exports) { 1715 | writer.write(`${name}: __String(${JSON.stringify(value)}),`); 1716 | writer.newLine(); 1717 | } 1718 | }); 1719 | writer.write("}"); 1720 | 1721 | return; 1722 | } 1723 | 1724 | writer.writeLine(`// #region ${node.getName()}`); 1725 | writer.newLine(); 1726 | writer.newLine(); 1727 | 1728 | const statements = node.getStatementsWithComments(); 1729 | 1730 | for (const statement of statements) { 1731 | visitStatement(statement); 1732 | } 1733 | 1734 | writer.writeLine(`// #endregion ${node.getName()}`); 1735 | writer.newLine(); 1736 | writer.newLine(); 1737 | 1738 | return; 1739 | } 1740 | 1741 | if (!isGlobal && Node.isExpressionStatement(node)) { 1742 | // Handling expressions separately so we _don't_ handle side effect expressions in writeExpression 1743 | visitExpressionStatement(node); 1744 | return; 1745 | } 1746 | 1747 | if (!isGlobal && Node.isReturnStatement(node)) { 1748 | writer.newLineIfLastNot(); 1749 | 1750 | let expression = node.getExpression(); 1751 | while (Node.isParenthesizedExpression(expression)) { 1752 | expression = expression.getExpression(); 1753 | } 1754 | 1755 | if (Node.isBinaryExpression(expression) && expression.getOperatorToken().isKind(ts.SyntaxKind.CommaToken)) { 1756 | const left = expression.getLeft(); 1757 | const right = expression.getRight(); 1758 | visitExpression(left, true); 1759 | writer.newLineIfLastNot(); 1760 | writer.write("return "); 1761 | visitExpression(right); 1762 | writer.newLine(); 1763 | return; 1764 | } 1765 | 1766 | if (Node.isConditionalExpression(expression)) { 1767 | writeConditionalExpression(expression, () => writer.write("return ")); 1768 | return; 1769 | } 1770 | 1771 | if ( 1772 | Node.isBinaryExpression(expression) && 1773 | expression.getOperatorToken().getKind() === ts.SyntaxKind.QuestionQuestionEqualsToken 1774 | ) { 1775 | writer.write("if "); 1776 | visitExpression(expression.getLeft()); 1777 | writer.write(" == nil { "); 1778 | visitExpression(expression.getLeft()); 1779 | writer.write(" = "); 1780 | visitExpression(expression.getRight()); 1781 | writer.write(" }"); 1782 | writer.newLine(); 1783 | writer.write("return "); 1784 | visitExpression(expression.getLeft()); 1785 | writer.newLine(); 1786 | return; 1787 | } 1788 | 1789 | if (Node.isBinaryExpression(expression) && isAssignmentOperator(expression.getOperatorToken().getKind())) { 1790 | writeBinaryExpression(expression, true); 1791 | writer.newLine(); 1792 | writer.write("return "); 1793 | visitExpression(expression.getLeft()); 1794 | writer.newLine(); 1795 | return; 1796 | } 1797 | 1798 | writer.write("return"); 1799 | if (expression) { 1800 | writer.write(" "); 1801 | visitExpression(expression); 1802 | } 1803 | writer.newLine(); 1804 | return; 1805 | } 1806 | 1807 | if (!isGlobal && Node.isContinueStatement(node)) { 1808 | const label = node.getLabel(); 1809 | writer.write("continue"); 1810 | if (label) { 1811 | writer.write(` ${sanitizeName(label.getText())}`); 1812 | } 1813 | writer.newLine(); 1814 | return; 1815 | } 1816 | 1817 | if (!isGlobal && Node.isBreakStatement(node)) { 1818 | const label = node.getLabel(); 1819 | writer.write("break"); 1820 | if (label) { 1821 | writer.write(` ${sanitizeName(label.getText())}`); 1822 | } 1823 | writer.newLine(); 1824 | return; 1825 | } 1826 | 1827 | if (!isGlobal && Node.isIfStatement(node)) { 1828 | visitIfStatement(node); 1829 | return; 1830 | } 1831 | 1832 | if (!isGlobal && Node.isForOfStatement(node)) { 1833 | writer.write("for _, "); 1834 | const initializer = node.getInitializer(); 1835 | if (Node.isVariableDeclarationList(initializer)) { 1836 | writer.write(getNameOfNamed(initializer.getDeclarations()[0])); 1837 | } 1838 | else { 1839 | writeTodoNode(initializer); 1840 | } 1841 | writer.write(" := range "); 1842 | visitExpression(node.getExpression()); 1843 | writer.write(" {"); 1844 | writer.indent(() => { 1845 | visitStatement(node.getStatement()); 1846 | }); 1847 | writer.write("}"); 1848 | writer.newLineIfLastNot(); 1849 | return; 1850 | } 1851 | 1852 | if (!isGlobal && Node.isForStatement(node)) { 1853 | writer.write("for "); 1854 | const initializer = node.getInitializer(); 1855 | if (initializer) { 1856 | if (Node.isVariableDeclarationList(initializer)) { 1857 | const declarations = initializer.getDeclarations(); 1858 | if (declarations.length > 1) { 1859 | writeTodoNode(initializer); 1860 | } 1861 | else { 1862 | const declaration = declarations[0]; 1863 | writer.write(`${getNameOfNamed(declaration)} := `); 1864 | visitExpression(declaration.getInitializerOrThrow()); 1865 | } 1866 | } 1867 | else { 1868 | visitExpression(initializer, true); 1869 | } 1870 | } 1871 | writer.write("; "); 1872 | const condition = node.getCondition(); 1873 | if (condition) { 1874 | visitExpression(condition, undefined, true); 1875 | } 1876 | writer.write("; "); 1877 | const incrementor = node.getIncrementor(); 1878 | if (incrementor) { 1879 | visitExpression(incrementor, true); 1880 | } 1881 | writer.write(" {"); 1882 | writer.indent(() => { 1883 | visitStatement(node.getStatement()); 1884 | }); 1885 | writer.write("}"); 1886 | writer.newLineIfLastNot(); 1887 | return; 1888 | } 1889 | 1890 | if (!isGlobal && Node.isWhileStatement(node)) { 1891 | writer.write("for "); 1892 | visitExpression(node.getExpression(), undefined, true); 1893 | writer.write(" {"); 1894 | writer.indent(() => { 1895 | visitStatement(node.getStatement()); 1896 | }); 1897 | writer.write("}"); 1898 | writer.newLineIfLastNot(); 1899 | return; 1900 | } 1901 | 1902 | if (!isGlobal && Node.isDoStatement(node)) { 1903 | writer.write("for ok := true; ok; ok = "); 1904 | visitExpression(node.getExpression(), undefined, true); 1905 | writer.write(" { // do-while loop"); 1906 | writer.indent(() => { 1907 | visitStatement(node.getStatement()); 1908 | }); 1909 | writer.write("}"); 1910 | writer.newLineIfLastNot(); 1911 | return; 1912 | } 1913 | 1914 | if (!isGlobal && Node.isBlock(node)) { 1915 | visitBlock(node); 1916 | return; 1917 | } 1918 | 1919 | if (!isGlobal && Node.isSwitchStatement(node)) { 1920 | writer.write("switch "); 1921 | visitExpression(node.getExpression()); 1922 | writer.write(" {"); 1923 | const clauses = node.getClauses(); 1924 | 1925 | function groupClauses() { 1926 | // Group clauses into: 1927 | // - Zero or more clauses with no statements followed by one non-empty case clause 1928 | // - Default clauses 1929 | // Groups should consist of (start, end] indices 1930 | 1931 | let groups: [number, number][] = []; 1932 | 1933 | let start = 0; 1934 | 1935 | while (start < clauses.length) { 1936 | const first = clauses[start]; 1937 | if (Node.isDefaultClause(first)) { 1938 | groups.push([start, start + 1]); 1939 | start++; 1940 | continue; 1941 | } 1942 | 1943 | const found = clauses.findIndex( 1944 | (clause, i) => 1945 | i >= start && (Node.isDefaultClause(clause) || clause.getStatements().length > 0), 1946 | ); 1947 | if (found === -1) { 1948 | groups.push([start, start + 1]); 1949 | start++; 1950 | continue; 1951 | } 1952 | 1953 | if (Node.isDefaultClause(clauses[found])) { 1954 | groups.push([start, found]); 1955 | start = found; 1956 | } 1957 | else { 1958 | groups.push([start, found + 1]); 1959 | start = found + 1; 1960 | } 1961 | } 1962 | 1963 | return groups; 1964 | } 1965 | 1966 | for (const [start, end] of groupClauses()) { 1967 | function writeColonAndCaseBody(clause: CaseOrDefaultClause, idx: number) { 1968 | const isLastClause = idx === clauses.length - 1; 1969 | let statementsOfClause = clause.getStatementsWithComments(); 1970 | const firstStatement = statementsOfClause.length === 1 ? statementsOfClause[0] : undefined; 1971 | if (firstStatement?.isKind(ts.SyntaxKind.Block)) { 1972 | statementsOfClause = firstStatement.getStatementsWithComments(); 1973 | } 1974 | 1975 | writer.write(":"); 1976 | writer.indent(() => { 1977 | const end = statementsOfClause.findLastIndex(s => !s.getKindName().endsWith("Trivia")); 1978 | 1979 | const first = statementsOfClause.slice(0, end); 1980 | const last = statementsOfClause.slice(end); 1981 | 1982 | for (const statement of first) { 1983 | visitStatement(statement); 1984 | } 1985 | 1986 | const lastStatement = last.at(0); 1987 | if (lastStatement) { 1988 | if (Node.isReturnStatement(lastStatement) || Node.isContinueStatement(lastStatement)) { 1989 | last.forEach(visitStatement); 1990 | } 1991 | else if (Node.isBreakStatement(lastStatement)) { 1992 | // Skip 1993 | last.slice(1).forEach(visitStatement); 1994 | } 1995 | else { 1996 | last.filter(node => { 1997 | if (node.getKindName().endsWith("Trivia")) { 1998 | if (/\/\/\s+falls? ?through/.test(node.getText())) { 1999 | return false; 2000 | } 2001 | } 2002 | return true; 2003 | }).forEach(visitStatement); 2004 | if (!isLastClause) { 2005 | writer.newLineIfLastNot(); 2006 | writer.writeLine("fallthrough"); 2007 | } 2008 | } 2009 | } 2010 | }); 2011 | } 2012 | 2013 | const startingClause = clauses[start]; 2014 | if (Node.isCaseClause(startingClause)) { 2015 | writer.newLineIfLastNot(); 2016 | writer.write("case "); 2017 | for (let i = start; i < end; i++) { 2018 | const isLastInGroup = i === end - 1; 2019 | 2020 | const clause = clauses[i]; 2021 | assert(Node.isCaseClause(clause)); 2022 | const expression = clause.getExpression(); 2023 | visitExpression(expression); 2024 | if (isLastInGroup) { 2025 | writeColonAndCaseBody(clause, i); 2026 | } 2027 | else { 2028 | writer.write(","); 2029 | writer.newLine(); 2030 | } 2031 | } 2032 | } 2033 | else { 2034 | writer.newLineIfLastNot(); 2035 | writer.write("default"); 2036 | writeColonAndCaseBody(startingClause, start); 2037 | } 2038 | } 2039 | 2040 | writer.write("}"); 2041 | writer.newLineIfLastNot(); 2042 | return; 2043 | } 2044 | 2045 | if (Node.isCommentStatement(node)) { 2046 | writer.write(node.getText()); 2047 | return; 2048 | } 2049 | 2050 | if (!isGlobal && Node.isLabeledStatement(node)) { 2051 | const label = node.getLabel().getText(); 2052 | writer.write(`${sanitizeName(label)}: `); 2053 | visitStatement(node.getStatement()); 2054 | return; 2055 | } 2056 | 2057 | if (!isGlobal && Node.isTryStatement(node)) { 2058 | writer.write("{ // try"); 2059 | writer.indent(() => { 2060 | visitStatement(node.getTryBlock()); 2061 | }); 2062 | writer.writeLine("}"); 2063 | const catchClause = node.getCatchClause(); 2064 | if (catchClause) { 2065 | writer.write(`{ // catch ${catchClause.getVariableDeclaration()?.getText() ?? ""}`); 2066 | writer.indent(() => { 2067 | visitStatement(catchClause.getBlock()); 2068 | }); 2069 | writer.writeLine("}"); 2070 | writer.newLineIfLastNot(); 2071 | } 2072 | const finallyBlock = node.getFinallyBlock(); 2073 | if (finallyBlock) { 2074 | writer.write("{ // finally"); 2075 | writer.indent(() => { 2076 | visitStatement(finallyBlock); 2077 | }); 2078 | writer.writeLine("}"); 2079 | writer.newLineIfLastNot(); 2080 | } 2081 | return; 2082 | } 2083 | 2084 | if (Node.isClassDeclaration(node)) { 2085 | writer.write(`type ${getNameOfNamed(node)} struct {`); 2086 | const members = node.getMembers(); 2087 | writer.indent(() => { 2088 | for (const member of members) { 2089 | if (Node.isPropertyDeclaration(member)) { 2090 | writer.write(`${getNameOfNamed(member)} `); 2091 | 2092 | const typeNode = member.getTypeNode(); 2093 | if (typeNode) { 2094 | visitTypeNode(typeNode); 2095 | } 2096 | else { 2097 | writeType(member, member.getType()); 2098 | } 2099 | 2100 | writer.newLine(); 2101 | } 2102 | } 2103 | }); 2104 | writer.write("}"); 2105 | 2106 | writer.newLineIfLastNot(); 2107 | 2108 | for (const member of members) { 2109 | if (Node.isPropertyDeclaration(member)) { 2110 | continue; 2111 | } 2112 | 2113 | if (Node.isConstructorDeclaration(member)) { 2114 | writer.write(`func New${getNameOfNamed(node)}`); 2115 | writeFunctionParametersAndReturn(member); 2116 | writer.write(" {"); 2117 | writer.indent(() => { 2118 | writer.write(`this := &${getNameOfNamed(node)}{}`); 2119 | const body = member.getBodyOrThrow(); 2120 | assert(Node.isBlock(body)); 2121 | visitBlock(body); 2122 | writer.write("return this"); 2123 | }); 2124 | writer.write("}"); 2125 | writer.newLine(); 2126 | writer.newLine(); 2127 | } 2128 | else if (Node.isMethodDeclaration(member)) { 2129 | writer.write(`func (this *${getNameOfNamed(node)}) ${getNameOfNamed(member)}`); 2130 | writeFunctionParametersAndReturn(member); 2131 | writeBody(member); 2132 | writer.newLine(); 2133 | writer.newLine(); 2134 | } 2135 | else { 2136 | writeTodoNode(member); 2137 | writer.newLine(); 2138 | } 2139 | } 2140 | 2141 | return; 2142 | } 2143 | 2144 | writeTodoNode(node); 2145 | // console.error(`Unhandled node kind: ${node.getKindName()}`); 2146 | } 2147 | 2148 | const structFields = new Set(); 2149 | 2150 | if (mainStruct) { 2151 | let seenReturn = false; 2152 | 2153 | writer.write(`type ${mainStruct} struct {`); 2154 | 2155 | writer.indent(() => { 2156 | sourceFile.getFunctionOrThrow(createFunction!).getStatements().forEach(statement => { 2157 | if (!seenReturn && Node.isVariableStatement(statement)) { 2158 | for (const declaration of statement.getDeclarations()) { 2159 | structFields.add(getNameOfNamed(declaration)); 2160 | writer.write(`${getNameOfNamed(declaration)} `); 2161 | const typeNode = declaration.getTypeNode(); 2162 | if (typeNode) { 2163 | visitTypeNode(typeNode); 2164 | } 2165 | else { 2166 | writeType(declaration, declaration.getType()); 2167 | } 2168 | writer.newLine(); 2169 | } 2170 | } 2171 | else if (seenReturn && Node.isFunctionDeclaration(statement)) { 2172 | structFields.add(getNameOfNamed(statement)); 2173 | } 2174 | else if (Node.isReturnStatement(statement)) { 2175 | seenReturn = true; 2176 | } 2177 | }); 2178 | }); 2179 | 2180 | writer.write("}"); 2181 | } 2182 | 2183 | sourceFile.getStatementsWithComments().forEach(visitStatement); 2184 | 2185 | const outFile = Bun.file(output); 2186 | 2187 | await Bun.write(outFile, writer.toString()); 2188 | 2189 | try { 2190 | cp.execFileSync(which.sync("gofmt"), ["-w", "-s", output]); 2191 | 2192 | cp.execFileSync(which.sync("ast-grep"), ["-U", "-p", "!($A != $B)", "-r", "$A == $B", output]); 2193 | cp.execFileSync(which.sync("ast-grep"), ["-U", "-p", "!($A == $B)", "-r", "$A != $B", output]); 2194 | 2195 | cp.execFileSync(which.sync("ast-grep"), ["-U", "-p", "($A.$B())", "-r", "$A.$B()", output]); 2196 | 2197 | cp.execFileSync(which.sync("gofmt"), ["-w", "-s", output]); 2198 | 2199 | console.log(" All good!"); 2200 | } 2201 | catch (e) { 2202 | console.error(e); 2203 | } 2204 | console.log(` took ${prettyMilliseconds(performance.now() - start)}`); 2205 | 2206 | const totalTodo = [...todoCounts.values()].reduce((a, b) => a + b, 0); 2207 | console.log(` Total TODOs: ${totalTodo}`); 2208 | console.log([...todoCounts.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => ` ${k} ${v}`).join("\n")); 2209 | } 2210 | 2211 | await convert("checker.ts", "output/checker.go", "TypeChecker"); 2212 | await convert("binder.ts", "output/binder.go", "Binder"); 2213 | await convert("scanner.ts", "output/scanner.go", "Scanner"); 2214 | await convert("parser.ts", "output/parser.go"); 2215 | await convert("utilities.ts", "output/utilities.go"); 2216 | await convert("utilitiesPublic.ts", "output/utilitiesPublic.go"); 2217 | await convert("program.ts", "output/program.go"); 2218 | await convert("emitter.ts", "output/emitter.go", "Printer"); 2219 | await convert("expressionToTypeNode.ts", "output/expressionToTypeNode.go", "SyntacticTypeNodeBuilder"); 2220 | await convert("types.ts", "output/types.go"); 2221 | 2222 | console.log("Done!"); 2223 | -------------------------------------------------------------------------------- /output/expressionToTypeNode.go: -------------------------------------------------------------------------------- 1 | // Code generated by ts-to-go at 52c59dbcbee274e523ef39e6c8be1bd5e110c2f1. DO NOT EDIT. 2 | 3 | package output 4 | 5 | type SyntacticTypeNodeBuilder struct { 6 | strictNullChecks bool 7 | } 8 | 9 | /** @internal */ 10 | 11 | func createSyntacticTypeNodeBuilder(options CompilerOptions, resolver SyntacticTypeNodeBuilderResolver) SyntacticNodeBuilder { 12 | tc.strictNullChecks = getStrictOptionValue(options, "strictNullChecks") 13 | 14 | return SyntacticNodeBuilder{ 15 | typeFromExpression: typeFromExpression, 16 | serializeTypeOfDeclaration: serializeTypeOfDeclaration, 17 | serializeReturnTypeForSignature: serializeReturnTypeForSignature, 18 | serializeTypeOfExpression: serializeTypeOfExpression, 19 | } 20 | } 21 | 22 | func (stnb *SyntacticTypeNodeBuilder) serializeExistingTypeAnnotation(t *TypeNode, addUndefined bool) *true { 23 | if t != nil && (!addUndefined || (t && stnb.canAddUndefined(t))) { 24 | return true 25 | } else { 26 | return nil 27 | } 28 | } 29 | 30 | func (stnb *SyntacticTypeNodeBuilder) serializeTypeOfExpression(expr Expression, context SyntacticTypeNodeBuilderContext, addUndefined bool, preserveLiterals bool) bool { 31 | return ifNotNilElse(stnb.typeFromExpression(expr, context, false /*isConstContext*/, addUndefined, preserveLiterals), stnb.inferExpressionType(expr, context)) 32 | } 33 | 34 | func (stnb *SyntacticTypeNodeBuilder) serializeTypeOfDeclaration(node HasInferredType, context SyntacticTypeNodeBuilderContext) *bool { 35 | switch node.Kind { 36 | case ast.KindPropertySignature: 37 | return stnb.serializeExistingTypeAnnotation(getEffectiveTypeAnnotationNode(node)) 38 | case ast.KindParameter: 39 | return stnb.typeFromParameter(node, context) 40 | case ast.KindVariableDeclaration: 41 | return stnb.typeFromVariable(node, context) 42 | case ast.KindPropertyDeclaration: 43 | return stnb.typeFromProperty(node, context) 44 | case ast.KindBindingElement: 45 | return stnb.inferTypeOfDeclaration(node, context) 46 | case ast.KindExportAssignment: 47 | return stnb.serializeTypeOfExpression(node.Expression, context, nil /*addUndefined*/, true /*preserveLiterals*/) 48 | case ast.KindPropertyAccessExpression, 49 | ast.KindElementAccessExpression, 50 | ast.KindBinaryExpression: 51 | return stnb.serializeExistingTypeAnnotation(getEffectiveTypeAnnotationNode(node)) || stnb.inferTypeOfDeclaration(node, context) 52 | case ast.KindPropertyAssignment: 53 | return stnb.typeFromExpression(node.Initializer, context) || stnb.inferTypeOfDeclaration(node, context) 54 | default: 55 | Debug.assertNever(node, __TEMPLATE__("Node needs to be an inferrable node, found ", Debug.formatSyntaxKind(node.AsNode().Kind))) 56 | } 57 | } 58 | 59 | func (stnb *SyntacticTypeNodeBuilder) serializeReturnTypeForSignature(node Union[SignatureDeclaration, JSDocSignature], context SyntacticTypeNodeBuilderContext) *bool { 60 | switch node.Kind { 61 | case ast.KindGetAccessor: 62 | return stnb.typeFromAccessor(node, context) 63 | case ast.KindMethodDeclaration, 64 | ast.KindFunctionDeclaration, 65 | ast.KindConstructSignature, 66 | ast.KindMethodSignature, 67 | ast.KindCallSignature, 68 | ast.KindConstructor, 69 | ast.KindSetAccessor, 70 | ast.KindIndexSignature, 71 | ast.KindFunctionType, 72 | ast.KindConstructorType, 73 | ast.KindFunctionExpression, 74 | ast.KindArrowFunction, 75 | ast.KindJSDocFunctionType, 76 | ast.KindJSDocSignature: 77 | return stnb.createReturnFromSignature(node, context) 78 | default: 79 | Debug.assertNever(node, __TEMPLATE__("Node needs to be an inferrable node, found ", Debug.formatSyntaxKind(node.AsNode().Kind))) 80 | } 81 | } 82 | 83 | func (stnb *SyntacticTypeNodeBuilder) getTypeAnnotationFromAccessor(accessor AccessorDeclaration) *TypeNode { 84 | if accessor { 85 | switch { 86 | case accessor.Kind == ast.KindGetAccessor: 87 | return getEffectiveReturnTypeNode(accessor) 88 | case accessor.Parameters.length > 0: 89 | return getEffectiveTypeAnnotationNode(accessor.Parameters[0]) 90 | default: 91 | return nil 92 | } 93 | } 94 | } 95 | 96 | func (stnb *SyntacticTypeNodeBuilder) getTypeAnnotationFromAllAccessorDeclarations(node AccessorDeclaration, accessors AllAccessorDeclarations) *TypeNode { 97 | accessorType := stnb.getTypeAnnotationFromAccessor(node) 98 | if accessorType == nil && node != accessors.firstAccessor { 99 | accessorType = stnb.getTypeAnnotationFromAccessor(accessors.firstAccessor) 100 | } 101 | if accessorType == nil && accessors.secondAccessor != nil && node != accessors.secondAccessor { 102 | accessorType = stnb.getTypeAnnotationFromAccessor(accessors.secondAccessor) 103 | } 104 | return accessorType 105 | } 106 | 107 | func (stnb *SyntacticTypeNodeBuilder) typeFromAccessor(node AccessorDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 108 | accessorDeclarations := resolver.getAllAccessorDeclarations(node) 109 | accessorType := stnb.getTypeAnnotationFromAllAccessorDeclarations(node, accessorDeclarations) 110 | if accessorType != nil { 111 | return stnb.serializeExistingTypeAnnotation(accessorType) 112 | } 113 | if accessorDeclarations.getAccessor != nil { 114 | return stnb.createReturnFromSignature(accessorDeclarations.getAccessor, context) 115 | } 116 | return false 117 | } 118 | 119 | func (stnb *SyntacticTypeNodeBuilder) typeFromVariable(node VariableDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 120 | declaredType := getEffectiveTypeAnnotationNode(node) 121 | if declaredType != nil { 122 | return stnb.serializeExistingTypeAnnotation(declaredType) 123 | } 124 | var resultType TODO 125 | if node.Initializer != nil { 126 | if !resolver.isExpandoFunctionDeclaration(node) { 127 | resultType = stnb.typeFromExpression(node.Initializer, context, nil /*isConstContext*/, nil /*requiresAddingUndefined*/, isVarConstLike(node)) 128 | } 129 | } 130 | return ifNotNilElse(resultType, stnb.inferTypeOfDeclaration(node, context)) 131 | } 132 | 133 | func (stnb *SyntacticTypeNodeBuilder) typeFromParameter(node ParameterDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 134 | parent := node.Parent 135 | if parent.Kind == ast.KindSetAccessor { 136 | return stnb.typeFromAccessor(parent, context) 137 | } 138 | declaredType := getEffectiveTypeAnnotationNode(node) 139 | addUndefined := resolver.requiresAddingImplicitUndefined(node, context.enclosingDeclaration) 140 | var resultType TODO 141 | if declaredType != nil { 142 | resultType = stnb.serializeExistingTypeAnnotation(declaredType, addUndefined) 143 | } else { 144 | if node.Initializer != nil && isIdentifier(node.Name) { 145 | resultType = stnb.typeFromExpression(node.Initializer, context, nil /*isConstContext*/, addUndefined) 146 | } 147 | } 148 | return ifNotNilElse(resultType, stnb.inferTypeOfDeclaration(node, context)) 149 | } 150 | 151 | func (stnb *SyntacticTypeNodeBuilder) typeFromProperty(node PropertyDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 152 | declaredType := getEffectiveTypeAnnotationNode(node) 153 | if declaredType != nil { 154 | return stnb.serializeExistingTypeAnnotation(declaredType) 155 | } 156 | var resultType TODO 157 | if node.Initializer != nil { 158 | isReadonly := isDeclarationReadonly(node) 159 | resultType = stnb.typeFromExpression(node.Initializer, context, nil /*isConstContext*/, nil /*requiresAddingUndefined*/, isReadonly) 160 | } 161 | return ifNotNilElse(resultType, stnb.inferTypeOfDeclaration(node, context)) 162 | } 163 | 164 | func (stnb *SyntacticTypeNodeBuilder) inferTypeOfDeclaration(node Union[PropertyAssignment, PropertyAccessExpression, BinaryExpression, ElementAccessExpression, VariableDeclaration, ParameterDeclaration, BindingElement, PropertyDeclaration, PropertySignature, ExportAssignment], context SyntacticTypeNodeBuilderContext) bool { 165 | context.tracker.reportInferenceFallback(node) 166 | return false 167 | } 168 | 169 | func (stnb *SyntacticTypeNodeBuilder) inferExpressionType(node Expression, context SyntacticTypeNodeBuilderContext) bool { 170 | context.tracker.reportInferenceFallback(node) 171 | return false 172 | } 173 | 174 | func (stnb *SyntacticTypeNodeBuilder) inferReturnTypeOfSignatureSignature(node Union[SignatureDeclaration, JSDocSignature], context SyntacticTypeNodeBuilderContext) bool { 175 | context.tracker.reportInferenceFallback(node) 176 | return false 177 | } 178 | 179 | func (stnb *SyntacticTypeNodeBuilder) inferAccessorType(node Union[GetAccessorDeclaration, SetAccessorDeclaration], allAccessors AllAccessorDeclarations, context SyntacticTypeNodeBuilderContext) bool { 180 | if node.Kind == ast.KindGetAccessor { 181 | return stnb.createReturnFromSignature(node, context) 182 | } else { 183 | context.tracker.reportInferenceFallback(node) 184 | return false 185 | } 186 | } 187 | 188 | func (stnb *SyntacticTypeNodeBuilder) typeFromTypeAssertion(expression Expression, t TypeNode, context SyntacticTypeNodeBuilderContext, requiresAddingUndefined bool) *bool { 189 | if isConstTypeReference(t) { 190 | return stnb.typeFromExpression(expression, context, true /*isConstContext*/, requiresAddingUndefined) 191 | } 192 | if requiresAddingUndefined && !stnb.canAddUndefined(t) { 193 | context.tracker.reportInferenceFallback(t) 194 | } 195 | return stnb.serializeExistingTypeAnnotation(t) 196 | } 197 | 198 | func (stnb *SyntacticTypeNodeBuilder) typeFromExpression(node Expression, context SyntacticTypeNodeBuilderContext, isConstContext bool /* = false */, requiresAddingUndefined bool /* = false */, preserveLiterals bool /* = false */) *bool { 199 | switch node.Kind { 200 | case ast.KindParenthesizedExpression: 201 | if isJSDocTypeAssertion(node) { 202 | return stnb.typeFromTypeAssertion(node.Expression, getJSDocTypeAssertionType(node), context, requiresAddingUndefined) 203 | } 204 | return stnb.typeFromExpression(node.AsParenthesizedExpression().Expression, context, isConstContext, requiresAddingUndefined) 205 | case ast.KindIdentifier: 206 | if resolver.isUndefinedIdentifierExpression(node.AsIdentifier()) { 207 | return true 208 | } 209 | case ast.KindNullKeyword: 210 | return true 211 | case ast.KindArrowFunction, 212 | ast.KindFunctionExpression: 213 | return stnb.typeFromFunctionLikeExpression(node /* as ArrowFunction | FunctionExpression */, context) 214 | case ast.KindTypeAssertionExpression, 215 | ast.KindAsExpression: 216 | asExpression := node /* as AsExpression | TypeAssertion */ 217 | return stnb.typeFromTypeAssertion(asExpression.Expression, asExpression.Type_, context, requiresAddingUndefined) 218 | case ast.KindPrefixUnaryExpression: 219 | unaryExpression := node.AsPrefixUnaryExpression() 220 | if isPrimitiveLiteralValue(unaryExpression) { 221 | if unaryExpression.Operand.Kind == ast.KindBigIntLiteral { 222 | return stnb.typeFromPrimitiveLiteral() 223 | } 224 | if unaryExpression.Operand.Kind == ast.KindNumericLiteral { 225 | return stnb.typeFromPrimitiveLiteral() 226 | } 227 | } 228 | case ast.KindNumericLiteral: 229 | return stnb.typeFromPrimitiveLiteral() 230 | case ast.KindTemplateExpression: 231 | if !isConstContext && !preserveLiterals { 232 | return true 233 | } 234 | case ast.KindNoSubstitutionTemplateLiteral, 235 | ast.KindStringLiteral: 236 | return stnb.typeFromPrimitiveLiteral() 237 | case ast.KindBigIntLiteral: 238 | return stnb.typeFromPrimitiveLiteral() 239 | case ast.KindTrueKeyword, 240 | ast.KindFalseKeyword: 241 | return stnb.typeFromPrimitiveLiteral() 242 | case ast.KindArrayLiteralExpression: 243 | return stnb.typeFromArrayLiteral(node.AsArrayLiteralExpression(), context, isConstContext) 244 | case ast.KindObjectLiteralExpression: 245 | return stnb.typeFromObjectLiteral(node.AsObjectLiteralExpression(), context, isConstContext) 246 | case ast.KindClassExpression: 247 | return stnb.inferExpressionType(node.AsClassExpression(), context) 248 | } 249 | return nil 250 | } 251 | 252 | func (stnb *SyntacticTypeNodeBuilder) typeFromFunctionLikeExpression(fnNode Union[FunctionExpression, ArrowFunction], context SyntacticTypeNodeBuilderContext) bool { 253 | returnType := ifNotNilElse(stnb.serializeExistingTypeAnnotation(fnNode.Type_), stnb.createReturnFromSignature(fnNode, context)) 254 | typeParameters := stnb.reuseTypeParameters(fnNode.TypeParameters) 255 | parameters := fnNode.Parameters.every(func(p ParameterDeclaration) *bool { 256 | return stnb.ensureParameter(p, context) 257 | }) 258 | return returnType && typeParameters && parameters 259 | } 260 | 261 | func (stnb *SyntacticTypeNodeBuilder) canGetTypeFromArrayLiteral(arrayLiteral ArrayLiteralExpression, context SyntacticTypeNodeBuilderContext, isConstContext bool) bool { 262 | if !isConstContext { 263 | context.tracker.reportInferenceFallback(arrayLiteral) 264 | return false 265 | } 266 | for _, element := range arrayLiteral.Elements { 267 | if element.Kind == ast.KindSpreadElement { 268 | context.tracker.reportInferenceFallback(element) 269 | return false 270 | } 271 | } 272 | return true 273 | } 274 | 275 | func (stnb *SyntacticTypeNodeBuilder) typeFromArrayLiteral(arrayLiteral ArrayLiteralExpression, context SyntacticTypeNodeBuilderContext, isConstContext bool) bool { 276 | if !stnb.canGetTypeFromArrayLiteral(arrayLiteral, context, isConstContext) { 277 | return false 278 | } 279 | 280 | canInferArray := true 281 | for _, element := range arrayLiteral.Elements { 282 | Debug.assert(element.Kind != ast.KindSpreadElement) 283 | if element.Kind != ast.KindOmittedExpression { 284 | canInferArray = (ifNotNilElse(stnb.typeFromExpression(element, context, isConstContext), stnb.inferExpressionType(element, context))) && canInferArray 285 | } 286 | } 287 | return true 288 | } 289 | 290 | func (stnb *SyntacticTypeNodeBuilder) canGetTypeFromObjectLiteral(objectLiteral ObjectLiteralExpression, context SyntacticTypeNodeBuilderContext) bool { 291 | result := true 292 | for _, prop := range objectLiteral.Properties { 293 | if prop.Flags&ast.NodeFlagsThisNodeHasError != 0 { 294 | result = false 295 | break 296 | // Bail if parse errors 297 | } 298 | if prop.Kind == ast.KindShorthandPropertyAssignment || prop.Kind == ast.KindSpreadAssignment { 299 | context.tracker.reportInferenceFallback(prop) 300 | result = false 301 | } else if prop.Name.Flags&ast.NodeFlagsThisNodeHasError != 0 { 302 | result = false 303 | break 304 | // Bail if parse errors 305 | } else if prop.Name.Kind == ast.KindPrivateIdentifier { 306 | // Not valid in object literals but the compiler will complain about this, we just ignore it here. 307 | result = false 308 | } else if prop.Name.Kind == ast.KindComputedPropertyName { 309 | expression := prop.Name.Expression 310 | if !isPrimitiveLiteralValue(expression, false /*includeBigInt*/) && !resolver.isDefinitelyReferenceToGlobalSymbolObject(expression) { 311 | context.tracker.reportInferenceFallback(prop.Name) 312 | result = false 313 | } 314 | } 315 | } 316 | return result 317 | } 318 | 319 | func (stnb *SyntacticTypeNodeBuilder) typeFromObjectLiteral(objectLiteral ObjectLiteralExpression, context SyntacticTypeNodeBuilderContext, isConstContext bool) bool { 320 | if !stnb.canGetTypeFromObjectLiteral(objectLiteral, context) { 321 | return false 322 | } 323 | 324 | canInferObjectLiteral := true 325 | for _, prop := range objectLiteral.Properties { 326 | Debug.assert(!isShorthandPropertyAssignment(prop) && !isSpreadAssignment(prop)) 327 | 328 | name := prop.Name 329 | switch prop.Kind { 330 | case ast.KindMethodDeclaration: 331 | canInferObjectLiteral = stnb.typeFromObjectLiteralMethod(prop, name, context) && canInferObjectLiteral 332 | case ast.KindPropertyAssignment: 333 | canInferObjectLiteral = stnb.typeFromObjectLiteralPropertyAssignment(prop, name, context, isConstContext) && canInferObjectLiteral 334 | case ast.KindSetAccessor, 335 | ast.KindGetAccessor: 336 | canInferObjectLiteral = stnb.typeFromObjectLiteralAccessor(prop, name, context) && canInferObjectLiteral 337 | } 338 | } 339 | 340 | return canInferObjectLiteral 341 | } 342 | 343 | func (stnb *SyntacticTypeNodeBuilder) typeFromObjectLiteralPropertyAssignment(prop PropertyAssignment, name PropertyName, context SyntacticTypeNodeBuilderContext, isConstContext bool) bool { 344 | return ifNotNilElse(stnb.typeFromExpression(prop.Initializer, context, isConstContext), stnb.inferTypeOfDeclaration(prop, context)) 345 | } 346 | 347 | func (stnb *SyntacticTypeNodeBuilder) ensureParameter(p ParameterDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 348 | return stnb.typeFromParameter(p, context) 349 | } 350 | 351 | func (stnb *SyntacticTypeNodeBuilder) reuseTypeParameters(typeParameters *NodeArray[TypeParameterDeclaration]) bool { 352 | // TODO: We will probably need to add a fake scopes for the signature (to hold the type parameters and the parameter) 353 | // For now this is good enough since the new serialization is used for Nodes in the same context. 354 | return ifNotNilElse(typeParameters. /* ? */ every(func(tp TypeParameterDeclaration) *true { 355 | return stnb.serializeExistingTypeAnnotation(tp.Constraint) && stnb.serializeExistingTypeAnnotation(tp.Default_) 356 | }), true) 357 | } 358 | 359 | func (stnb *SyntacticTypeNodeBuilder) typeFromObjectLiteralMethod(method MethodDeclaration, name PropertyName, context SyntacticTypeNodeBuilderContext) bool { 360 | returnType := stnb.createReturnFromSignature(method, context) 361 | typeParameters := stnb.reuseTypeParameters(method.TypeParameters) 362 | parameters := method.Parameters.every(func(p ParameterDeclaration) *bool { 363 | return stnb.ensureParameter(p, context) 364 | }) 365 | return returnType && typeParameters && parameters 366 | } 367 | 368 | func (stnb *SyntacticTypeNodeBuilder) typeFromObjectLiteralAccessor(accessor Union[GetAccessorDeclaration, SetAccessorDeclaration], name PropertyName, context SyntacticTypeNodeBuilderContext) *bool { 369 | allAccessors := resolver.getAllAccessorDeclarations(accessor) 370 | getAccessorType := allAccessors.getAccessor && stnb.getTypeAnnotationFromAccessor(allAccessors.getAccessor) 371 | setAccessorType := allAccessors.setAccessor && stnb.getTypeAnnotationFromAccessor(allAccessors.setAccessor) 372 | // We have types for both accessors, we can't know if they are the same type so we keep both accessors 373 | if getAccessorType != nil && setAccessorType != nil { 374 | parameters := accessor.Parameters.every(func(p ParameterDeclaration) *bool { 375 | return stnb.ensureParameter(p, context) 376 | }) 377 | 378 | if isGetAccessor(accessor) { 379 | return parameters && stnb.serializeExistingTypeAnnotation(getAccessorType) 380 | } else { 381 | return parameters 382 | } 383 | } else if allAccessors.firstAccessor == accessor { 384 | foundType := ifNotNilElse(getAccessorType, setAccessorType) 385 | var propertyType *bool 386 | if foundType != nil { 387 | propertyType = stnb.serializeExistingTypeAnnotation(foundType) 388 | } else { 389 | propertyType = stnb.inferAccessorType(accessor, allAccessors, context) 390 | } 391 | 392 | return propertyType 393 | } 394 | return false 395 | } 396 | 397 | func (stnb *SyntacticTypeNodeBuilder) typeFromPrimitiveLiteral() bool { 398 | return true 399 | } 400 | 401 | func (stnb *SyntacticTypeNodeBuilder) canAddUndefined(node TypeNode) bool { 402 | if !stnb.strictNullChecks { 403 | return true 404 | } 405 | if isKeyword(node.Kind) || node.Kind == ast.KindLiteralType || node.Kind == ast.KindFunctionType || node.Kind == ast.KindConstructorType || node.Kind == ast.KindArrayType || node.Kind == ast.KindTupleType || node.Kind == ast.KindTypeLiteral || node.Kind == ast.KindTemplateLiteralType || node.Kind == ast.KindThisType { 406 | return true 407 | } 408 | if node.Kind == ast.KindParenthesizedType { 409 | return stnb.canAddUndefined(node.AsParenthesizedTypeNode().Type_) 410 | } 411 | if node.Kind == ast.KindUnionType || node.Kind == ast.KindIntersectionType { 412 | return (node /* as UnionTypeNode | IntersectionTypeNode */).Types.every(stnb.canAddUndefined) 413 | } 414 | return false 415 | } 416 | 417 | func (stnb *SyntacticTypeNodeBuilder) createReturnFromSignature(fn Union[SignatureDeclaration, JSDocSignature], context SyntacticTypeNodeBuilderContext) bool { 418 | var returnType TODO 419 | returnTypeNode := getEffectiveReturnTypeNode(fn) 420 | if returnTypeNode != nil { 421 | returnType = stnb.serializeExistingTypeAnnotation(returnTypeNode) 422 | } 423 | if !returnType && isValueSignatureDeclaration(fn) { 424 | returnType = stnb.typeFromSingleReturnExpression(fn, context) 425 | } 426 | return ifNotNilElse(returnType, stnb.inferReturnTypeOfSignatureSignature(fn, context)) 427 | } 428 | 429 | func (stnb *SyntacticTypeNodeBuilder) typeFromSingleReturnExpression(declaration *FunctionLikeDeclaration, context SyntacticTypeNodeBuilderContext) *bool { 430 | var candidateExpr Expression 431 | if declaration != nil && !nodeIsMissing(declaration.Body) { 432 | if getFunctionFlags(declaration)&FunctionFlagsAsyncGenerator != 0 { 433 | return nil 434 | } 435 | 436 | body := declaration.Body 437 | if body != nil && isBlock(body) { 438 | forEachReturnStatement(body, func(s ReturnStatement) *true { 439 | if candidateExpr == nil { 440 | candidateExpr = s.Expression 441 | } else { 442 | candidateExpr = nil 443 | return true 444 | } 445 | }) 446 | } else { 447 | candidateExpr = body 448 | } 449 | } 450 | if candidateExpr != nil { 451 | return stnb.typeFromExpression(candidateExpr, context) 452 | } 453 | return nil 454 | } 455 | -------------------------------------------------------------------------------- /output/utilitiesPublic.go: -------------------------------------------------------------------------------- 1 | // Code generated by ts-to-go at 52c59dbcbee274e523ef39e6c8be1bd5e110c2f1. DO NOT EDIT. 2 | 3 | package output 4 | 5 | func isExternalModuleNameRelative(moduleName string) bool { 6 | // TypeScript 1.0 spec (April 2014): 11.2.1 7 | // An external module name is "relative" if the first term is "." or "..". 8 | // Update: We also consider a path like `C:\foo.ts` "relative" because we do not search for it in `node_modules` or treat it as an ambient module. 9 | return pathIsRelative(moduleName) || isRootedDiskPath(moduleName) 10 | } 11 | 12 | func sortAndDeduplicateDiagnostics(diagnostics []T) SortedReadonlyArray[T] { 13 | return sortAndDeduplicate(diagnostics, compareDiagnostics, diagnosticsEqualityComparer) 14 | } 15 | 16 | /** @internal */ 17 | 18 | var targetToLibMap Map[ScriptTarget, string] = NewMap([] /* TODO(TS-TO-GO) inferred type [ScriptTarget.ESNext, string] | [ScriptTarget.ES2023, string] | [ScriptTarget.ES2022, string] | [ScriptTarget.ES2021, string] | [ScriptTarget.ES2020, string] | [ScriptTarget.ES2019, string] | [ScriptTarget.ES2018, string] | [ScriptTarget.ES2017, string] | [ScriptTarget.ES2016, string] | [ScriptTarget.ES2015, string] */ any{[]any{ScriptTargetESNext, "lib.esnext.full.d.ts"}, []any{ScriptTargetES2023, "lib.es2023.full.d.ts"}, []any{ScriptTargetES2022, "lib.es2022.full.d.ts"}, []any{ScriptTargetES2021, "lib.es2021.full.d.ts"}, []any{ScriptTargetES2020, "lib.es2020.full.d.ts"}, []any{ScriptTargetES2019, "lib.es2019.full.d.ts"}, []any{ScriptTargetES2018, "lib.es2018.full.d.ts"}, []any{ScriptTargetES2017, "lib.es2017.full.d.ts"}, []any{ScriptTargetES2016, "lib.es2016.full.d.ts"}, []any{ScriptTargetES2015, "lib.es6.d.ts"}}) 19 | 20 | func getDefaultLibFileName(options CompilerOptions) string { 21 | target := getEmitScriptTarget(options) 22 | switch target { 23 | case ScriptTargetESNext, 24 | ScriptTargetES2023, 25 | ScriptTargetES2022, 26 | ScriptTargetES2021, 27 | ScriptTargetES2020, 28 | ScriptTargetES2019, 29 | ScriptTargetES2018, 30 | ScriptTargetES2017, 31 | ScriptTargetES2016, 32 | ScriptTargetES2015: 33 | return targetToLibMap.get(target) 34 | default: 35 | return "lib.d.ts" 36 | } 37 | } 38 | 39 | func textSpanEnd(span TextSpan) number { 40 | return span.start + span.length 41 | } 42 | 43 | func textSpanIsEmpty(span TextSpan) bool { 44 | return span.length == 0 45 | } 46 | 47 | func textSpanContainsPosition(span TextSpan, position number) bool { 48 | return position >= span.start && position < textSpanEnd(span) 49 | } 50 | 51 | /** @internal */ 52 | 53 | func textRangeContainsPositionInclusive(range_ TextRange, position number) bool { 54 | return position >= range_.pos && position <= range_.end 55 | } 56 | 57 | // Returns true if 'span' contains 'other'. 58 | func textSpanContainsTextSpan(span TextSpan, other TextSpan) bool { 59 | return other.start >= span.start && textSpanEnd(other) <= textSpanEnd(span) 60 | } 61 | 62 | /** @internal */ 63 | 64 | func textSpanContainsTextRange(span TextSpan, range_ TextRange) bool { 65 | return range_.pos >= span.start && range_.end <= textSpanEnd(span) 66 | } 67 | 68 | /** @internal */ 69 | 70 | func textRangeContainsTextSpan(range_ TextRange, span TextSpan) bool { 71 | return span.start >= range_.pos && textSpanEnd(span) <= range_.end 72 | } 73 | 74 | func textSpanOverlapsWith(span TextSpan, other TextSpan) bool { 75 | return textSpanOverlap(span, other) != nil 76 | } 77 | 78 | func textSpanOverlap(span1 TextSpan, span2 TextSpan) *TextSpan { 79 | overlap := textSpanIntersection(span1, span2) 80 | if overlap != nil && overlap.length == 0 { 81 | return nil 82 | } else { 83 | return overlap 84 | } 85 | } 86 | 87 | func textSpanIntersectsWithTextSpan(span TextSpan, other TextSpan) bool { 88 | return decodedTextSpanIntersectsWith(span.start, span.length, other.start, other.length) 89 | } 90 | 91 | func textSpanIntersectsWith(span TextSpan, start number, length number) bool { 92 | return decodedTextSpanIntersectsWith(span.start, span.length, start, length) 93 | } 94 | 95 | func decodedTextSpanIntersectsWith(start1 number, length1 number, start2 number, length2 number) bool { 96 | end1 := start1 + length1 97 | end2 := start2 + length2 98 | return start2 <= end1 && end2 >= start1 99 | } 100 | 101 | func textSpanIntersectsWithPosition(span TextSpan, position number) bool { 102 | return position <= textSpanEnd(span) && position >= span.start 103 | } 104 | 105 | /** @internal */ 106 | 107 | func textRangeIntersectsWithTextSpan(range_ TextRange, span TextSpan) bool { 108 | return textSpanIntersectsWith(span, range_.pos, range_.end-range_.pos) 109 | } 110 | 111 | func textSpanIntersection(span1 TextSpan, span2 TextSpan) *TextSpan { 112 | start := max(span1.start, span2.start) 113 | end := min(textSpanEnd(span1), textSpanEnd(span2)) 114 | if start <= end { 115 | return createTextSpanFromBounds(start, end) 116 | } else { 117 | return nil 118 | } 119 | } 120 | 121 | /** 122 | * Given an array of text spans, returns an equivalent sorted array of text spans 123 | * where no span overlaps or is adjacent to another span in the array. 124 | * @internal 125 | */ 126 | 127 | func normalizeSpans(spans []TextSpan) []TextSpan { 128 | spans = spans.filter(func(span TextSpan) bool { 129 | return span.length > 0 130 | }).sort(func(a TextSpan, b TextSpan) number { 131 | if a.start != b.start { 132 | return a.start - b.start 133 | } else { 134 | return a.length - b.length 135 | } 136 | }) 137 | 138 | var result []TextSpan = []never{} 139 | i := 0 140 | for i < spans.length { 141 | span := spans[i] 142 | j := i + 1 143 | for j < spans.length && textSpanIntersectsWithTextSpan(span, spans[j]) { 144 | start := min(span.start, spans[j].start) 145 | end := max(textSpanEnd(span), textSpanEnd(spans[j])) 146 | span = createTextSpanFromBounds(start, end) 147 | j++ 148 | } 149 | i = j 150 | result.push(span) 151 | } 152 | 153 | return result 154 | } 155 | 156 | func createTextSpan(start number, length number) TextSpan { 157 | if start < 0 { 158 | /* TODO(TS-TO-GO) Node ThrowStatement: throw new Error("start < 0"); */ 159 | } 160 | if length < 0 { 161 | /* TODO(TS-TO-GO) Node ThrowStatement: throw new Error("length < 0"); */ 162 | } 163 | 164 | return TextSpan{ 165 | start: start, 166 | length: length, 167 | } 168 | } 169 | 170 | func createTextSpanFromBounds(start number, end number) TextSpan { 171 | return createTextSpan(start, end-start) 172 | } 173 | 174 | func textChangeRangeNewSpan(range_ TextChangeRange) TextSpan { 175 | return createTextSpan(range_.span.start, range_.newLength) 176 | } 177 | 178 | func textChangeRangeIsUnchanged(range_ TextChangeRange) bool { 179 | return textSpanIsEmpty(range_.span) && range_.newLength == 0 180 | } 181 | 182 | func createTextChangeRange(span TextSpan, newLength number) TextChangeRange { 183 | if newLength < 0 { 184 | /* TODO(TS-TO-GO) Node ThrowStatement: throw new Error("newLength < 0"); */ 185 | } 186 | 187 | return TextChangeRange{ 188 | span: span, 189 | newLength: newLength, 190 | } 191 | } 192 | 193 | var unchangedTextChangeRange TextChangeRange = createTextChangeRange(createTextSpan(0, 0), 0) 194 | 195 | /** 196 | * Called to merge all the changes that occurred across several versions of a script snapshot 197 | * into a single change. i.e. if a user keeps making successive edits to a script we will 198 | * have a text change from V1 to V2, V2 to V3, ..., Vn. 199 | * 200 | * This function will then merge those changes into a single change range valid between V1 and 201 | * Vn. 202 | */ 203 | 204 | func collapseTextChangeRangesAcrossMultipleVersions(changes []TextChangeRange) TextChangeRange { 205 | if changes.length == 0 { 206 | return unchangedTextChangeRange 207 | } 208 | 209 | if changes.length == 1 { 210 | return changes[0] 211 | } 212 | 213 | // We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd } 214 | // as it makes things much easier to reason about. 215 | change0 := changes[0] 216 | 217 | oldStartN := change0.span.start 218 | oldEndN := textSpanEnd(change0.span) 219 | newEndN := oldStartN + change0.newLength 220 | 221 | for i := 1; i < changes.length; i++ { 222 | nextChange := changes[i] 223 | 224 | // Consider the following case: 225 | // i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting 226 | // at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }. 227 | // i.e. the span starting at 30 with length 30 is increased to length 40. 228 | // 229 | // 0 10 20 30 40 50 60 70 80 90 100 230 | // ------------------------------------------------------------------------------------------------------- 231 | // | / 232 | // | /---- 233 | // T1 | /---- 234 | // | /---- 235 | // | /---- 236 | // ------------------------------------------------------------------------------------------------------- 237 | // | \ 238 | // | \ 239 | // T2 | \ 240 | // | \ 241 | // | \ 242 | // ------------------------------------------------------------------------------------------------------- 243 | // 244 | // Merging these turns out to not be too difficult. First, determining the new start of the change is trivial 245 | // it's just the min of the old and new starts. i.e.: 246 | // 247 | // 0 10 20 30 40 50 60 70 80 90 100 248 | // ------------------------------------------------------------*------------------------------------------ 249 | // | / 250 | // | /---- 251 | // T1 | /---- 252 | // | /---- 253 | // | /---- 254 | // ----------------------------------------$-------------------$------------------------------------------ 255 | // . | \ 256 | // . | \ 257 | // T2 . | \ 258 | // . | \ 259 | // . | \ 260 | // ----------------------------------------------------------------------*-------------------------------- 261 | // 262 | // (Note the dots represent the newly inferred start. 263 | // Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the 264 | // absolute positions at the asterisks, and the relative change between the dollar signs. Basically, we see 265 | // which if the two $'s precedes the other, and we move that one forward until they line up. in this case that 266 | // means: 267 | // 268 | // 0 10 20 30 40 50 60 70 80 90 100 269 | // --------------------------------------------------------------------------------*---------------------- 270 | // | / 271 | // | /---- 272 | // T1 | /---- 273 | // | /---- 274 | // | /---- 275 | // ------------------------------------------------------------$------------------------------------------ 276 | // . | \ 277 | // . | \ 278 | // T2 . | \ 279 | // . | \ 280 | // . | \ 281 | // ----------------------------------------------------------------------*-------------------------------- 282 | // 283 | // In other words (in this case), we're recognizing that the second edit happened after where the first edit 284 | // ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started 285 | // that's the same as if we started at char 80 instead of 60. 286 | // 287 | // As it so happens, the same logic applies if the second edit precedes the first edit. In that case rather 288 | // than pushing the first edit forward to match the second, we'll push the second edit forward to match the 289 | // first. 290 | // 291 | // In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange 292 | // semantics: { { start: 10, length: 70 }, newLength: 60 } 293 | // 294 | // The math then works out as follows. 295 | // If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the 296 | // final result like so: 297 | // 298 | // { 299 | // oldStart3: Min(oldStart1, oldStart2), 300 | // oldEnd3: Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), 301 | // newEnd3: Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) 302 | // } 303 | 304 | oldStart1 := oldStartN 305 | oldEnd1 := oldEndN 306 | newEnd1 := newEndN 307 | 308 | oldStart2 := nextChange.span.start 309 | oldEnd2 := textSpanEnd(nextChange.span) 310 | newEnd2 := oldStart2 + nextChange.newLength 311 | 312 | oldStartN = min(oldStart1, oldStart2) 313 | oldEndN = max(oldEnd1, oldEnd1+(oldEnd2-newEnd1)) 314 | newEndN = max(newEnd2, newEnd2+(newEnd1-oldEnd2)) 315 | } 316 | 317 | return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), newEndN-oldStartN /*newLength*/) 318 | } 319 | 320 | func getTypeParameterOwner(d Declaration) Declaration { 321 | if d && d.Kind == ast.KindTypeParameter { 322 | for current := d; current; current = current.Parent { 323 | if isFunctionLike(current) || isClassLike(current) || current.Kind == ast.KindInterfaceDeclaration { 324 | return current.AsDeclaration() 325 | } 326 | } 327 | } 328 | } 329 | 330 | type ParameterPropertyDeclaration Intersection[ParameterDeclaration /* TODO(TS-TO-GO) TypeNode TypeLiteral: { parent: ConstructorDeclaration; name: Identifier; } */, any] 331 | 332 | func isParameterPropertyDeclaration(node *ast.Node, parent *ast.Node) bool { 333 | return isParameter(node) && hasSyntacticModifier(node, ast.ModifierFlagsParameterPropertyModifier) && parent.Kind == ast.KindConstructor 334 | } 335 | 336 | func isEmptyBindingPattern(node BindingName) bool { 337 | if isBindingPattern(node) { 338 | return core.Every(node.Elements, isEmptyBindingElement) 339 | } 340 | return false 341 | } 342 | 343 | // TODO(jakebailey): It is very weird that we have BindingElement and ArrayBindingElement; 344 | // we should have ObjectBindingElement and ArrayBindingElement, which are both BindingElement, 345 | // just like BindingPattern is a ObjectBindingPattern or a ArrayBindingPattern. 346 | func isEmptyBindingElement(node Union[BindingElement, ArrayBindingElement]) bool { 347 | if isOmittedExpression(node) { 348 | return true 349 | } 350 | return isEmptyBindingPattern(node.Name) 351 | } 352 | 353 | func walkUpBindingElementsAndPatterns(binding BindingElement) Union[VariableDeclaration, ParameterDeclaration] { 354 | node := binding.Parent 355 | for isBindingElement(node.Parent) { 356 | node = node.Parent.Parent 357 | } 358 | return node.Parent 359 | } 360 | 361 | func getCombinedFlags(node *ast.Node, getFlags func(n *ast.Node) number) number { 362 | if isBindingElement(node) { 363 | node = walkUpBindingElementsAndPatterns(node) 364 | } 365 | flags := getFlags(node) 366 | if node.Kind == ast.KindVariableDeclaration { 367 | node = node.Parent 368 | } 369 | if node && node.Kind == ast.KindVariableDeclarationList { 370 | flags |= getFlags(node) 371 | node = node.Parent 372 | } 373 | if node && node.Kind == ast.KindVariableStatement { 374 | flags |= getFlags(node) 375 | } 376 | return flags 377 | } 378 | 379 | func getCombinedModifierFlags(node Declaration) ModifierFlags { 380 | return getCombinedFlags(node, getEffectiveModifierFlags) 381 | } 382 | 383 | /** @internal */ 384 | 385 | func getCombinedNodeFlagsAlwaysIncludeJSDoc(node Declaration) ModifierFlags { 386 | return getCombinedFlags(node, getEffectiveModifierFlagsAlwaysIncludeJSDoc) 387 | } 388 | 389 | // Returns the node flags for this node and all relevant parent nodes. This is done so that 390 | // nodes like variable declarations and binding elements can returned a view of their flags 391 | // that includes the modifiers from their container. i.e. flags like export/declare aren't 392 | // stored on the variable declaration directly, but on the containing variable statement 393 | // (if it has one). Similarly, flags for let/const are stored on the variable declaration 394 | // list. By calling this function, all those flags are combined so that the client can treat 395 | // the node as if it actually had those flags. 396 | func getCombinedNodeFlags(node *ast.Node) NodeFlags { 397 | return getCombinedFlags(node, getNodeFlags) 398 | } 399 | 400 | func getNodeFlags(node *ast.Node) NodeFlags { 401 | return node.Flags 402 | } 403 | 404 | /** @internal */ 405 | 406 | var supportedLocaleDirectories = []any{"cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-br", "ru", "tr", "zh-cn", "zh-tw"} 407 | 408 | /** 409 | * Checks to see if the locale is in the appropriate format, 410 | * and if it is, attempts to set the appropriate language. 411 | */ 412 | 413 | func validateLocaleAndSetLanguage(locale string, sys /* TODO(TS-TO-GO) TypeNode TypeLiteral: { getExecutingFilePath(): string; resolvePath(path: string): string; fileExists(fileName: string): boolean; readFile(fileName: string): string | undefined; } */ any, errors []Diagnostic) { 414 | lowerCaseLocale := locale.toLowerCase() 415 | matchResult := regexp.MustParse(`^([a-z]+)(?:[_-]([a-z]+))?$`).exec(lowerCaseLocale) 416 | 417 | if matchResult == nil { 418 | if errors != nil { 419 | errors.push(createCompilerDiagnostic(Diagnostics.Locale_must_be_of_the_form_language_or_language_territory_For_example_0_or_1, "en", "ja-jp")) 420 | } 421 | return 422 | } 423 | 424 | language := matchResult[1] 425 | territory := matchResult[2] 426 | 427 | // First try the entire locale, then fall back to just language if that's all we have. 428 | // Either ways do not fail, and fallback to the English diagnostic strings. 429 | if contains(supportedLocaleDirectories, lowerCaseLocale) && !trySetLanguageAndTerritory(language, territory, errors) { 430 | trySetLanguageAndTerritory(language, nil /*territory*/, errors) 431 | } 432 | 433 | // Set the UI locale for string collation 434 | setUILocale(locale) 435 | 436 | trySetLanguageAndTerritory := func(language string, territory *string, errors []Diagnostic) bool { 437 | compilerFilePath := normalizePath(sys.getExecutingFilePath()) 438 | containingDirectoryPath := getDirectoryPath(compilerFilePath) 439 | 440 | filePath := combinePaths(containingDirectoryPath, language) 441 | 442 | if territory { 443 | filePath = filePath + "-" + territory 444 | } 445 | 446 | filePath = sys.resolvePath(combinePaths(filePath, "diagnosticMessages.generated.json")) 447 | 448 | if !sys.fileExists(filePath) { 449 | return false 450 | } 451 | 452 | // TODO: Add codePage support for readFile? 453 | var fileContents *string = "" 454 | { // try 455 | fileContents = sys.readFile(filePath) 456 | } 457 | { // catch 458 | if errors != nil { 459 | errors.push(createCompilerDiagnostic(Diagnostics.Unable_to_open_file_0, filePath)) 460 | } 461 | return false 462 | } 463 | { // try 464 | // this is a global mutation (or live binding update)! 465 | setLocalizedDiagnosticMessages(JSON.parse(fileContents)) 466 | } 467 | { // catch 468 | if errors != nil { 469 | errors.push(createCompilerDiagnostic(Diagnostics.Corrupted_locale_file_0, filePath)) 470 | } 471 | return false 472 | } 473 | 474 | return true 475 | } 476 | 477 | } 478 | 479 | /* OVERLOAD: export function getOriginalNode(node: Node): Node; */ 480 | /* OVERLOAD: export function getOriginalNode(node: Node, nodeTest: (node: Node) => node is T): T; */ 481 | /* OVERLOAD: export function getOriginalNode(node: Node | undefined): Node | undefined; */ 482 | /* OVERLOAD: export function getOriginalNode(node: Node | undefined, nodeTest: (node: Node) => node is T): T | undefined; */ 483 | func getOriginalNode(node *ast.Node, nodeTest func(node *ast.Node) /* TODO(TS-TO-GO) TypeNode TypePredicate: node is T */ any) *T { 484 | if node != nil { 485 | for node.Original != nil { 486 | node = node.Original 487 | } 488 | } 489 | 490 | if node == nil || nodeTest == nil { 491 | return node /* as T | undefined */ 492 | } 493 | 494 | if nodeTest(node) { 495 | return node 496 | } else { 497 | return nil 498 | } 499 | } 500 | 501 | /** 502 | * Iterates through the parent chain of a node and performs the callback on each parent until the callback 503 | * returns a truthy value, then returns that value. 504 | * If no such value is found, it applies the callback until the parent pointer is undefined or the callback returns "quit" 505 | * At that point findAncestor returns undefined. 506 | */ 507 | 508 | /* OVERLOAD: export function findAncestor(node: Node | undefined, callback: (element: Node) => element is T): T | undefined; */ 509 | /* OVERLOAD: export function findAncestor(node: Node | undefined, callback: (element: Node) => boolean | "quit"): Node | undefined; */ 510 | func findAncestor(node *ast.Node, callback func(element *ast.Node) Union[bool /* TODO(TS-TO-GO) TypeNode LiteralType: "quit" */, any]) *ast.Node { 511 | for node != nil { 512 | result := callback(node) 513 | if result == "quit" { 514 | return nil 515 | } else if result { 516 | return node 517 | } 518 | node = node.Parent 519 | } 520 | return nil 521 | } 522 | 523 | /** 524 | * Gets a value indicating whether a node originated in the parse tree. 525 | * 526 | * @param node The node to test. 527 | */ 528 | 529 | func isParseTreeNode(node *ast.Node) bool { 530 | return (node.Flags & ast.NodeFlagsSynthesized) == 0 531 | } 532 | 533 | /** 534 | * Gets the original parse tree node for a node. 535 | * 536 | * @param node The original node. 537 | * @returns The original parse tree node if found; otherwise, undefined. 538 | */ 539 | 540 | /* OVERLOAD: export function getParseTreeNode(node: Node | undefined): Node | undefined; */ 541 | /** 542 | * Gets the original parse tree node for a node. 543 | * 544 | * @param node The original node. 545 | * @param nodeTest A callback used to ensure the correct type of parse tree node is returned. 546 | * @returns The original parse tree node if found; otherwise, undefined. 547 | */ 548 | 549 | /* OVERLOAD: export function getParseTreeNode(node: T | undefined, nodeTest?: (node: Node) => node is T): T | undefined; */ 550 | func getParseTreeNode(node *ast.Node, nodeTest func(node *ast.Node) bool) *ast.Node { 551 | if node == nil || isParseTreeNode(node) { 552 | return node 553 | } 554 | 555 | node = node.Original 556 | for node != nil { 557 | if isParseTreeNode(node) { 558 | if nodeTest == nil || nodeTest(node) { 559 | return node 560 | } else { 561 | return nil 562 | } 563 | } 564 | node = node.Original 565 | } 566 | } 567 | 568 | /** Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__' */ 569 | 570 | func escapeLeadingUnderscores(identifier string) string { 571 | return (ifElse(identifier.length >= 2 && identifier.charCodeAt(0) == CharacterCodes_ && identifier.charCodeAt(1) == CharacterCodes_, "_"+identifier, identifier)).(string) 572 | } 573 | 574 | /** 575 | * Remove extra underscore from escaped identifier text content. 576 | * 577 | * @param identifier The escaped identifier text. 578 | * @returns The unescaped identifier text. 579 | */ 580 | 581 | func unescapeLeadingUnderscores(identifier string) string { 582 | id := identifier /* as string */ 583 | if id.length >= 3 && id.charCodeAt(0) == CharacterCodes_ && id.charCodeAt(1) == CharacterCodes_ && id.charCodeAt(2) == CharacterCodes_ { 584 | return id.substr(1) 585 | } else { 586 | return id 587 | } 588 | } 589 | 590 | func idText(identifierOrPrivateName Union[Identifier, PrivateIdentifier]) string { 591 | return unescapeLeadingUnderscores(identifierOrPrivateName.EscapedText) 592 | } 593 | 594 | /** 595 | * If the text of an Identifier matches a keyword (including contextual and TypeScript-specific keywords), returns the 596 | * SyntaxKind for the matching keyword. 597 | */ 598 | 599 | func identifierToKeywordKind(node Identifier) *KeywordSyntaxKind { 600 | token := stringToToken(node.EscapedText /* as string */) 601 | if token { 602 | return tryCast(token, isKeyword) 603 | } else { 604 | return nil 605 | } 606 | } 607 | 608 | func symbolName(symbol *ast.Symbol) string { 609 | if symbol.ValueDeclaration != nil && isPrivateIdentifierClassElementDeclaration(symbol.ValueDeclaration) { 610 | return idText(symbol.ValueDeclaration.Name) 611 | } 612 | return unescapeLeadingUnderscores(symbol.EscapedName) 613 | } 614 | 615 | /** 616 | * A JSDocTypedef tag has an _optional_ name field - if a name is not directly present, we should 617 | * attempt to draw the name from the node the declaration is on (as that declaration is what its' symbol 618 | * will be merged with) 619 | */ 620 | 621 | func nameForNamelessJSDocTypedef(declaration Union[JSDocTypedefTag, JSDocEnumTag]) Union[Identifier, PrivateIdentifier, undefined] { 622 | hostNode := declaration.Parent.Parent 623 | if !hostNode { 624 | return nil 625 | } 626 | // Covers classes, functions - any named declaration host node 627 | if isDeclaration(hostNode) { 628 | return getDeclarationIdentifier(hostNode) 629 | } 630 | // Covers remaining cases (returning undefined if none match). 631 | switch hostNode.Kind { 632 | case ast.KindVariableStatement: 633 | if hostNode.DeclarationList && hostNode.DeclarationList.Declarations[0] { 634 | return getDeclarationIdentifier(hostNode.DeclarationList.Declarations[0]) 635 | } 636 | case ast.KindExpressionStatement: 637 | expr := hostNode.Expression 638 | if expr.Kind == ast.KindBinaryExpression && expr.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken { 639 | expr = expr.AsBinaryExpression().Left 640 | } 641 | switch expr.Kind { 642 | case ast.KindPropertyAccessExpression: 643 | return expr.AsPropertyAccessExpression().Name 644 | case ast.KindElementAccessExpression: 645 | arg := expr.AsElementAccessExpression().ArgumentExpression 646 | if isIdentifier(arg) { 647 | return arg 648 | } 649 | } 650 | case ast.KindParenthesizedExpression: 651 | return getDeclarationIdentifier(hostNode.Expression) 652 | case ast.KindLabeledStatement: 653 | if isDeclaration(hostNode.Statement) || isExpression(hostNode.Statement) { 654 | return getDeclarationIdentifier(hostNode.Statement) 655 | } 656 | } 657 | } 658 | 659 | func getDeclarationIdentifier(node Union[Declaration, Expression]) *Identifier { 660 | name := getNameOfDeclaration(node) 661 | if name != nil && isIdentifier(name) { 662 | return name 663 | } else { 664 | return nil 665 | } 666 | } 667 | 668 | /** @internal */ 669 | 670 | func nodeHasName(statement *ast.Node, name Identifier) bool { 671 | if isNamedDeclaration(statement) && isIdentifier(statement.Name) && idText(statement.Name.AsIdentifier()) == idText(name) { 672 | return true 673 | } 674 | if isVariableStatement(statement) && core.Some(statement.DeclarationList.Declarations, func(d VariableDeclaration) bool { 675 | return nodeHasName(d, name) 676 | }) { 677 | return true 678 | } 679 | return false 680 | } 681 | 682 | func getNameOfJSDocTypedef(declaration JSDocTypedefTag) Union[Identifier, PrivateIdentifier, undefined] { 683 | return declaration.Name || nameForNamelessJSDocTypedef(declaration) 684 | } 685 | 686 | /** @internal */ 687 | 688 | func isNamedDeclaration(node *ast.Node) bool { 689 | return node.AsNamedDeclaration().Name != nil 690 | // A 'name' property should always be a DeclarationName. 691 | } 692 | 693 | /** @internal */ 694 | 695 | func getNonAssignedNameOfDeclaration(declaration Union[Declaration, Expression]) *DeclarationName { 696 | switch declaration.Kind { 697 | case ast.KindIdentifier: 698 | return declaration.AsIdentifier() 699 | case ast.KindJSDocPropertyTag, 700 | ast.KindJSDocParameterTag: 701 | TODO_IDENTIFIER := declaration.AsJSDocPropertyLikeTag() 702 | if name.Kind == ast.KindQualifiedName { 703 | return name.Right 704 | } 705 | case ast.KindCallExpression, 706 | ast.KindBinaryExpression: 707 | expr := declaration /* as BinaryExpression | CallExpression */ 708 | switch getAssignmentDeclarationKind(expr) { 709 | case AssignmentDeclarationKindExportsProperty, 710 | AssignmentDeclarationKindThisProperty, 711 | AssignmentDeclarationKindProperty, 712 | AssignmentDeclarationKindPrototypeProperty: 713 | return getElementOrPropertyAccessArgumentExpressionOrName(expr.AsBinaryExpression().Left.AsAccessExpression()) 714 | case AssignmentDeclarationKindObjectDefinePropertyValue, 715 | AssignmentDeclarationKindObjectDefinePropertyExports, 716 | AssignmentDeclarationKindObjectDefinePrototypeProperty: 717 | return expr.AsBindableObjectDefinePropertyCall().Arguments[1] 718 | default: 719 | return nil 720 | } 721 | fallthrough 722 | case ast.KindJSDocTypedefTag: 723 | return getNameOfJSDocTypedef(declaration.AsJSDocTypedefTag()) 724 | case ast.KindJSDocEnumTag: 725 | return nameForNamelessJSDocTypedef(declaration.AsJSDocEnumTag()) 726 | case ast.KindExportAssignment: 727 | TODO_IDENTIFIER := declaration.AsExportAssignment() 728 | if isIdentifier(expression) { 729 | return expression 730 | } else { 731 | return nil 732 | } 733 | case ast.KindElementAccessExpression: 734 | expr := declaration.AsElementAccessExpression() 735 | if isBindableStaticElementAccessExpression(expr) { 736 | return expr.ArgumentExpression 737 | } 738 | } 739 | return declaration.AsNamedDeclaration().Name 740 | } 741 | 742 | func getNameOfDeclaration(declaration Union[Declaration, Expression, undefined]) *DeclarationName { 743 | if declaration == nil { 744 | return nil 745 | } 746 | return getNonAssignedNameOfDeclaration(declaration) || (ifElse(isFunctionExpression(declaration) || isArrowFunction(declaration) || isClassExpression(declaration), getAssignedName(declaration), nil)) 747 | } 748 | 749 | /** @internal */ 750 | 751 | func getAssignedName(node *ast.Node) *DeclarationName { 752 | if !node.Parent { 753 | return nil 754 | } else if isPropertyAssignment(node.Parent) || isBindingElement(node.Parent) { 755 | return node.Parent.Name 756 | } else if isBinaryExpression(node.Parent) && node == node.Parent.Right { 757 | if isIdentifier(node.Parent.Left) { 758 | return node.Parent.Left 759 | } else if isAccessExpression(node.Parent.Left) { 760 | return getElementOrPropertyAccessArgumentExpressionOrName(node.Parent.Left) 761 | } 762 | } else if isVariableDeclaration(node.Parent) && isIdentifier(node.Parent.Name) { 763 | return node.Parent.Name 764 | } 765 | } 766 | 767 | func getDecorators(node HasDecorators) *[]Decorator { 768 | if hasDecorators(node) { 769 | return core.Filter(node.Modifiers, isDecorator) 770 | } 771 | } 772 | 773 | func getModifiers(node HasModifiers) *[]Modifier { 774 | if hasSyntacticModifier(node, ast.ModifierFlagsModifier) { 775 | return core.Filter(node.Modifiers, isModifier) 776 | } 777 | } 778 | 779 | func getJSDocParameterTagsWorker(param ParameterDeclaration, noCache bool) []JSDocParameterTag { 780 | if param.Name { 781 | if isIdentifier(param.Name) { 782 | name := param.Name.EscapedText 783 | return getJSDocTagsWorker(param.Parent, noCache).filter(func(tag JSDocTag) bool { 784 | return isJSDocParameterTag(tag) && isIdentifier(tag.Name) && tag.Name.EscapedText == name 785 | }) 786 | } else { 787 | i := param.Parent.Parameters.indexOf(param) 788 | Debug.assert(i > -1, "Parameters should always be in their parents' parameter list") 789 | paramTags := getJSDocTagsWorker(param.Parent, noCache).filter(isJSDocParameterTag) 790 | if i < paramTags.length { 791 | return []JSDocParameterTag{paramTags[i]} 792 | } 793 | } 794 | } 795 | // return empty array for: out-of-order binding patterns and JSDoc function syntax, which has un-named parameters 796 | return emptyArray 797 | } 798 | 799 | /** 800 | * Gets the JSDoc parameter tags for the node if present. 801 | * 802 | * @remarks Returns any JSDoc param tag whose name matches the provided 803 | * parameter, whether a param tag on a containing function 804 | * expression, or a param tag on a variable declaration whose 805 | * initializer is the containing function. The tags closest to the 806 | * node are returned first, so in the previous example, the param 807 | * tag on the containing function expression would be first. 808 | * 809 | * For binding patterns, parameter tags are matched by position. 810 | */ 811 | 812 | func getJSDocParameterTags(param ParameterDeclaration) []JSDocParameterTag { 813 | return getJSDocParameterTagsWorker(param, false /*noCache*/) 814 | } 815 | 816 | /** @internal */ 817 | 818 | func getJSDocParameterTagsNoCache(param ParameterDeclaration) []JSDocParameterTag { 819 | return getJSDocParameterTagsWorker(param, true /*noCache*/) 820 | } 821 | 822 | func getJSDocTypeParameterTagsWorker(param TypeParameterDeclaration, noCache bool) []JSDocTemplateTag { 823 | name := param.Name.EscapedText 824 | return getJSDocTagsWorker(param.Parent, noCache).filter(func(tag JSDocTag) bool { 825 | return isJSDocTemplateTag(tag) && tag.TypeParameters.some(func(tp TypeParameterDeclaration) bool { 826 | return tp.Name.EscapedText == name 827 | }) 828 | }) 829 | } 830 | 831 | /** 832 | * Gets the JSDoc type parameter tags for the node if present. 833 | * 834 | * @remarks Returns any JSDoc template tag whose names match the provided 835 | * parameter, whether a template tag on a containing function 836 | * expression, or a template tag on a variable declaration whose 837 | * initializer is the containing function. The tags closest to the 838 | * node are returned first, so in the previous example, the template 839 | * tag on the containing function expression would be first. 840 | */ 841 | 842 | func getJSDocTypeParameterTags(param TypeParameterDeclaration) []JSDocTemplateTag { 843 | return getJSDocTypeParameterTagsWorker(param, false /*noCache*/) 844 | } 845 | 846 | /** @internal */ 847 | 848 | func getJSDocTypeParameterTagsNoCache(param TypeParameterDeclaration) []JSDocTemplateTag { 849 | return getJSDocTypeParameterTagsWorker(param, true /*noCache*/) 850 | } 851 | 852 | /** 853 | * Return true if the node has JSDoc parameter tags. 854 | * 855 | * @remarks Includes parameter tags that are not directly on the node, 856 | * for example on a variable declaration whose initializer is a function expression. 857 | */ 858 | 859 | func hasJSDocParameterTags(node Union[FunctionLikeDeclaration, SignatureDeclaration]) bool { 860 | return getFirstJSDocTag(node, isJSDocParameterTag) != nil 861 | } 862 | 863 | /** Gets the JSDoc augments tag for the node if present */ 864 | 865 | func getJSDocAugmentsTag(node *ast.Node) *JSDocAugmentsTag { 866 | return getFirstJSDocTag(node, isJSDocAugmentsTag) 867 | } 868 | 869 | /** Gets the JSDoc implements tags for the node if present */ 870 | 871 | func getJSDocImplementsTags(node *ast.Node) []JSDocImplementsTag { 872 | return getAllJSDocTags(node, isJSDocImplementsTag) 873 | } 874 | 875 | /** Gets the JSDoc class tag for the node if present */ 876 | 877 | func getJSDocClassTag(node *ast.Node) *JSDocClassTag { 878 | return getFirstJSDocTag(node, isJSDocClassTag) 879 | } 880 | 881 | /** Gets the JSDoc public tag for the node if present */ 882 | 883 | func getJSDocPublicTag(node *ast.Node) *JSDocPublicTag { 884 | return getFirstJSDocTag(node, isJSDocPublicTag) 885 | } 886 | 887 | /** @internal */ 888 | 889 | func getJSDocPublicTagNoCache(node *ast.Node) *JSDocPublicTag { 890 | return getFirstJSDocTag(node, isJSDocPublicTag, true /*noCache*/) 891 | } 892 | 893 | /** Gets the JSDoc private tag for the node if present */ 894 | 895 | func getJSDocPrivateTag(node *ast.Node) *JSDocPrivateTag { 896 | return getFirstJSDocTag(node, isJSDocPrivateTag) 897 | } 898 | 899 | /** @internal */ 900 | 901 | func getJSDocPrivateTagNoCache(node *ast.Node) *JSDocPrivateTag { 902 | return getFirstJSDocTag(node, isJSDocPrivateTag, true /*noCache*/) 903 | } 904 | 905 | /** Gets the JSDoc protected tag for the node if present */ 906 | 907 | func getJSDocProtectedTag(node *ast.Node) *JSDocProtectedTag { 908 | return getFirstJSDocTag(node, isJSDocProtectedTag) 909 | } 910 | 911 | /** @internal */ 912 | 913 | func getJSDocProtectedTagNoCache(node *ast.Node) *JSDocProtectedTag { 914 | return getFirstJSDocTag(node, isJSDocProtectedTag, true /*noCache*/) 915 | } 916 | 917 | /** Gets the JSDoc protected tag for the node if present */ 918 | 919 | func getJSDocReadonlyTag(node *ast.Node) *JSDocReadonlyTag { 920 | return getFirstJSDocTag(node, isJSDocReadonlyTag) 921 | } 922 | 923 | /** @internal */ 924 | 925 | func getJSDocReadonlyTagNoCache(node *ast.Node) *JSDocReadonlyTag { 926 | return getFirstJSDocTag(node, isJSDocReadonlyTag, true /*noCache*/) 927 | } 928 | 929 | func getJSDocOverrideTagNoCache(node *ast.Node) *JSDocOverrideTag { 930 | return getFirstJSDocTag(node, isJSDocOverrideTag, true /*noCache*/) 931 | } 932 | 933 | /** Gets the JSDoc deprecated tag for the node if present */ 934 | 935 | func getJSDocDeprecatedTag(node *ast.Node) *JSDocDeprecatedTag { 936 | return getFirstJSDocTag(node, isJSDocDeprecatedTag) 937 | } 938 | 939 | /** @internal */ 940 | 941 | func getJSDocDeprecatedTagNoCache(node *ast.Node) *JSDocDeprecatedTag { 942 | return getFirstJSDocTag(node, isJSDocDeprecatedTag, true /*noCache*/) 943 | } 944 | 945 | /** Gets the JSDoc enum tag for the node if present */ 946 | 947 | func getJSDocEnumTag(node *ast.Node) *JSDocEnumTag { 948 | return getFirstJSDocTag(node, isJSDocEnumTag) 949 | } 950 | 951 | /** Gets the JSDoc this tag for the node if present */ 952 | 953 | func getJSDocThisTag(node *ast.Node) *JSDocThisTag { 954 | return getFirstJSDocTag(node, isJSDocThisTag) 955 | } 956 | 957 | /** Gets the JSDoc return tag for the node if present */ 958 | 959 | func getJSDocReturnTag(node *ast.Node) *JSDocReturnTag { 960 | return getFirstJSDocTag(node, isJSDocReturnTag) 961 | } 962 | 963 | /** Gets the JSDoc template tag for the node if present */ 964 | 965 | func getJSDocTemplateTag(node *ast.Node) *JSDocTemplateTag { 966 | return getFirstJSDocTag(node, isJSDocTemplateTag) 967 | } 968 | 969 | func getJSDocSatisfiesTag(node *ast.Node) *JSDocSatisfiesTag { 970 | return getFirstJSDocTag(node, isJSDocSatisfiesTag) 971 | } 972 | 973 | /** Gets the JSDoc type tag for the node if present and valid */ 974 | 975 | func getJSDocTypeTag(node *ast.Node) *JSDocTypeTag { 976 | // We should have already issued an error if there were multiple type jsdocs, so just use the first one. 977 | tag := getFirstJSDocTag(node, isJSDocTypeTag) 978 | if tag != nil && tag.TypeExpression && tag.TypeExpression.Type_ { 979 | return tag 980 | } 981 | return nil 982 | } 983 | 984 | /** 985 | * Gets the type node for the node if provided via JSDoc. 986 | * 987 | * @remarks The search includes any JSDoc param tag that relates 988 | * to the provided parameter, for example a type tag on the 989 | * parameter itself, or a param tag on a containing function 990 | * expression, or a param tag on a variable declaration whose 991 | * initializer is the containing function. The tags closest to the 992 | * node are examined first, so in the previous example, the type 993 | * tag directly on the node would be returned. 994 | */ 995 | 996 | func getJSDocType(node *ast.Node) *TypeNode { 997 | var tag Union[JSDocTypeTag, JSDocParameterTag, undefined] = getFirstJSDocTag(node, isJSDocTypeTag) 998 | if tag == nil && isParameter(node) { 999 | tag = core.Find(getJSDocParameterTags(node), func(tag JSDocParameterTag) bool { 1000 | return tag.TypeExpression != nil 1001 | }) 1002 | } 1003 | 1004 | return tag && tag.TypeExpression && tag.TypeExpression.Type_ 1005 | } 1006 | 1007 | /** 1008 | * Gets the return type node for the node if provided via JSDoc return tag or type tag. 1009 | * 1010 | * @remarks `getJSDocReturnTag` just gets the whole JSDoc tag. This function 1011 | * gets the type from inside the braces, after the fat arrow, etc. 1012 | */ 1013 | 1014 | func getJSDocReturnType(node *ast.Node) *TypeNode { 1015 | returnTag := getJSDocReturnTag(node) 1016 | if returnTag != nil && returnTag.TypeExpression != nil { 1017 | return returnTag.TypeExpression.Type_ 1018 | } 1019 | typeTag := getJSDocTypeTag(node) 1020 | if typeTag != nil && typeTag.TypeExpression { 1021 | t := typeTag.TypeExpression.Type_ 1022 | if isTypeLiteralNode(t) { 1023 | sig := core.Find(t.Members, isCallSignatureDeclaration) 1024 | return sig && sig.Type_ 1025 | } 1026 | if isFunctionTypeNode(t) || isJSDocFunctionType(t) { 1027 | return t.Type_ 1028 | } 1029 | } 1030 | } 1031 | 1032 | func getJSDocTagsWorker(node *ast.Node, noCache bool) []JSDocTag { 1033 | if !canHaveJSDoc(node) { 1034 | return emptyArray 1035 | } 1036 | tags := node.JsDoc. /* ? */ jsDocCache 1037 | // If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing. 1038 | if tags == nil || noCache { 1039 | comments := getJSDocCommentsAndTags(node, noCache) 1040 | Debug.assert(comments.length < 2 || comments[0] != comments[1]) 1041 | tags = flatMap(comments, func(j /* TODO(TS-TO-GO) inferred type JSDocTag | JSDoc */ any) * /* TODO(TS-TO-GO) inferred type JSDocTag | NodeArray */ any { 1042 | if isJSDoc(j) { 1043 | return j.Tags 1044 | } else { 1045 | return j 1046 | } 1047 | }) 1048 | if !noCache { 1049 | if node.JsDoc == nil { 1050 | node.JsDoc = []never{} 1051 | } 1052 | node.JsDoc.jsDocCache = tags 1053 | } 1054 | } 1055 | return tags 1056 | } 1057 | 1058 | /** Get all JSDoc tags related to a node, including those on parent nodes. */ 1059 | 1060 | func getJSDocTags(node *ast.Node) []JSDocTag { 1061 | return getJSDocTagsWorker(node, false /*noCache*/) 1062 | } 1063 | 1064 | /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ 1065 | 1066 | func getFirstJSDocTag(node *ast.Node, predicate func(tag JSDocTag) /* TODO(TS-TO-GO) TypeNode TypePredicate: tag is T */ any, noCache bool) *T { 1067 | return core.Find(getJSDocTagsWorker(node, noCache), predicate) 1068 | } 1069 | 1070 | /** Gets all JSDoc tags that match a specified predicate */ 1071 | 1072 | func getAllJSDocTags(node *ast.Node, predicate func(tag JSDocTag) /* TODO(TS-TO-GO) TypeNode TypePredicate: tag is T */ any) []T { 1073 | return getJSDocTags(node).filter(predicate) 1074 | } 1075 | 1076 | /** Gets all JSDoc tags of a specified kind */ 1077 | 1078 | func getAllJSDocTagsOfKind(node *ast.Node, kind SyntaxKind) []JSDocTag { 1079 | return getJSDocTags(node).filter(func(doc JSDocTag) bool { 1080 | return doc.Kind == kind 1081 | }) 1082 | } 1083 | 1084 | /** Gets the text of a jsdoc comment, flattening links to their text. */ 1085 | 1086 | func getTextOfJSDocComment(comment Union[string, NodeArray[JSDocComment]]) *string { 1087 | if /* TODO(TS-TO-GO) Expression TypeOfExpression: typeof comment */ TODO == "string" { 1088 | return comment 1089 | } else { 1090 | return comment. /* ? */ map_(func(c JSDocComment) string { 1091 | if c.Kind == ast.KindJSDocText { 1092 | return c.Text 1093 | } else { 1094 | return formatJSDocLink(c) 1095 | } 1096 | }).join("") 1097 | } 1098 | } 1099 | 1100 | func formatJSDocLink(link Union[JSDocLink, JSDocLinkCode, JSDocLinkPlain]) string { 1101 | var kind /* TODO(TS-TO-GO) inferred type "link" | "linkcode" | "linkplain" */ any 1102 | switch { 1103 | case link.Kind == ast.KindJSDocLink: 1104 | kind = "link" 1105 | case link.Kind == ast.KindJSDocLinkCode: 1106 | kind = "linkcode" 1107 | default: 1108 | kind = "linkplain" 1109 | } 1110 | var name string 1111 | if link.Name != nil { 1112 | name = entityNameToString(link.Name) 1113 | } else { 1114 | name = "" 1115 | } 1116 | var space /* TODO(TS-TO-GO) inferred type "" | " " */ any 1117 | if link.Name != nil && (link.Text == "" || link.Text.startsWith("://")) { 1118 | space = "" 1119 | } else { 1120 | space = " " 1121 | } 1122 | return __TEMPLATE__("{@", kind, " ", name, space, link.Text, "}") 1123 | } 1124 | 1125 | /** 1126 | * Gets the effective type parameters. If the node was parsed in a 1127 | * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. 1128 | * 1129 | * This does *not* return type parameters from a jsdoc reference to a generic type, eg 1130 | * 1131 | * type Id = (x: T) => T 1132 | * /** @type {Id} / 1133 | * function id(x) { return x } 1134 | */ 1135 | 1136 | func getEffectiveTypeParameterDeclarations(node DeclarationWithTypeParameters) []TypeParameterDeclaration { 1137 | if isJSDocSignature(node) { 1138 | if isJSDocOverloadTag(node.Parent) { 1139 | jsDoc := getJSDocRoot(node.Parent) 1140 | if jsDoc != nil && length(jsDoc.Tags) != 0 { 1141 | return flatMap(jsDoc.Tags, func(tag JSDocTag) *NodeArray[TypeParameterDeclaration] { 1142 | if isJSDocTemplateTag(tag) { 1143 | return tag.TypeParameters 1144 | } else { 1145 | return nil 1146 | } 1147 | }) 1148 | } 1149 | } 1150 | return emptyArray 1151 | } 1152 | if isJSDocTypeAlias(node) { 1153 | Debug.assert(node.Parent.Kind == ast.KindJSDoc) 1154 | return flatMap(node.Parent.Tags, func(tag JSDocTag) *NodeArray[TypeParameterDeclaration] { 1155 | if isJSDocTemplateTag(tag) { 1156 | return tag.TypeParameters 1157 | } else { 1158 | return nil 1159 | } 1160 | }) 1161 | } 1162 | if node.TypeParameters != nil { 1163 | return node.TypeParameters 1164 | } 1165 | if canHaveIllegalTypeParameters(node) && node.TypeParameters { 1166 | return node.TypeParameters 1167 | } 1168 | if isInJSFile(node) { 1169 | decls := getJSDocTypeParameterDeclarations(node) 1170 | if decls.length != 0 { 1171 | return decls 1172 | } 1173 | typeTag := getJSDocType(node) 1174 | if typeTag != nil && isFunctionTypeNode(typeTag) && typeTag.TypeParameters != nil { 1175 | return typeTag.TypeParameters 1176 | } 1177 | } 1178 | return emptyArray 1179 | } 1180 | 1181 | func getEffectiveConstraintOfTypeParameter(node TypeParameterDeclaration) *TypeNode { 1182 | switch { 1183 | case node.Constraint != nil: 1184 | return node.Constraint 1185 | case isJSDocTemplateTag(node.Parent) && node == node.Parent.TypeParameters[0]: 1186 | return node.Parent.Constraint 1187 | default: 1188 | return nil 1189 | } 1190 | } 1191 | 1192 | // #region 1193 | 1194 | func isMemberName(node *ast.Node) bool { 1195 | return node.Kind == ast.KindIdentifier || node.Kind == ast.KindPrivateIdentifier 1196 | } 1197 | 1198 | /** @internal */ 1199 | 1200 | func isGetOrSetAccessorDeclaration(node *ast.Node) bool { 1201 | return node.Kind == ast.KindSetAccessor || node.Kind == ast.KindGetAccessor 1202 | } 1203 | 1204 | func isPropertyAccessChain(node *ast.Node) bool { 1205 | return isPropertyAccessExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 1206 | } 1207 | 1208 | func isElementAccessChain(node *ast.Node) bool { 1209 | return isElementAccessExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 1210 | } 1211 | 1212 | func isCallChain(node *ast.Node) bool { 1213 | return isCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 1214 | } 1215 | 1216 | func isOptionalChain(node *ast.Node) bool { 1217 | kind := node.Kind 1218 | return node.Flags&ast.NodeFlagsOptionalChain != 0 && (kind == ast.KindPropertyAccessExpression || kind == ast.KindElementAccessExpression || kind == ast.KindCallExpression || kind == ast.KindNonNullExpression) 1219 | } 1220 | 1221 | /** @internal */ 1222 | 1223 | func isOptionalChainRoot(node *ast.Node) bool { 1224 | return isOptionalChain(node) && !isNonNullExpression(node) && node.QuestionDotToken != nil 1225 | } 1226 | 1227 | /** 1228 | * Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`). 1229 | * 1230 | * @internal 1231 | */ 1232 | 1233 | func isExpressionOfOptionalChainRoot(node *ast.Node) bool { 1234 | return isOptionalChainRoot(node.Parent) && node.Parent.Expression == node 1235 | } 1236 | 1237 | /** 1238 | * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: 1239 | * 1240 | * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) 1241 | * 2. For `a?.b!`, the outermost chain is `a?.b` (`b` is the end of the chain starting at `a?.`) 1242 | * 3. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) 1243 | * 4. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is 1244 | * the end of the chain starting at `c?.`) 1245 | * 5. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is 1246 | * the end of the chain starting at `a?.`) 1247 | * 1248 | * @internal 1249 | */ 1250 | 1251 | func isOutermostOptionalChain(node OptionalChain) bool { 1252 | return !isOptionalChain(node.Parent) || isOptionalChainRoot(node.Parent) || node != node.Parent.Expression 1253 | // case 5 1254 | } 1255 | 1256 | func isNullishCoalesce(node *ast.Node) bool { 1257 | return node.Kind == ast.KindBinaryExpression && node.AsBinaryExpression().OperatorToken.Kind == ast.KindQuestionQuestionToken 1258 | } 1259 | 1260 | func isConstTypeReference(node *ast.Node) bool { 1261 | return isTypeReferenceNode(node) && isIdentifier(node.TypeName) && node.TypeName.EscapedText == "const" && node.TypeArguments == nil 1262 | } 1263 | 1264 | /* OVERLOAD: export function skipPartiallyEmittedExpressions(node: Expression): Expression; */ 1265 | /* OVERLOAD: export function skipPartiallyEmittedExpressions(node: Node): Node; */ 1266 | func skipPartiallyEmittedExpressions(node *ast.Node) *ast.Node { 1267 | return skipOuterExpressions(node, OuterExpressionKindsPartiallyEmittedExpressions) 1268 | } 1269 | 1270 | func isNonNullChain(node *ast.Node) bool { 1271 | return isNonNullExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 1272 | } 1273 | 1274 | func isBreakOrContinueStatement(node *ast.Node) bool { 1275 | return node.Kind == ast.KindBreakStatement || node.Kind == ast.KindContinueStatement 1276 | } 1277 | 1278 | func isNamedExportBindings(node *ast.Node) bool { 1279 | return node.Kind == ast.KindNamespaceExport || node.Kind == ast.KindNamedExports 1280 | } 1281 | 1282 | func isJSDocPropertyLikeTag(node *ast.Node) bool { 1283 | return node.Kind == ast.KindJSDocPropertyTag || node.Kind == ast.KindJSDocParameterTag 1284 | } 1285 | 1286 | // #endregion 1287 | 1288 | // #region 1289 | // Node tests 1290 | // 1291 | // All node tests in the following list should *not* reference parent pointers so that 1292 | // they may be used with transformations. 1293 | 1294 | func isNodeKind(kind SyntaxKind) bool { 1295 | return kind >= ast.KindFirstNode 1296 | } 1297 | 1298 | /** 1299 | * True if kind is of some token syntax kind. 1300 | * For example, this is true for an IfKeyword but not for an IfStatement. 1301 | * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. 1302 | */ 1303 | 1304 | func isTokenKind(kind SyntaxKind) bool { 1305 | return kind >= ast.KindFirstToken && kind <= ast.KindLastToken 1306 | } 1307 | 1308 | /** 1309 | * True if node is of some token syntax kind. 1310 | * For example, this is true for an IfKeyword but not for an IfStatement. 1311 | * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. 1312 | */ 1313 | 1314 | func isToken(n *ast.Node) bool { 1315 | return isTokenKind(n.Kind) 1316 | } 1317 | 1318 | // Node Arrays 1319 | 1320 | func isNodeArray(array []T) bool { 1321 | return hasProperty(array, "pos") && hasProperty(array, "end") 1322 | } 1323 | 1324 | // Literals 1325 | 1326 | func isLiteralKind(kind SyntaxKind) bool { 1327 | return ast.KindFirstLiteralToken <= kind && kind <= ast.KindLastLiteralToken 1328 | } 1329 | 1330 | func isLiteralExpression(node *ast.Node) bool { 1331 | return isLiteralKind(node.Kind) 1332 | } 1333 | 1334 | /** @internal */ 1335 | 1336 | func isLiteralExpressionOfObject(node *ast.Node) bool { 1337 | switch node.Kind { 1338 | case ast.KindObjectLiteralExpression, 1339 | ast.KindArrayLiteralExpression, 1340 | ast.KindRegularExpressionLiteral, 1341 | ast.KindFunctionExpression, 1342 | ast.KindClassExpression: 1343 | return true 1344 | } 1345 | return false 1346 | } 1347 | 1348 | // Pseudo-literals 1349 | 1350 | func isTemplateLiteralKind(kind SyntaxKind) bool { 1351 | return ast.KindFirstTemplateToken <= kind && kind <= ast.KindLastTemplateToken 1352 | } 1353 | 1354 | func isTemplateLiteralToken(node *ast.Node) bool { 1355 | return isTemplateLiteralKind(node.Kind) 1356 | } 1357 | 1358 | func isTemplateMiddleOrTemplateTail(node *ast.Node) bool { 1359 | kind := node.Kind 1360 | return kind == ast.KindTemplateMiddle || kind == ast.KindTemplateTail 1361 | } 1362 | 1363 | func isImportOrExportSpecifier(node *ast.Node) bool { 1364 | return isImportSpecifier(node) || isExportSpecifier(node) 1365 | } 1366 | 1367 | func isTypeOnlyImportDeclaration(node *ast.Node) bool { 1368 | switch node.Kind { 1369 | case ast.KindImportSpecifier: 1370 | return node.AsImportSpecifier().IsTypeOnly || node.AsImportSpecifier().Parent.Parent.IsTypeOnly 1371 | case ast.KindNamespaceImport: 1372 | return node.AsNamespaceImport().Parent.IsTypeOnly 1373 | case ast.KindImportClause, 1374 | ast.KindImportEqualsDeclaration: 1375 | return (node /* as ImportClause | ImportEqualsDeclaration */).IsTypeOnly 1376 | } 1377 | return false 1378 | } 1379 | 1380 | func isTypeOnlyExportDeclaration(node *ast.Node) bool { 1381 | switch node.Kind { 1382 | case ast.KindExportSpecifier: 1383 | return node.AsExportSpecifier().IsTypeOnly || node.AsExportSpecifier().Parent.Parent.IsTypeOnly 1384 | case ast.KindExportDeclaration: 1385 | return node.AsExportDeclaration().IsTypeOnly && node.AsExportDeclaration().ModuleSpecifier != nil && node.AsExportDeclaration().ExportClause == nil 1386 | case ast.KindNamespaceExport: 1387 | return node.AsNamespaceExport().Parent.IsTypeOnly 1388 | } 1389 | return false 1390 | } 1391 | 1392 | func isTypeOnlyImportOrExportDeclaration(node *ast.Node) bool { 1393 | return isTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node) 1394 | } 1395 | 1396 | func isStringTextContainingNode(node *ast.Node) bool { 1397 | return node.Kind == ast.KindStringLiteral || isTemplateLiteralKind(node.Kind) 1398 | } 1399 | 1400 | func isImportAttributeName(node *ast.Node) bool { 1401 | return isStringLiteral(node) || isIdentifier(node) 1402 | } 1403 | 1404 | // Identifiers 1405 | 1406 | func isGeneratedIdentifier(node *ast.Node) bool { 1407 | return isIdentifier(node) && node.EmitNode. /* ? */ autoGenerate != nil 1408 | } 1409 | 1410 | /** @internal */ 1411 | 1412 | func isGeneratedPrivateIdentifier(node *ast.Node) bool { 1413 | return isPrivateIdentifier(node) && node.EmitNode. /* ? */ autoGenerate != nil 1414 | } 1415 | 1416 | /** @internal */ 1417 | 1418 | func isFileLevelReservedGeneratedIdentifier(node GeneratedIdentifier) bool { 1419 | flags := node.EmitNode.autoGenerate.flags 1420 | return flags&GeneratedIdentifierFlagsFileLevel != 0 && flags&GeneratedIdentifierFlagsOptimistic != 0 && flags&GeneratedIdentifierFlagsReservedInNestedScopes != 0 1421 | } 1422 | 1423 | // Private Identifiers 1424 | 1425 | func isPrivateIdentifierClassElementDeclaration(node *ast.Node) bool { 1426 | return (isPropertyDeclaration(node) || isMethodOrAccessor(node)) && isPrivateIdentifier(node.Name) 1427 | } 1428 | 1429 | /** @internal */ 1430 | 1431 | func isPrivateIdentifierPropertyAccessExpression(node *ast.Node) bool { 1432 | return isPropertyAccessExpression(node) && isPrivateIdentifier(node.Name) 1433 | } 1434 | 1435 | // Keywords 1436 | 1437 | func isModifierKind(token SyntaxKind) bool { 1438 | switch token { 1439 | case ast.KindAbstractKeyword, 1440 | ast.KindAccessorKeyword, 1441 | ast.KindAsyncKeyword, 1442 | ast.KindConstKeyword, 1443 | ast.KindDeclareKeyword, 1444 | ast.KindDefaultKeyword, 1445 | ast.KindExportKeyword, 1446 | ast.KindInKeyword, 1447 | ast.KindPublicKeyword, 1448 | ast.KindPrivateKeyword, 1449 | ast.KindProtectedKeyword, 1450 | ast.KindReadonlyKeyword, 1451 | ast.KindStaticKeyword, 1452 | ast.KindOutKeyword, 1453 | ast.KindOverrideKeyword: 1454 | return true 1455 | } 1456 | return false 1457 | } 1458 | 1459 | /** @internal */ 1460 | 1461 | func isParameterPropertyModifier(kind SyntaxKind) bool { 1462 | return modifierToFlag(kind)&ast.ModifierFlagsParameterPropertyModifier != 0 1463 | } 1464 | 1465 | /** @internal */ 1466 | 1467 | func isClassMemberModifier(idToken SyntaxKind) bool { 1468 | return isParameterPropertyModifier(idToken) || idToken == ast.KindStaticKeyword || idToken == ast.KindOverrideKeyword || idToken == ast.KindAccessorKeyword 1469 | } 1470 | 1471 | func isModifier(node *ast.Node) bool { 1472 | return isModifierKind(node.Kind) 1473 | } 1474 | 1475 | func isEntityName(node *ast.Node) bool { 1476 | kind := node.Kind 1477 | return kind == ast.KindQualifiedName || kind == ast.KindIdentifier 1478 | } 1479 | 1480 | func isPropertyName(node *ast.Node) bool { 1481 | kind := node.Kind 1482 | return kind == ast.KindIdentifier || kind == ast.KindPrivateIdentifier || kind == ast.KindStringLiteral || kind == ast.KindNumericLiteral || kind == ast.KindComputedPropertyName 1483 | } 1484 | 1485 | func isBindingName(node *ast.Node) bool { 1486 | kind := node.Kind 1487 | return kind == ast.KindIdentifier || kind == ast.KindObjectBindingPattern || kind == ast.KindArrayBindingPattern 1488 | } 1489 | 1490 | // Functions 1491 | 1492 | func isFunctionLike(node *ast.Node) bool { 1493 | return node != nil && isFunctionLikeKind(node.Kind) 1494 | } 1495 | 1496 | /** @internal */ 1497 | 1498 | func isFunctionLikeOrClassStaticBlockDeclaration(node *ast.Node) bool { 1499 | return node != nil && (isFunctionLikeKind(node.Kind) || isClassStaticBlockDeclaration(node)) 1500 | } 1501 | 1502 | /** @internal */ 1503 | 1504 | func isFunctionLikeDeclaration(node *ast.Node) bool { 1505 | return node && isFunctionLikeDeclarationKind(node.Kind) 1506 | } 1507 | 1508 | /** @internal */ 1509 | 1510 | func isBooleanLiteral(node *ast.Node) bool { 1511 | return node.Kind == ast.KindTrueKeyword || node.Kind == ast.KindFalseKeyword 1512 | } 1513 | 1514 | func isFunctionLikeDeclarationKind(kind SyntaxKind) bool { 1515 | switch kind { 1516 | case ast.KindFunctionDeclaration, 1517 | ast.KindMethodDeclaration, 1518 | ast.KindConstructor, 1519 | ast.KindGetAccessor, 1520 | ast.KindSetAccessor, 1521 | ast.KindFunctionExpression, 1522 | ast.KindArrowFunction: 1523 | return true 1524 | default: 1525 | return false 1526 | } 1527 | } 1528 | 1529 | /** @internal */ 1530 | 1531 | func isFunctionLikeKind(kind SyntaxKind) bool { 1532 | switch kind { 1533 | case ast.KindMethodSignature, 1534 | ast.KindCallSignature, 1535 | ast.KindJSDocSignature, 1536 | ast.KindConstructSignature, 1537 | ast.KindIndexSignature, 1538 | ast.KindFunctionType, 1539 | ast.KindJSDocFunctionType, 1540 | ast.KindConstructorType: 1541 | return true 1542 | default: 1543 | return isFunctionLikeDeclarationKind(kind) 1544 | } 1545 | } 1546 | 1547 | /** @internal */ 1548 | 1549 | func isFunctionOrModuleBlock(node *ast.Node) bool { 1550 | return isSourceFile(node) || isModuleBlock(node) || isBlock(node) && isFunctionLike(node.Parent) 1551 | } 1552 | 1553 | // Classes 1554 | func isClassElement(node *ast.Node) bool { 1555 | kind := node.Kind 1556 | return kind == ast.KindConstructor || kind == ast.KindPropertyDeclaration || kind == ast.KindMethodDeclaration || kind == ast.KindGetAccessor || kind == ast.KindSetAccessor || kind == ast.KindIndexSignature || kind == ast.KindClassStaticBlockDeclaration || kind == ast.KindSemicolonClassElement 1557 | } 1558 | 1559 | func isClassLike(node *ast.Node) bool { 1560 | return node && (node.Kind == ast.KindClassDeclaration || node.Kind == ast.KindClassExpression) 1561 | } 1562 | 1563 | func isAccessor(node *ast.Node) bool { 1564 | return node && (node.Kind == ast.KindGetAccessor || node.Kind == ast.KindSetAccessor) 1565 | } 1566 | 1567 | func isAutoAccessorPropertyDeclaration(node *ast.Node) bool { 1568 | return isPropertyDeclaration(node) && hasAccessorModifier(node) 1569 | } 1570 | 1571 | /** @internal */ 1572 | 1573 | func isClassInstanceProperty(node Declaration) bool { 1574 | if isInJSFile(node) && isExpandoPropertyDeclaration(node) { 1575 | return (!isBindableStaticAccessExpression(node) || !isPrototypeAccess(node.Expression)) && !isBindableStaticNameExpression(node, true /*excludeThisKeyword*/) 1576 | } 1577 | return node.Parent && isClassLike(node.Parent) && isPropertyDeclaration(node) && !hasAccessorModifier(node) 1578 | } 1579 | 1580 | /** @internal */ 1581 | 1582 | func isMethodOrAccessor(node *ast.Node) bool { 1583 | switch node.Kind { 1584 | case ast.KindMethodDeclaration, 1585 | ast.KindGetAccessor, 1586 | ast.KindSetAccessor: 1587 | return true 1588 | default: 1589 | return false 1590 | } 1591 | } 1592 | 1593 | // Type members 1594 | 1595 | func isModifierLike(node *ast.Node) bool { 1596 | return isModifier(node) || isDecorator(node) 1597 | } 1598 | 1599 | func isTypeElement(node *ast.Node) bool { 1600 | kind := node.Kind 1601 | return kind == ast.KindConstructSignature || kind == ast.KindCallSignature || kind == ast.KindPropertySignature || kind == ast.KindMethodSignature || kind == ast.KindIndexSignature || kind == ast.KindGetAccessor || kind == ast.KindSetAccessor || kind == ast.KindNotEmittedTypeElement 1602 | } 1603 | 1604 | func isClassOrTypeElement(node *ast.Node) bool { 1605 | return isTypeElement(node) || isClassElement(node) 1606 | } 1607 | 1608 | func isObjectLiteralElementLike(node *ast.Node) bool { 1609 | kind := node.Kind 1610 | return kind == ast.KindPropertyAssignment || kind == ast.KindShorthandPropertyAssignment || kind == ast.KindSpreadAssignment || kind == ast.KindMethodDeclaration || kind == ast.KindGetAccessor || kind == ast.KindSetAccessor 1611 | } 1612 | 1613 | // Type 1614 | 1615 | func isTypeNode(node *ast.Node) bool { 1616 | return isTypeNodeKind(node.Kind) 1617 | } 1618 | 1619 | func isFunctionOrConstructorTypeNode(node *ast.Node) bool { 1620 | switch node.Kind { 1621 | case ast.KindFunctionType, 1622 | ast.KindConstructorType: 1623 | return true 1624 | } 1625 | 1626 | return false 1627 | } 1628 | 1629 | // Binding patterns 1630 | 1631 | func isBindingPattern(node *ast.Node) bool { 1632 | if node != nil { 1633 | kind := node.Kind 1634 | return kind == ast.KindArrayBindingPattern || kind == ast.KindObjectBindingPattern 1635 | } 1636 | 1637 | return false 1638 | } 1639 | 1640 | /** @internal */ 1641 | 1642 | func isAssignmentPattern(node *ast.Node) bool { 1643 | kind := node.Kind 1644 | return kind == ast.KindArrayLiteralExpression || kind == ast.KindObjectLiteralExpression 1645 | } 1646 | 1647 | func isArrayBindingElement(node *ast.Node) bool { 1648 | kind := node.Kind 1649 | return kind == ast.KindBindingElement || kind == ast.KindOmittedExpression 1650 | } 1651 | 1652 | /** 1653 | * Determines whether the BindingOrAssignmentElement is a BindingElement-like declaration 1654 | * 1655 | * @internal 1656 | */ 1657 | 1658 | func isDeclarationBindingElement(bindingElement BindingOrAssignmentElement) bool { 1659 | switch bindingElement.Kind { 1660 | case ast.KindVariableDeclaration, 1661 | ast.KindParameter, 1662 | ast.KindBindingElement: 1663 | return true 1664 | } 1665 | 1666 | return false 1667 | } 1668 | 1669 | /** @internal */ 1670 | 1671 | func isBindingOrAssignmentElement(node *ast.Node) bool { 1672 | return isVariableDeclaration(node) || isParameter(node) || isObjectBindingOrAssignmentElement(node) || isArrayBindingOrAssignmentElement(node) 1673 | } 1674 | 1675 | /** 1676 | * Determines whether a node is a BindingOrAssignmentPattern 1677 | * 1678 | * @internal 1679 | */ 1680 | 1681 | func isBindingOrAssignmentPattern(node BindingOrAssignmentElementTarget) bool { 1682 | return isObjectBindingOrAssignmentPattern(node) || isArrayBindingOrAssignmentPattern(node) 1683 | } 1684 | 1685 | /** 1686 | * Determines whether a node is an ObjectBindingOrAssignmentPattern 1687 | * 1688 | * @internal 1689 | */ 1690 | 1691 | func isObjectBindingOrAssignmentPattern(node BindingOrAssignmentElementTarget) bool { 1692 | switch node.Kind { 1693 | case ast.KindObjectBindingPattern, 1694 | ast.KindObjectLiteralExpression: 1695 | return true 1696 | } 1697 | 1698 | return false 1699 | } 1700 | 1701 | /** @internal */ 1702 | 1703 | func isObjectBindingOrAssignmentElement(node *ast.Node) bool { 1704 | switch node.Kind { 1705 | case ast.KindBindingElement, 1706 | ast.KindPropertyAssignment, 1707 | ast.KindShorthandPropertyAssignment, 1708 | ast.KindSpreadAssignment: 1709 | return true 1710 | } 1711 | return false 1712 | } 1713 | 1714 | /** 1715 | * Determines whether a node is an ArrayBindingOrAssignmentPattern 1716 | * 1717 | * @internal 1718 | */ 1719 | 1720 | func isArrayBindingOrAssignmentPattern(node BindingOrAssignmentElementTarget) bool { 1721 | switch node.Kind { 1722 | case ast.KindArrayBindingPattern, 1723 | ast.KindArrayLiteralExpression: 1724 | return true 1725 | } 1726 | 1727 | return false 1728 | } 1729 | 1730 | /** @internal */ 1731 | 1732 | func isArrayBindingOrAssignmentElement(node *ast.Node) bool { 1733 | switch node.Kind { 1734 | case ast.KindBindingElement, 1735 | ast.KindOmittedExpression, 1736 | ast.KindSpreadElement, 1737 | ast.KindArrayLiteralExpression, 1738 | ast.KindObjectLiteralExpression, 1739 | ast.KindIdentifier, 1740 | ast.KindPropertyAccessExpression, 1741 | ast.KindElementAccessExpression: 1742 | return true 1743 | } 1744 | return isAssignmentExpression(node, true /*excludeCompoundAssignment*/) 1745 | // AssignmentElement 1746 | } 1747 | 1748 | /** @internal */ 1749 | 1750 | func isPropertyAccessOrQualifiedNameOrImportTypeNode(node *ast.Node) bool { 1751 | kind := node.Kind 1752 | return kind == ast.KindPropertyAccessExpression || kind == ast.KindQualifiedName || kind == ast.KindImportType 1753 | } 1754 | 1755 | // Expression 1756 | 1757 | func isPropertyAccessOrQualifiedName(node *ast.Node) bool { 1758 | kind := node.Kind 1759 | return kind == ast.KindPropertyAccessExpression || kind == ast.KindQualifiedName 1760 | } 1761 | 1762 | /** @internal */ 1763 | 1764 | func isCallLikeOrFunctionLikeExpression(node *ast.Node) bool { 1765 | return isCallLikeExpression(node) || isFunctionExpressionOrArrowFunction(node) 1766 | } 1767 | 1768 | func isCallLikeExpression(node *ast.Node) bool { 1769 | switch node.Kind { 1770 | case ast.KindJsxOpeningElement, 1771 | ast.KindJsxSelfClosingElement, 1772 | ast.KindCallExpression, 1773 | ast.KindNewExpression, 1774 | ast.KindTaggedTemplateExpression, 1775 | ast.KindDecorator: 1776 | return true 1777 | default: 1778 | return false 1779 | } 1780 | } 1781 | 1782 | func isCallOrNewExpression(node *ast.Node) bool { 1783 | return node.Kind == ast.KindCallExpression || node.Kind == ast.KindNewExpression 1784 | } 1785 | 1786 | func isTemplateLiteral(node *ast.Node) bool { 1787 | kind := node.Kind 1788 | return kind == ast.KindTemplateExpression || kind == ast.KindNoSubstitutionTemplateLiteral 1789 | } 1790 | 1791 | func isLeftHandSideExpression(node *ast.Node) bool { 1792 | return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).Kind) 1793 | } 1794 | 1795 | func isLeftHandSideExpressionKind(kind SyntaxKind) bool { 1796 | switch kind { 1797 | case ast.KindPropertyAccessExpression, 1798 | ast.KindElementAccessExpression, 1799 | ast.KindNewExpression, 1800 | ast.KindCallExpression, 1801 | ast.KindJsxElement, 1802 | ast.KindJsxSelfClosingElement, 1803 | ast.KindJsxFragment, 1804 | ast.KindTaggedTemplateExpression, 1805 | ast.KindArrayLiteralExpression, 1806 | ast.KindParenthesizedExpression, 1807 | ast.KindObjectLiteralExpression, 1808 | ast.KindClassExpression, 1809 | ast.KindFunctionExpression, 1810 | ast.KindIdentifier, 1811 | ast.KindPrivateIdentifier, 1812 | ast.KindRegularExpressionLiteral, 1813 | ast.KindNumericLiteral, 1814 | ast.KindBigIntLiteral, 1815 | ast.KindStringLiteral, 1816 | ast.KindNoSubstitutionTemplateLiteral, 1817 | ast.KindTemplateExpression, 1818 | ast.KindFalseKeyword, 1819 | ast.KindNullKeyword, 1820 | ast.KindThisKeyword, 1821 | ast.KindTrueKeyword, 1822 | ast.KindSuperKeyword, 1823 | ast.KindNonNullExpression, 1824 | ast.KindExpressionWithTypeArguments, 1825 | ast.KindMetaProperty, 1826 | ast.KindImportKeyword, 1827 | ast.KindMissingDeclaration: 1828 | return true 1829 | default: 1830 | return false 1831 | } 1832 | } 1833 | 1834 | /** @internal */ 1835 | 1836 | func isUnaryExpression(node *ast.Node) bool { 1837 | return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).Kind) 1838 | } 1839 | 1840 | func isUnaryExpressionKind(kind SyntaxKind) bool { 1841 | switch kind { 1842 | case ast.KindPrefixUnaryExpression, 1843 | ast.KindPostfixUnaryExpression, 1844 | ast.KindDeleteExpression, 1845 | ast.KindTypeOfExpression, 1846 | ast.KindVoidExpression, 1847 | ast.KindAwaitExpression, 1848 | ast.KindTypeAssertionExpression: 1849 | return true 1850 | default: 1851 | return isLeftHandSideExpressionKind(kind) 1852 | } 1853 | } 1854 | 1855 | /** @internal */ 1856 | 1857 | func isUnaryExpressionWithWrite(expr *ast.Node) bool { 1858 | switch expr.Kind { 1859 | case ast.KindPostfixUnaryExpression: 1860 | return true 1861 | case ast.KindPrefixUnaryExpression: 1862 | return expr.AsPrefixUnaryExpression().Operator == ast.KindPlusPlusToken || expr.AsPrefixUnaryExpression().Operator == ast.KindMinusMinusToken 1863 | default: 1864 | return false 1865 | } 1866 | } 1867 | 1868 | func isLiteralTypeLiteral(node *ast.Node) bool { 1869 | switch node.Kind { 1870 | case ast.KindNullKeyword, 1871 | ast.KindTrueKeyword, 1872 | ast.KindFalseKeyword, 1873 | ast.KindPrefixUnaryExpression: 1874 | return true 1875 | default: 1876 | return isLiteralExpression(node) 1877 | } 1878 | } 1879 | 1880 | /** 1881 | * Determines whether a node is an expression based only on its kind. 1882 | */ 1883 | 1884 | func isExpression(node *ast.Node) bool { 1885 | return isExpressionKind(skipPartiallyEmittedExpressions(node).Kind) 1886 | } 1887 | 1888 | func isExpressionKind(kind SyntaxKind) bool { 1889 | switch kind { 1890 | case ast.KindConditionalExpression, 1891 | ast.KindYieldExpression, 1892 | ast.KindArrowFunction, 1893 | ast.KindBinaryExpression, 1894 | ast.KindSpreadElement, 1895 | ast.KindAsExpression, 1896 | ast.KindOmittedExpression, 1897 | ast.KindCommaListExpression, 1898 | ast.KindPartiallyEmittedExpression, 1899 | ast.KindSatisfiesExpression: 1900 | return true 1901 | default: 1902 | return isUnaryExpressionKind(kind) 1903 | } 1904 | } 1905 | 1906 | func isAssertionExpression(node *ast.Node) bool { 1907 | kind := node.Kind 1908 | return kind == ast.KindTypeAssertionExpression || kind == ast.KindAsExpression 1909 | } 1910 | 1911 | // Statement 1912 | 1913 | /* OVERLOAD: export function isIterationStatement(node: Node, lookInLabeledStatements: false): node is IterationStatement; */ 1914 | /* OVERLOAD: export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement | LabeledStatement; */ 1915 | func isIterationStatement(node *ast.Node, lookInLabeledStatements bool) bool { 1916 | switch node.Kind { 1917 | case ast.KindForStatement, 1918 | ast.KindForInStatement, 1919 | ast.KindForOfStatement, 1920 | ast.KindDoStatement, 1921 | ast.KindWhileStatement: 1922 | return true 1923 | case ast.KindLabeledStatement: 1924 | return lookInLabeledStatements && isIterationStatement(node.AsLabeledStatement().Statement, lookInLabeledStatements) 1925 | } 1926 | 1927 | return false 1928 | } 1929 | 1930 | func isScopeMarker(node *ast.Node) bool { 1931 | return isExportAssignment(node) || isExportDeclaration(node) 1932 | } 1933 | 1934 | /** @internal */ 1935 | 1936 | func hasScopeMarker(statements []Statement) bool { 1937 | return core.Some(statements, isScopeMarker) 1938 | } 1939 | 1940 | /** @internal */ 1941 | 1942 | func needsScopeMarker(result Statement) bool { 1943 | return !isAnyImportOrReExport(result) && !isExportAssignment(result) && !hasSyntacticModifier(result, ast.ModifierFlagsExport) && !isAmbientModule(result) 1944 | } 1945 | 1946 | /** @internal */ 1947 | 1948 | func isExternalModuleIndicator(result Statement) bool { 1949 | // Exported top-level member indicates moduleness 1950 | return isAnyImportOrReExport(result) || isExportAssignment(result) || hasSyntacticModifier(result, ast.ModifierFlagsExport) 1951 | } 1952 | 1953 | /** @internal */ 1954 | 1955 | func isForInOrOfStatement(node *ast.Node) bool { 1956 | return node.Kind == ast.KindForInStatement || node.Kind == ast.KindForOfStatement 1957 | } 1958 | 1959 | // Element 1960 | 1961 | func isConciseBody(node *ast.Node) bool { 1962 | return isBlock(node) || isExpression(node) 1963 | } 1964 | 1965 | /** @internal */ 1966 | 1967 | func isFunctionBody(node *ast.Node) bool { 1968 | return isBlock(node) 1969 | } 1970 | 1971 | func isForInitializer(node *ast.Node) bool { 1972 | return isVariableDeclarationList(node) || isExpression(node) 1973 | } 1974 | 1975 | func isModuleBody(node *ast.Node) bool { 1976 | kind := node.Kind 1977 | return kind == ast.KindModuleBlock || kind == ast.KindModuleDeclaration || kind == ast.KindIdentifier 1978 | } 1979 | 1980 | /** @internal @knipignore */ 1981 | 1982 | func isNamespaceBody(node *ast.Node) bool { 1983 | kind := node.Kind 1984 | return kind == ast.KindModuleBlock || kind == ast.KindModuleDeclaration 1985 | } 1986 | 1987 | /** @internal @knipignore */ 1988 | 1989 | func isJSDocNamespaceBody(node *ast.Node) bool { 1990 | kind := node.Kind 1991 | return kind == ast.KindIdentifier || kind == ast.KindModuleDeclaration 1992 | } 1993 | 1994 | func isNamedImportBindings(node *ast.Node) bool { 1995 | kind := node.Kind 1996 | return kind == ast.KindNamedImports || kind == ast.KindNamespaceImport 1997 | } 1998 | 1999 | /** @internal */ 2000 | 2001 | func isModuleOrEnumDeclaration(node *ast.Node) bool { 2002 | return node.Kind == ast.KindModuleDeclaration || node.Kind == ast.KindEnumDeclaration 2003 | } 2004 | 2005 | /** @internal */ 2006 | 2007 | func canHaveSymbol(node *ast.Node) bool { 2008 | // NOTE: This should cover all possible declarations except MissingDeclaration and SemicolonClassElement 2009 | // since they aren't actually declarations and can't have a symbol. 2010 | switch node.Kind { 2011 | case ast.KindArrowFunction, 2012 | ast.KindBinaryExpression, 2013 | ast.KindBindingElement, 2014 | ast.KindCallExpression, 2015 | ast.KindCallSignature, 2016 | ast.KindClassDeclaration, 2017 | ast.KindClassExpression, 2018 | ast.KindClassStaticBlockDeclaration, 2019 | ast.KindConstructor, 2020 | ast.KindConstructorType, 2021 | ast.KindConstructSignature, 2022 | ast.KindElementAccessExpression, 2023 | ast.KindEnumDeclaration, 2024 | ast.KindEnumMember, 2025 | ast.KindExportAssignment, 2026 | ast.KindExportDeclaration, 2027 | ast.KindExportSpecifier, 2028 | ast.KindFunctionDeclaration, 2029 | ast.KindFunctionExpression, 2030 | ast.KindFunctionType, 2031 | ast.KindGetAccessor, 2032 | ast.KindIdentifier, 2033 | ast.KindImportClause, 2034 | ast.KindImportEqualsDeclaration, 2035 | ast.KindImportSpecifier, 2036 | ast.KindIndexSignature, 2037 | ast.KindInterfaceDeclaration, 2038 | ast.KindJSDocCallbackTag, 2039 | ast.KindJSDocEnumTag, 2040 | ast.KindJSDocFunctionType, 2041 | ast.KindJSDocParameterTag, 2042 | ast.KindJSDocPropertyTag, 2043 | ast.KindJSDocSignature, 2044 | ast.KindJSDocTypedefTag, 2045 | ast.KindJSDocTypeLiteral, 2046 | ast.KindJsxAttribute, 2047 | ast.KindJsxAttributes, 2048 | ast.KindJsxSpreadAttribute, 2049 | ast.KindMappedType, 2050 | ast.KindMethodDeclaration, 2051 | ast.KindMethodSignature, 2052 | ast.KindModuleDeclaration, 2053 | ast.KindNamedTupleMember, 2054 | ast.KindNamespaceExport, 2055 | ast.KindNamespaceExportDeclaration, 2056 | ast.KindNamespaceImport, 2057 | ast.KindNewExpression, 2058 | ast.KindNoSubstitutionTemplateLiteral, 2059 | ast.KindNumericLiteral, 2060 | ast.KindObjectLiteralExpression, 2061 | ast.KindParameter, 2062 | ast.KindPropertyAccessExpression, 2063 | ast.KindPropertyAssignment, 2064 | ast.KindPropertyDeclaration, 2065 | ast.KindPropertySignature, 2066 | ast.KindSetAccessor, 2067 | ast.KindShorthandPropertyAssignment, 2068 | ast.KindSourceFile, 2069 | ast.KindSpreadAssignment, 2070 | ast.KindStringLiteral, 2071 | ast.KindTypeAliasDeclaration, 2072 | ast.KindTypeLiteral, 2073 | ast.KindTypeParameter, 2074 | ast.KindVariableDeclaration: 2075 | return true 2076 | default: 2077 | return false 2078 | } 2079 | } 2080 | 2081 | /** @internal */ 2082 | 2083 | func canHaveLocals(node *ast.Node) bool { 2084 | switch node.Kind { 2085 | case ast.KindArrowFunction, 2086 | ast.KindBlock, 2087 | ast.KindCallSignature, 2088 | ast.KindCaseBlock, 2089 | ast.KindCatchClause, 2090 | ast.KindClassStaticBlockDeclaration, 2091 | ast.KindConditionalType, 2092 | ast.KindConstructor, 2093 | ast.KindConstructorType, 2094 | ast.KindConstructSignature, 2095 | ast.KindForStatement, 2096 | ast.KindForInStatement, 2097 | ast.KindForOfStatement, 2098 | ast.KindFunctionDeclaration, 2099 | ast.KindFunctionExpression, 2100 | ast.KindFunctionType, 2101 | ast.KindGetAccessor, 2102 | ast.KindIndexSignature, 2103 | ast.KindJSDocCallbackTag, 2104 | ast.KindJSDocEnumTag, 2105 | ast.KindJSDocFunctionType, 2106 | ast.KindJSDocSignature, 2107 | ast.KindJSDocTypedefTag, 2108 | ast.KindMappedType, 2109 | ast.KindMethodDeclaration, 2110 | ast.KindMethodSignature, 2111 | ast.KindModuleDeclaration, 2112 | ast.KindSetAccessor, 2113 | ast.KindSourceFile, 2114 | ast.KindTypeAliasDeclaration: 2115 | return true 2116 | default: 2117 | return false 2118 | } 2119 | } 2120 | 2121 | func isDeclarationKind(kind SyntaxKind) bool { 2122 | return kind == ast.KindArrowFunction || kind == ast.KindBindingElement || kind == ast.KindClassDeclaration || kind == ast.KindClassExpression || kind == ast.KindClassStaticBlockDeclaration || kind == ast.KindConstructor || kind == ast.KindEnumDeclaration || kind == ast.KindEnumMember || kind == ast.KindExportSpecifier || kind == ast.KindFunctionDeclaration || kind == ast.KindFunctionExpression || kind == ast.KindGetAccessor || kind == ast.KindImportClause || kind == ast.KindImportEqualsDeclaration || kind == ast.KindImportSpecifier || kind == ast.KindInterfaceDeclaration || kind == ast.KindJsxAttribute || kind == ast.KindMethodDeclaration || kind == ast.KindMethodSignature || kind == ast.KindModuleDeclaration || kind == ast.KindNamespaceExportDeclaration || kind == ast.KindNamespaceImport || kind == ast.KindNamespaceExport || kind == ast.KindParameter || kind == ast.KindPropertyAssignment || kind == ast.KindPropertyDeclaration || kind == ast.KindPropertySignature || kind == ast.KindSetAccessor || kind == ast.KindShorthandPropertyAssignment || kind == ast.KindTypeAliasDeclaration || kind == ast.KindTypeParameter || kind == ast.KindVariableDeclaration || kind == ast.KindJSDocTypedefTag || kind == ast.KindJSDocCallbackTag || kind == ast.KindJSDocPropertyTag || kind == ast.KindNamedTupleMember 2123 | } 2124 | 2125 | func isDeclarationStatementKind(kind SyntaxKind) bool { 2126 | return kind == ast.KindFunctionDeclaration || kind == ast.KindMissingDeclaration || kind == ast.KindClassDeclaration || kind == ast.KindInterfaceDeclaration || kind == ast.KindTypeAliasDeclaration || kind == ast.KindEnumDeclaration || kind == ast.KindModuleDeclaration || kind == ast.KindImportDeclaration || kind == ast.KindImportEqualsDeclaration || kind == ast.KindExportDeclaration || kind == ast.KindExportAssignment || kind == ast.KindNamespaceExportDeclaration 2127 | } 2128 | 2129 | func isStatementKindButNotDeclarationKind(kind SyntaxKind) bool { 2130 | return kind == ast.KindBreakStatement || kind == ast.KindContinueStatement || kind == ast.KindDebuggerStatement || kind == ast.KindDoStatement || kind == ast.KindExpressionStatement || kind == ast.KindEmptyStatement || kind == ast.KindForInStatement || kind == ast.KindForOfStatement || kind == ast.KindForStatement || kind == ast.KindIfStatement || kind == ast.KindLabeledStatement || kind == ast.KindReturnStatement || kind == ast.KindSwitchStatement || kind == ast.KindThrowStatement || kind == ast.KindTryStatement || kind == ast.KindVariableStatement || kind == ast.KindWhileStatement || kind == ast.KindWithStatement || kind == ast.KindNotEmittedStatement 2131 | } 2132 | 2133 | /** @internal */ 2134 | 2135 | func isDeclaration(node *ast.Node) bool { 2136 | if node.Kind == ast.KindTypeParameter { 2137 | return (node.Parent && node.Parent.Kind != ast.KindJSDocTemplateTag) || isInJSFile(node) 2138 | } 2139 | 2140 | return isDeclarationKind(node.Kind) 2141 | } 2142 | 2143 | func isDeclarationStatement(node *ast.Node) bool { 2144 | return isDeclarationStatementKind(node.Kind) 2145 | } 2146 | 2147 | /** 2148 | * Determines whether the node is a statement that is not also a declaration 2149 | * 2150 | * @internal 2151 | */ 2152 | 2153 | func isStatementButNotDeclaration(node *ast.Node) bool { 2154 | return isStatementKindButNotDeclarationKind(node.Kind) 2155 | } 2156 | 2157 | func isStatement(node *ast.Node) bool { 2158 | kind := node.Kind 2159 | return isStatementKindButNotDeclarationKind(kind) || isDeclarationStatementKind(kind) || isBlockStatement(node) 2160 | } 2161 | 2162 | func isBlockStatement(node *ast.Node) bool { 2163 | if node.Kind != ast.KindBlock { 2164 | return false 2165 | } 2166 | if node.Parent != nil { 2167 | if node.Parent.Kind == ast.KindTryStatement || node.Parent.Kind == ast.KindCatchClause { 2168 | return false 2169 | } 2170 | } 2171 | return !isFunctionBlock(node) 2172 | } 2173 | 2174 | // TODO(jakebailey): should we be exporting this function and not isStatement? 2175 | 2176 | func isStatementOrBlock(node *ast.Node) bool { 2177 | kind := node.Kind 2178 | return isStatementKindButNotDeclarationKind(kind) || isDeclarationStatementKind(kind) || kind == ast.KindBlock 2179 | } 2180 | 2181 | // Module references 2182 | 2183 | func isModuleReference(node *ast.Node) bool { 2184 | kind := node.Kind 2185 | return kind == ast.KindExternalModuleReference || kind == ast.KindQualifiedName || kind == ast.KindIdentifier 2186 | } 2187 | 2188 | // JSX 2189 | 2190 | func isJsxTagNameExpression(node *ast.Node) bool { 2191 | kind := node.Kind 2192 | return kind == ast.KindThisKeyword || kind == ast.KindIdentifier || kind == ast.KindPropertyAccessExpression || kind == ast.KindJsxNamespacedName 2193 | } 2194 | 2195 | func isJsxChild(node *ast.Node) bool { 2196 | kind := node.Kind 2197 | return kind == ast.KindJsxElement || kind == ast.KindJsxExpression || kind == ast.KindJsxSelfClosingElement || kind == ast.KindJsxText || kind == ast.KindJsxFragment 2198 | } 2199 | 2200 | func isJsxAttributeLike(node *ast.Node) bool { 2201 | kind := node.Kind 2202 | return kind == ast.KindJsxAttribute || kind == ast.KindJsxSpreadAttribute 2203 | } 2204 | 2205 | func isStringLiteralOrJsxExpression(node *ast.Node) bool { 2206 | kind := node.Kind 2207 | return kind == ast.KindStringLiteral || kind == ast.KindJsxExpression 2208 | } 2209 | 2210 | func isJsxOpeningLikeElement(node *ast.Node) bool { 2211 | kind := node.Kind 2212 | return kind == ast.KindJsxOpeningElement || kind == ast.KindJsxSelfClosingElement 2213 | } 2214 | 2215 | // Clauses 2216 | 2217 | func isCaseOrDefaultClause(node *ast.Node) bool { 2218 | kind := node.Kind 2219 | return kind == ast.KindCaseClause || kind == ast.KindDefaultClause 2220 | } 2221 | 2222 | // JSDoc 2223 | 2224 | func isJSDocNode(node *ast.Node) bool { 2225 | return node.Kind >= ast.KindFirstJSDocNode && node.Kind <= ast.KindLastJSDocNode 2226 | } 2227 | 2228 | /** True if node is of a kind that may contain comment text. */ 2229 | 2230 | func isJSDocCommentContainingNode(node *ast.Node) bool { 2231 | return node.Kind == ast.KindJSDoc || node.Kind == ast.KindJSDocNamepathType || node.Kind == ast.KindJSDocText || isJSDocLinkLike(node) || isJSDocTag(node) || isJSDocTypeLiteral(node) || isJSDocSignature(node) 2232 | } 2233 | 2234 | // TODO: determine what this does before making it public. 2235 | 2236 | func isJSDocTag(node *ast.Node) bool { 2237 | return node.Kind >= ast.KindFirstJSDocTagNode && node.Kind <= ast.KindLastJSDocTagNode 2238 | } 2239 | 2240 | func isSetAccessor(node *ast.Node) bool { 2241 | return node.Kind == ast.KindSetAccessor 2242 | } 2243 | 2244 | func isGetAccessor(node *ast.Node) bool { 2245 | return node.Kind == ast.KindGetAccessor 2246 | } 2247 | 2248 | /** 2249 | * True if has jsdoc nodes attached to it. 2250 | * 2251 | * @internal 2252 | */ 2253 | // TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times 2254 | 2255 | func hasJSDocNodes(node *ast.Node) bool { 2256 | if !canHaveJSDoc(node) { 2257 | return false 2258 | } 2259 | 2260 | TODO_IDENTIFIER := node.AsJSDocContainer() 2261 | return jsDoc != nil && jsDoc.length > 0 2262 | } 2263 | 2264 | /** 2265 | * True if has type node attached to it. 2266 | * 2267 | * @internal 2268 | */ 2269 | 2270 | func hasType(node *ast.Node) bool { 2271 | return node.AsHasType().Type_ != nil 2272 | } 2273 | 2274 | /** 2275 | * True if has initializer node attached to it. 2276 | * 2277 | * @internal 2278 | */ 2279 | 2280 | func hasInitializer(node *ast.Node) bool { 2281 | return node.AsHasInitializer().Initializer != nil 2282 | } 2283 | 2284 | /** True if has initializer node attached to it. */ 2285 | 2286 | func hasOnlyExpressionInitializer(node *ast.Node) bool { 2287 | switch node.Kind { 2288 | case ast.KindVariableDeclaration, 2289 | ast.KindParameter, 2290 | ast.KindBindingElement, 2291 | ast.KindPropertyDeclaration, 2292 | ast.KindPropertyAssignment, 2293 | ast.KindEnumMember: 2294 | return true 2295 | default: 2296 | return false 2297 | } 2298 | } 2299 | 2300 | func isObjectLiteralElement(node *ast.Node) bool { 2301 | return node.Kind == ast.KindJsxAttribute || node.Kind == ast.KindJsxSpreadAttribute || isObjectLiteralElementLike(node) 2302 | } 2303 | 2304 | /** @internal */ 2305 | 2306 | func isTypeReferenceType(node *ast.Node) bool { 2307 | return node.Kind == ast.KindTypeReference || node.Kind == ast.KindExpressionWithTypeArguments 2308 | } 2309 | 2310 | var MAX_SMI_X86 = 0x3fff_ffff 2311 | 2312 | /** @internal */ 2313 | 2314 | func guessIndentation(lines []string) *number { 2315 | indentation := MAX_SMI_X86 2316 | for _, line := range lines { 2317 | if line.length == 0 { 2318 | continue 2319 | } 2320 | i := 0 2321 | for ; i < line.length && i < indentation; i++ { 2322 | if !isWhiteSpaceLike(line.charCodeAt(i)) { 2323 | break 2324 | } 2325 | } 2326 | if i < indentation { 2327 | indentation = i 2328 | } 2329 | if indentation == 0 { 2330 | return 0 2331 | } 2332 | } 2333 | if indentation == MAX_SMI_X86 { 2334 | return nil 2335 | } else { 2336 | return indentation 2337 | } 2338 | } 2339 | 2340 | func isStringLiteralLike(node Union[*ast.Node, FileReference]) bool { 2341 | return node.AsNode().Kind == ast.KindStringLiteral || node.AsNode().Kind == ast.KindNoSubstitutionTemplateLiteral 2342 | } 2343 | 2344 | func isJSDocLinkLike(node *ast.Node) bool { 2345 | return node.Kind == ast.KindJSDocLink || node.Kind == ast.KindJSDocLinkCode || node.Kind == ast.KindJSDocLinkPlain 2346 | } 2347 | 2348 | func hasRestParameter(s Union[SignatureDeclaration, JSDocSignature]) bool { 2349 | last := lastOrUndefined(s.Parameters) 2350 | return last != nil && isRestParameter(last) 2351 | } 2352 | 2353 | func isRestParameter(node Union[ParameterDeclaration, JSDocParameterTag]) bool { 2354 | var t *TypeNode 2355 | if isJSDocParameterTag(node) { 2356 | t = (node.TypeExpression && node.TypeExpression.Type_) 2357 | } else { 2358 | t = node.Type_ 2359 | } 2360 | return node.AsParameterDeclaration().DotDotDotToken != nil || t && t.Kind == ast.KindJSDocVariadicType 2361 | } 2362 | 2363 | func hasInternalAnnotation(range_ CommentRange, sourceFile SourceFile) bool { 2364 | comment := sourceFile.Text.substring(range_.pos, range_.end) 2365 | return comment.includes("@internal") 2366 | } 2367 | 2368 | func isInternalDeclaration(node *ast.Node, sourceFile SourceFile) bool { 2369 | if sourceFile == nil { 2370 | sourceFile = getSourceFileOfNode(node) 2371 | } 2372 | parseTreeNode := getParseTreeNode(node) 2373 | if parseTreeNode != nil && parseTreeNode.Kind == ast.KindParameter { 2374 | paramIdx := parseTreeNode.Parent.AsSignatureDeclaration().Parameters.indexOf(parseTreeNode.AsParameterDeclaration()) 2375 | var previousSibling *ParameterDeclaration 2376 | if paramIdx > 0 { 2377 | previousSibling = parseTreeNode.Parent.AsSignatureDeclaration().Parameters[paramIdx-1] 2378 | } else { 2379 | previousSibling = nil 2380 | } 2381 | text := sourceFile.Text 2382 | var commentRanges *[]CommentRange 2383 | if previousSibling != nil { 2384 | commentRanges = core.Concatenate(getTrailingCommentRanges(text, skipTrivia(text, previousSibling.End+1, false /*stopAfterLineBreak*/, true /*stopAtComments*/)), getLeadingCommentRanges(text, node.Pos)) 2385 | } else { 2386 | commentRanges = getTrailingCommentRanges(text, skipTrivia(text, node.Pos, false /*stopAfterLineBreak*/, true /*stopAtComments*/)) 2387 | } 2388 | return core.Some(commentRanges) && hasInternalAnnotation(core.LastOrNil(commentRanges), sourceFile) 2389 | } 2390 | leadingCommentRanges := parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, sourceFile) 2391 | return forEach(leadingCommentRanges, func(range_ CommentRange) bool { 2392 | return hasInternalAnnotation(range_, sourceFile) 2393 | }) 2394 | } 2395 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-to-go", 3 | "module": "index.ts", 4 | "type": "module", 5 | "private": true, 6 | "devDependencies": { 7 | "@types/bun": "^1.1.10", 8 | "@types/which": "^3.0.4" 9 | }, 10 | "peerDependencies": { 11 | "typescript": "^5.6.2" 12 | }, 13 | "dependencies": { 14 | "code-block-writer": "^13.0.3", 15 | "dprint": "^0.47.2", 16 | "pretty-ms": "^9.1.0", 17 | "ts-morph": "^24.0.0", 18 | "which": "^5.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /processDiagnosticMessages.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import cp from "child_process"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import which from "which"; 7 | 8 | /** @typedef {{ 9 | category: string; 10 | code: number; 11 | reportsUnnecessary?: {}; 12 | reportsDeprecated?: {}; 13 | isEarly?: boolean; 14 | elidedInCompatabilityPyramid?: boolean; 15 | }} DiagnosticDetails */ 16 | void 0; 17 | 18 | /** @typedef {Map} InputDiagnosticMessageTable */ 19 | 20 | const inputFilePath = "/home/jabaile/work/ts-native-pin/src/compiler/diagnosticMessages.json"; 21 | const outputFilePath = "output/diagnostics/diagnostics_generated.go"; 22 | 23 | async function main() { 24 | const inputStr = await fs.promises.readFile(inputFilePath, { encoding: "utf-8" }); 25 | 26 | /** @type {{ [key: string]: DiagnosticDetails }} */ 27 | const diagnosticMessagesJson = JSON.parse(inputStr); 28 | 29 | /** @type {InputDiagnosticMessageTable} */ 30 | const diagnosticMessages = new Map(); 31 | for (const key in diagnosticMessagesJson) { 32 | if (Object.hasOwnProperty.call(diagnosticMessagesJson, key)) { 33 | diagnosticMessages.set(key, diagnosticMessagesJson[key]); 34 | } 35 | } 36 | 37 | const infoFileOutput = buildInfoFileOutput(diagnosticMessages); 38 | checkForUniqueCodes(diagnosticMessages); 39 | await fs.promises.mkdir(path.dirname(outputFilePath), { recursive: true }); 40 | await fs.promises.writeFile(outputFilePath, infoFileOutput); 41 | 42 | try { 43 | cp.execFileSync(which.sync("gofmt"), ["-w", outputFilePath]); 44 | console.log("All good!"); 45 | } 46 | catch (e) { 47 | console.error(e); 48 | } 49 | } 50 | 51 | /** 52 | * @param {InputDiagnosticMessageTable} diagnosticTable 53 | */ 54 | function checkForUniqueCodes(diagnosticTable) { 55 | /** @type {Record} */ 56 | const allCodes = []; 57 | diagnosticTable.forEach(({ code }) => { 58 | if (allCodes[code]) { 59 | throw new Error(`Diagnostic code ${code} appears more than once.`); 60 | } 61 | allCodes[code] = true; 62 | }); 63 | } 64 | 65 | /** 66 | * @param {InputDiagnosticMessageTable} messageTable 67 | * @returns {string} 68 | */ 69 | function buildInfoFileOutput(messageTable) { 70 | const result = [ 71 | `// GENERATED BY processDiagnosticMessages.mjs; DO NOT EDIT`, 72 | `package diagnostics`, 73 | ``, 74 | `type Category int32`, 75 | "", 76 | `const (`, 77 | `\tCategoryWarning Category = iota`, 78 | `\tCategoryError`, 79 | `\tCategorySuggestion`, 80 | `\tCategoryMessage`, 81 | `)`, 82 | ``, 83 | `type Message struct {`, 84 | `\tcode int32`, 85 | `\tcategory Category`, 86 | `\tkey string`, 87 | `\ttext string`, 88 | `\treportsUnnecessary bool`, 89 | `\telidedInCompatabilityPyramid bool`, 90 | `\treportsDeprecated bool`, 91 | `}`, 92 | "", 93 | `func (m *Message) Code() int32 { return m.code }`, 94 | `func (m *Message) Category() Category { return m.category }`, 95 | `func (m *Message) Key() string { return m.key }`, 96 | `func (m *Message) Message() string { return m.text }`, 97 | `func (m *Message) ReportsUnnecessary() bool { return m.reportsUnnecessary }`, 98 | `func (m *Message) ElidedInCompatabilityPyramid() bool { return m.elidedInCompatabilityPyramid }`, 99 | `func (m *Message) ReportsDeprecated() bool { return m.reportsDeprecated }`, 100 | "", 101 | ]; 102 | 103 | messageTable.forEach( 104 | ({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated }, name) => { 105 | const { propName, key } = convertPropertyName(name, code); 106 | 107 | result.push( 108 | `var ${propName} = &Message{code: ${code}, category: Category${category}, key: "${key}", text: ${ 109 | JSON.stringify(name) 110 | }, reportsUnnecessary: ${!!reportsUnnecessary}, elidedInCompatabilityPyramid: ${!!elidedInCompatabilityPyramid}, reportsDeprecated: ${!!reportsDeprecated}}`, 111 | ); 112 | }, 113 | ); 114 | 115 | return result.join("\n"); 116 | } 117 | 118 | /** 119 | * @param {string} name 120 | * @param {number} code 121 | * @returns {string} 122 | */ 123 | function createKey(name, code) { 124 | return name.slice(0, 100) + "_" + code; 125 | } 126 | 127 | /** 128 | * @param {string} origName 129 | * @param {number} code 130 | */ 131 | function convertPropertyName(origName, code) { 132 | let result = origName.split("").map(char => { 133 | if (char === "*") return "_Asterisk"; 134 | if (char === "/") return "_Slash"; 135 | if (char === ":") return "_Colon"; 136 | return /\w/.test(char) ? char : "_"; 137 | }).join(""); 138 | 139 | // get rid of all multi-underscores 140 | result = result.replace(/_+/g, "_"); 141 | 142 | // remove any leading underscore, unless it is followed by a number. 143 | result = result.replace(/^_(\D)/, "$1"); 144 | 145 | // get rid of all trailing underscores. 146 | result = result.replace(/_$/, ""); 147 | 148 | const key = createKey(result, code); 149 | 150 | if (!/^[A-Z]/.test(result)) { 151 | if (result.startsWith("_")) { 152 | result = "X" + result; 153 | } 154 | else { 155 | result = "X_" + result; 156 | } 157 | } 158 | 159 | return { propName: result, key }; 160 | } 161 | 162 | main(); 163 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | --------------------------------------------------------------------------------