fix(54310): "Move to file" does not eliminate re-export (#54329)

This commit is contained in:
Oleksandr T
2023-07-07 20:51:07 +03:00
committed by GitHub
parent 22a9ae9eed
commit b1da6eead3
7 changed files with 208 additions and 1 deletions

View File

@@ -1,8 +1,10 @@
import { getModuleSpecifier } from "../../compiler/moduleSpecifiers";
import {
__String,
AnyImportOrRequireStatement,
append,
ApplicableRefactorInfo,
arrayFrom,
AssignmentDeclarationKind,
BinaryExpression,
BindingElement,
@@ -26,15 +28,18 @@ import {
emptyArray,
EnumDeclaration,
escapeLeadingUnderscores,
ExportDeclaration,
Expression,
ExpressionStatement,
extensionFromPath,
ExternalModuleReference,
factory,
fileShouldUseJavaScriptRequire,
filter,
find,
FindAllReferences,
findIndex,
findLast,
findLastIndex,
firstDefined,
flatMap,
@@ -69,6 +74,8 @@ import {
isBinaryExpression,
isBindingElement,
isDeclarationName,
isExportDeclaration,
isExportSpecifier,
isExpressionStatement,
isExternalModuleReference,
isFunctionLikeDeclaration,
@@ -76,6 +83,7 @@ import {
isImportDeclaration,
isImportEqualsDeclaration,
isNamedDeclaration,
isNamedExports,
isObjectLiteralExpression,
isOmittedExpression,
isPrologueDirective,
@@ -236,7 +244,7 @@ function getNewStatementsAndRemoveFromOldFile(
const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromTargetFile, useEsModuleSyntax);
if (typeof targetFile !== "string") {
if (targetFile.statements.length > 0) {
changes.insertNodesAfter(targetFile, targetFile.statements[targetFile.statements.length - 1], body);
moveStatementsToTargetFile(changes, program, body, targetFile, toMove);
}
else {
changes.insertNodesAtEndOfFile(targetFile, body, /*blankLineBetween*/ false);
@@ -1137,6 +1145,59 @@ function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLev
}
}
function moveStatementsToTargetFile(changes: textChanges.ChangeTracker, program: Program, statements: readonly Statement[], targetFile: SourceFile, toMove: ToMove) {
const removedExports = new Set<ExportDeclaration>();
const targetExports = targetFile.symbol?.exports;
if (targetExports) {
const checker = program.getTypeChecker();
const targetToSourceExports = new Map<ExportDeclaration, Set<TopLevelDeclaration>>();
for (const node of toMove.all) {
if (isTopLevelDeclarationStatement(node) && hasSyntacticModifier(node, ModifierFlags.Export)) {
forEachTopLevelDeclaration(node, declaration => {
const targetDeclarations = canHaveSymbol(declaration) ? targetExports.get(declaration.symbol.escapedName)?.declarations : undefined;
const exportDeclaration = firstDefined(targetDeclarations, d =>
isExportDeclaration(d) ? d :
isExportSpecifier(d) ? tryCast(d.parent.parent, isExportDeclaration) : undefined);
if (exportDeclaration && exportDeclaration.moduleSpecifier) {
targetToSourceExports.set(exportDeclaration,
(targetToSourceExports.get(exportDeclaration) || new Set()).add(declaration));
}
});
}
}
for (const [exportDeclaration, topLevelDeclarations] of arrayFrom(targetToSourceExports)) {
if (exportDeclaration.exportClause && isNamedExports(exportDeclaration.exportClause) && length(exportDeclaration.exportClause.elements)) {
const elements = exportDeclaration.exportClause.elements;
const updatedElements = filter(elements, elem =>
find(skipAlias(elem.symbol, checker).declarations, d => isTopLevelDeclaration(d) && topLevelDeclarations.has(d)) === undefined);
if (length(updatedElements) === 0) {
changes.deleteNode(targetFile, exportDeclaration);
removedExports.add(exportDeclaration);
continue;
}
if (length(updatedElements) < length(elements)) {
changes.replaceNode(targetFile, exportDeclaration,
factory.updateExportDeclaration(exportDeclaration, exportDeclaration.modifiers, exportDeclaration.isTypeOnly,
factory.updateNamedExports(exportDeclaration.exportClause , factory.createNodeArray(updatedElements, elements.hasTrailingComma)), exportDeclaration.moduleSpecifier, exportDeclaration.assertClause));
}
}
}
}
const lastReExport = findLast(targetFile.statements, n =>
isExportDeclaration(n) && !!n.moduleSpecifier && !removedExports.has(n));
if (lastReExport) {
changes.insertNodesBefore(targetFile, lastReExport, statements, /*blankLineBetween*/ true);
}
else {
changes.insertNodesAfter(targetFile, targetFile.statements[targetFile.statements.length - 1], statements);
}
}
function getOverloadRangeToMove(sourceFile: SourceFile, statement: Statement) {
if (isFunctionLikeDeclaration(statement)) {
const declarations = statement.symbol.declarations;

View File

@@ -671,6 +671,10 @@ export class ChangeTracker {
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNode, this.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween));
}
public insertNodesBefore(sourceFile: SourceFile, before: Node, newNodes: readonly Node[], blankLineBetween = false, options: ConfigurableStartEnd = {}): void {
this.insertNodesAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNodes, this.getOptionsForInsertNodeBefore(before, first(newNodes), blankLineBetween));
}
public insertModifierAt(sourceFile: SourceFile, pos: number, modifier: SyntaxKind, options: InsertNodeOptions = {}): void {
this.insertNodeAt(sourceFile, pos, factory.createToken(modifier), options);
}