Remove test generation commands and related configurations from Copilot Chat (#3937)

* Remove test generation commands and related configurations from Copilot Chat

* Remove unused TestGenLensContribution import from contributions.ts
This commit is contained in:
Johannes Rieken
2026-02-23 17:42:10 +01:00
committed by GitHub
parent b9624f6cad
commit c0eaa94d6a
10 changed files with 15 additions and 419 deletions

View File

@@ -138,7 +138,6 @@
"scoredEdits.w.json": "https://microsoft.github.io/vscode-workbench-recorder-viewer/?editRating",
"workspaceRecording.jsonl": "https://microsoft.github.io/vscode-workbench-recorder-viewer/?jsonl=true",
},
"github.copilot.chat.generateTests.codeLens": true,
"explorer.fileNesting.patterns": {
"vscode.d.ts": "vscode.proposed.*.ts",
},

View File

@@ -2349,18 +2349,6 @@
"enablement": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"category": "Chat"
},
{
"command": "github.copilot.chat.generateDocs",
"title": "%github.copilot.command.generateDocs%",
"enablement": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"category": "Chat"
},
{
"command": "github.copilot.chat.generateTests",
"title": "%github.copilot.command.generateTests%",
"enablement": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"category": "Chat"
},
{
"command": "github.copilot.chat.fix",
"title": "%github.copilot.command.fixThis%",
@@ -3507,14 +3495,6 @@
"experimental"
]
},
"github.copilot.chat.generateTests.codeLens": {
"type": "boolean",
"default": false,
"description": "%github.copilot.config.generateTests.codeLens%",
"tags": [
"experimental"
]
},
"github.copilot.chat.setupTests.enabled": {
"type": "boolean",
"default": true,
@@ -4527,10 +4507,20 @@
}
],
"editor/context": [
{
"command": "github.copilot.chat.fix",
"when": "!github.copilot.interactiveSession.disabled && !editorReadonly && editorSelectionHasDiagnostics",
"group": "1_chat@4"
},
{
"command": "github.copilot.chat.explain",
"when": "!github.copilot.interactiveSession.disabled",
"group": "1_chat@4"
"group": "1_chat@5"
},
{
"command": "github.copilot.chat.review",
"when": "config.github.copilot.chat.reviewSelection.enabled && !github.copilot.interactiveSession.disabled && resourceScheme != 'vscode-chat-code-block'",
"group": "1_chat@6"
},
{
"command": "github.copilot.chat.copilotCLI.addFileReference",
@@ -4543,28 +4533,6 @@
"when": "github.copilot.chat.copilotCLI.hasSession && editorHasSelection && !inOutput && resourceScheme != 'vscode-webview' && resourceScheme != 'webview-panel'"
}
],
"editor/context/chat": [
{
"command": "github.copilot.chat.fix",
"when": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"group": "copilotAction@1"
},
{
"command": "github.copilot.chat.review",
"when": "config.github.copilot.chat.reviewSelection.enabled && !github.copilot.interactiveSession.disabled && resourceScheme != 'vscode-chat-code-block'",
"group": "copilotAction@2"
},
{
"command": "github.copilot.chat.generateDocs",
"when": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"group": "copilotGenerate@1"
},
{
"command": "github.copilot.chat.generateTests",
"when": "!github.copilot.interactiveSession.disabled && !editorReadonly",
"group": "copilotGenerate@2"
}
],
"chat/editor/inlineGutter": [
{
"command": "github.copilot.chat.explain",

View File

@@ -26,8 +26,6 @@
"github.copilot.command.unhelpfulReviewSuggestion": "Unhelpful",
"github.copilot.command.fixThis": "Fix",
"github.copilot.command.generateThis": "Generate This",
"github.copilot.command.generateDocs": "Generate Docs",
"github.copilot.command.generateTests": "Generate Tests",
"github.copilot.command.openUserPreferences": "Open User Preferences",
"github.copilot.command.openMemoryFolder": "Open Memory Folder",
"github.copilot.command.sendChatFeedback": "Send Chat Feedback",
@@ -158,7 +156,6 @@
"github.copilot.config.pullRequestDescriptionGeneration.instructions": "A set of instructions that will be added to Copilot requests that generate pull request titles and descriptions.\nInstructions can come from: \n- a file in the workspace: `{ \"file\": \"fileName\" }`\n- text in natural language: `{ \"text\": \"Always include a list of key changes.\" }`\n\nNote: Keep your instructions short and precise. Poor instructions can degrade Copilot's quality and performance.",
"github.copilot.config.pullRequestDescriptionGeneration.instruction.text": "Text instructions that will be added to Copilot requests that generate pull request titles and descriptions.",
"github.copilot.config.pullRequestDescriptionGeneration.instruction.file": "A path to a file with instructions that will be added to Copilot requests that generate pull request titles and descriptions.",
"github.copilot.config.generateTests.codeLens": "Show 'Generate tests' code lens for symbols that are not covered by current test coverage information.",
"github.copilot.config.notebook.followCellExecution": "Controls whether the currently executing cell is revealed into the viewport upon execution from Copilot.",
"github.copilot.config.notebook.enhancedNextEditSuggestions": "Controls whether to use an enhanced approach for generating next edit suggestions in notebook cells.",
"github.copilot.config.imageUpload.enabled": "Enables the use of image upload URLs in chat requests instead of raw base64 strings.",

View File

@@ -152,16 +152,6 @@ ConfigurationMigrationRegistry.registerConfigurationMigrations([{
}
}]);
ConfigurationMigrationRegistry.registerConfigurationMigrations([{
key: 'github.copilot.chat.experimental.generateTests.codeLens',
migrateFn: async (value: any) => {
return [
['github.copilot.chat.generateTests.codeLens', { value }],
['github.copilot.chat.experimental.generateTests.codeLens', { value: undefined }]
];
}
}]);
ConfigurationMigrationRegistry.registerConfigurationMigrations([{
key: 'github.copilot.chat.planAgent.model',
migrateFn: async (value: any) => {

View File

@@ -29,7 +29,6 @@ import { GitHubMcpContrib } from '../../githubMcp/vscode-node/githubMcp.contribu
import { IgnoredFileProviderContribution } from '../../ignore/vscode-node/ignoreProvider';
import { JointCompletionsProviderContribution } from '../../inlineEdits/vscode-node/jointInlineCompletionProvider';
import { FixTestFailureContribution } from '../../intents/vscode-node/fixTestFailureContributions';
import { TestGenLensContribution } from '../../intents/vscode-node/testGenLens';
import { ExtensionStateCommandContribution } from '../../log/vscode-node/extensionStateCommand';
import { FetcherTelemetryContribution, LoggingActionsContrib } from '../../log/vscode-node/loggingActions';
import { RequestLogTree } from '../../log/vscode-node/requestLogTree';
@@ -110,7 +109,6 @@ export const vscodeNodeContributions: IExtensionContributionFactory[] = [
*/
export const vscodeNodeChatContributions: IExtensionContributionFactory[] = [
asContributionFactory(ConfigurationMigrationContribution),
asContributionFactory(TestGenLensContribution),
asContributionFactory(RequestLogTree),
asContributionFactory(OnboardTerminalTestsContribution),
asContributionFactory(ToolsContribution),

View File

@@ -84,6 +84,10 @@ export class QuickFixesProvider implements vscode.CodeActionProvider {
codeActions.push(altTextQuickFixes);
}
if (vscode.workspace.getConfiguration('inlineChat').get('affordance') !== 'off') {
return codeActions;
}
if (this.reviewService.isCodeFeedbackEnabled() && !activeTextEditor.selection.isEmpty) {
const reviewAction = new AICodeAction(vscode.l10n.t('Review'), QuickFixesProvider.reviewKind);
reviewAction.command = {

View File

@@ -27,9 +27,7 @@ import * as path from '../../../util/vs/base/common/path';
import { URI } from '../../../util/vs/base/common/uri';
import { IInstantiationService, ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';
import { Intent } from '../../common/constants';
import { InlineDocIntent } from '../../intents/node/docIntent';
import { explainIntentPromptSnippet } from '../../intents/node/explainIntent';
import { GenerateTests } from '../../intents/vscode-node/testGenAction';
import { ChatParticipantRequestHandler } from '../../prompt/node/chatParticipantRequestHandler';
import { sendReviewActionTelemetry } from '../../prompt/node/feedbackGenerator';
import { CurrentSelection } from '../../prompts/node/panel/currentSelection';
@@ -240,21 +238,6 @@ ${message}`,
const doGenerate = () => {
return vscode.commands.executeCommand('vscode.editorChat.start', { message: '/generate ' });
};
const doGenerateDocs = () => {
return vscode.commands.executeCommand('vscode.editorChat.start', { message: `/${InlineDocIntent.ID} `, autoSend: true, initialRange: vscode.window.activeTextEditor?.selection });
};
const doGenerateTests = (arg?: unknown) => {
// @ulugbekna: `github.copilot.chat.generateTests` is invoked from editor context menu, which means
// the first arguments can be a vscode.Uri
const context =
(arg && typeof arg === 'object' &&
'document' in arg && arg.document && typeof arg.document === 'object' && 'getText' in arg.document &&
'selection' in arg && arg.selection instanceof vscode.Range
)
? arg as { document: vscode.TextDocument; selection: vscode.Range }
: undefined;
return instaService.createInstance(GenerateTests).runCommand(context);
};
const doFix = () => {
const activeDocument = vscode.window.activeTextEditor;
if (!activeDocument) {
@@ -305,8 +288,6 @@ ${message}`,
disposables.add(vscode.commands.registerCommand('github.copilot.chat.review.next', thread => goToNextReview(thread, +1)));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.review.current', thread => goToNextReview(thread, 0)));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.generate', doGenerate));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.generateDocs', doGenerateDocs));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.generateTests', doGenerateTests));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.fix', doFix));
disposables.add(vscode.commands.registerCommand('github.copilot.chat.generateAltText', doGenerateAltText));
// register code actions

View File

@@ -1,178 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
import { IParserService } from '../../../platform/parser/node/parserService';
import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { Intent } from '../../common/constants';
import { isTestFile, suggestUntitledTestFileLocation, TestFileFinder } from '../../prompt/node/testFiles';
import { ITestGenInfoStorage } from '../node/testIntent/testInfoStorage';
export class GenerateTests {
constructor(
@IInstantiationService private readonly instaService: IInstantiationService,
@IParserService private readonly parserService: IParserService,
@ITestGenInfoStorage private readonly testGenInfoStorage: ITestGenInfoStorage,
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
@IIgnoreService private readonly ignoreService: IIgnoreService,
) {
}
public async runCommand(context?: { document: vscode.TextDocument; selection: vscode.Range }) {
let srcFile: TextDocumentSnapshot;
let selection: vscode.Range;
if (context) {
srcFile = TextDocumentSnapshot.create(context.document);
selection = context.selection;
} else {
const initialActiveEditor = vscode.window.activeTextEditor;
if (initialActiveEditor === undefined) {
return;
}
srcFile = TextDocumentSnapshot.create(initialActiveEditor.document);
selection = initialActiveEditor.selection;
}
if (isTestFile(srcFile.uri)) {
return vscode.commands.executeCommand(
'vscode.editorChat.start',
{
message: `/${Intent.Tests} `,
autoSend: true,
initialRange: selection
});
} else {
// identify the range for the symbol to test
const testableNode = await this.identifyTestableNode(srcFile, selection);
this.updateTestGenInfo(srcFile, testableNode, selection);
// identify the file to write tests at -- either existing one or a new untitled one
const testFile = await this.findOrCreateTestFile(srcFile);
const testDoc = await vscode.workspace.openTextDocument(testFile);
// identify where in the test file to insert the tests at
const insertTestsAt: vscode.Range = await this.determineTestInsertPosition(testDoc);
const testEditor = await vscode.window.showTextDocument(testDoc, this.getTabGroupByUri(testFile));
testEditor.selection = new vscode.Selection(insertTestsAt.start, insertTestsAt.end);
testEditor.revealRange(insertTestsAt, vscode.TextEditorRevealType.InCenter);
const isDocEmpty = insertTestsAt.end.line === 0 && insertTestsAt.end.character === 0;
if (!isDocEmpty) {
await testEditor.edit(editBuilder => {
editBuilder.insert(insertTestsAt.start, '\n\n');
});
}
return vscode.commands.executeCommand(
'vscode.editorChat.start',
{
message: `/${Intent.Tests}`,
autoSend: true,
});
}
}
private async determineTestInsertPosition(testDoc: vscode.TextDocument) {
const testFileAST = this.parserService.getTreeSitterAST(testDoc);
const lastTest = testFileAST ? await testFileAST.findLastTest() : null;
let insertTestsAt: vscode.Range;
if (lastTest === null) {
const lastLine = testDoc.lineAt(testDoc.lineCount - 1);
insertTestsAt = new vscode.Range(lastLine.range.end, lastLine.range.end);
} else {
const lastTestEndPos = testDoc.positionAt(lastTest.endIndex);
const endOfLastLine = testDoc.lineAt(lastTestEndPos).range.end;
insertTestsAt = new vscode.Range(endOfLastLine, endOfLastLine);
}
return insertTestsAt;
}
private updateTestGenInfo(srcFile: TextDocumentSnapshot, testableNode: { identifier: string; range: vscode.Range } | null, selection: vscode.Range) {
this.testGenInfoStorage.sourceFileToTest = {
uri: srcFile.uri,
target: testableNode?.range ?? selection,
identifier: testableNode?.identifier,
};
}
private async identifyTestableNode(srcFile: TextDocumentSnapshot, selection: vscode.Range): Promise<{ identifier: string; range: vscode.Range } | null> {
const srcFileAST = this.parserService.getTreeSitterAST(srcFile);
if (!srcFileAST) {
return null;
}
const testableNode = await srcFileAST.getTestableNode({
startIndex: srcFile.offsetAt(selection.start),
endIndex: srcFile.offsetAt(selection.end),
});
if (!testableNode) {
return null;
}
const { startIndex, endIndex } = testableNode.node;
const testedSymbolRange = new vscode.Range(srcFile.positionAt(startIndex), srcFile.positionAt(endIndex));
return {
identifier: testableNode.identifier.name,
range: testedSymbolRange
};
}
private async findOrCreateTestFile(srcFile: TextDocumentSnapshot) {
const finder = this.instaService.createInstance(TestFileFinder);
let testFile = await finder.findTestFileForSourceFile(srcFile, CancellationToken.None);
if (testFile !== undefined && await this.ignoreService.isCopilotIgnored(testFile)) {
testFile = undefined;
}
if (testFile === undefined) {
testFile = suggestUntitledTestFileLocation(srcFile);
}
return testFile;
}
private getTabGroupByUri(uri: vscode.Uri) {
for (const tab of this.tabsAndEditorsService.tabs) {
if (tab.uri?.toString() === uri.toString()) {
return tab.tab.group.viewColumn;
}
}
const currentTab = this.tabsAndEditorsService.activeTextEditor?.viewColumn;
if (currentTab === undefined) {
return vscode.ViewColumn.Two;
} else {
return currentTab > vscode.ViewColumn.One ? currentTab - 1 : vscode.ViewColumn.Beside;
}
}
}

View File

@@ -1,161 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { ILogService } from '../../../platform/log/common/logService';
import { IParserService } from '../../../platform/parser/node/parserService';
import { range } from '../../../util/vs/base/common/arrays';
import { Disposable, DisposableStore, IDisposable } from '../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { IExtensionContribution } from '../../common/contributions';
class TestGenLensProvider implements vscode.CodeLensProvider<vscode.CodeLens>, IDisposable {
public static codeLensTitle = vscode.l10n.t('Generate tests using Copilot');
public static isEnabled(configService: IConfigurationService) {
return configService.getConfig(ConfigKey.GenerateTestsCodeLens);
}
public readonly onDidChangeCodeLenses: vscode.Event<void>;
private readonly store;
constructor(
@ILogService private readonly logService: ILogService,
@IParserService private readonly parserService: IParserService,
) {
this.store = new DisposableStore();
this.onDidChangeCodeLenses = vscode.tests.onDidChangeTestResults;
}
public dispose() {
this.store.dispose();
}
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
return this.computeCodeLens(document, token);
}
private async computeCodeLens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
// don't show code lenses for output channels
if (document.uri.scheme === 'output') {
return [];
}
const testResults = vscode.tests.testResults;
if (testResults.length === 0) {
this.logService.trace('No test results');
return [];
}
const lastTest = testResults[0];
let detailedCoverage: vscode.FileCoverageDetail[] | undefined;
try {
detailedCoverage = await lastTest.getDetailedCoverage?.(document.uri, token);
} catch (e) {
this.logService.error(e);
return [];
}
if (!detailedCoverage || detailedCoverage.length === 0) {
return [];
}
const codeLens: vscode.CodeLens[] = [];
for (const detail of detailedCoverage) {
if (detail instanceof vscode.DeclarationCoverage) {
this.logService.trace(`Received statement coverage for ${detail.name}. (detail.executed: ${detail.executed})`);
const wasExecuted = !!detail.executed;
if (wasExecuted) {
continue;
}
const locationAsRange = detail.location instanceof vscode.Range ? detail.location : new vscode.Range(detail.location, detail.location);
codeLens.push(this.createCodeLens(document, locationAsRange));
} else if (detail instanceof vscode.StatementCoverage) {
this.logService.trace('Received statement coverage; did nothing');
} else {
this.logService.error('Unexpected coverage type');
}
}
if (codeLens.length === 0) {
// try identifying untested declarations using tree sitter based approach
const ast = this.parserService.getTreeSitterAST(document);
if (ast === undefined) {
return codeLens;
}
const testableNodes = await ast.getTestableNodes();
if (testableNodes === null) {
return codeLens;
}
const uncoveredLines = detailedCoverage.flatMap(cov =>
!!cov.executed ? [] : (cov.location instanceof vscode.Position ? [cov.location.line] : this.toLineNumbers(cov.location))
);
const uncoveredLinesSet = new Set(uncoveredLines);
for (const node of testableNodes) {
const start = document.positionAt(node.node.startIndex);
const end = document.positionAt(node.node.endIndex);
const codeLensRange = new vscode.Range(start, end);
if (range(start.line, end.line).every(lineN => uncoveredLinesSet.has(lineN))) {
codeLens.push(this.createCodeLens(document, codeLensRange));
}
}
}
return codeLens;
}
private createCodeLens(document: vscode.TextDocument, range: vscode.Range) {
return new vscode.CodeLens(
range,
{
title: TestGenLensProvider.codeLensTitle,
command: 'github.copilot.chat.generateTests',
arguments: [{ document, selection: range }],
}
);
}
private toLineNumbers(range: vscode.Range): number[] {
const lineNumbers: number[] = [];
for (let i = range.start.line; i <= range.end.line; i++) {
lineNumbers.push(i);
}
return lineNumbers;
}
}
export class TestGenLensContribution extends Disposable implements IExtensionContribution {
constructor(
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
if (TestGenLensProvider.isEnabled(configurationService)) {
const testGenCodeLensProvider = this._register(instantiationService.createInstance(TestGenLensProvider));
this._register(
vscode.languages.registerCodeLensProvider(
'*',
testGenCodeLensProvider
));
}
}
}

View File

@@ -919,8 +919,6 @@ export namespace ConfigKey {
export const TestGenerationInstructions = defineSetting('chat.testGeneration.instructions', ConfigType.Simple, [] as CodeGenerationInstruction[]);
export const CommitMessageGenerationInstructions = defineSetting('chat.commitMessageGeneration.instructions', ConfigType.Simple, [] as CommitMessageGenerationInstruction[]);
export const PullRequestDescriptionGenerationInstructions = defineSetting('chat.pullRequestDescriptionGeneration.instructions', ConfigType.Simple, [] as CommitMessageGenerationInstruction[]);
/** Show code lens "Generate tests" when we have test coverage info about this symbol and it's not covered */
export const GenerateTestsCodeLens = defineSetting('chat.generateTests.codeLens', ConfigType.Simple, false);
/** Whether new flows around setting up tests are enabled */
export const SetupTests = defineSetting<boolean>('chat.setupTests.enabled', ConfigType.Simple, true);
/** Whether the Copilot TypeScript context provider is enabled and if how */