| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 17:42:42 +08:00
										 |  |  | import {AST, AstPath as AstPathBase, ASTWithName, ASTWithSource, Interpolation, RecursiveAstVisitor} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-13 14:26:58 -08:00
										 |  |  | import {AstType} from './expression_type'; | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  | import {BuiltinType, Span, Symbol, SymbolTable, TemplateSource} from './types'; | 
					
						
							| 
									
										
										
										
											2020-06-09 17:42:42 +08:00
										 |  |  | import {inSpan, isNarrower} from './utils'; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | type AstPath = AstPathBase<AST>; | 
					
						
							| 
									
										
										
										
											2016-12-13 11:20:45 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | function findAstAt(ast: AST, position: number, excludeEmpty: boolean = false): AstPath { | 
					
						
							|  |  |  |   const path: AST[] = []; | 
					
						
							| 
									
										
											  
											
												refactor(compiler): Remove NullAstVisitor and visitAstChildren (#35619)
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
											
										 
											2020-02-21 10:20:52 -08:00
										 |  |  |   const visitor = new class extends RecursiveAstVisitor { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     visit(ast: AST) { | 
					
						
							| 
									
										
										
										
											2019-10-24 19:07:04 -04:00
										 |  |  |       if ((!excludeEmpty || ast.sourceSpan.start < ast.sourceSpan.end) && | 
					
						
							|  |  |  |           inSpan(position, ast.sourceSpan)) { | 
					
						
							| 
									
										
										
										
											2020-06-09 17:42:42 +08:00
										 |  |  |         const isNotNarrower = path.length && !isNarrower(ast.span, path[path.length - 1].span); | 
					
						
							|  |  |  |         if (!isNotNarrower) { | 
					
						
							|  |  |  |           path.push(ast); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
											  
											
												refactor(compiler): Remove NullAstVisitor and visitAstChildren (#35619)
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
											
										 
											2020-02-21 10:20:52 -08:00
										 |  |  |         ast.visit(this); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // We never care about the ASTWithSource node and its visit() method calls its ast's visit so
 | 
					
						
							|  |  |  |   // the visit() method above would never see it.
 | 
					
						
							|  |  |  |   if (ast instanceof ASTWithSource) { | 
					
						
							|  |  |  |     ast = ast.ast; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 17:42:42 +08:00
										 |  |  |   // `Interpolation` is useless here except the `expressions` of it.
 | 
					
						
							|  |  |  |   if (ast instanceof Interpolation) { | 
					
						
							|  |  |  |     ast = ast.expressions.filter((_ast: AST) => inSpan(position, _ast.sourceSpan))[0]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ast) { | 
					
						
							|  |  |  |     visitor.visit(ast); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return new AstPathBase<AST>(path, position); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getExpressionCompletions( | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |     scope: SymbolTable, ast: AST, position: number, templateInfo: TemplateSource): Symbol[]| | 
					
						
							|  |  |  |     undefined { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   const path = findAstAt(ast, position); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   if (path.empty) return undefined; | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   const tail = path.tail!; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   let result: SymbolTable|undefined = scope; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |   function getType(ast: AST): Symbol { | 
					
						
							|  |  |  |     return new AstType(scope, templateInfo.query, {}, templateInfo.source).getType(ast); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // If the completion request is in a not in a pipe or property access then the global scope
 | 
					
						
							|  |  |  |   // (that is the scope of the implicit receiver) is the right scope as the user is typing the
 | 
					
						
							|  |  |  |   // beginning of an expression.
 | 
					
						
							|  |  |  |   tail.visit({ | 
					
						
							| 
									
										
										
										
											2020-07-04 01:52:40 +02:00
										 |  |  |     visitUnary(_ast) {}, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitBinary(_ast) {}, | 
					
						
							|  |  |  |     visitChain(_ast) {}, | 
					
						
							|  |  |  |     visitConditional(_ast) {}, | 
					
						
							|  |  |  |     visitFunctionCall(_ast) {}, | 
					
						
							|  |  |  |     visitImplicitReceiver(_ast) {}, | 
					
						
							|  |  |  |     visitInterpolation(_ast) { | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       result = undefined; | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitKeyedRead(_ast) {}, | 
					
						
							|  |  |  |     visitKeyedWrite(_ast) {}, | 
					
						
							|  |  |  |     visitLiteralArray(_ast) {}, | 
					
						
							|  |  |  |     visitLiteralMap(_ast) {}, | 
					
						
							| 
									
										
										
										
											2020-07-08 16:29:55 +08:00
										 |  |  |     visitLiteralPrimitive(ast) { | 
					
						
							|  |  |  |       // The type `LiteralPrimitive` include the `ERROR`, and it's wrapped as `string`.
 | 
					
						
							|  |  |  |       // packages/compiler/src/template_parser/binding_parser.ts#L308
 | 
					
						
							|  |  |  |       // So exclude the `ERROR` here.
 | 
					
						
							|  |  |  |       if (typeof ast.value === 'string' && | 
					
						
							|  |  |  |           ast.value === | 
					
						
							|  |  |  |               templateInfo.source.slice(ast.sourceSpan.start + 1, ast.sourceSpan.end - 1)) { | 
					
						
							|  |  |  |         result = undefined; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitMethodCall(_ast) {}, | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     visitPipe(ast) { | 
					
						
							|  |  |  |       if (position >= ast.exp.span.end && | 
					
						
							|  |  |  |           (!ast.args || !ast.args.length || position < (<AST>ast.args[0]).span.start)) { | 
					
						
							|  |  |  |         // We are in a position a pipe name is expected.
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |         result = templateInfo.query.getPipes(); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitPrefixNot(_ast) {}, | 
					
						
							|  |  |  |     visitNonNullAssert(_ast) {}, | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     visitPropertyRead(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       result = receiverType ? receiverType.members() : scope; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     visitPropertyWrite(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       result = receiverType ? receiverType.members() : scope; | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitQuote(_ast) { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       // For a quote, return the members of any (if there are any).
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |       result = templateInfo.query.getBuiltinType(BuiltinType.Any).members(); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |     visitSafeMethodCall(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       result = receiverType ? receiverType.members() : scope; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     visitSafePropertyRead(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       result = receiverType ? receiverType.members() : scope; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return result && result.values(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-09 22:16:08 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Retrieves the expression symbol at a particular position in a template. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param scope symbols in scope of the template | 
					
						
							|  |  |  |  * @param ast template AST | 
					
						
							|  |  |  |  * @param position absolute location in template to retrieve symbol at | 
					
						
							|  |  |  |  * @param query type symbol query for the template scope | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | export function getExpressionSymbol( | 
					
						
							|  |  |  |     scope: SymbolTable, ast: AST, position: number, | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |     templateInfo: TemplateSource): {symbol: Symbol, span: Span}|undefined { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   const path = findAstAt(ast, position, /* excludeEmpty */ true); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   if (path.empty) return undefined; | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   const tail = path.tail!; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |   function getType(ast: AST): Symbol { | 
					
						
							|  |  |  |     return new AstType(scope, templateInfo.query, {}, templateInfo.source).getType(ast); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |   function spanFromName(ast: ASTWithName): Span { | 
					
						
							|  |  |  |     // `nameSpan` is an absolute span, but the span expected by the result of this method is
 | 
					
						
							|  |  |  |     // relative to the start of the expression.
 | 
					
						
							|  |  |  |     // TODO(ayazhafiz): migrate to only using absolute spans
 | 
					
						
							|  |  |  |     const offset = ast.sourceSpan.start - ast.span.start; | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       start: ast.nameSpan.start - offset, | 
					
						
							|  |  |  |       end: ast.nameSpan.end - offset, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   let symbol: Symbol|undefined = undefined; | 
					
						
							|  |  |  |   let span: Span|undefined = undefined; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // If the completion request is in a not in a pipe or property access then the global scope
 | 
					
						
							|  |  |  |   // (that is the scope of the implicit receiver) is the right scope as the user is typing the
 | 
					
						
							|  |  |  |   // beginning of an expression.
 | 
					
						
							|  |  |  |   tail.visit({ | 
					
						
							| 
									
										
										
										
											2020-07-04 01:52:40 +02:00
										 |  |  |     visitUnary(_ast) {}, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitBinary(_ast) {}, | 
					
						
							|  |  |  |     visitChain(_ast) {}, | 
					
						
							|  |  |  |     visitConditional(_ast) {}, | 
					
						
							|  |  |  |     visitFunctionCall(_ast) {}, | 
					
						
							|  |  |  |     visitImplicitReceiver(_ast) {}, | 
					
						
							|  |  |  |     visitInterpolation(_ast) {}, | 
					
						
							|  |  |  |     visitKeyedRead(_ast) {}, | 
					
						
							|  |  |  |     visitKeyedWrite(_ast) {}, | 
					
						
							|  |  |  |     visitLiteralArray(_ast) {}, | 
					
						
							|  |  |  |     visitLiteralMap(_ast) {}, | 
					
						
							|  |  |  |     visitLiteralPrimitive(_ast) {}, | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     visitMethodCall(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       symbol = receiverType && receiverType.members().get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |     visitPipe(ast) { | 
					
						
							| 
									
										
										
										
											2020-03-09 22:16:08 -07:00
										 |  |  |       if (inSpan(position, ast.nameSpan, /* exclusive */ true)) { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |         // We are in a position a pipe name is expected.
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |         const pipes = templateInfo.query.getPipes(); | 
					
						
							| 
									
										
										
										
											2020-03-09 22:16:08 -07:00
										 |  |  |         symbol = pipes.get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |         span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitPrefixNot(_ast) {}, | 
					
						
							|  |  |  |     visitNonNullAssert(_ast) {}, | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     visitPropertyRead(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       symbol = receiverType && receiverType.members().get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |     visitPropertyWrite(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       symbol = receiverType && receiverType.members().get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     visitQuote(_ast) {}, | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     visitSafeMethodCall(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       symbol = receiverType && receiverType.members().get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |     visitSafePropertyRead(ast) { | 
					
						
							|  |  |  |       const receiverType = getType(ast.receiver); | 
					
						
							|  |  |  |       symbol = receiverType && receiverType.members().get(ast.name); | 
					
						
							| 
									
										
										
										
											2020-04-27 18:54:30 -07:00
										 |  |  |       span = spanFromName(ast); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (symbol && span) { | 
					
						
							|  |  |  |     return {symbol, span}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |