@jpoly1219/context-extractor 0.2.7 → 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 -4
  2. package/dist/src/app.js +319 -97
  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 +53 -13
  11. package/dist/src/ocaml-driver.d.ts +55 -5
  12. package/dist/src/ocaml-driver.js +296 -191
  13. package/dist/src/ocaml-utils/_build/default/test_parser.bc.cjs +194658 -0
  14. package/dist/src/runner.js +143 -10
  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 +80 -9
  25. package/dist/src/types.js +1 -6
  26. package/dist/src/typescript-driver.d.ts +86 -12
  27. package/dist/src/typescript-driver.js +1276 -205
  28. package/dist/src/typescript-type-checker.d.ts +22 -2
  29. package/dist/src/typescript-type-checker.js +290 -9
  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
+ }
121
144
  }
122
- async getHoleContext(lspClient, sketchFilePath) {
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);
151
+ }
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,42 +360,109 @@ 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,
199
- source: `file://${injectedSketchFilePath}`
371
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } },
372
+ // range: (sketchSymbol![0] as SymbolInformation).location.range,
373
+ source: `file://${injectedSketchFilePath}`,
374
+ trueHoleFunction: trueHoleFunction
375
+ };
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: ""
200
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;
201
448
  }
202
- async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
203
- currentFile) {
449
+ // TODO: delete
450
+ async extractRelevantTypes1(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
451
+ currentFile, foundTypeDefinitions, foundSymbols
452
+ // outputFile: fs.WriteStream,
453
+ ) {
204
454
  if (!foundSoFar.has(typeName)) {
205
455
  foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
206
456
  // outputFile.write(`${fullHoverResult};\n`);
207
457
  const content = fs.readFileSync(currentFile.slice(7), "utf8");
208
458
  for (let linePos = startLine; linePos <= endLine; ++linePos) {
459
+ // TODO: use a platform-agnostic command here
209
460
  const numOfCharsInLine = parseInt((0, child_process_1.execSync)(`wc -m <<< "${content.split("\n")[linePos]}"`, { shell: "/bin/bash" }).toString());
210
- for (let charPos = 0; charPos < numOfCharsInLine; ++charPos) {
461
+ const numOfCharsInLine2 = content.split("\n")[linePos].length;
462
+ const numOfCharsInLine3 = [...content.split("\n")[linePos]].map(c => c.codePointAt(0)).length;
463
+ // console.log(numOfCharsInLine === numOfCharsInLine2, content.split("\n")[linePos], numOfCharsInLine, numOfCharsInLine2, numOfCharsInLine3)
464
+ // console.time(`===loop ${content.split("\n")[linePos]}===`);
465
+ for (let charPos = 0; charPos < numOfCharsInLine2; ++charPos) {
211
466
  try {
212
467
  const typeDefinitionResult = await lspClient.typeDefinition({
213
468
  textDocument: {
@@ -218,68 +473,574 @@ class TypeScriptDriver {
218
473
  line: linePos
219
474
  }
220
475
  });
476
+ // if (content.split("\n")[linePos] === `type Action = AddBooking | CancelBooking | ClearBookings;`) {
477
+ // console.dir(typeDefinitionResult, { depth: null })
478
+ // console.log(charPos)
479
+ // }
221
480
  if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
222
- // Use documentSymbol instead of hover.
223
- // This prevents type alias "squashing" done by tsserver.
224
- // This also allows for grabbing the entire definition range and not just the symbol range.
225
- // PERF: feels like this could be memoized to improve performance.
226
- const documentSymbolResult = await lspClient.documentSymbol({
227
- textDocument: {
228
- uri: typeDefinitionResult[0].uri
481
+ const tdResultStr = JSON.stringify(typeDefinitionResult);
482
+ if (!foundTypeDefinitions.has(tdResultStr)) {
483
+ foundTypeDefinitions.set(tdResultStr, typeDefinitionResult);
484
+ // Use documentSymbol instead of hover.
485
+ // This prevents type alias "squashing" done by tsserver.
486
+ // This also allows for grabbing the entire definition range and not just the symbol range.
487
+ // PERF: feels like this could be memoized to improve performance.
488
+ // console.time("docSymbol")
489
+ const tdUri = typeDefinitionResult[0].uri;
490
+ let documentSymbolResult;
491
+ if (foundSymbols.has(tdUri)) {
492
+ documentSymbolResult = foundSymbols.get(tdUri);
493
+ }
494
+ else {
495
+ documentSymbolResult = await lspClient.documentSymbol({
496
+ textDocument: {
497
+ uri: typeDefinitionResult[0].uri
498
+ }
499
+ });
500
+ foundSymbols.set(tdUri, documentSymbolResult);
501
+ }
502
+ // console.timeEnd("docSymbol")
503
+ // console.time("dsMap")
504
+ const dsMap = documentSymbolResult.reduce((m, obj) => {
505
+ m.set(obj.location.range.start.line, obj.location.range);
506
+ return m;
507
+ }, new Map());
508
+ // console.timeEnd("dsMap")
509
+ // console.log("\n")
510
+ // console.dir(typeDefinitionResult, { depth: null })
511
+ // console.dir(documentSymbolResult, { depth: null })
512
+ // console.log("\n")
513
+ // grab if the line number of typeDefinitionResult and documentSymbolResult matches
514
+ // const dsMap = documentSymbolResult!.reduce((m, obj) => {
515
+ // m.set((obj as SymbolInformation).location.range.start.line, (obj as SymbolInformation).location.range as unknown as Range);
516
+ // return m;
517
+ // }, new Map<number, Range>());
518
+ const matchingSymbolRange = dsMap.get(typeDefinitionResult[0].range.start.line);
519
+ if (matchingSymbolRange) {
520
+ const snippetInRange = (0, utils_1.extractSnippet)(fs.readFileSync(typeDefinitionResult[0].uri.slice(7)).toString("utf8"), matchingSymbolRange.start, matchingSymbolRange.end);
521
+ // TODO: this can potentially be its own method. the driver would require some way to get type context.
522
+ // potentially, this type checker can be its own class.
523
+ const identifier = this.typeChecker.getIdentifierFromDecl(snippetInRange);
524
+ await this.extractRelevantTypes1(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, matchingSymbolRange.end.line, foundSoFar, typeDefinitionResult[0].uri, foundTypeDefinitions, foundSymbols
525
+ // outputFile,
526
+ );
229
527
  }
230
- });
231
- // grab if the line number of typeDefinitionResult and documentSymbolResult matches
232
- const dsMap = documentSymbolResult.reduce((m, obj) => {
233
- m.set(obj.location.range.start.line, obj.location.range);
234
- return m;
235
- }, new Map());
236
- const matchingSymbolRange = dsMap.get(typeDefinitionResult[0].range.start.line);
237
- if (matchingSymbolRange) {
238
- const snippetInRange = (0, utils_1.extractSnippet)(fs.readFileSync(typeDefinitionResult[0].uri.slice(7)).toString("utf8"), matchingSymbolRange.start, matchingSymbolRange.end);
239
- // TODO: this can potentially be its own method. the driver would require some way to get type context.
240
- // potentially, this type checker can be its own class.
241
- const identifier = this.typeChecker.getIdentifierFromDecl(snippetInRange);
242
- await this.extractRelevantTypes(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, matchingSymbolRange.end.line, foundSoFar, typeDefinitionResult[0].uri);
243
528
  }
244
529
  }
530
+ // else {
531
+ // console.log(`already found ${tdResultStr}!`)
532
+ // }
245
533
  }
246
534
  catch (err) {
247
535
  console.log(`${err}`);
248
536
  }
249
537
  }
538
+ // console.timeEnd(`===loop ${content.split("\n")[linePos]}===`);
250
539
  }
251
540
  }
252
541
  return foundSoFar;
253
542
  }
254
- async extractRelevantHeaders(_, sources, relevantTypes, holeType) {
543
+ async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
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");
550
+ // console.log(content)
551
+ await this.extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
552
+ return foundSoFar;
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
+ }
563
+ async extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
564
+ currentFile, foundContents, // uri -> contents
565
+ layer) {
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)
708
+ // Split the type span into identifiers, where each include the text, line number, and character range.
709
+ // For each identifier, invoke go to type definition.
710
+ if (!foundSoFar.has(typeName)) {
711
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
712
+ const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
713
+ const sourceFile = this.tsCompilerProgram.getSourceFile(currentFile.slice(7));
714
+ for (const identifier of identifiers) {
715
+ if (!foundSoFar.has(identifier.name)) {
716
+ try {
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;
731
+ }
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);
740
+ }
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);
789
+ }
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
+ }
859
+ }
860
+ else {
861
+ // console.log("td not found")
862
+ // console.dir(typeDefinitionResult, { depth: null })
863
+ }
864
+ }
865
+ catch (err) {
866
+ console.log(err);
867
+ }
868
+ }
869
+ else {
870
+ // console.log(`foundSoFar has ${node.text}`)
871
+ }
872
+ }
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
+ }
935
+ }
936
+ }
937
+ }
938
+ }
939
+ async extractRelevantHeaders(_, sources, relevantTypes, holeType, projectRoot) {
940
+ // console.log("extractRelevantHeaders")
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("")
255
947
  const relevantContext = new Set();
256
- const targetTypes = this.generateTargetTypes(relevantTypes, holeType);
948
+ // NOTE: This is necessary because TypeScript sucks.
949
+ // There is no way to compare objects by value,
950
+ // so sets of objects starts to accumulate tons of duplicates.
951
+ const relevantContextMap = new Map();
952
+ const trace = [];
953
+ const foundNormalForms = new Map();
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();
257
959
  // only consider lines that start with let or const
258
960
  for (const source of sources) {
259
961
  const sourceContent = fs.readFileSync(source).toString("utf8");
260
- const filteredLines = sourceContent.split("\n").filter((line) => {
261
- return line.slice(0, 3) === "let" || line.slice(0, 5) === "const";
262
- });
263
- // check for relationship between each line and relevant types
264
- filteredLines.forEach(line => {
265
- // TODO: Use the compiler API to split this.
266
- const splittedLine = line.split(" = ")[0];
267
- const typeSpanPattern = /(^[^:]*: )(.+)/;
268
- const regexMatch = splittedLine.match(typeSpanPattern);
269
- if (regexMatch) {
270
- const returnTypeSpan = regexMatch[2];
271
- if (!this.typeChecker.isPrimitive(returnTypeSpan.split(" => ")[1])) {
272
- this.extractRelevantHeadersHelper(returnTypeSpan, targetTypes, relevantTypes, relevantContext, splittedLine, source);
273
- }
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);
274
983
  }
275
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
+ // });
276
1029
  }
1030
+ // console.log(JSON.stringify(relevantContextMap, null, 2))
1031
+ // console.log(relevantContextMap.keys())
1032
+ for (const v of relevantContextMap.values()) {
1033
+ relevantContext.add(v);
1034
+ }
1035
+ // console.timeEnd("extractRelevantHeaders");
277
1036
  return relevantContext;
278
1037
  }
279
- generateTargetTypes(relevantTypes, holeType) {
1038
+ generateTargetTypes(relevantTypes, holeType, program, checker) {
1039
+ // console.time("generateTargetTypes");
280
1040
  const targetTypes = new Set();
281
1041
  targetTypes.add(holeType);
282
- this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes);
1042
+ this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes, program, checker);
1043
+ // console.timeEnd("generateTargetTypes");
283
1044
  return targetTypes;
284
1045
  }
