@jpoly1219/context-extractor 0.2.8 → 0.2.10

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 +334 -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 +1150 -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,102 @@ 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 queryPath = require.resolve(`./tree-sitter-files/queries/hole-queries/${language}.scm`);
394
+ const query = await (0, tree_sitter_1.getQueryForFile)(sketchFilePath, queryPath
395
+ // path.join(
396
+ // __dirname,
397
+ // "tree-sitter-files",
398
+ // "queries",
399
+ // "hole-queries",
400
+ // `${language}.scm`
401
+ // ),
402
+ );
403
+ if (!query) {
404
+ throw new Error(`failed to get query for file ${sketchFilePath} and language ${language}`);
405
+ }
406
+ // const matches = query.matches(ast.rootNode);
407
+ // console.log(JSON.stringify(matches))
408
+ // for (const m of matches) {
409
+ // console.log(m)
410
+ // }
411
+ // for (const m of matches) {
412
+ // for (const c of m.captures) {
413
+ // const { name, node } = c;
414
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
415
+ // }
416
+ // }
417
+ const captures = query.captures(ast.rootNode);
418
+ const res = {
419
+ fullHoverResult: "",
420
+ functionName: "",
421
+ functionTypeSpan: "",
422
+ linePosition: 0,
423
+ characterPosition: 0,
424
+ holeTypeDefLinePos: 0,
425
+ holeTypeDefCharPos: "declare function _(): ".length,
426
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } },
427
+ // range: (sketchSymbol![0] as SymbolInformation).location.range,
428
+ source: `file://${sketchFilePath}`,
429
+ trueHoleFunction: ""
430
+ };
431
+ for (const c of captures) {
432
+ const { name, node } = c;
433
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
434
+ switch (name) {
435
+ case "function.decl": {
436
+ res.fullHoverResult = node.text;
437
+ }
438
+ case "function.name": {
439
+ res.functionName = node.text;
440
+ }
441
+ case "function.type": {
442
+ res.functionTypeSpan = node.text;
443
+ res.range = {
444
+ start: {
445
+ line: node.startPosition.row,
446
+ character: node.startPosition.column,
447
+ },
448
+ end: {
449
+ line: node.endPosition.row,
450
+ character: node.endPosition.column,
451
+ }
452
+ };
453
+ }
454
+ }
455
+ }
456
+ return res;
457
+ }
458
+ // TODO: delete
203
459
  async extractRelevantTypes1(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
204
460
  currentFile, foundTypeDefinitions, foundSymbols
205
461
  // outputFile: fs.WriteStream,
@@ -294,87 +550,427 @@ class TypeScriptDriver {
294
550
  return foundSoFar;
295
551
  }
296
552
  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");
553
+ currentFile, foundContents, // uri -> contents
554
+ logStream) {
555
+ if (logStream) {
556
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
557
+ }
558
+ // const content = fs.readFileSync(currentFile.slice(7), "utf8");
300
559
  // console.log(content)
301
560
  await this.extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
302
561
  return foundSoFar;
303
562
  }
563
+ async extractRelevantTypesWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
564
+ currentFile, foundContents, // uri -> contents
565
+ logStream) {
566
+ if (logStream) {
567
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
568
+ }
569
+ await this.extractRelevantTypesHelperWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
570
+ return foundSoFar;
571
+ }
304
572
  async extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
305
573
  currentFile, foundContents, // uri -> contents
