Conversation
src/compiler/diagnosticMessages.json
Outdated
| "category": "Message", | ||
| "code": 95046 | ||
| }, | ||
| "Move to new file": { |
| }); | ||
| } | ||
|
|
||
| export function newFileChanges(oldFile: SourceFile, fileName: string, statements: ReadonlyArray<Statement>, newLineCharacter: string): FileTextChanges { |
| const range = createTextRangeFromSpan(getRefactorContextSpan(context)); | ||
| const { statements } = file; | ||
|
|
||
| const startNodeIndex = findIndex(statements, s => s.end > range.pos); |
There was a problem hiding this comment.
so that means that if i select a nested function to move, we will move the containing function instead.. would not it be better to not make the refactoring available at this case?
There was a problem hiding this comment.
See moveToNewFile_rangeInvalid.ts.
| ): ReadonlyArray<Statement> { | ||
| const checker = program.getTypeChecker(); | ||
|
|
||
| if (!oldFile.externalModuleIndicator) { |
There was a problem hiding this comment.
what if this is a .js file with require calls?
There was a problem hiding this comment.
also what should we do with require statements in a .js file. seems like we have enough information in the current file to know if we should create import or const x = require(...)..
| }); | ||
| } | ||
|
|
||
| function deleteUnusedImports(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { |
There was a problem hiding this comment.
this looks a lot like the logic we use in fixUnusedIdentifer for module imports.. can we consolidate?
There was a problem hiding this comment.
I don't think that's easy since fixUnusedIdentifier deletes a single identifier starting from the identifier, but here we have a set of symbols and want to recurse down starting from the sourcefile and delete everything in the set.
|
|
||
| function deleteUnusedOldImports(oldFile: SourceFile, toMove: ReadonlyArray<Statement>, changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { | ||
| for (const statement of oldFile.statements) { | ||
| if (!contains(toMove, statement) && isImportDeclaration(statement)) { |
There was a problem hiding this comment.
what about ImportEqualsDeclaration?
| while (true) { | ||
| const name = combinePaths(inDirectory, moduleName + extension); | ||
| if (!host.fileExists(name)) return moduleName; | ||
| moduleName += "0"; |
There was a problem hiding this comment.
nit, i would make that 1-based (more natural) and i would use a separator of sorts. VSCode adds a .1, Windows will add (2) to duplicate files.
we could go with the .1 or we can use _1.
|
|
||
| function getNewModuleName(movedSymbols: ReadonlySymbolSet): string { | ||
| let name: string | undefined; | ||
| movedSymbols.forEach(s => { if (name === undefined) name = symbolNameNoDefault(s); }); |
There was a problem hiding this comment.
return forEach(movedSymbols, symbolNameNoDefault) || "newFile";| }); | ||
|
|
||
| const oldFileImport = makeImportIfNecessary(oldFileDefault, oldFileNamedImports, `./${removeFileExtension(getBaseFileName(oldFile.fileName))}`); | ||
| return [...copiedOldImports, ...(oldFileImport ? [oldFileImport] : emptyArray)]; |
There was a problem hiding this comment.
nit, why not just push on copiedOldImports instead of cloning it.
| if (isInImport(decl)) { | ||
| oldImportsNeededByNewFile.add(symbol); | ||
| } | ||
| else if (isTopLevelDeclaration(decl) && !movedSymbols.has(symbol)) { |
There was a problem hiding this comment.
what if it is not in movedSymbols just case we have not visited it yet, e.g. a forward reference to a type or an interface.. or even a class, that exists later on in the selected range?
| function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { | ||
| const modifiers = concatenate([createModifier(SyntaxKind.ExportKeyword)], d.modifiers); | ||
| switch (d.kind) { | ||
| case SyntaxKind.FunctionDeclaration: |
|
Fixes #13859 |
|
New commit fixes #23793 |
src/compiler/factory.ts
Outdated
| node.expression = parenthesizeExpressionForExpressionStatement(expression); | ||
| return node; | ||
| return createExpressionStatement(parenthesizeExpressionForExpressionStatement(expression)); | ||
|
|
|
@Andy-MS we need to make this opt-in to avoid old versions of VS/VSCode form getting edits including new files. |
|
we also need to add the file to |
|
Latest commit adds an |
src/compiler/core.ts
Outdated
| /** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ | ||
| export function findIndex<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean): number { | ||
| for (let i = 0; i < array.length; i++) { | ||
| export function findIndex<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean, startIndex = 0): number { |
There was a problem hiding this comment.
can you instead make it startIndex?: number and use let i = startIndex || 0 ?
| // If previous file was global, this is easy. | ||
| changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatements(oldFile, usage, changes, toMove, program, newModuleName)); | ||
|
|
||
| addNewFileToTsconfig(program, changes, normalizePath(combinePaths(oldFile.fileName, "..", newFileNameWithExtension))); |
There was a problem hiding this comment.
should not that be the filename relative to the tsconfig.json path?
| const refactorName = "Move to a new file"; | ||
| registerRefactor(refactorName, { | ||
| getAvailableActions(context): ApplicableRefactorInfo[] { | ||
| if (getStatementsToMove(context) === undefined || !context.preferences.allowTextChangesInNewFiles) return undefined; |
There was a problem hiding this comment.
The second check looks cheaper. Would it make sense to do that first?
| } | ||
| }); | ||
|
|
||
| function getStatementsToMove(context: RefactorContext): ReadonlyArray<Statement> | undefined { |
There was a problem hiding this comment.
Could/should this logic be shared with extract function/constant?
There was a problem hiding this comment.
The corresponding function there would be getRangeToExtract. That seems to have a lot of extract-symbol-specific logic in it, though. And here we should only be moving top-level statements.
| ]; | ||
| } | ||
|
|
||
| function deleteUnusedOldImports(oldFile: SourceFile, toMove: ReadonlyArray<Statement>, changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { |
There was a problem hiding this comment.
I think the goal of this function is to remove the imports that were only needed for the code being moved. It seems though, like it might also remove imports that were unused to begin with. Personally, I think it seems strange to do a partial Organize Imports as part of this operation. If we are going to do so, then my preference would be to have the editor trigger (unless the server has some extra knowledge?) so that it can appear separately on the undo stack.
There was a problem hiding this comment.
unusedImportsFromOldFile will be a subset of oldImportsNeededByNewFile, so we won't remove purely-unused imports.
| /// <reference path='fourslash.ts' /> | ||
|
|
||
| // @Filename: /a.ts | ||
| ////import { a, b } from "./other"; |
There was a problem hiding this comment.
[I'm sure this is clear from the product code, but I haven't read it in detail.] What happens if you extract an import statement. Is that disallowed?
There was a problem hiding this comment.
What about a return statement? Can a return statement be moved to another file?
There was a problem hiding this comment.
What happens if you extract an import statement
I think we should allow this, though there's a bug if the moved import is still needed in the old file. #23968
Can a return statement be moved to another file?
Only top-level statements can be moved, and 'return' at top-level is an errorany way.
|
@amcasey Good to go? |
The user may highlight declarations on the top-level of a file and move them to a new file. Imports will be updated, and declarations will be exported as necessary (if they were private in the old file but used in the new file, or vice-versa).
This doesn't work for nested declarations because those may need to close over things -- extractSymbol handles that and adds parameters as necessary. So it would take two refactorings to take something from an inner scope to a different file.