Merge pull request #193810 from microsoft/revert-192602-ai-codefixes

Revert "Copilot-based TS refactors"
This commit is contained in:
Aaron Munger
2023-09-22 09:04:59 -07:00
committed by GitHub
7 changed files with 84 additions and 329 deletions

View File

@@ -145,58 +145,11 @@
"title": "%configuration.typescript%",
"order": 20,
"properties": {
"typescript.experimental.aiCodeActions": {
"type": "object",
"default": {},
"description": "%typescript.experimental.aiCodeActions%",
"scope": "resource",
"properties": {
"classIncorrectlyImplementsInterface": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface%"
},
"classDoesntImplementInheritedAbstractMember": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember%"
},
"missingFunctionDeclaration": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.missingFunctionDeclaration%"
},
"inferAndAddTypes": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.inferAndAddTypes%"
},
"addNameToNamelessParameter": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.addNameToNamelessParameter%"
},
"extractConstant": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.extractConstant%"
},
"extractFunction": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.extractFunction%"
},
"extractType": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.extractType%"
},
"extractInterface": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiCodeActions.extractInterface%"
}
}
"typescript.experimental.aiQuickFix": {
"type": "boolean",
"default": false,
"description": "%typescript.experimental.aiQuickFix%",
"scope": "resource"
},
"typescript.tsdk": {
"type": "string",

View File

@@ -8,16 +8,7 @@
"configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.",
"configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.",
"configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.",
"typescript.experimental.aiCodeActions": "Enable/disable AI-assisted code actions. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface": "Enable/disable AI assistance for Class Incorrectly Implements Interface quickfix. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember": "Enable/disable AI assistance for Class Doesn't Implement Inherited Abstract Member quickfix. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.missingFunctionDeclaration": "Enable/disable AI assistance for Missing Function Declaration quickfix. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.inferAndAddTypes": "Enable/disable AI assistance for Infer and Add Types refactor. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.addNameToNamelessParameter": "Enable/disable AI assistance for Add Name to Nameless Parameter quickfix. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.extractConstant": "Enable/disable AI assistance for Extract Constant refactor. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.extractFunction": "Enable/disable AI assistance for Extract Function refactor. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.extractType": "Enable/disable AI assistance for Extract Type refactor. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiCodeActions.extractInterface": "Enable/disable AI assistance for Extract Interface refactor. Requires an extension providing AI chat functionality.",
"typescript.experimental.aiQuickFix": "Enable/disable AI-assisted quick fixes. Requires an extension providing AI chat functionality.",
"typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.",
"typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.",
"typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.",

View File

@@ -18,7 +18,6 @@ import { DiagnosticsManager } from './diagnostics';
import FileConfigurationManager from './fileConfigurationManager';
import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction';
import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
import { Expand, EditorChatFollowUp, CompositeCommand } from './util/copilot';
type ApplyCodeActionCommand_args = {
readonly document: vscode.TextDocument;
@@ -27,6 +26,42 @@ type ApplyCodeActionCommand_args = {
readonly followupAction?: Command;
};
class EditorChatFollowUp implements Command {
id: string = '_typescript.quickFix.editorChatFollowUp';
constructor(private readonly prompt: string, private readonly document: vscode.TextDocument, private readonly range: vscode.Range, private readonly client: ITypeScriptServiceClient) {
}
async execute() {
const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined => {
for (const node of navigationTree) {
const range = typeConverters.Range.fromTextSpan(node.spans[0]);
if (startLine === range.start.line) {
return range;
} else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) {
return findScopeEndLineFromNavTree(startLine, node.childItems);
}
}
return undefined;
};
const filepath = this.client.toOpenTsFilePath(this.document);
if (!filepath) {
return;
}
const response = await this.client.execute('navtree', { file: filepath }, nulToken);
if (response.type !== 'response' || !response.body?.childItems) {
return;
}
const startLine = this.range.start.line;
const enclosingRange = findScopeEndLineFromNavTree(startLine, response.body.childItems);
if (!enclosingRange) {
return;
}
await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange: enclosingRange, message: this.prompt, autoSend: true });
}
}
class ApplyCodeActionCommand implements Command {
public static readonly ID = '_typescript.applyCodeActionCommand';
public readonly id = ApplyCodeActionCommand.ID;
@@ -220,10 +255,8 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
private readonly diagnosticsManager: DiagnosticsManager,
telemetryReporter: TelemetryReporter
) {
commandManager.register(new CompositeCommand());
commandManager.register(new ApplyCodeActionCommand(client, diagnosticsManager, telemetryReporter));
commandManager.register(new ApplyFixAllCodeAction(client, telemetryReporter));
commandManager.register(new EditorChatFollowUp(client));
this.supportedCodeActionProvider = new SupportedCodeActionProvider(client);
}
@@ -307,89 +340,42 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
}
for (const tsCodeFix of response.body) {
for (const action of this.getFixesForTsCodeAction(document, diagnostic, tsCodeFix)) {
results.addAction(action);
}
this.addFixAllForTsCodeAction(results, document.uri, file, diagnostic, tsCodeFix as Proto.CodeFixAction);
this.addAllFixesForTsCodeAction(results, document, file, diagnostic, tsCodeFix as Proto.CodeFixAction);
}
return results;
}
private getFixesForTsCodeAction(
private addAllFixesForTsCodeAction(
results: CodeActionSet,
document: vscode.TextDocument,
file: string,
diagnostic: vscode.Diagnostic,
tsAction: Proto.CodeFixAction
): CodeActionSet {
results.addAction(this.getSingleFixForTsCodeAction(document, diagnostic, tsAction));
this.addFixAllForTsCodeAction(results, document.uri, file, diagnostic, tsAction as Proto.CodeFixAction);
return results;
}
private getSingleFixForTsCodeAction(
document: vscode.TextDocument,
diagnostic: vscode.Diagnostic,
action: Proto.CodeFixAction
): VsCodeCodeAction[] {
const actions: VsCodeCodeAction[] = [];
let message: string | undefined;
let expand: Expand | undefined;
let title = action.description;
if (vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions')) {
if (action.fixName === fixNames.classIncorrectlyImplementsInterface && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.classIncorrectlyImplementsInterface')) {
title += ' with Copilot';
message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
expand = { kind: 'code-action', action };
}
else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember')) {
title += ' with Copilot';
message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
expand = { kind: 'code-action', action };
}
else if (action.fixName === fixNames.fixMissingFunctionDeclaration && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.missingFunctionDeclaration')) {
title += `Implement missing function declaration '${document.getText(diagnostic.range)}' using Copilot`;
message = `Provide a reasonable implementation of the function ${document.getText(diagnostic.range)} given its type and the context it's called in.`;
expand = { kind: 'code-action', action };
}
else if (action.fixName === fixNames.inferFromUsage && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.inferAndAddTypes')) {
const inferFromBody = new VsCodeCodeAction(action, 'Infer types using Copilot', vscode.CodeActionKind.QuickFix);
inferFromBody.edit = new vscode.WorkspaceEdit();
inferFromBody.diagnostics = [diagnostic];
inferFromBody.command = {
command: EditorChatFollowUp.ID,
arguments: [<EditorChatFollowUp.Args>{
message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.',
expand: { kind: 'navtree-function', pos: diagnostic.range.start },
document
}],
title: ''
};
actions.push(inferFromBody);
}
else if (action.fixName === fixNames.addNameToNamelessParameter && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.addNameToNamelessParameter')) {
const newText = action.changes.map(change => change.textChanges.map(textChange => textChange.newText).join('')).join('');
title = 'Add meaningful parameter name with Copilot';
message = `Rename the parameter ${newText} with a more meaningful name.`;
expand = {
kind: 'navtree-function',
pos: diagnostic.range.start
};
}
tsAction: Proto.CodeFixAction
): VsCodeCodeAction {
const aiQuickFixEnabled = vscode.workspace.getConfiguration('typescript').get('experimental.aiQuickFix');
let followupAction: Command | undefined;
if (aiQuickFixEnabled && tsAction.fixName === fixNames.classIncorrectlyImplementsInterface) {
followupAction = new EditorChatFollowUp('Implement the class using the interface', document, diagnostic.range, this.client);
}
const codeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix);
codeAction.edit = getEditForCodeAction(this.client, action);
const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix);
codeAction.edit = getEditForCodeAction(this.client, tsAction);
codeAction.diagnostics = [diagnostic];
codeAction.command = {
command: ApplyCodeActionCommand.ID,
arguments: [<ApplyCodeActionCommand_args>{ action: action, diagnostic, document }],
arguments: [<ApplyCodeActionCommand_args>{ action: tsAction, diagnostic, document, followupAction }],
title: ''
};
if (expand && message !== undefined) {
codeAction.command = {
command: CompositeCommand.ID,
title: '',
arguments: [codeAction.command, {
command: EditorChatFollowUp.ID,
title: '',
arguments: [<EditorChatFollowUp.Args>{
message,
expand,
document
}],
}],
};
}
actions.push(codeAction);
return actions;
return codeAction;
}
private addFixAllForTsCodeAction(

View File

@@ -20,7 +20,6 @@ import { coalesce } from '../utils/arrays';
import { nulToken } from '../utils/cancellation';
import FormattingOptionsManager from './fileConfigurationManager';
import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
import { EditorChatFollowUp, CompositeCommand } from './util/copilot';
function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto.FileCodeEdits[]): vscode.WorkspaceEdit {
const workspaceEdit = new vscode.WorkspaceEdit();
@@ -35,6 +34,17 @@ function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto
}
class CompositeCommand implements Command {
public static readonly ID = '_typescript.compositeCommand';
public readonly id = CompositeCommand.ID;
public async execute(...commands: vscode.Command[]): Promise<void> {
for (const command of commands) {
await vscode.commands.executeCommand(command.command, ...(command.arguments ?? []));
}
}
}
namespace DidApplyRefactoringCommand {
export interface Args {
readonly action: string;
@@ -345,17 +355,15 @@ class InlinedCodeAction extends vscode.CodeAction {
public readonly refactor: Proto.ApplicableRefactorInfo,
public readonly action: Proto.RefactorActionInfo,
public readonly range: vscode.Range,
public readonly copilotRename?: (info: Proto.RefactorEditInfo) => vscode.Command,
) {
const title = copilotRename ? action.description + ' and suggest a name with Copilot.' : action.description;
super(title, InlinedCodeAction.getKind(action));
super(action.description, InlinedCodeAction.getKind(action));
if (action.notApplicableReason) {
this.disabled = { reason: action.notApplicableReason };
}
this.command = {
title,
title: action.description,
command: DidApplyRefactoringCommand.ID,
arguments: [<DidApplyRefactoringCommand.Args>{ action: action.name }],
};
@@ -387,21 +395,18 @@ class InlinedCodeAction extends vscode.CodeAction {
if (response.body.renameLocation) {
// Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137
if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) {
if (this.copilotRename && this.command) {
this.command.title = 'Copilot: ' + this.command.title;
}
this.command = {
command: CompositeCommand.ID,
title: '',
arguments: coalesce([
this.command,
this.copilotRename ? this.copilotRename(response.body) : {
{
command: 'editor.action.rename',
arguments: [[
this.document.uri,
typeConverters.Position.fromLocation(response.body.renameLocation)
]]
},
}
])
};
}
@@ -451,6 +456,7 @@ class SelectCodeAction extends vscode.CodeAction {
};
}
}
type TsCodeAction = InlinedCodeAction | MoveToFileCodeAction | SelectCodeAction;
class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeAction> {
@@ -465,7 +471,6 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
commandManager.register(new CompositeCommand());
commandManager.register(new SelectRefactorCommand(this.client));
commandManager.register(new MoveToFileRefactorCommand(this.client, didApplyRefactoringCommand));
commandManager.register(new EditorChatFollowUp(this.client));
}
public static readonly metadata: vscode.CodeActionProviderMetadata = {
@@ -577,36 +582,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
if (action.name === 'Move to file') {
codeAction = new MoveToFileCodeAction(document, action, rangeOrSelection);
} else {
let copilotRename: ((info: Proto.RefactorEditInfo) => vscode.Command) | undefined;
if (vscode.workspace.getConfiguration('typescript', null).get('experimental.aiCodeActions')) {
if (Extract_Constant.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractConstant')
|| Extract_Function.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractFunction')
|| Extract_Type.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractType')
|| Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface')) {
const newName = Extract_Constant.matches(action) ? 'newLocal'
: Extract_Function.matches(action) ? 'newFunction'
: Extract_Type.matches(action) ? 'NewType'
: Extract_Interface.matches(action) ? 'NewInterface'
: '';
copilotRename = info => ({
title: '',
command: EditorChatFollowUp.ID,
arguments: [<EditorChatFollowUp.Args>{
message: `Rename ${newName} to a better name based on usage.`,
expand: Extract_Constant.matches(action) ? {
kind: 'navtree-function',
pos: typeConverters.Position.fromLocation(info.renameLocation!),
} : {
kind: 'refactor-info',
refactor: info,
},
document,
}]
});
}
}
codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename);
codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection);
}
codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);

