diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts index d5ee9717bc790..23d3bd3ce1624 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts @@ -582,6 +582,19 @@ export function formatTemplate(tmpl: string, templateType: string): string { //
||
]*\/>)[^>]*>?/; + // regex for matching an attribute string that was left open at the endof a line + // so we can ensure we have the proper indent + //
+ const closeAttrDoubleRegex = /^\s*([^><]|\\")*"/; + const closeAttrSingleRegex = /^\s*([^><]|\\')*'/; + // regex for matching a self closing html element that has no /> // const selfClosingRegex = new RegExp(`^\\s*<(${selfClosingList}).+\\/?>`); @@ -620,6 +633,8 @@ export function formatTemplate(tmpl: string, templateType: string): string { let i18nDepth = 0; let inMigratedBlock = false; let inI18nBlock = false; + let inAttribute = false; + let isDoubleQuotes = false; for (let [index, line] of lines.entries()) { depth += [...line.matchAll(startMarkerRegex)].length - [...line.matchAll(endMarkerRegex)].length; @@ -633,7 +648,7 @@ export function formatTemplate(tmpl: string, templateType: string): string { lineWasMigrated = true; } if ((line.trim() === '' && index !== 0 && index !== lines.length - 1) && - (inMigratedBlock || lineWasMigrated) && !inI18nBlock) { + (inMigratedBlock || lineWasMigrated) && !inI18nBlock && !inAttribute) { // skip blank lines except if it's the first line or last line // this preserves leading and trailing spaces if they are already present continue; @@ -655,10 +670,25 @@ export function formatTemplate(tmpl: string, templateType: string): string { indent = indent.slice(2); } - const newLine = - inI18nBlock ? line : mindent + (line.trim() !== '' ? indent : '') + line.trim(); + // if a line ends in an unclosed attribute, we need to note that and close it later + if (!inAttribute && openAttrDoubleRegex.test(line)) { + inAttribute = true; + isDoubleQuotes = true; + } else if (!inAttribute && openAttrSingleRegex.test(line)) { + inAttribute = true; + isDoubleQuotes = false; + } + + const newLine = (inI18nBlock || inAttribute) ? + line : + mindent + (line.trim() !== '' ? indent : '') + line.trim(); formatted.push(newLine); + if ((inAttribute && isDoubleQuotes && closeAttrDoubleRegex.test(line)) || + (inAttribute && !isDoubleQuotes && closeAttrSingleRegex.test(line))) { + inAttribute = false; + } + // this matches any self closing element that actually has a /> if (closeMultiLineElRegex.test(line)) { // multi line self closing tag diff --git a/packages/core/schematics/test/control_flow_migration_spec.ts b/packages/core/schematics/test/control_flow_migration_spec.ts index e55a691d9bb9f..04c95d46ffe01 100644 --- a/packages/core/schematics/test/control_flow_migration_spec.ts +++ b/packages/core/schematics/test/control_flow_migration_spec.ts @@ -4755,6 +4755,83 @@ describe('control flow migration', () => { expect(actual).toBe(expected); }); + + it('should indent multi-line attribute strings to the right place', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgIf} from '@angular/common'; + + @Component({ + templateUrl: './comp.html' + }) + class Comp { + show = false; + } + `); + + writeFile('/comp.html', [ + `
show
`, + ``, + ` Content here`, + ``, + ].join('\n')); + + await runMigration(); + const actual = tree.readContent('/comp.html'); + const expected = [ + `@if (show) {`, + `
show
`, + `}`, + ``, + ` Content here`, + ``, + ].join('\n'); + + expect(actual).toBe(expected); + }); + + it('should indent multi-line attribute strings as single quotes to the right place', + async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgIf} from '@angular/common'; + + @Component({ + templateUrl: './comp.html' + }) + class Comp { + show = false; + } + `); + + writeFile('/comp.html', [ + `
show
`, + ``, + ` Content here`, + ``, + ].join('\n')); + + await runMigration(); + const actual = tree.readContent('/comp.html'); + const expected = [ + `@if (show) {`, + `
show
`, + `}`, + ``, + ` Content here`, + ``, + ].join('\n'); + + expect(actual).toBe(expected); + }); }); describe('imports', () => {