Skip to content

Commit

Permalink
fix(material/schematics): replace pre-existing attribute values (#25754)
Browse files Browse the repository at this point in the history
Fixes that the MDC migration schematic was always adding new attributes, instead of replacing the values of the ones that are already there.
  • Loading branch information
crisbeto authored Oct 5, 2022
1 parent cd591b5 commit 9d5f0f3
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ describe('#visitElements', () => {
});
expect(html).toBe('<a attr2="val2" attr1="val1"></a>');
});

it('should replace value of existing attribute', async () => {
runAddAttributeTest('<a attr="default"></a>', '<a attr="val"></a>');
});

it('should add value to existing attribute that does not have a value', async () => {
runAddAttributeTest('<a attr></a>', '<a attr="val"></a>');
});
});

it('should match indentation', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import * as compiler from '@angular/compiler';
import {
ParsedTemplate,
TmplAstElement,
TmplAstNode,
parseTemplate as parseTemplateUsingCompiler,
} from '@angular/compiler';

/**
* Traverses the given tree of nodes and runs the given callbacks for each Element node encountered.
Expand All @@ -20,14 +25,14 @@ import * as compiler from '@angular/compiler';
* @param postorderCallback A function that gets run for each Element node in a postorder traversal.
*/
export function visitElements(
nodes: compiler.TmplAstNode[],
preorderCallback: (node: compiler.TmplAstElement) => void = () => {},
postorderCallback: (node: compiler.TmplAstElement) => void = () => {},
nodes: TmplAstNode[],
preorderCallback: (node: TmplAstElement) => void = () => {},
postorderCallback: (node: TmplAstElement) => void = () => {},
): void {
nodes.reverse();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node instanceof compiler.TmplAstElement) {
if (node instanceof TmplAstElement) {
preorderCallback(node);
visitElements(node.children, preorderCallback, postorderCallback);
postorderCallback(node);
Expand All @@ -45,8 +50,8 @@ export function visitElements(
* @param filePath URL to use for source mapping of the parsed template
* @returns the updated template html.
*/
export function parseTemplate(template: string, templateUrl: string = ''): compiler.ParsedTemplate {
return compiler.parseTemplate(template, templateUrl, {
export function parseTemplate(template: string, templateUrl: string = ''): ParsedTemplate {
return parseTemplateUsingCompiler(template, templateUrl, {
preserveWhitespaces: true,
preserveLineEndings: true,
leadingTriviaChars: [],
Expand All @@ -61,7 +66,7 @@ export function parseTemplate(template: string, templateUrl: string = ''): compi
* @param tag A new tag name.
* @returns an updated html document.
*/
export function replaceStartTag(html: string, node: compiler.TmplAstElement, tag: string): string {
export function replaceStartTag(html: string, node: TmplAstElement, tag: string): string {
return replaceAt(html, node.startSourceSpan.start.offset + 1, node.name, tag);
}

Expand All @@ -73,7 +78,7 @@ export function replaceStartTag(html: string, node: compiler.TmplAstElement, tag
* @param tag A new tag name.
* @returns an updated html document.
*/
export function replaceEndTag(html: string, node: compiler.TmplAstElement, tag: string): string {
export function replaceEndTag(html: string, node: TmplAstElement, tag: string): string {
if (!node.endSourceSpan) {
return html;
}
Expand All @@ -91,10 +96,32 @@ export function replaceEndTag(html: string, node: compiler.TmplAstElement, tag:
*/
export function addAttribute(
html: string,
node: compiler.TmplAstElement,
node: TmplAstElement,
name: string,
value: string,
): string {
const existingAttr = node.attributes.find(currentAttr => currentAttr.name === name);

if (existingAttr) {
// If the attribute has a value already, replace it.
if (existingAttr.valueSpan) {
return (
html.slice(0, existingAttr.valueSpan.start.offset) +
value +
html.slice(existingAttr.valueSpan.end.offset)
);
} else if (existingAttr.keySpan) {
// Otherwise add a value to a value-less attribute. Note that the `keySpan` null check is
// only necessary for the compiler. Technically an attribute should always have a key.
return (
html.slice(0, existingAttr.keySpan.end.offset) +
`="${value}"` +
html.slice(existingAttr.keySpan.end.offset)
);
}
}

// Otherwise insert a new attribute.
const index = node.startSourceSpan.start.offset + node.name.length + 1;
const prefix = html.slice(0, index);
const suffix = html.slice(index);
Expand Down

0 comments on commit 9d5f0f3

Please sign in to comment.