@jpoly1219/context-extractor 0.2.6 → 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 CHANGED
@@ -7,7 +7,6 @@ export declare class App {
7
7
  private sketchPath;
8
8
  private repoPath;
9
9
  private result;
10
- private timeout;
11
10
  constructor(language: Language, sketchPath: string, repoPath: string);
12
11
  init(): Promise<void>;
13
12
  run(): Promise<void>;
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
- const relevantTypes = await this.languageDriver.extractRelevantTypes(this.lspClient, holeContext.fullHoverResult, holeContext.functionName, holeContext.range.start.line, holeContext.range.end.line, new Map(), holeContext.source);
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.log(path.join(path.dirname(this.sketchPath), `sketch${path.extname(this.sketchPath)}`));
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,8 +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");
151
+ // console.log(relevantHeaders)
152
+ // console.log(relevantHeaders.size)
137
153
  // Postprocess the map.
154
+ // console.time("extractRelevantHaders postprocess");
138
155
  if (this.language === types_1.Language.TypeScript) {
139
156
  relevantTypes.delete("");
140
157
  for (const [k, { typeSpan: v, sourceFile: src }] of relevantTypes.entries()) {
@@ -144,6 +161,8 @@ class App {
144
161
  obj.typeSpan += ";";
145
162
  }
146
163
  }
164
+ // console.timeEnd("extractRelevantHeaders postprocess");
165
+ // console.time("toReturn");
147
166
  const relevantTypesToReturn = new Map();
148
167
  relevantTypes.forEach(({ typeSpan: v, sourceFile: src }, _) => {
149
168
  if (relevantTypesToReturn.has(src)) {
@@ -168,6 +187,7 @@ class App {
168
187
  relevantHeadersToReturn.set(src, [v]);
169
188
  }
170
189
  });
190
+ // console.timeEnd("toReturn");
171
191
  this.result = {
172
192
  holeType: holeContext.functionTypeSpan,
173
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, endLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, currentFile: string): Promise<Map<string, TypeSpanAndSourceFile>>;
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;
@@ -172,12 +172,12 @@ class OcamlDriver {
172
172
  source: `file://${sketchFilePath}`
173
173
  };
174
174
  }
175
- async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, currentFile) {
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 <= endLine; ++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, matchingSymbolRange.end.line, foundSoFar, typeDefinitionResult[0].uri);
227
+ await this.extractRelevantTypes(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, foundSoFar, typeDefinitionResult[0].uri);
228
228
  }
229
229
  }
230
230
  }
@@ -9,8 +9,35 @@ const types_1 = require("./types");
9
9
  // extract("/home/jacob/projects/context-extractor/targets/emojipaint/sketch.ts").then(r => console.log("emojipaint\n", r));
10
10
  (async () => {
11
11
  try {
12
- const x = await (0, main_1.extractContext)(types_1.Language.TypeScript, "/home/jacob/projects/context-extractor/targets/todo/sketch.ts", "/home/jacob/projects/context-extractor/targets/todo/");
12
+ let x;
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 })
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/");
13
34
  console.dir(x, { depth: null });
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 })
14
41
  // const y = await completeWithLLM(
15
42
  // x!,
16
43
  // Language.TypeScript,
