@@ -3,7 +3,7 @@ namespace ts.refactor {
3
3
const refactorName = "Move to a new file" ;
4
4
registerRefactor ( refactorName , {
5
5
getAvailableActions ( context ) : ApplicableRefactorInfo [ ] {
6
- if ( ! context . preferences . allowTextChangesInNewFiles || getStatementsToMove ( context ) === undefined ) return undefined ;
6
+ if ( ! context . preferences . allowTextChangesInNewFiles || getFirstAndLastStatementToMove ( context ) === undefined ) return undefined ;
7
7
const description = getLocaleSpecificMessage ( Diagnostics . Move_to_a_new_file ) ;
8
8
return [ { name : refactorName , description, actions : [ { name : refactorName , description } ] } ] ;
9
9
} ,
@@ -15,7 +15,7 @@ namespace ts.refactor {
15
15
}
16
16
} ) ;
17
17
18
- function getStatementsToMove ( context : RefactorContext ) : ReadonlyArray < Statement > | undefined {
18
+ function getFirstAndLastStatementToMove ( context : RefactorContext ) : { readonly first : number , readonly afterLast : number } | undefined {
19
19
const { file } = context ;
20
20
const range = createTextRangeFromSpan ( getRefactorContextSpan ( context ) ) ;
21
21
const { statements } = file ;
@@ -28,12 +28,12 @@ namespace ts.refactor {
28
28
// Can't be partially into the next node
29
29
if ( afterEndNodeIndex !== - 1 && ( afterEndNodeIndex === 0 || statements [ afterEndNodeIndex ] . getStart ( file ) < range . end ) ) return undefined ;
30
30
31
- return statements . slice ( startNodeIndex , afterEndNodeIndex === - 1 ? statements . length : afterEndNodeIndex ) ;
31
+ return { first : startNodeIndex , afterLast : afterEndNodeIndex === - 1 ? statements . length : afterEndNodeIndex } ;
32
32
}
33
33
34
- function doChange ( oldFile : SourceFile , program : Program , toMove : ReadonlyArray < Statement > , changes : textChanges . ChangeTracker , host : LanguageServiceHost ) : void {
34
+ function doChange ( oldFile : SourceFile , program : Program , toMove : ToMove , changes : textChanges . ChangeTracker , host : LanguageServiceHost ) : void {
35
35
const checker = program . getTypeChecker ( ) ;
36
- const usage = getUsageInfo ( oldFile , toMove , checker ) ;
36
+ const usage = getUsageInfo ( oldFile , toMove . all , checker ) ;
37
37
38
38
const currentDirectory = getDirectoryPath ( oldFile . fileName ) ;
39
39
const extension = extensionFromPath ( oldFile . fileName ) ;
@@ -46,6 +46,42 @@ namespace ts.refactor {
46
46
addNewFileToTsconfig ( program , changes , oldFile . fileName , newFileNameWithExtension , hostGetCanonicalFileName ( host ) ) ;
47
47
}
48
48
49
+ interface StatementRange {
50
+ readonly first : Statement ;
51
+ readonly last : Statement ;
52
+ }
53
+ interface ToMove {
54
+ readonly all : ReadonlyArray < Statement > ;
55
+ readonly ranges : ReadonlyArray < StatementRange > ;
56
+ }
57
+
58
+ // Filters imports out of the range of statements to move. Imports will be copied to the new file anyway, and may still be needed in the old file.
59
+ function getStatementsToMove ( context : RefactorContext ) : ToMove | undefined {
60
+ const { statements } = context . file ;
61
+ const { first, afterLast } = getFirstAndLastStatementToMove ( context ) ! ;
62
+ const all : Statement [ ] = [ ] ;
63
+ const ranges : StatementRange [ ] = [ ] ;
64
+ const rangeToMove = statements . slice ( first , afterLast ) ;
65
+ getRangesWhere ( rangeToMove , s => ! isPureImport ( s ) , ( start , afterEnd ) => {
66
+ for ( let i = start ; i < afterEnd ; i ++ ) all . push ( rangeToMove [ i ] ) ;
67
+ ranges . push ( { first : rangeToMove [ start ] , last : rangeToMove [ afterEnd - 1 ] } ) ;
68
+ } ) ;
69
+ return { all, ranges } ;
70
+ }
71
+
72
+ function isPureImport ( node : Node ) : boolean {
73
+ switch ( node . kind ) {
74
+ case SyntaxKind . ImportDeclaration :
75
+ return true ;
76
+ case SyntaxKind . ImportEqualsDeclaration :
77
+ return ! hasModifier ( node , ModifierFlags . Export ) ;
78
+ case SyntaxKind . VariableStatement :
79
+ return ( node as VariableStatement ) . declarationList . declarations . every ( d => isRequireCall ( d . initializer , /*checkArgumentIsStringLiteralLike*/ true ) ) ;
80
+ default :
81
+ return false ;
82
+ }
83
+ }
84
+
49
85
function addNewFileToTsconfig ( program : Program , changes : textChanges . ChangeTracker , oldFileName : string , newFileNameWithExtension : string , getCanonicalFileName : GetCanonicalFileName ) : void {
50
86
const cfg = program . getCompilerOptions ( ) . configFile ;
51
87
if ( ! cfg ) return ;
@@ -62,13 +98,13 @@ namespace ts.refactor {
62
98
}
63
99
64
100
function getNewStatements (
65
- oldFile : SourceFile , usage : UsageInfo , changes : textChanges . ChangeTracker , toMove : ReadonlyArray < Statement > , program : Program , newModuleName : string ,
101
+ oldFile : SourceFile , usage : UsageInfo , changes : textChanges . ChangeTracker , toMove : ToMove , program : Program , newModuleName : string ,
66
102
) : ReadonlyArray < Statement > {
67
103
const checker = program . getTypeChecker ( ) ;
68
104
69
105
if ( ! oldFile . externalModuleIndicator && ! oldFile . commonJsModuleIndicator ) {
70
- changes . deleteNodeRange ( oldFile , first ( toMove ) , last ( toMove ) ) ;
71
- return toMove ;
106
+ deleteMovedStatements ( oldFile , toMove . ranges , changes ) ;
107
+ return toMove . all ;
72
108
}
73
109
74
110
const useEs6ModuleSyntax = ! ! oldFile . externalModuleIndicator ;
@@ -77,17 +113,23 @@ namespace ts.refactor {
77
113
changes . insertNodeBefore ( oldFile , oldFile . statements [ 0 ] , importsFromNewFile , /*blankLineBetween*/ true ) ;
78
114
}
79
115
80
- deleteUnusedOldImports ( oldFile , toMove , changes , usage . unusedImportsFromOldFile , checker ) ;
81
- changes . deleteNodeRange ( oldFile , first ( toMove ) , last ( toMove ) ) ;
116
+ deleteUnusedOldImports ( oldFile , toMove . all , changes , usage . unusedImportsFromOldFile , checker ) ;
117
+ deleteMovedStatements ( oldFile , toMove . ranges , changes ) ;
82
118
83
119
updateImportsInOtherFiles ( changes , program , oldFile , usage . movedSymbols , newModuleName ) ;
84
120
85
121
return [
86
122
...getNewFileImportsAndAddExportInOldFile ( oldFile , usage . oldImportsNeededByNewFile , usage . newFileImportsFromOldFile , changes , checker , useEs6ModuleSyntax ) ,
87
- ...addExports ( oldFile , toMove , usage . oldFileImportsFromNewFile , useEs6ModuleSyntax ) ,
123
+ ...addExports ( oldFile , toMove . all , usage . oldFileImportsFromNewFile , useEs6ModuleSyntax ) ,
88
124
] ;
89
125
}
90
126
127
+ function deleteMovedStatements ( sourceFile : SourceFile , moved : ReadonlyArray < StatementRange > , changes : textChanges . ChangeTracker ) {
128
+ for ( const { first, last } of moved ) {
129
+ changes . deleteNodeRange ( sourceFile , first , last ) ;
130
+ }
131
+ }
132
+
91
133
function deleteUnusedOldImports ( oldFile : SourceFile , toMove : ReadonlyArray < Statement > , changes : textChanges . ChangeTracker , toDelete : ReadonlySymbolSet , checker : TypeChecker ) {
92
134
for ( const statement of oldFile . statements ) {
93
135
if ( contains ( toMove , statement ) ) continue ;
0 commit comments