From 9eec3527046c510523c0ce5e574e63129f830a51 Mon Sep 17 00:00:00 2001 From: Wagner Maciel Date: Tue, 21 Jun 2022 07:09:31 -0700 Subject: [PATCH] feat(material/schematics): create updateModuleSpecifier ts migration fn * move Update and writeUpdate to new file to avoid circular deps --- .../migration-utilities/BUILD.bazel | 5 +- .../schematics/migration-utilities/index.ts | 17 +---- .../typescript/import-operations.spec.ts | 67 +++++++++++++++++++ .../typescript/import-operations.ts | 31 +++++++++ .../schematics/migration-utilities/update.ts | 23 +++++++ 5 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 src/material/schematics/migration-utilities/typescript/import-operations.spec.ts create mode 100644 src/material/schematics/migration-utilities/typescript/import-operations.ts create mode 100644 src/material/schematics/migration-utilities/update.ts diff --git a/src/material/schematics/migration-utilities/BUILD.bazel b/src/material/schematics/migration-utilities/BUILD.bazel index c7cea1c774b5..4df0c478e56b 100644 --- a/src/material/schematics/migration-utilities/BUILD.bazel +++ b/src/material/schematics/migration-utilities/BUILD.bazel @@ -8,7 +8,9 @@ ts_library( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), - deps = [], + deps = [ + "@npm//typescript", + ], ) ts_library( @@ -18,6 +20,7 @@ ts_library( deps = [ ":migration-utilities", "@npm//@types/jasmine", + "@npm//typescript", ], ) diff --git a/src/material/schematics/migration-utilities/index.ts b/src/material/schematics/migration-utilities/index.ts index 680c4ec562f6..42111a99c45f 100644 --- a/src/material/schematics/migration-utilities/index.ts +++ b/src/material/schematics/migration-utilities/index.ts @@ -6,18 +6,5 @@ * found in the LICENSE file at https://angular.io/license */ -/** Stores the data needed to make a single update to a file. */ -export interface Update { - /** The start index of the location of the update. */ - offset: number; - - /** A function to be used to update the file content. */ - updateFn: (html: string) => string; -} - -/** Applies the updates to the given file content in reverse offset order. */ -export function writeUpdates(content: string, updates: Update[]): string { - updates.sort((a, b) => b.offset - a.offset); - updates.forEach(update => (content = update.updateFn(content))); - return content; -} +export {updateModuleSpecifier} from './typescript/import-operations'; +export {Update, writeUpdates} from './update'; diff --git a/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts b/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts new file mode 100644 index 000000000000..975435c37c46 --- /dev/null +++ b/src/material/schematics/migration-utilities/typescript/import-operations.spec.ts @@ -0,0 +1,67 @@ +import * as ts from 'typescript'; +import {updateModuleSpecifier} from './import-operations'; + +describe('import operations', () => { + describe('updateModuleSpecifier', () => { + function runUpdateModuleSpecifierTest( + description: string, + opts: {old: string; new: string}, + ): void { + const node = createNode(opts.old, ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration; + const update = updateModuleSpecifier(node!, {moduleSpecifier: 'new-module-name'}); + const newImport = update?.updateFn(opts.old); + expect(newImport).withContext(description).toBe(opts.new); + } + it('updates the module specifier of import declarations', () => { + runUpdateModuleSpecifierTest('default export', { + old: `import defaultExport from 'old-module-name';`, + new: `import defaultExport from 'new-module-name';`, + }); + runUpdateModuleSpecifierTest('namespace import', { + old: `import * as name from 'old-module-name';`, + new: `import * as name from 'new-module-name';`, + }); + runUpdateModuleSpecifierTest('named import', { + old: `import { export1 } from 'old-module-name';`, + new: `import { export1 } from 'new-module-name';`, + }); + runUpdateModuleSpecifierTest('aliased named import', { + old: `import { export1 as alias1 } from 'old-module-name';`, + new: `import { export1 as alias1 } from 'new-module-name';`, + }); + runUpdateModuleSpecifierTest('multiple named import', { + old: `import { export1, export2 } from 'old-module-name';`, + new: `import { export1, export2 } from 'new-module-name';`, + }); + runUpdateModuleSpecifierTest('multiple named import w/ alias', { + old: `import { export1, export2 as alias2 } from 'old-module-name';`, + new: `import { export1, export2 as alias2 } from 'new-module-name';`, + }); + }); + }); +}); + +function createSourceFile(text: string): ts.SourceFile { + return ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest); +} + +function visitNodes(node: ts.SourceFile | ts.Node, visitFn: (node: ts.Node) => void): void { + node.forEachChild(child => { + visitFn(child); + visitNodes(child, visitFn); + }); +} + +function getNodeByKind(file: ts.SourceFile, kind: ts.SyntaxKind): ts.Node | null { + let node: ts.Node | null = null; + visitNodes(file, (_node: ts.Node) => { + if (_node.kind === kind) { + node = _node; + } + }); + return node; +} + +function createNode(text: string, kind: ts.SyntaxKind): ts.Node | null { + return getNodeByKind(createSourceFile(text), kind); +} diff --git a/src/material/schematics/migration-utilities/typescript/import-operations.ts b/src/material/schematics/migration-utilities/typescript/import-operations.ts new file mode 100644 index 000000000000..df346f302440 --- /dev/null +++ b/src/material/schematics/migration-utilities/typescript/import-operations.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Update} from '../update'; +import * as ts from 'typescript'; + +/** Returns an Update that renames the module specifier of the given import declaration node. */ +export function updateModuleSpecifier( + node: ts.ImportDeclaration, + opts: { + moduleSpecifier: string; + }, +): Update { + const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral; + return { + 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) + ); + }, + }; +} diff --git a/src/material/schematics/migration-utilities/update.ts b/src/material/schematics/migration-utilities/update.ts new file mode 100644 index 000000000000..23b190f5b739 --- /dev/null +++ b/src/material/schematics/migration-utilities/update.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** Stores the data needed to make a single update to a file. */ +export interface Update { + /** The start index of the location of the update. */ + offset: number; + + /** A function to be used to update the file content. */ + updateFn: (text: string) => string; +} + +/** Applies the updates to the given file content in reverse offset order. */ +export function writeUpdates(content: string, updates: Update[]): string { + updates.sort((a, b) => b.offset - a.offset); + updates.forEach(update => (content = update.updateFn(content))); + return content; +}