@@ -10,7 +10,7 @@ import {HtmlParser, Node, ParseTreeResult, visitAll} from '@angular/compiler';
1010import { dirname , join } from 'path' ;
1111import ts from 'typescript' ;
1212
13- import { AnalyzedFile , boundngif , CaseCollector , ElementCollector , ElementToMigrate , MigrateError , nakedngif , ngfor , ngif , ngswitch , Result , Template } from './types' ;
13+ import { AnalyzedFile , boundcase , boundngif , ElementCollector , ElementToMigrate , MigrateError , nakedcase , nakeddefault , nakedngif , ngfor , ngif , ngswitch , Result , switchcase , switchdefault , Template } from './types' ;
1414
1515/**
1616 * Analyzes a source file to find file that need to be migrated and the text ranges within them.
@@ -134,10 +134,12 @@ export function migrateTemplate(template: string): {migrated: string|null, error
134134
135135 // start from top of template
136136 // loop through each element
137+ visitor . elements [ 0 ] . hasLineBreaks = hasLineBreaks ;
137138 let prevElEnd = visitor . elements [ 0 ] ?. el . sourceSpan . end . offset ?? result . length - 1 ;
138139 let nestedQueue : number [ ] = [ prevElEnd ] ;
139140 for ( let i = 1 ; i < visitor . elements . length ; i ++ ) {
140141 let currEl = visitor . elements [ i ] ;
142+ currEl . hasLineBreaks = hasLineBreaks ;
141143 currEl . nestCount = getNestedCount ( currEl , nestedQueue ) ;
142144 if ( currEl . el . sourceSpan . end . offset !== nestedQueue [ nestedQueue . length - 1 ] ) {
143145 nestedQueue . push ( currEl . el . sourceSpan . end . offset ) ;
@@ -165,19 +167,32 @@ export function migrateTemplate(template: string): {migrated: string|null, error
165167 // these are all migratable nodes
166168 if ( el . attr . name === ngif || el . attr . name === nakedngif || el . attr . name === boundngif ) {
167169 try {
168- migrateResult = migrateNgIf ( el , visitor . templates , result , offset , hasLineBreaks ) ;
170+ migrateResult = migrateNgIf ( el , visitor . templates , result , offset ) ;
169171 } catch ( error : unknown ) {
170172 errors . push ( { type : ngif , error} ) ;
171173 }
172174 } else if ( el . attr . name === ngfor ) {
173175 try {
174- migrateResult = migrateNgFor ( el , result , offset , hasLineBreaks ) ;
176+ migrateResult = migrateNgFor ( el , result , offset ) ;
175177 } catch ( error : unknown ) {
176178 errors . push ( { type : ngfor , error} ) ;
177179 }
178180 } else if ( el . attr . name === ngswitch ) {
179181 try {
180- migrateResult = migrateNgSwitch ( el , result , offset , hasLineBreaks ) ;
182+ migrateResult = migrateNgSwitch ( el , result , offset ) ;
183+ } catch ( error : unknown ) {
184+ errors . push ( { type : ngswitch , error} ) ;
185+ }
186+ } else if (
187+ el . attr . name === switchcase || el . attr . name === nakedcase || el . attr . name === boundcase ) {
188+ try {
189+ migrateResult = migrateNgSwitchCase ( el , result , offset ) ;
190+ } catch ( error : unknown ) {
191+ errors . push ( { type : ngswitch , error} ) ;
192+ }
193+ } else if ( el . attr . name === switchdefault || el . attr . name === nakeddefault ) {
194+ try {
195+ migrateResult = migrateNgSwitchDefault ( el , result , offset ) ;
181196 } catch ( error : unknown ) {
182197 errors . push ( { type : ngswitch , error} ) ;
183198 }
@@ -198,26 +213,24 @@ export function migrateTemplate(template: string): {migrated: string|null, error
198213}
199214
200215function migrateNgIf (
201- etm : ElementToMigrate , ngTemplates : Map < string , Template > , tmpl : string , offset : number ,
202- hasLineBreaks : boolean ) : Result {
216+ etm : ElementToMigrate , ngTemplates : Map < string , Template > , tmpl : string ,
217+ offset : number ) : Result {
203218 const matchThen = etm . attr . value . match ( / ; \s + t h e n / gm) ;
204219 const matchElse = etm . attr . value . match ( / ; \s + e l s e / gm) ;
205220
206221 if ( matchThen && matchThen . length > 0 ) {
207- return buildIfThenElseBlock (
208- etm , ngTemplates , tmpl , matchThen [ 0 ] , matchElse ! [ 0 ] , offset , hasLineBreaks ) ;
222+ return buildIfThenElseBlock ( etm , ngTemplates , tmpl , matchThen [ 0 ] , matchElse ! [ 0 ] , offset ) ;
209223 } else if ( matchElse && matchElse . length > 0 ) {
210224 // just else
211- return buildIfElseBlock ( etm , ngTemplates , tmpl , matchElse [ 0 ] , offset , hasLineBreaks ) ;
225+ return buildIfElseBlock ( etm , ngTemplates , tmpl , matchElse [ 0 ] , offset ) ;
212226 }
213227
214- return buildIfBlock ( etm , tmpl , offset , hasLineBreaks ) ;
228+ return buildIfBlock ( etm , tmpl , offset ) ;
215229}
216230
217- function buildIfBlock (
218- etm : ElementToMigrate , tmpl : string , offset : number , hasLineBreaks : boolean ) : Result {
231+ function buildIfBlock ( etm : ElementToMigrate , tmpl : string , offset : number ) : Result {
219232 // includes the mandatory semicolon before as
220- const lbString = hasLineBreaks ? lb : '' ;
233+ const lbString = etm . hasLineBreaks ? lb : '' ;
221234 const condition = etm . attr . value . replace ( ' as ' , '; as ' ) ;
222235
223236 const originals = getOriginals ( etm , tmpl , offset ) ;
@@ -239,9 +252,9 @@ function buildIfBlock(
239252
240253function buildIfElseBlock (
241254 etm : ElementToMigrate , ngTemplates : Map < string , Template > , tmpl : string , elseString : string ,
242- offset : number , hasLineBreaks : boolean ) : Result {
255+ offset : number ) : Result {
243256 // includes the mandatory semicolon before as
244- const lbString = hasLineBreaks ? lb : '' ;
257+ const lbString = etm . hasLineBreaks ? lb : '' ;
245258 const condition = etm . getCondition ( elseString ) . replace ( ' as ' , '; as ' ) ;
246259
247260 const originals = getOriginals ( etm , tmpl , offset ) ;
@@ -270,9 +283,9 @@ function buildIfElseBlock(
270283
271284function buildIfThenElseBlock (
272285 etm : ElementToMigrate , ngTemplates : Map < string , Template > , tmpl : string , thenString : string ,
273- elseString : string , offset : number , hasLineBreaks : boolean ) : Result {
286+ elseString : string , offset : number ) : Result {
274287 const condition = etm . getCondition ( thenString ) . replace ( ' as ' , '; as ' ) ;
275- const lbString = hasLineBreaks ? lb : '' ;
288+ const lbString = etm . hasLineBreaks ? lb : '' ;
276289
277290 const originals = getOriginals ( etm , tmpl , offset ) ;
278291
@@ -302,13 +315,12 @@ function buildIfThenElseBlock(
302315 return { tmpl : updatedTmpl , offsets : { pre, post} } ;
303316}
304317
305- function migrateNgFor (
306- etm : ElementToMigrate , tmpl : string , offset : number , hasLineBreaks : boolean ) : Result {
318+ function migrateNgFor ( etm : ElementToMigrate , tmpl : string , offset : number ) : Result {
307319 const aliasWithEqualRegexp = / = \s + ( c o u n t | i n d e x | f i r s t | l a s t | e v e n | o d d ) / gm;
308320 const aliasWithAsRegexp = / ( c o u n t | i n d e x | f i r s t | l a s t | e v e n | o d d ) \s + a s / gm;
309321 const aliases = [ ] ;
310- const lbString = hasLineBreaks ? lb : '' ;
311- const lbSpaces = hasLineBreaks ? `${ lb } ` : '' ;
322+ const lbString = etm . hasLineBreaks ? lb : '' ;
323+ const lbSpaces = etm . hasLineBreaks ? `${ lb } ` : '' ;
312324 const parts = etm . attr . value . split ( ';' ) ;
313325
314326 const originals = getOriginals ( etm , tmpl , offset ) ;
@@ -380,7 +392,8 @@ function getOriginals(
380392
381393function getMainBlock ( etm : ElementToMigrate , tmpl : string , offset : number ) :
382394 { start : string , middle : string , end : string } {
383- if ( etm . el . name === 'ng-container' && etm . el . attrs . length === 1 ) {
395+ if ( ( etm . el . name === 'ng-container' || etm . el . name === 'ng-template' ) &&
396+ etm . el . attrs . length === 1 ) {
384397 // this is the case where we're migrating and there's no need to keep the ng-container
385398 const childStart = etm . el . children [ 0 ] . sourceSpan . start . offset - offset ;
386399 const childEnd = etm . el . children [ etm . el . children . length - 1 ] . sourceSpan . end . offset - offset ;
@@ -389,7 +402,10 @@ function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number):
389402 }
390403
391404 const attrStart = etm . attr . keySpan ! . start . offset - 1 - offset ;
392- const valEnd = etm . attr . valueSpan ! . end . offset + 1 - offset ;
405+ const valEnd =
406+ ( etm . attr . valueSpan ? ( etm . attr . valueSpan . end . offset + 1 ) : etm . attr . keySpan ! . end . offset ) -
407+ offset ;
408+
393409 let childStart = valEnd ;
394410 let childEnd = valEnd ;
395411
@@ -400,83 +416,80 @@ function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number):
400416
401417 let start = tmpl . slice ( etm . start ( offset ) , attrStart ) ;
402418 start += tmpl . slice ( valEnd , childStart ) ;
419+
403420 const middle = tmpl . slice ( childStart , childEnd ) ;
404421 const end = tmpl . slice ( childEnd , etm . end ( offset ) ) ;
405422
406423 return { start, middle, end} ;
407424}
408425
409- function migrateNgSwitch (
410- etm : ElementToMigrate , tmpl : string , offset : number , hasLineBreaks : boolean ) : Result {
426+ function migrateNgSwitch ( etm : ElementToMigrate , tmpl : string , offset : number ) : Result {
427+ const lbString = etm . hasLineBreaks ? lb : '' ;
411428 const condition = etm . attr . value ;
412- const startBlock = `@switch (${ condition } ) {` ;
413- const lbString = hasLineBreaks ? lb : '' ;
414429
415- const { openTag, closeTag, children} = getSwitchBlockElements ( etm , tmpl , offset ) ;
416- const cases = getSwitchCases ( children , tmpl , offset , hasLineBreaks ) ;
417- const switchBlock = openTag + startBlock + cases . join ( '' ) + `${ lbString } }` + closeTag ;
430+ const originals = getOriginals ( etm , tmpl , offset ) ;
431+
432+ const { start, middle, end} = getMainBlock ( etm , tmpl , offset ) ;
433+ const startBlock = `${ start } ${ lbString } @switch (${ condition } ) {` ;
434+ const endBlock = `}${ lbString } ${ end } ` ;
435+
436+ const switchBlock = startBlock + middle + endBlock ;
418437 const updatedTmpl = tmpl . slice ( 0 , etm . start ( offset ) ) + switchBlock + tmpl . slice ( etm . end ( offset ) ) ;
419- const pre = etm . length ( ) - switchBlock . length ;
420438
421- return { tmpl : updatedTmpl , offsets : { pre, post : 0 } } ;
422- }
439+ // this should be the difference between the starting element up to the start of the closing
440+ // element and the mainblock sans }
441+ const pre = originals . start . length - startBlock . length ;
442+ const post = originals . end . length - endBlock . length ;
423443
424- function getSwitchBlockElements ( etm : ElementToMigrate , tmpl : string , offset : number ) {
425- const attrStart = etm . attr . keySpan ! . start . offset - 1 - offset ;
426- const valEnd = etm . attr . valueSpan ! . end . offset + 1 - offset ;
427- const childStart = etm . el . children [ 0 ] . sourceSpan . start . offset - offset ;
428- const childEnd = etm . el . children [ etm . el . children . length - 1 ] . sourceSpan . end . offset - offset ;
429- let openTag = ( etm . el . name === 'ng-container' ) ?
430- '' :
431- tmpl . slice ( etm . start ( offset ) , attrStart ) + tmpl . slice ( valEnd , childStart ) ;
432- if ( tmpl . slice ( childStart , childStart + 1 ) === lb ) {
433- openTag += lb ;
434- }
435- let closeTag = ( etm . el . name === 'ng-container' ) ? '' : tmpl . slice ( childEnd , etm . end ( offset ) ) ;
436- if ( tmpl . slice ( childEnd - 1 , childEnd ) === lb ) {
437- closeTag = lb + closeTag ;
438- }
439- return {
440- openTag,
441- closeTag,
442- children : etm . el . children ,
443- } ;
444+ return { tmpl : updatedTmpl , offsets : { pre, post} } ;
444445}
445446
446- function getSwitchCases ( children : Node [ ] , tmpl : string , offset : number , hasLineBreaks : boolean ) {
447- const collector = new CaseCollector ( ) ;
448- visitAll ( collector , children ) ;
449- return collector . elements . map ( etm => getSwitchCaseBlock ( etm , tmpl , offset , hasLineBreaks ) ) ;
447+ function migrateNgSwitchCase ( etm : ElementToMigrate , tmpl : string , offset : number ) : Result {
448+ // includes the mandatory semicolon before as
449+ const lbString = etm . hasLineBreaks ? lb : '' ;
450+ const lbSpaces = etm . hasLineBreaks ? ' ' : '' ;
451+ const leadingSpace = etm . hasLineBreaks ? '' : ' ' ;
452+ const condition = etm . attr . value ;
453+
454+ const originals = getOriginals ( etm , tmpl , offset ) ;
455+
456+ const { start, middle, end} = getMainBlock ( etm , tmpl , offset ) ;
457+ const startBlock =
458+ `${ leadingSpace } @case (${ condition } ) {${ leadingSpace } ${ lbString } ${ lbSpaces } ${ start } ` ;
459+ const endBlock = `${ end } ${ lbString } ${ leadingSpace } }` ;
460+
461+ const defaultBlock = startBlock + middle + endBlock ;
462+ const updatedTmpl = tmpl . slice ( 0 , etm . start ( offset ) ) + defaultBlock + tmpl . slice ( etm . end ( offset ) ) ;
463+
464+ // this should be the difference between the starting element up to the start of the closing
465+ // element and the mainblock sans }
466+ const pre = originals . start . length - startBlock . length ;
467+ const post = originals . end . length - endBlock . length ;
468+
469+ return { tmpl : updatedTmpl , offsets : { pre, post} } ;
450470}
451471
452- function getSwitchCaseBlock (
453- etm : ElementToMigrate , tmpl : string , offset : number , hasLineBreaks : boolean ) : string {
454- let elStart = etm . el . sourceSpan ?. start . offset - offset ;
455- let elEnd = etm . el . sourceSpan ?. end . offset - offset ;
456- const lbString = hasLineBreaks ? '\n ' : ' ' ;
457- const lbSpaces = hasLineBreaks ? ' ' : '' ;
458- let shift = 0 ;
472+ function migrateNgSwitchDefault ( etm : ElementToMigrate , tmpl : string , offset : number ) : Result {
473+ // includes the mandatory semicolon before as
474+ const lbString = etm . hasLineBreaks ? lb : '' ;
475+ const lbSpaces = etm . hasLineBreaks ? ' ' : '' ;
476+ const leadingSpace = etm . hasLineBreaks ? '' : ' ' ;
459477
460- if ( ( etm . el . name === 'ng-container' || etm . el . name === 'ng-template' ) &&
461- etm . el . attrs . length === 1 ) {
462- // no need to keep the containers
463- elStart = etm . el . children [ 0 ] . sourceSpan . start . offset - offset ;
464- elEnd = etm . el . children [ etm . el . children . length - 1 ] . sourceSpan . end . offset - offset ;
465- // account for the `>` that isn't needed
466- shift += 1 ;
467- }
478+ const originals = getOriginals ( etm , tmpl , offset ) ;
468479
469- const attrStart = etm . attr . keySpan ! . start . offset - 1 - offset + shift ;
470- // ngSwitchDefault case has no valueSpan and relies on the end of the key
471- if ( etm . attr . name === '*ngSwitchDefault' || etm . attr . name === 'ngSwitchDefault' ) {
472- const attrEnd = etm . attr . keySpan ! . end . offset - offset + shift ;
473- return `${ lbString } @default {${ lbString } ${ lbSpaces } ${
474- tmpl . slice ( elStart , attrStart ) + tmpl . slice ( attrEnd , elEnd ) } ${ lbString } }`;
475- }
476- // ngSwitchCase has a valueSpan
477- let valEnd = etm . attr . valueSpan ! . end . offset + 1 - offset + shift ;
478- return `${ lbString } @case (${ etm . attr . value } ) {${ lbString } ${ lbSpaces } ${
479- tmpl . slice ( elStart , attrStart ) + tmpl . slice ( valEnd , elEnd ) } ${ lbString } }`;
480+ const { start, middle, end} = getMainBlock ( etm , tmpl , offset ) ;
481+ const startBlock = `${ leadingSpace } @default {${ leadingSpace } ${ lbString } ${ lbSpaces } ${ start } ` ;
482+ const endBlock = `${ end } ${ lbString } ${ leadingSpace } }` ;
483+
484+ const defaultBlock = startBlock + middle + endBlock ;
485+ const updatedTmpl = tmpl . slice ( 0 , etm . start ( offset ) ) + defaultBlock + tmpl . slice ( etm . end ( offset ) ) ;
486+
487+ // this should be the difference between the starting element up to the start of the closing
488+ // element and the mainblock sans }
489+ const pre = originals . start . length - startBlock . length ;
490+ const post = originals . end . length - endBlock . length ;
491+
492+ return { tmpl : updatedTmpl , offsets : { pre, post} } ;
480493}
481494
482495/** Executes a callback on each class declaration in a file. */
0 commit comments