From 1219556758f7aa7ecabe64ea66b8674909dffe9f Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Fri, 11 Nov 2022 21:28:54 -0800 Subject: [PATCH 1/6] fixes #50725 --- src/compiler/moduleSpecifiers.ts | 6 +- src/services/refactors/moveToNewFile.ts | 1339 +++++++++-------- ...etEditsForFileRename_keepFileExtensions.ts | 23 + .../moveToNewFile_importFileExtensions.ts | 60 + 4 files changed, 756 insertions(+), 672 deletions(-) create mode 100644 tests/cases/fourslash/getEditsForFileRename_keepFileExtensions.ts create mode 100644 tests/cases/fourslash/moveToNewFile_importFileExtensions.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index b6ddd551cf9c6..0d0b974971539 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -771,9 +771,9 @@ function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileNam return undefined; } - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs - ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions) - : removeFileExtension(shortest); + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic + ? removeFileExtension(shortest) + : removeExtensionAndIndexPostFix(shortest, ending, compilerOptions); } function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined { diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 8963d7d7534bf..3c9f2c520ffb8 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -91,785 +91,786 @@ function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes // If previous file was global, this is easy. changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); - addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); -} - -interface StatementRange { - readonly first: Statement; - readonly afterLast: Statement | undefined; -} -interface ToMove { - readonly all: readonly Statement[]; - readonly ranges: readonly StatementRange[]; -} - -function getStatementsToMove(context: RefactorContext): ToMove | undefined { - const rangeToMove = getRangeToMove(context); - if (rangeToMove === undefined) return undefined; - const all: Statement[] = []; - const ranges: StatementRange[] = []; - const { toMove, afterLast } = rangeToMove; - getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { - for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); - ranges.push({ first: toMove[start], afterLast }); - }); - return all.length === 0 ? undefined : { all, ranges }; -} + addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); + } -function isAllowedStatementToMove(statement: Statement): boolean { - // Filters imports and prologue directives 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. - // Prologue directives will be copied to the new file and should be left in the old file. - return !isPureImport(statement) && !isPrologueDirective(statement); -} + interface StatementRange { + readonly first: Statement; + readonly afterLast: Statement | undefined; + } + interface ToMove { + readonly all: readonly Statement[]; + readonly ranges: readonly StatementRange[]; + } -function isPureImport(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return true; - case SyntaxKind.ImportEqualsDeclaration: - return !hasSyntacticModifier(node, ModifierFlags.Export); - case SyntaxKind.VariableStatement: - return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); - default: - return false; + function getStatementsToMove(context: RefactorContext): ToMove | undefined { + const rangeToMove = getRangeToMove(context); + if (rangeToMove === undefined) return undefined; + const all: Statement[] = []; + const ranges: StatementRange[] = []; + const { toMove, afterLast } = rangeToMove; + getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { + for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); + ranges.push({ first: toMove[start], afterLast }); + }); + return all.length === 0 ? undefined : { all, ranges }; + } + + function isAllowedStatementToMove(statement: Statement): boolean { + // Filters imports and prologue directives 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. + // Prologue directives will be copied to the new file and should be left in the old file. + return !isPureImport(statement) && !isPrologueDirective(statement); + } + + function isPureImport(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return true; + case SyntaxKind.ImportEqualsDeclaration: + return !hasSyntacticModifier(node, ModifierFlags.Export); + case SyntaxKind.VariableStatement: + return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); + default: + return false; + } } -} -function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { - const cfg = program.getCompilerOptions().configFile; - if (!cfg) return; + function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { + const cfg = program.getCompilerOptions().configFile; + if (!cfg) return; - const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); - const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); + const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); + const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); - const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); - const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => - isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); - if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { - changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); + const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); + const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => + isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); + if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { + changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); + } } -} -function getNewStatementsAndRemoveFromOldFile( - oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, -) { - const checker = program.getTypeChecker(); - const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); - if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { - deleteMovedStatements(oldFile, toMove.ranges, changes); - return [...prologueDirectives, ...toMove.all]; - } + function getNewStatementsAndRemoveFromOldFile( + oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, + ) { + const checker = program.getTypeChecker(); + const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); + if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { + deleteMovedStatements(oldFile, toMove.ranges, changes); + return [...prologueDirectives, ...toMove.all]; + } - const useEsModuleSyntax = !!oldFile.externalModuleIndicator; - const quotePreference = getQuotePreference(oldFile, preferences); - const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); - if (importsFromNewFile) { - insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); - } + const useEsModuleSyntax = !!oldFile.externalModuleIndicator; + const quotePreference = getQuotePreference(oldFile, preferences); + const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); + if (importsFromNewFile) { + insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); + } - deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); - deleteMovedStatements(oldFile, toMove.ranges, changes); - updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); + deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); + deleteMovedStatements(oldFile, toMove.ranges, changes); + updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); + + const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); + const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); + if (imports.length && body.length) { + return [ + ...prologueDirectives, + ...imports, + SyntaxKind.NewLineTrivia as const, + ...body + ]; + } - const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); - const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); - if (imports.length && body.length) { return [ ...prologueDirectives, ...imports, - SyntaxKind.NewLineTrivia as const, - ...body + ...body, ]; } - return [ - ...prologueDirectives, - ...imports, - ...body, - ]; -} - -function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { - for (const { first, afterLast } of moved) { - changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); + function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { + for (const { first, afterLast } of moved) { + changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); + } } -} -function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; - forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); + function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; + forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); + } } -} -function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { - const checker = program.getTypeChecker(); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile === oldFile) continue; - for (const statement of sourceFile.statements) { - forEachImportInStatement(statement, importNode => { - if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; - - const shouldMove = (name: Identifier): boolean => { - const symbol = isBindingElement(name.parent) - ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) - : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 - return !!symbol && movedSymbols.has(symbol); - }; - deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); - const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); - if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); - - const ns = getNamespaceLikeImport(importNode); - if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); - }); + function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { + const checker = program.getTypeChecker(); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile === oldFile) continue; + for (const statement of sourceFile.statements) { + forEachImportInStatement(statement, importNode => { + if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; + + const shouldMove = (name: Identifier): boolean => { + const symbol = isBindingElement(name.parent) + ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) + : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 + return !!symbol && movedSymbols.has(symbol); + }; + deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file + const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); + const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); + if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); + + const ns = getNamespaceLikeImport(importNode); + if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); + }); + } } } -} -function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? - node.importClause.namedBindings.name : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; - case SyntaxKind.VariableDeclaration: - return tryCast(node.name, isIdentifier); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? + node.importClause.namedBindings.name : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + case SyntaxKind.VariableDeclaration: + return tryCast(node.name, isIdentifier); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + } } -} -function updateNamespaceLikeImport( - changes: textChanges.ChangeTracker, - sourceFile: SourceFile, - checker: TypeChecker, - movedSymbols: ReadonlySymbolSet, - newModuleName: string, - newModuleSpecifier: string, - oldImportId: Identifier, - oldImportNode: SupportedImport, -): void { - const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); - let needUniqueName = false; - const toChange: Identifier[] = []; - FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { - if (!isPropertyAccessExpression(ref.parent)) return; - needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); - if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { - toChange.push(ref); - } - }); - - if (toChange.length) { - const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; - for (const ref of toChange) { - changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); - } - changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); - } -} + function updateNamespaceLikeImport( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + checker: TypeChecker, + movedSymbols: ReadonlySymbolSet, + newModuleName: string, + newModuleSpecifier: string, + oldImportId: Identifier, + oldImportNode: SupportedImport, + ): void { + const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); + let needUniqueName = false; + const toChange: Identifier[] = []; + FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { + if (!isPropertyAccessExpression(ref.parent)) return; + needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); + if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { + toChange.push(ref); + } + }); -function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { - const newNamespaceId = factory.createIdentifier(newNamespaceName); - const newModuleString = factory.createStringLiteral(newModuleSpecifier); - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), - newModuleString, - /*assertClause*/ undefined); - case SyntaxKind.ImportEqualsDeclaration: - return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); - case SyntaxKind.VariableDeclaration: - return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + if (toChange.length) { + const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; + for (const ref of toChange) { + changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); + } + changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); + } } -} -function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { - return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier - : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression - : i.initializer.arguments[0]); -} + function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { + const newNamespaceId = factory.createIdentifier(newNamespaceName); + const newModuleString = factory.createStringLiteral(newModuleSpecifier); + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), + newModuleString, + /*assertClause*/ undefined); + case SyntaxKind.ImportEqualsDeclaration: + return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); + case SyntaxKind.VariableDeclaration: + return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + } + } -function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { - if (isImportDeclaration(statement)) { - if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); + function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { + return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier + : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression + : i.initializer.arguments[0]); } - else if (isImportEqualsDeclaration(statement)) { - if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { - cb(statement as SupportedImport); + + function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { + if (isImportDeclaration(statement)) { + if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); } - } - else if (isVariableStatement(statement)) { - for (const decl of statement.declarationList.declarations) { - if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { - cb(decl as SupportedImport); + else if (isImportEqualsDeclaration(statement)) { + if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { + cb(statement as SupportedImport); + } + } + else if (isVariableStatement(statement)) { + for (const decl of statement.declarationList.declarations) { + if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { + cb(decl as SupportedImport); + } } } } -} -type SupportedImport = - | ImportDeclaration & { moduleSpecifier: StringLiteralLike } - | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } - | VariableDeclaration & { initializer: RequireOrImportCall }; -type SupportedImportStatement = - | ImportDeclaration - | ImportEqualsDeclaration - | VariableStatement; - -function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - let defaultImport: Identifier | undefined; - const imports: string[] = []; - newFileNeedExport.forEach(symbol => { - if (symbol.escapedName === InternalSymbolName.Default) { - defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 + type SupportedImport = + | ImportDeclaration & { moduleSpecifier: StringLiteralLike } + | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } + | VariableDeclaration & { initializer: RequireOrImportCall }; + type SupportedImportStatement = + | ImportDeclaration + | ImportEqualsDeclaration + | VariableStatement; + + function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + let defaultImport: Identifier | undefined; + const imports: string[] = []; + newFileNeedExport.forEach(symbol => { + if (symbol.escapedName === InternalSymbolName.Default) { + defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 + } + else { + imports.push(symbol.name); + } + }); + return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); + } + + function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + path = ensurePathIsNonModuleName(path); + if (useEs6Imports) { + const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); + return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); } else { - imports.push(symbol.name); + 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(factory.createStringLiteral(path))) as RequireVariableStatement + : undefined; } - }); - return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); -} - -function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - path = ensurePathIsNonModuleName(path); - if (useEs6Imports) { - const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, path, 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(factory.createStringLiteral(path))) as RequireVariableStatement - : undefined; + + function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { + return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); } -} -function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { - return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); -} + function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { + return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); + } -function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { - return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); -} + function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { + return flatMap(toMove, statement => { + if (isTopLevelDeclarationStatement(statement) && + !isExported(sourceFile, statement, useEs6Exports) && + forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { + const exports = addExport(statement, useEs6Exports); + if (exports) return exports; + } + return statement; + }); + } -function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { - return flatMap(toMove, statement => { - if (isTopLevelDeclarationStatement(statement) && - !isExported(sourceFile, statement, useEs6Exports) && - forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { - const exports = addExport(statement, useEs6Exports); - if (exports) return exports; + 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}`); } - return statement; - }); -} - -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) - ); + 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); } - else if (namedBindings.kind === SyntaxKind.NamedImports) { - for (const element of namedBindings.elements) { - if (isUnused(element.name)) changes.delete(sourceFile, element); + 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); + } } } } } -} -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)) { + 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) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl); + isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 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); + for (const element of name.elements) { + if (isIdentifier(element.name) && isUnused(element.name)) { + changes.delete(sourceFile, element.name); + } } } - } - break; - } -} - -function getNewFileImportsAndAddExportInOldFile( - oldFile: SourceFile, - importsToCopy: ReadonlySymbolSet, - newFileImportsFromOldFile: ReadonlySymbolSet, - changes: textChanges.ChangeTracker, - checker: TypeChecker, - 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)!))); - }); + break; + } } - // 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; + function getNewFileImportsAndAddExportInOldFile( + oldFile: SourceFile, + importsToCopy: ReadonlySymbolSet, + newFileImportsFromOldFile: ReadonlySymbolSet, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + 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)!))); + }); } - 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); + // 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; - append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); - return copiedOldImports; -} + 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); + } + } + }); -function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { - let newModuleName = moduleName; - for (let i = 1; ; i++) { - const name = combinePaths(inDirectory, newModuleName + extension); - if (!host.fileExists(name)) return newModuleName; - newModuleName = `${moduleName}.${i}`; + append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); + return copiedOldImports; } -} -function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { - return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; -} - -interface UsageInfo { - // Symbols whose declarations are moved from the old file to the new file. - readonly movedSymbols: ReadonlySymbolSet; + function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { + let newModuleName = moduleName; + for (let i = 1; ; i++) { + const name = combinePaths(inDirectory, newModuleName + extension); + if (!host.fileExists(name)) return newModuleName; + newModuleName = `${moduleName}.${i}`; + } + } - // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) - readonly newFileImportsFromOldFile: ReadonlySymbolSet; - // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. - readonly oldFileImportsFromNewFile: ReadonlySymbolSet; + function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { + return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; + } - readonly oldImportsNeededByNewFile: ReadonlySymbolSet; - // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. - readonly unusedImportsFromOldFile: ReadonlySymbolSet; -} -function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { - const movedSymbols = new SymbolSet(); - const oldImportsNeededByNewFile = new SymbolSet(); - const newFileImportsFromOldFile = new SymbolSet(); + interface UsageInfo { + // Symbols whose declarations are moved from the old file to the new file. + readonly movedSymbols: ReadonlySymbolSet; - const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); - const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); - if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) - oldImportsNeededByNewFile.add(jsxNamespaceSymbol); - } + // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) + readonly newFileImportsFromOldFile: ReadonlySymbolSet; + // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. + readonly oldFileImportsFromNewFile: ReadonlySymbolSet; - for (const statement of toMove) { - forEachTopLevelDeclaration(statement, decl => { - movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); - }); - } - for (const statement of toMove) { - forEachReference(statement, checker, symbol => { - if (!symbol.declarations) return; - for (const decl of symbol.declarations) { - if (isInImport(decl)) { - oldImportsNeededByNewFile.add(symbol); - } - else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { - newFileImportsFromOldFile.add(symbol); - } - } - }); + readonly oldImportsNeededByNewFile: ReadonlySymbolSet; + // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. + readonly unusedImportsFromOldFile: ReadonlySymbolSet; } + function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { + const movedSymbols = new SymbolSet(); + const oldImportsNeededByNewFile = new SymbolSet(); + const newFileImportsFromOldFile = new SymbolSet(); - const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); - - const oldFileImportsFromNewFile = new SymbolSet(); - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; + const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); + const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); + if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) + oldImportsNeededByNewFile.add(jsxNamespaceSymbol); + } - // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. - if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { - unusedImportsFromOldFile.delete(jsxNamespaceSymbol); + for (const statement of toMove) { + forEachTopLevelDeclaration(statement, decl => { + movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); + }); + } + for (const statement of toMove) { + forEachReference(statement, checker, symbol => { + if (!symbol.declarations) return; + for (const decl of symbol.declarations) { + if (isInImport(decl)) { + oldImportsNeededByNewFile.add(symbol); + } + else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { + newFileImportsFromOldFile.add(symbol); + } + } + }); } - forEachReference(statement, checker, symbol => { - if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); - unusedImportsFromOldFile.delete(symbol); - }); - } + const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); - return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + const oldFileImportsFromNewFile = new SymbolSet(); + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; + + // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. + if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { + unusedImportsFromOldFile.delete(jsxNamespaceSymbol); + } - function getJsxNamespaceSymbol(containsJsx: Node | undefined) { - if (containsJsx === undefined) { - return undefined; + forEachReference(statement, checker, symbol => { + if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); + unusedImportsFromOldFile.delete(symbol); + }); } - const jsxNamespace = checker.getJsxNamespace(containsJsx); + return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; - // Strictly speaking, this could resolve to a symbol other than the JSX namespace. - // This will produce erroneous output (probably, an incorrectly copied import) but - // is expected to be very rare and easily reversible. - const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); + function getJsxNamespaceSymbol(containsJsx: Node | undefined) { + if (containsJsx === undefined) { + return undefined; + } - return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) - ? jsxNamespaceSymbol - : undefined; - } -} + const jsxNamespace = checker.getJsxNamespace(containsJsx); -// Below should all be utilities - -function isInImport(decl: Declaration) { - switch (decl.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - return true; - case SyntaxKind.VariableDeclaration: - return isVariableDeclarationInImport(decl as VariableDeclaration); - case SyntaxKind.BindingElement: - return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); - default: - return false; - } -} -function isVariableDeclarationInImport(decl: VariableDeclaration) { - return isSourceFile(decl.parent.parent.parent) && - !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); -} + // Strictly speaking, this could resolve to a symbol other than the JSX namespace. + // This will produce erroneous output (probably, an incorrectly copied import) but + // is expected to be very rare and easily reversible. + const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); -function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { - switch (i.kind) { - case SyntaxKind.ImportDeclaration: { - const clause = i.importClause; - if (!clause) return undefined; - const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; - const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); - return defaultImport || namedBindings - ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) + return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) + ? jsxNamespaceSymbol : undefined; } - case SyntaxKind.ImportEqualsDeclaration: - return keep(i.name) ? i : undefined; - case SyntaxKind.VariableDeclaration: { - const name = filterBindingName(i.name, keep); - return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; - } - default: - return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); } -} -function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - return keep(namedBindings.name) ? namedBindings : undefined; - } - else { - const newElements = namedBindings.elements.filter(e => keep(e.name)); - return newElements.length ? factory.createNamedImports(newElements) : undefined; + + // Below should all be utilities + + function isInImport(decl: Declaration) { + switch (decl.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + return true; + case SyntaxKind.VariableDeclaration: + return isVariableDeclarationInImport(decl as VariableDeclaration); + case SyntaxKind.BindingElement: + return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); + default: + return false; + } } -} -function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return keep(name) ? name : undefined; - case SyntaxKind.ArrayBindingPattern: - return name; - case SyntaxKind.ObjectBindingPattern: { - // We can't handle nested destructurings or property names well here, so just copy them all. - const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); - return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; + function isVariableDeclarationInImport(decl: VariableDeclaration) { + return isSourceFile(decl.parent.parent.parent) && + !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); + } + + function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { + switch (i.kind) { + case SyntaxKind.ImportDeclaration: { + const clause = i.importClause; + if (!clause) return undefined; + const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; + const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); + return defaultImport || namedBindings + ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) + : undefined; + } + case SyntaxKind.ImportEqualsDeclaration: + return keep(i.name) ? i : undefined; + case SyntaxKind.VariableDeclaration: { + const name = filterBindingName(i.name, keep); + return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; + } + default: + return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); } } -} - -function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { - node.forEachChild(function cb(node) { - if (isIdentifier(node) && !isDeclarationName(node)) { - const sym = checker.getSymbolAtLocation(node); - if (sym) onReference(sym); + function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + return keep(namedBindings.name) ? namedBindings : undefined; } else { - node.forEachChild(cb); + const newElements = namedBindings.elements.filter(e => keep(e.name)); + return newElements.length ? factory.createNamedImports(newElements) : undefined; } - }); -} - -interface ReadonlySymbolSet { - size(): number; - has(symbol: Symbol): boolean; - forEach(cb: (symbol: Symbol) => void): void; - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; -} - -class SymbolSet implements ReadonlySymbolSet { - private map = new Map(); - add(symbol: Symbol): void { - this.map.set(String(getSymbolId(symbol)), symbol); } - has(symbol: Symbol): boolean { - return this.map.has(String(getSymbolId(symbol))); - } - delete(symbol: Symbol): void { - this.map.delete(String(getSymbolId(symbol))); - } - forEach(cb: (symbol: Symbol) => void): void { - this.map.forEach(cb); - } - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { - return forEachEntry(this.map, cb); - } - clone(): SymbolSet { - const clone = new SymbolSet(); - copyEntries(this.map, clone.map); - return clone; - } - size() { - return this.map.size; + function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return keep(name) ? name : undefined; + case SyntaxKind.ArrayBindingPattern: + return name; + case SyntaxKind.ObjectBindingPattern: { + // We can't handle nested destructurings or property names well here, so just copy them all. + const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); + return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; + } + } } -} -type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' -type NonVariableTopLevelDeclaration = - | FunctionDeclaration - | ClassDeclaration - | EnumDeclaration - | TypeAliasDeclaration - | InterfaceDeclaration - | ModuleDeclaration - | TopLevelExpressionStatement - | ImportEqualsDeclaration; -type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; -interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } -type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; -function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { - return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); -} + function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { + node.forEachChild(function cb(node) { + if (isIdentifier(node) && !isDeclarationName(node)) { + const sym = checker.getSymbolAtLocation(node); + if (sym) onReference(sym); + } + else { + node.forEachChild(cb); + } + }); + } -function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { - return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; -} + interface ReadonlySymbolSet { + size(): number; + has(symbol: Symbol): boolean; + forEach(cb: (symbol: Symbol) => void): void; + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; + } -function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { - Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); - return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); -} + class SymbolSet implements ReadonlySymbolSet { + private map = new Map(); + add(symbol: Symbol): void { + this.map.set(String(getSymbolId(symbol)), symbol); + } + has(symbol: Symbol): boolean { + return this.map.has(String(getSymbolId(symbol))); + } + delete(symbol: Symbol): void { + this.map.delete(String(getSymbolId(symbol))); + } + forEach(cb: (symbol: Symbol) => void): void { + this.map.forEach(cb); + } + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { + return forEachEntry(this.map, cb); + } + clone(): SymbolSet { + const clone = new SymbolSet(); + copyEntries(this.map, clone.map); + return clone; + } + size() { + return this.map.size; + } + } -function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return true; - default: - return false; + type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' + type NonVariableTopLevelDeclaration = + | FunctionDeclaration + | ClassDeclaration + | EnumDeclaration + | TypeAliasDeclaration + | InterfaceDeclaration + | ModuleDeclaration + | TopLevelExpressionStatement + | ImportEqualsDeclaration; + type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; + interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } + type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; + function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { + return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); + } + + function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { + return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; + } + + function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { + Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); + return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); + } + + function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return true; + default: + return false; + } } -} -function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (statement.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); - - case SyntaxKind.VariableStatement: - return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); - - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty - ? cb(statement as TopLevelExpressionStatement) - : undefined; + function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (statement.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); + + case SyntaxKind.VariableStatement: + return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); + + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty + ? cb(statement as TopLevelExpressionStatement) + : undefined; + } } } -} -function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); - default: - return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); + function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); + default: + return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); + } } -} -function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { - return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); -} - -function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { - switch (d.kind) { - case SyntaxKind.VariableDeclaration: - return d.parent.parent; - case SyntaxKind.BindingElement: - return getTopLevelDeclarationStatement( - cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); - default: - return d; + function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { + return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); } -} -function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { - if (isExported(sourceFile, decl, useEs6Exports, name)) return; - if (useEs6Exports) { - if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); - } - else { - const names = getNamesToExportInCommonJS(decl); - if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); + function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { + switch (d.kind) { + case SyntaxKind.VariableDeclaration: + return d.parent.parent; + case SyntaxKind.BindingElement: + return getTopLevelDeclarationStatement( + cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); + default: + return d; + } } -} -function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { - if (useEs6Exports) { - return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); + function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { + if (isExported(sourceFile, decl, useEs6Exports, name)) return; + if (useEs6Exports) { + if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); + } + else { + const names = getNamesToExportInCommonJS(decl); + if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); + } } - return !!sourceFile.symbol && !!sourceFile.symbol.exports && - getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); -} -function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { - return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); -} -function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { - const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; - switch (d.kind) { - case SyntaxKind.FunctionDeclaration: - return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); - case SyntaxKind.ClassDeclaration: - const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; - return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.VariableStatement: - return factory.updateVariableStatement(d, modifiers, d.declarationList); - case SyntaxKind.ModuleDeclaration: - return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); - case SyntaxKind.EnumDeclaration: - return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); - case SyntaxKind.TypeAliasDeclaration: - return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); - case SyntaxKind.InterfaceDeclaration: - return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.ImportEqualsDeclaration: - return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); - case SyntaxKind.ExpressionStatement: - return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); + function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { + if (useEs6Exports) { + return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); + } + return !!sourceFile.symbol && !!sourceFile.symbol.exports && + getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); + } + + function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { + return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); + } + function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { + const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; + switch (d.kind) { + case SyntaxKind.FunctionDeclaration: + return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); + case SyntaxKind.ClassDeclaration: + const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; + return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.VariableStatement: + return factory.updateVariableStatement(d, modifiers, d.declarationList); + case SyntaxKind.ModuleDeclaration: + return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); + case SyntaxKind.EnumDeclaration: + return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); + case SyntaxKind.TypeAliasDeclaration: + return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); + case SyntaxKind.InterfaceDeclaration: + return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.ImportEqualsDeclaration: + return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); + case SyntaxKind.ExpressionStatement: + return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); + } } -} -function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { - return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; -} -function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { - switch (decl.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - return [decl.name!.text]; // TODO: GH#18217 - case SyntaxKind.VariableStatement: - return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return emptyArray; - case SyntaxKind.ExpressionStatement: - return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); + function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { + return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; + } + function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { + switch (decl.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + return [decl.name!.text]; // TODO: GH#18217 + case SyntaxKind.VariableStatement: + return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return emptyArray; + case SyntaxKind.ExpressionStatement: + return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); + } } -} -/** Creates `exports.x = x;` */ -function createExportAssignment(name: string): Statement { - return factory.createExpressionStatement( - factory.createBinaryExpression( - factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), - SyntaxKind.EqualsToken, - factory.createIdentifier(name))); + /** Creates `exports.x = x;` */ + function createExportAssignment(name: string): Statement { + return factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), + SyntaxKind.EqualsToken, + factory.createIdentifier(name))); + } } diff --git a/tests/cases/fourslash/getEditsForFileRename_keepFileExtensions.ts b/tests/cases/fourslash/getEditsForFileRename_keepFileExtensions.ts new file mode 100644 index 0000000000000..acc02153b0bf2 --- /dev/null +++ b/tests/cases/fourslash/getEditsForFileRename_keepFileExtensions.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /tsconfig.json +////{ +//// "compilerOptions": { +//// "module": "Node16", +//// "rootDirs": ["src"] +//// } +////} + +// @Filename: /src/person.ts +////export const name = 0; + +// @Filename: /src/index.ts +////import {name} from "./person.js"; + +verify.getEditsForFileRename({ + oldPath: '/src/person.ts', + newPath: '/src/vip.ts', + newFileContents: { + '/src/index.ts': 'import {name} from "./vip.js";', + }, +}); diff --git a/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts new file mode 100644 index 0000000000000..92491831ba0cd --- /dev/null +++ b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts @@ -0,0 +1,60 @@ +/// + +// @Filename: /tsconfig.json +////{ +//// "compilerOptions": { +//// "module": "Node16", +//// } +////} + +// @Filename: /main.ts +////[|function someLibFn(): string { +//// return someOtherFn(); +////}|] +//// +////function someOtherFn(): string { +//// return "hello world!"; +////} +////console.log(someLibFn()); + +verify.moveToNewFile({ + newFileContents: { + "/main.ts": +`import { someLibFn } from "./someLibFn.js"; + +export function someOtherFn(): string { + return "hello world!"; +} +console.log(someLibFn());`, + + "/someLibFn.ts": +`import { someOtherFn } from "./main.js"; + +export function someLibFn(): string { + return someOtherFn(); +} +`, + } + }); + + +// "compilerOptions": { +// "composite": true, +// "declaration": true, +// "declarationMap": true, +// "downlevelIteration": true, +// "isolatedModules": true, +// "module": "NodeNext", +// "rootDir": "./", +// "sourceMap": true, +// "target": "esnext", +// "newLine": "lf", +// "useDefineForClassFields": true, +// "strict": true, +// "alwaysStrict": true, +// "exactOptionalPropertyTypes": true, +// "noImplicitOverride": true, +// "noUncheckedIndexedAccess": true, +// "importsNotUsedAsValues": "error", +// "forceConsistentCasingInFileNames": true +// } From d784d5e989e4dfa3baa6bb3d66425c11f745ccaa Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Mon, 28 Nov 2022 13:14:39 -0800 Subject: [PATCH 2/6] fixed 50710 --- src/services/refactors/moveToNewFile.ts | 1394 +++++++++-------- .../moveToNewFile_importFileExtensions.ts | 56 +- 2 files changed, 741 insertions(+), 709 deletions(-) diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 3c9f2c520ffb8..6007ba1b04fdf 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -1,8 +1,11 @@ +import { getModuleSpecifier } from "../../compiler/moduleSpecifiers"; import { AnyImportOrRequireStatement, append, ApplicableRefactorInfo, AssignmentDeclarationKind, BinaryExpression, BindingElement, BindingName, CallExpression, canHaveDecorators, canHaveModifiers, cast, ClassDeclaration, codefix, - combinePaths, concatenate, contains, copyEntries, createTextRangeFromSpan, Debug, Declaration, DeclarationStatement, - Diagnostics, emptyArray, ensurePathIsNonModuleName, EnumDeclaration, escapeLeadingUnderscores, Expression, + combinePaths, concatenate, contains, copyEntries, createModuleSpecifierResolutionHost, createTextRangeFromSpan, Debug, Declaration, DeclarationStatement, + Diagnostics, emptyArray, + // ensurePathIsNonModuleName, ISABEL + EnumDeclaration, escapeLeadingUnderscores, Expression, ExpressionStatement, extensionFromPath, ExternalModuleReference, factory, find, FindAllReferences, findIndex, firstDefined, flatMap, forEachEntry, FunctionDeclaration, getAssignmentDeclarationKind, getBaseFileName, GetCanonicalFileName, getDecorators, getDirectoryPath, getLocaleSpecificMessage, getModifiers, @@ -15,9 +18,14 @@ import { isPropertyAssignment, isRequireCall, isSourceFile, isStringLiteral, isStringLiteralLike, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, LanguageServiceHost, last, length, makeImportIfNecessary, Map, mapDefined, ModifierFlags, ModifierLike, ModuleDeclaration, NamedImportBindings, Node, NodeFlags, nodeSeenTracker, - normalizePath, ObjectBindingElementWithoutPropertyName, Program, PropertyAccessExpression, PropertyAssignment, - QuotePreference, rangeContainsRange, RefactorContext, RefactorEditInfo, removeFileExtension, RequireOrImportCall, - RequireVariableStatement, ScriptTarget, skipAlias, some, SourceFile, Statement, StringLiteralLike, Symbol, + normalizePath, ObjectBindingElementWithoutPropertyName, Program, + // programContainsEsModules, + PropertyAccessExpression, PropertyAssignment, + QuotePreference, rangeContainsRange, RefactorContext, RefactorEditInfo, + // removeExtension, + // removeFileExtension, + RequireOrImportCall, + RequireVariableStatement, resolvePath, ScriptTarget, skipAlias, some, SourceFile, Statement, StringLiteralLike, Symbol, SymbolFlags, symbolNameNoDefault, SyntaxKind, takeWhile, textChanges, TransformFlags, tryCast, TypeAliasDeclaration, TypeChecker, TypeNode, UserPreferences, VariableDeclaration, VariableDeclarationList, VariableStatement, } from "../_namespaces/ts"; @@ -85,792 +93,820 @@ function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes const currentDirectory = getDirectoryPath(oldFile.fileName); const extension = extensionFromPath(oldFile.fileName); - const newModuleName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); - const newFileNameWithExtension = newModuleName + extension; + // const newModuleName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); + const newFileName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); - // If previous file was global, this is easy. - changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); + // If previous file was global, this is easy. ISABEL added host to getNewStatementsAndRemoveFromOldFile call + changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileName + extension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFileName, extension, preferences)); - addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); - } + addNewFileToTsconfig(program, changes, oldFile.fileName, newFileName + extension, hostGetCanonicalFileName(host)); +} - interface StatementRange { - readonly first: Statement; - readonly afterLast: Statement | undefined; - } - interface ToMove { - readonly all: readonly Statement[]; - readonly ranges: readonly StatementRange[]; - } +interface StatementRange { + readonly first: Statement; + readonly afterLast: Statement | undefined; +} +interface ToMove { + readonly all: readonly Statement[]; + readonly ranges: readonly StatementRange[]; +} - function getStatementsToMove(context: RefactorContext): ToMove | undefined { - const rangeToMove = getRangeToMove(context); - if (rangeToMove === undefined) return undefined; - const all: Statement[] = []; - const ranges: StatementRange[] = []; - const { toMove, afterLast } = rangeToMove; - getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { - for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); - ranges.push({ first: toMove[start], afterLast }); - }); - return all.length === 0 ? undefined : { all, ranges }; - } - - function isAllowedStatementToMove(statement: Statement): boolean { - // Filters imports and prologue directives 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. - // Prologue directives will be copied to the new file and should be left in the old file. - return !isPureImport(statement) && !isPrologueDirective(statement); - } - - function isPureImport(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return true; - case SyntaxKind.ImportEqualsDeclaration: - return !hasSyntacticModifier(node, ModifierFlags.Export); - case SyntaxKind.VariableStatement: - return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); - default: - return false; - } +function getStatementsToMove(context: RefactorContext): ToMove | undefined { + const rangeToMove = getRangeToMove(context); + if (rangeToMove === undefined) return undefined; + const all: Statement[] = []; + const ranges: StatementRange[] = []; + const { toMove, afterLast } = rangeToMove; + getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { + for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); + ranges.push({ first: toMove[start], afterLast }); + }); + return all.length === 0 ? undefined : { all, ranges }; +} + +function isAllowedStatementToMove(statement: Statement): boolean { + // Filters imports and prologue directives 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. + // Prologue directives will be copied to the new file and should be left in the old file. + return !isPureImport(statement) && !isPrologueDirective(statement); +} + +function isPureImport(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return true; + case SyntaxKind.ImportEqualsDeclaration: + return !hasSyntacticModifier(node, ModifierFlags.Export); + case SyntaxKind.VariableStatement: + return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); + default: + return false; } +} - function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { - const cfg = program.getCompilerOptions().configFile; - if (!cfg) return; +function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { + const cfg = program.getCompilerOptions().configFile; + if (!cfg) return; - const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); - const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); + const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); + const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); - const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); - const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => - isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); - if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { - changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); - } + const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); + const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => + isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); + if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { + changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); } +} - function getNewStatementsAndRemoveFromOldFile( - oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, - ) { - const checker = program.getTypeChecker(); - const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); - if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { - deleteMovedStatements(oldFile, toMove.ranges, changes); - return [...prologueDirectives, ...toMove.all]; - } +function getNewStatementsAndRemoveFromOldFile( // ISABEL added host to function + oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, host: LanguageServiceHost, newModuleName: string, extension: string, preferences: UserPreferences, +) { + const checker = program.getTypeChecker(); + const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); + if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { + deleteMovedStatements(oldFile, toMove.ranges, changes); + return [...prologueDirectives, ...toMove.all]; + } - const useEsModuleSyntax = !!oldFile.externalModuleIndicator; - const quotePreference = getQuotePreference(oldFile, preferences); - const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); - if (importsFromNewFile) { - insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); - } + const useEsModuleSyntax = !!oldFile.externalModuleIndicator; + const quotePreference = getQuotePreference(oldFile, preferences); + const newModuleNameWithExtension = newModuleName + extension; + // ISABEL added host to function call + // const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); + const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newModuleNameWithExtension, program, host, useEsModuleSyntax, quotePreference); + if (importsFromNewFile) { + insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); + } - deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); - deleteMovedStatements(oldFile, toMove.ranges, changes); - updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); - - const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); - const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); - if (imports.length && body.length) { - return [ - ...prologueDirectives, - ...imports, - SyntaxKind.NewLineTrivia as const, - ...body - ]; - } + deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); + deleteMovedStatements(oldFile, toMove.ranges, changes); + updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, newModuleName, extension); + // ISABEL added Program, Host + const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference); + const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); + if (imports.length && body.length) { return [ ...prologueDirectives, ...imports, - ...body, + SyntaxKind.NewLineTrivia as const, + ...body ]; } - function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { - for (const { first, afterLast } of moved) { - changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); - } - } + return [ + ...prologueDirectives, + ...imports, + ...body, + ]; +} - function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; - forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); - } +function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { + for (const { first, afterLast } of moved) { + changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); } +} - function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { - const checker = program.getTypeChecker(); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile === oldFile) continue; - for (const statement of sourceFile.statements) { - forEachImportInStatement(statement, importNode => { - if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; - - const shouldMove = (name: Identifier): boolean => { - const symbol = isBindingElement(name.parent) - ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) - : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 - return !!symbol && movedSymbols.has(symbol); - }; - deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); - const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); - if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); - - const ns = getNamespaceLikeImport(importNode); - if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); - }); - } - } +function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; + forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); } +} - function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? - node.importClause.namedBindings.name : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; - case SyntaxKind.VariableDeclaration: - return tryCast(node.name, isIdentifier); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); +function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, host: LanguageServiceHost, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string, extension: string): void { + const checker = program.getTypeChecker(); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile === oldFile) continue; + for (const statement of sourceFile.statements) { + forEachImportInStatement(statement, importNode => { + if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; + + const shouldMove = (name: Identifier): boolean => { + const symbol = isBindingElement(name.parent) + ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) + : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 + return !!symbol && movedSymbols.has(symbol); + }; + deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file + const newFileNameWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newModuleName + extension); + const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); + //ISABEL fix variable names + // newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); + const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); + if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); + + const ns = getNamespaceLikeImport(importNode); + if (ns) { + updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); + } + }); } } +} - function updateNamespaceLikeImport( - changes: textChanges.ChangeTracker, - sourceFile: SourceFile, - checker: TypeChecker, - movedSymbols: ReadonlySymbolSet, - newModuleName: string, - newModuleSpecifier: string, - oldImportId: Identifier, - oldImportNode: SupportedImport, - ): void { - const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); - let needUniqueName = false; - const toChange: Identifier[] = []; - FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { - if (!isPropertyAccessExpression(ref.parent)) return; - needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); - if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { - toChange.push(ref); - } - }); - - if (toChange.length) { - const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; - for (const ref of toChange) { - changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); - } - changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); - } +function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? + node.importClause.namedBindings.name : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + case SyntaxKind.VariableDeclaration: + return tryCast(node.name, isIdentifier); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); } +} - function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { - const newNamespaceId = factory.createIdentifier(newNamespaceName); - const newModuleString = factory.createStringLiteral(newModuleSpecifier); - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), - newModuleString, - /*assertClause*/ undefined); - case SyntaxKind.ImportEqualsDeclaration: - return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); - case SyntaxKind.VariableDeclaration: - return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); - } +function updateNamespaceLikeImport( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + checker: TypeChecker, + movedSymbols: ReadonlySymbolSet, + newModuleName: string, + newModuleSpecifier: string, + oldImportId: Identifier, + oldImportNode: SupportedImport, +): void { + const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); + let needUniqueName = false; + const toChange: Identifier[] = []; + FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { + if (!isPropertyAccessExpression(ref.parent)) return; + needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); + if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { + toChange.push(ref); + } + }); + + if (toChange.length) { + const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; + for (const ref of toChange) { + changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); + } + changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); } +} - function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { - return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier - : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression - : i.initializer.arguments[0]); +function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { + const newNamespaceId = factory.createIdentifier(newNamespaceName); + const newModuleString = factory.createStringLiteral(newModuleSpecifier); + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), + newModuleString, + /*assertClause*/ undefined); + case SyntaxKind.ImportEqualsDeclaration: + return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); + case SyntaxKind.VariableDeclaration: + return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); } +} - function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { - if (isImportDeclaration(statement)) { - if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); - } - else if (isImportEqualsDeclaration(statement)) { - if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { - cb(statement as SupportedImport); - } - } - else if (isVariableStatement(statement)) { - for (const decl of statement.declarationList.declarations) { - if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { - cb(decl as SupportedImport); - } - } +function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { + return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier + : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression + : i.initializer.arguments[0]); +} + +function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { + if (isImportDeclaration(statement)) { + if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); + } + else if (isImportEqualsDeclaration(statement)) { + if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { + cb(statement as SupportedImport); } } - - type SupportedImport = - | ImportDeclaration & { moduleSpecifier: StringLiteralLike } - | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } - | VariableDeclaration & { initializer: RequireOrImportCall }; - type SupportedImportStatement = - | ImportDeclaration - | ImportEqualsDeclaration - | VariableStatement; - - function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - let defaultImport: Identifier | undefined; - const imports: string[] = []; - newFileNeedExport.forEach(symbol => { - if (symbol.escapedName === InternalSymbolName.Default) { - defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 - } - else { - imports.push(symbol.name); + else if (isVariableStatement(statement)) { + for (const decl of statement.declarationList.declarations) { + if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { + cb(decl as SupportedImport); } - }); - return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); + } } +} - function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - path = ensurePathIsNonModuleName(path); - if (useEs6Imports) { - const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); +type SupportedImport = + | ImportDeclaration & { moduleSpecifier: StringLiteralLike } + | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } + | VariableDeclaration & { initializer: RequireOrImportCall }; +type SupportedImportStatement = + | ImportDeclaration + | ImportEqualsDeclaration + | VariableStatement; + +function createOldFileImportsFromNewFile(sourceFile: SourceFile, newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, program: Program, host: LanguageServiceHost, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + let defaultImport: Identifier | undefined; + const imports: string[] = []; + newFileNeedExport.forEach(symbol => { + if (symbol.escapedName === InternalSymbolName.Default) { + defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 } 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(factory.createStringLiteral(path))) as RequireVariableStatement - : undefined; + imports.push(symbol.name); } - } + }); + //ISABEL added sourceFile, program, host to function call + return makeImportOrRequire(sourceFile, defaultImport, imports, newFileNameWithExtension, program, host, useEs6Imports, quotePreference); +} - function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { - return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +function makeImportOrRequire( + sourceFile: SourceFile, + defaultImport: Identifier | undefined, + imports: readonly string[], newFileNameWithExtension: string, + program: Program, host: LanguageServiceHost, + useEs6Imports: boolean, + quotePreference: QuotePreference + ): AnyImportOrRequireStatement | undefined { + //DEBUG + newFileNameWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension); + newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); + + // const getCanonicalFileName = hostGetCanonicalFileName(host); + // const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); + //ISABEL added sourceFile, host to function call and changed path to require module specifier + // path = ensurePathIsNonModuleName(path); + // normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); + if (useEs6Imports) { + const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); + return makeImportIfNecessary(defaultImport, specifiers, newFileNameWithExtension, 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(factory.createStringLiteral(newFileNameWithExtension))) as RequireVariableStatement + : undefined; } +} - function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { - return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); - } +function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { + return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +} - function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { - return flatMap(toMove, statement => { - if (isTopLevelDeclarationStatement(statement) && - !isExported(sourceFile, statement, useEs6Exports) && - forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { - const exports = addExport(statement, useEs6Exports); - if (exports) return exports; - } - return statement; - }); - } +function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { + return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); +} - 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 addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { + return flatMap(toMove, statement => { + if (isTopLevelDeclarationStatement(statement) && + !isExported(sourceFile, statement, useEs6Exports) && + forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { + const exports = addExport(statement, useEs6Exports); + if (exports) return exports; } + return statement; + }); +} + +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); +} +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) + ); } - 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); - } + else if (namedBindings.kind === SyntaxKind.NamedImports) { + for (const element of namedBindings.elements) { + if (isUnused(element.name)) changes.delete(sourceFile, element); } } } } - 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))) { +} +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) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl); + isVariableDeclarationList(varDecl.parent) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl); } else { - for (const element of name.elements) { - if (isIdentifier(element.name) && isUnused(element.name)) { - changes.delete(sourceFile, element.name); - } + 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; - } + } + break; } +} - function getNewFileImportsAndAddExportInOldFile( - oldFile: SourceFile, - importsToCopy: ReadonlySymbolSet, - newFileImportsFromOldFile: ReadonlySymbolSet, - changes: textChanges.ChangeTracker, - checker: TypeChecker, - 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)!))); - }); +function getNewFileImportsAndAddExportInOldFile( + oldFile: SourceFile, + importsToCopy: ReadonlySymbolSet, + newFileImportsFromOldFile: ReadonlySymbolSet, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + program: Program, + host: LanguageServiceHost, //ISABEL added program, host + 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; - // 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; + const top = getTopLevelDeclarationStatement(decl); + if (markSeenTop(top)) { + addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax); } - 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); - } + if (hasSyntacticModifier(decl, ModifierFlags.Default)) { + oldFileDefault = name; } - }); + else { + oldFileNamedImports.push(name.text); + } + } + }); - append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); - return copiedOldImports; - } + // ISABEL changed makeImportOrRequire call + // append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); + append(copiedOldImports, makeImportOrRequire(oldFile, oldFileDefault, oldFileNamedImports, getBaseFileName(oldFile.fileName), program, host, useEsModuleSyntax, quotePreference)); + return copiedOldImports; +} - function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { - let newModuleName = moduleName; - for (let i = 1; ; i++) { - const name = combinePaths(inDirectory, newModuleName + extension); - if (!host.fileExists(name)) return newModuleName; - newModuleName = `${moduleName}.${i}`; - } +function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { + let newModuleName = moduleName; + for (let i = 1; ; i++) { + const name = combinePaths(inDirectory, newModuleName + extension); + if (!host.fileExists(name)) return newModuleName; + newModuleName = `${moduleName}.${i}`; } +} - function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { - return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; - } +function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { + return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; +} - interface UsageInfo { - // Symbols whose declarations are moved from the old file to the new file. - readonly movedSymbols: ReadonlySymbolSet; +interface UsageInfo { + // Symbols whose declarations are moved from the old file to the new file. + readonly movedSymbols: ReadonlySymbolSet; - // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) - readonly newFileImportsFromOldFile: ReadonlySymbolSet; - // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. - readonly oldFileImportsFromNewFile: ReadonlySymbolSet; + // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) + readonly newFileImportsFromOldFile: ReadonlySymbolSet; + // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. + readonly oldFileImportsFromNewFile: ReadonlySymbolSet; - readonly oldImportsNeededByNewFile: ReadonlySymbolSet; - // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. - readonly unusedImportsFromOldFile: ReadonlySymbolSet; - } - function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { - const movedSymbols = new SymbolSet(); - const oldImportsNeededByNewFile = new SymbolSet(); - const newFileImportsFromOldFile = new SymbolSet(); + readonly oldImportsNeededByNewFile: ReadonlySymbolSet; + // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. + readonly unusedImportsFromOldFile: ReadonlySymbolSet; +} +function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { + const movedSymbols = new SymbolSet(); + const oldImportsNeededByNewFile = new SymbolSet(); + const newFileImportsFromOldFile = new SymbolSet(); - const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); - const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); - if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) - oldImportsNeededByNewFile.add(jsxNamespaceSymbol); - } + const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); + const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); + if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) + oldImportsNeededByNewFile.add(jsxNamespaceSymbol); + } - for (const statement of toMove) { - forEachTopLevelDeclaration(statement, decl => { - movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); - }); - } - for (const statement of toMove) { - forEachReference(statement, checker, symbol => { - if (!symbol.declarations) return; - for (const decl of symbol.declarations) { - if (isInImport(decl)) { - oldImportsNeededByNewFile.add(symbol); - } - else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { - newFileImportsFromOldFile.add(symbol); - } + for (const statement of toMove) { + forEachTopLevelDeclaration(statement, decl => { + movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); + }); + } + for (const statement of toMove) { + forEachReference(statement, checker, symbol => { + if (!symbol.declarations) return; + for (const decl of symbol.declarations) { + if (isInImport(decl)) { + oldImportsNeededByNewFile.add(symbol); } - }); - } + else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { + newFileImportsFromOldFile.add(symbol); + } + } + }); + } - const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); + const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); - const oldFileImportsFromNewFile = new SymbolSet(); - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; + const oldFileImportsFromNewFile = new SymbolSet(); + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; - // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. - if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { - unusedImportsFromOldFile.delete(jsxNamespaceSymbol); - } + // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. + if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { + unusedImportsFromOldFile.delete(jsxNamespaceSymbol); + } - forEachReference(statement, checker, symbol => { - if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); - unusedImportsFromOldFile.delete(symbol); - }); + forEachReference(statement, checker, symbol => { + if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); + unusedImportsFromOldFile.delete(symbol); + }); + } + + return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + + function getJsxNamespaceSymbol(containsJsx: Node | undefined) { + if (containsJsx === undefined) { + return undefined; } - return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + const jsxNamespace = checker.getJsxNamespace(containsJsx); - function getJsxNamespaceSymbol(containsJsx: Node | undefined) { - if (containsJsx === undefined) { - return undefined; - } + // Strictly speaking, this could resolve to a symbol other than the JSX namespace. + // This will produce erroneous output (probably, an incorrectly copied import) but + // is expected to be very rare and easily reversible. + const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); - const jsxNamespace = checker.getJsxNamespace(containsJsx); + return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) + ? jsxNamespaceSymbol + : undefined; + } +} - // Strictly speaking, this could resolve to a symbol other than the JSX namespace. - // This will produce erroneous output (probably, an incorrectly copied import) but - // is expected to be very rare and easily reversible. - const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); +// Below should all be utilities + +function isInImport(decl: Declaration) { + switch (decl.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + return true; + case SyntaxKind.VariableDeclaration: + return isVariableDeclarationInImport(decl as VariableDeclaration); + case SyntaxKind.BindingElement: + return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); + default: + return false; + } +} +function isVariableDeclarationInImport(decl: VariableDeclaration) { + return isSourceFile(decl.parent.parent.parent) && + !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); +} - return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) - ? jsxNamespaceSymbol +function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { + switch (i.kind) { + case SyntaxKind.ImportDeclaration: { + const clause = i.importClause; + if (!clause) return undefined; + const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; + const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); + return defaultImport || namedBindings + ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) : undefined; } - } - - // Below should all be utilities - - function isInImport(decl: Declaration) { - switch (decl.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - return true; - case SyntaxKind.VariableDeclaration: - return isVariableDeclarationInImport(decl as VariableDeclaration); - case SyntaxKind.BindingElement: - return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); - default: - return false; + case SyntaxKind.ImportEqualsDeclaration: + return keep(i.name) ? i : undefined; + case SyntaxKind.VariableDeclaration: { + const name = filterBindingName(i.name, keep); + return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; } + default: + return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); } - function isVariableDeclarationInImport(decl: VariableDeclaration) { - return isSourceFile(decl.parent.parent.parent) && - !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); - } - - function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { - switch (i.kind) { - case SyntaxKind.ImportDeclaration: { - const clause = i.importClause; - if (!clause) return undefined; - const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; - const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); - return defaultImport || namedBindings - ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) - : undefined; - } - case SyntaxKind.ImportEqualsDeclaration: - return keep(i.name) ? i : undefined; - case SyntaxKind.VariableDeclaration: { - const name = filterBindingName(i.name, keep); - return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; - } - default: - return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); +} +function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + return keep(namedBindings.name) ? namedBindings : undefined; + } + else { + const newElements = namedBindings.elements.filter(e => keep(e.name)); + return newElements.length ? factory.createNamedImports(newElements) : undefined; + } +} +function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return keep(name) ? name : undefined; + case SyntaxKind.ArrayBindingPattern: + return name; + case SyntaxKind.ObjectBindingPattern: { + // We can't handle nested destructurings or property names well here, so just copy them all. + const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); + return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; } } - function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - return keep(namedBindings.name) ? namedBindings : undefined; +} + +function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { + node.forEachChild(function cb(node) { + if (isIdentifier(node) && !isDeclarationName(node)) { + const sym = checker.getSymbolAtLocation(node); + if (sym) onReference(sym); } else { - const newElements = namedBindings.elements.filter(e => keep(e.name)); - return newElements.length ? factory.createNamedImports(newElements) : undefined; + node.forEachChild(cb); } + }); +} + +interface ReadonlySymbolSet { + size(): number; + has(symbol: Symbol): boolean; + forEach(cb: (symbol: Symbol) => void): void; + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; +} + +class SymbolSet implements ReadonlySymbolSet { + private map = new Map(); + add(symbol: Symbol): void { + this.map.set(String(getSymbolId(symbol)), symbol); } - function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return keep(name) ? name : undefined; - case SyntaxKind.ArrayBindingPattern: - return name; - case SyntaxKind.ObjectBindingPattern: { - // We can't handle nested destructurings or property names well here, so just copy them all. - const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); - return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; - } - } + has(symbol: Symbol): boolean { + return this.map.has(String(getSymbolId(symbol))); } - - function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { - node.forEachChild(function cb(node) { - if (isIdentifier(node) && !isDeclarationName(node)) { - const sym = checker.getSymbolAtLocation(node); - if (sym) onReference(sym); - } - else { - node.forEachChild(cb); - } - }); + delete(symbol: Symbol): void { + this.map.delete(String(getSymbolId(symbol))); } - - interface ReadonlySymbolSet { - size(): number; - has(symbol: Symbol): boolean; - forEach(cb: (symbol: Symbol) => void): void; - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; + forEach(cb: (symbol: Symbol) => void): void { + this.map.forEach(cb); } - - class SymbolSet implements ReadonlySymbolSet { - private map = new Map(); - add(symbol: Symbol): void { - this.map.set(String(getSymbolId(symbol)), symbol); - } - has(symbol: Symbol): boolean { - return this.map.has(String(getSymbolId(symbol))); - } - delete(symbol: Symbol): void { - this.map.delete(String(getSymbolId(symbol))); - } - forEach(cb: (symbol: Symbol) => void): void { - this.map.forEach(cb); - } - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { - return forEachEntry(this.map, cb); - } - clone(): SymbolSet { - const clone = new SymbolSet(); - copyEntries(this.map, clone.map); - return clone; - } - size() { - return this.map.size; - } + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { + return forEachEntry(this.map, cb); } + clone(): SymbolSet { + const clone = new SymbolSet(); + copyEntries(this.map, clone.map); + return clone; + } + size() { + return this.map.size; + } +} - type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' - type NonVariableTopLevelDeclaration = - | FunctionDeclaration - | ClassDeclaration - | EnumDeclaration - | TypeAliasDeclaration - | InterfaceDeclaration - | ModuleDeclaration - | TopLevelExpressionStatement - | ImportEqualsDeclaration; - type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; - interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } - type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; - function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { - return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); - } - - function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { - return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; - } - - function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { - Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); - return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); - } - - function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return true; - default: - return false; - } +type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' +type NonVariableTopLevelDeclaration = + | FunctionDeclaration + | ClassDeclaration + | EnumDeclaration + | TypeAliasDeclaration + | InterfaceDeclaration + | ModuleDeclaration + | TopLevelExpressionStatement + | ImportEqualsDeclaration; +type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; +interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } +type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; +function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { + return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); +} + +function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { + return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; +} + +function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { + Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); + return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); +} + +function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return true; + default: + return false; } +} - function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (statement.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); - - case SyntaxKind.VariableStatement: - return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); - - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty - ? cb(statement as TopLevelExpressionStatement) - : undefined; - } +function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (statement.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); + + case SyntaxKind.VariableStatement: + return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); + + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty + ? cb(statement as TopLevelExpressionStatement) + : undefined; } } - function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); - default: - return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); - } +} +function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); + default: + return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); } +} - function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { - return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { + return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +} + +function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { + switch (d.kind) { + case SyntaxKind.VariableDeclaration: + return d.parent.parent; + case SyntaxKind.BindingElement: + return getTopLevelDeclarationStatement( + cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); + default: + return d; } +} - function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { - switch (d.kind) { - case SyntaxKind.VariableDeclaration: - return d.parent.parent; - case SyntaxKind.BindingElement: - return getTopLevelDeclarationStatement( - cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); - default: - return d; - } +function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { + if (isExported(sourceFile, decl, useEs6Exports, name)) return; + if (useEs6Exports) { + if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); + } + else { + const names = getNamesToExportInCommonJS(decl); + if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); } +} - function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { - if (isExported(sourceFile, decl, useEs6Exports, name)) return; - if (useEs6Exports) { - if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); - } - else { - const names = getNamesToExportInCommonJS(decl); - if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); - } +function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { + if (useEs6Exports) { + return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); } + return !!sourceFile.symbol && !!sourceFile.symbol.exports && + getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); +} - function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { - if (useEs6Exports) { - return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); - } - return !!sourceFile.symbol && !!sourceFile.symbol.exports && - getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); - } - - function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { - return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); - } - function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { - const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; - switch (d.kind) { - case SyntaxKind.FunctionDeclaration: - return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); - case SyntaxKind.ClassDeclaration: - const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; - return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.VariableStatement: - return factory.updateVariableStatement(d, modifiers, d.declarationList); - case SyntaxKind.ModuleDeclaration: - return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); - case SyntaxKind.EnumDeclaration: - return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); - case SyntaxKind.TypeAliasDeclaration: - return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); - case SyntaxKind.InterfaceDeclaration: - return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.ImportEqualsDeclaration: - return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); - case SyntaxKind.ExpressionStatement: - return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); - } +function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { + return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); +} +function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { + const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; + switch (d.kind) { + case SyntaxKind.FunctionDeclaration: + return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); + case SyntaxKind.ClassDeclaration: + const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; + return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.VariableStatement: + return factory.updateVariableStatement(d, modifiers, d.declarationList); + case SyntaxKind.ModuleDeclaration: + return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); + case SyntaxKind.EnumDeclaration: + return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); + case SyntaxKind.TypeAliasDeclaration: + return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); + case SyntaxKind.InterfaceDeclaration: + return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.ImportEqualsDeclaration: + return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); + case SyntaxKind.ExpressionStatement: + return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); } - function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { - return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; - } - function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { - switch (decl.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - return [decl.name!.text]; // TODO: GH#18217 - case SyntaxKind.VariableStatement: - return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return emptyArray; - case SyntaxKind.ExpressionStatement: - return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); - } +} +function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { + return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; +} +function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { + switch (decl.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + return [decl.name!.text]; // TODO: GH#18217 + case SyntaxKind.VariableStatement: + return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return emptyArray; + case SyntaxKind.ExpressionStatement: + return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); } +} - /** Creates `exports.x = x;` */ - function createExportAssignment(name: string): Statement { - return factory.createExpressionStatement( - factory.createBinaryExpression( - factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), - SyntaxKind.EqualsToken, - factory.createIdentifier(name))); - } +/** Creates `exports.x = x;` */ +function createExportAssignment(name: string): Statement { + return factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), + SyntaxKind.EqualsToken, + factory.createIdentifier(name))); } diff --git a/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts index 92491831ba0cd..b5872578ae8a3 100644 --- a/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts +++ b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts @@ -3,58 +3,54 @@ // @Filename: /tsconfig.json ////{ //// "compilerOptions": { -//// "module": "Node16", +//// "moduleResolution": "Node16", //// } ////} +// @Filename: /package.json +//// { "type": "module" } + // @Filename: /main.ts -////[|function someLibFn(): string { -//// return someOtherFn(); +////[|export function someLibFn(): string { +//// return main(); ////}|] //// -////function someOtherFn(): string { +////function main(): string { //// return "hello world!"; ////} ////console.log(someLibFn()); +// @Filename: /other.ts +////import { someLibFn } from "./main.js"; +//// +////function someOtherFn(): string { +//// return someLibFn(); +////} + + verify.moveToNewFile({ newFileContents: { "/main.ts": `import { someLibFn } from "./someLibFn.js"; -export function someOtherFn(): string { +export function main(): string { return "hello world!"; } console.log(someLibFn());`, "/someLibFn.ts": -`import { someOtherFn } from "./main.js"; +`import { main } from "./main.js"; export function someLibFn(): string { - return someOtherFn(); + return main(); } `, + + "/other.ts": +`import { someLibFn } from "./someLibFn.js"; + +function someOtherFn(): string { + return someLibFn(); +}`, } - }); - - -// "compilerOptions": { -// "composite": true, -// "declaration": true, -// "declarationMap": true, -// "downlevelIteration": true, -// "isolatedModules": true, -// "module": "NodeNext", -// "rootDir": "./", -// "sourceMap": true, -// "target": "esnext", -// "newLine": "lf", -// "useDefineForClassFields": true, -// "strict": true, -// "alwaysStrict": true, -// "exactOptionalPropertyTypes": true, -// "noImplicitOverride": true, -// "noUncheckedIndexedAccess": true, -// "importsNotUsedAsValues": "error", -// "forceConsistentCasingInFileNames": true -// } +}); \ No newline at end of file From 1360836e477ff384a0539f1bb88f2e12a77ae8fa Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 29 Nov 2022 16:40:55 -0800 Subject: [PATCH 3/6] fixed broken test --- src/services/refactors/moveToNewFile.ts | 34 +++++++------------ .../moveToNewFile_importFileExtensions.ts | 26 ++++++++++---- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 6007ba1b04fdf..ed68cb91bdc16 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -2,10 +2,8 @@ import { getModuleSpecifier } from "../../compiler/moduleSpecifiers"; import { AnyImportOrRequireStatement, append, ApplicableRefactorInfo, AssignmentDeclarationKind, BinaryExpression, BindingElement, BindingName, CallExpression, canHaveDecorators, canHaveModifiers, cast, ClassDeclaration, codefix, - combinePaths, concatenate, contains, copyEntries, createModuleSpecifierResolutionHost, createTextRangeFromSpan, Debug, Declaration, DeclarationStatement, - Diagnostics, emptyArray, - // ensurePathIsNonModuleName, ISABEL - EnumDeclaration, escapeLeadingUnderscores, Expression, + combinePaths, concatenate, contains, copyEntries, createModuleSpecifierResolutionHost, createTextRangeFromSpan, + Debug, Declaration, DeclarationStatement, Diagnostics, emptyArray, EnumDeclaration, escapeLeadingUnderscores, Expression, ExpressionStatement, extensionFromPath, ExternalModuleReference, factory, find, FindAllReferences, findIndex, firstDefined, flatMap, forEachEntry, FunctionDeclaration, getAssignmentDeclarationKind, getBaseFileName, GetCanonicalFileName, getDecorators, getDirectoryPath, getLocaleSpecificMessage, getModifiers, @@ -18,13 +16,8 @@ import { isPropertyAssignment, isRequireCall, isSourceFile, isStringLiteral, isStringLiteralLike, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, LanguageServiceHost, last, length, makeImportIfNecessary, Map, mapDefined, ModifierFlags, ModifierLike, ModuleDeclaration, NamedImportBindings, Node, NodeFlags, nodeSeenTracker, - normalizePath, ObjectBindingElementWithoutPropertyName, Program, - // programContainsEsModules, - PropertyAccessExpression, PropertyAssignment, - QuotePreference, rangeContainsRange, RefactorContext, RefactorEditInfo, - // removeExtension, - // removeFileExtension, - RequireOrImportCall, + normalizePath, ObjectBindingElementWithoutPropertyName, Program, PropertyAccessExpression, PropertyAssignment, + QuotePreference, rangeContainsRange, RefactorContext, RefactorEditInfo, RequireOrImportCall, RequireVariableStatement, resolvePath, ScriptTarget, skipAlias, some, SourceFile, Statement, StringLiteralLike, Symbol, SymbolFlags, symbolNameNoDefault, SyntaxKind, takeWhile, textChanges, TransformFlags, tryCast, TypeAliasDeclaration, TypeChecker, TypeNode, UserPreferences, VariableDeclaration, VariableDeclarationList, VariableStatement, @@ -93,10 +86,9 @@ function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes const currentDirectory = getDirectoryPath(oldFile.fileName); const extension = extensionFromPath(oldFile.fileName); - // const newModuleName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); const newFileName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); - // If previous file was global, this is easy. ISABEL added host to getNewStatementsAndRemoveFromOldFile call + // If previous file was global, this is easy. changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileName + extension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFileName, extension, preferences)); addNewFileToTsconfig(program, changes, oldFile.fileName, newFileName + extension, hostGetCanonicalFileName(host)); @@ -172,8 +164,7 @@ function getNewStatementsAndRemoveFromOldFile( // ISABEL added host to function const useEsModuleSyntax = !!oldFile.externalModuleIndicator; const quotePreference = getQuotePreference(oldFile, preferences); const newModuleNameWithExtension = newModuleName + extension; - // ISABEL added host to function call - // const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); + const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newModuleNameWithExtension, program, host, useEsModuleSyntax, quotePreference); if (importsFromNewFile) { insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); @@ -183,7 +174,6 @@ function getNewStatementsAndRemoveFromOldFile( // ISABEL added host to function deleteMovedStatements(oldFile, toMove.ranges, changes); updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, newModuleName, extension); - // ISABEL added Program, Host const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference); const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); if (imports.length && body.length) { @@ -230,7 +220,7 @@ function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: return !!symbol && movedSymbols.has(symbol); }; deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newFileNameWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newModuleName + extension); + const newFileNameWithExtension = resolvePath(getDirectoryPath(oldFile.path), newModuleName + extension); const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); //ISABEL fix variable names // newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); @@ -360,14 +350,16 @@ function createOldFileImportsFromNewFile(sourceFile: SourceFile, newFileNeedExpo function makeImportOrRequire( sourceFile: SourceFile, defaultImport: Identifier | undefined, - imports: readonly string[], newFileNameWithExtension: string, - program: Program, host: LanguageServiceHost, + imports: readonly string[], + newFileNameWithExtension: string, + program: Program, + host: LanguageServiceHost, useEs6Imports: boolean, quotePreference: QuotePreference ): AnyImportOrRequireStatement | undefined { //DEBUG - newFileNameWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension); - newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); + const pathToNewFileWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension); + newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFileWithExtension, createModuleSpecifierResolutionHost(program, host)); // const getCanonicalFileName = hostGetCanonicalFileName(host); // const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); diff --git a/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts index b5872578ae8a3..852dccb95ca37 100644 --- a/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts +++ b/tests/cases/fourslash/moveToNewFile_importFileExtensions.ts @@ -10,7 +10,7 @@ // @Filename: /package.json //// { "type": "module" } -// @Filename: /main.ts +// @Filename: /src/main.ts ////[|export function someLibFn(): string { //// return main(); ////}|] @@ -21,16 +21,23 @@ ////console.log(someLibFn()); // @Filename: /other.ts -////import { someLibFn } from "./main.js"; +////import { someLibFn } from "./src/main.js"; //// ////function someOtherFn(): string { //// return someLibFn(); ////} +// @Filename: /act/action.ts +////import { someLibFn } from "../src/main.js"; +//// +////function doAction(): string { +//// return someLibFn(); +////} + verify.moveToNewFile({ newFileContents: { - "/main.ts": + "/src/main.ts": `import { someLibFn } from "./someLibFn.js"; export function main(): string { @@ -38,7 +45,7 @@ export function main(): string { } console.log(someLibFn());`, - "/someLibFn.ts": + "/src/someLibFn.ts": `import { main } from "./main.js"; export function someLibFn(): string { @@ -46,11 +53,18 @@ export function someLibFn(): string { } `, - "/other.ts": -`import { someLibFn } from "./someLibFn.js"; + "/other.ts": +`import { someLibFn } from "./src/someLibFn.js"; function someOtherFn(): string { return someLibFn(); }`, + + "/act/action.ts": +`import { someLibFn } from "../src/someLibFn.js"; + +function doAction(): string { + return someLibFn(); +}`, } }); \ No newline at end of file From dac4620288bb2e318f99867a22da1e636a186d27 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 29 Nov 2022 18:44:25 -0800 Subject: [PATCH 4/6] clean up --- src/services/refactors/moveToNewFile.ts | 52 ++++++++++++------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index ed68cb91bdc16..fd588d9574b5c 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -151,7 +151,7 @@ function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTrack } } -function getNewStatementsAndRemoveFromOldFile( // ISABEL added host to function +function getNewStatementsAndRemoveFromOldFile( oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, host: LanguageServiceHost, newModuleName: string, extension: string, preferences: UserPreferences, ) { const checker = program.getTypeChecker(); @@ -163,9 +163,7 @@ function getNewStatementsAndRemoveFromOldFile( // ISABEL added host to function const useEsModuleSyntax = !!oldFile.externalModuleIndicator; const quotePreference = getQuotePreference(oldFile, preferences); - const newModuleNameWithExtension = newModuleName + extension; - - const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newModuleNameWithExtension, program, host, useEsModuleSyntax, quotePreference); + const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newModuleName + extension, program, host, useEsModuleSyntax, quotePreference); if (importsFromNewFile) { insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); } @@ -205,7 +203,9 @@ function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[ } } -function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, host: LanguageServiceHost, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string, extension: string): void { +function updateImportsInOtherFiles( + changes: textChanges.ChangeTracker, program: Program, host: LanguageServiceHost, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string, extension: string +): void { const checker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { if (sourceFile === oldFile) continue; @@ -220,17 +220,14 @@ function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: return !!symbol && movedSymbols.has(symbol); }; deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newFileNameWithExtension = resolvePath(getDirectoryPath(oldFile.path), newModuleName + extension); - const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); - //ISABEL fix variable names - // newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, newFileNameWithExtension, createModuleSpecifierResolutionHost(program, host)); + + const pathToNewFileWithExtension = resolvePath(getDirectoryPath(oldFile.path), newModuleName + extension); + const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFileWithExtension, createModuleSpecifierResolutionHost(program, host)); const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); const ns = getNamespaceLikeImport(importNode); - if (ns) { - updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); - } + if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); }); } } @@ -332,7 +329,15 @@ type SupportedImportStatement = | ImportEqualsDeclaration | VariableStatement; -function createOldFileImportsFromNewFile(sourceFile: SourceFile, newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, program: Program, host: LanguageServiceHost, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { +function createOldFileImportsFromNewFile( + sourceFile: SourceFile, + newFileNeedExport: ReadonlySymbolSet, + newFileNameWithExtension: string, + program: Program, + host: LanguageServiceHost, + useEs6Imports: boolean, + quotePreference: QuotePreference +): AnyImportOrRequireStatement | undefined { let defaultImport: Identifier | undefined; const imports: string[] = []; newFileNeedExport.forEach(symbol => { @@ -343,7 +348,6 @@ function createOldFileImportsFromNewFile(sourceFile: SourceFile, newFileNeedExpo imports.push(symbol.name); } }); - //ISABEL added sourceFile, program, host to function call return makeImportOrRequire(sourceFile, defaultImport, imports, newFileNameWithExtension, program, host, useEs6Imports, quotePreference); } @@ -356,19 +360,13 @@ function makeImportOrRequire( host: LanguageServiceHost, useEs6Imports: boolean, quotePreference: QuotePreference - ): AnyImportOrRequireStatement | undefined { - //DEBUG - const pathToNewFileWithExtension = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension); - newFileNameWithExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFileWithExtension, createModuleSpecifierResolutionHost(program, host)); - - // const getCanonicalFileName = hostGetCanonicalFileName(host); - // const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); - //ISABEL added sourceFile, host to function call and changed path to require module specifier - // path = ensurePathIsNonModuleName(path); - // normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); +): AnyImportOrRequireStatement | undefined { + const pathToNewFile = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension); + const pathToNewFileWithCorrectExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFile, createModuleSpecifierResolutionHost(program, host)); + if (useEs6Imports) { const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, newFileNameWithExtension, quotePreference); + return makeImportIfNecessary(defaultImport, specifiers, pathToNewFileWithCorrectExtension, quotePreference); } else { Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module. @@ -484,7 +482,7 @@ function getNewFileImportsAndAddExportInOldFile( changes: textChanges.ChangeTracker, checker: TypeChecker, program: Program, - host: LanguageServiceHost, //ISABEL added program, host + host: LanguageServiceHost, useEsModuleSyntax: boolean, quotePreference: QuotePreference, ): readonly SupportedImportStatement[] { @@ -521,8 +519,6 @@ function getNewFileImportsAndAddExportInOldFile( } }); - // ISABEL changed makeImportOrRequire call - // append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); append(copiedOldImports, makeImportOrRequire(oldFile, oldFileDefault, oldFileNamedImports, getBaseFileName(oldFile.fileName), program, host, useEsModuleSyntax, quotePreference)); return copiedOldImports; } From b4f97c26d220b31391e20c5d89447f1bc013bd6e Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 30 Nov 2022 13:42:05 -0800 Subject: [PATCH 5/6] variable rename --- src/services/refactors/moveToNewFile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index ad38d1807d433..19606f48c4140 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -478,7 +478,7 @@ function makeImportOrRequire( 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(factory.createStringLiteral(newFileNameWithExtension))) as RequireVariableStatement + ? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(factory.createStringLiteral(pathToNewFileWithCorrectExtension))) as RequireVariableStatement : undefined; } } From 73536463e25b5de4f65e3f11c4294d4bba3846c5 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 6 Dec 2022 00:18:12 -0800 Subject: [PATCH 6/6] rename variable to newFileBaseName --- src/services/refactors/moveToNewFile.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 19606f48c4140..dd4bc63d6beac 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -192,12 +192,12 @@ function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes const currentDirectory = getDirectoryPath(oldFile.fileName); const extension = extensionFromPath(oldFile.fileName); - const newFileName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); + const newFileBasename = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); // If previous file was global, this is easy. - changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileName + extension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFileName, extension, preferences)); + changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileBasename + extension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFileBasename, extension, preferences)); - addNewFileToTsconfig(program, changes, oldFile.fileName, newFileName + extension, hostGetCanonicalFileName(host)); + addNewFileToTsconfig(program, changes, oldFile.fileName, newFileBasename + extension, hostGetCanonicalFileName(host)); } interface StatementRange {