306
574
  layer) {
307
- // console.log("===", fullHoverResult, layer, startLine, "===")
575
+ if (lspClient) {
576
+ // console.log("===", fullHoverResult, layer, startLine, "===")
577
+ // Split the type span into identifiers, where each include the text, line number, and character range.
578
+ // For each identifier, invoke go to type definition.
579
+ if (!foundSoFar.has(typeName)) {
580
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
581
+ // console.log(fullHoverResult)
582
+ const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
583
+ // DEBUG: REMOVE
584
+ // console.log("identifiers")
585
+ // console.log(fullHoverResult)
586
+ // console.dir(identifiers, { depth: null })
587
+ for (const identifier of identifiers) {
588
+ // console.log(`== loop ==`)
589
+ // console.dir(identifier, { depth: null })
590
+ // console.time(`loop ${identifier.name} layer ${layer}`)
591
+ // if (identifier.name === "_") {
592
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
593
+ // continue;
594
+ // };
595
+ // console.log(identifier)
596
+ // console.log(foundSoFar.has(identifier.name))
597
+ if (!foundSoFar.has(identifier.name)) {
598
+ try {
599
+ // const start = performance.now()
600
+ const typeDefinitionResult = await lspClient.typeDefinition({
601
+ textDocument: {
602
+ uri: currentFile
603
+ },
604
+ position: {
605
+ character: identifier.start,
606
+ line: startLine + identifier.line - 1 // startLine is already 1-indexed
607
+ }
608
+ });
609
+ // const end = performance.now()
610
+ // console.log(end - start)
611
+ // if (identifier.name == "Model") {
612
+ // console.log(identifier)
613
+ // console.dir(typeDefinitionResult, { depth: null })
614
+ // }
615
+ if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
616
+ const tdLocation = typeDefinitionResult[0];
617
+ let content = "";
618
+ if (foundContents.has(tdLocation.uri.slice(7))) {
619
+ content = foundContents.get(tdLocation.uri.slice(7));
620
+ }
621
+ else {
622
+ content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
623
+ foundContents.set(tdLocation.uri.slice(7), content);
624
+ }
625
+ // console.log(extractSnippet(content, { line: tdLocation.range.start.line, character: tdLocation.range.start.character }, { line: tdLocation.range.end.line, character: tdLocation.range.end.character }))
626
+ const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
627
+ if (decl) {
628
+ // const ident = this.typeChecker.getIdentifierFromDecl(decl);
629
+ // console.log(ident == identifier.name, ident, identifier.name, decl)
630
+ // console.log(`Decl: ${decl} || Identifier: ${ident}`)
631
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
632
+ await this.extractRelevantTypesHelper(lspClient, decl, identifier.name, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
633
+ }
634
+ else {
635
+ // console.log("decl not found")
636
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
637
+ }
638
+ }
639
+ else {
640
+ // console.log("td not found")
641
+ // console.dir(typeDefinitionResult, { depth: null })
642
+ }
643
+ }
644
+ catch (err) {
645
+ console.log(err);
646
+ }
647
+ }
648
+ else {
649
+ // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
650
+ }
651
+ }
652
+ }
653
+ }
654
+ else {
655
+ // TODO: Test this.
656
+ if (!foundSoFar.has(typeName)) {
657
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
658
+ const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
659
+ for (const identifier of identifiers) {
660
+ if (!foundSoFar.has(identifier.name)) {
661
+ try {
662
+ const typeDefinitionResult = await this.vscodeImport.VsCode.gotoTypeDefinition({
663
+ filepath: currentFile,
664
+ position: {
665
+ line: startLine + identifier.line - 1,
666
+ character: identifier.start
667
+ }
668
+ });
669
+ if (typeDefinitionResult.length != 0) {
670
+ const tdLocation = typeDefinitionResult[0];
671
+ let content = "";
672
+ if (foundContents.has(tdLocation.filepath)) {
673
+ content = foundContents.get(tdLocation.filepath);
674
+ }
675
+ else {
676
+ content = fs.readFileSync(tdLocation.filepath, "utf8");
677
+ foundContents.set(tdLocation.filepath, content);
678
+ }
679
+ const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
680
+ if (decl) {
681
+ await this.extractRelevantTypesHelper(lspClient, decl, identifier.name, tdLocation.range.start.line, foundSoFar, tdLocation.filepath, foundContents, layer + 1);
682
+ }
683
+ else {
684
+ console.log("decl not found");
685
+ }
686
+ }
687
+ else {
688
+ console.log("td not found");
689
+ }
690
+ }
691
+ catch (err) {
692
+ console.log(err);
693
+ }
694
+ }
695
+ else {
696
+ }
697
+ }
698
+ }
699
+ }
700
+ }
701
+ async extractRelevantTypesWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, // identifier -> [full hover result, source]
702
+ currentFile, foundContents, // uri -> contents
703
+ logStream) {
704
+ if (logStream) {
705
+ logStream.write(`\n\n=*=*=*=*=*=[begin extracting relevant headers][${new Date().toISOString()}]\n\n`);
706
+ }
707
+ // const content = fs.readFileSync(currentFile.slice(7), "utf8");
708
+ // console.log(content)
709
+ await this.extractRelevantTypesHelperWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, currentFile, foundContents, 0);
710
+ return foundSoFar;
711
+ }
712
+ async extractRelevantTypesHelperWithCompilerAPI(fullHoverResult, typeName, linePosition, characterPosition, foundSoFar, // identifier -> [full hover result, source]
713
+ currentFile, foundContents, // uri -> contents
714
+ layer) {
715
+ var _a, _b;
716
+ // console.log("CURRENT TYPE:", typeName, fullHoverResult)
308
717
  // Split the type span into identifiers, where each include the text, line number, and character range.
309
718
  // For each identifier, invoke go to type definition.
310
719
  if (!foundSoFar.has(typeName)) {
311
720
  foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
312
721
  const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
313
- // console.dir(identifiers, { depth: null })
722
+ const sourceFile = this.tsCompilerProgram.getSourceFile(currentFile.slice(7));
314
723
  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
724
  if (!foundSoFar.has(identifier.name)) {
325
725
  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
726
+ // console.log(identifier, linePosition, characterPosition, linePosition + identifier.line, identifier.start)
727
+ const position = ts.getPositionOfLineAndCharacter(sourceFile, linePosition + identifier.line - 1, identifier.start);
728
+ // const typeDefinitionResult = await lspClient.typeDefinition({
729
+ // textDocument: {
730
+ // uri: currentFile
731
+ // },
732
+ // position: {
733
+ // character: identifier.start,
734
+ // line: startLine + identifier.line - 1 // startLine is already 1-indexed
735
+ // }
736
+ // });
737
+ function findNodeAtPosition(node) {
738
+ if (position >= node.getStart() && position < node.getEnd()) {
739
+ return ts.forEachChild(node, findNodeAtPosition) || node;
333
740
  }
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));
741
+ return undefined;
742
+ }
743
+ function findIdentifierAtPosition(sourceFile, position) {
744
+ function find(node) {
745
+ if (ts.isIdentifier(node) && position >= node.getStart() && position <= node.getEnd()) {
746
+ return node;
747
+ }
748
+ return ts.forEachChild(node, find);
343
749
  }
344
- else {
345
- content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
346
- foundContents.set(tdLocation.uri.slice(7), content);
750
+ return find(sourceFile);
751
+ }
752
+ const node = findNodeAtPosition(sourceFile);
753
+ // const node = findIdentifierAtPosition(sourceFile, position);
754
+ if (!node) {
755
+ throw new Error("Node not found");
756
+ }
757
+ const possiblyPrimitiveType = this.tsCompilerTypeChecker.getTypeAtLocation(node);
758
+ if (possiblyPrimitiveType.flags & ts.TypeFlags.String ||
759
+ possiblyPrimitiveType.flags & ts.TypeFlags.Number ||
760
+ possiblyPrimitiveType.flags & ts.TypeFlags.Boolean ||
761
+ possiblyPrimitiveType.flags & ts.TypeFlags.Null ||
762
+ possiblyPrimitiveType.flags & ts.TypeFlags.Undefined ||
763
+ possiblyPrimitiveType.flags & ts.TypeFlags.Void ||
764
+ possiblyPrimitiveType.flags & ts.TypeFlags.ESSymbol ||
765
+ possiblyPrimitiveType.flags & ts.TypeFlags.ESSymbolLike ||
766
+ possiblyPrimitiveType.flags & ts.TypeFlags.UniqueESSymbol ||
767
+ possiblyPrimitiveType.flags & ts.TypeFlags.BigInt) {
768
+ // console.log("Primitive type", this.tsCompilerTypeChecker.typeToString(possiblyPrimitiveType))
769
+ return;
770
+ }
771
+ const symbol = this.tsCompilerTypeChecker.getSymbolAtLocation(node);
772
+ if (!symbol || !((_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
773
+ throw new Error("Symbol not found");
774
+ }
775
+ const trueSymbol = symbol && symbol.flags & ts.SymbolFlags.Alias
776
+ ? this.tsCompilerTypeChecker.getAliasedSymbol(symbol)
777
+ : symbol;
778
+ if ((_b = trueSymbol === null || trueSymbol === void 0 ? void 0 : trueSymbol.declarations) === null || _b === void 0 ? void 0 : _b.length) {
779
+ const decl = trueSymbol.declarations[0];
780
+ // console.log(decl)
781
+ if (ts.isTypeAliasDeclaration(decl)) {
782
+ // console.log("DECL TEXT", decl.getText())
783
+ // const rhs = decl.type;
784
+ // const typ = this.tsCompilerTypeChecker.getTypeAtLocation(rhs);
785
+ // const typStr = this.tsCompilerTypeChecker.typeToString(typ);
786
+ //
787
+ // console.log("Resolved type (RHS):", typStr);
788
+ // const rhsSource = rhs.getSourceFile();
789
+ // console.log(symbol.declarations)
790
+ // console.log("DECL", decl)
791
+ // console.log("TYPE AT LOC", this.tsCompilerTypeChecker.getTypeAtLocation(decl))
792
+ // console.log("TYPE TO STR", this.tsCompilerTypeChecker.typeToString(this.tsCompilerTypeChecker.getTypeAtLocation(decl)))
793
+ const declSourceFile = decl.getSourceFile();
794
+ const start = ts.getLineAndCharacterOfPosition(declSourceFile, decl.getStart());
795
+ const end = ts.getLineAndCharacterOfPosition(declSourceFile, decl.getEnd());
796
+ // console.log("LOCATION", start, end, declSourceFile.fileName)
797
+ await this.extractRelevantTypesHelperWithCompilerAPI(decl.getText(), identifier.name, start.line, start.character, foundSoFar, `file://${declSourceFile.fileName}`, foundContents, layer + 1);
347
798
  }
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);
799
+ }
800
+ }
801
+ catch (err) {
802
+ console.log(err);
803
+ }
804
+ }
805
+ else {
806
+ }
807
+ }
808
+ }
809
+ }
810
+ async extractRelevantTypesHelperWithTreesitter(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
811
+ currentFile, foundContents, // uri -> contents
812
+ layer) {
813
+ if (lspClient) {
814
+ // Split the type span into identifiers, where each include the text, line number, and character range.
815
+ // For each identifier, invoke go to type definition.
816
+ if (!foundSoFar.has(typeName)) {
817
+ // const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
818
+ // const currentFileContent = fs.readFileSync(currentFile, "utf8");
819
+ const ast = await (0, ast_1.getAst)(currentFile, fullHoverResult);
820
+ if (!ast) {
821
+ throw new Error(`failed to get ast for file ${currentFile}`);
822
+ }
823
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7), ast: ast });
824
+ const language = (0, tree_sitter_1.getFullLanguageName)(currentFile);
825
+ const queryPath = require.resolve(`./tree-sitter-files/queries/relevant-types-queries/${language}-extract-identifiers.scm`);
826
+ const query = await (0, tree_sitter_1.getQueryForFile)(currentFile, queryPath
827
+ // path.join(
828
+ // __dirname,
829
+ // "tree-sitter-files",
830
+ // "queries",
831
+ // "relevant-types-queries",
832
+ // `${language}-extract-identifiers.scm`,
833
+ // )
834
+ );
835
+ if (!query) {
836
+ throw new Error(`failed to get query for file ${currentFile} and language ${language}`);
837
+ }
838
+ const identifiers = query.captures(ast.rootNode);
839
+ for (const { name, node } of identifiers) {
840
+ // console.log(`${name} →`, node.text, node.startPosition, node.endPosition);
841
+ if (!foundSoFar.has(node.text)) {
842
+ try {
843
+ const typeDefinitionResult = await lspClient.typeDefinition({
844
+ textDocument: {
845
+ uri: currentFile
846
+ },
847
+ position: {
848
+ character: node.startPosition.column,
849
+ line: startLine + node.startPosition.row
850
+ }
851
+ });
852
+ if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
853
+ const tdLocation = typeDefinitionResult[0];
854
+ let content = "";
855
+ if (foundContents.has(tdLocation.uri.slice(7))) {
856
+ content = foundContents.get(tdLocation.uri.slice(7));
857
+ }
858
+ else {
859
+ content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
860
+ foundContents.set(tdLocation.uri.slice(7), content);
861
+ }
862
+ const ast = await (0, ast_1.getAst)(tdLocation.uri, content);
863
+ if (!ast) {
864
+ throw new Error(`failed to get ast for file ${tdLocation.uri}`);
865
+ }
866
+ const decl = (0, tree_sitter_1.findEnclosingTypeDeclaration)(content, tdLocation.range.start.line, tdLocation.range.start.character, ast);
867
+ if (!decl) {
868
+ // throw new Error(`failed to get decl for file ${tdLocation.uri}`);
869
+ console.log(`failed to get decl for file ${tdLocation.uri}`);
870
+ }
871
+ if (decl) {
872
+ await this.extractRelevantTypesHelperWithTreesitter(lspClient, decl.fullText, node.text, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
873
+ }
874
+ else {
875
+ // console.log("decl not found")
876
+ }
355
877
  }
356
878
  else {
357
- console.log("decl not found");
358
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
879
+ // console.log("td not found")
880
+ // console.dir(typeDefinitionResult, { depth: null })
359
881
  }
360
882
  }
361
- else {
362
- console.log("td not found");
363
- // console.dir(typeDefinitionResult, { depth: null })
883
+ catch (err) {
884
+ console.log(err);
364
885
  }
365
886
  }
366
- catch (err) {
367
- console.log(err);
887
+ else {
888
+ // console.log(`foundSoFar has ${node.text}`)
368
889
  }
369
890
  }
370
- else {
371
- // console.timeEnd(`loop ${identifier.name} layer ${layer}`)
891
+ }
892
+ }
893
+ else {
894
+ // TODO: Test this.
895
+ if (!foundSoFar.has(typeName)) {
896
+ const ast = await (0, ast_1.getAst)(currentFile, fullHoverResult);
897
+ if (!ast) {
898
+ throw new Error(`failed to get ast for file ${currentFile}`);
899
+ }
900
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7), ast: ast });
901
+ const language = (0, tree_sitter_1.getFullLanguageName)(currentFile);
902
+ const queryPath = require.resolve(`./tree-sitter-files/queries/relevant-headers-queries/${language}-extract-identifiers.scm`);
903
+ const query = await (0, tree_sitter_1.getQueryForFile)(currentFile, queryPath
904
+ // path.join(
905
+ // __dirname,
906
+ // "tree-sitter-files",
907
+ // "queries",
908
+ // "relevant-headers-queries",
909
+ // `${language}-extract-identifiers.scm`,
910
+ // )
911
+ );
912
+ if (!query) {
913
+ throw new Error(`failed to get query for file ${currentFile} and language ${language}`);
914
+ }
915
+ const identifiers = query.captures(ast.rootNode);
916
+ for (const { name, node } of identifiers) {
917
+ if (!foundSoFar.has(node.text)) {
918
+ try {
919
+ const typeDefinitionResult = await this.vscodeImport.VsCode.gotoTypeDefinition({
920
+ filepath: currentFile,
921
+ position: {
922
+ character: node.startPosition.column,
923
+ line: startLine + node.startPosition.row
924
+ }
925
+ });
926
+ if (typeDefinitionResult.length != 0) {
927
+ const tdLocation = typeDefinitionResult[0];
928
+ let content = "";
929
+ if (foundContents.has(tdLocation.filepath)) {
930
+ content = foundContents.get(tdLocation.filepath);
931
+ }
932
+ else {
933
+ content = fs.readFileSync(tdLocation.filepath, "utf8");
934
+ foundContents.set(tdLocation.filepath, content);
935
+ }
936
+ const ast = await (0, ast_1.getAst)(tdLocation.filepath, content);
937
+ if (!ast) {
938
+ throw new Error(`failed to get ast for file ${tdLocation.filepath}`);
939
+ }
940
+ const decl = (0, tree_sitter_1.findEnclosingTypeDeclaration)(content, tdLocation.range.start.line, tdLocation.range.start.character, ast);
941
+ if (!decl) {
942
+ // throw new Error(`failed to get decl for file ${tdLocation.uri}`);
943
+ console.log(`failed to get decl for file ${tdLocation.uri}`);
944
+ }
945
+ if (decl) {
946
+ await this.extractRelevantTypesHelper(lspClient, decl.fullText, node.text, tdLocation.range.start.line, foundSoFar, tdLocation.filepath, foundContents, layer + 1);
947
+ }
948
+ else {
949
+ // console.log("decl not found");
950
+ }
951
+ }
952
+ else {
953
+ // console.log("td not found");
954
+ }
955
+ }
956
+ catch (err) {
957
+ console.log(err);
958
+ }
959
+ }
960
+ else {
961
+ }
372
962
  }
