diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 7d9d3c6c9f9bb..08698bf8b2721 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3979,8 +3979,8 @@ export class TestState { }; } - public verifyRefactorAvailable(negative: boolean, triggerReason: ts.RefactorTriggerReason, name: string, actionName?: string, actionDescription?: string) { - let refactors = this.getApplicableRefactorsAtSelection(triggerReason); + public verifyRefactorAvailable(negative: boolean, triggerReason: ts.RefactorTriggerReason, name: string, actionName?: string, actionDescription?: string, kind?: string, preferences = ts.emptyOptions, includeInteractiveActions?: boolean) { + let refactors = this.getApplicableRefactorsAtSelection(triggerReason, kind, preferences, includeInteractiveActions); refactors = refactors.filter(r => r.name === name); if (actionName !== undefined) { @@ -4445,8 +4445,8 @@ export class TestState { test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved"); } - private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions) { - return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind); + private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions, includeInteractiveActions?: boolean) { + return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind, includeInteractiveActions); } private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, includeInteractiveActions?: boolean): readonly ts.ApplicableRefactorInfo[] { return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind, includeInteractiveActions); // eslint-disable-line local/no-in-operator diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 4ec5ff653d045..64577f69ced7c 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -212,12 +212,12 @@ export class VerifyNegatable { this.state.verifyRefactorsAvailable(names); } - public refactorAvailable(name: string, actionName?: string, actionDescription?: string) { - this.state.verifyRefactorAvailable(this.negative, "implicit", name, actionName, actionDescription); + public refactorAvailable(name: string, actionName?: string, actionDescription?: string, kind?: string, preferences = ts.emptyOptions, includeInteractiveActions?: boolean) { + this.state.verifyRefactorAvailable(this.negative, "implicit", name, actionName, actionDescription, kind, preferences, includeInteractiveActions); } - public refactorAvailableForTriggerReason(triggerReason: ts.RefactorTriggerReason, name: string, actionName?: string) { - this.state.verifyRefactorAvailable(this.negative, triggerReason, name, actionName); + public refactorAvailableForTriggerReason(triggerReason: ts.RefactorTriggerReason, name: string, actionName?: string, actionDescription?: string, kind?: string, preferences = ts.emptyOptions, includeInteractiveActions?: boolean) { + this.state.verifyRefactorAvailable(this.negative, triggerReason, name, actionName, actionDescription, kind, preferences, includeInteractiveActions); } public refactorKindAvailable(kind: string, expected: string[], preferences = ts.emptyOptions) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index e55e4e37f7af5..b9406e8f1b594 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -6,7 +6,6 @@ import { assertType, BindingElement, Block, - BlockLike, BreakStatement, CancellationToken, canHaveModifiers, @@ -67,6 +66,7 @@ import { isAssignmentExpression, isBinaryExpression, isBlock, + isBlockLike, isBlockScope, isCaseClause, isClassLike, @@ -2221,18 +2221,6 @@ function isExtractableExpression(node: Node): boolean { return true; } -function isBlockLike(node: Node): node is BlockLike { - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseClause: - return true; - default: - return false; - } -} - function isInJSXContent(node: Node) { return isStringLiteralJsxAttribute(node) || (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent)); diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index 64fe7b0a4ae25..1d3d072d89c07 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -39,6 +39,7 @@ import { filter, find, FindAllReferences, + findAncestor, findIndex, findLast, firstDefined, @@ -58,6 +59,7 @@ import { getRelativePathFromFile, getSourceFileOfNode, getSynthesizedDeepClone, + getTokenAtPosition, getUniqueName, hasJSFileExtension, hasSyntacticModifier, @@ -73,6 +75,7 @@ import { isArrayLiteralExpression, isBinaryExpression, isBindingElement, + isBlockLike, isDeclarationName, isExportDeclaration, isExportSpecifier, @@ -165,6 +168,16 @@ registerRefactor(refactorNameForMoveToFile, { if (!interactiveRefactorArguments) { return emptyArray; } + /** If the start/end nodes of the selection are inside a block like node do not show the `Move to file` code action + * This condition is used in order to show less often the `Move to file` code action */ + if (context.endPosition !== undefined) { + const file = context.file; + const startNodeAncestor = findAncestor(getTokenAtPosition(file, context.startPosition), isBlockLike); + const endNodeAncestor = findAncestor(getTokenAtPosition(file, context.endPosition), isBlockLike); + if (startNodeAncestor && !isSourceFile(startNodeAncestor) && endNodeAncestor && !isSourceFile(endNodeAncestor)) { + return emptyArray; + } + } if (context.preferences.allowTextChangesInNewFiles && statements) { return [{ name: refactorNameForMoveToFile, description, actions: [moveToFileAction] }]; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0b2072f449008..dc5c6c777528e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -9,6 +9,7 @@ import { BinaryExpression, binarySearchKey, BindingElement, + BlockLike, BreakOrContinueStatement, CallExpression, canHaveModifiers, @@ -4274,3 +4275,16 @@ export function fileShouldUseJavaScriptRequire(file: SourceFile | string, progra } return preferRequire; } + +/** @internal */ +export function isBlockLike(node: Node): node is BlockLike { + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseClause: + return true; + default: + return false; + } +} diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5daa7c0b3167e..ff8fad9abff01 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -281,8 +281,8 @@ declare namespace FourSlashInterface { codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; applicableRefactorAvailableForRange(): void; - refactorAvailable(name: string, actionName?: string, actionDescription?: string): void; - refactorAvailableForTriggerReason(triggerReason: RefactorTriggerReason, name: string, action?: string): void; + refactorAvailable(name: string, actionName?: string, actionDescription?: string, kind?: string, preferences?: {}, includeInteractiveActions?: boolean): void; + refactorAvailableForTriggerReason(triggerReason: RefactorTriggerReason, name: string, action?: string, actionDescription?: string, kind?: string, preferences?: {}, includeInteractiveActions?: boolean): void; refactorKindAvailable(refactorKind: string, expected: string[], preferences?: {}): void; } class verify extends verifyNegatable { diff --git a/tests/cases/fourslash/moveToFile_refactorAvailable1.ts b/tests/cases/fourslash/moveToFile_refactorAvailable1.ts new file mode 100644 index 0000000000000..3681be5cf49b6 --- /dev/null +++ b/tests/cases/fourslash/moveToFile_refactorAvailable1.ts @@ -0,0 +1,18 @@ +/// + +////namespace ns { +//// /*a*/export function fn() { +//// } +//// fn(); +//// /*b*/ +////} + +goTo.select("a", "b"); +verify.not.refactorAvailable("Move to file", +/*actionName*/ undefined, +/*actionDescription*/ undefined, +/*kind*/ undefined, +{ + allowTextChangesInNewFiles : true +}, +/*includeInteractiveActions*/ true); diff --git a/tests/cases/fourslash/moveToFile_refactorAvailable2.ts b/tests/cases/fourslash/moveToFile_refactorAvailable2.ts new file mode 100644 index 0000000000000..7353c76cb0cff --- /dev/null +++ b/tests/cases/fourslash/moveToFile_refactorAvailable2.ts @@ -0,0 +1,20 @@ +/// + +/////*a*/ +////namespace ns { +//// export function fn() { +//// } +//// fn(); +////} +/////*b*/ + +goTo.select("a", "b"); +verify.refactorAvailable("Move to file", +/*actionName*/ undefined, +/*actionDescription*/ undefined, +/*kind*/ undefined, +{ + allowTextChangesInNewFiles : true +}, +/*includeInteractiveActions*/ true); +