Adding preparePasteEdits method to check if smart copy/paste should be applied (#60053)

This commit is contained in:
navya9singh 2024-09-26 11:50:23 -07:00 коммит произвёл GitHub
Родитель 3ad0f75248
Коммит 8499803ae0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
17 изменённых файлов: 207 добавлений и 2 удалений

Просмотреть файл

@ -1020,6 +1020,16 @@ export class SessionClient implements LanguageService {
return getSupportedCodeFixes();
}
preparePasteEditsForFile(copiedFromFile: string, copiedTextSpan: TextRange[]): boolean {
const args: protocol.PreparePasteEditsRequestArgs = {
file: copiedFromFile,
copiedTextSpan: copiedTextSpan.map(span => ({ start: this.positionToOneBasedLineOffset(copiedFromFile, span.pos), end: this.positionToOneBasedLineOffset(copiedFromFile, span.end) })),
};
const request = this.processRequest<protocol.PreparePasteEditsRequest>(protocol.CommandTypes.PreparePasteEdits, args);
const response = this.processResponse<protocol.PreparePasteEditsResponse>(request);
return response.body;
}
getPasteEdits(
{ targetFile, pastedText, pasteLocations, copiedFrom }: PasteEditsArgs,
formatOptions: FormatCodeSettings,

Просмотреть файл

@ -3630,6 +3630,13 @@ export class TestState {
assert.deepEqual(actualModuleSpecifiers, moduleSpecifiers);
}
public verifyPreparePasteEdits(options: FourSlashInterface.PreparePasteEditsOptions): void {
const providePasteEdits = this.languageService.preparePasteEditsForFile(options.copiedFromFile, options.copiedTextRange);
if (providePasteEdits !== options.providePasteEdits) {
this.raiseError(`preparePasteEdits failed - Expected prepare paste edits to return ${options.providePasteEdits}, but got ${providePasteEdits}.`);
}
}
public verifyPasteEdits(options: FourSlashInterface.PasteEditsOptions): void {
const editInfo = this.languageService.getPasteEdits({ targetFile: this.activeFile.fileName, pastedText: options.args.pastedText, pasteLocations: options.args.pasteLocations, copiedFrom: options.args.copiedFrom, preferences: options.args.preferences }, this.formatCodeSettings);
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);

Просмотреть файл

@ -657,6 +657,9 @@ export class Verify extends VerifyNegatable {
this.state.verifyOrganizeImports(newContent, mode, preferences);
}
public preparePasteEdits(options: PreparePasteEditsOptions): void {
this.state.verifyPreparePasteEdits(options);
}
public pasteEdits(options: PasteEditsOptions): void {
this.state.verifyPasteEdits(options);
}
@ -2017,6 +2020,11 @@ export interface MoveToFileOptions {
readonly preferences?: ts.UserPreferences;
}
export interface PreparePasteEditsOptions {
readonly providePasteEdits: boolean;
readonly copiedTextRange: ts.TextRange[];
readonly copiedFromFile: string;
}
export interface PasteEditsOptions {
readonly newFileContents: { readonly [fileName: string]: string; };
args: ts.PasteEditsArgs;

Просмотреть файл

@ -169,6 +169,7 @@ export const enum CommandTypes {
GetApplicableRefactors = "getApplicableRefactors",
GetEditsForRefactor = "getEditsForRefactor",
GetMoveToRefactoringFileSuggestions = "getMoveToRefactoringFileSuggestions",
PreparePasteEdits = "preparePasteEdits",
GetPasteEdits = "getPasteEdits",
/** @internal */
GetEditsForRefactorFull = "getEditsForRefactor-full",
@ -671,6 +672,20 @@ export interface GetMoveToRefactoringFileSuggestions extends Response {
};
}
/**
* Request to check if `pasteEdits` should be provided for a given location post copying text from that location.
*/
export interface PreparePasteEditsRequest extends FileRequest {
command: CommandTypes.PreparePasteEdits;
arguments: PreparePasteEditsRequestArgs;
}
export interface PreparePasteEditsRequestArgs extends FileRequestArgs {
copiedTextSpan: TextSpan[];
}
export interface PreparePasteEditsResponse extends Response {
body: boolean;
}
/**
* Request refactorings at a given position post pasting text from some other location.
*/

Просмотреть файл

@ -966,6 +966,7 @@ const invalidSyntacticModeCommands: readonly protocol.CommandTypes[] = [
protocol.CommandTypes.NavtoFull,
protocol.CommandTypes.DocumentHighlights,
protocol.CommandTypes.DocumentHighlightsFull,
protocol.CommandTypes.PreparePasteEdits,
];
export interface SessionOptions {
@ -2966,6 +2967,10 @@ export class Session<TMessage = string> implements EventSender {
return project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file));
}
private preparePasteEdits(args: protocol.PreparePasteEditsRequestArgs): boolean {
const { file, project } = this.getFileAndProject(args);
return project.getLanguageService().preparePasteEditsForFile(file, args.copiedTextSpan.map(copies => this.getRange({ file, startLine: copies.start.line, startOffset: copies.start.offset, endLine: copies.end.line, endOffset: copies.end.offset }, this.projectService.getScriptInfoForNormalizedPath(file)!)));
}
private getPasteEdits(args: protocol.GetPasteEditsRequestArgs): protocol.PasteEditsAction | undefined {
const { file, project } = this.getFileAndProject(args);
const copiedFrom = args.copiedFrom
@ -3716,6 +3721,9 @@ export class Session<TMessage = string> implements EventSender {
[protocol.CommandTypes.GetMoveToRefactoringFileSuggestions]: (request: protocol.GetMoveToRefactoringFileSuggestionsRequest) => {
return this.requiredResponse(this.getMoveToRefactoringFileSuggestions(request.arguments));
},
[protocol.CommandTypes.PreparePasteEdits]: (request: protocol.PreparePasteEditsRequest) => {
return this.requiredResponse(this.preparePasteEdits(request.arguments));
},
[protocol.CommandTypes.GetPasteEdits]: (request: protocol.GetPasteEditsRequest) => {
return this.requiredResponse(this.getPasteEdits(request.arguments));
},

Просмотреть файл

@ -0,0 +1 @@
export * from "../preparePasteEdits.js";

Просмотреть файл

@ -58,5 +58,7 @@ import * as textChanges from "./ts.textChanges.js";
export { textChanges };
import * as formatting from "./ts.formatting.js";
export { formatting };
import * as PreparePasteEdits from "./ts.preparePasteEdits.js";
export { PreparePasteEdits };
import * as pasteEdits from "./ts.PasteEdits.js";
export { pasteEdits };

Просмотреть файл

@ -0,0 +1,46 @@
import {
findAncestor,
forEachChild,
getTokenAtPosition,
isIdentifier,
rangeContainsPosition,
rangeContainsRange,
SourceFile,
SymbolFlags,
TextRange,
TypeChecker,
} from "./_namespaces/ts.js";
import { isInImport } from "./refactors/moveToFile.js";
/** @internal */
export function preparePasteEdits(
sourceFile: SourceFile,
copiedFromRange: TextRange[],
checker: TypeChecker,
): boolean {
let shouldProvidePasteEdits = false;
copiedFromRange.forEach(range => {
const enclosingNode = findAncestor(
getTokenAtPosition(sourceFile, range.pos),
ancestorNode => rangeContainsRange(ancestorNode, range),
);
if (!enclosingNode) return;
forEachChild(enclosingNode, function checkNameResolution(node) {
if (shouldProvidePasteEdits) return;
if (isIdentifier(node) && rangeContainsPosition(range, node.getStart(sourceFile))) {
const resolvedSymbol = checker.resolveName(node.text, node, SymbolFlags.All, /*excludeGlobals*/ false);
if (resolvedSymbol && resolvedSymbol.declarations) {
for (const decl of resolvedSymbol.declarations) {
if (isInImport(decl) || !!(node.text && sourceFile.symbol && sourceFile.symbol.exports?.has(node.escapedText))) {
shouldProvidePasteEdits = true;
return;
}
}
}
}
node.forEachChild(checkNameResolution);
});
if (shouldProvidePasteEdits) return;
});
return shouldProvidePasteEdits;
}

Просмотреть файл

@ -1000,8 +1000,8 @@ function forEachTopLevelDeclaration<T>(statement: Statement, cb: (node: TopLevel
}
}
}
function isInImport(decl: Declaration) {
/** @internal */
export function isInImport(decl: Declaration): boolean {
switch (decl.kind) {
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportSpecifier:

Просмотреть файл

@ -258,6 +258,7 @@ import {
positionIsSynthesized,
PossibleProgramFileInfo,
PragmaMap,
PreparePasteEdits,
PrivateIdentifier,
Program,
PropertyName,
@ -1621,6 +1622,7 @@ const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [
"getRenameInfo",
"findRenameLocations",
"getApplicableRefactors",
"preparePasteEditsForFile",
];
export function createLanguageService(
host: LanguageServiceHost,
@ -2308,6 +2310,15 @@ export function createLanguageService(
};
}
function preparePasteEditsForFile(fileName: string, copiedTextRange: TextRange[]): boolean {
synchronizeHostData();
return PreparePasteEdits.preparePasteEdits(
getValidSourceFile(fileName),
copiedTextRange,
program.getTypeChecker(),
);
}
function getPasteEdits(
args: PasteEditsArgs,
formatOptions: FormatCodeSettings,
@ -3424,6 +3435,7 @@ export function createLanguageService(
uncommentSelection,
provideInlayHints,
getSupportedCodeFixes,
preparePasteEditsForFile,
getPasteEdits,
mapCode,
};

Просмотреть файл

@ -699,6 +699,7 @@ export interface LanguageService {
/** @internal */ mapCode(fileName: string, contents: string[], focusLocations: TextSpan[][] | undefined, formatOptions: FormatCodeSettings, preferences: UserPreferences): readonly FileTextChanges[];
dispose(): void;
preparePasteEditsForFile(fileName: string, copiedTextRanges: TextRange[]): boolean;
getPasteEdits(
args: PasteEditsArgs,
formatOptions: FormatCodeSettings,

16
tests/baselines/reference/api/typescript.d.ts поставляемый
Просмотреть файл

@ -107,6 +107,7 @@ declare namespace ts {
GetApplicableRefactors = "getApplicableRefactors",
GetEditsForRefactor = "getEditsForRefactor",
GetMoveToRefactoringFileSuggestions = "getMoveToRefactoringFileSuggestions",
PreparePasteEdits = "preparePasteEdits",
GetPasteEdits = "getPasteEdits",
OrganizeImports = "organizeImports",
GetEditsForFileRename = "getEditsForFileRename",
@ -514,6 +515,19 @@ declare namespace ts {
files: string[];
};
}
/**
* Request to check if `pasteEdits` should be provided for a given location post copying text from that location.
*/
export interface PreparePasteEditsRequest extends FileRequest {
command: CommandTypes.PreparePasteEdits;
arguments: PreparePasteEditsRequestArgs;
}
export interface PreparePasteEditsRequestArgs extends FileRequestArgs {
copiedTextSpan: TextSpan[];
}
export interface PreparePasteEditsResponse extends Response {
body: boolean;
}
/**
* Request refactorings at a given position post pasting text from some other location.
*/
@ -3556,6 +3570,7 @@ declare namespace ts {
private getApplicableRefactors;
private getEditsForRefactor;
private getMoveToRefactoringFileSuggestions;
private preparePasteEdits;
private getPasteEdits;
private organizeImports;
private getEditsForFileRename;
@ -10211,6 +10226,7 @@ declare namespace ts {
uncommentSelection(fileName: string, textRange: TextRange): TextChange[];
getSupportedCodeFixes(fileName?: string): readonly string[];
dispose(): void;
preparePasteEditsForFile(fileName: string, copiedTextRanges: TextRange[]): boolean;
getPasteEdits(args: PasteEditsArgs, formatOptions: FormatCodeSettings): PasteEdits;
}
interface JsxClosingTagInfo {

Просмотреть файл

@ -458,6 +458,11 @@ declare namespace FourSlashInterface {
toggleMultilineComment(newFileContent: string): void;
commentSelection(newFileContent: string): void;
uncommentSelection(newFileContent: string): void;
preparePasteEdits(options: {
copiedFromFile: string,
copiedTextRange: { pos: number, end: number }[],
providePasteEdits: boolean
}): void;
pasteEdits(options: {
newFileContents: { readonly [fileName: string]: string };
args: {

Просмотреть файл

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// @module: commonjs
// @allowJs: true
// @Filename: /file1.js
//// import { aa, bb } = require("./other");
//// [|const r = 10;|]
//// export const s = 12;
//// [|export const t = aa + bb + r + s;
//// const u = 1;|]
// @Filename: /other.js
//// export const aa = 1;
//// export const bb = 2;
//// module.exports = { aa, bb };
verify.preparePasteEdits({
copiedFromFile: "/file1.js",
copiedTextRange: test.ranges(),
providePasteEdits: true,
})

Просмотреть файл

@ -0,0 +1,20 @@
/// <reference path='./fourslash.ts' />
// @Filename: /file2.ts
////import { b } from './file1';
////export const a = 1;
//// [|function MyFunction() {}
//// namespace MyFunction {
//// export const value = b;
//// }|]
////const c = a + 20;
////const t = 9;
// @Filename: /file1.ts
////export const b = 2;
verify.preparePasteEdits({
copiedFromFile: "/file2.ts",
copiedTextRange: test.ranges(),
providePasteEdits: true,
})

Просмотреть файл

@ -0,0 +1,19 @@
/// <reference path='./fourslash.ts' />
// @Filename: /file2.ts
//// import { T } from './file1';
////
//// [|function MyFunction(param: T): T {
//// type U = { value: T }
//// const localVariable: U = { value: param };
//// return localVariable.value;
//// }|]
// @Filename: /file1.ts
//// export type T = string;
verify.preparePasteEdits({
copiedFromFile: "/file2.ts",
copiedTextRange: test.ranges(),
providePasteEdits: true
})

Просмотреть файл

@ -0,0 +1,13 @@
/// <reference path='./fourslash.ts' />
// @Filename: /file1.ts
//// [|const a = 1;|]
//// [|function foo() {
//// console.log("testing");}|]
//// [|//This is a comment|]
verify.preparePasteEdits({
copiedFromFile: "/file1.ts",
copiedTextRange: test.ranges(),
providePasteEdits: false,
})