| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07: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-04-08 20:05:32 -07:00
										 |  |  | import {AST, AstPath, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, CompileDirectiveSummary, CompileTypeMetadata, DirectiveAst, ElementAst, EmbeddedTemplateAst, identifierName, ParseSourceSpan, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference, VariableAst} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  | import {createDiagnostic, Diagnostic} from './diagnostic_messages'; | 
					
						
							| 
									
										
										
										
											2020-01-31 12:34:20 -08:00
										 |  |  | import {AstType} from './expression_type'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | import {BuiltinType, Definition, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols'; | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  | import * as ng from './types'; | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | import {findOutputBinding, getPathToNodeAtPosition} from './utils'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  | export function getTemplateExpressionDiagnostics(info: ng.DiagnosticTemplateInfo): ng.Diagnostic[] { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   const visitor = new ExpressionDiagnosticsVisitor( | 
					
						
							| 
									
										
										
										
											2019-12-26 08:44:14 -06:00
										 |  |  |       info, (path: TemplateAstPath) => getExpressionScope(info, path)); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   templateVisitAll(visitor, info.templateAst); | 
					
						
							|  |  |  |   return visitor.diagnostics; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  | function getReferences(info: ng.DiagnosticTemplateInfo): SymbolDeclaration[] { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   const result: SymbolDeclaration[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function processReferences(references: ReferenceAst[]) { | 
					
						
							|  |  |  |     for (const reference of references) { | 
					
						
							|  |  |  |       let type: Symbol|undefined = undefined; | 
					
						
							|  |  |  |       if (reference.value) { | 
					
						
							|  |  |  |         type = info.query.getTypeSymbol(tokenReference(reference.value)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       result.push({ | 
					
						
							|  |  |  |         name: reference.name, | 
					
						
							|  |  |  |         kind: 'reference', | 
					
						
							|  |  |  |         type: type || info.query.getBuiltinType(BuiltinType.Any), | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |         get definition() { | 
					
						
							|  |  |  |           return getDefinitionOf(info, reference); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const visitor = new class extends RecursiveTemplateAstVisitor { | 
					
						
							|  |  |  |     visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { | 
					
						
							|  |  |  |       super.visitEmbeddedTemplate(ast, context); | 
					
						
							|  |  |  |       processReferences(ast.references); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     visitElement(ast: ElementAst, context: any): any { | 
					
						
							|  |  |  |       super.visitElement(ast, context); | 
					
						
							|  |  |  |       processReferences(ast.references); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   templateVisitAll(visitor, info.templateAst); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  | function getDefinitionOf(info: ng.DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   if (info.fileName) { | 
					
						
							|  |  |  |     const templateOffset = info.offset; | 
					
						
							|  |  |  |     return [{ | 
					
						
							|  |  |  |       fileName: info.fileName, | 
					
						
							|  |  |  |       span: { | 
					
						
							|  |  |  |         start: ast.sourceSpan.start.offset + templateOffset, | 
					
						
							|  |  |  |         end: ast.sourceSpan.end.offset + templateOffset | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Resolve all variable declarations in a template by traversing the specified | 
					
						
							|  |  |  |  * `path`. | 
					
						
							|  |  |  |  * @param info | 
					
						
							|  |  |  |  * @param path template AST path | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | function getVarDeclarations( | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration[] { | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |   const results: SymbolDeclaration[] = []; | 
					
						
							|  |  |  |   for (let current = path.head; current; current = path.childOf(current)) { | 
					
						
							|  |  |  |     if (!(current instanceof EmbeddedTemplateAst)) { | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-12 17:40:56 -08:00
										 |  |  |     for (const variable of current.variables) { | 
					
						
							| 
									
										
										
										
											2020-03-10 12:22:46 +08:00
										 |  |  |       let symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       const kind = info.query.getTypeKind(symbol); | 
					
						
							|  |  |  |       if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) { | 
					
						
							|  |  |  |         // For special cases such as ngFor and ngIf, the any type is not very useful.
 | 
					
						
							|  |  |  |         // We can do better by resolving the binding value.
 | 
					
						
							|  |  |  |         const symbolsInScope = info.query.mergeSymbolTable([ | 
					
						
							|  |  |  |           info.members, | 
					
						
							|  |  |  |           // Since we are traversing the AST path from head to tail, any variables
 | 
					
						
							|  |  |  |           // that have been declared so far are also in scope.
 | 
					
						
							|  |  |  |           info.query.createSymbolTable(results), | 
					
						
							|  |  |  |         ]); | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |         symbol = refinedVariableType(variable.value, symbolsInScope, info, current); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       results.push({ | 
					
						
							|  |  |  |         name: variable.name, | 
					
						
							|  |  |  |         kind: 'variable', | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |         type: symbol, | 
					
						
							|  |  |  |         get definition() { | 
					
						
							|  |  |  |           return getDefinitionOf(info, variable); | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |   return results; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |  * Resolve the type for the variable in `templateElement` by finding the structural | 
					
						
							|  |  |  |  * directive which has the context member. Returns any when not found. | 
					
						
							|  |  |  |  * @param value variable value name | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |  * @param query type symbol query | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |  * @param templateElement | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  | function getVariableTypeFromDirectiveContext( | 
					
						
							|  |  |  |     value: string, query: SymbolQuery, templateElement: EmbeddedTemplateAst): Symbol { | 
					
						
							|  |  |  |   for (const {directive} of templateElement.directives) { | 
					
						
							|  |  |  |     const context = query.getTemplateContext(directive.type.reference); | 
					
						
							|  |  |  |     if (context) { | 
					
						
							|  |  |  |       const member = context.get(value); | 
					
						
							|  |  |  |       if (member && member.type) { | 
					
						
							|  |  |  |         return member.type; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |   return query.getBuiltinType(BuiltinType.Any); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Resolve a more specific type for the variable in `templateElement` by inspecting | 
					
						
							|  |  |  |  * all variables that are in scope in the `mergedTable`. This function is a special | 
					
						
							|  |  |  |  * case for `ngFor` and `ngIf`. If resolution fails, return the `any` type. | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |  * @param value variable value name | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |  * @param mergedTable symbol table for all variables in scope | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |  * @param info available template information | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |  * @param templateElement | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | function refinedVariableType( | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     value: string, mergedTable: SymbolTable, info: ng.DiagnosticTemplateInfo, | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |     templateElement: EmbeddedTemplateAst): Symbol { | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |   if (value === '$implicit') { | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |     // Special case: ngFor directive
 | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |     const ngForDirective = templateElement.directives.find(d => { | 
					
						
							|  |  |  |       const name = identifierName(d.directive.type); | 
					
						
							|  |  |  |       return name == 'NgFor' || name == 'NgForOf'; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     if (ngForDirective) { | 
					
						
							|  |  |  |       const ngForOfBinding = ngForDirective.inputs.find(i => i.directiveName == 'ngForOf'); | 
					
						
							|  |  |  |       if (ngForOfBinding) { | 
					
						
							|  |  |  |         // Check if there is a known type for the ngFor binding.
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |         const bindingType = | 
					
						
							|  |  |  |             new AstType(mergedTable, info.query, {}, info.source).getType(ngForOfBinding.value); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |         if (bindingType) { | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |           const result = info.query.getElementType(bindingType); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |           if (result) { | 
					
						
							|  |  |  |             return result; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |   if (value === 'ngIf' || value === '$implicit') { | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |     const ngIfDirective = | 
					
						
							|  |  |  |         templateElement.directives.find(d => identifierName(d.directive.type) === 'NgIf'); | 
					
						
							|  |  |  |     if (ngIfDirective) { | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |       // Special case: ngIf directive. The NgIf structural directive owns a template context with
 | 
					
						
							|  |  |  |       // "$implicit" and "ngIf" members. These properties are typed as generics. Until the language
 | 
					
						
							|  |  |  |       // service uses an Ivy and TypecheckBlock backend, we cannot bind these values to a concrete
 | 
					
						
							|  |  |  |       // type without manual inference. To get the concrete type, look up the type of the "ngIf"
 | 
					
						
							|  |  |  |       // import on the NgIf directive bound to the template.
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       // See @angular/common/ng_if.ts for more information.
 | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |       const ngIfBinding = ngIfDirective.inputs.find(i => i.directiveName === 'ngIf'); | 
					
						
							|  |  |  |       if (ngIfBinding) { | 
					
						
							| 
									
										
										
										
											2020-03-08 13:42:05 -07:00
										 |  |  |         // Check if there is a known type bound to the ngIf input.
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |         const bindingType = | 
					
						
							|  |  |  |             new AstType(mergedTable, info.query, {}, info.source).getType(ngIfBinding.value); | 
					
						
							| 
									
										
										
										
											2020-02-19 20:38:21 +01:00
										 |  |  |         if (bindingType) { | 
					
						
							|  |  |  |           return bindingType; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-10-05 15:18:05 +03:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-19 10:07:54 -07:00
										 |  |  |   // We can't do better, return any
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |   return info.query.getBuiltinType(BuiltinType.Any); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | function getEventDeclaration( | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined { | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |   const event = path.tail; | 
					
						
							|  |  |  |   if (!(event instanceof BoundEventAst)) { | 
					
						
							|  |  |  |     // No event available in this context.
 | 
					
						
							|  |  |  |     return; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const genericEvent: SymbolDeclaration = { | 
					
						
							|  |  |  |     name: '$event', | 
					
						
							|  |  |  |     kind: 'variable', | 
					
						
							|  |  |  |     type: info.query.getBuiltinType(BuiltinType.Any), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const outputSymbol = findOutputBinding(event, path, info.query); | 
					
						
							|  |  |  |   if (!outputSymbol) { | 
					
						
							|  |  |  |     // The `$event` variable doesn't belong to an output, so its type can't be refined.
 | 
					
						
							|  |  |  |     // TODO: type `$event` variables in bindings to DOM events.
 | 
					
						
							|  |  |  |     return genericEvent; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // The raw event type is wrapped in a generic, like EventEmitter<T> or Observable<T>.
 | 
					
						
							|  |  |  |   const ta = outputSymbol.typeArguments(); | 
					
						
							|  |  |  |   if (!ta || ta.length !== 1) return genericEvent; | 
					
						
							|  |  |  |   const eventType = ta[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return {...genericEvent, type: eventType}; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Returns the symbols available in a particular scope of a template. | 
					
						
							|  |  |  |  * @param info parsed template information | 
					
						
							|  |  |  |  * @param path path of template nodes narrowing to the context the expression scope should be | 
					
						
							|  |  |  |  * derived for. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | export function getExpressionScope( | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |     info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   let result = info.members; | 
					
						
							|  |  |  |   const references = getReferences(info); | 
					
						
							|  |  |  |   const variables = getVarDeclarations(info, path); | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |   const event = getEventDeclaration(info, path); | 
					
						
							|  |  |  |   if (references.length || variables.length || event) { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     const referenceTable = info.query.createSymbolTable(references); | 
					
						
							|  |  |  |     const variableTable = info.query.createSymbolTable(variables); | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  |     const eventsTable = info.query.createSymbolTable(event ? [event] : []); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     result = info.query.mergeSymbolTable([result, referenceTable, variableTable, eventsTable]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExpressionDiagnosticsVisitor extends RecursiveTemplateAstVisitor { | 
					
						
							|  |  |  |   private path: TemplateAstPath; | 
					
						
							| 
									
										
										
										
											2020-01-14 22:48:13 -08:00
										 |  |  |   private directiveSummary: CompileDirectiveSummary|undefined; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  |   diagnostics: ng.Diagnostic[] = []; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   constructor( | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  |       private info: ng.DiagnosticTemplateInfo, | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       private getExpressionScope: (path: TemplateAstPath, includeEvent: boolean) => SymbolTable) { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |     this.path = new AstPath<TemplateAst>([]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitDirective(ast: DirectiveAst, context: any): any { | 
					
						
							|  |  |  |     // Override the default child visitor to ignore the host properties of a directive.
 | 
					
						
							|  |  |  |     if (ast.inputs && ast.inputs.length) { | 
					
						
							|  |  |  |       templateVisitAll(this, ast.inputs, context); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitBoundText(ast: BoundTextAst): void { | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  |     this.diagnoseExpression(ast.value, ast.sourceSpan.start.offset, false); | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitDirectiveProperty(ast: BoundDirectivePropertyAst): void { | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  |     this.diagnoseExpression(ast.value, this.attributeValueLocation(ast), false); | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitElementProperty(ast: BoundElementPropertyAst): void { | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  |     this.diagnoseExpression(ast.value, this.attributeValueLocation(ast), false); | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitEvent(ast: BoundEventAst): void { | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  |     this.diagnoseExpression(ast.handler, this.attributeValueLocation(ast), true); | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitVariable(ast: VariableAst): void { | 
					
						
							|  |  |  |     const directive = this.directiveSummary; | 
					
						
							|  |  |  |     if (directive && ast.value) { | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       const context = this.info.query.getTemplateContext(directive.type.reference)!; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       if (context && !context.has(ast.value)) { | 
					
						
							| 
									
										
										
										
											2020-01-29 10:17:36 -08:00
										 |  |  |         const missingMember = | 
					
						
							|  |  |  |             ast.value === '$implicit' ? 'an implicit value' : `a member called '${ast.value}'`; | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const span = this.absSpan(spanOf(ast.sourceSpan)); | 
					
						
							|  |  |  |         this.diagnostics.push(createDiagnostic( | 
					
						
							|  |  |  |             span, Diagnostic.template_context_missing_member, directive.type.reference.name, | 
					
						
							|  |  |  |             missingMember)); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitElement(ast: ElementAst, context: any): void { | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  |     super.visitElement(ast, context); | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { | 
					
						
							|  |  |  |     const previousDirectiveSummary = this.directiveSummary; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.push(ast); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-10 17:14:58 +00:00
										 |  |  |     // Find directive that references this template
 | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     this.directiveSummary = | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |         ast.directives.map(d => d.directive).find(d => hasTemplateReference(d.type))!; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Process children
 | 
					
						
							|  |  |  |     super.visitEmbeddedTemplate(ast, context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.pop(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.directiveSummary = previousDirectiveSummary; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private attributeValueLocation(ast: TemplateAst) { | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  |     const path = getPathToNodeAtPosition(this.info.htmlAst, ast.sourceSpan.start.offset); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     const last = path.tail; | 
					
						
							|  |  |  |     if (last instanceof Attribute && last.valueSpan) { | 
					
						
							| 
									
										
										
										
											2019-02-08 22:10:20 +00:00
										 |  |  |       return last.valueSpan.start.offset; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     return ast.sourceSpan.start.offset; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 12:29:45 -08:00
										 |  |  |   private diagnoseExpression(ast: AST, offset: number, inEvent: boolean) { | 
					
						
							|  |  |  |     const scope = this.getExpressionScope(this.path, inEvent); | 
					
						
							|  |  |  |     const analyzer = new AstType(scope, this.info.query, {inEvent}, this.info.source); | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  |     for (const diagnostic of analyzer.getDiagnostics(ast)) { | 
					
						
							|  |  |  |       diagnostic.span = this.absSpan(diagnostic.span, offset); | 
					
						
							|  |  |  |       this.diagnostics.push(diagnostic); | 
					
						
							| 
									
										
										
										
											2020-01-31 12:34:20 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   private push(ast: TemplateAst) { | 
					
						
							|  |  |  |     this.path.push(ast); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   private pop() { | 
					
						
							|  |  |  |     this.path.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-25 08:32:38 -08:00
										 |  |  |   private absSpan(span: Span, additionalOffset: number = 0): Span { | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       start: span.start + this.info.offset + additionalOffset, | 
					
						
							|  |  |  |       end: span.end + this.info.offset + additionalOffset, | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function hasTemplateReference(type: CompileTypeMetadata): boolean { | 
					
						
							|  |  |  |   if (type.diDeps) { | 
					
						
							|  |  |  |     for (let diDep of type.diDeps) { | 
					
						
							|  |  |  |       if (diDep.token && diDep.token.identifier && | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |           identifierName(diDep.token!.identifier!) == 'TemplateRef') | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function spanOf(sourceSpan: ParseSourceSpan): Span { | 
					
						
							|  |  |  |   return {start: sourceSpan.start.offset, end: sourceSpan.end.offset}; | 
					
						
							| 
									
										
										
										
											2019-10-24 10:17:25 -07:00
										 |  |  | } |