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);
+