@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.
- package/dist/src/app.d.ts +7 -4
- package/dist/src/app.js +319 -97
- package/dist/src/ast.d.ts +6 -0
- package/dist/src/ast.js +80 -0
- package/dist/src/codeql.js +17 -7
- package/dist/src/constants.js +17 -7
- package/dist/src/core.d.ts +0 -1
- package/dist/src/core.js +17 -7
- package/dist/src/main.d.ts +8 -2
- package/dist/src/main.js +53 -13
- package/dist/src/ocaml-driver.d.ts +55 -5
- package/dist/src/ocaml-driver.js +296 -191
- package/dist/src/ocaml-utils/_build/default/test_parser.bc.cjs +194658 -0
- package/dist/src/runner.js +143 -10
- package/dist/src/tree-sitter-files/queries/hole-queries/typescript.scm +36 -0
- package/dist/src/tree-sitter-files/queries/relevant-headers-queries/typescript-get-toplevel-headers.scm +22 -0
- package/dist/src/tree-sitter-files/queries/relevant-types-queries/typescript-extract-identifiers.scm +10 -0
- package/dist/src/tree-sitter-files/queries/relevant-types-queries/typescript-find-typedecl-given-typeidentifier.scm +11 -0
- package/dist/src/tree-sitter-files/wasms/tree-sitter-ocaml.wasm +0 -0
- package/dist/src/tree-sitter-files/wasms/tree-sitter-typescript.wasm +0 -0
- package/dist/src/tree-sitter-files/wasms/tree-sitter.wasm +0 -0
- package/dist/src/tree-sitter.d.ts +40 -0
- package/dist/src/tree-sitter.js +311 -0
- package/dist/src/types.d.ts +80 -9
- package/dist/src/types.js +1 -6
- package/dist/src/typescript-driver.d.ts +86 -12
- package/dist/src/typescript-driver.js +1276 -205
- package/dist/src/typescript-type-checker.d.ts +22 -2
- package/dist/src/typescript-type-checker.js +290 -9
- package/dist/src/utils.d.ts +11 -1
- package/dist/src/utils.js +87 -10
- package/dist/src/vscode-ide.d.ts +29 -0
- package/dist/src/vscode-ide.js +161 -0
- 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 (
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
38
|
-
|
39
|
-
'
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
'
|
44
|
-
'
|
45
|
-
|
46
|
-
|
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
|
-
'
|
49
|
-
|
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
|
-
'
|
52
|
-
'
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
'dynamicRegistration': true,
|
59
|
-
'
|
60
|
-
|
61
|
-
}
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
'
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
'
|
80
|
-
'
|
81
|
-
'dynamicRegistration': true,
|
82
|
-
'willSave': true,
|
83
|
-
'willSaveWaitUntil': true
|
123
|
+
'general': {
|
124
|
+
'positionEncodings': ['utf-8']
|
84
125
|
},
|
85
|
-
|
86
|
-
|
87
|
-
}
|
88
|
-
'
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
//
|
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
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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 =
|
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:
|
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
|
-
|
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
|
-
|
203
|
-
|
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
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
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
|
-
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
//
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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}
|
1163
|
+
// console.log(`isTypeEquivalent: ${t1} {}{} ${t2}`)
|
382
1164
|
// console.log(t1 == undefined)
|
383
1165
|
// console.log(t2 == undefined)
|
384
|
-
|
385
|
-
|
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
|
-
|
399
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
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;
|