@jpoly1219/context-extractor 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/src/app.d.ts +7 -3
  2. package/dist/src/app.js +304 -101
  3. package/dist/src/ast.d.ts +6 -0
  4. package/dist/src/ast.js +80 -0
  5. package/dist/src/codeql.js +17 -7
  6. package/dist/src/constants.js +17 -7
  7. package/dist/src/core.d.ts +0 -1
  8. package/dist/src/core.js +17 -7
  9. package/dist/src/main.d.ts +8 -2
  10. package/dist/src/main.js +51 -13
  11. package/dist/src/ocaml-driver.d.ts +55 -5
  12. package/dist/src/ocaml-driver.js +295 -190
  13. package/dist/src/ocaml-utils/_build/default/test_parser.bc.cjs +194658 -0
  14. package/dist/src/runner.js +118 -3
  15. package/dist/src/tree-sitter-files/queries/hole-queries/typescript.scm +36 -0
  16. package/dist/src/tree-sitter-files/queries/relevant-headers-queries/typescript-get-toplevel-headers.scm +22 -0
  17. package/dist/src/tree-sitter-files/queries/relevant-types-queries/typescript-extract-identifiers.scm +10 -0
  18. package/dist/src/tree-sitter-files/queries/relevant-types-queries/typescript-find-typedecl-given-typeidentifier.scm +11 -0
  19. package/dist/src/tree-sitter-files/wasms/tree-sitter-ocaml.wasm +0 -0
  20. package/dist/src/tree-sitter-files/wasms/tree-sitter-typescript.wasm +0 -0
  21. package/dist/src/tree-sitter-files/wasms/tree-sitter.wasm +0 -0
  22. package/dist/src/tree-sitter.d.ts +40 -0
  23. package/dist/src/tree-sitter.js +311 -0
  24. package/dist/src/types.d.ts +79 -9
  25. package/dist/src/types.js +1 -6
  26. package/dist/src/typescript-driver.d.ts +81 -13
  27. package/dist/src/typescript-driver.js +1123 -237
  28. package/dist/src/typescript-type-checker.d.ts +6 -2
  29. package/dist/src/typescript-type-checker.js +222 -10
  30. package/dist/src/utils.d.ts +11 -1
  31. package/dist/src/utils.js +87 -10
  32. package/dist/src/vscode-ide.d.ts +29 -0
  33. package/dist/src/vscode-ide.js +161 -0
  34. package/package.json +12 -6
@@ -15,156 +15,344 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.TypeScriptDriver = void 0;
37
+ const ts = __importStar(require("typescript"));
27
38
  const fs = __importStar(require("fs"));
28
39
  const path = __importStar(require("path"));
29
40
  const child_process_1 = require("child_process");
30
41
  const typescript_type_checker_1 = require("./typescript-type-checker");
31
42
  const utils_1 = require("./utils");
