Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(material/schematics): create updateModuleSpecifier ts migrat… #25128

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/material/schematics/migration-utilities/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ ts_library(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
deps = [],
deps = [
"@npm//typescript",
],
)

ts_library(
Expand All @@ -18,6 +20,7 @@ ts_library(
deps = [
":migration-utilities",
"@npm//@types/jasmine",
"@npm//typescript",
],
)

Expand Down
17 changes: 2 additions & 15 deletions src/material/schematics/migration-utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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)
);
},
};
}
23 changes: 23 additions & 0 deletions src/material/schematics/migration-utilities/update.ts
Original file line number Diff line number Diff line change
@@ -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;
}