373
963
  }
374
964
  }
375
965
  }
376
- async extractRelevantHeaders(_, sources, relevantTypes, holeType) {
966
+ async extractRelevantHeaders(_, sources, relevantTypes, holeType, projectRoot) {
967
+ // console.log("extractRelevantHeaders")
377
968
  // console.time("extractRelevantHeaders");
969
+ // NOTE: This takes around 550ms.
970
+ // TODO: Move this to the init method.
971
+ // const program = this.typeChecker.createTsCompilerProgram(sources, projectRoot);
972
+ // const checker = program.getTypeChecker();
973
+ // NOTE: program = this.typeChecker.createProgramFromSource("")
378
974
  const relevantContext = new Set();
379
975
  // NOTE: This is necessary because TypeScript sucks.
380
976
  // There is no way to compare objects by value,
@@ -382,35 +978,81 @@ class TypeScriptDriver {
382
978
  const relevantContextMap = new Map();
383
979
  const trace = [];
384
980
  const foundNormalForms = new Map();
385
- const targetTypes = this.generateTargetTypes(relevantTypes, holeType);
981
+ const foundTypeAnalysisResults = new Map();
982
+ // TODO: As long as you have a this binding to the program and the checker, you don't have to pass it around.
983
+ const targetTypes = this.generateTargetTypes(relevantTypes, holeType, this.tsCompilerProgram, this.tsCompilerTypeChecker);
984
+ // console.log("root", projectRoot)
985
+ const seenDecls = new Set();
386
986
  // only consider lines that start with let or const
387
987
  for (const source of sources) {
388
988
  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
- }
989
+ // TODO: this can be replaced by using typescript compiler api
990
+ // what really needs to happen is the following:
991
+ // filter by variable and function decls
992
+ // get a d.ts of them (or get a type decl)
993
+ // type decl makes more sense because d.ts format is a bit weird with class methods
994
+ // const start = performance.now()
995
+ const varFuncDecls = this.typeChecker.findTopLevelDeclarations(this.tsCompilerProgram, this.tsCompilerTypeChecker, source);
996
+ // const end = performance.now()
997
+ // console.log("varFuncDecls")
998
+ // console.log(varFuncDecls)
999
+ varFuncDecls.forEach((decl) => {
1000
+ // TODO: Memoize decls that are already seen. (update: this moves 10ms from normalize2 to isTypeEquivalent.)
1001
+ // const typeAnalysisResult = this.typeChecker.analyzeTypeString(decl.type);
1002
+ // console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
1003
+ // NOTE: debugging
1004
+ // console.log("decl**")
1005
+ // console.dir(decl, { depth: null })
1006
+ const declStr = JSON.stringify(decl);
1007
+ if (!seenDecls.has(declStr)) {
1008
+ this.extractRelevantHeadersHelper2(decl.declarationText, decl.returnType ? decl.returnType : decl.type, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, this.tsCompilerProgram, this.tsCompilerTypeChecker);
1009
+ seenDecls.add(declStr);
411
1010
  }
412
- // console.timeEnd(`helper, line: ${line}`);
413
1011
  });
1012
+ // const filteredLines = sourceContent.split("\n").filter((line) => {
1013
+ // return line.slice(0, 3) === "let" || line.slice(0, 5) === "const";
1014
+ // });
1015
+ //
1016
+ // // check for relationship between each line and relevant types
1017
+ // filteredLines.forEach(line => {
1018
+ //
1019
+ // // console.time(`helper, line: ${line}`);
1020
+ //
1021
+ // let tag = false;
1022
+ // // if (line === `const initFormState: [[Weekday, TimeOfDay], string] = [["M", "AM"], ""];`) {
1023
+ // // tag = true;
1024
+ // // }
1025
+ // // TODO: Use the compiler API to split this.
1026
+ // const splittedLine = line.split(" = ")[0];
1027
+ //
1028
+ // const typeSpanPattern = /(^[^:]*: )(.+)/;
1029
+ // const regexMatch = splittedLine.match(typeSpanPattern)
1030
+ // if (regexMatch) {
1031
+ // const returnTypeSpan = regexMatch[2];
1032
+ // // console.log(`returnTypeSpan: ${returnTypeSpan}`)
1033
+ //
1034
+ // // const typeAnalysisResult = this.typeChecker.analyzeTypeString(returnTypeSpan);
1035
+ // // console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
1036
+ //
1037
+ // if (!this.typeChecker.isPrimitive(returnTypeSpan.split(" => ")[1])) {
1038
+ // this.extractRelevantHeadersHelper(
1039
+ // returnTypeSpan,
1040
+ // targetTypes,
1041
+ // relevantTypes,
1042
+ // relevantContext,
1043
+ // splittedLine,
1044
+ // source,
1045
+ // relevantContextMap,
1046
+ // tag,
1047
+ // trace,
1048
+ // foundNormalForms,
1049
+ // foundTypeAnalysisResults
1050
+ // );
1051
+ // }
1052
+ // }
1053
+ //
1054
+ // // console.timeEnd(`helper, line: ${line}`);
1055
+ // });
414
1056
  }
415
1057
  // console.log(JSON.stringify(relevantContextMap, null, 2))
416
1058
  // console.log(relevantContextMap.keys())
@@ -420,11 +1062,11 @@ class TypeScriptDriver {
420
1062
  // console.timeEnd("extractRelevantHeaders");
421
1063
  return relevantContext;
422
1064
  }
423
- generateTargetTypes(relevantTypes, holeType) {
1065
+ generateTargetTypes(relevantTypes, holeType, program, checker) {
424
1066
  // console.time("generateTargetTypes");
425
1067
  const targetTypes = new Set();
426
1068
  targetTypes.add(holeType);
427
- this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes);
1069
+ this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes, program, checker);
428
1070
  // console.timeEnd("generateTargetTypes");
429
1071
  return targetTypes;
430
1072
  }
@@ -454,25 +1096,25 @@ class TypeScriptDriver {
454
1096
  // }
455
1097
  // }
456
1098
  // }
457
- generateTargetTypesHelper(relevantTypes, currType, targetTypes) {
1099
+ generateTargetTypesHelper(relevantTypes, currType, targetTypes, program, checker) {
458
1100
  // Run analysis on currType.
459
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType);
1101
+ const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType, program, checker);
460
1102
  // Match on its kind.
461
1103
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
462
1104
  const rettype = typeAnalysisResult.returnType;
463
1105
  targetTypes.add(rettype.text);
464
- this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes);
1106
+ this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes, program, checker);
465
1107
  }