43
+ const ast_1 = require("./ast");
44
+ const tree_sitter_1 = require("./tree-sitter");
32
45
  class TypeScriptDriver {
33
- constructor() {
46
+ constructor(ide, sources, projectRoot) {
34
47
  this.typeChecker = new typescript_type_checker_1.TypeScriptTypeChecker();
48
+ this.ide = ide;
49
+ if (ide == "vscode") {
50
+ Promise.resolve().then(() => __importStar(require("./vscode-ide"))).then(module => this.vscodeImport = module);
51
+ }
52
+ // Initialize TypeScript compiler API.
53
+ this.tsCompilerProgram = this.typeChecker.createTsCompilerProgram(sources, projectRoot);
54
+ this.tsCompilerTypeChecker = this.tsCompilerProgram.getTypeChecker();
35
55
  }
36
56
  async init(lspClient, sketchPath) {
37
- const capabilities = {
38
- 'textDocument': {
39
- 'codeAction': { 'dynamicRegistration': true },
40
- 'codeLens': { 'dynamicRegistration': true },
41
- 'colorProvider': { 'dynamicRegistration': true },
42
- 'completion': {
43
- 'completionItem': {
44
- 'commitCharactersSupport': true,
45
- 'documentationFormat': ['markdown', 'plaintext'],
46
- 'snippetSupport': true
57
+ if (lspClient) {
58
+ const capabilities = {
59
+ 'textDocument': {
60
+ 'codeAction': { 'dynamicRegistration': true },
61
+ 'codeLens': { 'dynamicRegistration': true },
62
+ 'colorProvider': { 'dynamicRegistration': true },
63
+ 'completion': {
64
+ 'completionItem': {
65
+ 'commitCharactersSupport': true,
66
+ 'documentationFormat': ['markdown', 'plaintext'],
67
+ 'snippetSupport': true
68
+ },
69
+ 'completionItemKind': {
70
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
71
+ },
72
+ 'contextSupport': true,
73
+ 'dynamicRegistration': true
47
74
  },
48
- 'completionItemKind': {
49
- 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
75
+ 'definition': { 'dynamicRegistration': true },
76
+ 'documentHighlight': { 'dynamicRegistration': true },
77
+ 'documentLink': { 'dynamicRegistration': true },
78
+ 'documentSymbol': {
79
+ 'dynamicRegistration': true,
80
+ 'symbolKind': {
81
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
82
+ }
50
83
  },
51
- 'contextSupport': true,
52
- 'dynamicRegistration': true
53
- },
54
- 'definition': { 'dynamicRegistration': true },
55
- 'documentHighlight': { 'dynamicRegistration': true },
56
- 'documentLink': { 'dynamicRegistration': true },
57
- 'documentSymbol': {
58
- 'dynamicRegistration': true,
59
- 'symbolKind': {
60
- 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
61
- }
62
- },
63
- 'formatting': { 'dynamicRegistration': true },
64
- 'hover': {
65
- 'contentFormat': ['markdown', 'plaintext'],
66
- 'dynamicRegistration': true
84
+ 'formatting': { 'dynamicRegistration': true },
85
+ 'hover': {
86
+ 'contentFormat': ['markdown', 'plaintext'],
87
+ 'dynamicRegistration': true
88
+ },
89
+ 'implementation': { 'dynamicRegistration': true },
90
+ // 'inlayhint': { 'dynamicRegistration': true },
91
+ 'onTypeFormatting': { 'dynamicRegistration': true },
92
+ 'publishDiagnostics': { 'relatedInformation': true },
93
+ 'rangeFormatting': { 'dynamicRegistration': true },
94
+ 'references': { 'dynamicRegistration': true },
95
+ 'rename': { 'dynamicRegistration': true },
96
+ 'signatureHelp': {
97
+ 'dynamicRegistration': true,
98
+ 'signatureInformation': { 'documentationFormat': ['markdown', 'plaintext'] }
99
+ },
100
+ 'synchronization': {
101
+ 'didSave': true,
102
+ 'dynamicRegistration': true,
103
+ 'willSave': true,
104
+ 'willSaveWaitUntil': true
105
+ },
106
+ 'typeDefinition': { 'dynamicRegistration': true, 'linkSupport': true },
107
+ // 'typeHierarchy': { 'dynamicRegistration': true }
67
108
  },
68
- 'implementation': { 'dynamicRegistration': true },
69
- // 'inlayhint': { 'dynamicRegistration': true },
70
- 'onTypeFormatting': { 'dynamicRegistration': true },
71
- 'publishDiagnostics': { 'relatedInformation': true },
72
- 'rangeFormatting': { 'dynamicRegistration': true },
73
- 'references': { 'dynamicRegistration': true },
74
- 'rename': { 'dynamicRegistration': true },
75
- 'signatureHelp': {
76
- 'dynamicRegistration': true,
77
- 'signatureInformation': { 'documentationFormat': ['markdown', 'plaintext'] }
109
+ 'workspace': {
110
+ 'applyEdit': true,
111
+ 'configuration': true,
112
+ 'didChangeConfiguration': { 'dynamicRegistration': true },
113
+ 'didChangeWatchedFiles': { 'dynamicRegistration': true },
114
+ 'executeCommand': { 'dynamicRegistration': true },
115
+ 'symbol': {
116
+ 'dynamicRegistration': true,
117
+ 'symbolKind': {
118
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
119
+ }
120
+ }, 'workspaceEdit': { 'documentChanges': true },
121
+ 'workspaceFolders': true
78
122
  },
79
- 'synchronization': {
80
- 'didSave': true,
81
- 'dynamicRegistration': true,
82
- 'willSave': true,
83
- 'willSaveWaitUntil': true
123
+ 'general': {
124
+ 'positionEncodings': ['utf-8']
84
125
  },
85
- 'typeDefinition': { 'dynamicRegistration': true, 'linkSupport': true },
86
- // 'typeHierarchy': { 'dynamicRegistration': true }
87
- },
88
- 'workspace': {
89
- 'applyEdit': true,
90
- 'configuration': true,
91
- 'didChangeConfiguration': { 'dynamicRegistration': true },
92
- 'didChangeWatchedFiles': { 'dynamicRegistration': true },
93
- 'executeCommand': { 'dynamicRegistration': true },
94
- 'symbol': {
95
- 'dynamicRegistration': true,
96
- 'symbolKind': {
97
- 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
126
+ };
127
+ const rootPath = path.dirname(sketchPath);
128
+ const rootUri = `file://${rootPath}`;
129
+ const workspaceFolders = [{ 'name': 'context-extractor', 'uri': rootUri }];
130
+ await lspClient.initialize({
131
+ processId: process.pid,
132
+ capabilities: capabilities,
133
+ trace: 'off',
134
+ rootUri: null,
135
+ workspaceFolders: workspaceFolders,
136
+ initializationOptions: {
137
+ disableAutomaticTypingAcquisition: true,
138
+ preferences: {
139
+ includeInlayVariableTypeHints: true,
98
140
  }
99
- }, 'workspaceEdit': { 'documentChanges': true },
100
- 'workspaceFolders': true
101
- },
102
- 'general': {
103
- 'positionEncodings': ['utf-8']
104
- },
105
- };
106
- const rootPath = path.dirname(sketchPath);
107
- const rootUri = `file://${rootPath}`;
108
- const workspaceFolders = [{ 'name': 'context-extractor', 'uri': rootUri }];
109
- await lspClient.initialize({
110
- processId: process.pid,
111
- capabilities: capabilities,
112
- trace: 'off',
113
- rootUri: null,
114
- workspaceFolders: workspaceFolders,
115
- initializationOptions: {
116
- preferences: {
117
- includeInlayVariableTypeHints: true
118
141
  }
119
- }
120
- });
142
+ });
143
+ }
144
+ }
145
+ injectHole(sketchFilePath) {
146
+ const sketchDir = path.dirname(sketchFilePath);
147
+ const injectedSketchFilePath = path.join(sketchDir, "injected_sketch.ts");
148
+ const sketchFileContent = fs.readFileSync(sketchFilePath, "utf8");
149
+ const injectedSketchFileContent = `declare function _<T>(): T\n${sketchFileContent}`;
150
+ fs.writeFileSync(injectedSketchFilePath, injectedSketchFileContent);
121
151
  }
122
- async getHoleContext(lspClient, sketchFilePath) {
152
+ async getHoleContext(lspClient, sketchFilePath, logStream) {
153
+ if (logStream) {
154
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting hole context][${new Date().toISOString()}]\n\n`);
155
+ }
123
156
  // For TypeScript programs, we need to inject the hole function before getting its context.
124
157
  // NOTE: this can be abstracted to its own method?
125
158
  const sketchDir = path.dirname(sketchFilePath);
126
159
  const injectedSketchFilePath = path.join(sketchDir, "injected_sketch.ts");
127
160
  const sketchFileContent = fs.readFileSync(sketchFilePath, "utf8");
128
161
  const injectedSketchFileContent = `declare function _<T>(): T\n${sketchFileContent}`;
129
- fs.writeFileSync(injectedSketchFilePath, injectedSketchFileContent);
130
- // Sync client and server by notifying that the client has opened all the files inside the target directory.
131
- fs.readdirSync(sketchDir).map(fileName => {
132
- if (fs.lstatSync(path.join(sketchDir, fileName)).isFile()) {
133
- lspClient.didOpen({
134
- textDocument: {
135
- uri: `file://${sketchDir}/${fileName}`,
136
- languageId: "typescript",
137
- text: fs.readFileSync(`${sketchDir}/${fileName}`).toString("ascii"),
138
- version: 1
139
- }
140
- });
141
- }
142
- });
143
- // Get hole context.
162
+ // fs.writeFileSync(injectedSketchFilePath, injectedSketchFileContent);
163
+ // Get the hole's position.
144
164
  const holePattern = /_\(\)/;
145
165
  const firstPatternIndex = injectedSketchFileContent.search(holePattern);
146
- const linePosition = (injectedSketchFileContent.substring(0, firstPatternIndex).match(/\n/g)).length;
147
- const characterPosition = firstPatternIndex - injectedSketchFileContent.split("\n", linePosition).join("\n").length - 1;
148
- const holeHoverResult = await lspClient.hover({
149
- textDocument: {
150
- uri: injectedSketchFilePath
151
- },
152
- position: {
153
- character: characterPosition,
154
- line: linePosition
155
- }
156
- });
157
- const formattedHoverResult = holeHoverResult.contents.value.split("\n").reduce((acc, curr) => {
158
- if (curr != "" && curr != "```typescript" && curr != "```") {
159
- return acc + curr;
160
- }
161
- else {
162
- return acc;
166
+ const linePosition = (injectedSketchFileContent
167
+ .substring(0, firstPatternIndex)
168
+ .match(/\n/g)).length;
169
+ const characterPosition = firstPatternIndex - (injectedSketchFileContent
170
+ .split("\n", linePosition)
171
+ .join("\n")
172
+ .length) - 1;
173
+ if (lspClient) {
174
+ // Sync client and server by notifying that
175
+ // the client has opened all the files
176
+ // inside the target directory.
177
+ // lspClient.didOpen({
178
+ // textDocument: {
179
+ // uri: injectedSketchFilePath,
180
+ // languageId: "typescript",
181
+ // text: injectedSketchFileContent,
182
+ // version: 1
183
+ // }
184
+ // });
185
+ // fs.readdirSync(sketchDir).map(fileName => {
186
+ // if (fs.lstatSync(path.join(sketchDir, fileName)).isFile()) {
187
+ // lspClient.didOpen({
188
+ // textDocument: {
189
+ // uri: `file://${sketchDir}/${fileName}`,
190
+ // languageId: "typescript",
191
+ // text: fs.readFileSync(`${sketchDir}/${fileName}`).toString("ascii"),
192
+ // version: 1
193
+ // }
194
+ // });
195
+ // }
196
+ // });
197
+ // await (async () => {
198
+ // return new Promise(resolve => setTimeout(resolve, 500))
199
+ // })();
200
+ // const blocker = () => {
201
+ // for (let i = 0; i < 10000; ++i) {
202
+ // console.log(i)
203
+ // }
204
+ // }
205
+ // blocker();
206
+ // console.log(characterPosition, linePosition)
207
+ console.time("hover");
208
+ const holeHoverResult = await lspClient.hover({
209
+ textDocument: {
210
+ uri: injectedSketchFilePath
211
+ },
212
+ position: {
213
+ character: characterPosition,
214
+ line: linePosition
215
+ }
216
+ });
217
+ console.timeEnd("hover");
218
+ const formattedHoverResult = holeHoverResult.contents.value.split("\n").reduce((acc, curr) => {
219
+ if (curr != "" && curr != "```typescript" && curr != "```") {
220
+ return acc + curr;
221
+ }
222
+ else {
223
+ return acc;
224
+ }
225
+ }, "");
226
+ // console.log(formattedHoverResult)
227
+ // function _<(a: Apple, c: Cherry, b: Banana) => Cherry > (): (a: Apple, c: Cherry, b: Banana) => Cherry
228
+ const holeFunctionPattern = /(function _)(\<.+\>)(\(\): )(.+)/;
229
+ const match = formattedHoverResult.match(holeFunctionPattern);
230
+ const functionName = "_()";
231
+ // console.log("aaa")
232
+ const functionTypeSpan = match[4];
233
+ // console.log("bbb")
234
+ // Clean up and inject the true hole function without the generic type signature.
235
+ // NOTE: this can be abstracted to its own method?
236
+ const trueHoleFunction = `declare function _(): ${functionTypeSpan}`;
237
+ const trueInjectedSketchFileContent = `${trueHoleFunction}\n${sketchFileContent}`;
238
+ fs.writeFileSync(injectedSketchFilePath, trueInjectedSketchFileContent);
239
+ console.time("didChange");
240
+ lspClient.didChange({
241
+ textDocument: {
242
+ uri: `file://${injectedSketchFilePath}`,
243
+ version: 2
244
+ },
245
+ contentChanges: [{
246
+ text: trueInjectedSketchFileContent
247
+ }]
248
+ });
249
+ console.timeEnd("didChange");
250
+ console.time("documentSymbol");
251
+ const sketchSymbol = await lspClient.documentSymbol({
252
+ textDocument: {
253
+ uri: `file://${injectedSketchFilePath}`,
254
+ }
255
+ });
256
+ console.timeEnd("documentSymbol");
257
+ return {
258
+ fullHoverResult: formattedHoverResult,
259
+ functionName: functionName,
260
+ functionTypeSpan: functionTypeSpan,
261
+ linePosition: linePosition,
262
+ characterPosition: characterPosition,
263
+ holeTypeDefLinePos: 0,
264
+ holeTypeDefCharPos: "declare function _(): ".length,
265
+ // range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } }
266
+ range: sketchSymbol[0].location.range,
267
+ source: `file://${injectedSketchFilePath}`,
268
+ trueHoleFunction: trueHoleFunction
269
+ };
270
+ }
271
+ else {
272
+ const holeHoverResult = await this.vscodeImport.VsCode.hover({
273
+ filepath: injectedSketchFilePath,
274
+ position: {
275
+ line: linePosition,
276
+ character: characterPosition
277
+ }
278
+ });
279
+ const formattedHoverResult = holeHoverResult.text.split("\n").reduce((acc, curr) => {
280
+ if (curr != "" && curr != "```typescript" && curr != "```") {
281
+ return acc + curr;
282
+ }
283
+ else {
284
+ return acc;
285
+ }
286
+ }, "");
287
+ const holeFunctionPattern = /(function _)(\<.+\>)(\(\): )(.+)/;
288
+ const match = formattedHoverResult.match(holeFunctionPattern);
289
+ const functionName = "_()";
290
+ const functionTypeSpan = match[4];
291
+ // Clean up and inject the true hole function without the generic type signature.
292
+ // NOTE: this can be abstracted to its own method?
293
+ const trueHoleFunction = `declare function _(): ${functionTypeSpan}`;
294
+ const trueInjectedSketchFileContent = `${trueHoleFunction}\n${sketchFileContent}`;
295
+ fs.writeFileSync(injectedSketchFilePath, trueInjectedSketchFileContent);
296
+ const sketchSymbol = await this.vscodeImport.VsCode.getDocumentSymbols({
297
+ filepath: injectedSketchFilePath
298
+ });
299
+ return {
300
+ fullHoverResult: formattedHoverResult,
301
+ functionName: functionName,
302
+ functionTypeSpan: functionTypeSpan,
303
+ linePosition: linePosition,
304
+ characterPosition: characterPosition,
305
+ holeTypeDefLinePos: 0,
306
+ holeTypeDefCharPos: "declare function _(): ".length,
307
+ range: sketchSymbol[0].range,
308
+ source: `file://${injectedSketchFilePath}`,
309
+ trueHoleFunction: trueHoleFunction
310
+ };
311
+ }
312
+ }
313
+ async getHoleContextWithCompilerAPI(sketchFilePath, logStream) {
314
+ if (logStream) {
315
+ // logStream.write("")
316
+ }
317
+ // For TypeScript programs, we need to inject the hole function before getting its context.
318
+ const sketchDir = path.dirname(sketchFilePath);
319
+ const injectedSketchFilePath = path.join(sketchDir, "injected_sketch.ts");
320
+ const sketchFileContent = fs.readFileSync(sketchFilePath, "utf8");
321
+ const injectedSketchFileContent = `declare function _<T>(): T\n${sketchFileContent}`;
322
+ // Get the hole's position.
323
+ const holePattern = /_\(\)/;
324
+ const firstPatternIndex = injectedSketchFileContent.search(holePattern);
325
+ const linePosition = (injectedSketchFileContent
326
+ .substring(0, firstPatternIndex)
327
+ .match(/\n/g)).length;
328
+ const characterPosition = firstPatternIndex - (injectedSketchFileContent
329
+ .split("\n", linePosition)
330
+ .join("\n")
331
+ .length) - 1;
332
+ // const sourceFile = ts.createSourceFile("sample.ts", injectedSketchFileContent, ts.ScriptTarget.Latest, true);
333
+ const sourceFile = this.tsCompilerProgram.getSourceFile(injectedSketchFilePath);
334
+ const position = ts.getPositionOfLineAndCharacter(sourceFile, linePosition, characterPosition);
335
+ function findNode(node) {
336
+ if (position >= node.getStart() && position <= node.getEnd()) {
337
+ // NOTE: this is probably unnecessary if characterPosition accounted for the (), not just _
338
+ if (node.getText() === "_()") {
339
+ return node;
340
+ }
341
+ return ts.forEachChild(node, findNode) || node;
163
342
  }
164
- }, "");
343
+ }
344
+ const targetNode = findNode(sourceFile);
345
+ if (!(targetNode && ts.isCallExpression(targetNode))) {
346
+ console.log("Node not found or not a call expression.");
347
+ throw new Error("Node not found or not a call expression.");
348
+ }
349
+ const type = this.tsCompilerTypeChecker.getTypeAtLocation(targetNode);
350
+ const typeString = this.tsCompilerTypeChecker.typeToString(type);
351
+ const typeStr = `function _<${typeString}>(): ${typeString}`;
352
+ // console.log("TYPE:", typeStr);
165
353
  // function _<(a: Apple, c: Cherry, b: Banana) => Cherry > (): (a: Apple, c: Cherry, b: Banana) => Cherry
166
354
  const holeFunctionPattern = /(function _)(\<.+\>)(\(\): )(.+)/;
167
- const match = formattedHoverResult.match(holeFunctionPattern);
355
+ const match = typeStr.match(holeFunctionPattern);
168
356
  const functionName = "_()";
169
357
  const functionTypeSpan = match[4];
170
358
  // Clean up and inject the true hole function without the generic type signature.
@@ -172,34 +360,93 @@ class TypeScriptDriver {
172
360
  const trueHoleFunction = `declare function _(): ${functionTypeSpan}`;
173
361
  const trueInjectedSketchFileContent = `${trueHoleFunction}\n${sketchFileContent}`;
174
362
  fs.writeFileSync(injectedSketchFilePath, trueInjectedSketchFileContent);
175
- lspClient.didChange({
176
- textDocument: {
177
- uri: `file://${injectedSketchFilePath}`,
178
- version: 2
179
- },
180
- contentChanges: [{
181
- text: trueInjectedSketchFileContent
182
- }]
183
- });
184
- const sketchSymbol = await lspClient.documentSymbol({
185
- textDocument: {
186
- uri: `file://${injectedSketchFilePath}`,
187
- }
188
- });
189
363
  return {
190
- fullHoverResult: formattedHoverResult,
364
+ fullHoverResult: typeStr,
191
365
  functionName: functionName,
192
366
  functionTypeSpan: functionTypeSpan,
193
367
  linePosition: linePosition,
194
368
  characterPosition: characterPosition,
195
369
  holeTypeDefLinePos: 0,
196
370
  holeTypeDefCharPos: "declare function _(): ".length,
197
- // range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } }
198
- range: sketchSymbol[0].location.range,
371
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } },
372
+ // range: (sketchSymbol![0] as SymbolInformation).location.range,
199
373
  source: `file://${injectedSketchFilePath}`,
200
374
  trueHoleFunction: trueHoleFunction
201
375
  };
202
376
  }
377
+ async getHoleContextWithTreesitter(sketchFilePath, cursorPosition, logStream) {
378
+ if (logStream) {
379
+ // logStream.write("")
380
+ }
381
+ // We need to inject the hole @ to trigger an error node.
382
+ const sketchFileContent = fs.readFileSync(sketchFilePath, "utf8");
383
+ const injectedContent = (0, utils_1.insertAtPosition)(sketchFileContent, cursorPosition, "@;");
384
+ // The hole's position is cursorPosition.
385
+ const snip = (0, utils_1.extractSnippet)(injectedContent, cursorPosition, { line: cursorPosition.line, character: cursorPosition.character + 2 });
386
+ // console.log(snip)
387
+ // Use treesitter to parse.
388
+ const ast = await (0, ast_1.getAst)(sketchFilePath, injectedContent);
389
+ if (!ast) {
390
+ throw new Error("failed to get ast");
391
+ }
392
+ const language = (0, tree_sitter_1.getFullLanguageName)(sketchFilePath);
393
+ const query = await (0, tree_sitter_1.getQueryForFile)(sketchFilePath, path.join(__dirname, "tree-sitter-files", "queries", "hole-queries", `${language}.scm`));
394
+ if (!query) {
395
+ throw new Error(`failed to get query for file ${sketchFilePath} and language ${language}`);
396
+ }
397
+ // const matches = query.matches(ast.rootNode);
398
+ // console.log(JSON.stringify(matches))
399
+ // for (const m of matches) {
400
+ // console.log(m)
401
+ // }
402
+ // for (const m of matches) {
403
+ // for (const c of m.captures) {
404
+ // const { name, node } = c;
405
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
406
+ // }
407
+ // }
408
+ const captures = query.captures(ast.rootNode);
409
+ const res = {
410
+ fullHoverResult: "",
411
+ functionName: "",
412
+ functionTypeSpan: "",
413
+ linePosition: 0,
414
+ characterPosition: 0,
415
+ holeTypeDefLinePos: 0,
416
+ holeTypeDefCharPos: "declare function _(): ".length,
417
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } },
418
+ // range: (sketchSymbol![0] as SymbolInformation).location.range,
419
+ source: `file://${sketchFilePath}`,
420
+ trueHoleFunction: ""
421
+ };
422
+ for (const c of captures) {
423
+ const { name, node } = c;
424
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
425
+ switch (name) {
426
+ case "function.decl": {
427
+ res.fullHoverResult = node.text;
428
+ }
429
+ case "function.name": {
430
+ res.functionName = node.text;
431
+ }
432
+ case "function.type": {
433
+ res.functionTypeSpan = node.text;
434
+ res.range = {
435
+ start: {
436
+ line: node.startPosition.row,
437
+ character: node.startPosition.column,
438
+ },
439
+ end: {
440
+ line: node.endPosition.row,
441
+ character: node.endPosition.column,
442
+ }
443
+ };
444
+ }
445
+ }
446
+ }
447
+ return res;
448
+ }
449
+ // TODO: delete
203
450
  async extractRelevantTypes1(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
204
451
  currentFile, foundTypeDefinitions, foundSymbols
205
452
  // outputFile: fs.WriteStream,
@@ -294,87 +541,409 @@ class TypeScriptDriver {
294
541
  return foundSoFar;
295
542
  }
296
543
  async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
297
- currentFile, foundContents // uri -> contents
298
- ) {
299
- const content = fs.readFileSync(currentFile.slice(7), "utf8");
544
+ currentFile, foundContents, // uri -> contents
545
+ logStream) {
546
+ if (logStream) {
547
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
548
+ }
549
+ // const content = fs.readFileSync(currentFile.slice(7), "utf8");
300
550
  // console.log(content)
301
551
  await this.extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
302
552
  return foundSoFar;
303
553
  }
554
+ async extractRelevantTypesWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
555
+ currentFile, foundContents, // uri -> contents
556
+ logStream) {
557
+ if (logStream) {
558
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
559
+ }
560
+ await this.extractRelevantTypesHelperWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
561
+ return foundSoFar;
562
+ }
304
563
  async extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
305
564
  currentFile, foundContents, // uri -> contents
306
565
  layer) {
307
- // console.log("===", fullHoverResult, layer, startLine, "===")
566
+ if (lspClient) {
567
+ // console.log("===", fullHoverResult, layer, startLine, "===")
568
+ // Split the type span into identifiers, where each include the text, line number, and character range.
569
+ // For each identifier, invoke go to type definition.
570
+ if (!foundSoFar.has(typeName)) {
571
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
572
+ // console.log(fullHoverResult)
573
+ const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
574
+ // DEBUG: REMOVE
575
+ // console.log("identifiers")
576
+ // console.log(fullHoverResult)
577
+ // console.dir(identifiers, { depth: null })
578
+ for (const identifier of identifiers) {
579
+ // console.log(`== loop ==`)
580
+ // console.dir(identifier, { depth: null })
581
+ // console.time(`loop ${identifier.name} layer ${layer}`)
582
+ // if (identifier.name === "_") {
583
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
584
+ // continue;
585
+ // };
586
+ // console.log(identifier)
587
+ // console.log(foundSoFar.has(identifier.name))
588
+ if (!foundSoFar.has(identifier.name)) {
589
+ try {
590
+ // const start = performance.now()
591
+ const typeDefinitionResult = await lspClient.typeDefinition({
592
+ textDocument: {
593
+ uri: currentFile
594
+ },
595
+ position: {
596
+ character: identifier.start,
597
+ line: startLine + identifier.line - 1 // startLine is already 1-indexed
598
+ }
599
+ });
600
+ // const end = performance.now()
601
+ // console.log(end - start)
602
+ // if (identifier.name == "Model") {
603
+ // console.log(identifier)
604
+ // console.dir(typeDefinitionResult, { depth: null })
605
+ // }
606
+ if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
607
+ const tdLocation = typeDefinitionResult[0];
608
+ let content = "";
609
+ if (foundContents.has(tdLocation.uri.slice(7))) {
610
+ content = foundContents.get(tdLocation.uri.slice(7));
611
+ }
612
+ else {
613
+ content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
614
+ foundContents.set(tdLocation.uri.slice(7), content);
615
+ }
616
+ // console.log(extractSnippet(content, { line: tdLocation.range.start.line, character: tdLocation.range.start.character }, { line: tdLocation.range.end.line, character: tdLocation.range.end.character }))
617
+ const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
618
+ if (decl) {
619
+ // const ident = this.typeChecker.getIdentifierFromDecl(decl);
620
+ // console.log(ident == identifier.name, ident, identifier.name, decl)
621
+ // console.log(`Decl: ${decl} || Identifier: ${ident}`)
622
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
623
+ await this.extractRelevantTypesHelper(lspClient, decl, identifier.name, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
624
+ }
625
+ else {
626
+ // console.log("decl not found")
627
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
628
+ }
629
+ }
630
+ else {
631
+ // console.log("td not found")
632
+ // console.dir(typeDefinitionResult, { depth: null })
633
+ }
634
+ }
635
+ catch (err) {
636
+ console.log(err);
637
+ }
638
+ }
639
+ else {
640
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
641
+ }
642
+ }
643
+ }
644
+ }
645
+ else {
646
+ // TODO: Test this.
647
+ if (!foundSoFar.has(typeName)) {
648
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
649
+ const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
650
+ for (const identifier of identifiers) {
651
+ if (!foundSoFar.has(identifier.name)) {
652
+ try {
653
+ const typeDefinitionResult = await this.vscodeImport.VsCode.gotoTypeDefinition({
654
+ filepath: currentFile,
655
+ position: {
656
+ line: startLine + identifier.line - 1,
657
+ character: identifier.start
658
+ }
659
+ });
660
+ if (typeDefinitionResult.length != 0) {
661
+ const tdLocation = typeDefinitionResult[0];
662
+ let content = "";
663
+ if (foundContents.has(tdLocation.filepath)) {
664
+ content = foundContents.get(tdLocation.filepath);
665
+ }
666
+ else {
667
+ content = fs.readFileSync(tdLocation.filepath, "utf8");
668
+ foundContents.set(tdLocation.filepath, content);
669
+ }
670
+ const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
671
+ if (decl) {
672
+ await this.extractRelevantTypesHelper(lspClient, decl, identifier.name, tdLocation.range.start.line, foundSoFar, tdLocation.filepath, foundContents, layer + 1);
673
+ }
674
+ else {
675
+ console.log("decl not found");
676
+ }
677
+ }
678
+ else {
679
+ console.log("td not found");
680
+ }
681
+ }
682
+ catch (err) {
683
+ console.log(err);
684
+ }
685
+ }
686
+ else {
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ async extractRelevantTypesWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, // identifier -> [full hover result, source]
693
+ currentFile, foundContents, // uri -> contents
694
+ logStream) {
695
+ if (logStream) {
696
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
697
+ }
698
+ // const content = fs.readFileSync(currentFile.slice(7), "utf8");
699
+ // console.log(content)
700
+ await this.extractRelevantTypesHelperWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, currentFile, foundContents, 0);
701
+ return foundSoFar;
702
+ }
703
+ async extractRelevantTypesHelperWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, // identifier -> [full hover result, source]
704
+ currentFile, foundContents, // uri -> contents
705
+ layer) {
706
+ var _a, _b;
707
+ // console.log("CURRENT TYPE:", typeName, fullHoverResult)
308
708
  // Split the type span into identifiers, where each include the text, line number, and character range.
309
709
  // For each identifier, invoke go to type definition.
310
710
  if (!foundSoFar.has(typeName)) {
311
711
  foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
312
712
  const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
313
- // console.dir(identifiers, { depth: null })
713
+ const sourceFile = this.tsCompilerProgram.getSourceFile(currentFile.slice(7));
314
714
  for (const identifier of identifiers) {
315
- // console.log(`== loop ==`)
316
- // console.dir(identifier, { depth: null })
317
- // console.time(`loop ${identifier.name} layer ${layer}`)
318
- // if (identifier.name === "_") {
319
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
320
- // continue;
321
- // };
322
- // console.log(identifier)
323
- // console.log(foundSoFar.has(identifier.name))
324
715
  if (!foundSoFar.has(identifier.name)) {
325
716
  try {
326
- const typeDefinitionResult = await lspClient.typeDefinition({
327
- textDocument: {
328
- uri: currentFile
329
- },
330
- position: {
331
- character: identifier.start,
332
- line: startLine + identifier.line - 1 // startLine is already 1-indexed
717
+ // console.log(identifier, linePosition, characterPosition, linePosition + identifier.line, identifier.start)
718
+ const position = ts.getPositionOfLineAndCharacter(sourceFile, linePosition + identifier.line - 1, identifier.start);
719
+ // const typeDefinitionResult = await lspClient.typeDefinition({
720
+ // textDocument: {
721
+ // uri: currentFile
722
+ // },
723
+ // position: {
724
+ // character: identifier.start,
725
+ // line: startLine + identifier.line - 1 // startLine is already 1-indexed
726
+ // }
727
+ // });
728
+ function findNodeAtPosition(node) {
729
+ if (position >= node.getStart() && position < node.getEnd()) {
730
+ return ts.forEachChild(node, findNodeAtPosition) || node;
333
731
  }
334
- });
335
- // if (identifier.name == "Model") {
336
- // console.dir(typeDefinitionResult, { depth: null })
337
- // }
338
- if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
339
- const tdLocation = typeDefinitionResult[0];
340
- let content = "";
341
- if (foundContents.has(tdLocation.uri.slice(7))) {
342
- content = foundContents.get(tdLocation.uri.slice(7));
732
+ return undefined;
733
+ }
734
+ function findIdentifierAtPosition(sourceFile, position) {
735
+ function find(node) {
736
+ if (ts.isIdentifier(node) && position >= node.getStart() && position <= node.getEnd()) {
737
+ return node;
738
+ }
739
+ return ts.forEachChild(node, find);
343
740
  }
344
- else {
345
- content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
346
- foundContents.set(tdLocation.uri.slice(7), content);
741
+ return find(sourceFile);
742
+ }
743
+ const node = findNodeAtPosition(sourceFile);
744
+ // const node = findIdentifierAtPosition(sourceFile, position);
745
+ if (!node) {
746
+ throw new Error("Node not found");
747
+ }
748
+ const possiblyPrimitiveType = this.tsCompilerTypeChecker.getTypeAtLocation(node);
749
+ if (possiblyPrimitiveType.flags & ts.TypeFlags.String ||
750
+ possiblyPrimitiveType.flags & ts.TypeFlags.Number ||
751
+ possiblyPrimitiveType.flags & ts.TypeFlags.Boolean ||
752
+ possiblyPrimitiveType.flags & ts.TypeFlags.Null ||
753
+ possiblyPrimitiveType.flags & ts.TypeFlags.Undefined ||
754
+ possiblyPrimitiveType.flags & ts.TypeFlags.Void ||
755
+ possiblyPrimitiveType.flags & ts.TypeFlags.ESSymbol ||
756
+ possiblyPrimitiveType.flags & ts.TypeFlags.ESSymbolLike ||
757
+ possiblyPrimitiveType.flags & ts.TypeFlags.UniqueESSymbol ||
758
+ possiblyPrimitiveType.flags & ts.TypeFlags.BigInt) {
759
+ // console.log("Primitive type", this.tsCompilerTypeChecker.typeToString(possiblyPrimitiveType))
760
+ return;
761
+ }
762
+ const symbol = this.tsCompilerTypeChecker.getSymbolAtLocation(node);
763
+ if (!symbol || !((_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
764
+ throw new Error("Symbol not found");
765
+ }
766
+ const trueSymbol = symbol && symbol.flags & ts.SymbolFlags.Alias
767
+ ? this.tsCompilerTypeChecker.getAliasedSymbol(symbol)
768
+ : symbol;
769
+ if ((_b = trueSymbol === null || trueSymbol === void 0 ? void 0 : trueSymbol.declarations) === null || _b === void 0 ? void 0 : _b.length) {
770
+ const decl = trueSymbol.declarations[0];
771
+ // console.log(decl)
772
+ if (ts.isTypeAliasDeclaration(decl)) {
773
+ // console.log("DECL TEXT", decl.getText())
774
+ // const rhs = decl.type;
775
+ // const typ = this.tsCompilerTypeChecker.getTypeAtLocation(rhs);
776
+ // const typStr = this.tsCompilerTypeChecker.typeToString(typ);
777
+ //
778
+ // console.log("Resolved type (RHS):", typStr);
779
+ // const rhsSource = rhs.getSourceFile();
780
+ // console.log(symbol.declarations)
781
+ // console.log("DECL", decl)
782
+ // console.log("TYPE AT LOC", this.tsCompilerTypeChecker.getTypeAtLocation(decl))
783
+ // console.log("TYPE TO STR", this.tsCompilerTypeChecker.typeToString(this.tsCompilerTypeChecker.getTypeAtLocation(decl)))
784
+ const declSourceFile = decl.getSourceFile();
785
+ const start = ts.getLineAndCharacterOfPosition(declSourceFile, decl.getStart());
786
+ const end = ts.getLineAndCharacterOfPosition(declSourceFile, decl.getEnd());
787
+ // console.log("LOCATION", start, end, declSourceFile.fileName)
788
+ await this.extractRelevantTypesHelperWithCompilerAPI(decl.getText(), identifier.name, start.line, start.character, foundSoFar, `file://${declSourceFile.fileName}`, foundContents, layer + 1);
347
789
  }
348
- const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
349
- if (decl) {
350
- const ident = this.typeChecker.getIdentifierFromDecl(decl);
351
- // console.log(ident == identifier.name, ident, identifier.name, decl)
352
- // console.log(`Decl: ${decl} || Identifier: ${ident}`)
353
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
354
- await this.extractRelevantTypesHelper(lspClient, decl, ident, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
790
+ }
791
+ }
792
+ catch (err) {
793
+ console.log(err);
794
+ }
795
+ }
796
+ else {
797
+ }
798
+ }
799
+ }
800
+ }
801
+ async extractRelevantTypesHelperWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
802
+ currentFile, foundContents, // uri -> contents
803
+ layer) {
804
+ if (lspClient) {
805
+ // Split the type span into identifiers, where each include the text, line number, and character range.
806
+ // For each identifier, invoke go to type definition.
807
+ if (!foundSoFar.has(typeName)) {
808
+ // const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
809
+ // const currentFileContent = fs.readFileSync(currentFile, "utf8");
810
+ const ast = await (0, ast_1.getAst)(currentFile, fullHoverResult);
811
+ if (!ast) {
812
+ throw new Error(`failed to get ast for file ${currentFile}`);
813
+ }
814
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7), ast: ast });
815
+ const language = (0, tree_sitter_1.getFullLanguageName)(currentFile);
816
+ const query = await (0, tree_sitter_1.getQueryForFile)(currentFile, path.join(__dirname, "tree-sitter-files", "queries", "relevant-types-queries", `${language}-extract-identifiers.scm`));
817
+ if (!query) {
818
+ throw new Error(`failed to get query for file ${currentFile} and language ${language}`);
819
+ }
820
+ const identifiers = query.captures(ast.rootNode);
821
+ for (const { name, node } of identifiers) {
822
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
823
+ if (!foundSoFar.has(node.text)) {
824
+ try {
825
+ const typeDefinitionResult = await lspClient.typeDefinition({
826
+ textDocument: {
827
+ uri: currentFile
828
+ },
829
+ position: {
830
+ character: node.startPosition.column,
831
+ line: startLine + node.startPosition.row
832
+ }
833
+ });
834
+ if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
835
+ const tdLocation = typeDefinitionResult[0];
836
+ let content = "";
837
+ if (foundContents.has(tdLocation.uri.slice(7))) {
838
+ content = foundContents.get(tdLocation.uri.slice(7));
839
+ }
840
+ else {
841
+ content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
842
+ foundContents.set(tdLocation.uri.slice(7), content);
843
+ }
844
+ const ast = await (0, ast_1.getAst)(tdLocation.uri, content);
845
+ if (!ast) {
846
+ throw new Error(`failed to get ast for file ${tdLocation.uri}`);
847
+ }
848
+ const decl = (0, tree_sitter_1.findEnclosingTypeDeclaration)(content, tdLocation.range.start.line, tdLocation.range.start.character, ast);
849
+ if (!decl) {
850
+ // throw new Error(`failed to get decl for file ${tdLocation.uri}`);
851
+ console.log(`failed to get decl for file ${tdLocation.uri}`);
852
+ }
853
+ if (decl) {
854
+ await this.extractRelevantTypesHelperWithTreesitter(lspClient, decl.fullText, node.text, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
855
+ }
856
+ else {
857
+ // console.log("decl not found")
858
+ }
355
859
  }
356
860
  else {
357
- console.log("decl not found");
358
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
861
+ // console.log("td not found")
862
+ // console.dir(typeDefinitionResult, { depth: null })
359
863
  }
360
864
  }
361
- else {
362
- console.log("td not found");
363
- // console.dir(typeDefinitionResult, { depth: null })
865
+ catch (err) {
866
+ console.log(err);
364
867
  }
365
868
  }
366
- catch (err) {
367
- console.log(err);
869
+ else {
870
+ // console.log(`foundSoFar has ${node.text}`)
368
871
  }
369
872
  }
370
- else {
371
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
873
+ }
874
+ }
875
+ else {
876
+ // TODO: Test this.
877
+ if (!foundSoFar.has(typeName)) {
878
+ const ast = await (0, ast_1.getAst)(currentFile, fullHoverResult);
879
+ if (!ast) {
880
+ throw new Error(`failed to get ast for file ${currentFile}`);
881
+ }
882
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7), ast: ast });
883
+ const language = (0, tree_sitter_1.getFullLanguageName)(currentFile);
884
+ const query = await (0, tree_sitter_1.getQueryForFile)(currentFile, path.join(__dirname, "tree-sitter-files", "queries", "relevant-headers-queries", `${language}-extract-identifiers.scm`));
885
+ if (!query) {
886
+ throw new Error(`failed to get query for file ${currentFile} and language ${language}`);
887
+ }
888
+ const identifiers = query.captures(ast.rootNode);
889
+ for (const { name, node } of identifiers) {
890
+ if (!foundSoFar.has(node.text)) {
891
+ try {
892
+ const typeDefinitionResult = await this.vscodeImport.VsCode.gotoTypeDefinition({
893
+ filepath: currentFile,
894
+ position: {
895
+ character: node.startPosition.column,
896
+ line: startLine + node.startPosition.row
897
+ }
898
+ });
899
+ if (typeDefinitionResult.length != 0) {
900
+ const tdLocation = typeDefinitionResult[0];
901
+ let content = "";
902
+ if (foundContents.has(tdLocation.filepath)) {
903
+ content = foundContents.get(tdLocation.filepath);
904
+ }
905
+ else {
906
+ content = fs.readFileSync(tdLocation.filepath, "utf8");
907
+ foundContents.set(tdLocation.filepath, content);
908
+ }
909
+ const ast = await (0, ast_1.getAst)(tdLocation.filepath, content);
910
+ if (!ast) {
911
+ throw new Error(`failed to get ast for file ${tdLocation.filepath}`);
912
+ }
913
+ const decl = (0, tree_sitter_1.findEnclosingTypeDeclaration)(content, tdLocation.range.start.line, tdLocation.range.start.character, ast);
914
+ if (!decl) {
915
+ // throw new Error(`failed to get decl for file ${tdLocation.uri}`);
916
+ console.log(`failed to get decl for file ${tdLocation.uri}`);
917
+ }
918
+ if (decl) {
919
+ await this.extractRelevantTypesHelper(lspClient, decl.fullText, node.text, tdLocation.range.start.line, foundSoFar, tdLocation.filepath, foundContents, layer + 1);
920
+ }
921
+ else {
922
+ // console.log("decl not found");
923
+ }
924
+ }
925
+ else {
926
+ // console.log("td not found");
927
+ }
928
+ }
929
+ catch (err) {
930
+ console.log(err);
931
+ }
932
+ }
933
+ else {
934
+ }
372
935
  }
373
936
  }
374
937
  }
375
938
  }
376
- async extractRelevantHeaders(_, sources, relevantTypes, holeType) {
939
+ async extractRelevantHeaders(_, sources, relevantTypes, holeType, projectRoot) {
940
+ // console.log("extractRelevantHeaders")
377
941
  // console.time("extractRelevantHeaders");
942
+ // NOTE: This takes around 550ms.
943
+ // TODO: Move this to the init method.
944
+ // const program = this.typeChecker.createTsCompilerProgram(sources, projectRoot);
945
+ // const checker = program.getTypeChecker();
946
+ // NOTE: program = this.typeChecker.createProgramFromSource("")
378
947
  const relevantContext = new Set();
379
948
  // NOTE: This is necessary because TypeScript sucks.
380
949
  // There is no way to compare objects by value,
@@ -382,35 +951,81 @@ class TypeScriptDriver {
382
951
  const relevantContextMap = new Map();
383
952
  const trace = [];
384
953
  const foundNormalForms = new Map();
385
- const targetTypes = this.generateTargetTypes(relevantTypes, holeType);
954
+ const foundTypeAnalysisResults = new Map();
955
+ // TODO: As long as you have a this binding to the program and the checker, you don't have to pass it around.
956
+ const targetTypes = this.generateTargetTypes(relevantTypes, holeType, this.tsCompilerProgram, this.tsCompilerTypeChecker);
957
+ // console.log("root", projectRoot)
958
+ const seenDecls = new Set();
386
959
  // only consider lines that start with let or const
387
960
  for (const source of sources) {
388
961
  const sourceContent = fs.readFileSync(source).toString("utf8");
389
- const filteredLines = sourceContent.split("\n").filter((line) => {
390
- return line.slice(0, 3) === "let" || line.slice(0, 5) === "const";
391
- });
392
- // check for relationship between each line and relevant types
393
- filteredLines.forEach(line => {
394
- // console.time(`helper, line: ${line}`);
395
- let tag = false;
396
- if (line === `const initFormState: [[Weekday, TimeOfDay], string] = [["M", "AM"], ""];`) {
397
- tag = true;
398
- }
399
- // TODO: Use the compiler API to split this.
400
- const splittedLine = line.split(" = ")[0];
401
- const typeSpanPattern = /(^[^:]*: )(.+)/;
402
- const regexMatch = splittedLine.match(typeSpanPattern);
403
- if (regexMatch) {
404
- const returnTypeSpan = regexMatch[2];
405
- // console.log(`returnTypeSpan: ${returnTypeSpan}`)
406
- // const typeAnalysisResult = this.typeChecker.analyzeTypeString(returnTypeSpan);
407
- // console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
408
- if (!this.typeChecker.isPrimitive(returnTypeSpan.split(" => ")[1])) {
409
- this.extractRelevantHeadersHelper(returnTypeSpan, targetTypes, relevantTypes, relevantContext, splittedLine, source, relevantContextMap, tag, trace, foundNormalForms);
410
- }
962
+ // TODO: this can be replaced by using typescript compiler api
963
+ // what really needs to happen is the following:
964
+ // filter by variable and function decls
965
+ // get a d.ts of them (or get a type decl)
966
+ // type decl makes more sense because d.ts format is a bit weird with class methods
967
+ // const start = performance.now()
968
+ const varFuncDecls = this.typeChecker.findTopLevelDeclarations(this.tsCompilerProgram, this.tsCompilerTypeChecker, source);
969
+ // const end = performance.now()
970
+ // console.log("varFuncDecls")
971
+ // console.log(varFuncDecls)
972
+ varFuncDecls.forEach((decl) => {
973
+ // TODO: Memoize decls that are already seen. (update: this moves 10ms from normalize2 to isTypeEquivalent.)
974
+ // const typeAnalysisResult = this.typeChecker.analyzeTypeString(decl.type);
975
+ // console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
976
+ // NOTE: debugging
977
+ // console.log("decl**")
978
+ // console.dir(decl, { depth: null })
979
+ const declStr = JSON.stringify(decl);
980
+ if (!seenDecls.has(declStr)) {
981
+ this.extractRelevantHeadersHelper2(decl.declarationText, decl.returnType ? decl.returnType : decl.type, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, this.tsCompilerProgram, this.tsCompilerTypeChecker);
982
+ seenDecls.add(declStr);
411
983
  }
412
- // console.timeEnd(`helper, line: ${line}`);
413
984
  });
985
+ // const filteredLines = sourceContent.split("\n").filter((line) => {
986
+ // return line.slice(0, 3) === "let" || line.slice(0, 5) === "const";
987
+ // });
988
+ //
989
+ // // check for relationship between each line and relevant types
990
+ // filteredLines.forEach(line => {
991
+ //
992
+ // // console.time(`helper, line: ${line}`);
993
+ //
994
+ // let tag = false;
995
+ // // if (line === `const initFormState: [[Weekday, TimeOfDay], string] = [["M", "AM"], ""];`) {
996
+ // // tag = true;
997
+ // // }
998
+ // // TODO: Use the compiler API to split this.
999
+ // const splittedLine = line.split(" = ")[0];
1000
+ //
1001
+ // const typeSpanPattern = /(^[^:]*: )(.+)/;
1002
+ // const regexMatch = splittedLine.match(typeSpanPattern)
1003
+ // if (regexMatch) {
1004
+ // const returnTypeSpan = regexMatch[2];
1005
+ // // console.log(`returnTypeSpan: ${returnTypeSpan}`)
1006
+ //
1007
+ // // const typeAnalysisResult = this.typeChecker.analyzeTypeString(returnTypeSpan);
1008
+ // // console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
1009
+ //
1010
+ // if (!this.typeChecker.isPrimitive(returnTypeSpan.split(" => ")[1])) {
1011
+ // this.extractRelevantHeadersHelper(
1012
+ // returnTypeSpan,
1013
+ // targetTypes,
1014
+ // relevantTypes,
1015
+ // relevantContext,
1016
+ // splittedLine,
1017
+ // source,
1018
+ // relevantContextMap,
1019
+ // tag,
1020
+ // trace,
1021
+ // foundNormalForms,
1022
+ // foundTypeAnalysisResults
1023
+ // );
1024
+ // }
1025
+ // }
1026
+ //
1027
+ // // console.timeEnd(`helper, line: ${line}`);
1028
+ // });
414
1029
  }
415
1030
  // console.log(JSON.stringify(relevantContextMap, null, 2))
416
1031
  // console.log(relevantContextMap.keys())
@@ -420,11 +1035,11 @@ class TypeScriptDriver {
420
1035
  // console.timeEnd("extractRelevantHeaders");
421
1036
  return relevantContext;
422
1037
  }
423
- generateTargetTypes(relevantTypes, holeType) {
1038
+ generateTargetTypes(relevantTypes, holeType, program, checker) {
424
1039
  // console.time("generateTargetTypes");
425
1040
  const targetTypes = new Set();
426
1041
  targetTypes.add(holeType);
427
- this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes);
1042
+ this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes, program, checker);
428
1043
  // console.timeEnd("generateTargetTypes");
429
1044
  return targetTypes;
430
1045
  }
@@ -454,25 +1069,25 @@ class TypeScriptDriver {
454
1069
  // }
455
1070
  // }
456
1071
  // }
457
- generateTargetTypesHelper(relevantTypes, currType, targetTypes) {
1072
+ generateTargetTypesHelper(relevantTypes, currType, targetTypes, program, checker) {
458
1073
  // Run analysis on currType.
459
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType);
1074
+ const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType, program, checker);
460
1075
  // Match on its kind.
461
1076
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
462
1077
  const rettype = typeAnalysisResult.returnType;
463
1078
  targetTypes.add(rettype.text);
464
- this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes);
1079
+ this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes, program, checker);
465
1080
  }
466
1081
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
467
1082
  typeAnalysisResult.constituents.forEach(constituent => {
468
1083
  targetTypes.add(constituent.text);
469
- this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes);
1084
+ this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes, program, checker);
470
1085
  });
471
1086
  }
472
1087
  else {
473
1088
  if (relevantTypes.has(currType)) {
474
1089
  const definition = relevantTypes.get(currType).typeSpan.split(" = ")[1];
475
- this.generateTargetTypesHelper(relevantTypes, definition, targetTypes);
1090
+ this.generateTargetTypesHelper(relevantTypes, definition, targetTypes, program, checker);
476
1091
  }
477
1092
  }
478
1093
  }
@@ -504,17 +1119,23 @@ class TypeScriptDriver {
504
1119
  // }
505
1120
  // });
506
1121
  // }
507
- extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms) {
1122
+ extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, // filename+typeSpan -> typeAnalysisResult
1123
+ program, checker) {
508
1124
  if (tag) {
509
1125
  // console.time(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
510
1126
  // trace.push(typeSpan)
511
1127
  // console.log(trace)
512
1128
  }
513
- // TODO: this can probably done at the top level.
514
- // analyzeTypeString is recursive by itself.
515
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1129
+ let typeAnalysisResult;
1130
+ if (!foundTypeAnalysisResults.has(source + ":" + typeSpan)) {
1131
+ typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
1132
+ foundTypeAnalysisResults.set(source + ":" + typeSpan, typeAnalysisResult);
1133
+ }
1134
+ else {
1135
+ typeAnalysisResult = foundTypeAnalysisResults.get(source + ":" + typeSpan);
1136
+ }
516
1137
  targetTypes.forEach(typ => {
517
- if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms)) {
1138
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms, program, checker)) {
518
1139
  // NOTE: This checks for dupes. ctx is an object so you need to check for each field.
519
1140
  // relevantContext.add({ typeSpan: line, sourceFile: source });
520
1141
  const ctx = { typeSpan: line, sourceFile: source };
@@ -522,11 +1143,11 @@ class TypeScriptDriver {
522
1143
  }
523
1144
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
524
1145
  const rettype = typeAnalysisResult.returnType;
525
- this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
1146
+ this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
526
1147
  }
527
1148
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
528
1149
  typeAnalysisResult.constituents.forEach(constituent => {
529
- this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
1150
+ this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
530
1151
  });
531
1152
  }
532
1153
  });
@@ -536,30 +1157,35 @@ class TypeScriptDriver {
536
1157
  }
537
1158
  }
