Path: blob/main/extensions/copilot/src/extension/prompts/node/test/summarizeDocument.spec.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/456import * as fs from 'fs/promises';7import { describe, expect, test } from 'vitest';8import * as path from '../../../../util/vs/base/common/path';9import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';10import { SummarizedDocumentLineNumberStyle } from '../inline/summarizedDocument/implementation';11import { RemovableNode } from '../inline/summarizedDocument/summarizeDocument';12import { fileVariableCostFn } from '../panel/fileVariable';13import { DEFAULT_CHAR_LIMIT, fixture, fromFixtureOld, generateSummarizedDocument, generateSummarizedDocumentAndExtractGoodSelection, generateSummarizedDocuments, getSummarizedSnapshotPath, loadFile, selectionDocPathInFixture, summarizedDocPathInFixture } from './utils';1415describe('createSummarizedDocument[visualizable]', () => {1617test('[tsx] can summarize public fields', async () => {18const filename = 'simpleClass.tsx';1920const result = await generateSummarizedDocument(21fromFixtureOld(filename, 'typescriptreact'),22[9, 0],231,24{ alwaysUseEllipsisForElisions: true }25);2627await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));28});2930test('[cpp] CppNoExtraSemicolons', async () => {31const file = await loadFile({ filePath: fixture('cppNoExtraSemicolons.cpp'), languageId: 'cpp' });32const result = await generateSummarizedDocument(file, [50, 0], 628, { alwaysUseEllipsisForElisions: true });33await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));34});3536test('[cpp] Do not throw invalid range error', async () => {37const file = await loadFile({ filePath: fixture('problem1.cpp'), languageId: 'cpp' });38const result = await generateSummarizedDocument(file, [17, 10], 10, { alwaysUseEllipsisForElisions: true });39await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));40});4142test('[cpp] Do not throw invalid range error - 2', async () => {43const file = await loadFile({ filePath: fixture('problem2.cpp'), languageId: 'cpp' });44const result = await generateSummarizedDocument(file, [6, 19], 100, { alwaysUseEllipsisForElisions: true });45await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));46});4748test('should include nearby code - strings.test.ts', async () => {49const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });50const result = await generateSummarizedDocument(file, [344, 0]);51await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));52});5354test('should include nearby code - strings.test.ts - SummarizedDocumentLineNumberStyle.OmittedRanges', async () => {55const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });56const result = await generateSummarizedDocument(file, [344, 0], undefined, { lineNumberStyle: SummarizedDocumentLineNumberStyle.OmittedRanges });57await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '2'));58});5960test('should include nearby code - strings.test.ts - SummarizedDocumentLineNumberStyle.Full', async () => {61const file = await loadFile({ filePath: fixture('strings.test-example.ts'), languageId: 'typescript' });62const result = await generateSummarizedDocument(file, [344, 0], undefined, { lineNumberStyle: SummarizedDocumentLineNumberStyle.Full });63await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '3'));64});6566test('should include whitespace touching the selection - codeEditorWidget.ts', async () => {67const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });68const result = await generateSummarizedDocument(file, [1085, 2, 1089, 3]);69await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '1'));70});7172test('should not select parent node when the selection contains children but starts/ends in whitespace - codeEditorWidget.ts', async () => {73const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });74const result = await generateSummarizedDocument(file, [211, 0, 213, 0]);75await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '2'));76});7778test('no selection - codeEditorWidget.ts', async () => {79const file = await loadFile({ filePath: fixture('codeEditorWidget.ts'), languageId: 'typescript' });80const result = await generateSummarizedDocument(file, undefined, DEFAULT_CHAR_LIMIT, {81costFnOverride: fileVariableCostFn,82});83await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file, '3'));84});8586test('should select at least one node - codeEditorWidget.ts', async () => {87const file = await loadFile({ filePath: fixture('editorGroupWatermark.ts'), languageId: 'typescript' });88const result = await generateSummarizedDocument(file, [24, 0]);89await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));90});9192test('issue #6614: Should include ... only once when eliding code', async () => {93const file = await loadFile({ filePath: fixture('view.css'), languageId: 'css' });94const result = await generateSummarizedDocument(file, [225, 0, 237, 15], 0, { alwaysUseEllipsisForElisions: false });95await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));96});9798test('should expand selection end to closing brace - extHost.api.impl.ts', async () => {99const filename = 'extHost.api.impl.ts';100const [otherCode, selectedCode] =101await generateSummarizedDocumentAndExtractGoodSelection(102fromFixtureOld(filename, 'typescript'),103[696, 0, 711, 0]104);105await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));106await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));107});108109test('should expand selection when it sits on identifier - webview/index.ts', async () => {110const filename = 'webview-index.ts';111const [otherCode, selectedCode] =112await generateSummarizedDocumentAndExtractGoodSelection(113fromFixtureOld(filename, 'typescript'),114[47, 14]115);116await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));117await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));118});119120test('should not expand selection to the left in whitespace - pullRequestModel.ts', async () => {121const filename = 'pullRequestModel.ts';122const [otherCode, selectedCode] =123await generateSummarizedDocumentAndExtractGoodSelection(124fromFixtureOld(filename, 'typescript'),125[1071, 0]126);127await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));128await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));129});130131test('should not expand selection to select whitespace - keybindingParser.ts', async () => {132const filename = 'keybindingParser.ts';133const [otherCode, selectedCode] =134await generateSummarizedDocumentAndExtractGoodSelection(135fromFixtureOld(filename, 'typescript'),136[15, 8]137);138await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));139expect(selectedCode).toMatchInlineSnapshot(`""`);140});141142test('should expand selection start to opening brace - BasketService.cs', async () => {143const filename = 'BasketService.cs';144const [otherCode, selectedCode] =145await generateSummarizedDocumentAndExtractGoodSelection(146fromFixtureOld(filename, 'csharp', {147insertSpaces: true,148tabSize: 4,149}),150[44, 5]151);152await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));153await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));154});155156test('should not expand end selection to select whitespace - pseudoStartStopConversationCallbackTest.ts', async () => {157const filename = 'pseudoStartStopConversationCallbackTest.ts';158const [otherCode, selectedCode] =159await generateSummarizedDocumentAndExtractGoodSelection(160fromFixtureOld(filename, 'typescript'),161[125, 0, 132, 0]162);163await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));164await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));165});166167test('issue #5755: should not expand selection from property to the entire interface', async () => {168const filename = 'vscode.proposed.chatParticipantAdditions.d.ts';169const [otherCode, selectedCode] =170await generateSummarizedDocumentAndExtractGoodSelection(171fromFixtureOld(filename, 'typescript'),172[158, 0, 166, 0]173);174await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));175await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));176});177178test('issue #5710: should move the start of the selection to next line', async () => {179const filename = '5710.ts';180const [otherCode, selectedCode] =181await generateSummarizedDocumentAndExtractGoodSelection(182fromFixtureOld(filename, 'typescript'),183[7, 66, 10, 5],184);185await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));186await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));187});188189test('issue #7487: should not expand selection outside the React element', async () => {190const filename = 'EditForm.tsx';191const [otherCode, selectedCode] =192await generateSummarizedDocumentAndExtractGoodSelection(193fromFixtureOld(filename, 'typescriptreact'),194[138, 0, 147, 17]195);196await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));197await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));198});199200test('issue #6614: should not expand selection to entire HTML document', async () => {201const filename = 'workbench-dev.html';202const [otherCode, selectedCode] =203await generateSummarizedDocumentAndExtractGoodSelection(204fromFixtureOld(filename, 'html'),205[75, 4, 75, 4]206);207await expect(otherCode).toMatchFileSnapshot(summarizedDocPathInFixture(filename));208await expect(selectedCode).toMatchFileSnapshot(selectionDocPathInFixture(filename));209});210211});212213describe('cutoff cost', () => {214215test('Everything is too expensive', async () => {216217const filename = 'map.ts';218219const result = await generateSummarizedDocument(220fromFixtureOld(filename, 'typescript'),221undefined,222DEFAULT_CHAR_LIMIT,223{224costFnOverride: () => false225}226);227228expect(result.text).toBe('');229});230231test('cutoff cost is respected', async () => {232233const viewport = OffsetRange.ofStartAndLength(0, 1323);234235function costFnOverride(n: RemovableNode, currentScore: number) {236// view port line 1 to line 49237if (n.range.intersectsOrTouches(viewport)) {238return 1;239} else {240return false;241}242}243244const filename = 'map.ts';245const result = await generateSummarizedDocument(246fromFixtureOld(filename, 'typescript'),247undefined,248Number.MAX_SAFE_INTEGER,249{250costFnOverride251}252);253await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename) + '.view-port');254});255256});257258describe('/tests summarization[visualizable]', () => {259test('keep constructor & method signatures', async () => {260261function costFnOverride(node: RemovableNode, currentScore: number) {262return node.kind === 'constructor' || node.kind === 'method_definition' ? 0 : currentScore;263}264265const filename = 'bracketPairsTree.ts';266267const result = await generateSummarizedDocument(268fromFixtureOld(filename, 'typescript'),269[88, 4, 102, 5],270DEFAULT_CHAR_LIMIT,271{ costFnOverride }272);273274await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));275});276277test('keep constructor - 2', async () => {278279function costFnOverride(node: RemovableNode, currentScore: number) {280return node.kind === 'constructor' ? 0 : currentScore;281}282283const filename = 'map.ts';284285const result = await generateSummarizedDocument(286fromFixtureOld(filename, 'typescript'),287[671, 0, 725, 1],288DEFAULT_CHAR_LIMIT,289{ costFnOverride }290);291292await expect(result.text).toMatchFileSnapshot(summarizedDocPathInFixture(filename));293});294});295296297describe('createSummarizedDocuments', () => {298299test('summarize two document equally', async () => {300301302const filenames: string[] = [303'editorGroupWatermark.ts',304'strings.test-example.ts'305];306307const files = [308await loadFile({ filePath: fixture(filenames[0]), languageId: 'typescript' }),309await loadFile({ filePath: fixture(filenames[1]), languageId: 'typescript' })310];311312const docs = await generateSummarizedDocuments([313{314filePromise: files[0],315selection: [24, 0]316},317{318filePromise: files[1],319selection: [344, 0]320},321]);322323expect(docs.length).toBe(2);324325for (let i = 0; i < docs.length; i++) {326const document = docs[i];327expect(document.originalText).toBe(files[i].contents);328await expect(document.text).toMatchFileSnapshot(summarizedDocPathInFixture(filenames[i] + '.round1'));329}330});331332test('summarize two document un-equally', async () => {333334const filenames: string[] = [335'editorGroupWatermark.ts',336'strings.test-example.ts'337];338339const files = [340await loadFile({ filePath: fixture(filenames[0]), languageId: 'typescript' }),341await loadFile({ filePath: fixture(filenames[1]), languageId: 'typescript' })342];343344const docs = await generateSummarizedDocuments([345{346filePromise: files[0],347selection: [24, 0]348},349{350filePromise: files[1],351selection: [344, 0]352},353], 5000, {354costFnOverride(node, currentCost, document) {355if (document.uri.path.includes(filenames[1])) {356return 1;357}358return 100;359},360});361362// small budget, BIASED scores363364expect(docs.length).toBe(2);365366for (let i = 0; i < docs.length; i++) {367const document = docs[i];368expect(document.originalText).toBe(files[i].contents);369await expect(document.text).toMatchFileSnapshot(summarizedDocPathInFixture(filenames[i] + '.round2'));370}371});372373374test.skip('run on repositories', async () => {375376const N_FILES_LIMIT = 1500;377378const reposWithLangs: Record<string, { repoPath: string; language: string }> = {379'vscode-copilot': { repoPath: path.join(__dirname, '../../../../../src/'), language: 'typescript' },380'llama.cpp': { repoPath: path.join(__dirname, '../../../../../../llama.cpp/src'), language: 'cpp' },381};382383const langToExts: Record<string, string[]> = {384'cpp': ['cpp', 'h'],385'typescript': ['ts', 'tsx'],386};387388const { repoPath, language } = reposWithLangs['vscode-copilot'];389const exts = langToExts[language];390391async function* traverseDirectory(pathToDir: string): AsyncGenerator<string> {392const dirEntries = await fs.readdir(pathToDir, { withFileTypes: true });393for (const entry of dirEntries) {394if (entry.isDirectory()) {395yield* traverseDirectory(path.join(pathToDir, entry.name));396} else if (exts.some(ext => !entry.parentPath.includes('fixture') && !entry.name.includes('.summarized.') && entry.name.endsWith('.' + ext))) {397console.log(path.join(pathToDir, entry.name));398yield path.join(pathToDir, entry.name);399}400}401}402403let i = -1;404for await (const filePath of traverseDirectory(repoPath)) {405++i;406try {407if (i > N_FILES_LIMIT) {408break;409}410const file = await loadFile({ filePath, languageId: language });411const fileLines = file.contents.split('\n');412const selection: [number, number] = [Math.floor(fileLines.length / 2), Math.floor(fileLines[Math.floor(fileLines.length / 2)].length / 2)];413const result = await generateSummarizedDocument(file, selection, 400, { alwaysUseEllipsisForElisions: true });414await expect(result.text).toMatchFileSnapshot(getSummarizedSnapshotPath(file));415} catch (e) {416console.log(`processing ${filePath} threw error`, e);417}418}419});420421});422423424