This commit removes the `NullAstVisitor` and `visitAstChildren` exported from `packages/compiler/src/expression_parser/ast.ts` because they contain duplicate and buggy implementation, and their use cases could be sufficiently covered by `RecursiveAstVisitor` if the latter implements the `visit` method. This use case is only needed in the language service. With this change, any visitor that extends `RecursiveAstVisitor` could just define their own `visit` function and the parent class will behave correctly. A bit of historical context: In language service, we need a way to tranverse the expression AST in a selective manner based on where the user's cursor is. This means we need a "filtering" function to decide which node to visit and which node to not visit. Instead of refactoring `RecursiveAstVisitor` to support this, `visitAstChildren` was created. `visitAstChildren` duplicates the implementation of `RecursiveAstVisitor`, but introduced some bugs along the way. For example, in `visitKeyedWrite`, it visits ``` obj -> key -> obj ``` instead of ``` obj -> key -> value ``` Moreover, because of the following line ``` visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context); ``` `visitAstChildren` visits every node *twice*. PR Close #35619
		
			
				
	
	
		
			43 lines
		
	
	
		
			1.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			43 lines
		
	
	
		
			1.5 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 {AST, Lexer, Parser, RecursiveAstVisitor} from '@angular/compiler';
 | |
| import {ImplicitReceiver, MethodCall, PropertyRead} from '@angular/compiler/src/compiler';
 | |
| 
 | |
| describe('RecursiveAstVisitor', () => {
 | |
|   it('should visit every node', () => {
 | |
|     const parser = new Parser(new Lexer());
 | |
|     const ast = parser.parseBinding('x.y()', null /* location */, 0 /* absoluteOffset */);
 | |
|     const visitor = new Visitor();
 | |
|     const path: AST[] = [];
 | |
|     visitor.visit(ast.ast, path);
 | |
|     // If the visitor method of RecursiveAstVisitor is implemented correctly,
 | |
|     // then we should have collected the full path from root to leaf.
 | |
|     expect(path.length).toBe(3);
 | |
|     const [methodCall, propertyRead, implicitReceiver] = path;
 | |
|     expectType(methodCall, MethodCall);
 | |
|     expectType(propertyRead, PropertyRead);
 | |
|     expectType(implicitReceiver, ImplicitReceiver);
 | |
|     expect(methodCall.name).toBe('y');
 | |
|     expect(methodCall.args).toEqual([]);
 | |
|     expect(propertyRead.name).toBe('x');
 | |
|   });
 | |
| });
 | |
| 
 | |
| class Visitor extends RecursiveAstVisitor {
 | |
|   visit(node: AST, path: AST[]) {
 | |
|     path.push(node);
 | |
|     node.visit(this, path);
 | |
|   }
 | |
| }
 | |
| 
 | |
| type Newable = new (...args: any) => any;
 | |
| function expectType<T extends Newable>(val: any, t: T): asserts val is InstanceType<T> {
 | |
|   expect(val instanceof t).toBe(true, `expect ${val.constructor.name} to be ${t.name}`);
 | |
| }
 |