Adding preparePasteEdits method to check if smart copy/paste should be applied (#60053)
This commit is contained in:
Родитель
3ad0f75248
Коммит
8499803ae0
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
Загрузка…
Ссылка в новой задаче