@jpoly1219/context-extractor 0.2.7 → 0.2.8
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 +0 -1
- package/dist/src/app.js +29 -10
- package/dist/src/main.js +2 -0
- package/dist/src/ocaml-driver.d.ts +1 -1
- package/dist/src/ocaml-driver.js +3 -3
- package/dist/src/runner.js +26 -8
- package/dist/src/types.d.ts +2 -1
- package/dist/src/typescript-driver.d.ts +11 -5
- package/dist/src/typescript-driver.js +227 -42
- package/dist/src/typescript-type-checker.d.ts +16 -0
- package/dist/src/typescript-type-checker.js +69 -0
- package/package.json +1 -1
package/dist/src/app.d.ts
CHANGED
package/dist/src/app.js
CHANGED
@@ -40,6 +40,13 @@ const typescript_driver_1 = require("./typescript-driver");
|
|
40
40
|
const ocaml_driver_1 = require("./ocaml-driver");
|
41
41
|
const utils_1 = require("./utils");
|
42
42
|
class App {
|
43
|
+
// Optional timeout for forced termination
|
44
|
+
// private timeout = setTimeout(() => {
|
45
|
+
// if (!this.languageServer.killed) {
|
46
|
+
// console.log('Forcibly killing the process...');
|
47
|
+
// this.languageServer.kill('SIGKILL');
|
48
|
+
// }
|
49
|
+
// }, 5000);
|
43
50
|
constructor(language, sketchPath, repoPath) {
|
44
51
|
// private result: {
|
45
52
|
// hole: string;
|
@@ -47,13 +54,6 @@ class App {
|
|
47
54
|
// relevantHeaders: string[];
|
48
55
|
// } | null = null;
|
49
56
|
this.result = null;
|
50
|
-
// Optional timeout for forced termination
|
51
|
-
this.timeout = setTimeout(() => {
|
52
|
-
if (!this.languageServer.killed) {
|
53
|
-
console.log('Forcibly killing the process...');
|
54
|
-
this.languageServer.kill('SIGKILL');
|
55
|
-
}
|
56
|
-
}, 5000);
|
57
57
|
this.language = language;
|
58
58
|
this.sketchPath = sketchPath;
|
59
59
|
this.repoPath = repoPath;
|
@@ -93,6 +93,7 @@ class App {
|
|
93
93
|
const c = new main_1.LspClient(e);
|
94
94
|
this.languageServer = r;
|
95
95
|
this.lspClient = c;
|
96
|
+
// console.log(r.pid)
|
96
97
|
this.languageServer.on('close', (code) => {
|
97
98
|
if (code !== 0) {
|
98
99
|
console.log(`ls process exited with code ${code}`);
|
@@ -100,7 +101,7 @@ class App {
|
|
100
101
|
});
|
101
102
|
// Clear timeout once the process exits
|
102
103
|
this.languageServer.on('exit', () => {
|
103
|
-
clearTimeout(this.timeout);
|
104
|
+
// clearTimeout(this.timeout);
|
104
105
|
console.log('Process terminated cleanly.');
|
105
106
|
});
|
106
107
|
// const logFile = fs.createWriteStream("log.txt");
|
@@ -113,9 +114,17 @@ class App {
|
|
113
114
|
// const outputFile = fs.createWriteStream("output.txt");
|
114
115
|
try {
|
115
116
|
await this.init();
|
117
|
+
// console.time("getHoleContext");
|
116
118
|
const holeContext = await this.languageDriver.getHoleContext(this.lspClient, this.sketchPath);
|
117
|
-
|
119
|
+
// console.timeEnd("getHoleContext");
|
120
|
+
// console.time("extractRelevantTypes");
|
121
|
+
const relevantTypes = await this.languageDriver.extractRelevantTypes(this.lspClient,
|
122
|
+
// NOTE: sometimes fullHoverResult isn't representative of the actual file contents, especially with generic functions.
|
123
|
+
holeContext.trueHoleFunction ? holeContext.trueHoleFunction : holeContext.fullHoverResult, holeContext.functionName, holeContext.range.start.line, new Map(), holeContext.source, new Map());
|
124
|
+
// console.timeEnd("extractRelevantTypes");
|
125
|
+
// console.dir(relevantTypes, { depth: null })
|
118
126
|
// Postprocess the map.
|
127
|
+
// console.time("extractRelevantTypes postprocess");
|
119
128
|
if (this.language === types_1.Language.TypeScript) {
|
120
129
|
relevantTypes.delete("_()");
|
121
130
|
for (const [k, { typeSpan: v, sourceFile: src }] of relevantTypes.entries()) {
|
@@ -125,7 +134,8 @@ class App {
|
|
125
134
|
else if (this.language === types_1.Language.OCaml) {
|
126
135
|
relevantTypes.delete("_");
|
127
136
|
}
|
128
|
-
// console.
|
137
|
+
// console.timeEnd("extractRelevantTypes postprocess");
|
138
|
+
// console.time("extractRelevantHeaders repo");
|
129
139
|
let repo = [];
|
130
140
|
if (this.language === types_1.Language.TypeScript) {
|
131
141
|
repo = (0, utils_1.getAllTSFiles)(this.repoPath);
|
@@ -133,9 +143,15 @@ class App {
|
|
133
143
|
else if (this.language === types_1.Language.OCaml) {
|
134
144
|
repo = (0, utils_1.getAllOCamlFiles)(this.repoPath);
|
135
145
|
}
|
146
|
+
// console.timeEnd("extractRelevantHeaders repo");
|
147
|
+
// console.time("extractRelevantHeaders");
|
136
148
|
const relevantHeaders = await this.languageDriver.extractRelevantHeaders(this.lspClient, repo, relevantTypes, holeContext.functionTypeSpan);
|
149
|
+
// const relevantHeaders: { typeSpan: string, sourceFile: string }[] = []
|
150
|
+
// console.timeEnd("extractRelevantHeaders");
|
137
151
|
// console.log(relevantHeaders)
|
152
|
+
// console.log(relevantHeaders.size)
|
138
153
|
// Postprocess the map.
|
154
|
+
// console.time("extractRelevantHaders postprocess");
|
139
155
|
if (this.language === types_1.Language.TypeScript) {
|
140
156
|
relevantTypes.delete("");
|
141
157
|
for (const [k, { typeSpan: v, sourceFile: src }] of relevantTypes.entries()) {
|
@@ -145,6 +161,8 @@ class App {
|
|
145
161
|
obj.typeSpan += ";";
|
146
162
|
}
|
147
163
|
}
|
164
|
+
// console.timeEnd("extractRelevantHeaders postprocess");
|
165
|
+
// console.time("toReturn");
|
148
166
|
const relevantTypesToReturn = new Map();
|
149
167
|
relevantTypes.forEach(({ typeSpan: v, sourceFile: src }, _) => {
|
150
168
|
if (relevantTypesToReturn.has(src)) {
|
@@ -169,6 +187,7 @@ class App {
|
|
169
187
|
relevantHeadersToReturn.set(src, [v]);
|
170
188
|
}
|
171
189
|
});
|
190
|
+
// console.timeEnd("toReturn");
|
172
191
|
this.result = {
|
173
192
|
holeType: holeContext.functionTypeSpan,
|
174
193
|
relevantTypes: relevantTypesToReturn,
|
package/dist/src/main.js
CHANGED
@@ -229,10 +229,12 @@ const extractWithCodeQL = async (sketchPath) => {
|
|
229
229
|
};
|
230
230
|
exports.extractWithCodeQL = extractWithCodeQL;
|
231
231
|
const extractContext = async (language, sketchPath, repoPath) => {
|
232
|
+
// console.time("extractContext")
|
232
233
|
const app = new app_1.App(language, sketchPath, repoPath);
|
233
234
|
await app.run();
|
234
235
|
const res = app.getSavedResult();
|
235
236
|
app.close();
|
237
|
+
// console.timeEnd("extractContext")
|
236
238
|
return res;
|
237
239
|
// if (!getCompletion) {
|
238
240
|
// await app.close()
|
@@ -15,7 +15,7 @@ export declare class OcamlDriver implements LanguageDriver {
|
|
15
15
|
range: Range;
|
16
16
|
source: string;
|
17
17
|
}>;
|
18
|
-
extractRelevantTypes(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number,
|
18
|
+
extractRelevantTypes(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, currentFile: string): Promise<Map<string, TypeSpanAndSourceFile>>;
|
19
19
|
extractRelevantHeaders(lspClient: LspClient, sources: string[], relevantTypes: Map<string, TypeSpanAndSourceFile>, holeType: string): Promise<Set<TypeSpanAndSourceFile>>;
|
20
20
|
extractHeaderTypeSpans(lspClient: LspClient, preludeFilePath: string): Promise<{
|
21
21
|
identifier: string;
|
package/dist/src/ocaml-driver.js
CHANGED
@@ -172,12 +172,12 @@ class OcamlDriver {
|
|
172
172
|
source: `file://${sketchFilePath}`
|
173
173
|
};
|
174
174
|
}
|
175
|
-
async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine,
|
175
|
+
async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile) {
|
176
176
|
if (!foundSoFar.has(typeName)) {
|
177
177
|
foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
|
178
178
|
// outputFile.write(`${fullHoverResult};\n`);
|
179
179
|
const content = fs.readFileSync(currentFile.slice(7), "utf8");
|
180
|
-
for (let linePos = startLine; linePos <=
|
180
|
+
for (let linePos = startLine; linePos <= fullHoverResult.length; ++linePos) {
|
181
181
|
const numOfCharsInLine = parseInt((0, child_process_1.execSync)(`wc -m <<< "${content.split("\n")[linePos]}"`, { shell: "/bin/bash" }).toString());
|
182
182
|
for (let charPos = 0; charPos < numOfCharsInLine; ++charPos) {
|
183
183
|
try {
|
@@ -224,7 +224,7 @@ class OcamlDriver {
|
|
224
224
|
// TODO: this can potentially be its own method. the driver would require some way to get type context.
|
225
225
|
// potentially, this type checker can be its own class.
|
226
226
|
const identifier = this.typeChecker.getIdentifierFromDecl(snippetInRange);
|
227
|
-
await this.extractRelevantTypes(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line,
|
227
|
+
await this.extractRelevantTypes(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, foundSoFar, typeDefinitionResult[0].uri);
|
228
228
|
}
|
229
229
|
}
|
230
230
|
}
|
package/dist/src/runner.js
CHANGED
@@ -10,16 +10,34 @@ const types_1 = require("./types");
|
|
10
10
|
(async () => {
|
11
11
|
try {
|
12
12
|
let x;
|
13
|
-
x = await
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
console.dir(x, { depth: null })
|
13
|
+
// x = await extractContext(
|
14
|
+
// Language.TypeScript,
|
15
|
+
// "/home/jacob/projects/context-extractor/targets/todo/sketch.ts",
|
16
|
+
// "/home/jacob/projects/context-extractor/targets/todo/",
|
17
|
+
// )
|
18
|
+
// console.dir(x, { depth: null })
|
19
|
+
//
|
20
|
+
// x = await extractContext(
|
21
|
+
// Language.TypeScript,
|
22
|
+
// "/home/jacob/projects/context-extractor/targets/playlist/sketch.ts",
|
23
|
+
// "/home/jacob/projects/context-extractor/targets/playlist/",
|
24
|
+
// )
|
25
|
+
// console.dir(x, { depth: null })
|
26
|
+
//
|
27
|
+
// x = await extractContext(
|
28
|
+
// Language.TypeScript,
|
29
|
+
// "/home/jacob/projects/context-extractor/targets/passwords/sketch.ts",
|
30
|
+
// "/home/jacob/projects/context-extractor/targets/passwords/",
|
31
|
+
// )
|
32
|
+
// console.dir(x, { depth: null })
|
19
33
|
x = await (0, main_1.extractContext)(types_1.Language.TypeScript, "/home/jacob/projects/context-extractor/targets/booking/sketch.ts", "/home/jacob/projects/context-extractor/targets/booking/");
|
20
34
|
console.dir(x, { depth: null });
|
21
|
-
x = await
|
22
|
-
|
35
|
+
// x = await extractContext(
|
36
|
+
// Language.TypeScript,
|
37
|
+
// "/home/jacob/projects/context-extractor/targets/emojipaint/sketch.ts",
|
38
|
+
// "/home/jacob/projects/context-extractor/targets/emojipaint/",
|
39
|
+
// )
|
40
|
+
// console.dir(x, { depth: null })
|
23
41
|
// const y = await completeWithLLM(
|
24
42
|
// x!,
|
25
43
|
// Language.TypeScript,
|
package/dist/src/types.d.ts
CHANGED
@@ -72,8 +72,9 @@ interface LanguageDriver {
|
|
72
72
|
holeTypeDefCharPos: number;
|
73
73
|
range: Range;
|
74
74
|
source: string;
|
75
|
+
trueHoleFunction?: string;
|
75
76
|
}>;
|
76
|
-
extractRelevantTypes: (lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number,
|
77
|
+
extractRelevantTypes: (lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, currentFile: string, foundContents: Map<string, string>) => Promise<Map<string, TypeSpanAndSourceFile>>;
|
77
78
|
extractRelevantHeaders: (lspClient: LspClient, sources: string[], relevantTypes: Map<string, TypeSpanAndSourceFile>, holeType: string) => Promise<Set<TypeSpanAndSourceFile>>;
|
78
79
|
}
|
79
80
|
type Filepath = string;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { LspClient, Range } from "../ts-lsp-client-dist/src/main";
|
1
|
+
import { LspClient, Location, Range, SymbolInformation } from "../ts-lsp-client-dist/src/main";
|
2
2
|
import { LanguageDriver, TypeSpanAndSourceFile } from "./types";
|
3
3
|
import { TypeScriptTypeChecker } from "./typescript-type-checker";
|
4
4
|
export declare class TypeScriptDriver implements LanguageDriver {
|
@@ -14,14 +14,20 @@ export declare class TypeScriptDriver implements LanguageDriver {
|
|
14
14
|
holeTypeDefCharPos: number;
|
15
15
|
range: Range;
|
16
16
|
source: string;
|
17
|
+
trueHoleFunction: string;
|
17
18
|
}>;
|
18
|
-
|
19
|
-
currentFile: string): Promise<Map<string, TypeSpanAndSourceFile>>;
|
19
|
+
extractRelevantTypes1(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, endLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, // identifier -> [full hover result, source]
|
20
|
+
currentFile: string, foundTypeDefinitions: Map<string, Location[]>, foundSymbols: Map<string, SymbolInformation[]>): Promise<Map<string, TypeSpanAndSourceFile>>;
|
21
|
+
extractRelevantTypes(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, // identifier -> [full hover result, source]
|
22
|
+
currentFile: string, foundContents: Map<string, string>): Promise<Map<string, TypeSpanAndSourceFile>>;
|
23
|
+
extractRelevantTypesHelper(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, // identifier -> [full hover result, source]
|
24
|
+
currentFile: string, foundContents: Map<string, string>, // uri -> contents
|
25
|
+
layer: number): Promise<void>;
|
20
26
|
extractRelevantHeaders(_: LspClient, sources: string[], relevantTypes: Map<string, TypeSpanAndSourceFile>, holeType: string): Promise<Set<TypeSpanAndSourceFile>>;
|
21
27
|
generateTargetTypes(relevantTypes: Map<string, TypeSpanAndSourceFile>, holeType: string): Set<string>;
|
22
28
|
generateTargetTypesHelper(relevantTypes: Map<string, TypeSpanAndSourceFile>, currType: string, targetTypes: Set<string>): void;
|
23
|
-
extractRelevantHeadersHelper(typeSpan: string, targetTypes: Set<string>, relevantTypes: Map<string, TypeSpanAndSourceFile>, relevantContext: Set<TypeSpanAndSourceFile>, line: string, source: string): void;
|
24
|
-
isTypeEquivalent(t1: string, t2: string, relevantTypes: Map<string, TypeSpanAndSourceFile>): boolean;
|
29
|
+
extractRelevantHeadersHelper(typeSpan: string, targetTypes: Set<string>, relevantTypes: Map<string, TypeSpanAndSourceFile>, relevantContext: Set<TypeSpanAndSourceFile>, line: string, source: string, relevantContextMap: Map<string, TypeSpanAndSourceFile>, tag: boolean, trace: string[], foundNormalForms: Map<string, string>): void;
|
30
|
+
isTypeEquivalent(t1: string, t2: string, relevantTypes: Map<string, TypeSpanAndSourceFile>, foundNormalForms: Map<string, string>): boolean;
|
25
31
|
normalize(typeSpan: string, relevantTypes: Map<string, TypeSpanAndSourceFile>): string;
|
26
32
|
normalize2(typeSpan: string, relevantTypes: Map<string, TypeSpanAndSourceFile>): string;
|
27
33
|
}
|
@@ -196,18 +196,26 @@ class TypeScriptDriver {
|
|
196
196
|
holeTypeDefCharPos: "declare function _(): ".length,
|
197
197
|
// range: { start: { line: 0, character: 0 }, end: { line: 0, character: 52 } }
|
198
198
|
range: sketchSymbol[0].location.range,
|
199
|
-
source: `file://${injectedSketchFilePath}
|
199
|
+
source: `file://${injectedSketchFilePath}`,
|
200
|
+
trueHoleFunction: trueHoleFunction
|
200
201
|
};
|
201
202
|
}
|
202
|
-
async
|
203
|
-
currentFile
|
203
|
+
async extractRelevantTypes1(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
|
204
|
+
currentFile, foundTypeDefinitions, foundSymbols
|
205
|
+
// outputFile: fs.WriteStream,
|
206
|
+
) {
|
204
207
|
if (!foundSoFar.has(typeName)) {
|
205
208
|
foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
|
206
209
|
// outputFile.write(`${fullHoverResult};\n`);
|
207
210
|
const content = fs.readFileSync(currentFile.slice(7), "utf8");
|
208
211
|
for (let linePos = startLine; linePos <= endLine; ++linePos) {
|
212
|
+
// TODO: use a platform-agnostic command here
|
209
213
|
const numOfCharsInLine = parseInt((0, child_process_1.execSync)(`wc -m <<< "${content.split("\n")[linePos]}"`, { shell: "/bin/bash" }).toString());
|
210
|
-
|
214
|
+
const numOfCharsInLine2 = content.split("\n")[linePos].length;
|
215
|
+
const numOfCharsInLine3 = [...content.split("\n")[linePos]].map(c => c.codePointAt(0)).length;
|
216
|
+
// console.log(numOfCharsInLine === numOfCharsInLine2, content.split("\n")[linePos], numOfCharsInLine, numOfCharsInLine2, numOfCharsInLine3)
|
217
|
+
// console.time(`===loop ${content.split("\n")[linePos]}===`);
|
218
|
+
for (let charPos = 0; charPos < numOfCharsInLine2; ++charPos) {
|
211
219
|
try {
|
212
220
|
const typeDefinitionResult = await lspClient.typeDefinition({
|
213
221
|
textDocument: {
|
@@ -218,41 +226,162 @@ class TypeScriptDriver {
|
|
218
226
|
line: linePos
|
219
227
|
}
|
220
228
|
});
|
229
|
+
// if (content.split("\n")[linePos] === `type Action = AddBooking | CancelBooking | ClearBookings;`) {
|
230
|
+
// console.dir(typeDefinitionResult, { depth: null })
|
231
|
+
// console.log(charPos)
|
232
|
+
// }
|
221
233
|
if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
234
|
+
const tdResultStr = JSON.stringify(typeDefinitionResult);
|
235
|
+
if (!foundTypeDefinitions.has(tdResultStr)) {
|
236
|
+
foundTypeDefinitions.set(tdResultStr, typeDefinitionResult);
|
237
|
+
// Use documentSymbol instead of hover.
|
238
|
+
// This prevents type alias "squashing" done by tsserver.
|
239
|
+
// This also allows for grabbing the entire definition range and not just the symbol range.
|
240
|
+
// PERF: feels like this could be memoized to improve performance.
|
241
|
+
// console.time("docSymbol")
|
242
|
+
const tdUri = typeDefinitionResult[0].uri;
|
243
|
+
let documentSymbolResult;
|
244
|
+
if (foundSymbols.has(tdUri)) {
|
245
|
+
documentSymbolResult = foundSymbols.get(tdUri);
|
246
|
+
}
|
247
|
+
else {
|
248
|
+
documentSymbolResult = await lspClient.documentSymbol({
|
249
|
+
textDocument: {
|
250
|
+
uri: typeDefinitionResult[0].uri
|
251
|
+
}
|
252
|
+
});
|
253
|
+
foundSymbols.set(tdUri, documentSymbolResult);
|
254
|
+
}
|
255
|
+
// console.timeEnd("docSymbol")
|
256
|
+
// console.time("dsMap")
|
257
|
+
const dsMap = documentSymbolResult.reduce((m, obj) => {
|
258
|
+
m.set(obj.location.range.start.line, obj.location.range);
|
259
|
+
return m;
|
260
|
+
}, new Map());
|
261
|
+
// console.timeEnd("dsMap")
|
262
|
+
// console.log("\n")
|
263
|
+
// console.dir(typeDefinitionResult, { depth: null })
|
264
|
+
// console.dir(documentSymbolResult, { depth: null })
|
265
|
+
// console.log("\n")
|
266
|
+
// grab if the line number of typeDefinitionResult and documentSymbolResult matches
|
267
|
+
// const dsMap = documentSymbolResult!.reduce((m, obj) => {
|
268
|
+
// m.set((obj as SymbolInformation).location.range.start.line, (obj as SymbolInformation).location.range as unknown as Range);
|
269
|
+
// return m;
|
270
|
+
// }, new Map<number, Range>());
|
271
|
+
const matchingSymbolRange = dsMap.get(typeDefinitionResult[0].range.start.line);
|
272
|
+
if (matchingSymbolRange) {
|
273
|
+
const snippetInRange = (0, utils_1.extractSnippet)(fs.readFileSync(typeDefinitionResult[0].uri.slice(7)).toString("utf8"), matchingSymbolRange.start, matchingSymbolRange.end);
|
274
|
+
// TODO: this can potentially be its own method. the driver would require some way to get type context.
|
275
|
+
// potentially, this type checker can be its own class.
|
276
|
+
const identifier = this.typeChecker.getIdentifierFromDecl(snippetInRange);
|
277
|
+
await this.extractRelevantTypes1(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, matchingSymbolRange.end.line, foundSoFar, typeDefinitionResult[0].uri, foundTypeDefinitions, foundSymbols
|
278
|
+
// outputFile,
|
279
|
+
);
|
229
280
|
}
|
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
281
|
}
|
244
282
|
}
|
283
|
+
// else {
|
284
|
+
// console.log(`already found ${tdResultStr}!`)
|
285
|
+
// }
|
245
286
|
}
|
246
287
|
catch (err) {
|
247
288
|
console.log(`${err}`);
|
248
289
|
}
|
249
290
|
}
|
291
|
+
// console.timeEnd(`===loop ${content.split("\n")[linePos]}===`);
|
250
292
|
}
|
251
293
|
}
|
252
294
|
return foundSoFar;
|
253
295
|
}
|
296
|
+
async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
|
297
|
+
currentFile, foundContents // uri -> contents
|
298
|
+
) {
|
299
|
+
const content = fs.readFileSync(currentFile.slice(7), "utf8");
|
300
|
+
// console.log(content)
|
301
|
+
await this.extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, currentFile, foundContents, 0);
|
302
|
+
return foundSoFar;
|
303
|
+
}
|
304
|
+
async extractRelevantTypesHelper(lspClient, fullHoverResult, typeName, startLine, foundSoFar, // identifier -> [full hover result, source]
|
305
|
+
currentFile, foundContents, // uri -> contents
|
306
|
+
layer) {
|
307
|
+
// console.log("===", fullHoverResult, layer, startLine, "===")
|
308
|
+
// Split the type span into identifiers, where each include the text, line number, and character range.
|
309
|
+
// For each identifier, invoke go to type definition.
|
310
|
+
if (!foundSoFar.has(typeName)) {
|
311
|
+
foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
|
312
|
+
const identifiers = this.typeChecker.extractIdentifiers(fullHoverResult);
|
313
|
+
// console.dir(identifiers, { depth: null })
|
314
|
+
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
|
+
if (!foundSoFar.has(identifier.name)) {
|
325
|
+
try {
|
326
|
+
const typeDefinitionResult = await lspClient.typeDefinition({
|
327
|
+
textDocument: {
|
328
|
+
uri: currentFile
|
329
|
+
},
|
330
|
+
position: {
|
331
|
+
character: identifier.start,
|
332
|
+
line: startLine + identifier.line - 1 // startLine is already 1-indexed
|
333
|
+
}
|
334
|
+
});
|
335
|
+
// if (identifier.name == "Model") {
|
336
|
+
// console.dir(typeDefinitionResult, { depth: null })
|
337
|
+
// }
|
338
|
+
if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
|
339
|
+
const tdLocation = typeDefinitionResult[0];
|
340
|
+
let content = "";
|
341
|
+
if (foundContents.has(tdLocation.uri.slice(7))) {
|
342
|
+
content = foundContents.get(tdLocation.uri.slice(7));
|
343
|
+
}
|
344
|
+
else {
|
345
|
+
content = fs.readFileSync(tdLocation.uri.slice(7), "utf8");
|
346
|
+
foundContents.set(tdLocation.uri.slice(7), content);
|
347
|
+
}
|
348
|
+
const decl = this.typeChecker.findDeclarationForIdentifier(content, tdLocation.range.start.line, tdLocation.range.start.character, tdLocation.range.end.character);
|
349
|
+
if (decl) {
|
350
|
+
const ident = this.typeChecker.getIdentifierFromDecl(decl);
|
351
|
+
// console.log(ident == identifier.name, ident, identifier.name, decl)
|
352
|
+
// console.log(`Decl: ${decl} || Identifier: ${ident}`)
|
353
|
+
// console.timeEnd(`loop ${identifier.name} layer ${layer}`)
|
354
|
+
await this.extractRelevantTypesHelper(lspClient, decl, ident, tdLocation.range.start.line, foundSoFar, tdLocation.uri, foundContents, layer + 1);
|
355
|
+
}
|
356
|
+
else {
|
357
|
+
console.log("decl not found");
|
358
|
+
// console.timeEnd(`loop ${identifier.name} layer ${layer}`)
|
359
|
+
}
|
360
|
+
}
|
361
|
+
else {
|
362
|
+
console.log("td not found");
|
363
|
+
// console.dir(typeDefinitionResult, { depth: null })
|
364
|
+
}
|
365
|
+
}
|
366
|
+
catch (err) {
|
367
|
+
console.log(err);
|
368
|
+
}
|
369
|
+
}
|
370
|
+
else {
|
371
|
+
// console.timeEnd(`loop ${identifier.name} layer ${layer}`)
|
372
|
+
}
|
373
|
+
}
|
374
|
+
}
|
375
|
+
}
|
254
376
|
async extractRelevantHeaders(_, sources, relevantTypes, holeType) {
|
377
|
+
// console.time("extractRelevantHeaders");
|
255
378
|
const relevantContext = new Set();
|
379
|
+
// NOTE: This is necessary because TypeScript sucks.
|
380
|
+
// There is no way to compare objects by value,
|
381
|
+
// so sets of objects starts to accumulate tons of duplicates.
|
382
|
+
const relevantContextMap = new Map();
|
383
|
+
const trace = [];
|
384
|
+
const foundNormalForms = new Map();
|
256
385
|
const targetTypes = this.generateTargetTypes(relevantTypes, holeType);
|
257
386
|
// only consider lines that start with let or const
|
258
387
|
for (const source of sources) {
|
@@ -262,24 +391,41 @@ class TypeScriptDriver {
|
|
262
391
|
});
|
263
392
|
// check for relationship between each line and relevant types
|
264
393
|
filteredLines.forEach(line => {
|
394
|
+
// console.time(`helper, line: ${line}`);
|
395
|
+
let tag = false;
|
396
|
+
if (line === `const initFormState: [[Weekday, TimeOfDay], string] = [["M", "AM"], ""];`) {
|
397
|
+
tag = true;
|
398
|
+
}
|
265
399
|
// TODO: Use the compiler API to split this.
|
266
400
|
const splittedLine = line.split(" = ")[0];
|
267
401
|
const typeSpanPattern = /(^[^:]*: )(.+)/;
|
268
402
|
const regexMatch = splittedLine.match(typeSpanPattern);
|
269
403
|
if (regexMatch) {
|
270
404
|
const returnTypeSpan = regexMatch[2];
|
405
|
+
// console.log(`returnTypeSpan: ${returnTypeSpan}`)
|
406
|
+
// const typeAnalysisResult = this.typeChecker.analyzeTypeString(returnTypeSpan);
|
407
|
+
// console.log(`typeAnalysisResult: ${JSON.stringify(typeAnalysisResult, null, 2)}`)
|
271
408
|
if (!this.typeChecker.isPrimitive(returnTypeSpan.split(" => ")[1])) {
|
272
|
-
this.extractRelevantHeadersHelper(returnTypeSpan, targetTypes, relevantTypes, relevantContext, splittedLine, source);
|
409
|
+
this.extractRelevantHeadersHelper(returnTypeSpan, targetTypes, relevantTypes, relevantContext, splittedLine, source, relevantContextMap, tag, trace, foundNormalForms);
|
273
410
|
}
|
274
411
|
}
|
412
|
+
// console.timeEnd(`helper, line: ${line}`);
|
275
413
|
});
|
276
414
|
}
|
415
|
+
// console.log(JSON.stringify(relevantContextMap, null, 2))
|
416
|
+
// console.log(relevantContextMap.keys())
|
417
|
+
for (const v of relevantContextMap.values()) {
|
418
|
+
relevantContext.add(v);
|
419
|
+
}
|
420
|
+
// console.timeEnd("extractRelevantHeaders");
|
277
421
|
return relevantContext;
|
278
422
|
}
|
279
423
|
generateTargetTypes(relevantTypes, holeType) {
|
424
|
+
// console.time("generateTargetTypes");
|
280
425
|
const targetTypes = new Set();
|
281
426
|
targetTypes.add(holeType);
|
282
427
|
this.generateTargetTypesHelper(relevantTypes, holeType, targetTypes);
|
428
|
+
// console.timeEnd("generateTargetTypes");
|
283
429
|
return targetTypes;
|
284
430
|
}
|
285
431
|
// generateTargetTypesHelper(
|
@@ -358,31 +504,62 @@ class TypeScriptDriver {
|
|
358
504
|
// }
|
359
505
|
// });
|
360
506
|
// }
|
361
|
-
extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source) {
|
507
|
+
extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms) {
|
508
|
+
if (tag) {
|
509
|
+
// console.time(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
|
510
|
+
// trace.push(typeSpan)
|
511
|
+
// console.log(trace)
|
512
|
+
}
|
513
|
+
// TODO: this can probably done at the top level.
|
514
|
+
// analyzeTypeString is recursive by itself.
|
362
515
|
const typeAnalysisResult = this.typeChecker.analyzeTypeString(typeSpan);
|
363
516
|
targetTypes.forEach(typ => {
|
364
|
-
if (this.isTypeEquivalent(typeSpan, typ, relevantTypes)) {
|
365
|
-
|
517
|
+
if (this.isTypeEquivalent(typeSpan, typ, relevantTypes, foundNormalForms)) {
|
518
|
+
// NOTE: This checks for dupes. ctx is an object so you need to check for each field.
|
519
|
+
// relevantContext.add({ typeSpan: line, sourceFile: source });
|
520
|
+
const ctx = { typeSpan: line, sourceFile: source };
|
521
|
+
relevantContextMap.set(JSON.stringify(ctx), ctx);
|
366
522
|
}
|
367
523
|
if (this.typeChecker.isFunction2(typeAnalysisResult)) {
|
368
524
|
const rettype = typeAnalysisResult.returnType;
|
369
|
-
this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source);
|
525
|
+
this.extractRelevantHeadersHelper(rettype.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
|
370
526
|
}
|
371
527
|
else if (this.typeChecker.isTuple2(typeAnalysisResult)) {
|
372
528
|
typeAnalysisResult.constituents.forEach(constituent => {
|
373
|
-
this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source);
|
529
|
+
this.extractRelevantHeadersHelper(constituent.text, targetTypes, relevantTypes, relevantContext, line, source, relevantContextMap, tag, trace, foundNormalForms);
|
374
530
|
});
|
375
531
|
}
|
376
532
|
});
|
533
|
+
if (tag) {
|
534
|
+
// console.log("\n\n\n")
|
535
|
+
// console.timeEnd(`extractRelevantHeadersHelper, typeSpan: ${typeSpan}`)
|
536
|
+
}
|
377
537
|
}
|
378
538
|
// two types are equivalent if they have the same normal forms
|
379
|
-
isTypeEquivalent(t1, t2, relevantTypes) {
|
539
|
+
isTypeEquivalent(t1, t2, relevantTypes, foundNormalForms) {
|
380
540
|
// NOTE: BUGFIX
|
381
541
|
// console.log(`isTypeEquivalent: ${t1}, ${t2}`)
|
382
542
|
// console.log(t1 == undefined)
|
383
543
|
// console.log(t2 == undefined)
|
384
|
-
|
385
|
-
|
544
|
+
let normT1 = "";
|
545
|
+
let normT2 = "";
|
546
|
+
if (foundNormalForms.has(t1)) {
|
547
|
+
normT1 = foundNormalForms.get(t1);
|
548
|
+
}
|
549
|
+
else {
|
550
|
+
normT1 = this.normalize2(t1, relevantTypes);
|
551
|
+
foundNormalForms.set(t1, normT1);
|
552
|
+
}
|
553
|
+
if (foundNormalForms.has(t2)) {
|
554
|
+
normT2 = foundNormalForms.get(t2);
|
555
|
+
}
|
556
|
+
else {
|
557
|
+
normT2 = this.normalize2(t2, relevantTypes);
|
558
|
+
foundNormalForms.set(t2, normT2);
|
559
|
+
}
|
560
|
+
// const normT1 = foundNormalForms.has(t1) ? foundNormalForms.get(t1) : this.normalize2(t1, relevantTypes);
|
561
|
+
// const normT2 = foundNormalForms.has(t2) ? foundNormalForms.get(t2) : this.normalize2(t2, relevantTypes);
|
562
|
+
// console.log(normT1, normT2)
|
386
563
|
return normT1 === normT2;
|
387
564
|
}
|
388
565
|
// return the normal form given a type span and a set of relevant types
|
@@ -395,9 +572,10 @@ class TypeScriptDriver {
|
|
395
572
|
if (typeSpan.slice(typeSpan.length - 2) == " =") {
|
396
573
|
typeSpan = typeSpan.slice(0, typeSpan.length - 2);
|
397
574
|
}
|
398
|
-
|
399
|
-
|
400
|
-
|
575
|
+
// DEBUG
|
576
|
+
// if (typeSpan.slice(typeSpan.length - 1) == ";") {
|
577
|
+
// typeSpan = typeSpan.slice(0, typeSpan.length - 1);
|
578
|
+
// }
|
401
579
|
// console.log(typeSpan)
|
402
580
|
let normalForm = "";
|
403
581
|
// pattern matching for typeSpan
|
@@ -478,6 +656,7 @@ class TypeScriptDriver {
|
|
478
656
|
// console.log(typeSpan)
|
479
657
|
let normalForm = "";
|
480
658
|
const analysisResult = this.typeChecker.analyzeTypeString(typeSpan);
|
659
|
+
// console.dir(analysisResult, { depth: null })
|
481
660
|
// pattern matching for typeSpan
|
482
661
|
// if (this.typeChecker.isPrimitive(typeSpan)) {
|
483
662
|
if (this.typeChecker.isPrimitive2(analysisResult)) {
|
@@ -491,7 +670,7 @@ class TypeScriptDriver {
|
|
491
670
|
elements.forEach(element => {
|
492
671
|
if (element !== "") {
|
493
672
|
const kv = element.split(": ");
|
494
|
-
normalForm += kv[0].slice(1, kv[0].length), ": ", this.
|
673
|
+
normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes);
|
495
674
|
normalForm += "; ";
|
496
675
|
}
|
497
676
|
});
|
@@ -505,7 +684,7 @@ class TypeScriptDriver {
|
|
505
684
|
const elements = this.typeChecker.parseTypeArrayString(typeSpan);
|
506
685
|
normalForm += "[";
|
507
686
|
elements.forEach((element, i) => {
|
508
|
-
normalForm += this.
|
687
|
+
normalForm += this.normalize2(element, relevantTypes);
|
509
688
|
if (i < elements.length - 1) {
|
510
689
|
normalForm += ", ";
|
511
690
|
}
|
@@ -519,7 +698,7 @@ class TypeScriptDriver {
|
|
519
698
|
const elements = typeSpan.split(" | ");
|
520
699
|
elements.forEach((element, i) => {
|
521
700
|
normalForm += "(";
|
522
|
-
normalForm += this.
|
701
|
+
normalForm += this.normalize2(element, relevantTypes);
|
523
702
|
normalForm += ")";
|
524
703
|
if (i < elements.length - 1) {
|
525
704
|
normalForm += " | ";
|
@@ -531,17 +710,23 @@ class TypeScriptDriver {
|
|
531
710
|
else if (this.typeChecker.isArray2(analysisResult)) {
|
532
711
|
// console.log(`isArray: ${typeSpan}`)
|
533
712
|
const element = typeSpan.split("[]")[0];
|
534
|
-
normalForm += this.
|
713
|
+
normalForm += this.normalize2(element, relevantTypes);
|
535
714
|
normalForm += "[]";
|
536
715
|
return normalForm;
|
537
716
|
// } else if (this.typeChecker.isTypeAlias(typeSpan)) {
|
538
717
|
}
|
539
718
|
else if (this.typeChecker.isTypeAlias2(analysisResult)) {
|
540
|
-
|
719
|
+
// console.log("ALERT!!!!!!")
|
720
|
+
// console.dir(relevantTypes, { depth: null })
|
721
|
+
// console.dir(analysisResult, { depth: null })
|
722
|
+
// console.log("typeSpan:", typeSpan)
|
723
|
+
// console.log("analysis:", analysisResult.text)
|
724
|
+
// console.log(relevantTypes.get(analysisResult.text))
|
725
|
+
const typ = relevantTypes.get(analysisResult.text).typeSpan.split(" = ")[1];
|
541
726
|
if (typ === undefined) {
|
542
727
|
return typeSpan;
|
543
728
|
}
|
544
|
-
normalForm += this.
|
729
|
+
normalForm += this.normalize2(typ, relevantTypes);
|
545
730
|
return normalForm;
|
546
731
|
}
|
547
732
|
else {
|
@@ -61,4 +61,20 @@ export declare class TypeScriptTypeChecker implements TypeChecker {
|
|
61
61
|
isUnion2(typeAnalysisResult: TypeAnalysis): boolean;
|
62
62
|
isArray2(typeAnalysisResult: TypeAnalysis): boolean;
|
63
63
|
isTypeAlias2(typeAnalysisResult: TypeAnalysis): boolean;
|
64
|
+
extractIdentifiers(code: string): {
|
65
|
+
name: string;
|
66
|
+
start: number;
|
67
|
+
end: number;
|
68
|
+
line: number;
|
69
|
+
column: number;
|
70
|
+
}[];
|
71
|
+
extractIdentifiersHelper(node: ts.Node, identifiers: Set<string>): void;
|
72
|
+
extractIdentifiersWithPosHelper(sourceFile: ts.SourceFile, node: ts.Node, identifiers: {
|
73
|
+
name: string;
|
74
|
+
start: number;
|
75
|
+
end: number;
|
76
|
+
line: number;
|
77
|
+
column: number;
|
78
|
+
}[]): void;
|
79
|
+
findDeclarationForIdentifier(sourceCode: string, targetLine: number, targetCharStart: number, targetCharEnd: number): string | null;
|
64
80
|
}
|
@@ -386,6 +386,9 @@ class TypeScriptTypeChecker {
|
|
386
386
|
type: this.analyzeTypeNode(element.type, checker)
|
387
387
|
};
|
388
388
|
}
|
389
|
+
else if (ts.isNamedTupleMember(element)) {
|
390
|
+
return this.analyzeTypeNode(element.type, checker);
|
391
|
+
}
|
389
392
|
else {
|
390
393
|
return this.analyzeTypeNode(element, checker);
|
391
394
|
}
|
@@ -508,5 +511,71 @@ class TypeScriptTypeChecker {
|
|
508
511
|
isTypeAlias2(typeAnalysisResult) {
|
509
512
|
return typeAnalysisResult.kind === "TypeReference";
|
510
513
|
}
|
514
|
+
extractIdentifiers(code) {
|
515
|
+
const sourceFile = ts.createSourceFile("sample.ts", code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
516
|
+
const identifiers = [];
|
517
|
+
this.extractIdentifiersWithPosHelper(sourceFile, sourceFile, identifiers);
|
518
|
+
return identifiers;
|
519
|
+
}
|
520
|
+
extractIdentifiersHelper(node, identifiers) {
|
521
|
+
if (ts.isIdentifier(node)) {
|
522
|
+
// console.log(node.kind, node.text)
|
523
|
+
if (ts.isTypeReferenceNode(node.parent) ||
|
524
|
+
ts.isTypeAliasDeclaration(node.parent) ||
|
525
|
+
ts.isInterfaceDeclaration(node.parent) ||
|
526
|
+
ts.isClassDeclaration(node.parent) ||
|
527
|
+
ts.isFunctionTypeNode(node.parent)) {
|
528
|
+
identifiers.add(node.getText());
|
529
|
+
}
|
530
|
+
}
|
531
|
+
node.forEachChild(child => this.extractIdentifiersHelper(child, identifiers));
|
532
|
+
}
|
533
|
+
extractIdentifiersWithPosHelper(sourceFile, node, identifiers) {
|
534
|
+
if (ts.isIdentifier(node)) {
|
535
|
+
// console.log(node.kind, node.text)
|
536
|
+
if (ts.isTypeReferenceNode(node.parent) ||
|
537
|
+
ts.isTypeAliasDeclaration(node.parent) ||
|
538
|
+
ts.isInterfaceDeclaration(node.parent) ||
|
539
|
+
ts.isClassDeclaration(node.parent) ||
|
540
|
+
ts.isFunctionTypeNode(node.parent)) {
|
541
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
542
|
+
identifiers.push({
|
543
|
+
name: node.getText(),
|
544
|
+
start: node.getStart(),
|
545
|
+
end: node.getEnd(),
|
546
|
+
line: line + 1, // Convert 0-based to 1-based
|
547
|
+
column: character + 1 // Convert 0-based to 1-based
|
548
|
+
});
|
549
|
+
}
|
550
|
+
}
|
551
|
+
node.forEachChild(child => this.extractIdentifiersWithPosHelper(sourceFile, child, identifiers));
|
552
|
+
}
|
553
|
+
findDeclarationForIdentifier(sourceCode, targetLine, targetCharStart, targetCharEnd) {
|
554
|
+
const sourceFile = ts.createSourceFile("sample.ts", sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
555
|
+
function findDeclarationForIdentifierHelper(node) {
|
556
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
557
|
+
const identifier = node.name;
|
558
|
+
// Get start and end positions for identifier
|
559
|
+
const startPos = sourceFile.getLineAndCharacterOfPosition(identifier.getStart());
|
560
|
+
const endPos = sourceFile.getLineAndCharacterOfPosition(identifier.getEnd());
|
561
|
+
// Match the identifier position
|
562
|
+
if (startPos.line === targetLine &&
|
563
|
+
startPos.character === targetCharStart &&
|
564
|
+
endPos.character === targetCharEnd) {
|
565
|
+
// Extract full declaration text from source code
|
566
|
+
return sourceCode.slice(node.getStart(), node.getEnd());
|
567
|
+
}
|
568
|
+
}
|
569
|
+
return ts.forEachChild(node, findDeclarationForIdentifierHelper) || null;
|
570
|
+
}
|
571
|
+
const declarationText = findDeclarationForIdentifierHelper(sourceFile);
|
572
|
+
// console.log(`declarationText: ${declarationText}`)
|
573
|
+
if (declarationText) {
|
574
|
+
return declarationText;
|
575
|
+
}
|
576
|
+
else {
|
577
|
+
return null;
|
578
|
+
}
|
579
|
+
}
|
511
580
|
}
|
512
581
|
exports.TypeScriptTypeChecker = TypeScriptTypeChecker;
|