feat(compiler-cli): Add ability to get `Symbol` of `Template`s and `Element`s in component template (#38618)
Adds support to the `TemplateTypeChecker` for retrieving a `Symbol` for `TmplAstTemplate` and `TmplAstElement` nodes in a component template. PR Close #38618
This commit is contained in:
parent
c4556db9f5
commit
cf2e8b99a8
|
@ -88,14 +88,7 @@ export interface FindOptions<T extends ts.Node> {
|
||||||
withSpan?: AbsoluteSourceSpan|ParseSourceSpan;
|
withSpan?: AbsoluteSourceSpan|ParseSourceSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getSpanFromOptions(opts: FindOptions<ts.Node>) {
|
||||||
* Given a `ts.Node` with finds the first node whose matching the criteria specified
|
|
||||||
* by the `FindOptions`.
|
|
||||||
*
|
|
||||||
* Returns `null` when no `ts.Node` matches the given conditions.
|
|
||||||
*/
|
|
||||||
export function findFirstMatchingNode<T extends ts.Node>(tcb: ts.Node, opts: FindOptions<T>): T|
|
|
||||||
null {
|
|
||||||
let withSpan: {start: number, end: number}|null = null;
|
let withSpan: {start: number, end: number}|null = null;
|
||||||
if (opts.withSpan !== undefined) {
|
if (opts.withSpan !== undefined) {
|
||||||
if (opts.withSpan instanceof AbsoluteSourceSpan) {
|
if (opts.withSpan instanceof AbsoluteSourceSpan) {
|
||||||
|
@ -104,6 +97,18 @@ export function findFirstMatchingNode<T extends ts.Node>(tcb: ts.Node, opts: Fin
|
||||||
withSpan = {start: opts.withSpan.start.offset, end: opts.withSpan.end.offset};
|
withSpan = {start: opts.withSpan.start.offset, end: opts.withSpan.end.offset};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return withSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `ts.Node` with finds the first node whose matching the criteria specified
|
||||||
|
* by the `FindOptions`.
|
||||||
|
*
|
||||||
|
* Returns `null` when no `ts.Node` matches the given conditions.
|
||||||
|
*/
|
||||||
|
export function findFirstMatchingNode<T extends ts.Node>(tcb: ts.Node, opts: FindOptions<T>): T|
|
||||||
|
null {
|
||||||
|
const withSpan = getSpanFromOptions(opts);
|
||||||
const sf = tcb.getSourceFile();
|
const sf = tcb.getSourceFile();
|
||||||
const visitor = makeRecursiveVisitor<T>(node => {
|
const visitor = makeRecursiveVisitor<T>(node => {
|
||||||
if (!opts.filter(node)) {
|
if (!opts.filter(node)) {
|
||||||
|
@ -120,6 +125,41 @@ export function findFirstMatchingNode<T extends ts.Node>(tcb: ts.Node, opts: Fin
|
||||||
return tcb.forEachChild(visitor) ?? null;
|
return tcb.forEachChild(visitor) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a `ts.Node` with source span comments, finds the first node whose source span comment
|
||||||
|
* matches the given `sourceSpan`. Additionally, the `filter` function allows matching only
|
||||||
|
* `ts.Nodes` of a given type, which provides the ability to select only matches of a given type
|
||||||
|
* when there may be more than one.
|
||||||
|
*
|
||||||
|
* Returns `null` when no `ts.Node` matches the given conditions.
|
||||||
|
*/
|
||||||
|
export function findAllMatchingNodes<T extends ts.Node>(tcb: ts.Node, opts: FindOptions<T>): T[] {
|
||||||
|
const withSpan = getSpanFromOptions(opts);
|
||||||
|
const results: T[] = [];
|
||||||
|
const stack: ts.Node[] = [tcb];
|
||||||
|
const sf = tcb.getSourceFile();
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const node = stack.pop()!;
|
||||||
|
|
||||||
|
if (!opts.filter(node)) {
|
||||||
|
stack.push(...node.getChildren());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (withSpan !== null) {
|
||||||
|
const comment = readSpanComment(node, sf);
|
||||||
|
if (comment === null || withSpan.start !== comment.start || withSpan.end !== comment.end) {
|
||||||
|
stack.push(...node.getChildren());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
export function hasExpressionIdentifier(
|
export function hasExpressionIdentifier(
|
||||||
sourceFile: ts.SourceFile, node: ts.Node, identifier: ExpressionIdentifier): boolean {
|
sourceFile: ts.SourceFile, node: ts.Node, identifier: ExpressionIdentifier): boolean {
|
||||||
return ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
return ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
||||||
|
|
|
@ -11,12 +11,13 @@ 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, ReferenceSymbol, Symbol, SymbolKind, TsNodeSymbolInfo, VariableSymbol} from '../api';
|
import {DirectiveSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, Symbol, SymbolKind, TemplateSymbol, TsNodeSymbolInfo} from '../api';
|
||||||
|
|
||||||
import {ExpressionIdentifier, findFirstMatchingNode, hasExpressionIdentifier} from './comments';
|
import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments';
|
||||||
import {TemplateData} from './context';
|
import {TemplateData} from './context';
|
||||||
import {TcbDirectiveOutputsOp} from './type_check_block';
|
import {TcbDirectiveOutputsOp} from './type_check_block';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class which extracts information from a type check block.
|
* A class which extracts information from a type check block.
|
||||||
* This class is essentially used as just a closure around the constructor parameters.
|
* This class is essentially used as just a closure around the constructor parameters.
|
||||||
|
@ -26,6 +27,8 @@ export class SymbolBuilder {
|
||||||
private readonly typeChecker: ts.TypeChecker, private readonly shimPath: AbsoluteFsPath,
|
private readonly typeChecker: ts.TypeChecker, private readonly shimPath: AbsoluteFsPath,
|
||||||
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: AST|TmplAstNode): Symbol|null;
|
||||||
getSymbol(node: AST|TmplAstNode): Symbol|null {
|
getSymbol(node: AST|TmplAstNode): Symbol|null {
|
||||||
if (node instanceof TmplAstBoundAttribute) {
|
if (node instanceof TmplAstBoundAttribute) {
|
||||||
// TODO(atscott): input and output bindings only return the first directive match but should
|
// TODO(atscott): input and output bindings only return the first directive match but should
|
||||||
|
@ -33,10 +36,65 @@ export class SymbolBuilder {
|
||||||
return this.getSymbolOfInputBinding(node);
|
return this.getSymbolOfInputBinding(node);
|
||||||
} else if (node instanceof TmplAstBoundEvent) {
|
} else if (node instanceof TmplAstBoundEvent) {
|
||||||
return this.getSymbolOfBoundEvent(node);
|
return this.getSymbolOfBoundEvent(node);
|
||||||
|
} else if (node instanceof TmplAstElement) {
|
||||||
|
return this.getSymbolOfElement(node);
|
||||||
|
} else if (node instanceof TmplAstTemplate) {
|
||||||
|
return this.getSymbolOfAstTemplate(node);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSymbolOfAstTemplate(template: TmplAstTemplate): TemplateSymbol|null {
|
||||||
|
const directives = this.getDirectivesOfNode(template);
|
||||||
|
return {kind: SymbolKind.Template, directives};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSymbolOfElement(element: TmplAstElement): ElementSymbol|null {
|
||||||
|
const elementSourceSpan = element.startSourceSpan ?? element.sourceSpan;
|
||||||
|
|
||||||
|
const node = findFirstMatchingNode(
|
||||||
|
this.typeCheckBlock, {withSpan: elementSourceSpan, filter: ts.isVariableDeclaration});
|
||||||
|
if (node === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbolFromDeclaration = this.getSymbolOfVariableDeclaration(node);
|
||||||
|
if (symbolFromDeclaration === null || symbolFromDeclaration.tsSymbol === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directives = this.getDirectivesOfNode(element);
|
||||||
|
// All statements in the TCB are `Expression`s that optionally include more information.
|
||||||
|
// An `ElementSymbol` uses the information returned for the variable declaration expression,
|
||||||
|
// adds the directives for the element, and updates the `kind` to be `SymbolKind.Element`.
|
||||||
|
return {
|
||||||
|
...symbolFromDeclaration,
|
||||||
|
kind: SymbolKind.Element,
|
||||||
|
directives,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDirectivesOfNode(element: TmplAstElement|TmplAstTemplate): DirectiveSymbol[] {
|
||||||
|
const elementSourceSpan = element.startSourceSpan ?? element.sourceSpan;
|
||||||
|
const tcbSourceFile = this.typeCheckBlock.getSourceFile();
|
||||||
|
const isDirectiveDeclaration = (node: ts.Node): node is ts.TypeNode => ts.isTypeNode(node) &&
|
||||||
|
hasExpressionIdentifier(tcbSourceFile, node, ExpressionIdentifier.DIRECTIVE);
|
||||||
|
|
||||||
|
const nodes = findAllMatchingNodes(
|
||||||
|
this.typeCheckBlock, {withSpan: elementSourceSpan, filter: isDirectiveDeclaration});
|
||||||
|
return nodes
|
||||||
|
.map(node => {
|
||||||
|
const symbol = this.getSymbolOfTsNode(node);
|
||||||
|
if (symbol === null || symbol.tsSymbol === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const directiveSymbol:
|
||||||
|
DirectiveSymbol = {...symbol, tsSymbol: symbol.tsSymbol, kind: SymbolKind.Directive};
|
||||||
|
return directiveSymbol;
|
||||||
|
})
|
||||||
|
.filter((d): d is DirectiveSymbol => d !== null);
|
||||||
|
}
|
||||||
|
|
||||||
private getSymbolOfBoundEvent(eventBinding: TmplAstBoundEvent): OutputBindingSymbol|null {
|
private getSymbolOfBoundEvent(eventBinding: TmplAstBoundEvent): OutputBindingSymbol|null {
|
||||||
// Outputs are a `ts.CallExpression` that look like one of the two:
|
// Outputs are a `ts.CallExpression` that look like one of the two:
|
||||||
// * _outputHelper(_t1["outputField"]).subscribe(handler);
|
// * _outputHelper(_t1["outputField"]).subscribe(handler);
|
||||||
|
@ -110,10 +168,9 @@ export class SymbolBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
const consumer = this.templateData.boundTarget.getConsumerOfBinding(attributeBinding);
|
const consumer = this.templateData.boundTarget.getConsumerOfBinding(attributeBinding);
|
||||||
let target: ElementSymbol|DirectiveSymbol|null;
|
let target: ElementSymbol|TemplateSymbol|DirectiveSymbol|null;
|
||||||
if (consumer instanceof TmplAstTemplate || consumer instanceof TmplAstElement) {
|
if (consumer instanceof TmplAstTemplate || consumer instanceof TmplAstElement) {
|
||||||
// TODO(atscott): handle bindings to elements and templates
|
target = this.getSymbol(consumer);
|
||||||
target = null;
|
|
||||||
} else {
|
} else {
|
||||||
target = this.getDirectiveSymbolForAccessExpression(node.left);
|
target = this.getDirectiveSymbolForAccessExpression(node.left);
|
||||||
}
|
}
|
||||||
|
@ -154,15 +211,15 @@ export class SymbolBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
const symbol = this.getSymbolOfVariableDeclaration(declaration);
|
const symbol = this.getSymbolOfVariableDeclaration(declaration);
|
||||||
if (symbol === null || symbol.tsSymbol === null || symbol.tsType === null) {
|
if (symbol === null || symbol.tsSymbol === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...symbol,
|
|
||||||
kind: SymbolKind.Directive,
|
kind: SymbolKind.Directive,
|
||||||
tsSymbol: symbol.tsSymbol,
|
tsSymbol: symbol.tsSymbol,
|
||||||
tsType: symbol.tsType,
|
tsType: symbol.tsType,
|
||||||
|
shimLocation: symbol.shimLocation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,69 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TmplAstBoundAttribute, TmplAstElement, TmplAstTemplate} from '@angular/compiler';
|
import {TmplAstBoundAttribute, TmplAstElement, TmplAstNode, 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 {InputBindingSymbol, OutputBindingSymbol, Symbol, SymbolKind, TypeCheckingConfig} from '../api';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
import {DirectiveSymbol, ElementSymbol, InputBindingSymbol, OutputBindingSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig} from '../api';
|
||||||
|
|
||||||
import {getClass, ngForDeclaration, ngForTypeCheckTarget, setup as baseTestSetup, TypeCheckingTarget} from './test_utils';
|
import {getClass, ngForDeclaration, ngForTypeCheckTarget, setup as baseTestSetup, TypeCheckingTarget} from './test_utils';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
describe('TemplateTypeChecker.getSymbolOfNodeInComponentTemplate', () => {
|
describe('TemplateTypeChecker.getSymbolOfNode', () => {
|
||||||
|
describe('templates', () => {
|
||||||
|
describe('ng-templates', () => {
|
||||||
|
let templateTypeChecker: TemplateTypeChecker;
|
||||||
|
let cmp: ClassDeclaration<ts.ClassDeclaration>;
|
||||||
|
let templateNode: TmplAstTemplate;
|
||||||
|
let program: ts.Program;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const fileName = absoluteFrom('/main.ts');
|
||||||
|
const dirFile = absoluteFrom('/dir.ts');
|
||||||
|
const templateString = `
|
||||||
|
<ng-template dir #ref0 #ref1="dir" let-contextFoo="bar">
|
||||||
|
<div [input0]="contextFoo" [input1]="ref0" [input2]="ref1"></div>
|
||||||
|
</ng-template>`;
|
||||||
|
const testValues = setup([
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
templates: {'Cmp': templateString},
|
||||||
|
source: `
|
||||||
|
export class Cmp { }`,
|
||||||
|
declarations: [{
|
||||||
|
name: 'TestDir',
|
||||||
|
selector: '[dir]',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
exportAs: ['dir'],
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileName: dirFile,
|
||||||
|
source: `export class TestDir {}`,
|
||||||
|
templates: {},
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
templateTypeChecker = testValues.templateTypeChecker;
|
||||||
|
program = testValues.program;
|
||||||
|
const sf = getSourceFileOrError(testValues.program, fileName);
|
||||||
|
cmp = getClass(sf, 'Cmp');
|
||||||
|
templateNode = getAstTemplates(templateTypeChecker, cmp)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get symbol for the template itself', () => {
|
||||||
|
const symbol = templateTypeChecker.getSymbolOfNode(templateNode, cmp)!;
|
||||||
|
assertTemplateSymbol(symbol);
|
||||||
|
expect(symbol.directives.length).toBe(1);
|
||||||
|
assertDirectiveSymbol(symbol.directives[0]);
|
||||||
|
expect(symbol.directives[0].tsSymbol.getName()).toBe('TestDir');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('input bindings', () => {
|
describe('input bindings', () => {
|
||||||
it('can retrieve a symbol for an input binding', () => {
|
it('can retrieve a symbol for an input binding', () => {
|
||||||
const fileName = absoluteFrom('/main.ts');
|
const fileName = absoluteFrom('/main.ts');
|
||||||
|
@ -473,9 +525,117 @@ runInEachFileSystem(() => {
|
||||||
expect(symbol).toBeNull();
|
expect(symbol).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('for elements', () => {
|
||||||
|
it('for elements that are components with no inputs', () => {
|
||||||
|
const fileName = absoluteFrom('/main.ts');
|
||||||
|
const dirFile = absoluteFrom('/dir.ts');
|
||||||
|
const {program, templateTypeChecker} = setup(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
templates: {'Cmp': `<child-component></child-component>`},
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
name: 'ChildComponent',
|
||||||
|
selector: 'child-component',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileName: dirFile,
|
||||||
|
source: `
|
||||||
|
export class ChildComponent {}
|
||||||
|
`,
|
||||||
|
templates: {'ChildComponent': ''},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const sf = getSourceFileOrError(program, fileName);
|
||||||
|
const cmp = getClass(sf, 'Cmp');
|
||||||
|
|
||||||
|
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||||
|
|
||||||
|
const symbol = templateTypeChecker.getSymbolOfNode(nodes[0] as TmplAstElement, cmp)!;
|
||||||
|
assertElementSymbol(symbol);
|
||||||
|
expect(symbol.directives.length).toBe(1);
|
||||||
|
assertDirectiveSymbol(symbol.directives[0]);
|
||||||
|
expect(program.getTypeChecker().typeToString(symbol.directives[0].tsType))
|
||||||
|
.toEqual('ChildComponent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('element with directive matches', () => {
|
||||||
|
const fileName = absoluteFrom('/main.ts');
|
||||||
|
const dirFile = absoluteFrom('/dir.ts');
|
||||||
|
const {program, templateTypeChecker} = setup(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
templates: {'Cmp': `<div dir dir2></div>`},
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
name: 'TestDir',
|
||||||
|
selector: '[dir]',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'TestDir2',
|
||||||
|
selector: '[dir2]',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'TestDirAllDivs',
|
||||||
|
selector: 'div',
|
||||||
|
file: dirFile,
|
||||||
|
type: 'directive',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileName: dirFile,
|
||||||
|
source: `
|
||||||
|
export class TestDir {}
|
||||||
|
export class TestDir2 {}
|
||||||
|
export class TestDirAllDivs {}
|
||||||
|
`,
|
||||||
|
templates: {},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const sf = getSourceFileOrError(program, fileName);
|
||||||
|
const cmp = getClass(sf, 'Cmp');
|
||||||
|
|
||||||
|
const nodes = templateTypeChecker.getTemplate(cmp)!;
|
||||||
|
|
||||||
|
const symbol = templateTypeChecker.getSymbolOfNode(nodes[0] as TmplAstElement, cmp)!;
|
||||||
|
assertElementSymbol(symbol);
|
||||||
|
expect(symbol.directives.length).toBe(3);
|
||||||
|
const expectedDirectives = ['TestDir', 'TestDir2', 'TestDirAllDivs'].sort();
|
||||||
|
const actualDirectives =
|
||||||
|
symbol.directives.map(dir => program.getTypeChecker().typeToString(dir.tsType)).sort();
|
||||||
|
expect(actualDirectives).toEqual(expectedDirectives);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onlyAstTemplates(nodes: TmplAstNode[]): TmplAstTemplate[] {
|
||||||
|
return nodes.filter((n): n is TmplAstTemplate => n instanceof TmplAstTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAstTemplates(
|
||||||
|
templateTypeChecker: TemplateTypeChecker, cmp: ts.ClassDeclaration&{name: ts.Identifier}) {
|
||||||
|
return onlyAstTemplates(templateTypeChecker.getTemplate(cmp)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDirectiveSymbol(tSymbol: Symbol): asserts tSymbol is DirectiveSymbol {
|
||||||
|
expect(tSymbol.kind).toEqual(SymbolKind.Directive);
|
||||||
|
}
|
||||||
|
|
||||||
function assertInputBindingSymbol(tSymbol: Symbol): asserts tSymbol is InputBindingSymbol {
|
function assertInputBindingSymbol(tSymbol: Symbol): asserts tSymbol is InputBindingSymbol {
|
||||||
expect(tSymbol.kind).toEqual(SymbolKind.Input);
|
expect(tSymbol.kind).toEqual(SymbolKind.Input);
|
||||||
}
|
}
|
||||||
|
@ -484,6 +644,14 @@ function assertOutputBindingSymbol(tSymbol: Symbol): asserts tSymbol is OutputBi
|
||||||
expect(tSymbol.kind).toEqual(SymbolKind.Output);
|
expect(tSymbol.kind).toEqual(SymbolKind.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertTemplateSymbol(tSymbol: Symbol): asserts tSymbol is TemplateSymbol {
|
||||||
|
expect(tSymbol.kind).toEqual(SymbolKind.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertElementSymbol(tSymbol: Symbol): asserts tSymbol is ElementSymbol {
|
||||||
|
expect(tSymbol.kind).toEqual(SymbolKind.Element);
|
||||||
|
}
|
||||||
|
|
||||||
export function setup(targets: TypeCheckingTarget[], config?: Partial<TypeCheckingConfig>) {
|
export function setup(targets: TypeCheckingTarget[], config?: Partial<TypeCheckingConfig>) {
|
||||||
return baseTestSetup(
|
return baseTestSetup(
|
||||||
targets, {inlining: false, config: {...config, enableTemplateTypeChecker: true}});
|
targets, {inlining: false, config: {...config, enableTemplateTypeChecker: true}});
|
||||||
|
|
Loading…
Reference in New Issue