@@ -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, endLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, currentFile: string) => Promise<Map<string, TypeSpanAndSourceFile>>;
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
- extractRelevantTypes(lspClient: LspClient, fullHoverResult: string, typeName: string, startLine: number, endLine: number, foundSoFar: Map<string, TypeSpanAndSourceFile>, // identifier -> [full hover result, source]
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 extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, // identifier -> [full hover result, source]
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
- for (let charPos = 0; charPos < numOfCharsInLine; ++charPos) {
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
- // Use documentSymbol instead of hover.
223
- // This prevents type alias "squashing" done by tsserver.
224
- // This also allows for grabbing the entire definition range and not just the symbol range.
225
- // PERF: feels like this could be memoized to improve performance.
226
- const documentSymbolResult = await lspClient.documentSymbol({
227
- textDocument: {
228
- uri: typeDefinitionResult[0].uri
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
- relevantContext.add({ typeSpan: line, sourceFile: source });
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
- const normT1 = this.normalize(t1, relevantTypes);
385
- const normT2 = this.normalize(t2, relevantTypes);
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
@@ -390,9 +567,16 @@ class TypeScriptDriver {
390
567
  normalize(typeSpan, relevantTypes) {
391
568
  // NOTE: BUGFIX
392
569
  // console.log(`normalize: ${typeSpan}`)
570
+ // console.log(`normalize: ${typeSpan}`)
571
+ // console.log(`normalize: ${typeSpan == undefined}`)
393
572
  if (typeSpan.slice(typeSpan.length - 2) == " =") {
394
- typeSpan = typeSpan.slice(typeSpan.length - 2);
573
+ typeSpan = typeSpan.slice(0, typeSpan.length - 2);
395
574
  }
575
+ // DEBUG
576
+ // if (typeSpan.slice(typeSpan.length - 1) == ";") {
577
+ // typeSpan = typeSpan.slice(0, typeSpan.length - 1);
578
+ // }
579
+ // console.log(typeSpan)
396
580
  let normalForm = "";
397
581
  // pattern matching for typeSpan
398
582
  if (this.typeChecker.isPrimitive(typeSpan)) {
@@ -462,11 +646,17 @@ class TypeScriptDriver {
462
646
  normalize2(typeSpan, relevantTypes) {
463
647
  // NOTE: BUGFIX
464
648
  // console.log(`normalize: ${typeSpan}`)
649
+ // console.log(`normalize: ${typeSpan == undefined}`)
465
650
  if (typeSpan.slice(typeSpan.length - 2) == " =") {
466
- typeSpan = typeSpan.slice(typeSpan.length - 2);
651
+ typeSpan = typeSpan.slice(0, typeSpan.length - 2);
652
+ }
653
+ if (typeSpan.slice(typeSpan.length - 1) == ";") {
654
+ typeSpan = typeSpan.slice(0, typeSpan.length - 1);
467
655
  }
656
+ // console.log(typeSpan)
468
657
  let normalForm = "";
469
658
  const analysisResult = this.typeChecker.analyzeTypeString(typeSpan);
659
+ // console.dir(analysisResult, { depth: null })
470
660
  // pattern matching for typeSpan
471
661
  // if (this.typeChecker.isPrimitive(typeSpan)) {
472
662
  if (this.typeChecker.isPrimitive2(analysisResult)) {
@@ -480,7 +670,7 @@ class TypeScriptDriver {
480
670
  elements.forEach(element => {
481
671
  if (element !== "") {
482
672
  const kv = element.split(": ");
483
- normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize(kv[1], relevantTypes);
673
+ normalForm += kv[0].slice(1, kv[0].length), ": ", this.normalize2(kv[1], relevantTypes);
484
674
  normalForm += "; ";
485
675
  }
486
676
  });
@@ -494,7 +684,7 @@ class TypeScriptDriver {
494
684
  const elements = this.typeChecker.parseTypeArrayString(typeSpan);
495
685
  normalForm += "[";
496
686
  elements.forEach((element, i) => {
497
- normalForm += this.normalize(element, relevantTypes);
687
+ normalForm += this.normalize2(element, relevantTypes);
498
688
  if (i < elements.length - 1) {
499
689
  normalForm += ", ";
500
690
  }
@@ -508,7 +698,7 @@ class TypeScriptDriver {
508
698
  const elements = typeSpan.split(" | ");
509
699
  elements.forEach((element, i) => {
510
700
  normalForm += "(";
511
- normalForm += this.normalize(element, relevantTypes);
701
+ normalForm += this.normalize2(element, relevantTypes);
512
702
  normalForm += ")";
513
703
  if (i < elements.length - 1) {
514
704
  normalForm += " | ";
@@ -520,17 +710,23 @@ class TypeScriptDriver {
520
710
  else if (this.typeChecker.isArray2(analysisResult)) {
521
711
  // console.log(`isArray: ${typeSpan}`)
522
712
  const element = typeSpan.split("[]")[0];
523
- normalForm += this.normalize(element, relevantTypes);
713
+ normalForm += this.normalize2(element, relevantTypes);
524
714
  normalForm += "[]";
525
715
  return normalForm;
526
716
  // } else if (this.typeChecker.isTypeAlias(typeSpan)) {
527
717
  }
528
718
  else if (this.typeChecker.isTypeAlias2(analysisResult)) {
529
- const typ = relevantTypes.get(typeSpan).typeSpan.split(" = ")[1];
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];
530
726
  if (typ === undefined) {
531
727
  return typeSpan;
532
728
  }
533
- normalForm += this.normalize(typ, relevantTypes);
729
+ normalForm += this.normalize2(typ, relevantTypes);
534
730
  return normalForm;
535
731
  }
536
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpoly1219/context-extractor",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Extract relevant context from an incomplete program sketch.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,7 @@
12
12
  "dist/"
13
13
  ],
14
14
  "scripts": {
15
- "build": "npx tsc && npm run copy-deps",
15
+ "build": "rm -rf dist/ && npx tsc && npm run copy-deps",
16
16
  "copy-deps": "npm run copy-lsp-client && npm run copy-ocaml",
17
17
  "copy-lsp-client": "shx cp -r ts-lsp-client-dist dist/",
18
18
  "copy-ocaml": "shx mkdir -p dist/src/ocaml-utils/ && shx cp -r src/ocaml-utils/_build dist/src/ocaml-utils/_build/",