310 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			310 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @license | ||
|  |  * Copyright Google Inc. 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 {ParseSourceSpan} from '../../src/parse_util'; | ||
|  | import * as t from '../../src/render3/r3_ast'; | ||
|  | import {parseR3 as parse} from './view/util'; | ||
|  | 
 | ||
|  | 
 | ||
|  | class R3AstSourceSpans implements t.Visitor<void> { | ||
|  |   result: any[] = []; | ||
|  | 
 | ||
|  |   visitElement(element: t.Element) { | ||
|  |     this.result.push([ | ||
|  |       'Element', humanizeSpan(element.sourceSpan), humanizeSpan(element.startSourceSpan), | ||
|  |       humanizeSpan(element.endSourceSpan) | ||
|  |     ]); | ||
|  |     this.visitAll([ | ||
|  |       element.attributes, | ||
|  |       element.inputs, | ||
|  |       element.outputs, | ||
|  |       element.references, | ||
|  |       element.children, | ||
|  |     ]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitTemplate(template: t.Template) { | ||
|  |     this.result.push([ | ||
|  |       'Template', humanizeSpan(template.sourceSpan), humanizeSpan(template.startSourceSpan), | ||
|  |       humanizeSpan(template.endSourceSpan) | ||
|  |     ]); | ||
|  |     this.visitAll([ | ||
|  |       template.attributes, | ||
|  |       template.inputs, | ||
|  |       template.outputs, | ||
|  |       template.templateAttrs, | ||
|  |       template.references, | ||
|  |       template.variables, | ||
|  |       template.children, | ||
|  |     ]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitContent(content: t.Content) { | ||
|  |     this.result.push(['Content', humanizeSpan(content.sourceSpan)]); | ||
|  |     t.visitAll(this, content.attributes); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitVariable(variable: t.Variable) { | ||
|  |     this.result.push( | ||
|  |         ['Variable', humanizeSpan(variable.sourceSpan), humanizeSpan(variable.valueSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitReference(reference: t.Reference) { | ||
|  |     this.result.push( | ||
|  |         ['Reference', humanizeSpan(reference.sourceSpan), humanizeSpan(reference.valueSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitTextAttribute(attribute: t.TextAttribute) { | ||
|  |     this.result.push( | ||
|  |         ['TextAttribute', humanizeSpan(attribute.sourceSpan), humanizeSpan(attribute.valueSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitBoundAttribute(attribute: t.BoundAttribute) { | ||
|  |     this.result.push( | ||
|  |         ['BoundAttribute', humanizeSpan(attribute.sourceSpan), humanizeSpan(attribute.valueSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitBoundEvent(event: t.BoundEvent) { | ||
|  |     this.result.push( | ||
|  |         ['BoundEvent', humanizeSpan(event.sourceSpan), humanizeSpan(event.handlerSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitText(text: t.Text) { this.result.push(['Text', humanizeSpan(text.sourceSpan)]); } | ||
|  | 
 | ||
|  |   visitBoundText(text: t.BoundText) { | ||
|  |     this.result.push(['BoundText', humanizeSpan(text.sourceSpan)]); | ||
|  |   } | ||
|  | 
 | ||
|  |   visitIcu(icu: t.Icu) { return null; } | ||
|  | 
 | ||
|  |   private visitAll(nodes: t.Node[][]) { nodes.forEach(node => t.visitAll(this, node)); } | ||
|  | } | ||
|  | 
 | ||
|  | function humanizeSpan(span: ParseSourceSpan | null | undefined): string { | ||
|  |   if (span === null || span === undefined) { | ||
|  |     return `<empty>`; | ||
|  |   } | ||
|  |   return `${span.start.offset}:${span.end.offset}`; | ||
|  | } | ||
|  | 
 | ||
|  | function expectFromHtml(html: string) { | ||
|  |   const res = parse(html); | ||
|  |   return expectFromR3Nodes(res.nodes); | ||
|  | } | ||
|  | 
 | ||
|  | function expectFromR3Nodes(nodes: t.Node[]) { | ||
|  |   const humanizer = new R3AstSourceSpans(); | ||
|  |   t.visitAll(humanizer, nodes); | ||
|  |   return expect(humanizer.result); | ||
|  | } | ||
|  | 
 | ||
|  | describe('R3 AST source spans', () => { | ||
|  |   describe('nodes without binding', () => { | ||
|  |     it('is correct for text nodes', () => { | ||
|  |       expectFromHtml('a').toEqual([ | ||
|  |         ['Text', '0:1'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for elements with attributes', () => { | ||
|  |       expectFromHtml('<div a="b"></div>').toEqual([ | ||
|  |         ['Element', '0:17', '0:11', '11:17'], | ||
|  |         ['TextAttribute', '5:10', '8:9'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for elements with attributes without value', () => { | ||
|  |       expectFromHtml('<div a></div>').toEqual([ | ||
|  |         ['Element', '0:13', '0:7', '7:13'], | ||
|  |         ['TextAttribute', '5:6', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('bound text nodes', () => { | ||
|  |     it('is correct for bound text nodes', () => { | ||
|  |       expectFromHtml('{{a}}').toEqual([ | ||
|  |         ['BoundText', '0:5'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('bound attributes', () => { | ||
|  |     it('is correct for bound properties', () => { | ||
|  |       expectFromHtml('<div [someProp]="v"></div>').toEqual([ | ||
|  |         ['Element', '0:26', '0:20', '20:26'], | ||
|  |         ['BoundAttribute', '5:19', '17:18'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound properties without value', () => { | ||
|  |       expectFromHtml('<div [someProp]></div>').toEqual([ | ||
|  |         ['Element', '0:22', '0:16', '16:22'], | ||
|  |         ['BoundAttribute', '5:15', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound properties via bind- ', () => { | ||
|  |       expectFromHtml('<div bind-prop="v"></div>').toEqual([ | ||
|  |         ['Element', '0:25', '0:19', '19:25'], | ||
|  |         ['BoundAttribute', '5:18', '16:17'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound properties via {{...}}', () => { | ||
|  |       expectFromHtml('<div prop="{{v}}"></div>').toEqual([ | ||
|  |         ['Element', '0:24', '0:18', '18:24'], | ||
|  |         ['BoundAttribute', '5:17', '11:16'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('templates', () => { | ||
|  |     it('is correct for * directives', () => { | ||
|  |       expectFromHtml('<div *ngIf></div>').toEqual([ | ||
|  |         ['Template', '0:11', '0:11', '11:17'], | ||
|  |         ['TextAttribute', '5:10', '<empty>'], | ||
|  |         ['Element', '0:17', '0:11', '11:17'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for <ng-template>', () => { | ||
|  |       expectFromHtml('<ng-template></ng-template>').toEqual([ | ||
|  |         ['Template', '0:13', '0:13', '13:27'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for reference via #...', () => { | ||
|  |       expectFromHtml('<ng-template #a></ng-template>').toEqual([ | ||
|  |         ['Template', '0:16', '0:16', '16:30'], | ||
|  |         ['Reference', '13:15', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for reference with name', () => { | ||
|  |       expectFromHtml('<ng-template #a="b"></ng-template>').toEqual([ | ||
|  |         ['Template', '0:20', '0:20', '20:34'], | ||
|  |         ['Reference', '13:19', '17:18'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for reference via ref-...', () => { | ||
|  |       expectFromHtml('<ng-template ref-a></ng-template>').toEqual([ | ||
|  |         ['Template', '0:19', '0:19', '19:33'], | ||
|  |         ['Reference', '13:18', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for variables via let-...', () => { | ||
|  |       expectFromHtml('<ng-template let-a="b"></ng-template>').toEqual([ | ||
|  |         ['Template', '0:23', '0:23', '23:37'], | ||
|  |         ['Variable', '13:22', '20:21'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for attributes', () => { | ||
|  |       expectFromHtml('<ng-template k1="v1"></ng-template>').toEqual([ | ||
|  |         ['Template', '0:21', '0:21', '21:35'], | ||
|  |         ['TextAttribute', '13:20', '17:19'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound attributes', () => { | ||
|  |       expectFromHtml('<ng-template [k1]="v1"></ng-template>').toEqual([ | ||
|  |         ['Template', '0:23', '0:23', '23:37'], | ||
|  |         ['BoundAttribute', '13:22', '19:21'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   // TODO(joost): improve spans of nodes extracted from macrosyntax
 | ||
|  |   describe('inline templates', () => { | ||
|  |     it('is correct for attribute and bound attributes', () => { | ||
|  |       expectFromHtml('<div *ngFor="item of items"></div>').toEqual([ | ||
|  |         ['Template', '0:28', '0:28', '28:34'], | ||
|  |         ['BoundAttribute', '5:27', '<empty>'], | ||
|  |         ['BoundAttribute', '5:27', '<empty>'], | ||
|  |         ['Element', '0:34', '0:28', '28:34'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for variables via let ...', () => { | ||
|  |       expectFromHtml('<div *ngIf="let a=b"></div>').toEqual([ | ||
|  |         ['Template', '0:21', '0:21', '21:27'], | ||
|  |         ['TextAttribute', '5:20', '<empty>'], | ||
|  |         ['Variable', '5:20', '<empty>'], | ||
|  |         ['Element', '0:27', '0:21', '21:27'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for variables via as ...', () => { | ||
|  |       expectFromHtml('<div *ngIf="expr as local"></div>').toEqual([ | ||
|  |         ['Template', '0:27', '0:27', '27:33'], | ||
|  |         ['BoundAttribute', '5:26', '<empty>'], | ||
|  |         ['Variable', '5:26', '<empty>'], | ||
|  |         ['Element', '0:33', '0:27', '27:33'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('events', () => { | ||
|  |     it('is correct for event names case sensitive', () => { | ||
|  |       expectFromHtml('<div (someEvent)="v"></div>').toEqual([ | ||
|  |         ['Element', '0:27', '0:21', '21:27'], | ||
|  |         ['BoundEvent', '5:20', '18:19'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound events via on-', () => { | ||
|  |       expectFromHtml('<div on-event="v"></div>').toEqual([ | ||
|  |         ['Element', '0:24', '0:18', '18:24'], | ||
|  |         ['BoundEvent', '5:17', '15:16'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound events and properties via [(...)]', () => { | ||
|  |       expectFromHtml('<div [(prop)]="v"></div>').toEqual([ | ||
|  |         ['Element', '0:24', '0:18', '18:24'], | ||
|  |         ['BoundAttribute', '5:17', '15:16'], | ||
|  |         ['BoundEvent', '5:17', '15:16'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for bound events and properties via bindon-', () => { | ||
|  |       expectFromHtml('<div bindon-prop="v"></div>').toEqual([ | ||
|  |         ['Element', '0:27', '0:21', '21:27'], | ||
|  |         ['BoundAttribute', '5:20', '18:19'], | ||
|  |         ['BoundEvent', '5:20', '18:19'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('references', () => { | ||
|  |     it('is correct for references via #...', () => { | ||
|  |       expectFromHtml('<div #a></div>').toEqual([ | ||
|  |         ['Element', '0:14', '0:8', '8:14'], | ||
|  |         ['Reference', '5:7', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for references with name', () => { | ||
|  |       expectFromHtml('<div #a="b"></div>').toEqual([ | ||
|  |         ['Element', '0:18', '0:12', '12:18'], | ||
|  |         ['Reference', '5:11', '9:10'], | ||
|  |       ]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is correct for references via ref-', () => { | ||
|  |       expectFromHtml('<div ref-a></div>').toEqual([ | ||
|  |         ['Element', '0:17', '0:11', '11:17'], | ||
|  |         ['Reference', '5:10', '<empty>'], | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | }); |