285
1046
  // generateTargetTypesHelper(
@@ -308,25 +1069,25 @@ class TypeScriptDriver {
308
1069
  // }
309
1070
  // }
310
1071
  // }
311
- generateTargetTypesHelper(relevantTypes, currType, targetTypes) {
1072
+ generateTargetTypesHelper(relevantTypes, currType, targetTypes, program, checker) {
312
1073
  // Run analysis on currType.
313
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType);
1074
+ const typeAnalysisResult = this.typeChecker.analyzeTypeString(currType, program, checker);
314
1075
  // Match on its kind.
315
1076
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
316
1077
  const rettype = typeAnalysisResult.returnType;
317
1078
  targetTypes.add(rettype.text);
318
- this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes);
1079
+ this.generateTargetTypesHelper(relevantTypes, rettype.text, targetTypes, program, checker);
319
1080
  }
320
1081
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
321
1082
  typeAnalysisResult.constituents.forEach(constituent => {
322
1083
  targetTypes.add(constituent.text);
323
- this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes);
1084
+ this.generateTargetTypesHelper(relevantTypes, constituent.text, targetTypes, program, checker);
324
1085
  });
325
1086
  }
326
1087
  else {
327
1088
  if (relevantTypes.has(currType)) {
328
1089
  const definition = relevantTypes.get(currType).typeSpan.split(" = ")[1];
329
- this.generateTargetTypesHelper(relevantTypes, definition, targetTypes);
1090
+ this.generateTargetTypesHelper(relevantTypes, definition, targetTypes, program, checker);
330
1091
  }
331
1092
  }
