Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/test/summarizeDocument.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
7
import * as fs from 'fs/promises';
8
import { describe, expect, test } from 'vitest';
9
import * as path from '../../../../util/vs/base/common/path';
10
import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';
11
import { SummarizedDocumentLineNumberStyle } from '../inline/summarizedDocument/implementation';
12
import { RemovableNode } from '../inline/summarizedDocument/summarizeDocument';
13
import { fileVariableCostFn } from '../panel/fileVariable';
14
import { DEFAULT_CHAR_LIMIT, fixture, fromFixtureOld, generateSummarizedDocument, generateSummarizedDocumentAndExtractGoodSelection, generateSummarizedDocuments, getSummarizedSnapshotPath, loadFile, selectionDocPathInFixture, summarizedDocPathInFixture } from './utils';
15
16
describe('createSummarizedDocument[visualizable]', () => {
17
18
test('[tsx] can summarize public fields', async () => {
19
const filename = 'simpleClass.tsx';
20
21
const result = await generateSummarizedDocument(
22
fromFixtureOld(filename, 'typescriptreact'),
23
[9, 0],
24
1,
25
{ alwaysUseEllipsisForElisions: true }
26
);
27
28
await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
29
});
30
31
test('[cpp] CppNoExtraSemicolons', async () => {
32
const file = await loadFile({ filePath: fixture('cppNoExtraSemicolons.cpp'), languageId: 'cpp' });
33
const result = await generateSummarizedDocument(file, [50, 0], 628, { alwaysUseEllipsisForElisions: true });
34
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
35
});
36
37
test('[cpp] Do not throw invalid range error', async () => {
38
const file = await loadFile({ filePath: fixture('problem1.cpp'), languageId: 'cpp' });
39
const result = await generateSummarizedDocument(file, [17, 10], 10, { alwaysUseEllipsisForElisions: true });
40
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
41
});
42
43
test('[cpp] Do not throw invalid range error - 2', async () => {
44
const file = await loadFile({ filePath: fixture('problem2.cpp'), languageId: 'cpp' });
45
const result = await generateSummarizedDocument(file, [6, 19], 100, { alwaysUseEllipsisForElisions: true });
46
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
47
});
48
49
test('should include nearby code - strings.test.ts', async () => {
50
const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });
51
const result = await generateSummarizedDocument(file, [344, 0]);
52
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
53
});
54
55
test('should include nearby code - strings.test.ts - SummarizedDocumentLineNumberStyle.OmittedRanges', async () => {
56
const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });
57
const result = await generateSummarizedDocument(file, [344, 0], undefined, { lineNumberStyle: SummarizedDocumentLineNumberStyle.OmittedRanges });
58
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '2'));
59
});
60
61
test('should include nearby code - strings.test.ts - SummarizedDocumentLineNumberStyle.Full', async () => {
62
const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });
63
const result = await generateSummarizedDocument(file, [344, 0], undefined, { lineNumberStyle: SummarizedDocumentLineNumberStyle.Full });
64
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '3'));
65
});
66
67
test('should include whitespace touching the selection - codeEditorWidget.ts', async () => {
68
const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });
69
const result = await generateSummarizedDocument(file, [1085, 2, 1089, 3]);
70
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '1'));
71
});
72
73
test('should not select parent node when the selection contains children but starts/ends in whitespace - codeEditorWidget.ts', async () => {
74
const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });
75
const result = await generateSummarizedDocument(file, [211, 0, 213, 0]);
76
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '2'));
77
});
78
79
test('no selection - codeEditorWidget.ts', async () => {
80
const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });
81
const result = await generateSummarizedDocument(file, undefined, DEFAULT_CHAR_LIMIT, {
82
costFnOverride: fileVariableCostFn,
83
});
84
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '3'));
85
});
86
87
test('should select at least one node - codeEditorWidget.ts', async () => {
88
const file = await loadFile({ filePath: fixture('editorGroupWatermark.ts'), languageId: 'typescript' });
89
const result = await generateSummarizedDocument(file, [24, 0]);
90
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
91
});
92
93
test('issue #6614: Should include ... only once when eliding code', async () => {
94
const file = await loadFile({ filePath: fixture('view.css'), languageId: 'css' });
95
const result = await generateSummarizedDocument(file, [225, 0, 237, 15], 0, { alwaysUseEllipsisForElisions: false });
96
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
97
});
98
99
test('should expand selection end to closing brace - extHost.api.impl.ts', async () => {
100
const filename = 'extHost.api.impl.ts';
101
const [otherCode, selectedCode] =
102
await generateSummarizedDocumentAndExtractGoodSelection(
103
fromFixtureOld(filename, 'typescript'),
104
[696, 0, 711, 0]
105
);
106
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
107
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
108
});
109
110
test('should expand selection when it sits on identifier - webview/index.ts', async () => {
111
const filename = 'webview-index.ts';
112
const [otherCode, selectedCode] =
113
await generateSummarizedDocumentAndExtractGoodSelection(
114
fromFixtureOld(filename, 'typescript'),
115
[47, 14]
116
);
117
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
118
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
119
});
120
121
test('should not expand selection to the left in whitespace - pullRequestModel.ts', async () => {
122
const filename = 'pullRequestModel.ts';
123
const [otherCode, selectedCode] =
124
await generateSummarizedDocumentAndExtractGoodSelection(
125
fromFixtureOld(filename, 'typescript'),
126
[1071, 0]
127
);
128
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
129
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
130
});
131
132
test('should not expand selection to select whitespace - keybindingParser.ts', async () => {
133
const filename = 'keybindingParser.ts';
134
const [otherCode, selectedCode] =
135
await generateSummarizedDocumentAndExtractGoodSelection(
136
fromFixtureOld(filename, 'typescript'),
137
[15, 8]
138
);
139
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
140
expect(selectedCode).toMatchInlineSnapshot(`""`);
141
});
142
143
test('should expand selection start to opening brace - BasketService.cs', async () => {
144
const filename = 'BasketService.cs';
145
const [otherCode, selectedCode] =
146
await generateSummarizedDocumentAndExtractGoodSelection(
147
fromFixtureOld(filename, 'csharp', {
148
insertSpaces: true,
149
tabSize: 4,
150
}),
151
[44, 5]
152
);
153
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
154
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
155
});
156
157
test('should not expand end selection to select whitespace - pseudoStartStopConversationCallbackTest.ts', async () => {
158
const filename = 'pseudoStartStopConversationCallbackTest.ts';
159
const [otherCode, selectedCode] =
160
await generateSummarizedDocumentAndExtractGoodSelection(
161
fromFixtureOld(filename, 'typescript'),
162
[125, 0, 132, 0]
163
);
164
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
165
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
166
});
167
168
test('issue #5755: should not expand selection from property to the entire interface', async () => {
169
const filename = 'vscode.proposed.chatParticipantAdditions.d.ts';
170
const [otherCode, selectedCode] =
171
await generateSummarizedDocumentAndExtractGoodSelection(
172
fromFixtureOld(filename, 'typescript'),
173
[158, 0, 166, 0]
174
);
175
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
176
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
177
});
178
179
test('issue #5710: should move the start of the selection to next line', async () => {
180
const filename = '5710.ts';
181
const [otherCode, selectedCode] =
182
await generateSummarizedDocumentAndExtractGoodSelection(
183
fromFixtureOld(filename, 'typescript'),
184
[7, 66, 10, 5],
185
);
186
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
187
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
188
});
189
190
test('issue #7487: should not expand selection outside the React element', async () => {
191
const filename = 'EditForm.tsx';
192
const [otherCode, selectedCode] =
193
await generateSummarizedDocumentAndExtractGoodSelection(
194
fromFixtureOld(filename, 'typescriptreact'),
195
[138, 0, 147, 17]
196
);
197
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
198
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
199
});
200
201
test('issue #6614: should not expand selection to entire HTML document', async () => {
202
const filename = 'workbench-dev.html';
203
const [otherCode, selectedCode] =
204
await generateSummarizedDocumentAndExtractGoodSelection(
205
fromFixtureOld(filename, 'html'),
206
[75, 4, 75, 4]
207
);
208
await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
209
await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));
210
});
211
212
});
213
214
describe('cutoff cost', () => {
215
216
test('Everything is too expensive', async () => {
217
218
const filename = 'map.ts';
219
220
const result = await generateSummarizedDocument(
221
fromFixtureOld(filename, 'typescript'),
222
undefined,
223
DEFAULT_CHAR_LIMIT,
224
{
225
costFnOverride: () => false
226
}
227
);
228
229
expect(result.text).toBe('');
230
});
231
232
test('cutoff cost is respected', async () => {
233
234
const viewport = OffsetRange.ofStartAndLength(0, 1323);
235
236
function costFnOverride(n: RemovableNode, currentScore: number) {
237
// view port line 1 to line 49
238
if (n.range.intersectsOrTouches(viewport)) {
239
return 1;
240
} else {
241
return false;
242
}
243
}
244
245
const filename = 'map.ts';
246
const result = await generateSummarizedDocument(
247
fromFixtureOld(filename, 'typescript'),
248
undefined,
249
Number.MAX_SAFE_INTEGER,
250
{
251
costFnOverride
252
}
253
);
254
await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename) + '.view-port');
255
});
256
257
});
258
259
describe('/tests summarization[visualizable]', () => {
260
test('keep constructor & method signatures', async () => {
261
262
function costFnOverride(node: RemovableNode, currentScore: number) {
263
return node.kind === 'constructor' || node.kind === 'method_definition' ? 0 : currentScore;
264
}
265
266
const filename = 'bracketPairsTree.ts';
267
268
const result = await generateSummarizedDocument(
269
fromFixtureOld(filename, 'typescript'),
270
[88, 4, 102, 5],
271
DEFAULT_CHAR_LIMIT,
272
{ costFnOverride }
273
);
274
275
await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
276
});
277
278
test('keep constructor - 2', async () => {
279
280
function costFnOverride(node: RemovableNode, currentScore: number) {
281
return node.kind === 'constructor' ? 0 : currentScore;
282
}
283
284
const filename = 'map.ts';
285
286
const result = await generateSummarizedDocument(
287
fromFixtureOld(filename, 'typescript'),
288
[671, 0, 725, 1],
289
DEFAULT_CHAR_LIMIT,
290
{ costFnOverride }
291
);
292
293
await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));
294
});
295
});
296
297
298
describe('createSummarizedDocuments', () => {
299
300
test('summarize two document equally', async () => {
301
302
303
const filenames: string[] = [
304
'editorGroupWatermark.ts',
305
'strings.test-example.ts'
306
];
307
308
const files = [
309
await loadFile({ filePath: fixture(filenames[0]), languageId: 'typescript' }),
310
await loadFile({ filePath: fixture(filenames[1]), languageId: 'typescript' })
311
];
312
313
const docs = await generateSummarizedDocuments([
314
{
315
filePromise: files[0],
316
selection: [24, 0]
317
},
318
{
319
filePromise: files[1],
320
selection: [344, 0]
321
},
322
]);
323
324
expect(docs.length).toBe(2);
325
326
for (let i = 0; i < docs.length; i++) {
327
const document = docs[i];
328
expect(document.originalText).toBe(files[i].contents);
329
await expect(document.text).toMatchFileSnapshot(summarizedDocPathInFixture(filenames[i] + '.round1'));
330
}
331
});
332
333
test('summarize two document un-equally', async () => {
334
335
const filenames: string[] = [
336
'editorGroupWatermark.ts',
337
'strings.test-example.ts'
338
];
339
340
const files = [
341
await loadFile({ filePath: fixture(filenames[0]), languageId: 'typescript' }),
342
await loadFile({ filePath: fixture(filenames[1]), languageId: 'typescript' })
343
];
344
345
const docs = await generateSummarizedDocuments([
346
{
347
filePromise: files[0],
348
selection: [24, 0]
349
},
350
{
351
filePromise: files[1],
352
selection: [344, 0]
353
},
354
], 5000, {
355
costFnOverride(node, currentCost, document) {
356
if (document.uri.path.includes(filenames[1])) {
357
return 1;
358
}
359
return 100;
360
},
361
});
362
363
// small budget, BIASED scores
364
365
expect(docs.length).toBe(2);
366
367
for (let i = 0; i < docs.length; i++) {
368
const document = docs[i];
369
expect(document.originalText).toBe(files[i].contents);
370
await expect(document.text).toMatchFileSnapshot(summarizedDocPathInFixture(filenames[i] + '.round2'));
371
}
372
});
373
374
375
test.skip('run on repositories', async () => {
376
377
const N_FILES_LIMIT = 1500;
378
379
const reposWithLangs: Record<string, { repoPath: string; language: string }> = {
380
'vscode-copilot': { repoPath: path.join(__dirname, '../../../../../src/'), language: 'typescript' },
381
'llama.cpp': { repoPath: path.join(__dirname, '../../../../../../llama.cpp/src'), language: 'cpp' },
382
};
383
384
const langToExts: Record<string, string[]> = {
385
'cpp': ['cpp', 'h'],
386
'typescript': ['ts', 'tsx'],
387
};
388
389
const { repoPath, language } = reposWithLangs['vscode-copilot'];
390
const exts = langToExts[language];
391
392
async function* traverseDirectory(pathToDir: string): AsyncGenerator<string> {
393
const dirEntries = await fs.readdir(pathToDir, { withFileTypes: true });
394
for (const entry of dirEntries) {
395
if (entry.isDirectory()) {
396
yield* traverseDirectory(path.join(pathToDir, entry.name));
397
} else if (exts.some(ext => !entry.parentPath.includes('fixture') && !entry.name.includes('.summarized.') && entry.name.endsWith('.' + ext))) {
398
console.log(path.join(pathToDir, entry.name));
399
yield path.join(pathToDir, entry.name);
400
}
401
}
402
}
403
404
let i = -1;
405
for await (const filePath of traverseDirectory(repoPath)) {
406
++i;
407
try {
408
if (i > N_FILES_LIMIT) {
409
break;
410
}
411
const file = await loadFile({ filePath, languageId: language });
412
const fileLines = file.contents.split('\n');
413
const selection: [number, number] = [Math.floor(fileLines.length / 2), Math.floor(fileLines[Math.floor(fileLines.length / 2)].length / 2)];
414
const result = await generateSummarizedDocument(file, selection, 400, { alwaysUseEllipsisForElisions: true });
415
await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));
416
} catch (e) {
417
console.log(`processing ${filePath} threw error`, e);
418
}
419
}
420
});
421
422
});
423
424