538
1159
  // two types are equivalent if they have the same normal forms
539
- isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms) {
1160
+ // TODO: Create a memo of comparisons made.
1161
+ isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms, program, checker) {
540
1162
  // NOTE: BUGFIX
541
- // console.log(`isTypeEquivalent: ${t1}, ${t2}`)
1163
+ // console.log(`isTypeEquivalent: ${t1} {}{} ${t2}`)
542
1164
  // console.log(t1 == undefined)
543
1165
  // console.log(t2 == undefined)
544
1166
  let normT1 = "";
545
1167
  let normT2 = "";
546
1168
  if (foundNormalForms.has(t1)) {
1169
+ // console.log("found t1", true)
547
1170
  normT1 = foundNormalForms.get(t1);
548
1171
  }
549
1172
  else {
550
- normT1 = this.normalize2(t1, relevantTypes);
1173
+ // console.log("not found t1", false)
1174
+ normT1 = this.normalize2(t1, relevantTypes, program, checker);
551
1175
  foundNormalForms.set(t1, normT1);
552
1176
  }
553
1177
  if (foundNormalForms.has(t2)) {
1178
+ // console.log("found t2", true)
554
1179
  normT2 = foundNormalForms.get(t2);
555
1180
  }
556
1181
  else {
557
- normT2 = this.normalize2(t2, relevantTypes);
1182
+ // console.log("not found t2", false)
1183
+ normT2 = this.normalize2(t2, relevantTypes, program, checker);
558
1184
  foundNormalForms.set(t2, normT2);
559
1185
  }
560
1186
  // const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
561
1187
  // const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
562
- // console.log(normT1, normT2)
1188
+ // console.log(`normal forms: ${normT1} {}{} ${normT2}`)
563
1189
  return normT1 === normT2;
564
1190
  }