332
1093
  }
@@ -358,31 +1119,73 @@ class TypeScriptDriver {
358
1119
  // }
359
1120
  // });
360
1121
  // }
361
- extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source) {
362
- const typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1122
+ extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, // filename+typeSpan -> typeAnalysisResult
1123
+ program, checker) {
1124
+ if (tag) {
1125
+ // console.time(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
1126
+ // trace.push(typeSpan)
1127
+ // console.log(trace)
1128
+ }
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
+ }
363
1137
  targetTypes.forEach(typ => {
364
- if (this.isTypeEquivalent(typeSpan, typ, relevantTypes)) {
365
- relevantContext.add({ typeSpan: line, sourceFile: source });
1138
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms, program, checker)) {
1139
+ // NOTE: This checks for dupes. ctx is an object so you need to check for each field.
1140
+ // relevantContext.add({ typeSpan: line, sourceFile: source });
1141
+ const ctx = { typeSpan: line, sourceFile: source };
1142
+ relevantContextMap.set(JSON.stringify(ctx), ctx);
366
1143
  }
367
1144
  if (this.typeChecker.isFunction2(typeAnalysisResult)) {
368
1145
  const rettype = typeAnalysisResult.returnType;
369
- this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source);
1146
+ this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
370
1147
  }