466
1108
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
467
1109
  typeAnalysisResult.constituents.forEach(constituent => {
468
1110
  targetTypes.add(constituent.text);
469
- this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes);
1111
+ this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes, program, checker);
470
1112
  });
471
1113
  }
472
1114
  else {
473
1115
  if (relevantTypes.has(currType)) {
474
1116
  const definition = relevantTypes.get(currType).typeSpan.split(" = ")[1];
475
- this.generateTargetTypesHelper(relevantTypes, definition, targetTypes);
1117
+ this.generateTargetTypesHelper(relevantTypes, definition, targetTypes, program, checker);
476
1118
  }
477
1119
  }
478
1120
  }
@@ -504,17 +1146,23 @@ class TypeScriptDriver {
504
1146
  // }
505
1147
  // });
506
1148
  // }
507
- extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms) {
1149
+ extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, // filename+typeSpan -> typeAnalysisResult
1150
+ program, checker) {
508
1151
  if (tag) {
509
1152
  // console.time(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
510
1153
  // trace.push(typeSpan)
511
1154
  // console.log(trace)
512
1155
  }
513
- // TODO: this can probably done at the top level.
514
- // analyzeTypeString is recursive by itself.
515
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1156
+ let typeAnalysisResult;
1157
+ if (!foundTypeAnalysisResults.has(source + ":" + typeSpan)) {
1158
+ typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
1159
+ foundTypeAnalysisResults.set(source + ":" + typeSpan, typeAnalysisResult);
1160
+ }
1161
+ else {
1162
+ typeAnalysisResult = foundTypeAnalysisResults.get(source + ":" + typeSpan);
1163
+ }
516
1164
  targetTypes.forEach(typ => {
517
- if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms)) {
1165
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms, program, checker)) {
518
1166
  // NOTE: This checks for dupes. ctx is an object so you need to check for each field.
519
1167
  // relevantContext.add({ typeSpan: line, sourceFile: source });
520
1168
  const ctx = { typeSpan: line, sourceFile: source };
@@ -522,11 +1170,11 @@ class TypeScriptDriver {
522
1170
  }
523
1171
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
524
1172
  const rettype = typeAnalysisResult.returnType;
525
- this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
1173
+ this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
526
1174
  }