View File

@@ -1,147 +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 { Command } from '../../commands/commandManager';
import { nulToken } from '../../utils/cancellation';
import type * as Proto from '../../tsServer/protocol/protocol';
import * as typeConverters from '../../typeConverters';
import { ITypeScriptServiceClient } from '../../typescriptService';
export class EditorChatFollowUp implements Command {
public static readonly ID = '_typescript.quickFix.editorChatReplacement2';
public readonly id = EditorChatFollowUp.ID;
constructor(private readonly client: ITypeScriptServiceClient) { }
async execute({ message, document, expand }: EditorChatFollowUp.Args) {
const initialRange =
expand.kind === 'navtree-function'
? await findScopeEndLineFromNavTree(
this.client,
document,
expand.pos.line
)
: expand.kind === 'refactor-info'
? await findEditScope(
this.client,
document,
expand.refactor.edits.flatMap((e) => e.textChanges)
)
: expand.kind === 'code-action'
? await findEditScope(
this.client,
document,
expand.action.changes.flatMap((c) => c.textChanges)
)
: expand.range;
await vscode.commands.executeCommand('vscode.editorChat.start', {
initialRange,
message,
autoSend: true,
});
}
}
export namespace EditorChatFollowUp {
export interface Args {
readonly message: string;
readonly document: vscode.TextDocument;
readonly expand: Expand;
}
}
export class CompositeCommand implements Command {
public static readonly ID = '_typescript.compositeCommand';
public readonly id = CompositeCommand.ID;
public async execute(...commands: vscode.Command[]): Promise<void> {
for (const command of commands) {
await vscode.commands.executeCommand(
command.command,
...(command.arguments ?? [])
);
}
}
}
export type Expand =
| { kind: 'none'; readonly range: vscode.Range }
| { kind: 'navtree-function'; readonly pos: vscode.Position }
| { kind: 'refactor-info'; readonly refactor: Proto.RefactorEditInfo }
| { kind: 'code-action'; readonly action: Proto.CodeAction };
function findScopeEndLineFromNavTreeWorker(
startLine: number,
navigationTree: Proto.NavigationTree[]
): vscode.Range | undefined {
for (const node of navigationTree) {
const range = typeConverters.Range.fromTextSpan(node.spans[0]);
if (startLine === range.start.line) {
return range;
} else if (
startLine > range.start.line &&
startLine <= range.end.line &&
node.childItems
) {
return findScopeEndLineFromNavTreeWorker(startLine, node.childItems);
}
}
return undefined;
}
async function findScopeEndLineFromNavTree(
client: ITypeScriptServiceClient,
document: vscode.TextDocument,
startLine: number
) {
const filepath = client.toOpenTsFilePath(document);
if (!filepath) {
return;
}
const response = await client.execute(
'navtree',
{ file: filepath },
nulToken
);
if (response.type !== 'response' || !response.body?.childItems) {
return;
}
return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems);
}
async function findEditScope(
client: ITypeScriptServiceClient,
document: vscode.TextDocument,
edits: Proto.CodeEdit[]
): Promise<vscode.Range> {
let first = typeConverters.Position.fromLocation(edits[0].start);
let firstEdit = edits[0];
let lastEdit = edits[0];
let last = typeConverters.Position.fromLocation(edits[0].start);
for (const edit of edits) {
const start = typeConverters.Position.fromLocation(edit.start);
const end = typeConverters.Position.fromLocation(edit.end);
if (start.compareTo(first) < 0) {
first = start;
firstEdit = edit;
}
if (end.compareTo(last) > 0) {
last = end;
lastEdit = edit;
}
}
const text = document.getText();
const startIndex = text.indexOf(firstEdit.newText);
const start = startIndex > -1 ? document.positionAt(startIndex) : first;
const endIndex = text.lastIndexOf(lastEdit.newText);
const end =
endIndex > -1
? document.positionAt(endIndex + lastEdit.newText.length)
: last;
const expandEnd = await findScopeEndLineFromNavTree(
client,
document,
end.line
);
return new vscode.Range(start, expandEnd?.end ?? end);
}

View File

@@ -16,9 +16,5 @@ export const fixImport = 'import';
export const forgottenThisPropertyAccess = 'forgottenThisPropertyAccess';
export const removeUnnecessaryAwait = 'removeUnnecessaryAwait';
export const spelling = 'spelling';
export const inferFromUsage = 'inferFromUsage';
export const addNameToNamelessParameter = 'addNameToNamelessParameter';
export const fixMissingFunctionDeclaration = 'fixMissingFunctionDeclaration';
export const fixClassDoesntImplementInheritedAbstractMember = 'fixClassDoesntImplementInheritedAbstractMember';
export const unreachableCode = 'fixUnreachableCode';
export const unusedIdentifier = 'unusedIdentifier';

View File

@@ -11,6 +11,6 @@
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts"
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts",
]
}