@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 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(relevantTypes)
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, 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
  }
@@ -10,16 +10,34 @@ const types_1 = require("./types");
10
10
  (async () => {
11
11
  try {
12
12
  let x;
13
- 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/");
14
- console.dir(x, { depth: null });
15
- x = await (0, main_1.extractContext)(types_1.Language.TypeScript, "/home/jacob/projects/context-extractor/targets/playlist/sketch.ts", "/home/jacob/projects/context-extractor/targets/playlist/");
16
- console.dir(x, { depth: null });
17
- x = await (0, main_1.extractContext)(types_1.Language.TypeScript, "/home/jacob/projects/context-extractor/targets/passwords/sketch.ts", "/home/jacob/projects/context-extractor/targets/passwords/");
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 (0, main_1.extractContext)(types_1.Language.TypeScript, "/home/jacob/projects/context-extractor/targets/emojipaint/sketch.ts", "/home/jacob/projects/context-extractor/targets/emojipaint/");
22
- 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 })
23
41
  // const y = await completeWithLLM(
24
42
  // x!,
25
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
@@ -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
- if (typeSpan.slice(typeSpan.length - 1) == ";") {
399
- typeSpan = typeSpan.slice(0, typeSpan.length - 1);
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.normalize(kv[1], relevantTypes);
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.normalize(element, relevantTypes);
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.normalize(element, relevantTypes);
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.normalize(element, relevantTypes);
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
- 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];
541
726
  if (typ === undefined) {
542
727
  return typeSpan;
543
728
  }
544
- normalForm += this.normalize(typ, relevantTypes);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpoly1219/context-extractor",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Extract relevant context from an incomplete program sketch.",
5
5
  "repository": {
6
6
  "type": "git",