527
1175
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
528
1176
  typeAnalysisResult.constituents.forEach(constituent => {
529
- this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
1177
+ this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
530
1178
  });
531
1179
  }
532
1180
  });
@@ -536,30 +1184,35 @@ class TypeScriptDriver {
536
1184
  }
537
1185
  }
538
1186
  // two types are equivalent if they have the same normal forms
539
- isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms) {
1187
+ // TODO: Create a memo of comparisons made.
1188
+ isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms, program, checker) {
540
1189
  // NOTE: BUGFIX
541
- // console.log(`isTypeEquivalent: ${t1}, ${t2}`)
1190
+ // console.log(`isTypeEquivalent: ${t1} {}{} ${t2}`)
542
1191
  // console.log(t1 == undefined)
543
1192
  // console.log(t2 == undefined)
544
1193
  let normT1 = "";
545
1194
  let normT2 = "";
546
1195
  if (foundNormalForms.has(t1)) {
1196
+ // console.log("found t1", true)
547
1197
  normT1 = foundNormalForms.get(t1);
548
1198
  }
549
1199
  else {
550
- normT1 = this.normalize2(t1, relevantTypes);
1200
+ // console.log("not found t1", false)
1201
+ normT1 = this.normalize2(t1, relevantTypes, program, checker);
551
1202
  foundNormalForms.set(t1, normT1);
552
1203
  }
553
1204
  if (foundNormalForms.has(t2)) {
1205
+ // console.log("found t2", true)
554
1206
  normT2 = foundNormalForms.get(t2);
555
1207
  }
556
1208
  else {
557
- normT2 = this.normalize2(t2, relevantTypes);
1209
+ // console.log("not found t2", false)
1210
+ normT2 = this.normalize2(t2, relevantTypes, program, checker);
558
1211
  foundNormalForms.set(t2, normT2);
559
1212
  }
560
1213
  // const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
561
1214
  // const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
562
- // console.log(normT1, normT2)
1215
+ // console.log(`normal forms: ${normT1} {}{} ${normT2}`)
563
1216
  return normT1 === normT2;
564
1217
  }