371
1148
  else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
372
1149
  typeAnalysisResult.constituents.forEach(constituent => {
373
- this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source);
1150
+ this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms, foundTypeAnalysisResults, program, checker);
374
1151
  });
375
1152
  }
376
1153
  });
1154
+ if (tag) {
1155
+ // console.log("\n\n\n")
1156
+ // console.timeEnd(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
1157
+ }
377
1158
  }
378
1159
  // two types are equivalent if they have the same normal forms
379
- isTypeEquivalent(t1, t2, relevantTypes) {
1160
+ // TODO: Create a memo of comparisons made.
1161
+ isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms, program, checker) {
380
1162
  // NOTE: BUGFIX
381
- // console.log(`isTypeEquivalent: ${t1}, ${t2}`)
1163
+ // console.log(`isTypeEquivalent: ${t1} {}{} ${t2}`)
382
1164
  // console.log(t1 == undefined)
383
1165
  // console.log(t2 == undefined)
384
- const normT1 = this.normalize(t1, relevantTypes);
385
- const normT2 = this.normalize(t2, relevantTypes);
1166
+ let normT1 = "";
1167
+ let normT2 = "";
1168
+ if (foundNormalForms.has(t1)) {
1169
+ // console.log("found t1", true)
1170
+ normT1 = foundNormalForms.get(t1);
1171
+ }
1172
+ else {
1173
+ // console.log("not found t1", false)
1174
+ normT1 = this.normalize2(t1, relevantTypes, program, checker);
1175
+ foundNormalForms.set(t1, normT1);
1176
+ }
1177
+ if (foundNormalForms.has(t2)) {
1178
+ // console.log("found t2", true)
1179
+ normT2 = foundNormalForms.get(t2);
1180
+ }
1181
+ else {
1182
+ // console.log("not found t2", false)
1183
+ normT2 = this.normalize2(t2, relevantTypes, program, checker);
1184
+ foundNormalForms.set(t2, normT2);
1185
+ }
1186
+ // const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
1187
+ // const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
1188
+ // console.log(`normal forms: ${normT1} {}{} ${normT2}`)
386
1189
  return normT1 === normT2;
387
1190
  }
388
1191
  // return the normal form given a type span and a set of relevant types
@@ -395,9 +1198,10 @@ class TypeScriptDriver {
395
1198
  if (typeSpan.slice(typeSpan.length - 2) == " =") {
396
1199
  typeSpan = typeSpan.slice(0, typeSpan.length - 2);
397
1200
  }
398
- if (typeSpan.slice(typeSpan.length - 1) == ";") {
399
- typeSpan = typeSpan.slice(0, typeSpan.length - 1);
400
- }
1201
+ // DEBUG
1202
+ // if (typeSpan.slice(typeSpan.length - 1) == ";") {
1203
+ // typeSpan = typeSpan.slice(0, typeSpan.length - 1);
1204
+ // }
401
1205
  // console.log(typeSpan)
402
1206
  let normalForm = "";
403
1207
  // pattern matching for typeSpan
@@ -465,7 +1269,7 @@ class TypeScriptDriver {
465
1269
  return typeSpan;
466
1270
  }
467
1271
  }
