refactor(compiler): simplify visitor logic for attributes (#38899)
The logic for computing identifiers, specifically for bound attributes can be simplified by using the value span of the binding rather than the source span. PR Close #38899
This commit is contained in:
		
							parent
							
								
									dfbfabc052
								
							
						
					
					
						commit
						0c0c54d615
					
				| @ -97,15 +97,12 @@ class ExpressionVisitor extends RecursiveAstVisitor { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Get the location of the identifier of real interest.
 |     // The source span of the requested AST starts at a location that is offset from the expression.
 | ||||||
|     // The compiler's expression parser records the location of some expressions in a manner not
 |     const identifierStart = ast.sourceSpan.start - this.absoluteOffset; | ||||||
|     // useful to the indexer. For example, a `MethodCall` `foo(a, b)` will record the span of the
 |     if (!this.expressionStr.substring(identifierStart).startsWith(ast.name)) { | ||||||
|     // entire method call, but the indexer is interested only in the method identifier.
 |       throw new Error(`Impossible state: "${ast.name}" not found in "${ | ||||||
|     const localExpression = this.expressionStr.substr(ast.span.start); |           this.expressionStr}" at location ${identifierStart}`);
 | ||||||
|     if (!localExpression.includes(ast.name)) { |  | ||||||
|       throw new Error(`Impossible state: "${ast.name}" not found in "${localExpression}"`); |  | ||||||
|     } |     } | ||||||
|     const identifierStart = ast.span.start + localExpression.indexOf(ast.name); |  | ||||||
| 
 | 
 | ||||||
|     // Join the relative position of the expression within a node with the absolute position
 |     // Join the relative position of the expression within a node with the absolute position
 | ||||||
|     // of the node to get the absolute position of the expression in the source code.
 |     // of the node to get the absolute position of the expression in the source code.
 | ||||||
| @ -191,25 +188,14 @@ class TemplateVisitor extends TmplAstRecursiveVisitor { | |||||||
|     this.visitAll(template.references); |     this.visitAll(template.references); | ||||||
|   } |   } | ||||||
|   visitBoundAttribute(attribute: TmplAstBoundAttribute) { |   visitBoundAttribute(attribute: TmplAstBoundAttribute) { | ||||||
|     // A BoundAttribute's value (the parent AST) may have subexpressions (children ASTs) that have
 |     // If the bound attribute has no value, it cannot have any identifiers in the value expression.
 | ||||||
|     // recorded spans extending past the recorded span of the parent. The most common example of
 |     if (attribute.valueSpan === undefined) { | ||||||
|     // this is with `*ngFor`.
 |       return; | ||||||
|     // To resolve this, use the information on the BoundAttribute Template AST, which is always
 |     } | ||||||
|     // correct, to determine locations of identifiers in the expression.
 |  | ||||||
|     //
 |  | ||||||
|     // TODO(ayazhafiz): Remove this when https://github.com/angular/angular/pull/31813 lands.
 |  | ||||||
|     const attributeSrc = attribute.sourceSpan.toString(); |  | ||||||
|     const attributeAbsolutePosition = attribute.sourceSpan.start.offset; |  | ||||||
| 
 |  | ||||||
|     // Skip the bytes of the attribute name so that there are no collisions between the attribute
 |  | ||||||
|     // name and expression identifier names later.
 |  | ||||||
|     const nameSkipOffet = attributeSrc.indexOf(attribute.name) + attribute.name.length; |  | ||||||
|     const expressionSrc = attributeSrc.substring(nameSkipOffet); |  | ||||||
|     const expressionAbsolutePosition = attributeAbsolutePosition + nameSkipOffet; |  | ||||||
| 
 | 
 | ||||||
|     const identifiers = ExpressionVisitor.getIdentifiers( |     const identifiers = ExpressionVisitor.getIdentifiers( | ||||||
|         attribute.value, expressionSrc, expressionAbsolutePosition, this.boundTemplate, |         attribute.value, attribute.valueSpan.toString(), attribute.valueSpan.start.offset, | ||||||
|         this.targetToIdentifier.bind(this)); |         this.boundTemplate, this.targetToIdentifier.bind(this)); | ||||||
|     identifiers.forEach(id => this.identifiers.add(id)); |     identifiers.forEach(id => this.identifiers.add(id)); | ||||||
|   } |   } | ||||||
|   visitBoundEvent(attribute: TmplAstBoundEvent) { |   visitBoundEvent(attribute: TmplAstBoundEvent) { | ||||||
|  | |||||||
| @ -124,6 +124,20 @@ runInEachFileSystem(() => { | |||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       it('should handle bound attributes with no value', () => { | ||||||
|  |         const template = '<div [bar]></div>'; | ||||||
|  |         const refs = getTemplateIdentifiers(bind(template)); | ||||||
|  | 
 | ||||||
|  |         const refArr = Array.from(refs); | ||||||
|  |         expect(refArr).toEqual([{ | ||||||
|  |           name: 'div', | ||||||
|  |           kind: IdentifierKind.Element, | ||||||
|  |           span: new AbsoluteSourceSpan(1, 4), | ||||||
|  |           attributes: new Set(), | ||||||
|  |           usedDirectives: new Set(), | ||||||
|  |         }]); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|       it('should discover variables in bound attributes', () => { |       it('should discover variables in bound attributes', () => { | ||||||
|         const template = '<div #div [value]="div.innerText"></div>'; |         const template = '<div #div [value]="div.innerText"></div>'; | ||||||
|         const refs = getTemplateIdentifiers(bind(template)); |         const refs = getTemplateIdentifiers(bind(template)); | ||||||
| @ -189,6 +203,33 @@ runInEachFileSystem(() => { | |||||||
|           target: null, |           target: null, | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|  |       it('should discover properties in template expressions and resist collisions', () => { | ||||||
|  |         const template = '<div *ngFor="let foo of (foos ? foos : foos)"></div>'; | ||||||
|  |         const refs = getTemplateIdentifiers(bind(template)); | ||||||
|  | 
 | ||||||
|  |         const refArr = Array.from(refs); | ||||||
|  |         expect(refArr).toEqual(jasmine.arrayContaining([ | ||||||
|  |           { | ||||||
|  |             name: 'foos', | ||||||
|  |             kind: IdentifierKind.Property, | ||||||
|  |             span: new AbsoluteSourceSpan(25, 29), | ||||||
|  |             target: null, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'foos', | ||||||
|  |             kind: IdentifierKind.Property, | ||||||
|  |             span: new AbsoluteSourceSpan(32, 36), | ||||||
|  |             target: null, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'foos', | ||||||
|  |             kind: IdentifierKind.Property, | ||||||
|  |             span: new AbsoluteSourceSpan(39, 43), | ||||||
|  |             target: null, | ||||||
|  |           }, | ||||||
|  |         ])); | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('generates identifiers for PropertyWrites', () => { |     describe('generates identifiers for PropertyWrites', () => { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user