feat(compiler-cli): add ability to get symbol of reference or variable (#38618)
Adds `TemplateTypeChecker` operation to retrieve the `Symbol` of a
`TmplAstVariable` or `TmplAstReference` in a template.
Sometimes we need to traverse an intermediate variable declaration to arrive at
the correct `ts.Symbol`. For example, loop variables are declared using an intermediate:
```
<div *ngFor="let user of users">
  {{user.name}}
</div>
```
Getting the symbol of user here (from the expression) is tricky, because the TCB looks like:
```
var _t0 = ...; // type of NgForOf
var _t1: any; // context of embedded view for NgForOf structural directive
if (NgForOf.ngTemplateContextGuard(_t0, _t1)) {
  // _t1 is now NgForOfContext<...>
  var _t2 = _t1.$implicit; // let user = '$implicit'
  _t2.name; // user.name expression
}
```
Just getting the `ts.Expression` for the `AST` node `PropRead(ImplicitReceiver, 'user')`
via the sourcemaps will yield the `_t2` expression.  This function recognizes that `_t2`
is a variable declared locally in the TCB, and actually fetch the `ts.Symbol` of its initializer.
These special handlings show the versatility of the `Symbol`
interface defined in the API. With this, when we encounter a template variable,
we can provide the declaration node, as well as specific information
about the variable instance, such as the `ts.Type` and `ts.Symbol`.
PR Close #38618
			
			
This commit is contained in:
		
							parent
							
								
									f56ece4fdc
								
							
						
					
					
						commit
						19598b47ca
					
				| @ -6,12 +6,12 @@ | |||||||
|  * found in the LICENSE file at https://angular.io/license
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import {AbsoluteSourceSpan, AST, ASTWithSource, BindingPipe, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler'; | import {AbsoluteSourceSpan, AST, ASTWithSource, BindingPipe, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstVariable} from '@angular/compiler'; | ||||||
| import * as ts from 'typescript'; | import * as ts from 'typescript'; | ||||||
| 
 | 
 | ||||||
| import {AbsoluteFsPath} from '../../file_system'; | import {AbsoluteFsPath} from '../../file_system'; | ||||||
| import {isAssignment} from '../../util/src/typescript'; | import {isAssignment} from '../../util/src/typescript'; | ||||||
| import {DirectiveSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, Symbol, SymbolKind, TemplateSymbol, TsNodeSymbolInfo} from '../api'; | import {DirectiveSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, ReferenceSymbol, Symbol, SymbolKind, TemplateSymbol, TsNodeSymbolInfo, VariableSymbol} from '../api'; | ||||||
| 
 | 
 | ||||||
| import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments'; | import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments'; | ||||||
| import {TemplateData} from './context'; | import {TemplateData} from './context'; | ||||||
| @ -28,6 +28,7 @@ export class SymbolBuilder { | |||||||
|       private readonly typeCheckBlock: ts.Node, private readonly templateData: TemplateData) {} |       private readonly typeCheckBlock: ts.Node, private readonly templateData: TemplateData) {} | ||||||
| 
 | 
 | ||||||
|   getSymbol(node: TmplAstTemplate|TmplAstElement): TemplateSymbol|ElementSymbol|null; |   getSymbol(node: TmplAstTemplate|TmplAstElement): TemplateSymbol|ElementSymbol|null; | ||||||
|  |   getSymbol(node: TmplAstReference|TmplAstVariable): ReferenceSymbol|VariableSymbol|null; | ||||||
|   getSymbol(node: AST|TmplAstNode): Symbol|null; |   getSymbol(node: AST|TmplAstNode): Symbol|null; | ||||||
|   getSymbol(node: AST|TmplAstNode): Symbol|null { |   getSymbol(node: AST|TmplAstNode): Symbol|null { | ||||||
|     if (node instanceof TmplAstBoundAttribute) { |     if (node instanceof TmplAstBoundAttribute) { | ||||||
| @ -40,6 +41,10 @@ export class SymbolBuilder { | |||||||
|       return this.getSymbolOfElement(node); |       return this.getSymbolOfElement(node); | ||||||
|     } else if (node instanceof TmplAstTemplate) { |     } else if (node instanceof TmplAstTemplate) { | ||||||
|       return this.getSymbolOfAstTemplate(node); |       return this.getSymbolOfAstTemplate(node); | ||||||
|  |     } else if (node instanceof TmplAstVariable) { | ||||||
|  |       return this.getSymbolOfVariable(node); | ||||||
|  |     } else if (node instanceof TmplAstReference) { | ||||||
|  |       return this.getSymbolOfReference(node); | ||||||
|     } else if (node instanceof AST) { |     } else if (node instanceof AST) { | ||||||
|       return this.getSymbolOfTemplateExpression(node); |       return this.getSymbolOfTemplateExpression(node); | ||||||
|     } |     } | ||||||
| @ -226,11 +231,70 @@ export class SymbolBuilder { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private getSymbolOfTemplateExpression(expression: AST): ExpressionSymbol|null { |   private getSymbolOfVariable(variable: TmplAstVariable): VariableSymbol|null { | ||||||
|  |     const node = findFirstMatchingNode( | ||||||
|  |         this.typeCheckBlock, {withSpan: variable.sourceSpan, filter: ts.isVariableDeclaration}); | ||||||
|  |     if (node === null) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const expressionSymbol = this.getSymbolOfVariableDeclaration(node); | ||||||
|  |     if (expressionSymbol === null) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return {...expressionSymbol, kind: SymbolKind.Variable, declaration: variable}; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getSymbolOfReference(ref: TmplAstReference): ReferenceSymbol|null { | ||||||
|  |     const target = this.templateData.boundTarget.getReferenceTarget(ref); | ||||||
|  |     // Find the node for the reference declaration, i.e. `var _t2 = _t1;`
 | ||||||
|  |     let node = findFirstMatchingNode( | ||||||
|  |         this.typeCheckBlock, {withSpan: ref.sourceSpan, filter: ts.isVariableDeclaration}); | ||||||
|  |     if (node === null || target === null || node.initializer === undefined) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO(atscott): Shim location will need to be adjusted
 | ||||||
|  |     const symbol = this.getSymbolOfTsNode(node.name); | ||||||
|  |     if (symbol === null || symbol.tsSymbol === null) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (target instanceof TmplAstTemplate || target instanceof TmplAstElement) { | ||||||
|  |       return { | ||||||
|  |         ...symbol, | ||||||
|  |         tsSymbol: symbol.tsSymbol, | ||||||
|  |         kind: SymbolKind.Reference, | ||||||
|  |         target, | ||||||
|  |         declaration: ref, | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       if (!ts.isClassDeclaration(target.directive.ref.node)) { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return { | ||||||
|  |         ...symbol, | ||||||
|  |         kind: SymbolKind.Reference, | ||||||
|  |         tsSymbol: symbol.tsSymbol, | ||||||
|  |         declaration: ref, | ||||||
|  |         target: target.directive.ref.node, | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getSymbolOfTemplateExpression(expression: AST): VariableSymbol|ReferenceSymbol | ||||||
|  |       |ExpressionSymbol|null { | ||||||
|     if (expression instanceof ASTWithSource) { |     if (expression instanceof ASTWithSource) { | ||||||
|       expression = expression.ast; |       expression = expression.ast; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const expressionTarget = this.templateData.boundTarget.getExpressionTarget(expression); | ||||||
|  |     if (expressionTarget !== null) { | ||||||
|  |       return this.getSymbol(expressionTarget); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let node = findFirstMatchingNode( |     let node = findFirstMatchingNode( | ||||||
|         this.typeCheckBlock, |         this.typeCheckBlock, | ||||||
|         {withSpan: expression.sourceSpan, filter: (n: ts.Node): n is ts.Node => true}); |         {withSpan: expression.sourceSpan, filter: (n: ts.Node): n is ts.Node => true}); | ||||||
|  | |||||||
| @ -6,13 +6,13 @@ | |||||||
|  * found in the LICENSE file at https://angular.io/license
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler'; | import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate} from '@angular/compiler'; | ||||||
| import * as ts from 'typescript'; | import * as ts from 'typescript'; | ||||||
| 
 | 
 | ||||||
| import {absoluteFrom, getSourceFileOrError} from '../../file_system'; | import {absoluteFrom, getSourceFileOrError} from '../../file_system'; | ||||||
| import {runInEachFileSystem} from '../../file_system/testing'; | import {runInEachFileSystem} from '../../file_system/testing'; | ||||||
| import {ClassDeclaration} from '../../reflection'; | import {ClassDeclaration} from '../../reflection'; | ||||||
| import {DirectiveSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig} from '../api'; | import {DirectiveSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, ReferenceSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig, VariableSymbol} from '../api'; | ||||||
| 
 | 
 | ||||||
| import {getClass, ngForDeclaration, ngForTypeCheckTarget, setup as baseTestSetup, TypeCheckingTarget} from './test_utils'; | import {getClass, ngForDeclaration, ngForTypeCheckTarget, setup as baseTestSetup, TypeCheckingTarget} from './test_utils'; | ||||||
| 
 | 
 | ||||||
| @ -79,6 +79,59 @@ runInEachFileSystem(() => { | |||||||
|           templateNode = getAstTemplates(templateTypeChecker, cmp)[0]; |           templateNode = getAstTemplates(templateTypeChecker, cmp)[0]; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |         it('should get symbol for variables at the declaration', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode(templateNode.variables[0], cmp)!; | ||||||
|  |           assertVariableSymbol(symbol); | ||||||
|  |           expect(program.getTypeChecker().typeToString(symbol.tsType!)).toEqual('any'); | ||||||
|  |           expect(symbol.declaration.name).toEqual('contextFoo'); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('should get symbol for variables when used', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode( | ||||||
|  |               (templateNode.children[0] as TmplAstTemplate).inputs[0].value, cmp)!; | ||||||
|  |           assertVariableSymbol(symbol); | ||||||
|  |           expect(program.getTypeChecker().typeToString(symbol.tsType!)).toEqual('any'); | ||||||
|  |           expect(symbol.declaration.name).toEqual('contextFoo'); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('should get a symbol for local ref which refers to a directive', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode(templateNode.references[1], cmp)!; | ||||||
|  |           assertReferenceSymbol(symbol); | ||||||
|  |           assertDirectiveReference(symbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('should get a symbol for usage local ref which refers to a directive', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode( | ||||||
|  |               (templateNode.children[0] as TmplAstTemplate).inputs[2].value, cmp)!; | ||||||
|  |           assertReferenceSymbol(symbol); | ||||||
|  |           assertDirectiveReference(symbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         function assertDirectiveReference(symbol: ReferenceSymbol) { | ||||||
|  |           expect(program.getTypeChecker().typeToString(symbol.tsType)).toEqual('TestDir'); | ||||||
|  |           expect((symbol.target as ts.ClassDeclaration).name!.getText()).toEqual('TestDir'); | ||||||
|  |           expect(symbol.declaration.name).toEqual('ref1'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         it('should get a symbol for local ref which refers to the template', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode(templateNode.references[0], cmp)!; | ||||||
|  |           assertReferenceSymbol(symbol); | ||||||
|  |           assertTemplateReference(symbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('should get a symbol for usage local ref which refers to a template', () => { | ||||||
|  |           const symbol = templateTypeChecker.getSymbolOfNode( | ||||||
|  |               (templateNode.children[0] as TmplAstTemplate).inputs[1].value, cmp)!; | ||||||
|  |           assertReferenceSymbol(symbol); | ||||||
|  |           assertTemplateReference(symbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         function assertTemplateReference(symbol: ReferenceSymbol) { | ||||||
|  |           expect(program.getTypeChecker().typeToString(symbol.tsType)).toEqual('TemplateRef<any>'); | ||||||
|  |           expect((symbol.target as TmplAstTemplate).tagName).toEqual('ng-template'); | ||||||
|  |           expect(symbol.declaration.name).toEqual('ref0'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         it('should get symbol for the template itself', () => { |         it('should get symbol for the template itself', () => { | ||||||
|           const symbol = templateTypeChecker.getSymbolOfNode(templateNode, cmp)!; |           const symbol = templateTypeChecker.getSymbolOfNode(templateNode, cmp)!; | ||||||
|           assertTemplateSymbol(symbol); |           assertTemplateSymbol(symbol); | ||||||
| @ -150,7 +203,46 @@ runInEachFileSystem(() => { | |||||||
|              expect(program.getTypeChecker().symbolToString(streetSymbol.tsSymbol!)) |              expect(program.getTypeChecker().symbolToString(streetSymbol.tsSymbol!)) | ||||||
|                  .toEqual('streetNumber'); |                  .toEqual('streetNumber'); | ||||||
|              expect(program.getTypeChecker().typeToString(streetSymbol.tsType)).toEqual('number'); |              expect(program.getTypeChecker().typeToString(streetSymbol.tsType)).toEqual('number'); | ||||||
|  | 
 | ||||||
|  |              const userSymbol = templateTypeChecker.getSymbolOfNode(namePropRead.receiver, cmp)!; | ||||||
|  |              expectUserSymbol(userSymbol); | ||||||
|            }); |            }); | ||||||
|  | 
 | ||||||
|  |         it('finds symbols for variables', () => { | ||||||
|  |           const userVar = templateNode.variables.find(v => v.name === 'user')!; | ||||||
|  |           const userSymbol = templateTypeChecker.getSymbolOfNode(userVar, cmp)!; | ||||||
|  |           expectUserSymbol(userSymbol); | ||||||
|  | 
 | ||||||
|  |           const iVar = templateNode.variables.find(v => v.name === 'i')!; | ||||||
|  |           const iSymbol = templateTypeChecker.getSymbolOfNode(iVar, cmp)!; | ||||||
|  |           expectIndexSymbol(iSymbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('finds symbol when using a template variable', () => { | ||||||
|  |           const innerElementNodes = | ||||||
|  |               onlyAstElements((templateNode.children[0] as TmplAstElement).children); | ||||||
|  |           const indexSymbol = | ||||||
|  |               templateTypeChecker.getSymbolOfNode(innerElementNodes[0].inputs[0].value, cmp)!; | ||||||
|  |           expectIndexSymbol(indexSymbol); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         function expectUserSymbol(userSymbol: Symbol) { | ||||||
|  |           assertVariableSymbol(userSymbol); | ||||||
|  |           expect(userSymbol.tsSymbol!.escapedName).toContain('$implicit'); | ||||||
|  |           expect(userSymbol.tsSymbol!.declarations[0].parent!.getText()) | ||||||
|  |               .toContain('NgForOfContext'); | ||||||
|  |           expect(program.getTypeChecker().typeToString(userSymbol.tsType!)).toEqual('User'); | ||||||
|  |           expect((userSymbol).declaration).toEqual(templateNode.variables[0]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         function expectIndexSymbol(indexSymbol: Symbol) { | ||||||
|  |           assertVariableSymbol(indexSymbol); | ||||||
|  |           expect(indexSymbol.tsSymbol!.escapedName).toContain('index'); | ||||||
|  |           expect(indexSymbol.tsSymbol!.declarations[0].parent!.getText()) | ||||||
|  |               .toContain('NgForOfContext'); | ||||||
|  |           expect(program.getTypeChecker().typeToString(indexSymbol.tsType!)).toEqual('number'); | ||||||
|  |           expect((indexSymbol).declaration).toEqual(templateNode.variables[1]); | ||||||
|  |         } | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -364,6 +456,89 @@ runInEachFileSystem(() => { | |||||||
|         expect(program.getTypeChecker().typeToString(bSymbol.tsType)).toEqual('number'); |         expect(program.getTypeChecker().typeToString(bSymbol.tsType)).toEqual('number'); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       it('should get symbol for local reference of an Element', () => { | ||||||
|  |         const fileName = absoluteFrom('/main.ts'); | ||||||
|  |         const {templateTypeChecker, program} = setup([ | ||||||
|  |           { | ||||||
|  |             fileName, | ||||||
|  |             templates: { | ||||||
|  |               'Cmp': ` | ||||||
|  |                   <input #myRef> | ||||||
|  |                   <div [input]="myRef"></div>` | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ]); | ||||||
|  |         const sf = getSourceFileOrError(program, fileName); | ||||||
|  |         const cmp = getClass(sf, 'Cmp'); | ||||||
|  |         const nodes = getAstElements(templateTypeChecker, cmp); | ||||||
|  | 
 | ||||||
|  |         const refSymbol = templateTypeChecker.getSymbolOfNode(nodes[0].references[0], cmp)!; | ||||||
|  |         assertReferenceSymbol(refSymbol); | ||||||
|  |         expect((refSymbol.target as TmplAstElement).name).toEqual('input'); | ||||||
|  |         expect((refSymbol.declaration as TmplAstReference).name).toEqual('myRef'); | ||||||
|  | 
 | ||||||
|  |         const myRefUsage = templateTypeChecker.getSymbolOfNode(nodes[1].inputs[0].value, cmp)!; | ||||||
|  |         assertReferenceSymbol(myRefUsage); | ||||||
|  |         expect((myRefUsage.target as TmplAstElement).name).toEqual('input'); | ||||||
|  |         expect((myRefUsage.declaration as TmplAstReference).name).toEqual('myRef'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should get symbols for references which refer to directives', () => { | ||||||
|  |         const fileName = absoluteFrom('/main.ts'); | ||||||
|  |         const dirFile = absoluteFrom('/dir.ts'); | ||||||
|  |         const templateString = ` | ||||||
|  |         <div dir #myDir1="dir"></div> | ||||||
|  |         <div dir #myDir2="dir"></div> | ||||||
|  |         <div [inputA]="myDir1.dirValue" [inputB]="myDir1"></div> | ||||||
|  |         <div [inputA]="myDir2.dirValue" [inputB]="myDir2"></div>`;
 | ||||||
|  |         const {templateTypeChecker, program} = setup([ | ||||||
|  |           { | ||||||
|  |             fileName, | ||||||
|  |             templates: {'Cmp': templateString}, | ||||||
|  |             declarations: [{ | ||||||
|  |               name: 'TestDir', | ||||||
|  |               selector: '[dir]', | ||||||
|  |               file: dirFile, | ||||||
|  |               type: 'directive', | ||||||
|  |               exportAs: ['dir'], | ||||||
|  |             }] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             fileName: dirFile, | ||||||
|  |             source: `export class TestDir { dirValue = 'helloWorld' }`, | ||||||
|  |             templates: {} | ||||||
|  |           } | ||||||
|  |         ]); | ||||||
|  |         const sf = getSourceFileOrError(program, fileName); | ||||||
|  |         const cmp = getClass(sf, 'Cmp'); | ||||||
|  |         const nodes = getAstElements(templateTypeChecker, cmp); | ||||||
|  | 
 | ||||||
|  |         const ref1Declaration = templateTypeChecker.getSymbolOfNode(nodes[0].references[0], cmp)!; | ||||||
|  |         assertReferenceSymbol(ref1Declaration); | ||||||
|  |         expect((ref1Declaration.target as ts.ClassDeclaration).name!.getText()).toEqual('TestDir'); | ||||||
|  |         expect((ref1Declaration.declaration as TmplAstReference).name).toEqual('myDir1'); | ||||||
|  | 
 | ||||||
|  |         const ref2Declaration = templateTypeChecker.getSymbolOfNode(nodes[1].references[0], cmp)!; | ||||||
|  |         assertReferenceSymbol(ref2Declaration); | ||||||
|  |         expect((ref2Declaration.target as ts.ClassDeclaration).name!.getText()).toEqual('TestDir'); | ||||||
|  |         expect((ref2Declaration.declaration as TmplAstReference).name).toEqual('myDir2'); | ||||||
|  | 
 | ||||||
|  |         const dirValueSymbol = templateTypeChecker.getSymbolOfNode(nodes[2].inputs[0].value, cmp)!; | ||||||
|  |         assertExpressionSymbol(dirValueSymbol); | ||||||
|  |         expect(program.getTypeChecker().symbolToString(dirValueSymbol.tsSymbol!)).toBe('dirValue'); | ||||||
|  |         expect(program.getTypeChecker().typeToString(dirValueSymbol.tsType)).toEqual('string'); | ||||||
|  | 
 | ||||||
|  |         const dir1Symbol = templateTypeChecker.getSymbolOfNode(nodes[2].inputs[1].value, cmp)!; | ||||||
|  |         assertReferenceSymbol(dir1Symbol); | ||||||
|  |         expect((dir1Symbol.target as ts.ClassDeclaration).name!.getText()).toEqual('TestDir'); | ||||||
|  |         expect((dir1Symbol.declaration as TmplAstReference).name).toEqual('myDir1'); | ||||||
|  | 
 | ||||||
|  |         const dir2Symbol = templateTypeChecker.getSymbolOfNode(nodes[3].inputs[1].value, cmp)!; | ||||||
|  |         assertReferenceSymbol(dir2Symbol); | ||||||
|  |         expect((dir2Symbol.target as ts.ClassDeclaration).name!.getText()).toEqual('TestDir'); | ||||||
|  |         expect((dir2Symbol.declaration as TmplAstReference).name).toEqual('myDir2'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|       describe('literals', () => { |       describe('literals', () => { | ||||||
|         let templateTypeChecker: TemplateTypeChecker; |         let templateTypeChecker: TemplateTypeChecker; | ||||||
|         let cmp: ClassDeclaration<ts.ClassDeclaration>; |         let cmp: ClassDeclaration<ts.ClassDeclaration>; | ||||||
| @ -1061,10 +1236,18 @@ function assertOutputBindingSymbol(tSymbol: Symbol): asserts tSymbol is OutputBi | |||||||
|   expect(tSymbol.kind).toEqual(SymbolKind.Output); |   expect(tSymbol.kind).toEqual(SymbolKind.Output); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function assertVariableSymbol(tSymbol: Symbol): asserts tSymbol is VariableSymbol { | ||||||
|  |   expect(tSymbol.kind).toEqual(SymbolKind.Variable); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function assertTemplateSymbol(tSymbol: Symbol): asserts tSymbol is TemplateSymbol { | function assertTemplateSymbol(tSymbol: Symbol): asserts tSymbol is TemplateSymbol { | ||||||
|   expect(tSymbol.kind).toEqual(SymbolKind.Template); |   expect(tSymbol.kind).toEqual(SymbolKind.Template); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function assertReferenceSymbol(tSymbol: Symbol): asserts tSymbol is ReferenceSymbol { | ||||||
|  |   expect(tSymbol.kind).toEqual(SymbolKind.Reference); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function assertExpressionSymbol(tSymbol: Symbol): asserts tSymbol is ExpressionSymbol { | function assertExpressionSymbol(tSymbol: Symbol): asserts tSymbol is ExpressionSymbol { | ||||||
|   expect(tSymbol.kind).toEqual(SymbolKind.Expression); |   expect(tSymbol.kind).toEqual(SymbolKind.Expression); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user