Path: blob/main/extensions/copilot/test/inline/inlineEditCode.stest.ts
13388 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*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { EditCodeIntent } from '../../src/extension/intents/node/editCodeIntent';6import { TestingServiceCollection } from '../../src/platform/test/node/services';7import { Selection } from '../../src/vscodeTypes';8import { NonExtensionConfiguration, ssuite, stest } from '../base/stest';9import { KnownDiagnosticProviders } from '../simulation/diagnosticProviders';10import { simulateInlineChat, simulateInlineChatIntent } from '../simulation/inlineChatSimulator';11import { assertContainsAllSnippets, assertNoDiagnosticsAsync, assertNoElidedCodeComments, assertNoSyntacticDiagnosticsAsync, findTextBetweenMarkersFromTop } from '../simulation/outcomeValidators';12import { simulatePanelCodeMapper } from '../simulation/panelCodeMapperSimulator';13import { assertInlineEdit, assertInlineEditShape, assertNoOccurrence, assertOccursOnce, assertSomeStrings, extractInlineReplaceEdits, fromFixture, toFile } from '../simulation/stestUtil';14import { EditTestStrategy, IScenario } from '../simulation/types';1516function executeEditTest(17strategy: EditTestStrategy,18testingServiceCollection: TestingServiceCollection,19scenario: IScenario20): Promise<void> {21if (strategy === EditTestStrategy.Inline) {22return simulateInlineChat(testingServiceCollection, scenario);23} else if (strategy === EditTestStrategy.InlineChatIntent) {24return simulateInlineChatIntent(testingServiceCollection, scenario);25} else {26return simulatePanelCodeMapper(testingServiceCollection, scenario, strategy);27}28}2930function forInlineChatIntent(callback: (strategy: EditTestStrategy, location: 'inline' | 'panel', variant: string | undefined, configurations?: NonExtensionConfiguration[]) => void): void {31callback(EditTestStrategy.InlineChatIntent, 'inline', '-InlineChatIntent', [['chat.agent.autoFix', false]]);32}3334forInlineChatIntent((strategy, location, variant, nonExtensionConfigurations) => {3536ssuite({ title: `edit${variant}`, location }, () => {37stest({ description: 'Context Outline: TypeScript between methods', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {38return executeEditTest(strategy, testingServiceCollection, {39files: [fromFixture('vscode/codeEditorWidget.ts')],40queries: [41{42file: 'codeEditorWidget.ts',43selection: [211, 0, 213, 0],44query: 'convert private property to lowercase',45expectedIntent: EditCodeIntent.ID,46validate: async (outcome, workspace, accessor) => {47assertInlineEdit(outcome);48await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');49assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');50const edit = assertInlineEditShape(outcome, {51line: 211,52originalLength: 2,53modifiedLength: 2,54});55assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['_onkeyup']);56assertNoElidedCodeComments(outcome.fileContents);57}58}59]60});61});6263stest({ description: 'Context Outline: TypeScript in method', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {64return executeEditTest(strategy, testingServiceCollection, {65files: [fromFixture('vscode/codeEditorWidget.ts')],66queries: [67{68file: 'codeEditorWidget.ts',69selection: [1085, 2, 1089, 3],70query: 'log to console in case the action is missing',71expectedIntent: EditCodeIntent.ID,72validate: async (outcome, workspace, accessor) => {73assertInlineEdit(outcome);74await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');75const edit = assertInlineEditShape(outcome, [{76line: 1089,77originalLength: 0,78modifiedLength: 2,79}, {80line: 1090,81originalLength: 0,82modifiedLength: 1,83}, {84line: 1090,85originalLength: 9,86modifiedLength: 1,87}, {88line: 1091,89originalLength: 0,90modifiedLength: 2,91}, {92line: 1091,93originalLength: 8,94modifiedLength: 1,95}]);96assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['console']);97assertNoElidedCodeComments(outcome.fileContents);98}99}100]101});102});103104stest({ description: 'issue #404: Add a cat to a comment', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {105return executeEditTest(strategy, testingServiceCollection, {106files: [fromFixture('ghpr/commands.ts')],107queries: [108{109file: 'commands.ts',110selection: [45, 0, 45, 79],111query: 'Add a cat to this comment',112expectedIntent: EditCodeIntent.ID,113validate: async (outcome, workspace, accessor) => {114if (outcome.type === 'conversational') {115// ok116assert.ok(true);117return;118}119assertInlineEdit(outcome);120assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');121await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');122const edit = assertInlineEditShape(outcome, [{123line: 45,124originalLength: 1,125modifiedLength: 1,126}, {127line: 46,128originalLength: 0,129modifiedLength: undefined,130}]);131assertSomeStrings(edit.changedModifiedLines.join('\n'), ['🐱', '( o.o )']);132assertNoElidedCodeComments(outcome.fileContents);133}134}135]136});137});138139140stest({ description: 'issue #405: "make simpler" query is surprising', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {141// SKIPPED because of the error below142// <NO REPLY> {"type":"failed","reason":"Request Failed: 400 {\"error\":{\"message\":\"prompt token count of 13613 exceeds the limit of 12288\",\"code\":\"model_max_prompt_tokens_exceeded\"}}\n","requestId":"2e91a4a5-366b-4cae-b9c8-cce59d06a7bb","serverRequestId":"EA6B:3DFF07:151BC22:18DE2D8:68F22ED4","isCacheHit":false,"copilotFunctionCalls":[]}143if (1) {144throw new Error('SKIPPED');145}146147return executeEditTest(strategy, testingServiceCollection, {148files: [fromFixture('vscode/extHost.api.impl.ts')],149queries: [150{151file: 'extHost.api.impl.ts',152selection: [696, 0, 711, 0],153query: 'make simpler',154expectedIntent: EditCodeIntent.ID,155validate: async (outcome, workspace, accessor) => {156if (outcome.type === 'conversational') {157// acceptable158assert.ok(true);159return;160}161assertInlineEdit(outcome);162assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');163await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');164assertNoElidedCodeComments(outcome.fileContents);165}166}167]168});169});170171stest({ description: 'issue #246: Add comment sends request to sidebar', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {172return executeEditTest(strategy, testingServiceCollection, {173files: [fromFixture('vscode/vscode.proposed.notebookDocumentWillSave.d.ts')],174queries: [175{176file: 'vscode.proposed.notebookDocumentWillSave.d.ts',177selection: [52, 5, 52, 5],178visibleRanges: [[0, 65]],179query: 'add comment',180expectedIntent: EditCodeIntent.ID,181validate: async (outcome, workspace, accessor) => {182assertInlineEdit(outcome);183await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');184assertNoElidedCodeComments(outcome.fileContents);185}186}187]188});189});190191stest({ description: 'issue #4151: Rewrite the selection to use async/await', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {192return executeEditTest(strategy, testingServiceCollection, {193files: [fromFixture('edit-asyncawait-4151/index.ts')],194queries: [195{196file: 'index.ts',197selection: [47, 0, 57, 3],198query: 'Rewrite the selection to use async/await',199expectedIntent: EditCodeIntent.ID,200validate: async (outcome, workspace, accessor) => {201assertInlineEdit(outcome);202await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');203const edit = assertInlineEditShape(outcome, {204line: 47,205originalLength: 10,206modifiedLength: 10,207});208assert.deepStrictEqual(209edit.changedModifiedLines.join('\n'),210`app.get('/episodes/:id/summary', async (req: Request, res: Response) => {\n` +211' try {\n' +212' const response = await fetch(`${process.env.PODCAST_URL}episodes/${req.params.id}`);\n' +213' const json: Episode = await response.json();\n' +214' const summary = json.description;\n' +215' res.send({ summary });\n' +216' } catch (error) {\n' +217' console.log(error);\n' +218' res.status(500).send({ error });\n' +219' }'220);221assertNoElidedCodeComments(outcome.fileContents);222},223},224],225});226});227228stest({ description: 'issue #4149: If ChatGPT makes the request, send only the first 20 episodes', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {229return executeEditTest(strategy, testingServiceCollection, {230files: [231fromFixture('edit-slice-4149/index.ts'),232],233queries: [234{235file: 'index.ts',236selection: [44, 1],237visibleRanges: [[24, 64]],238query: 'If ChatGPT user agent makes the request, send only the first 20 episodes',239expectedIntent: EditCodeIntent.ID,240validate: async (outcome, workspace, accessor) => {241assertInlineEdit(outcome);242await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');243const edit = extractInlineReplaceEdits(outcome);244assert.ok(edit, 'unexpected identical files');245const newText = edit.allModifiedLines.join('\n');246assert.ok(247newText.includes('\'user-agent\'')248|| newText.includes('\'User-Agent\'')249);250assert.ok(!newText.includes('limit: \'20\''));251assert.ok(newText.includes('slice(0, 20)'));252assert.ok(newText.includes('\'ChatGPT\''));253assertNoElidedCodeComments(outcome.fileContents);254},255},256],257});258});259260stest({ description: 'issue #3759: add type', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {261// SKIPPED because of the error below262// <NO REPLY> {"type":"failed","reason":"Request Failed: 400 {\"error\":{\"message\":\"prompt token count of 13613 exceeds the limit of 12288\",\"code\":\"model_max_prompt_tokens_exceeded\"}}\n","requestId":"2e91a4a5-366b-4cae-b9c8-cce59d06a7bb","serverRequestId":"EA6B:3DFF07:151BC22:18DE2D8:68F22ED4","isCacheHit":false,"copilotFunctionCalls":[]}263if (1) {264throw new Error('SKIPPED');265}266return executeEditTest(strategy, testingServiceCollection, {267files: [268fromFixture('edit-add-explicit-type-issue-3759/pullRequestModel.ts'),269],270queries: [271{272file: 'pullRequestModel.ts',273selection: [1071, 0],274visibleRanges: [[1051, 1091]],275query: 'Add types to `reviewRequiredCheck`',276expectedIntent: EditCodeIntent.ID,277validate: async (outcome, workspace, accessor) => {278assertInlineEdit(outcome);279assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');280await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');281const edit = assertInlineEditShape(outcome, {282line: 1071,283originalLength: 1,284modifiedLength: 1,285});286let text = findTextBetweenMarkersFromTop(edit.changedModifiedLines.join('\n'), 'const reviewRequiredCheck', '= await this._getReviewRequiredCheck();');287assert(text);288text = text.trim();289assert(text.length > 3);290assertNoElidedCodeComments(outcome.fileContents);291},292},293],294});295});296297stest({ description: 'issue #1198: Multi-lingual queries throw off the inline response formatting', language: 'python', nonExtensionConfigurations }, (testingServiceCollection) => {298return executeEditTest(strategy, testingServiceCollection, {299files: [fromFixture('edit-issue-1198/main.py')],300queries: [301{302file: 'main.py',303selection: [1, 0, 7, 0],304query: 'Translate to German',305fileIndentInfo: { insertSpaces: true, tabSize: 4 },306expectedIntent: EditCodeIntent.ID,307validate: async (outcome, workspace, accessor) => {308if (outcome.type === 'conversational') {309// This is acceptable because translating is not strictly a development action310assert.ok(true);311return;312}313assertInlineEdit(outcome);314const expectedLines = [315`{"id": "1", "text": "Roter Karabiner, groß, Edelstahl", "url": None},`,316`{"id": "2", "text": "Blauer kleiner Karabiner", "url": None},`,317[318`{"id": "3", "text": "Ganzjahres-Wanderhose", "url": None},`,319`{"id": "3", "text": "Ganzjahreshose zum Wandern", "url": None},`,320],321[322`{"id": "4", "text": "Schwarze Lederschuhe, Größe 10", "url": None},`,323`{"id": "4", "text": "Schwarze Lederstiefel, Größe 10", "url": None},`,324`{"id": "4", "text": "Schwarze Lederstiefel, Größe 44", "url": None},`,325],326[327`{"id": "5", "text": "Gelbe wasserdichte Jacke, mittelgroß", "url": None},`,328`{"id": "5", "text": "Gelbe wasserdichte Jacke, mittel", "url": None},`,329`{"id": "5", "text": "Gelbe wasserdichte Jacke, Größe M", "url": None},`,330`{"id": "5", "text": "Gelbe wasserdichte Jacke, Medium", "url": None},`,331],332[333`{"id": "6", "text": "Grünes Campingzelt, 4 Personen", "url": None}`,334`{"id": "6", "text": "Grünes Campingzelt, 4-Personen", "url": None}`,335`{"id": "6", "text": "Grünes Campingzelt, für 4 Personen", "url": None}`,336]337];338const actualLines = outcome.fileContents.split('\n').map(s => s.trim()).slice(1, 7);339for (let i = 0; i < expectedLines.length; i++) {340const expected = expectedLines[i];341const actual = actualLines[i];342if (Array.isArray(expected)) {343assert.ok(expected.includes(actual), `Line ${i + 2} does not match any expected variant. Actual: "${actual}"`);344} else {345assert.strictEqual(actual, expected);346}347}348assertNoElidedCodeComments(outcome.fileContents);349},350},351],352});353});354355stest({ description: 'refactor forloop, but only selected one', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {356const selection: [number, number, number, number] = [109, 8, 125, 9];357return executeEditTest(strategy, testingServiceCollection, {358files: [fromFixture('edit-refactor-loop/index.ts')],359queries: [{360file: 'index.ts',361selection: selection,362query: 'change for-of loop to use an index',363expectedIntent: EditCodeIntent.ID,364validate: async (outcome, workspace, accessor) => {365assertInlineEdit(outcome);366await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');367const edit = assertInlineEditShape(outcome, [{368line: 109,369originalLength: 16,370modifiedLength: 16,371}, {372line: 109,373originalLength: 14,374modifiedLength: 14,375}, {376line: 109,377originalLength: 1,378modifiedLength: 2,379}]);380assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['for (let i = 0; i < groups.length; i++)']);381assertNoElidedCodeComments(outcome.fileContents);382}383}]384});385});386387stest({ description: 'convert ternary to if/else in short function', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {388return executeEditTest(strategy, testingServiceCollection, {389files: [390fromFixture('edit-convert-ternary-to-if-else/index.ts'),391],392queries: [393{394file: 'index.ts',395selection: [4, 28],396visibleRanges: [[0, 14]],397query: 'convert to if/else',398expectedIntent: EditCodeIntent.ID,399validate: async (outcome, workspace, accessor) => {400await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');401assertInlineEdit(outcome);402// Only the ternary expression should be replaced403const edit = assertInlineEditShape(outcome, [{404line: 4,405originalLength: 1,406modifiedLength: undefined,407}, {408line: 4,409originalLength: 3,410modifiedLength: undefined,411}]);412assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['if', 'else']);413assertNoElidedCodeComments(outcome.fileContents);414}415}416]417});418});419420stest({ description: 'edit: add toString1', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {421return executeEditTest(strategy, testingServiceCollection, {422files: [423fromFixture('edit-add-toString/index.ts'),424],425queries: [426{427file: 'index.ts',428selection: [53, 1],429visibleRanges: [[33, 73]],430query: 'add toString',431expectedIntent: EditCodeIntent.ID,432validate: async (outcome, workspace, accessor) => {433await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');434assertInlineEdit(outcome);435assertNoElidedCodeComments(outcome.fileContents);436}437}438]439});440});441442stest({ description: 'edit: add toString2', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {443return executeEditTest(strategy, testingServiceCollection, {444files: [445fromFixture('edit-add-toString2/index.ts')446],447queries: [448{449file: 'index.ts',450selection: [54, 1],451visibleRanges: [[34, 74]],452query: 'add toString()',453expectedIntent: EditCodeIntent.ID,454validate: async (outcome, workspace, accessor) => {455await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');456assertInlineEdit(outcome);457assertNoElidedCodeComments(outcome.fileContents);458}459}460]461});462});463464stest({ description: 'edit: add enum variant', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {465return executeEditTest(strategy, testingServiceCollection, {466files: [467fromFixture('edit-add-enum-variant/index.ts'),468],469queries: [470{471file: 'index.ts',472selection: [8, 9],473visibleRanges: [[0, 32]],474query: 'add enum variant NearBottom',475expectedIntent: EditCodeIntent.ID,476validate: async (outcome, workspace, accessor) => {477await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');478assertInlineEdit(outcome);479assertNoElidedCodeComments(outcome.fileContents);480}481}482]483});484});485486function verifyTsImportStatementsAreTogether(fileContents: string): boolean {487const lines = fileContents.split('\n');488let i = 0;489while (i < lines.length && !lines[i].trim().startsWith('import ')) {490i++;491}492while (i < lines.length && lines[i].trim().startsWith('import ')) {493i++;494}495while (i < lines.length && !lines[i].trim().startsWith('import ')) {496i++;497}498if (lines.length !== i) {499return false;500}501return true;502}503504stest({ description: 'edit: import assert', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {505return executeEditTest(strategy, testingServiceCollection, {506files: [507fromFixture('edit-import-assert/index.ts'),508],509queries: [510{511file: 'index.ts',512selection: [47, 14],513query: 'use the assert library to check that element is defined',514expectedIntent: EditCodeIntent.ID,515validate: async (outcome, workspace, accessor) => {516assertInlineEdit(outcome);517assert(verifyTsImportStatementsAreTogether(outcome.fileContents));518await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');519assertNoElidedCodeComments(outcome.fileContents);520}521}522]523});524});525526stest({ description: 'edit: import assert 2', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {527return executeEditTest(strategy, testingServiceCollection, {528files: [529fromFixture('edit-import-assert2/index.ts'),530],531queries: [532{533file: 'index.ts',534selection: [5, 0],535query: 'use assert to check that file is defined',536expectedIntent: EditCodeIntent.ID,537validate: async (outcome, workspace, accessor) => {538assertInlineEdit(outcome);539await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');540541function countOccurences(str: string, substr: string): number {542return str.split(substr).length - 1;543}544545assert.deepStrictEqual(546countOccurences(outcome.fileContents, 'ises'),547countOccurences(outcome.fileContents, 'promises')548);549assertNoElidedCodeComments(outcome.fileContents);550}551}552]553});554});555556stest({ description: 'issue #2431: Inline Chat follow-up tweak ends up in noop text-only answer', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {557return executeEditTest(strategy, testingServiceCollection, {558files: [559fromFixture('vscode/editorGroupWatermark.ts'),560],561queries: [562{563file: 'editorGroupWatermark.ts',564selection: [24, 0],565query: 'Add a title to each entry, expanding what the feature does',566expectedIntent: EditCodeIntent.ID,567validate: async (outcome, workspace, accessor) => {568assertInlineEdit(outcome);569await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');570assertNoElidedCodeComments(outcome.fileContents);571},572},573{574query: 'use localize and ALL CAPS for the title',575expectedIntent: EditCodeIntent.ID,576validate: async (outcome, workspace, accessor) => {577assertInlineEdit(outcome);578await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');579assertNoElidedCodeComments(outcome.fileContents);580},581}582],583});584});585586stest({ description: 'Inline chat does not leak system prompt', language: 'json', nonExtensionConfigurations }, (testingServiceCollection) => {587return executeEditTest(strategy, testingServiceCollection, {588files: [589fromFixture('gen-json/test.json'),590],591queries: [592{593file: 'test.json',594selection: [0, 0],595query: 'edit this file to contain json, use tabs',596expectedIntent: EditCodeIntent.ID,597validate: async (outcome, workspace, accessor) => {598assertInlineEdit(outcome);599['assistant', 'Microsoft', 'AI'].forEach((text) => {600assert.strictEqual(outcome.fileContents.includes(text), false);601});602assertNoElidedCodeComments(outcome.fileContents);603},604}605],606});607});608609stest({ description: 'Inline chat touching code outside of my selection #2988', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {610611const selection = new Selection(107, 1, 132, 7);612613return executeEditTest(strategy, testingServiceCollection, {614files: [615fromFixture('edit/issue-2988/pseudoStartStopConversationCallback.test.ts'),616],617queries: [618{619file: 'pseudoStartStopConversationCallback.test.ts',620selection: [selection.start.line, selection.start.character, selection.end.line, selection.end.character],621query: 'rewrite these asserts as one assert on an array',622expectedIntent: EditCodeIntent.ID,623validate: async (outcome, workspace, accessor) => {624assertInlineEdit(outcome);625await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');626627assertInlineEditShape(outcome, [{628line: 123,629originalLength: 9,630modifiedLength: undefined,631}, {632line: 125,633originalLength: 7,634modifiedLength: undefined,635}, {636line: 125,637originalLength: 9,638modifiedLength: undefined,639}]);640assertNoElidedCodeComments(outcome.fileContents);641}642}643]644});645});646647stest({ description: 'Inline chat touching code outside of my selection #2988 with good selection', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {648649const selection = new Selection(125, 0, 132, 0);650651return executeEditTest(strategy, testingServiceCollection, {652files: [653fromFixture('edit/issue-2988/pseudoStartStopConversationCallback.test.ts'),654],655queries: [656{657file: 'pseudoStartStopConversationCallback.test.ts',658selection: [selection.start.line, selection.start.character, selection.end.line, selection.end.character],659query: 'rewrite these asserts as one assert on an array',660expectedIntent: EditCodeIntent.ID,661validate: async (outcome, workspace, accessor) => {662assertInlineEdit(outcome);663await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');664665const edit = assertInlineEditShape(outcome, [{666line: 125,667originalLength: 7,668modifiedLength: undefined,669}, {670line: 125,671originalLength: 9,672modifiedLength: undefined,673}]);674assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['assert.deepStrictEqual', '[', ']']);675assertNoElidedCodeComments(outcome.fileContents);676}677}678]679});680});681682stest({ description: 'issue #2946: Inline chat markers don\'t work', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {683return executeEditTest(strategy, testingServiceCollection, {684files: [685fromFixture('editing/math.js'),686],687queries: [688{689file: 'math.js',690selection: [17, 0, 32, 1],691query: 'use recursion',692expectedIntent: EditCodeIntent.ID,693validate: async (outcome, workspace, accessor) => {694const edit = assertInlineEditShape(outcome, [{695line: 21,696originalLength: 11,697modifiedLength: 1,698}, {699line: 22,700originalLength: 10,701modifiedLength: 1,702}]);703assertContainsAllSnippets(edit.changedModifiedLines[0], ['return', 'doSomething', 'n - 1', 'n - 2']);704assertInlineEdit(outcome);705assertNoElidedCodeComments(outcome.fileContents);706}707}708],709});710});711712stest({ description: 'issue #3257: Inline chat ends up duplicating code', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {713return executeEditTest(strategy, testingServiceCollection, {714files: [715fromFixture('editing/mainThreadChatAgents2.ts'),716],717queries: [718{719file: 'mainThreadChatAgents2.ts',720selection: [100, 3],721visibleRanges: [[80, 120]],722query: 'add a function for welcome message',723expectedIntent: EditCodeIntent.ID,724validate: async (outcome, workspace, accessor) => {725const edit = assertInlineEditShape(outcome, {726line: 100,727originalLength: 1,728modifiedLength: undefined,729});730assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), [': async']);731assertInlineEdit(outcome);732assertNoElidedCodeComments(outcome.fileContents);733}734}735],736});737});738739stest({ description: 'issue release#275: Inline Diff refinement causes massive duplication of code', language: 'csharp', nonExtensionConfigurations }, (testingServiceCollection) => {740return executeEditTest(strategy, testingServiceCollection, {741files: [742fromFixture('edit/issue-release-275/BasketService.cs')743],744queries: [745{746file: 'BasketService.cs',747selection: [0, 0, 83, 1],748query: 'replace ardalis guard classes with vanilla null checking and remove dependency on ardalis throughout the class',749expectedIntent: EditCodeIntent.ID,750validate: async (outcome, workspace, accessor) => {751assertInlineEdit(outcome);752753assertOccursOnce(outcome.fileContents, 'public class BasketService');754const fileContentsWithoutComments = outcome.fileContents.replace(/\/\/.*/g, '');755assertNoOccurrence(fileContentsWithoutComments, 'using Ardalis.GuardClauses;');756assertNoOccurrence(fileContentsWithoutComments, 'using Ardalis.Result;');757assert.ok(outcome.fileContents.split(/\r\n|\r|\n/g).length < 95, 'file stays under 95 lines');758assertNoElidedCodeComments(outcome.fileContents);759}760}761],762});763});764765stest({ description: 'issue #5755: Inline edits go outside the selection', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {766return executeEditTest(strategy, testingServiceCollection, {767files: [768fromFixture('edit/issue-5755/vscode.proposed.chatParticipantAdditions.d.ts')769],770queries: [771{772file: 'vscode.proposed.chatParticipantAdditions.d.ts',773selection: [158, 0, 166, 0],774query: 'make the comment more readable',775expectedIntent: EditCodeIntent.ID,776validate: async (outcome, workspace, accessor) => {777assertInlineEdit(outcome);778assertInlineEditShape(outcome, [{779line: 159,780originalLength: 2,781modifiedLength: 2,782}, {783line: 159,784originalLength: 4,785modifiedLength: 4,786}, {787line: 159,788originalLength: 5,789modifiedLength: 4,790}, {791line: 159,792originalLength: 4,793modifiedLength: 5,794}, {795line: 162,796originalLength: 1,797modifiedLength: 1,798}]);799assertNoElidedCodeComments(outcome.fileContents);800}801}802],803});804});805806stest({ description: 'issue #4302: Code doesn\'t come with backticks', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {807return executeEditTest(strategy, testingServiceCollection, {808files: [809fromFixture('edit/4302.ts')810],811queries: [812{813file: '4302.ts',814selection: [12, 0, 23, 0],815query: 'put it all in one line',816expectedIntent: EditCodeIntent.ID,817validate: async (outcome, workspace, accessor) => {818assertInlineEdit(outcome);819const edit = assertInlineEditShape(outcome, {820line: 12,821originalLength: 11,822modifiedLength: undefined,823});824assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['clojure', 'coffeescript', 'fsharp', 'latex', 'markdown', 'pug', 'python', 'sql', 'yaml']);825assertNoElidedCodeComments(outcome.fileContents);826}827}828],829});830});831832stest({ description: 'issue #5710: Code doesn\'t come with backticks', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {833return executeEditTest(strategy, testingServiceCollection, {834files: [835fromFixture('edit/5710.ts')836],837queries: [838{839file: '5710.ts',840selection: [7, 66, 10, 5],841query: 'Implement the stubbed-out class members for BinaryExpression with a useful implementation.',842expectedIntent: EditCodeIntent.ID,843validate: async (outcome, workspace, accessor) => {844assertInlineEdit(outcome);845await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');846const edit = assertInlineEditShape(outcome, {847line: 9,848originalLength: 1,849modifiedLength: 1,850});851assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['this.left', 'this.operator', 'this.right']);852assertNoElidedCodeComments(outcome.fileContents);853}854}855],856});857});858859stest({ description: 'issue #3575: Inline Chat in function expands to delete whole file', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {860return executeEditTest(strategy, testingServiceCollection, {861files: [862fromFixture('edit/3575.ts')863],864queries: [865{866file: '3575.ts',867selection: [51, 9, 51, 9],868visibleRanges: [[14, 54]],869query: 'make faster',870expectedIntent: EditCodeIntent.ID,871validate: async (outcome, workspace, accessor) => {872assertInlineEdit(outcome);873await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');874assertInlineEditShape(outcome, [{875line: 47,876originalLength: 4,877modifiedLength: undefined,878}, {879line: 47,880originalLength: 5,881modifiedLength: undefined,882}, {883line: 45,884originalLength: 9,885modifiedLength: 1,886}, {887line: 39,888originalLength: 13,889modifiedLength: undefined,890}, {891line: 39,892originalLength: 15,893modifiedLength: undefined,894}, {895line: 46,896originalLength: 6,897modifiedLength: undefined,898}]);899// assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['break']);900assertNoElidedCodeComments(outcome.fileContents);901}902}903],904});905});906907stest({ description: 'edit for cpp', language: 'cpp', nonExtensionConfigurations }, (testingServiceCollection) => {908return executeEditTest(strategy, testingServiceCollection, {909files: [910fromFixture('cpp/basic/main.cpp')911],912queries: [913{914file: 'main.cpp',915selection: [4, 0, 17, 0],916query: 'add a parameter to getName that controls whether or not to ask for a last name',917expectedIntent: EditCodeIntent.ID,918validate: async (outcome, workspace, accessor) => {919assertInlineEdit(outcome);920assertContainsAllSnippets(outcome.fileContents, ['bool', 'lastName', 'if']);921await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'cpp');922assertNoElidedCodeComments(outcome.fileContents);923}924}925],926});927});928929stest({ description: 'edit for macro', language: 'cpp', nonExtensionConfigurations }, (testingServiceCollection) => {930return executeEditTest(strategy, testingServiceCollection, {931files: [932fromFixture('cpp/headers/abi_macros.hpp'),933],934queries: [935{936file: 'abi_macros.hpp',937selection: [0, 0, 100, 0],938query: 'Update the version to 4.2.4',939expectedIntent: EditCodeIntent.ID,940validate: async (outcome, workspace, accessor) => {941assertInlineEdit(outcome);942assertContainsAllSnippets(outcome.fileContents, ['#define NLOHMANN_JSON_VERSION_MAJOR 4', '#define NLOHMANN_JSON_VERSION_MINOR 2', '#define NLOHMANN_JSON_VERSION_PATCH 4']);943await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'cpp');944assertNoElidedCodeComments(outcome.fileContents);945}946}947],948});949});950951stest({ description: 'merge markdown sections', language: 'markdown', nonExtensionConfigurations }, (testingServiceCollection) => {952return executeEditTest(strategy, testingServiceCollection, {953files: [954fromFixture('edit/markdown/README.md')955],956queries: [957{958file: 'README.md',959selection: [11, 0, 32, 0],960query: 'merge these two sections in a single one',961expectedIntent: EditCodeIntent.ID,962validate: async (outcome, workspace, accessor) => {963assertInlineEdit(outcome);964assertContainsAllSnippets(outcome.fileContents, ['npm install monaco-editor\n```']);965assertNoElidedCodeComments(outcome.fileContents);966}967}968],969});970});971972stest({ description: 'issue #5899: make this code more efficient inside markdown', language: 'markdown', nonExtensionConfigurations }, (testingServiceCollection) => {973return executeEditTest(strategy, testingServiceCollection, {974files: [975fromFixture('edit/markdown/explanation.md')976],977queries: [978{979file: 'explanation.md',980selection: [4, 0, 17, 0],981visibleRanges: [[0, 23]],982query: 'make this code more efficient',983expectedIntent: EditCodeIntent.ID,984validate: async (outcome, workspace, accessor) => {985assertInlineEdit(outcome);986assertOccursOnce(outcome.fileContents, 'Here is an example');987assertNoElidedCodeComments(outcome.fileContents);988}989}990],991});992});993994stest({ description: 'issue #6276', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {995return executeEditTest(strategy, testingServiceCollection, {996files: [997fromFixture('edit/6276.ts')998],999queries: [1000{1001file: '6276.ts',1002selection: [162, 0, 163, 39],1003query: 'declare as fields',1004expectedIntent: 'edit',1005validate: async (outcome, workspace, accessor) => {1006await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1007assertInlineEdit(outcome);1008assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');1009assertNoElidedCodeComments(outcome.fileContents);1010}1011}1012]1013});1014});10151016stest({ description: 'issue #7487', language: 'typescriptreact', nonExtensionConfigurations }, (testingServiceCollection) => {1017return executeEditTest(strategy, testingServiceCollection, {1018files: [1019fromFixture('edit/issue-7487/EditForm.tsx')1020],1021queries: [1022{1023file: 'EditForm.tsx',1024selection: [138, 0, 147, 17],1025query: 'smaller lighter text with more padding',1026diagnostics: 'tsc',1027expectedIntent: 'edit',1028validate: async (outcome, workspace, accessor) => {1029assertInlineEdit(outcome);1030await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');1031assertInlineEditShape(outcome, [{1032line: 142,1033originalLength: 1,1034modifiedLength: 1,1035}]);1036assertNoElidedCodeComments(outcome.fileContents);1037}1038}1039]1040});1041});10421043stest({ description: 'issue #6329', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {1044return executeEditTest(strategy, testingServiceCollection, {1045files: [toFile({1046filePath: fromFixture('edit/issue-6329/math.js')1047})],1048queries: [1049{1050file: 'math.js',1051selection: [36, 0, 36, 0],1052query: 'use assert lib from nodejs to check that N is positive',1053diagnostics: 'tsc',1054expectedIntent: 'edit',1055validate: async (outcome, workspace, accessor) => {1056await assertNoDiagnosticsAsync(accessor, outcome, workspace, 'tsc');1057assertInlineEdit(outcome);1058assertOccursOnce(outcome.fileContents, 'isPrime');1059assertNoElidedCodeComments(outcome.fileContents);1060}1061}1062]1063});1064});10651066stest({ description: 'issue #7202', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1067return executeEditTest(strategy, testingServiceCollection, {1068files: [1069fromFixture('edit/issue-7202/languageModelToolsContribution.ts')1070],1071queries: [1072{1073file: 'languageModelToolsContribution.ts',1074selection: [112, 127, 112, 127],1075visibleRanges: [[92, 132]],1076query: 'make this message match the format of the log message below',1077diagnostics: 'tsc',1078expectedIntent: 'edit',1079validate: async (outcome, workspace, accessor) => {1080assertInlineEdit(outcome);1081await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');10821083const edit = assertInlineEditShape(outcome, {1084line: 112,1085originalLength: 1,1086modifiedLength: 1,1087});1088assertContainsAllSnippets(edit.changedModifiedLines.join('\n'), ['Extension', 'CANNOT register']);1089assertNoElidedCodeComments(outcome.fileContents);1090}1091}1092]1093});1094});10951096stest({ description: 'issue #6469', language: 'css', nonExtensionConfigurations }, (testingServiceCollection) => {1097return executeEditTest(strategy, testingServiceCollection, {1098files: [fromFixture('edit/issue-6469/inlineChat.css')],1099queries: [1100{1101file: 'inlineChat.css',1102selection: [80, 0, 81, 17],1103query: 'combine this',1104expectedIntent: 'edit',1105validate: async (outcome, workspace, accessor) => {1106assertInlineEdit(outcome);1107assertInlineEditShape(outcome, [{1108line: 80,1109originalLength: 2,1110modifiedLength: 1,1111}]);1112assertNoElidedCodeComments(outcome.fileContents);1113}1114}1115]1116});1117});11181119stest({ description: 'issue #6956', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {1120return executeEditTest(strategy, testingServiceCollection, {1121files: [fromFixture('generate/issue-6956/.eslintrc.js')],1122queries: [1123{1124file: '.eslintrc.js',1125selection: [23, 6, 23, 6],1126query: 'turn prefer-const off for destructured variables',1127diagnostics: 'tsc',1128expectedIntent: 'generate',1129validate: async (outcome, workspace, accessor) => {1130assertInlineEdit(outcome);1131await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1132assertNoElidedCodeComments(outcome.fileContents);1133}1134}1135]1136});1137});11381139stest({ description: 'Issue #7282', language: 'javascript', nonExtensionConfigurations }, (testingServiceCollection) => {1140return executeEditTest(strategy, testingServiceCollection, {1141files: [fromFixture('edit/issue-7282/math.js')],1142queries: [1143{1144file: 'math.js',1145selection: [1, 0, 8, 0],1146query: 'avoid recursion',1147diagnostics: 'tsc',1148expectedIntent: 'edit',1149validate: async (outcome, workspace, accessor) => {1150assertInlineEdit(outcome);1151assertNoElidedCodeComments(outcome.fileContents);1152}1153}1154]1155});1156});11571158stest({ description: 'issue #6973', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1159return executeEditTest(strategy, testingServiceCollection, {1160files: [1161fromFixture('edit/issue-6973/utils.ts')1162],1163queries: [1164{1165file: 'utils.ts',1166selection: [7, 0, 17, 0],1167query: 'implement logging',1168diagnostics: 'tsc',1169expectedIntent: 'edit',1170validate: async (outcome, workspace, accessor) => {1171assertInlineEdit(outcome);1172await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1173assertNoElidedCodeComments(outcome.fileContents);1174}1175}1176]1177});1178});11791180stest({ description: 'issue #7660', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1181return executeEditTest(strategy, testingServiceCollection, {1182files: [fromFixture('unknown/issue-7660/positionOffsetTransformer.spec.ts')],1183queries: [1184{1185file: 'positionOffsetTransformer.spec.ts',1186selection: [0, 0, 77, 0],1187query: 'convert to suite, test and assert',1188diagnostics: 'tsc',1189expectedIntent: 'unknown',1190validate: async (outcome, workspace, accessor) => {1191assertInlineEdit(outcome);1192await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1193const firstLine = outcome.fileContents.split('\n')[0];1194assert.ok(!firstLine.includes('import'), 'First line should not contain an import statement');1195assertNoElidedCodeComments(outcome.fileContents);1196}1197}1198]1199});1200});12011202stest({ description: 'issue #6614', language: 'html', nonExtensionConfigurations }, (testingServiceCollection) => {1203return executeEditTest(strategy, testingServiceCollection, {1204files: [fromFixture('edit/issue-6614/workbench-dev.html')],1205queries: [1206{1207file: 'workbench-dev.html',1208selection: [75, 4, 75, 4],1209visibleRanges: [[37, 77]],1210query: 'add a style sheel from out/vs/workbench/workbench.web.main.css',1211expectedIntent: 'edit',1212validate: async (outcome, workspace, accessor) => {1213assertInlineEdit(outcome);1214assertInlineEditShape(outcome, [{1215line: 76,1216originalLength: 0,1217modifiedLength: 1,1218}, {1219line: 75,1220originalLength: 0,1221modifiedLength: 1,1222}, {1223line: 71,1224originalLength: 0,1225modifiedLength: 1,1226}, {1227line: 72,1228originalLength: 0,1229modifiedLength: 1,1230}, {1231line: 66,1232originalLength: 0,1233modifiedLength: 1,1234}]);1235assertNoElidedCodeComments(outcome.fileContents);1236}1237}1238]1239});1240});12411242stest({ description: 'issue #6059', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1243return executeEditTest(strategy, testingServiceCollection, {1244files: [fromFixture('edit/issue-6059/serializers.ts')],1245queries: [1246{1247file: 'serializers.ts',1248selection: [202, 0, 211, 5],1249query: 'sort properties',1250diagnostics: 'tsc',1251expectedIntent: 'edit',1252validate: async (outcome, workspace, accessor) => {1253assertInlineEdit(outcome);1254assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');1255await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, 'tsc');1256assertNoElidedCodeComments(outcome.fileContents);1257}1258}1259]1260});1261});126212631264stest({ description: 'Issue #7996 - use entire context window', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1265return executeEditTest(strategy, testingServiceCollection, {1266files: [fromFixture('edit/issue-7996/codeEditorWidget.ts')],1267queries: [1268{1269file: 'codeEditorWidget.ts',1270selection: [1666, 0, 1757, 0],1271query: 'convert this to if/else',1272diagnostics: 'tsc',1273expectedIntent: 'edit',1274validate: async (outcome, workspace) => {1275assertInlineEdit(outcome);1276assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');1277assertNoElidedCodeComments(outcome.fileContents);1278}1279}1280]1281});1282});128312841285stest({ description: 'Issue #8129 (no errors)', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1286return executeEditTest(strategy, testingServiceCollection, {1287files: [fromFixture('edit/issue-8129/optimize.ts')],1288queries: [1289{1290file: 'optimize.ts',1291selection: [365, 6, 376, 79],1292query: 'adjust the sourcemaps if we have a filecontentmapper',1293diagnostics: 'tsc',1294expectedIntent: 'edit',1295validate: async (outcome, workspace, accessor) => {1296assertInlineEdit(outcome);1297assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');1298await assertNoDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1299assertNoElidedCodeComments(outcome.fileContents);1300}1301}1302]1303});1304});13051306stest({ description: 'Issue #8129 (no syntax errors)', language: 'typescript', nonExtensionConfigurations }, (testingServiceCollection) => {1307return executeEditTest(strategy, testingServiceCollection, {1308files: [fromFixture('edit/issue-8129/optimize.ts')],1309queries: [1310{1311file: 'optimize.ts',1312selection: [365, 6, 376, 79],1313query: 'adjust the sourcemaps if we have a filecontentmapper',1314diagnostics: 'tsc',1315expectedIntent: 'edit',1316validate: async (outcome, workspace, accessor) => {1317assertInlineEdit(outcome);1318assert.ok(outcome.fileContents.length > outcome.originalFileContents.length / 2, 'File was truncated');1319await assertNoSyntacticDiagnosticsAsync(accessor, outcome, workspace, KnownDiagnosticProviders.tscIgnoreImportErrors);1320assertNoElidedCodeComments(outcome.fileContents);1321}1322}1323]1324});1325});1326});1327});132813291330