565
1218
  // return the normal form given a type span and a set of relevant types
@@ -643,7 +1296,7 @@ class TypeScriptDriver {
643
1296
  return typeSpan;
644
1297
  }
645
1298
  }
646
- normalize2(typeSpan, relevantTypes) {
1299
+ normalize2(typeSpan, relevantTypes, program, checker) {
647
1300
  // NOTE: BUGFIX
648
1301
  // console.log(`normalize: ${typeSpan}`)
649
1302
  // console.log(`normalize: ${typeSpan == undefined}`)
@@ -655,7 +1308,7 @@ class TypeScriptDriver {
655
1308
  }
656
1309
  // console.log(typeSpan)
657
1310
  let normalForm = "";
658
- const analysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1311
+ const analysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
659
1312
  // console.dir(analysisResult, { depth: null })
660
1313
  // pattern matching for typeSpan
661
1314
  // if (this.typeChecker.isPrimitive(typeSpan)) {
@@ -670,7 +1323,7 @@ class TypeScriptDriver {
670
1323
  elements.forEach(element => {
671
1324
  if (element !== "") {
672
1325
  const kv = element.split(": ");
673
- normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes);
1326
+ normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes, program, checker);
674
1327
  normalForm += "; ";
675
1328
  }
676
1329
  });
@@ -684,7 +1337,7 @@ class TypeScriptDriver {
684
1337
  const elements = this.typeChecker.parseTypeArrayString(typeSpan);
685
1338
  normalForm += "[";
686
1339
  elements.forEach((element, i) => {
687
- normalForm += this.normalize2(element, relevantTypes);
1340
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
688
1341
  if (i < elements.length - 1) {
689
1342
  normalForm += ", ";
690
1343
  }
@@ -698,7 +1351,7 @@ class TypeScriptDriver {
698
1351
  const elements = typeSpan.split(" | ");
699
1352
  elements.forEach((element, i) => {
700
1353
  normalForm += "(";
701
- normalForm += this.normalize2(element, relevantTypes);
1354
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
702
1355
  normalForm += ")";
703
1356
  if (i < elements.length - 1) {
704
1357
  normalForm += " | ";
@@ -710,7 +1363,7 @@ class TypeScriptDriver {
710
1363
  else if (this.typeChecker.isArray2(analysisResult)) {
711
1364
  // console.log(`isArray: ${typeSpan}`)
712
1365
  const element = typeSpan.split("[]")[0];
713
- normalForm += this.normalize2(element, relevantTypes);
1366
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
714
1367
  normalForm += "[]";
715
1368
  return normalForm;
716
1369
  // } else if (this.typeChecker.isTypeAlias(typeSpan)) {
@@ -722,11 +1375,14 @@ class TypeScriptDriver {
722
1375
  // console.log("typeSpan:", typeSpan)
723
1376
  // console.log("analysis:", analysisResult.text)
724
1377
  // console.log(relevantTypes.get(analysisResult.text))
1378
+ if (!relevantTypes.has(analysisResult.text)) {
1379
+ return typeSpan;
1380
+ }
725
1381
  const typ = relevantTypes.get(analysisResult.text).typeSpan.split(" = ")[1];
726
1382
  if (typ === undefined) {
727
1383
  return typeSpan;
728
1384
  }
729
- normalForm += this.normalize2(typ, relevantTypes);
1385
+ normalForm += this.normalize2(typ, relevantTypes, program, checker);
730
1386
  return normalForm;
731
1387
  }
732
1388
  else {
@@ -734,5 +1390,262 @@ class TypeScriptDriver {
734
1390
  return typeSpan;
735
1391
  }
736
1392
  }
1393
+ extractRelevantHeadersHelper2(declText, typeSpan, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, // filename+typeSpan -> typeAnalysisResult
1394
+ program, checker) {
1395
+ // console.log("extractRelevantHeaders2")
1396
+ // if (declText.includes("getBookings")) {
1397
+ // if (declText.slice(0, 11) === "getBookings") {
1398
+ // console.log("toplevel", declText, "-=-=-", typeSpan)
1399
+ // }
1400
+ let typeAnalysisResult;
1401
+ if (!foundTypeAnalysisResults.has(source + ":" + typeSpan)) {
1402
+ typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
1403
+ foundTypeAnalysisResults.set(source + ":" + typeSpan, typeAnalysisResult);
1404
+ }
1405
+ else {
1406
+ typeAnalysisResult = foundTypeAnalysisResults.get(source + ":" + typeSpan);
1407
+ }
1408
+ targetTypes.forEach(typ => {
1409
+ // if (declText.includes("getBookings")) {
1410
+ // if (declText.slice(0, 11) === "getBookings") {
1411
+ // console.log("innermost", declText, "-=-=-", typeSpan)
1412
+ // console.log(this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms))
1413
+ // console.log("============")
1414
+ // }
1415
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms, program, checker)) {
1416
+ // NOTE: This checks for dupes. ctx is an object so you need to check for each field.
1417
+ // relevantContext.add({ typeSpan: line, sourceFile: source });
1418
+ const ctx = { typeSpan: declText, sourceFile: source };
1419
+ relevantContextMap.set(JSON.stringify(ctx), ctx);
1420
+ }
1421
+ if (this.typeChecker.isFunction2(typeAnalysisResult)) {
1422
+ const rettype = typeAnalysisResult.returnType;
1423
+ this.extractRelevantHeadersHelper2(declText, rettype.text, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
1424
+ foundTypeAnalysisResults;
1425
+ }
1426
+ else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
1427
+ typeAnalysisResult.constituents.forEach(constituent => {
1428
+ this.extractRelevantHeadersHelper2(declText, constituent.text, targetTypes, relevantTypes, relevantContext, source, relevantContextMap, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
1429
+ });
1430
+ }
1431
+ });
1432
+ }
1433
+ async extractRelevantHeadersWithTreesitter(_, sources, relevantTypes, holeType, holeIdentifier, projectRoot) {
1434
+ const relevantContext = new Set();
1435
+ // NOTE: This is necessary because TypeScript sucks.
1436
+ // There is no way to compare objects by value,
1437
+ // so sets of objects starts to accumulate tons of duplicates.
1438
+ const relevantContextMap = new Map();
1439
+ const trace = [];
1440
+ const foundNormalForms = new Map();
1441
+ const foundTypeAnalysisResults = new Map();
1442
+ const targetTypes = await this.generateTargetTypesWithTreesitter(relevantTypes, holeType, holeIdentifier);
1443
+ // return new Set<TypeSpanAndSourceFileAndAst>();
1444
+ const seenDecls = new Set();
1445
+ // only consider lines that start with let or const
1446
+ for (const source of sources) {
1447
+ // TODO: this can be replaced by using typescript compiler api
1448
+ // what really needs to happen is the following:
1449
+ // filter by variable and function decls
1450
+ // get a d.ts of them (or get a type decl)
1451
+ // type decl makes more sense because d.ts format is a bit weird with class methods
1452
+ const topLevelDecls = await (0, tree_sitter_1.extractTopLevelDecls)(source);
1453
+ for (const tld of topLevelDecls) {
1454
+ // pattern 0 is let/const, 1 is var, 2 is fun
1455
+ // if (!seenDecls.has(JSON.stringify()) {
1456
+ const originalDeclText = tld.pattern === 2
1457
+ ? tld.captures.find(d => d.name === "top.fn.decl").node.text
1458
+ : tld.captures.find(d => d.name === "top.var.decl").node.text;
1459
+ if (tld.pattern === 2) {
1460
+ // build a type span
1461
+ const funcType = (0, tree_sitter_1.extractFunctionTypeFromDecl)(tld);
1462
+ const wrapped = `type __TMP = ${funcType}`;
1463
+ const ast = await (0, ast_1.getAst)("file.ts", wrapped);
1464
+ if (!ast) {
1465
+ throw new Error(`failed to generate ast for ${wrapped}`);
1466
+ }
1467
+ const alias = ast.rootNode.namedChild(0);
1468
+ if (!alias || alias.type !== "type_alias_declaration") {
1469
+ throw new Error("Failed to parse type alias");
1470
+ }
1471
+ const valueNode = alias.childForFieldName("value");
1472
+ if (!valueNode)
1473
+ throw new Error("No type value found");
1474
+ const baseNode = this.unwrapToBaseType(valueNode);
1475
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, baseNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1476
+ }
1477
+ else {
1478
+ const varTypNode = tld.captures.find(d => d.name === "top.var.type").node;
1479
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, varTypNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1480
+ }
1481
+ }
1482
+ }
1483
+ for (const v of relevantContextMap.values()) {
1484
+ relevantContext.add(v);
1485
+ }
1486
+ return relevantContext;
1487
+ }
1488
+ async extractRelevantHeadersWithTreesitterHelper(originalDeclText, node, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source) {
1489
+ for (const typ of targetTypes) {
1490
+ if (await this.isTypeEquivalentWithTreesitter(node, typ, relevantTypes, foundNormalForms)) {
1491
+ const ctx = { typeSpan: originalDeclText, sourceFile: source };
1492
+ relevantContextMap.set(JSON.stringify(ctx), ctx);
1493
+ }
1494
+ if (node.type === "function_type") {
1495
+ const retTypeNode = node.namedChildren.find(c => c && c.type === "return_type");
1496
+ if (retTypeNode) {
1497
+ this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, retTypeNode, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1498
+ }
1499
+ }
1500
+ else if (node.type === "tuple_type") {
1501
+ for (const c of node.namedChildren) {
1502
+ await this.extractRelevantHeadersWithTreesitterHelper(originalDeclText, c, targetTypes, relevantTypes, relevantContext, relevantContextMap, foundNormalForms, source);
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+ async generateTargetTypesWithTreesitter(relevantTypes, holeType, holeIdentifier) {
1508
+ const targetTypes = new Set();
1509
+ // const ast = relevantTypes.get(holeIdentifier)!.ast;
1510
+ const ast = await (0, ast_1.getAst)("file.ts", `type T = ${holeType}`);
1511
+ if (!ast) {
1512
+ throw new Error(`failed to generate ast for ${holeType}`);
1513
+ }
1514
+ const alias = ast.rootNode.namedChild(0);
1515
+ if (!alias || alias.type !== "type_alias_declaration") {
1516
+ throw new Error("Failed to parse type alias");
1517
+ }
1518
+ const valueNode = alias.childForFieldName("value");
1519
+ if (!valueNode)
1520
+ throw new Error("No type value found");
1521
+ // console.log(valueNode.text)
1522
+ const baseNode = this.unwrapToBaseType(valueNode);
1523
+ targetTypes.add(baseNode);
1524
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, holeType, targetTypes, baseNode);
1525
+ // console.log(targetTypes)
1526
+ return targetTypes;
1527
+ }
1528
+ unwrapToBaseType(node) {
1529
+ if (["function_type", "tuple_type", "type_identifier", "predefined_type"].includes(node.type)) {
1530
+ return node;
1531
+ }
1532
+ for (const child of node.namedChildren) {
1533
+ const unwrapped = this.unwrapToBaseType(child);
1534
+ if (unwrapped !== child || ["function_type", "tuple_type", "type_identifier", "predefined_type"].includes(unwrapped.type)) {
1535
+ return unwrapped;
1536
+ }
1537
+ }
1538
+ return node;
1539
+ }
1540
+ async generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, node) {
1541
+ var _a;
1542
+ if (!node)
1543
+ return;
1544
+ if (node.type === "function_type") {
1545
+ const returnType = node.childForFieldName("return_type");
1546
+ if (returnType) {
1547
+ targetTypes.add(returnType);
1548
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, returnType);
1549
+ }
1550
+ }
1551
+ if (node.type === "tuple_type") {
1552
+ for (const child of node.namedChildren) {
1553
+ if (child) {
1554
+ targetTypes.add(child);
1555
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, child);
1556
+ }
1557
+ }
1558
+ }
1559
+ if (relevantTypes.has(node.text)) {
1560
+ // const ast = relevantTypes.get(node.text)!.ast;
1561
+ const typeSpan = (_a = relevantTypes.get(node.text)) === null || _a === void 0 ? void 0 : _a.typeSpan;
1562
+ // const ast = await getAst("file.ts", `type T = ${typeSpan}`);
1563
+ const ast = await (0, ast_1.getAst)("file.ts", typeSpan);
1564
+ if (!ast) {
1565
+ throw new Error(`failed to generate ast for ${typeSpan}`);
1566
+ }
1567
+ const alias = ast.rootNode.namedChild(0);
1568
+ if (!alias || alias.type !== "type_alias_declaration") {
1569
+ throw new Error("Failed to parse type alias");
1570
+ }
1571
+ const valueNode = alias.childForFieldName("value");
1572
+ if (!valueNode)
1573
+ throw new Error("No type value found");
1574
+ const baseNode = this.unwrapToBaseType(valueNode);
1575
+ await this.generateTargetTypesWithTreesitterHelper(relevantTypes, currType, targetTypes, baseNode);
1576
+ }
1577
+ // if (node.type === "type_identifier" || node.type === "predefined_type") {
1578
+ // return [node.text];
1579
+ // }
1580
+ return;
1581
+ }
1582
+ async isTypeEquivalentWithTreesitter(node, typ, relevantTypes, foundNormalForms) {
1583
+ if (!node || !typ) {
1584
+ return false;
1585
+ }
1586
+ let normT1 = "";
1587
+ let normT2 = "";
1588
+ if (foundNormalForms.has(node.text)) {
1589
+ // console.log("found t1", true)
1590
+ normT1 = foundNormalForms.get(node.text);
1591
+ }
1592
+ else {
1593
+ // console.log("not found t1", false)
1594
+ normT1 = await this.normalizeWithTreesitter(node, relevantTypes);
1595
+ foundNormalForms.set(node.text, normT1);
1596
+ }
1597
+ if (foundNormalForms.has(typ.text)) {
1598
+ // console.log("found t2", true)
1599
+ normT2 = foundNormalForms.get(typ.text);
1600
+ }
1601
+ else {
1602
+ // console.log("not found t2", false)
1603
+ normT2 = await this.normalizeWithTreesitter(typ, relevantTypes);
1604
+ foundNormalForms.set(typ.text, normT2);
1605
+ }
1606
+ // const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
1607
+ // const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
1608
+ // console.log(`normal forms: ${normT1} {}{} ${normT2}`)
1609
+ return normT1 === normT2;
1610
+ }
1611
+ async normalizeWithTreesitter(node, relevantTypes) {
1612
+ var _a;
1613
+ if (!node)
1614
+ return "";
1615
+ switch (node.type) {
1616
+ case "function_type": {
1617
+ const params = node.child(0); // formal_parameters
1618
+ const returnType = node.childForFieldName("type") || node.namedChildren[1]; // function_type → parameters, =>, return
1619
+ const paramTypes = (params === null || params === void 0 ? void 0 : params.namedChildren.map(param => this.normalizeWithTreesitter(param.childForFieldName("type") || param.namedChildren.at(-1), relevantTypes)).join(", ")) || "";
1620
+ const ret = this.normalizeWithTreesitter(returnType, relevantTypes);
1621
+ return `(${paramTypes}) => ${ret}`;
1622
+ }
1623
+ case "tuple_type": {
1624
+ const elements = node.namedChildren.map(c => this.normalizeWithTreesitter(c, relevantTypes));
1625
+ return `[${elements.join(", ")}]`;
1626
+ }
1627
+ case "union_type": {
1628
+ const parts = node.namedChildren.map(c => this.normalizeWithTreesitter(c, relevantTypes));
1629
+ return parts.join(" | ");
1630
+ }
1631
+ case "type_identifier": {
1632
+ const alias = relevantTypes.get(node.text);
1633
+ if (!alias)
1634
+ return node.text;
1635
+ // Parse the alias's type span
1636
+ const wrapped = `type __TMP = ${alias};`;
1637
+ const tree = await (0, ast_1.getAst)("file.ts", wrapped);
1638
+ const valueNode = (_a = tree.rootNode.descendantsOfType("type_alias_declaration")[0]) === null || _a === void 0 ? void 0 : _a.childForFieldName("value");
1639
+ return this.normalizeWithTreesitter(valueNode, relevantTypes);
1640
+ }
1641
+ case "predefined_type":
1642
+ case "number":
1643
+ case "string":
1644
+ return node.text;
1645
+ default:
1646
+ // Fallback for types like array, etc.
1647
+ return node.text;
1648
+ }
1649
+ }
737
1650
  }
738
1651
  exports.TypeScriptDriver = TypeScriptDriver;