diff --git a/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts b/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts index 975435c37c46..e008bf394259 100644 --- a/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts +++ b/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import {updateModuleSpecifier} from './import-operations'; +import {updateModuleSpecifier, updateNamedImport} from './import-operations'; describe('import operations', () => { describe('updateModuleSpecifier', () => { @@ -39,6 +39,51 @@ describe('import operations', () => { }); }); }); + + describe('updateNamedExport', () => { + function runUpdateNamedExportTest( + description: string, + opts: { + oldFile: string; + newFile: string; + oldExport: string; + newExport: string; + }, + ): void { + const node = createNode(opts.oldFile, ts.SyntaxKind.NamedImports) as ts.NamedImports; + const newImport = updateNamedImport(node, { + oldExport: opts.oldExport, + newExport: opts.newExport, + })?.updateFn(opts.oldFile); + expect(newImport).withContext(description).toBe(opts.newFile); + } + it('updates the named exports of import declarations', () => { + runUpdateNamedExportTest('named binding', { + oldExport: 'oldExport', + newExport: 'newExport', + oldFile: `import { oldExport } from 'module-name';`, + newFile: `import { newExport } from 'module-name';`, + }); + runUpdateNamedExportTest('aliased named binding', { + oldExport: 'oldExport', + newExport: 'newExport', + oldFile: `import { oldExport as alias } from 'module-name';`, + newFile: `import { newExport as alias } from 'module-name';`, + }); + runUpdateNamedExportTest('multiple named bindings', { + oldExport: 'oldExport1', + newExport: 'newExport1', + oldFile: `import { oldExport1, export2 } from 'module-name';`, + newFile: `import { newExport1, export2 } from 'module-name';`, + }); + runUpdateNamedExportTest('multiple named bindings w/ alias', { + oldExport: 'oldExport2', + newExport: 'newExport2', + oldFile: `import { export1, oldExport2 as alias2 } from 'module-name';`, + newFile: `import { export1, newExport2 as alias2 } from 'module-name';`, + }); + }); + }); }); function createSourceFile(text: string): ts.SourceFile { diff --git a/src/material/schematics/migration-utilities/typescript/import-operations.ts b/src/material/schematics/migration-utilities/typescript/import-operations.ts index df346f302440..d7dbb0a48ad6 100644 --- a/src/material/schematics/migration-utilities/typescript/import-operations.ts +++ b/src/material/schematics/migration-utilities/typescript/import-operations.ts @@ -21,11 +21,42 @@ export function updateModuleSpecifier( offset: moduleSpecifier.pos, updateFn: (text: string) => { const index = text.indexOf(moduleSpecifier.text, moduleSpecifier.pos); - return ( - text.slice(0, index) + - opts.moduleSpecifier + - text.slice(index + moduleSpecifier.text.length) - ); + return replaceAt(text, index, { + old: moduleSpecifier.text, + new: opts.moduleSpecifier, + }); }, }; } + +/** Returns an Update that renames an export of the given named import node. */ +export function updateNamedImport( + node: ts.NamedImports, + opts: { + oldExport: string; + newExport: string; + }, +): Update | undefined { + for (let i = 0; i < node.elements.length; i++) { + const n = node.elements[i]; + const name = n.propertyName ? n.propertyName : n.name; + if (name.escapedText === opts.oldExport) { + return { + offset: name.pos, + updateFn: (text: string) => { + const index = text.indexOf(opts.oldExport, name.pos); + return replaceAt(text, index, { + old: opts.oldExport, + new: opts.newExport, + }); + }, + }; + } + } + return; +} + +/** Replaces the first instance of substring.old after the given index. */ +function replaceAt(str: string, index: number, substring: {old: string; new: string}): string { + return str.slice(0, index) + substring.new + str.slice(index + substring.old.length); +}