468
- normalize2(typeSpan, relevantTypes) {
1272
+ normalize2(typeSpan, relevantTypes, program, checker) {
469
1273
  // NOTE: BUGFIX
470
1274
  // console.log(`normalize: ${typeSpan}`)
471
1275
  // console.log(`normalize: ${typeSpan == undefined}`)
@@ -477,7 +1281,8 @@ class TypeScriptDriver {
477
1281
  }
478
1282
  // console.log(typeSpan)
479
1283
  let normalForm = "";
480
- const analysisResult = this.typeChecker.analyzeTypeString(typeSpan);
1284
+ const analysisResult = this.typeChecker.analyzeTypeString(typeSpan, program, checker);
1285
+ // console.dir(analysisResult, { depth: null })
481
1286
  // pattern matching for typeSpan
482
1287
  // if (this.typeChecker.isPrimitive(typeSpan)) {
483
1288
  if (this.typeChecker.isPrimitive2(analysisResult)) {
@@ -491,7 +1296,7 @@ class TypeScriptDriver {
491
1296
  elements.forEach(element => {
492
1297
  if (element !== "") {
493
1298
  const kv = element.split(": ");
494
- normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize(kv[1], relevantTypes);
1299
+ normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes, program, checker);
495
1300
  normalForm += "; ";
496
1301
  }
497
1302
  });
@@ -505,7 +1310,7 @@ class TypeScriptDriver {
505
1310
  const elements = this.typeChecker.parseTypeArrayString(typeSpan);
506
1311
  normalForm += "[";
507
1312
  elements.forEach((element, i) => {
508
- normalForm += this.normalize(element, relevantTypes);
1313
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
509
1314
  if (i < elements.length - 1) {
510
1315
  normalForm += ", ";
511
1316
  }
@@ -519,7 +1324,7 @@ class TypeScriptDriver {
519
1324
  const elements = typeSpan.split(" | ");
520
1325
  elements.forEach((element, i) => {
521
1326
  normalForm += "(";
522
- normalForm += this.normalize(element, relevantTypes);
1327
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
523
1328
  normalForm += ")";
524
1329
  if (i < elements.length - 1) {
525
1330
  normalForm += " | ";
@@ -531,17 +1336,26 @@ class TypeScriptDriver {
531
1336
  else if (this.typeChecker.isArray2(analysisResult)) {
532
1337
  // console.log(`isArray: ${typeSpan}`)
533
1338
  const element = typeSpan.split("[]")[0];
534
- normalForm += this.normalize(element, relevantTypes);
1339
+ normalForm += this.normalize2(element, relevantTypes, program, checker);
535
1340
  normalForm += "[]";
536
1341
  return normalForm;
537
1342
  // } else if (this.typeChecker.isTypeAlias(typeSpan)) {
538
1343
  }
539
1344
  else if (this.typeChecker.isTypeAlias2(analysisResult)) {
540
- const typ = relevantTypes.get(typeSpan).typeSpan.split(" = ")[1];
1345
+ // console.log("ALERT!!!!!!")
1346
+ // console.dir(relevantTypes, { depth: null })
1347
+ // console.dir(analysisResult, { depth: null })
1348
+ // console.log("typeSpan:", typeSpan)
1349
+ // console.log("analysis:", analysisResult.text)
1350
+ // console.log(relevantTypes.get(analysisResult.text))
1351
+ if (!relevantTypes.has(analysisResult.text)) {
1352
+ return typeSpan;
1353
+ }
1354
+ const typ = relevantTypes.get(analysisResult.text).typeSpan.split(" = ")[1];
541
1355
  if (typ === undefined) {
542
1356
  return typeSpan;
543
1357
  }
544
- normalForm += this.normalize(typ, relevantTypes);
1358
+ normalForm += this.normalize2(typ, relevantTypes, program, checker);
545
1359
  return normalForm;
546
1360
  }
547
1361
  else {
@@ -549,5 +1363,262 @@ class TypeScriptDriver {
549
1363
  return typeSpan;
550
1364
  }
551
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
+ }
552
1623
  }
553
1624
  exports.TypeScriptDriver = TypeScriptDriver;