mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-04-17 01:49:41 -05:00
Updating move to file (#58257)
This commit is contained in:
@@ -404,6 +404,15 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
|
||||
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
|
||||
break;
|
||||
case ImportKind.CommonJS:
|
||||
if (compilerOptions.verbatimModuleSyntax) {
|
||||
const prevValue = (entry.namedImports ||= new Map()).get(symbolName);
|
||||
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
|
||||
}
|
||||
else {
|
||||
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
|
||||
entry.namespaceLikeImport = { importKind, name: symbolName, addAsTypeOnly };
|
||||
}
|
||||
break;
|
||||
case ImportKind.Namespace:
|
||||
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
|
||||
entry.namespaceLikeImport = { importKind, name: symbolName, addAsTypeOnly };
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
import {
|
||||
codefix,
|
||||
Debug,
|
||||
findAncestor,
|
||||
isAnyImportOrRequireStatement,
|
||||
Program,
|
||||
skipAlias,
|
||||
SourceFile,
|
||||
Symbol,
|
||||
TypeChecker,
|
||||
} from "../_namespaces/ts";
|
||||
import { addImportsForMovedSymbols } from "./moveToFile";
|
||||
/**
|
||||
* Returned by refactor functions when some error message needs to be surfaced to users.
|
||||
*
|
||||
@@ -26,3 +38,30 @@ export function refactorKindBeginsWith(known: string, requested: string | undefi
|
||||
if (!requested) return true;
|
||||
return known.substr(0, requested.length) === requested;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function addTargetFileImports(
|
||||
oldFile: SourceFile,
|
||||
importsToCopy: Map<Symbol, [boolean, codefix.ImportOrRequireAliasDeclaration | undefined]>,
|
||||
targetFileImportsFromOldFile: Map<Symbol, boolean>,
|
||||
checker: TypeChecker,
|
||||
program: Program,
|
||||
importAdder: codefix.ImportAdder,
|
||||
) {
|
||||
/**
|
||||
* Recomputing the imports is preferred with importAdder because it manages multiple import additions for a file and writes then to a ChangeTracker,
|
||||
* but sometimes it fails because of unresolved imports from files, or when a source file is not available for the target file (in this case when creating a new file).
|
||||
* So in that case, fall back to copying the import verbatim.
|
||||
*/
|
||||
importsToCopy.forEach(([isValidTypeOnlyUseSite, declaration], symbol) => {
|
||||
const targetSymbol = skipAlias(symbol, checker);
|
||||
if (checker.isUnknownSymbol(targetSymbol)) {
|
||||
importAdder.addVerbatimImport(Debug.checkDefined(declaration ?? findAncestor(symbol.declarations?.[0], isAnyImportOrRequireStatement)));
|
||||
}
|
||||
else {
|
||||
importAdder.addImportFromExportedSymbol(targetSymbol, isValidTypeOnlyUseSite, declaration);
|
||||
}
|
||||
});
|
||||
|
||||
addImportsForMovedSymbols(targetFileImportsFromOldFile, oldFile.fileName, importAdder, program);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { getModuleSpecifier } from "../../compiler/moduleSpecifiers";
|
||||
import { getModuleSpecifier } from "../../compiler/_namespaces/ts.moduleSpecifiers";
|
||||
import {
|
||||
AnyImportOrRequireStatement,
|
||||
append,
|
||||
ApplicableRefactorInfo,
|
||||
arrayFrom,
|
||||
AssignmentDeclarationKind,
|
||||
@@ -19,6 +17,7 @@ import {
|
||||
Comparison,
|
||||
concatenate,
|
||||
contains,
|
||||
createFutureSourceFile,
|
||||
createModuleSpecifierResolutionHost,
|
||||
createTextRangeFromSpan,
|
||||
Debug,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
EnumDeclaration,
|
||||
escapeLeadingUnderscores,
|
||||
ExportDeclaration,
|
||||
ExportKind,
|
||||
Expression,
|
||||
ExpressionStatement,
|
||||
extensionFromPath,
|
||||
@@ -45,13 +45,16 @@ import {
|
||||
flatMap,
|
||||
forEachKey,
|
||||
FunctionDeclaration,
|
||||
FutureSourceFile,
|
||||
getAssignmentDeclarationKind,
|
||||
GetCanonicalFileName,
|
||||
getDecorators,
|
||||
getDirectoryPath,
|
||||
getEmitScriptTarget,
|
||||
getLineAndCharacterOfPosition,
|
||||
getLocaleSpecificMessage,
|
||||
getModifiers,
|
||||
getNameForExportedSymbol,
|
||||
getNormalizedAbsolutePath,
|
||||
getPropertySymbolFromBindingElement,
|
||||
getQuotePreference,
|
||||
@@ -71,9 +74,7 @@ import {
|
||||
ImportDeclaration,
|
||||
ImportEqualsDeclaration,
|
||||
importFromModuleSpecifier,
|
||||
insertImports,
|
||||
InterfaceDeclaration,
|
||||
InternalSymbolName,
|
||||
isArrayLiteralExpression,
|
||||
isBinaryExpression,
|
||||
isBindingElement,
|
||||
@@ -83,12 +84,16 @@ import {
|
||||
isExportSpecifier,
|
||||
isExpressionStatement,
|
||||
isExternalModuleReference,
|
||||
isFullSourceFile,
|
||||
isFunctionLikeDeclaration,
|
||||
isIdentifier,
|
||||
isImportClause,
|
||||
isImportDeclaration,
|
||||
isImportEqualsDeclaration,
|
||||
isImportSpecifier,
|
||||
isNamedExports,
|
||||
isNamedImports,
|
||||
isNamespaceImport,
|
||||
isObjectBindingPattern,
|
||||
isObjectLiteralExpression,
|
||||
isOmittedExpression,
|
||||
@@ -103,17 +108,16 @@ import {
|
||||
isValidTypeOnlyAliasUseSite,
|
||||
isVariableDeclaration,
|
||||
isVariableDeclarationInitializedToRequire,
|
||||
isVariableDeclarationList,
|
||||
isVariableStatement,
|
||||
LanguageServiceHost,
|
||||
last,
|
||||
length,
|
||||
makeImportIfNecessary,
|
||||
makeStringLiteral,
|
||||
mapDefined,
|
||||
ModifierFlags,
|
||||
ModifierLike,
|
||||
ModuleDeclaration,
|
||||
ModuleKind,
|
||||
NamedImportBindings,
|
||||
Node,
|
||||
NodeFlags,
|
||||
@@ -127,7 +131,6 @@ import {
|
||||
RefactorContext,
|
||||
RefactorEditInfo,
|
||||
RequireOrImportCall,
|
||||
RequireVariableStatement,
|
||||
resolvePath,
|
||||
ScriptTarget,
|
||||
skipAlias,
|
||||
@@ -151,6 +154,7 @@ import {
|
||||
VariableDeclarationList,
|
||||
VariableStatement,
|
||||
} from "../_namespaces/ts";
|
||||
import { addTargetFileImports } from "../_namespaces/ts.refactor";
|
||||
import { registerRefactor } from "../refactorProvider";
|
||||
|
||||
const refactorNameForMoveToFile = "Move to file";
|
||||
@@ -213,169 +217,58 @@ function error(notApplicableReason: string) {
|
||||
|
||||
function doChange(context: RefactorContext, oldFile: SourceFile, targetFile: string, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void {
|
||||
const checker = program.getTypeChecker();
|
||||
// For a new file
|
||||
if (!host.fileExists(targetFile)) {
|
||||
changes.createNewFile(oldFile, targetFile, getNewStatementsAndRemoveFromOldFile(oldFile, targetFile, getUsageInfo(oldFile, toMove.all, checker), changes, toMove, program, host, preferences));
|
||||
const isForNewFile = !host.fileExists(targetFile);
|
||||
const targetSourceFile = isForNewFile
|
||||
? createFutureSourceFile(targetFile, oldFile.externalModuleIndicator ? ModuleKind.ESNext : oldFile.commonJsModuleIndicator ? ModuleKind.CommonJS : undefined, program, host)
|
||||
: Debug.checkDefined(program.getSourceFile(targetFile));
|
||||
const importAdderForOldFile = codefix.createImportAdder(oldFile, context.program, context.preferences, context.host);
|
||||
const importAdderForNewFile = codefix.createImportAdder(targetSourceFile, context.program, context.preferences, context.host);
|
||||
getNewStatementsAndRemoveFromOldFile(oldFile, targetSourceFile, getUsageInfo(oldFile, toMove.all, checker, isForNewFile ? undefined : getExistingLocals(targetSourceFile as SourceFile, toMove.all, checker)), changes, toMove, program, host, preferences, importAdderForNewFile, importAdderForOldFile);
|
||||
if (isForNewFile) {
|
||||
addNewFileToTsconfig(program, changes, oldFile.fileName, targetFile, hostGetCanonicalFileName(host));
|
||||
}
|
||||
else {
|
||||
const targetSourceFile = Debug.checkDefined(program.getSourceFile(targetFile));
|
||||
const importAdder = codefix.createImportAdder(targetSourceFile, context.program, context.preferences, context.host);
|
||||
getNewStatementsAndRemoveFromOldFile(oldFile, targetSourceFile, getUsageInfo(oldFile, toMove.all, checker, getExistingLocals(targetSourceFile, toMove.all, checker)), changes, toMove, program, host, preferences, importAdder);
|
||||
}
|
||||
}
|
||||
|
||||
function getNewStatementsAndRemoveFromOldFile(
|
||||
/** @internal */
|
||||
export function getNewStatementsAndRemoveFromOldFile(
|
||||
oldFile: SourceFile,
|
||||
targetFile: string | SourceFile,
|
||||
targetFile: SourceFile | FutureSourceFile,
|
||||
usage: UsageInfo,
|
||||
changes: textChanges.ChangeTracker,
|
||||
toMove: ToMove,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
importAdder?: codefix.ImportAdder,
|
||||
importAdderForNewFile: codefix.ImportAdder,
|
||||
importAdderForOldFile: codefix.ImportAdder,
|
||||
) {
|
||||
const checker = program.getTypeChecker();
|
||||
const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective);
|
||||
if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByTargetFile.size === 0 && usage.targetFileImportsFromOldFile.size === 0 && typeof targetFile === "string") {
|
||||
deleteMovedStatements(oldFile, toMove.ranges, changes);
|
||||
return [...prologueDirectives, ...toMove.all];
|
||||
}
|
||||
|
||||
// If the targetFile is a string, it’s the file name for a new file, if it’s a SourceFile, it’s the existing target file.
|
||||
const targetFileName = typeof targetFile === "string" ? targetFile : targetFile.fileName;
|
||||
|
||||
const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(targetFileName, program, host, !!oldFile.commonJsModuleIndicator);
|
||||
const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(targetFile.fileName, program, host, !!oldFile.commonJsModuleIndicator);
|
||||
const quotePreference = getQuotePreference(oldFile, preferences);
|
||||
const importsFromTargetFile = createOldFileImportsFromTargetFile(oldFile, usage.oldFileImportsFromTargetFile, targetFileName, program, host, useEsModuleSyntax, quotePreference);
|
||||
if (importsFromTargetFile) {
|
||||
insertImports(changes, oldFile, importsFromTargetFile, /*blankLineBetween*/ true, preferences);
|
||||
}
|
||||
|
||||
deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker);
|
||||
addImportsForMovedSymbols(usage.oldFileImportsFromTargetFile, targetFile.fileName, importAdderForOldFile, program);
|
||||
deleteUnusedOldImports(oldFile, toMove.all, usage.unusedImportsFromOldFile, importAdderForOldFile);
|
||||
importAdderForOldFile.writeFixes(changes, quotePreference);
|
||||
deleteMovedStatements(oldFile, toMove.ranges, changes);
|
||||
updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, targetFileName, quotePreference);
|
||||
|
||||
const imports = getTargetFileImportsAndAddExportInOldFile(oldFile, targetFileName, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference, importAdder);
|
||||
const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromTargetFile, useEsModuleSyntax);
|
||||
if (typeof targetFile !== "string") {
|
||||
if (targetFile.statements.length > 0) {
|
||||
moveStatementsToTargetFile(changes, program, body, targetFile, toMove);
|
||||
}
|
||||
else {
|
||||
changes.insertNodesAtEndOfFile(targetFile, body, /*blankLineBetween*/ false);
|
||||
}
|
||||
if (imports.length > 0) {
|
||||
insertImports(changes, targetFile, imports, /*blankLineBetween*/ true, preferences);
|
||||
}
|
||||
updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, targetFile.fileName, quotePreference);
|
||||
addExportsInOldFile(oldFile, usage.targetFileImportsFromOldFile, changes, useEsModuleSyntax);
|
||||
addTargetFileImports(oldFile, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, checker, program, importAdderForNewFile);
|
||||
if (!isFullSourceFile(targetFile) && prologueDirectives.length) {
|
||||
// Ensure prologue directives come before imports
|
||||
changes.insertStatementsInNewFile(targetFile.fileName, prologueDirectives, oldFile);
|
||||
}
|
||||
if (importAdder) {
|
||||
importAdder.writeFixes(changes, quotePreference);
|
||||
importAdderForNewFile.writeFixes(changes, quotePreference);
|
||||
const body = addExports(oldFile, toMove.all, arrayFrom(usage.oldFileImportsFromTargetFile.keys()), useEsModuleSyntax);
|
||||
if (isFullSourceFile(targetFile) && targetFile.statements.length > 0) {
|
||||
moveStatementsToTargetFile(changes, program, body, targetFile, toMove);
|
||||
}
|
||||
if (imports.length && body.length) {
|
||||
return [
|
||||
...prologueDirectives,
|
||||
...imports,
|
||||
SyntaxKind.NewLineTrivia as const,
|
||||
...body,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...prologueDirectives,
|
||||
...imports,
|
||||
...body,
|
||||
];
|
||||
}
|
||||
|
||||
function getTargetFileImportsAndAddExportInOldFile(
|
||||
oldFile: SourceFile,
|
||||
targetFile: string,
|
||||
importsToCopy: Map<Symbol, boolean>,
|
||||
targetFileImportsFromOldFile: Set<Symbol>,
|
||||
changes: textChanges.ChangeTracker,
|
||||
checker: TypeChecker,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
useEsModuleSyntax: boolean,
|
||||
quotePreference: QuotePreference,
|
||||
importAdder?: codefix.ImportAdder,
|
||||
): readonly AnyImportOrRequireStatement[] {
|
||||
const copiedOldImports: AnyImportOrRequireStatement[] = [];
|
||||
/**
|
||||
* Recomputing the imports is preferred with importAdder because it manages multiple import additions for a file and writes then to a ChangeTracker,
|
||||
* but sometimes it fails because of unresolved imports from files, or when a source file is not available for the target file (in this case when creating a new file).
|
||||
* So in that case, fall back to copying the import verbatim.
|
||||
*/
|
||||
if (importAdder) {
|
||||
importsToCopy.forEach((isValidTypeOnlyUseSite, symbol) => {
|
||||
try {
|
||||
importAdder.addImportFromExportedSymbol(skipAlias(symbol, checker), isValidTypeOnlyUseSite);
|
||||
}
|
||||
catch {
|
||||
for (const oldStatement of oldFile.statements) {
|
||||
forEachImportInStatement(oldStatement, i => {
|
||||
append(copiedOldImports, filterImport(i, factory.createStringLiteral(moduleSpecifierFromImport(i).text), name => importsToCopy.has(checker.getSymbolAtLocation(name)!)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
else if (isFullSourceFile(targetFile)) {
|
||||
changes.insertNodesAtEndOfFile(targetFile, body, /*blankLineBetween*/ false);
|
||||
}
|
||||
else {
|
||||
const targetSourceFile = program.getSourceFile(targetFile); // Would be undefined for a new file
|
||||
for (const oldStatement of oldFile.statements) {
|
||||
forEachImportInStatement(oldStatement, i => {
|
||||
// Recomputing module specifier
|
||||
const moduleSpecifier = moduleSpecifierFromImport(i);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const resolved = program.getResolvedModuleFromModuleSpecifier(moduleSpecifier);
|
||||
const fileName = resolved?.resolvedModule?.resolvedFileName;
|
||||
if (fileName && targetSourceFile) {
|
||||
const newModuleSpecifier = getModuleSpecifier(compilerOptions, targetSourceFile, targetSourceFile.fileName, fileName, createModuleSpecifierResolutionHost(program, host));
|
||||
append(copiedOldImports, filterImport(i, makeStringLiteral(newModuleSpecifier, quotePreference), name => importsToCopy.has(checker.getSymbolAtLocation(name)!)));
|
||||
}
|
||||
else {
|
||||
append(copiedOldImports, filterImport(i, factory.createStringLiteral(moduleSpecifierFromImport(i).text), name => importsToCopy.has(checker.getSymbolAtLocation(name)!)));
|
||||
}
|
||||
});
|
||||
}
|
||||
changes.insertStatementsInNewFile(targetFile.fileName, importAdderForNewFile.hasFixes() ? [SyntaxKind.NewLineTrivia, ...body] : body, oldFile);
|
||||
}
|
||||
|
||||
// Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file.
|
||||
const targetFileSourceFile = program.getSourceFile(targetFile);
|
||||
let oldFileDefault: Identifier | undefined;
|
||||
const oldFileNamedImports: string[] = [];
|
||||
const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`.
|
||||
targetFileImportsFromOldFile.forEach(symbol => {
|
||||
if (!symbol.declarations) {
|
||||
return;
|
||||
}
|
||||
for (const decl of symbol.declarations) {
|
||||
if (!isTopLevelDeclaration(decl)) continue;
|
||||
const name = nameOfTopLevelDeclaration(decl);
|
||||
if (!name) continue;
|
||||
|
||||
const top = getTopLevelDeclarationStatement(decl);
|
||||
if (markSeenTop(top)) {
|
||||
addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax);
|
||||
}
|
||||
if (importAdder && checker.isUnknownSymbol(symbol)) {
|
||||
importAdder.addImportFromExportedSymbol(skipAlias(symbol, checker));
|
||||
}
|
||||
else {
|
||||
if (hasSyntacticModifier(decl, ModifierFlags.Default)) {
|
||||
oldFileDefault = name;
|
||||
}
|
||||
else {
|
||||
oldFileNamedImports.push(name.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return targetFileSourceFile
|
||||
? append(copiedOldImports, makeImportOrRequire(targetFileSourceFile, oldFileDefault, oldFileNamedImports, oldFile.fileName, program, host, useEsModuleSyntax, quotePreference))
|
||||
: append(copiedOldImports, makeImportOrRequire(oldFile, oldFileDefault, oldFileNamedImports, oldFile.fileName, program, host, useEsModuleSyntax, quotePreference));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -401,13 +294,38 @@ export function deleteMovedStatements(sourceFile: SourceFile, moved: readonly St
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: Set<Symbol>, checker: TypeChecker) {
|
||||
export function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], toDelete: Set<Symbol>, importAdder: codefix.ImportAdder) {
|
||||
for (const statement of oldFile.statements) {
|
||||
if (contains(toMove, statement)) continue;
|
||||
forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!)));
|
||||
forEachImportInStatement(statement, i => {
|
||||
forEachAliasDeclarationInImportOrRequire(i, decl => {
|
||||
if (toDelete.has(decl.symbol)) {
|
||||
importAdder.removeExistingImport(decl);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addExportsInOldFile(oldFile: SourceFile, targetFileImportsFromOldFile: Map<Symbol, boolean>, changes: textChanges.ChangeTracker, useEsModuleSyntax: boolean) {
|
||||
const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`.
|
||||
targetFileImportsFromOldFile.forEach((_, symbol) => {
|
||||
if (!symbol.declarations) {
|
||||
return;
|
||||
}
|
||||
for (const decl of symbol.declarations) {
|
||||
if (!isTopLevelDeclaration(decl)) continue;
|
||||
const name = nameOfTopLevelDeclaration(decl);
|
||||
if (!name) continue;
|
||||
|
||||
const top = getTopLevelDeclarationStatement(decl);
|
||||
if (markSeenTop(top)) {
|
||||
addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function updateImportsInOtherFiles(
|
||||
changes: textChanges.ChangeTracker,
|
||||
@@ -544,6 +462,37 @@ export function forEachImportInStatement(statement: Statement, cb: (importNode:
|
||||
}
|
||||
}
|
||||
|
||||
function forEachAliasDeclarationInImportOrRequire(importOrRequire: SupportedImport, cb: (declaration: codefix.ImportOrRequireAliasDeclaration) => void): void {
|
||||
if (importOrRequire.kind === SyntaxKind.ImportDeclaration) {
|
||||
if (importOrRequire.importClause?.name) {
|
||||
cb(importOrRequire.importClause);
|
||||
}
|
||||
if (importOrRequire.importClause?.namedBindings?.kind === SyntaxKind.NamespaceImport) {
|
||||
cb(importOrRequire.importClause.namedBindings);
|
||||
}
|
||||
if (importOrRequire.importClause?.namedBindings?.kind === SyntaxKind.NamedImports) {
|
||||
for (const element of importOrRequire.importClause.namedBindings.elements) {
|
||||
cb(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (importOrRequire.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
cb(importOrRequire);
|
||||
}
|
||||
else if (importOrRequire.kind === SyntaxKind.VariableDeclaration) {
|
||||
if (importOrRequire.name.kind === SyntaxKind.Identifier) {
|
||||
cb(importOrRequire);
|
||||
}
|
||||
else if (importOrRequire.name.kind === SyntaxKind.ObjectBindingPattern) {
|
||||
for (const element of importOrRequire.name.elements) {
|
||||
if (isIdentifier(element.name)) {
|
||||
cb(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type SupportedImport =
|
||||
| ImportDeclaration & { moduleSpecifier: StringLiteralLike; }
|
||||
@@ -557,52 +506,16 @@ export type SupportedImportStatement =
|
||||
| VariableStatement;
|
||||
|
||||
/** @internal */
|
||||
export function createOldFileImportsFromTargetFile(
|
||||
sourceFile: SourceFile,
|
||||
targetFileNeedExport: Set<Symbol>,
|
||||
targetFileNameWithExtension: string,
|
||||
export function addImportsForMovedSymbols(
|
||||
symbols: Map<Symbol, boolean>,
|
||||
targetFileName: string,
|
||||
importAdder: codefix.ImportAdder,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
useEs6Imports: boolean,
|
||||
quotePreference: QuotePreference,
|
||||
): AnyImportOrRequireStatement | undefined {
|
||||
let defaultImport: Identifier | undefined;
|
||||
const imports: string[] = [];
|
||||
targetFileNeedExport.forEach(symbol => {
|
||||
if (symbol.escapedName === InternalSymbolName.Default) {
|
||||
defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!);
|
||||
}
|
||||
else {
|
||||
imports.push(symbol.name);
|
||||
}
|
||||
});
|
||||
return makeImportOrRequire(sourceFile, defaultImport, imports, targetFileNameWithExtension, program, host, useEs6Imports, quotePreference);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function makeImportOrRequire(
|
||||
sourceFile: SourceFile,
|
||||
defaultImport: Identifier | undefined,
|
||||
imports: readonly string[],
|
||||
targetFileNameWithExtension: string,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
useEs6Imports: boolean,
|
||||
quotePreference: QuotePreference,
|
||||
): AnyImportOrRequireStatement | undefined {
|
||||
const pathToTargetFile = resolvePath(getDirectoryPath(getNormalizedAbsolutePath(sourceFile.fileName, program.getCurrentDirectory())), targetFileNameWithExtension);
|
||||
const pathToTargetFileWithCorrectExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.fileName, pathToTargetFile, createModuleSpecifierResolutionHost(program, host));
|
||||
|
||||
if (useEs6Imports) {
|
||||
const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i)));
|
||||
return makeImportIfNecessary(defaultImport, specifiers, pathToTargetFileWithCorrectExtension, quotePreference);
|
||||
}
|
||||
else {
|
||||
Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module.
|
||||
const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i));
|
||||
return bindingElements.length
|
||||
? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(makeStringLiteral(pathToTargetFileWithCorrectExtension, quotePreference))) as RequireVariableStatement
|
||||
: undefined;
|
||||
) {
|
||||
for (const [symbol, isValidTypeOnlyUseSite] of symbols) {
|
||||
const symbolName = getNameForExportedSymbol(symbol, getEmitScriptTarget(program.getCompilerOptions()));
|
||||
const exportKind = symbol.name === "default" && symbol.parent ? ExportKind.Default : ExportKind.Named;
|
||||
importAdder.addImportForNonExistentExport(symbolName, targetFileName, exportKind, symbol.flags, isValidTypeOnlyUseSite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,13 +523,12 @@ function makeVariableStatement(name: BindingName, type: TypeNode | undefined, in
|
||||
return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: Set<Symbol>, useEs6Exports: boolean): readonly Statement[] {
|
||||
function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: Symbol[], useEs6Exports: boolean): readonly Statement[] {
|
||||
return flatMap(toMove, statement => {
|
||||
if (
|
||||
isTopLevelDeclarationStatement(statement) &&
|
||||
!isExported(sourceFile, statement, useEs6Exports) &&
|
||||
forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(tryCast(d, canHaveSymbol)?.symbol)))
|
||||
forEachTopLevelDeclaration(statement, d => needExport.includes(Debug.checkDefined(tryCast(d, canHaveSymbol)?.symbol)))
|
||||
) {
|
||||
const exports = addExport(getSynthesizedDeepClone(statement), useEs6Exports);
|
||||
if (exports) return exports;
|
||||
@@ -635,81 +547,18 @@ function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement,
|
||||
|
||||
/** @internal */
|
||||
export function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void {
|
||||
switch (importDecl.kind) {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused);
|
||||
break;
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
if (isUnused(importDecl.name)) {
|
||||
changes.delete(sourceFile, importDecl);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused);
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void {
|
||||
if (!importDecl.importClause) return;
|
||||
const { name, namedBindings } = importDecl.importClause;
|
||||
const defaultUnused = !name || isUnused(name);
|
||||
const namedBindingsUnused = !namedBindings ||
|
||||
(namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name)));
|
||||
if (defaultUnused && namedBindingsUnused) {
|
||||
changes.delete(sourceFile, importDecl);
|
||||
}
|
||||
else {
|
||||
if (name && defaultUnused) {
|
||||
changes.delete(sourceFile, name);
|
||||
}
|
||||
if (namedBindings) {
|
||||
if (namedBindingsUnused) {
|
||||
changes.replaceNode(
|
||||
sourceFile,
|
||||
importDecl.importClause,
|
||||
factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined),
|
||||
);
|
||||
}
|
||||
else if (namedBindings.kind === SyntaxKind.NamedImports) {
|
||||
for (const element of namedBindings.elements) {
|
||||
if (isUnused(element.name)) changes.delete(sourceFile, element);
|
||||
}
|
||||
}
|
||||
if (importDecl.kind === SyntaxKind.ImportDeclaration && importDecl.importClause) {
|
||||
const { name, namedBindings } = importDecl.importClause;
|
||||
if ((!name || isUnused(name)) && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name)))) {
|
||||
return changes.delete(sourceFile, importDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) {
|
||||
const { name } = varDecl;
|
||||
switch (name.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
if (isUnused(name)) {
|
||||
if (varDecl.initializer && isRequireCall(varDecl.initializer, /*requireStringLiteralLikeArgument*/ true)) {
|
||||
changes.delete(sourceFile, isVariableDeclarationList(varDecl.parent) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl);
|
||||
}
|
||||
else {
|
||||
changes.delete(sourceFile, name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
break;
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) {
|
||||
changes.delete(sourceFile, isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl);
|
||||
}
|
||||
else {
|
||||
for (const element of name.elements) {
|
||||
if (isIdentifier(element.name) && isUnused(element.name)) {
|
||||
changes.delete(sourceFile, element.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
forEachAliasDeclarationInImportOrRequire(importDecl, i => {
|
||||
if (i.name && isIdentifier(i.name) && isUnused(i.name)) {
|
||||
changes.delete(sourceFile, i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -879,16 +728,16 @@ export interface StatementRange {
|
||||
|
||||
/** @internal */
|
||||
export interface UsageInfo {
|
||||
// Symbols whose declarations are moved from the old file to the new file.
|
||||
/** Symbols whose declarations are moved from the old file to the new file. */
|
||||
readonly movedSymbols: Set<Symbol>;
|
||||
|
||||
// Symbols declared in the old file that must be imported by the new file. (May not already be exported.)
|
||||
readonly targetFileImportsFromOldFile: Set<Symbol>;
|
||||
// Subset of movedSymbols that are still used elsewhere in the old file and must be imported back.
|
||||
readonly oldFileImportsFromTargetFile: Set<Symbol>;
|
||||
/** Symbols declared in the old file that must be imported by the new file. (May not already be exported.) */
|
||||
readonly targetFileImportsFromOldFile: Map<Symbol, boolean>;
|
||||
/** Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. */
|
||||
readonly oldFileImportsFromTargetFile: Map<Symbol, boolean>;
|
||||
|
||||
readonly oldImportsNeededByTargetFile: Map<Symbol, boolean>;
|
||||
// Subset of oldImportsNeededByTargetFile that are will no longer be used in the old file.
|
||||
readonly oldImportsNeededByTargetFile: Map<Symbol, [boolean, codefix.ImportOrRequireAliasDeclaration | undefined]>;
|
||||
/** Subset of oldImportsNeededByTargetFile that are will no longer be used in the old file. */
|
||||
readonly unusedImportsFromOldFile: Set<Symbol>;
|
||||
}
|
||||
|
||||
@@ -1022,13 +871,13 @@ function isPureImport(node: Node): boolean {
|
||||
/** @internal */
|
||||
export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker, existingTargetLocals: ReadonlySet<Symbol> = new Set()): UsageInfo {
|
||||
const movedSymbols = new Set<Symbol>();
|
||||
const oldImportsNeededByTargetFile = new Map<Symbol, /*isValidTypeOnlyUseSite*/ boolean>();
|
||||
const targetFileImportsFromOldFile = new Set<Symbol>();
|
||||
const oldImportsNeededByTargetFile = new Map<Symbol, [/*isValidTypeOnlyUseSite*/ boolean, codefix.ImportOrRequireAliasDeclaration | undefined]>();
|
||||
const targetFileImportsFromOldFile = new Map<Symbol, /*isValidTypeOnlyUseSite*/ boolean>();
|
||||
|
||||
const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx(toMove));
|
||||
|
||||
if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code)
|
||||
oldImportsNeededByTargetFile.set(jsxNamespaceSymbol, false);
|
||||
oldImportsNeededByTargetFile.set(jsxNamespaceSymbol, [false, tryCast(jsxNamespaceSymbol.declarations?.[0], (d): d is codefix.ImportOrRequireAliasDeclaration => isImportSpecifier(d) || isImportClause(d) || isNamespaceImport(d) || isImportEqualsDeclaration(d) || isBindingElement(d) || isVariableDeclaration(d))]);
|
||||
}
|
||||
|
||||
for (const statement of toMove) {
|
||||
@@ -1050,10 +899,13 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[],
|
||||
for (const decl of symbol.declarations) {
|
||||
if (isInImport(decl)) {
|
||||
const prevIsTypeOnly = oldImportsNeededByTargetFile.get(symbol);
|
||||
oldImportsNeededByTargetFile.set(symbol, prevIsTypeOnly === undefined ? isValidTypeOnlyUseSite : prevIsTypeOnly && isValidTypeOnlyUseSite);
|
||||
oldImportsNeededByTargetFile.set(symbol, [
|
||||
prevIsTypeOnly === undefined ? isValidTypeOnlyUseSite : prevIsTypeOnly && isValidTypeOnlyUseSite,
|
||||
tryCast(decl, (d): d is codefix.ImportOrRequireAliasDeclaration => isImportSpecifier(d) || isImportClause(d) || isNamespaceImport(d) || isImportEqualsDeclaration(d) || isBindingElement(d) || isVariableDeclaration(d)),
|
||||
]);
|
||||
}
|
||||
else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) {
|
||||
targetFileImportsFromOldFile.add(symbol);
|
||||
targetFileImportsFromOldFile.set(symbol, isValidTypeOnlyUseSite);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1063,7 +915,7 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[],
|
||||
unusedImportsFromOldFile.add(unusedImport);
|
||||
}
|
||||
|
||||
const oldFileImportsFromTargetFile = new Set<Symbol>();
|
||||
const oldFileImportsFromTargetFile = new Map<Symbol, boolean>();
|
||||
for (const statement of oldFile.statements) {
|
||||
if (contains(toMove, statement)) continue;
|
||||
|
||||
@@ -1072,8 +924,8 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[],
|
||||
unusedImportsFromOldFile.delete(jsxNamespaceSymbol);
|
||||
}
|
||||
|
||||
forEachReference(statement, checker, symbol => {
|
||||
if (movedSymbols.has(symbol)) oldFileImportsFromTargetFile.add(symbol);
|
||||
forEachReference(statement, checker, (symbol, isValidTypeOnlyUseSite) => {
|
||||
if (movedSymbols.has(symbol)) oldFileImportsFromTargetFile.set(symbol, isValidTypeOnlyUseSite);
|
||||
unusedImportsFromOldFile.delete(symbol);
|
||||
});
|
||||
}
|
||||
@@ -1107,7 +959,7 @@ function makeUniqueFilename(proposedFilename: string, extension: string, inDirec
|
||||
}
|
||||
}
|
||||
|
||||
function inferNewFileName(importsFromNewFile: Set<Symbol>, movedSymbols: Set<Symbol>): string {
|
||||
function inferNewFileName(importsFromNewFile: Map<Symbol, unknown>, movedSymbols: Set<Symbol>): string {
|
||||
return forEachKey(importsFromNewFile, symbolNameNoDefault) || forEachKey(movedSymbols, symbolNameNoDefault) || "newFile";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,56 +1,31 @@
|
||||
import {
|
||||
append,
|
||||
ApplicableRefactorInfo,
|
||||
codefix,
|
||||
createFutureSourceFile,
|
||||
Debug,
|
||||
Diagnostics,
|
||||
emptyArray,
|
||||
fileShouldUseJavaScriptRequire,
|
||||
getBaseFileName,
|
||||
getLineAndCharacterOfPosition,
|
||||
getLocaleSpecificMessage,
|
||||
getQuotePreference,
|
||||
hasSyntacticModifier,
|
||||
hostGetCanonicalFileName,
|
||||
Identifier,
|
||||
insertImports,
|
||||
isPrologueDirective,
|
||||
LanguageServiceHost,
|
||||
last,
|
||||
ModifierFlags,
|
||||
nodeSeenTracker,
|
||||
ModuleKind,
|
||||
Program,
|
||||
QuotePreference,
|
||||
RefactorContext,
|
||||
RefactorEditInfo,
|
||||
SourceFile,
|
||||
Symbol,
|
||||
SyntaxKind,
|
||||
takeWhile,
|
||||
textChanges,
|
||||
TypeChecker,
|
||||
UserPreferences,
|
||||
} from "../_namespaces/ts";
|
||||
import {
|
||||
addExports,
|
||||
addExportToChanges,
|
||||
addNewFileToTsconfig,
|
||||
createNewFileName,
|
||||
createOldFileImportsFromTargetFile,
|
||||
deleteMovedStatements,
|
||||
deleteUnusedOldImports,
|
||||
filterImport,
|
||||
forEachImportInStatement,
|
||||
getNewStatementsAndRemoveFromOldFile,
|
||||
getStatementsToMove,
|
||||
getTopLevelDeclarationStatement,
|
||||
getUsageInfo,
|
||||
isTopLevelDeclaration,
|
||||
makeImportOrRequire,
|
||||
moduleSpecifierFromImport,
|
||||
nameOfTopLevelDeclaration,
|
||||
registerRefactor,
|
||||
SupportedImportStatement,
|
||||
ToMove,
|
||||
updateImportsInOtherFiles,
|
||||
UsageInfo,
|
||||
} from "../_namespaces/ts.refactor";
|
||||
|
||||
const refactorName = "Move to a new file";
|
||||
@@ -81,112 +56,19 @@ registerRefactor(refactorName, {
|
||||
getEditsForAction: function getRefactorEditsToMoveToNewFile(context, actionName): RefactorEditInfo {
|
||||
Debug.assert(actionName === refactorName, "Wrong refactor invoked");
|
||||
const statements = Debug.checkDefined(getStatementsToMove(context));
|
||||
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences));
|
||||
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context, context.preferences));
|
||||
return { edits, renameFilename: undefined, renameLocation: undefined };
|
||||
},
|
||||
});
|
||||
|
||||
function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void {
|
||||
function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, context: RefactorContext, preferences: UserPreferences): void {
|
||||
const checker = program.getTypeChecker();
|
||||
const usage = getUsageInfo(oldFile, toMove.all, checker);
|
||||
const newFilename = createNewFileName(oldFile, program, host, toMove);
|
||||
const newSourceFile = createFutureSourceFile(newFilename, oldFile.externalModuleIndicator ? ModuleKind.ESNext : oldFile.commonJsModuleIndicator ? ModuleKind.CommonJS : undefined, program, host);
|
||||
|
||||
// If previous file was global, this is easy.
|
||||
changes.createNewFile(oldFile, newFilename, getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFilename, preferences));
|
||||
|
||||
const importAdderForOldFile = codefix.createImportAdder(oldFile, context.program, context.preferences, context.host);
|
||||
const importAdderForNewFile = codefix.createImportAdder(newSourceFile, context.program, context.preferences, context.host);
|
||||
getNewStatementsAndRemoveFromOldFile(oldFile, newSourceFile, usage, changes, toMove, program, host, preferences, importAdderForNewFile, importAdderForOldFile);
|
||||
addNewFileToTsconfig(program, changes, oldFile.fileName, newFilename, hostGetCanonicalFileName(host));
|
||||
}
|
||||
|
||||
function getNewStatementsAndRemoveFromOldFile(
|
||||
oldFile: SourceFile,
|
||||
usage: UsageInfo,
|
||||
changes: textChanges.ChangeTracker,
|
||||
toMove: ToMove,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
newFilename: string,
|
||||
preferences: UserPreferences,
|
||||
) {
|
||||
const checker = program.getTypeChecker();
|
||||
const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective);
|
||||
if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByTargetFile.size === 0) {
|
||||
deleteMovedStatements(oldFile, toMove.ranges, changes);
|
||||
return [...prologueDirectives, ...toMove.all];
|
||||
}
|
||||
|
||||
const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(newFilename, program, host, !!oldFile.commonJsModuleIndicator);
|
||||
const quotePreference = getQuotePreference(oldFile, preferences);
|
||||
const importsFromNewFile = createOldFileImportsFromTargetFile(oldFile, usage.oldFileImportsFromTargetFile, newFilename, program, host, useEsModuleSyntax, quotePreference);
|
||||
if (importsFromNewFile) {
|
||||
insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true, preferences);
|
||||
}
|
||||
|
||||
deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker);
|
||||
deleteMovedStatements(oldFile, toMove.ranges, changes);
|
||||
updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, newFilename, quotePreference);
|
||||
|
||||
const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference);
|
||||
const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromTargetFile, useEsModuleSyntax);
|
||||
if (imports.length && body.length) {
|
||||
return [
|
||||
...prologueDirectives,
|
||||
...imports,
|
||||
SyntaxKind.NewLineTrivia as const,
|
||||
...body,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...prologueDirectives,
|
||||
...imports,
|
||||
...body,
|
||||
];
|
||||
}
|
||||
|
||||
function getNewFileImportsAndAddExportInOldFile(
|
||||
oldFile: SourceFile,
|
||||
importsToCopy: Map<Symbol, boolean>,
|
||||
newFileImportsFromOldFile: Set<Symbol>,
|
||||
changes: textChanges.ChangeTracker,
|
||||
checker: TypeChecker,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
useEsModuleSyntax: boolean,
|
||||
quotePreference: QuotePreference,
|
||||
): readonly SupportedImportStatement[] {
|
||||
const copiedOldImports: SupportedImportStatement[] = [];
|
||||
for (const oldStatement of oldFile.statements) {
|
||||
forEachImportInStatement(oldStatement, i => {
|
||||
append(copiedOldImports, filterImport(i, moduleSpecifierFromImport(i), name => importsToCopy.has(checker.getSymbolAtLocation(name)!)));
|
||||
});
|
||||
}
|
||||
|
||||
// Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file.
|
||||
let oldFileDefault: Identifier | undefined;
|
||||
const oldFileNamedImports: string[] = [];
|
||||
const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`.
|
||||
newFileImportsFromOldFile.forEach(symbol => {
|
||||
if (!symbol.declarations) {
|
||||
return;
|
||||
}
|
||||
for (const decl of symbol.declarations) {
|
||||
if (!isTopLevelDeclaration(decl)) continue;
|
||||
const name = nameOfTopLevelDeclaration(decl);
|
||||
if (!name) continue;
|
||||
|
||||
const top = getTopLevelDeclarationStatement(decl);
|
||||
if (markSeenTop(top)) {
|
||||
addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax);
|
||||
}
|
||||
if (hasSyntacticModifier(decl, ModifierFlags.Default)) {
|
||||
oldFileDefault = name;
|
||||
}
|
||||
else {
|
||||
oldFileNamedImports.push(name.text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
append(copiedOldImports, makeImportOrRequire(oldFile, oldFileDefault, oldFileNamedImports, getBaseFileName(oldFile.fileName), program, host, useEsModuleSyntax, quotePreference));
|
||||
return copiedOldImports;
|
||||
}
|
||||
|
||||
@@ -2507,11 +2507,6 @@ export function moduleResolutionUsesNodeModules(moduleResolution: ModuleResoluti
|
||||
|| moduleResolution === ModuleResolutionKind.Bundler;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined {
|
||||
return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration {
|
||||
return factory.createImportDeclaration(
|
||||
|
||||
@@ -241,13 +241,13 @@ Info seq [hh:mm:ss:mss] response:
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"name": "Move to file",
|
||||
"description": "Move to file",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"kind": "refactor.move.newFile",
|
||||
"name": "Move to file",
|
||||
"description": "Move to file",
|
||||
"kind": "refactor.move.file",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
@@ -262,13 +262,13 @@ Info seq [hh:mm:ss:mss] response:
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Move to file",
|
||||
"description": "Move to file",
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move to file",
|
||||
"description": "Move to file",
|
||||
"kind": "refactor.move.file",
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"kind": "refactor.move.newFile",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
|
||||
@@ -106,27 +106,6 @@ Info seq [hh:mm:ss:mss] request:
|
||||
Info seq [hh:mm:ss:mss] response:
|
||||
{
|
||||
"response": [
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"kind": "refactor.move.newFile",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"offset": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"offset": 13
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Move to file",
|
||||
"description": "Move to file",
|
||||
@@ -147,6 +126,27 @@ Info seq [hh:mm:ss:mss] response:
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Move to a new file",
|
||||
"description": "Move to a new file",
|
||||
"kind": "refactor.move.newFile",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"offset": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"offset": 13
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"responseRequired": true
|
||||
|
||||
@@ -20,8 +20,6 @@ verify.moveToFile({
|
||||
"/bar.ts":
|
||||
`//
|
||||
import { p } from './a';
|
||||
|
||||
|
||||
import { b } from './other';
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ verify.moveToFile({
|
||||
|
||||
"/bar.ts":
|
||||
`import { a } from './a';
|
||||
|
||||
import { b } from './other';
|
||||
|
||||
export const tt = 2;
|
||||
|
||||
@@ -24,7 +24,6 @@ y;`,
|
||||
|
||||
"/src/dir2/bar.ts":
|
||||
`import { a } from '../dir1/a';
|
||||
|
||||
import { b } from '../dir1/other';
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const x: T={
|
||||
`,
|
||||
|
||||
"/bar.ts":
|
||||
`import { x } from "./a";
|
||||
`import { x } from './a';
|
||||
import { b } from './other';
|
||||
const t = b;
|
||||
const b = x.a;
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
////
|
||||
////export const p = 0;
|
||||
////export const b = 1;
|
||||
////[|const y = p + b;|]
|
||||
////[|export const y = p + b;
|
||||
////export const z = 1;|]
|
||||
|
||||
// @Filename: /other.ts
|
||||
////import { z, y } from './a';
|
||||
////export const t = 2;
|
||||
////const u = z + t + y;
|
||||
|
||||
|
||||
verify.moveToFile({
|
||||
@@ -34,8 +37,14 @@ export const b = 1;
|
||||
`import { p, b } from './a';
|
||||
|
||||
const q = 0;
|
||||
const y = p + b;
|
||||
export const y = p + b;
|
||||
export const z = 1;
|
||||
`,
|
||||
|
||||
"/other.ts":
|
||||
`import { z, y } from './bar';
|
||||
export const t = 2;
|
||||
const u = z + t + y;`
|
||||
},
|
||||
interactiveRefactorArguments: { targetFile: "/bar.ts" },
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ verify.moveToFile({
|
||||
``,
|
||||
|
||||
"/bar.ts":
|
||||
`import { a } from "doesnt-exist";
|
||||
`import { a } from 'doesnt-exist';
|
||||
|
||||
const a = 1;
|
||||
a();
|
||||
|
||||
@@ -21,8 +21,8 @@ export const p = 0;
|
||||
a; y;`,
|
||||
|
||||
"/y.ts":
|
||||
`import { b } from './other';
|
||||
import { p } from './a';
|
||||
`import { p } from './a';
|
||||
import { b } from './other';
|
||||
|
||||
export const y: Date = p + b;
|
||||
`,
|
||||
|
||||
@@ -18,11 +18,13 @@ verify.noErrors();
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`const decorator1: any = () => {};
|
||||
const decorator2: any = () => {};
|
||||
`export const decorator1: any = () => {};
|
||||
export const decorator2: any = () => {};
|
||||
`,
|
||||
"/Foo.ts":
|
||||
`class Foo {
|
||||
`import { decorator1, decorator2 } from "./a";
|
||||
|
||||
class Foo {
|
||||
constructor(@decorator1 private readonly x: number,
|
||||
@decorator1 @decorator2 private readonly y: number) { }
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ verify.noErrors();
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`const decorator1: any = () => {};
|
||||
const decorator2: any = () => {};
|
||||
`export const decorator1: any = () => {};
|
||||
export const decorator2: any = () => {};
|
||||
`,
|
||||
"/Foo.ts":
|
||||
`class Foo {
|
||||
`import { decorator1, decorator2 } from "./a";
|
||||
|
||||
class Foo {
|
||||
@decorator1 method1() { }
|
||||
@decorator1 @decorator2 method2() { }
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ verify.noErrors();
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`const decorator1: any = () => {};
|
||||
`export const decorator1: any = () => {};
|
||||
`,
|
||||
"/Foo.ts":
|
||||
`@decorator1 class Foo {
|
||||
`import { decorator1 } from "./a";
|
||||
|
||||
@decorator1 class Foo {
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ verify.noErrors();
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`const decorator1: any = () => {};
|
||||
const decorator2: any = () => {};
|
||||
`export const decorator1: any = () => {};
|
||||
export const decorator2: any = () => {};
|
||||
`,
|
||||
"/Foo.ts":
|
||||
`@decorator1 @decorator2 class Foo {
|
||||
`import { decorator1, decorator2 } from "./a";
|
||||
|
||||
@decorator1 @decorator2 class Foo {
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -7,11 +7,15 @@
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`const x = y;
|
||||
`import { y } from "./y";
|
||||
|
||||
export const x = y;
|
||||
`,
|
||||
|
||||
"/y.ts":
|
||||
`const y = x;
|
||||
`import { x } from "./a";
|
||||
|
||||
export const y = x;
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.js":
|
||||
`const { a, } = require("./other");
|
||||
`const { a } = require("./other");
|
||||
const { y, z } = require("./y");
|
||||
const p = 0;
|
||||
exports.p = p;
|
||||
a; y; z;`,
|
||||
|
||||
"/y.js":
|
||||
`const { b } = require("./other");
|
||||
const { p } = require("./a");
|
||||
`const { p } = require("./a");
|
||||
const { b } = require("./other");
|
||||
|
||||
const y = p + b;
|
||||
exports.y = y;
|
||||
|
||||
@@ -11,11 +11,14 @@ verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`"use strict";
|
||||
|
||||
import { b } from "./b";
|
||||
|
||||
b();`,
|
||||
|
||||
"/b.ts":
|
||||
`"use strict";
|
||||
function b() {
|
||||
export function b() {
|
||||
return this;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -12,12 +12,15 @@ verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.ts":
|
||||
`"use strict";
|
||||
|
||||
import { b } from "./b";
|
||||
|
||||
"prologue directive like statement";
|
||||
b();`,
|
||||
|
||||
"/b.ts":
|
||||
`"use strict";
|
||||
function b() {
|
||||
export function b() {
|
||||
return this;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -18,7 +18,7 @@ verify.moveToNewFile({
|
||||
"/b.js": "",
|
||||
|
||||
"/f.js":
|
||||
`var a = require("./a");
|
||||
`const a = require("./a");
|
||||
|
||||
function f() {
|
||||
a;
|
||||
|
||||
@@ -23,7 +23,7 @@ verify.moveToNewFile({
|
||||
`,
|
||||
|
||||
"/f.js":
|
||||
`var b = require("./a");
|
||||
`const b = require("./a");
|
||||
|
||||
function f() {
|
||||
b;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
// @moduleResolution: node
|
||||
|
||||
// @filename: /node.d.ts
|
||||
////declare var module: any;
|
||||
////declare var require: any;
|
||||
|
||||
// @filename: /a.ts
|
||||
////module.exports = 1;
|
||||
|
||||
// @filename: /b.ts
|
||||
////var a = require("./a");
|
||||
////[|function f() {
|
||||
//// a;
|
||||
////}|]
|
||||
|
||||
verify.noErrors();
|
||||
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/b.ts": "",
|
||||
|
||||
"/f.ts":
|
||||
`var a = require("./a");
|
||||
|
||||
function f() {
|
||||
a;
|
||||
}
|
||||
`,
|
||||
},
|
||||
});
|
||||
@@ -13,7 +13,7 @@ verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/b.ts": "",
|
||||
"/f.ts":
|
||||
`import { type A } from "./a";
|
||||
`import type { A } from "./a";
|
||||
|
||||
function f(a: A) { }
|
||||
`,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
// @Filename: /user.ts
|
||||
////import { x, y } from "./a";
|
||||
////import { x as x2 } from "./a";
|
||||
////import { y as y2 } from "./a";
|
||||
////import {} from "./a";
|
||||
|
||||
verify.moveToNewFile({
|
||||
@@ -24,7 +23,6 @@ verify.moveToNewFile({
|
||||
`import { x } from "./a";
|
||||
import { y } from "./y";
|
||||
import { x as x2 } from "./a";
|
||||
import { y as y2 } from "./y";
|
||||
import {} from "./a";`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ verify.moveToNewFile({
|
||||
|
||||
"/user.js":
|
||||
// TODO: GH#22330
|
||||
`const { x, } = require("./a");
|
||||
`const { x } = require("./a");
|
||||
const { y } = require("./y");
|
||||
`,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user