Skip to content

Commit

Permalink
feat(material/schematics): create updateModuleSpecifier ts migration …
Browse files Browse the repository at this point in the history
…fn (#25128)

* move Update and writeUpdate to new file
  to avoid circular deps
  • Loading branch information
wagnermaciel authored and mmalerba committed Jul 15, 2022
1 parent 62607c2 commit c682965
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 16 deletions.
5 changes: 4 additions & 1 deletion src/material/schematics/migration-utilities/BUILD.bazel
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
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';
@@ -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);
}
@@ -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
@@ -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;
}

0 comments on commit c682965

Please sign in to comment.