| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  | import {AST, AstPath, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, CompileDirectiveSummary, CompileTypeMetadata, DirectiveAst, ElementAst, EmbeddedTemplateAst, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstPath, VariableAst, identifierName, templateVisitAll, tokenReference} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2019-11-27 16:31:00 -08:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 16:31:00 -08:00
										 |  |  | import {AstType, ExpressionDiagnosticsContext, TypeDiagnostic} from './expression_type'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | import {BuiltinType, Definition, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols'; | 
					
						
							| 
									
										
										
										
											2019-11-27 16:31:00 -08:00
										 |  |  | import {Diagnostic} from './types'; | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | import {findOutputBinding, getPathToNodeAtPosition} from './utils'; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | export interface DiagnosticTemplateInfo { | 
					
						
							|  |  |  |   fileName?: string; | 
					
						
							|  |  |  |   offset: number; | 
					
						
							|  |  |  |   query: SymbolQuery; | 
					
						
							|  |  |  |   members: SymbolTable; | 
					
						
							|  |  |  |   htmlAst: Node[]; | 
					
						
							|  |  |  |   templateAst: TemplateAst[]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 16:31:00 -08:00
										 |  |  | export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getExpressionDiagnostics( | 
					
						
							|  |  |  |     scope: SymbolTable, ast: AST, query: SymbolQuery, | 
					
						
							|  |  |  |     context: ExpressionDiagnosticsContext = {}): TypeDiagnostic[] { | 
					
						
							|  |  |  |   const analyzer = new AstType(scope, query, context); | 
					
						
							|  |  |  |   analyzer.getDiagnostics(ast); | 
					
						
							|  |  |  |   return analyzer.diagnostics; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] { | 
					
						
							|  |  |  |   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), | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  | function getDefinitionOf(info: 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( | 
					
						
							|  |  |  |     info: 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) { | 
					
						
							|  |  |  |       let symbol = info.members.get(variable.value) || info.query.getBuiltinType(BuiltinType.Any); | 
					
						
							| 
									
										
										
										
											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), | 
					
						
							|  |  |  |         ]); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |         symbol = refinedVariableType(variable.value, symbolsInScope, info.query, current); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       results.push({ | 
					
						
							|  |  |  |         name: variable.name, | 
					
						
							|  |  |  |         kind: 'variable', | 
					
						
							|  |  |  |         type: symbol, get definition() { return getDefinitionOf(info, variable); }, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											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
										 |  |  | /** | 
					
						
							|  |  |  |  * Gets the type of an ngFor exported value, as enumerated in | 
					
						
							|  |  |  |  * https://angular.io/api/common/NgForOfContext
 | 
					
						
							|  |  |  |  * @param value exported value name | 
					
						
							|  |  |  |  * @param query type symbol query | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getNgForExportedValueType(value: string, query: SymbolQuery): Symbol|undefined { | 
					
						
							|  |  |  |   switch (value) { | 
					
						
							|  |  |  |     case 'index': | 
					
						
							|  |  |  |     case 'count': | 
					
						
							|  |  |  |       return query.getBuiltinType(BuiltinType.Number); | 
					
						
							|  |  |  |     case 'first': | 
					
						
							|  |  |  |     case 'last': | 
					
						
							|  |  |  |     case 'even': | 
					
						
							|  |  |  |     case 'odd': | 
					
						
							|  |  |  |       return query.getBuiltinType(BuiltinType.Boolean); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |  * @param query | 
					
						
							|  |  |  |  * @param templateElement | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | function refinedVariableType( | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |     value: string, mergedTable: SymbolTable, query: SymbolQuery, | 
					
						
							|  |  |  |     templateElement: EmbeddedTemplateAst): Symbol { | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |   // Special case the ngFor directive
 | 
					
						
							|  |  |  |   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) { | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |       // Check if the variable value is a type exported by the ngFor statement.
 | 
					
						
							|  |  |  |       let result = getNgForExportedValueType(value, query); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Otherwise, check if there is a known type for the ngFor binding.
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       const bindingType = new AstType(mergedTable, query, {}).getType(ngForOfBinding.value); | 
					
						
							| 
									
										
										
										
											2019-11-27 11:29:37 -06:00
										 |  |  |       if (!result && bindingType) { | 
					
						
							|  |  |  |         result = query.getElementType(bindingType); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (result) { | 
					
						
							|  |  |  |         return result; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 15:18:05 +03:00
										 |  |  |   // Special case the ngIf directive ( *ngIf="data$ | async as variable" )
 | 
					
						
							|  |  |  |   const ngIfDirective = | 
					
						
							|  |  |  |       templateElement.directives.find(d => identifierName(d.directive.type) === 'NgIf'); | 
					
						
							|  |  |  |   if (ngIfDirective) { | 
					
						
							|  |  |  |     const ngIfBinding = ngIfDirective.inputs.find(i => i.directiveName === 'ngIf'); | 
					
						
							|  |  |  |     if (ngIfBinding) { | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |       const bindingType = new AstType(mergedTable, query, {}).getType(ngIfBinding.value); | 
					
						
							| 
									
										
										
										
											2019-10-05 15:18:05 +03:00
										 |  |  |       if (bindingType) { | 
					
						
							|  |  |  |         return bindingType; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-19 10:07:54 -07:00
										 |  |  |   // We can't do better, return any
 | 
					
						
							| 
									
										
										
										
											2019-11-06 10:32:45 -08:00
										 |  |  |   return query.getBuiltinType(BuiltinType.Any); | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-26 13:26:54 -06:00
										 |  |  | function getEventDeclaration( | 
					
						
							|  |  |  |     info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined { | 
					
						
							|  |  |  |   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( | 
					
						
							| 
									
										
										
										
											2019-12-26 08:44:14 -06:00
										 |  |  |     info: 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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-27 16:31:00 -08:00
										 |  |  |   diagnostics: Diagnostic[] = []; | 
					
						
							| 
									
										
										
										
											2017-05-09 16:16:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   constructor( | 
					
						
							|  |  |  |       private info: DiagnosticTemplateInfo, | 
					
						
							|  |  |  |       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) { | 
					
						
							|  |  |  |       const context = this.info.query.getTemplateContext(directive.type.reference) !; | 
					
						
							|  |  |  |       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}'`; | 
					
						
							|  |  |  |         this.reportDiagnostic( | 
					
						
							|  |  |  |             `The template context of '${directive.type.reference.name}' does not define ${missingMember}.\n` + | 
					
						
							|  |  |  |                 `If the context type is a base type or 'any', consider refining it to a more specific type.`, | 
					
						
							|  |  |  |             spanOf(ast.sourceSpan), ts.DiagnosticCategory.Suggestion); | 
					
						
							| 
									
										
										
										
											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 = | 
					
						
							|  |  |  |         ast.directives.map(d => d.directive).find(d => hasTemplateReference(d.type)) !; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 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; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private diagnoseExpression(ast: AST, offset: number, includeEvent: boolean) { | 
					
						
							|  |  |  |     const scope = this.getExpressionScope(this.path, includeEvent); | 
					
						
							|  |  |  |     this.diagnostics.push(...getExpressionDiagnostics(scope, ast, this.info.query, { | 
					
						
							|  |  |  |                             event: includeEvent | 
					
						
							|  |  |  |                           }).map(d => ({ | 
					
						
							|  |  |  |                                    span: offsetSpan(d.ast.span, offset + this.info.offset), | 
					
						
							|  |  |  |                                    kind: d.kind, | 
					
						
							|  |  |  |                                    message: d.message | 
					
						
							|  |  |  |                                  }))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private push(ast: TemplateAst) { this.path.push(ast); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private pop() { this.path.pop(); } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-29 10:17:36 -08:00
										 |  |  |   private reportDiagnostic( | 
					
						
							|  |  |  |       message: string, span: Span, kind: ts.DiagnosticCategory = ts.DiagnosticCategory.Error) { | 
					
						
							|  |  |  |     this.diagnostics.push({span: offsetSpan(span, this.info.offset), kind, message}); | 
					
						
							| 
									
										
										
										
											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 && | 
					
						
							|  |  |  |           identifierName(diDep.token !.identifier !) == 'TemplateRef') | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function offsetSpan(span: Span, amount: number): Span { | 
					
						
							|  |  |  |   return {start: span.start + amount, end: span.end + amount}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function spanOf(sourceSpan: ParseSourceSpan): Span { | 
					
						
							|  |  |  |   return {start: sourceSpan.start.offset, end: sourceSpan.end.offset}; | 
					
						
							| 
									
										
										
										
											2019-10-24 10:17:25 -07:00
										 |  |  | } |