565
1191
  // return the normal form given a type span and a set of relevant types
@@ -643,7 +1269,7 @@ class TypeScriptDriver {
643
1269
  return typeSpan;
644
1270
  }
645
1271
  }
646
- normalize2(typeSpan, relevantTypes) {
1272
+ normalize2(typeSpan, relevantTypes, program, checker) {
647
1273
  // NOTE: BUGFIX
648
1274
  // console.log(`normalize: ${typeSpan}`)
649
1275
  // console.log(`normalize: ${typeSpan == undefined}`)
@@ -655,7 +1281,7 @@ class TypeScriptDriver {
655
1281
  }
656
1282
  // console.log(typeSpan)
657
1283
  let normalForm = "";
658
- const analysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1284
+ const analysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
659
1285
  // console.dir(analysisResult, { depth: null })
660
1286
  // pattern matching for typeSpan
661
1287
  // if (this.typeChecker.isPrimitive(typeSpan)) {
@@ -670,7 +1296,7 @@ class TypeScriptDriver {
670
1296
  elements.forEach(element => {
671
1297
  if (element !== "") {
672
1298
  const kv = element.split(": ");
673
- normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes);
1299
+ normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes, program, checker);
674
1300
  normalForm += "; ";
675
1301
  }
676
1302
  });
@@ -684,7 +1310,7 @@ class TypeScriptDriver {
684
1310
  const elements = this.typeChecker.parseTypeArrayString(typeSpan);
685
1311
  normalForm += "[";
686
1312
  elements.forEach((element, i) => {
687
- normalForm += this.normalize2(element, relevantTypes);
1313
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
688
1314
  if (i < elements.length - 1) {
689
1315
  normalForm += ", ";
690
1316
  }
@@ -698,7 +1324,7 @@ class TypeScriptDriver {
698
1324
  const elements = typeSpan.split(" | ");
699
1325
  elements.forEach((element, i) => {
700
1326
  normalForm += "(";
701
- normalForm += this.normalize2(element, relevantTypes);
1327
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
702
1328
  normalForm += ")";
703
1329
  if (i < elements.length - 1) {
704
1330
  normalForm += " | ";
@@ -710,7 +1336,7 @@ class TypeScriptDriver {
710
1336
  else if (this.typeChecker.isArray2(analysisResult)) {
711
1337
  // console.log(`isArray: ${typeSpan}`)
712
1338
  const element = typeSpan.split("[]")[0];
713
- normalForm += this.normalize2(element, relevantTypes);
1339
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
714
1340
  normalForm += "[]";
715
1341
  return normalForm;
716
1342
  // } else if (this.typeChecker.isTypeAlias(typeSpan)) {
@@ -722,11 +1348,14 @@ class TypeScriptDriver {
722
1348
  // console.log("typeSpan:", typeSpan)
723
1349
  // console.log("analysis:", analysisResult.text)
724
1350
  // console.log(relevantTypes.get(analysisResult.text))
1351
+ if (!relevantTypes.has(analysisResult.text)) {
1352
+ return typeSpan;
1353
+ }
725
1354
  const typ = relevantTypes.get(analysisResult.text).typeSpan.split(" = ")[1];
726
1355
  if (typ === undefined) {
727
1356
  return typeSpan;
728
1357
  }
729
- normalForm += this.normalize2(typ, relevantTypes);
1358
+ normalForm += this.normalize2(typ, relevantTypes, program, checker);
730
1359
  return normalForm;
731
1360
  }
732
1361
  else {
@@ -734,5 +1363,262 @@ class TypeScriptDriver {
734
1363
  return typeSpan;
735
1364
  }
736
1365
  }
1366
+ extractRelevantHeadersHelper2(declText, typeSpan, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, // filename+typeSpan -> typeAnalysisResult
1367
+ program, checker) {
1368
+ // console.log("extractRelevantHeaders2")
1369
+ // if (declText.includes("getBookings")) {
1370
+ // if (declText.slice(0, 11) === "getBookings") {
1371
+ // console.log("toplevel", declText, "-=-=-", typeSpan)
1372
+ // }
1373
+ let typeAnalysisResult;
1374
+ if (!foundTypeAnalysisResults.has(source + ":" + typeSpan)) {
1375
+ typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
1376
+ foundTypeAnalysisResults.set(source + ":" + typeSpan, typeAnalysisResult);
1377
+ }
1378
+ else {
1379
+ typeAnalysisResult = foundTypeAnalysisResults.get(source + ":" + typeSpan);
1380
+ }
1381
+ targetTypes.forEach(typ => {
1382
+ // if (declText.includes("getBookings")) {
1383
+ // if (declText.slice(0, 11) === "getBookings") {
1384
+ // console.log("innermost", declText, "-=-=-", typeSpan)
1385
+ // console.log(this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms))
1386
+ // console.log("============")
1387
+ // }
1388
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms, program, checker)) {
1389
+ // NOTE: This checks for dupes. ctx is an object so you need to check for each field.
1390
+ // relevantContext.add({ typeSpan: line, sourceFile: source });
1391
+ const ctx = { typeSpan: declText, sourceFile: source };
1392
+ relevantContextMap.set(JSON.stringify(ctx), ctx);
1393
+ }
1394
+ if (this.typeChecker.isFunction2(typeAnalysisResult)) {
1395
+ const rettype = typeAnalysisResult.returnType;
1396
+ this.extractRelevantHeadersHelper2(declText, rettype.text, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
1397
+ foundTypeAnalysisResults;
1398
+ }
1399
+ else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
1400
+ typeAnalysisResult.constituents.forEach(constituent => {
1401
+ this.extractRelevantHeadersHelper2(declText, constituent.text, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
1402
+ });
1403
+ }
1404
+ });
1405
+ }
1406
+ async extractRelevantHeadersWithTreesitter(_, sources, relevantTypes, holeType, holeIdentifier, projectRoot) {
1407
+ const relevantContext = new Set();
1408
+ // NOTE: This is necessary because TypeScript sucks.
1409
+ // There is no way to compare objects by value,
1410
+ // so sets of objects starts to accumulate tons of duplicates.
1411
+ const relevantContextMap = new Map();
1412
+ const trace = [];
1413
+ const foundNormalForms = new Map();
1414
+ const foundTypeAnalysisResults = new Map();
1415
+ const targetTypes = await this.generateTargetTypesWithTreesitter(relevantTypes, holeType, holeIdentifier);
1416
+ // return new Set<TypeSpanAndSourceFileAndAst>();
1417
+ const seenDecls = new Set();
1418
+ // only consider lines that start with let or const
1419
+ for (const source of sources) {
1420
+ // TODO: this can be replaced by using typescript compiler api
1421
+ // what really needs to happen is the following:
1422
+ // filter by variable and function decls
1423
+ // get a d.ts of them (or get a type decl)
1424
+ // type decl makes more sense because d.ts format is a bit weird with class methods
1425
+ const topLevelDecls = await (0, tree_sitter_1.extractTopLevelDecls)(source);
1426
+ for (const tld of topLevelDecls) {
1427
+ // pattern 0 is let/const, 1 is var, 2 is fun
1428
+ // if (!seenDecls.has(JSON.stringify()) {
1429
+ const originalDeclText = tld.pattern === 2
1430
+ ? tld.captures.find(d => d.name === "top.fn.decl").node.text
1431
+ : tld.captures.find(d => d.name === "top.var.decl").node.text;
1432
+ if (tld.pattern === 2) {
1433
+ // build a type span
1434
+ const funcType = (0, tree_sitter_1.extractFunctionTypeFromDecl)(tld);
1435
+ const wrapped = `type __TMP = ${funcType}`;
1436
+ const ast = await (0, ast_1.getAst)("file.ts", wrapped);
1437
+ if (!ast) {
1438
+ throw new Error(`failed to generate ast for ${wrapped}`);
1439
+ }
1440
+ const alias = ast.rootNode.namedChild(0);
1441
+ if (!alias || alias.type !== "type_alias_declaration") {
1442
+ throw new Error("Failed to parse type alias");
1443
+ }
1444
+ const valueNode = alias.childForFieldName("value");
1445
+ if (!valueNode)
1446
+ throw new Error("No type value found");
1447
+ const baseNode = this.unwrapToBaseType(valueNode);
1448
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, baseNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1449
+ }
1450
+ else {
1451
+ const varTypNode = tld.captures.find(d => d.name === "top.var.type").node;
1452
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, varTypNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1453
+ }
1454
+ }
1455
+ }
1456
+ for (const v of relevantContextMap.values()) {
1457
+ relevantContext.add(v);
1458
+ }
1459
+ return relevantContext;
1460
+ }
1461
+ async extractRelevantHeadersWithTreesitterHelper(originalDeclText, node, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source) {
1462
+ for (const typ of targetTypes) {
1463
+ if (await this.isTypeEquivalentWithTreesitter(node, typ, relevantTypes, foundNormalForms)) {
1464
+ const ctx = { typeSpan: originalDeclText, sourceFile: source };
1465
+ relevantContextMap.set(JSON.stringify(ctx), ctx);
1466
+ }
1467
+ if (node.type === "function_type") {
1468
+ const retTypeNode = node.namedChildren.find(c => c && c.type === "return_type");
1469
+ if (retTypeNode) {
1470
+ this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, retTypeNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1471
+ }
1472
+ }
1473
+ else if (node.type === "tuple_type") {
1474
+ for (const c of node.namedChildren) {
1475
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, c, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+ async generateTargetTypesWithTreesitter(relevantTypes, holeType, holeIdentifier) {
1481
+ const targetTypes = new Set();
1482
+ // const ast = relevantTypes.get(holeIdentifier)!.ast;
1483
+ const ast = await (0, ast_1.getAst)("file.ts", `type T = ${holeType}`);
1484
+ if (!ast) {
1485
+ throw new Error(`failed to generate ast for ${holeType}`);
1486
+ }
1487
+ const alias = ast.rootNode.namedChild(0);
1488
+ if (!alias || alias.type !== "type_alias_declaration") {
1489
+ throw new Error("Failed to parse type alias");
1490
+ }
1491
+ const valueNode = alias.childForFieldName("value");
1492
+ if (!valueNode)
1493
+ throw new Error("No type value found");
1494
+ // console.log(valueNode.text)
1495
+ const baseNode = this.unwrapToBaseType(valueNode);
1496
+ targetTypes.add(baseNode);
1497
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, holeType, targetTypes, baseNode);
1498
+ // console.log(targetTypes)
1499
+ return targetTypes;
1500
+ }
1501
+ unwrapToBaseType(node) {
1502
+ if (["function_type", "tuple_type", "type_identifier", "predefined_type"].includes(node.type)) {
1503
+ return node;
1504
+ }
1505
+ for (const child of node.namedChildren) {
1506
+ const unwrapped = this.unwrapToBaseType(child);
1507
+ if (unwrapped !== child || ["function_type", "tuple_type", "type_identifier", "predefined_type"].includes(unwrapped.type)) {
1508
+ return unwrapped;
1509
+ }
1510
+ }
1511
+ return node;
1512
+ }
1513
+ async generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, node) {
1514
+ var _a;
1515
+ if (!node)
1516
+ return;
1517
+ if (node.type === "function_type") {
1518
+ const returnType = node.childForFieldName("return_type");
1519
+ if (returnType) {
1520
+ targetTypes.add(returnType);
1521
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, returnType);
1522
+ }
1523
+ }
1524
+ if (node.type === "tuple_type") {
1525
+ for (const child of node.namedChildren) {
1526
+ if (child) {
1527
+ targetTypes.add(child);
1528
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, child);
1529
+ }
1530
+ }
1531
+ }
1532
+ if (relevantTypes.has(node.text)) {
1533
+ // const ast = relevantTypes.get(node.text)!.ast;
1534
+ const typeSpan = (_a = relevantTypes.get(node.text)) === null || _a === void 0 ? void 0 : _a.typeSpan;
1535
+ // const ast = await getAst("file.ts", `type T = ${typeSpan}`);
1536
+ const ast = await (0, ast_1.getAst)("file.ts", typeSpan);
1537
+ if (!ast) {
1538
+ throw new Error(`failed to generate ast for ${typeSpan}`);
1539
+ }
1540
+ const alias = ast.rootNode.namedChild(0);
1541
+ if (!alias || alias.type !== "type_alias_declaration") {
1542
+ throw new Error("Failed to parse type alias");
1543
+ }
1544
+ const valueNode = alias.childForFieldName("value");
1545
+ if (!valueNode)
1546
+ throw new Error("No type value found");
1547
+ const baseNode = this.unwrapToBaseType(valueNode);
1548
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, baseNode);
1549
+ }
1550
+ // if (node.type === "type_identifier" || node.type === "predefined_type") {
1551
+ // return [node.text];
1552
+ // }
1553
+ return;
1554
+ }
1555
+ async isTypeEquivalentWithTreesitter(node, typ, relevantTypes, foundNormalForms) {
1556
+ if (!node || !typ) {
1557
+ return false;
1558
+ }
1559
+ let normT1 = "";
1560
+ let normT2 = "";
1561
+ if (foundNormalForms.has(node.text)) {
1562
+ // console.log("found t1", true)
1563
+ normT1 = foundNormalForms.get(node.text);
1564
+ }
1565
+ else {
1566
+ // console.log("not found t1", false)
1567
+ normT1 = await this.normalizeWithTreesitter(node, relevantTypes);
1568
+ foundNormalForms.set(node.text, normT1);
1569
+ }
1570
+ if (foundNormalForms.has(typ.text)) {
1571
+ // console.log("found t2", true)
1572
+ normT2 = foundNormalForms.get(typ.text);
1573
+ }
1574
+ else {
1575
+ // console.log("not found t2", false)
1576
+ normT2 = await this.normalizeWithTreesitter(typ, relevantTypes);
1577
+ foundNormalForms.set(typ.text, normT2);
1578
+ }
1579
+ // const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
1580
+ // const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
1581
+ // console.log(`normal forms: ${normT1} {}{} ${normT2}`)
1582
+ return normT1 === normT2;
1583
+ }
1584
+ async normalizeWithTreesitter(node, relevantTypes) {
1585
+ var _a;
1586
+ if (!node)
1587
+ return "";
1588
+ switch (node.type) {
1589
+ case "function_type": {
1590
+ const params = node.child(0); // formal_parameters
1591
+ const returnType = node.childForFieldName("type") || node.namedChildren[1]; // function_type → parameters, =>, return
1592
+ const paramTypes = (params === null || params === void 0 ? void 0 : params.namedChildren.map(param => this.normalizeWithTreesitter(param.childForFieldName("type") || param.namedChildren.at(-1), relevantTypes)).join(", ")) || "";
1593
+ const ret = this.normalizeWithTreesitter(returnType, relevantTypes);
1594
+ return `(${paramTypes}) => ${ret}`;
1595
+ }
1596
+ case "tuple_type": {
1597
+ const elements = node.namedChildren.map(c => this.normalizeWithTreesitter(c, relevantTypes));
1598
+ return `[${elements.join(", ")}]`;
1599
+ }
1600
+ case "union_type": {
1601
+ const parts = node.namedChildren.map(c => this.normalizeWithTreesitter(c, relevantTypes));
1602
+ return parts.join(" | ");
1603
+ }
1604
+ case "type_identifier": {
1605
+ const alias = relevantTypes.get(node.text);
1606
+ if (!alias)
1607
+ return node.text;
1608
+ // Parse the alias's type span
1609
+ const wrapped = `type __TMP = ${alias};`;
1610
+ const tree = await (0, ast_1.getAst)("file.ts", wrapped);
1611
+ const valueNode = (_a = tree.rootNode.descendantsOfType("type_alias_declaration")[0]) === null || _a === void 0 ? void 0 : _a.childForFieldName("value");
1612
+ return this.normalizeWithTreesitter(valueNode, relevantTypes);
1613
+ }
1614
+ case "predefined_type":
1615
+ case "number":
1616
+ case "string":
1617
+ return node.text;
1618
+ default:
1619
+ // Fallback for types like array, etc.
1620
+ return node.text;
1621
+ }
1622
+ }
737
1623
  }
738
1624
  exports.TypeScriptDriver = TypeScriptDriver;