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