From 8f3dd8560034cd7a85badd40fc0c966187466c62 Mon Sep 17 00:00:00 2001 From: JoostK Date: Mon, 15 Apr 2019 20:21:54 +0200 Subject: [PATCH] refactor(ivy): move ngtsc's TCB generation test util to separate file (#30181) PR Close #30181 --- .../src/ngtsc/typecheck/test/test_utils.ts | 130 ++++++++++++++++++ .../typecheck/test/type_check_block_spec.ts | 122 +--------------- 2 files changed, 132 insertions(+), 120 deletions(-) create mode 100644 packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts new file mode 100644 index 0000000000..20ce5b77aa --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts @@ -0,0 +1,130 @@ +/** + * @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 + */ + +import {CssSelector, R3TargetBinder, SelectorMatcher, parseTemplate} from '@angular/compiler'; +import * as ts from 'typescript'; + +import {Reference} from '../../imports'; +import {ClassDeclaration, isNamedClassDeclaration} from '../../reflection'; +import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../src/api'; +import {Environment} from '../src/environment'; +import {generateTypeCheckBlock} from '../src/type_check_block'; + +// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead. +export type TestDirective = + Partial>>& + {selector: string, name: string, type: 'directive'}; +export type TestPipe = { + name: string, + pipeName: string, + type: 'pipe', +}; + +export type TestDeclaration = TestDirective | TestPipe; + +export function tcb( + template: string, declarations: TestDeclaration[] = [], config?: TypeCheckingConfig): string { + const classes = ['Test', ...declarations.map(decl => decl.name)]; + const code = classes.map(name => `class ${name} {}`).join('\n'); + + const sf = ts.createSourceFile('synthetic.ts', code, ts.ScriptTarget.Latest, true); + const clazz = getClass(sf, 'Test'); + const {nodes} = parseTemplate(template, 'synthetic.html'); + const matcher = new SelectorMatcher(); + + for (const decl of declarations) { + if (decl.type !== 'directive') { + continue; + } + const selector = CssSelector.parse(decl.selector); + const meta: TypeCheckableDirectiveMeta = { + name: decl.name, + ref: new Reference(getClass(sf, decl.name)), + exportAs: decl.exportAs || null, + hasNgTemplateContextGuard: decl.hasNgTemplateContextGuard || false, + inputs: decl.inputs || {}, + isComponent: decl.isComponent || false, + ngTemplateGuards: decl.ngTemplateGuards || [], + outputs: decl.outputs || {}, + queries: decl.queries || [], + }; + matcher.addSelectables(selector, meta); + } + + const binder = new R3TargetBinder(matcher); + const boundTarget = binder.bind({template: nodes}); + + const pipes = new Map>>(); + for (const decl of declarations) { + if (decl.type === 'pipe') { + pipes.set(decl.pipeName, new Reference(getClass(sf, decl.name))); + } + } + + const meta: TypeCheckBlockMetadata = {boundTarget, pipes}; + + config = config || { + applyTemplateContextGuards: true, + checkQueries: false, + checkTypeOfBindings: true, + checkTypeOfPipes: true, + checkTemplateBodies: true, + strictSafeNavigationTypes: true, + }; + + const tcb = generateTypeCheckBlock( + FakeEnvironment.newFake(config), new Reference(clazz), ts.createIdentifier('Test_TCB'), meta); + + const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tcb, sf); + return res.replace(/\s+/g, ' '); +} + +function getClass(sf: ts.SourceFile, name: string): ClassDeclaration { + for (const stmt of sf.statements) { + if (isNamedClassDeclaration(stmt) && stmt.name.text === name) { + return stmt; + } + } + throw new Error(`Class ${name} not found in file`); +} + +class FakeEnvironment /* implements Environment */ { + constructor(readonly config: TypeCheckingConfig) {} + + typeCtorFor(dir: TypeCheckableDirectiveMeta): ts.Expression { + return ts.createPropertyAccess(ts.createIdentifier(dir.name), 'ngTypeCtor'); + } + + pipeInst(ref: Reference>): ts.Expression { + return ts.createParen(ts.createAsExpression(ts.createNull(), this.referenceType(ref))); + } + + reference(ref: Reference>): ts.Expression { + return ref.node.name; + } + + referenceType(ref: Reference>): ts.TypeNode { + return ts.createTypeReferenceNode(ref.node.name, /* typeArguments */ undefined); + } + + referenceCoreType(name: string, typeParamCount: number = 0): ts.TypeNode { + const typeArgs: ts.TypeNode[] = []; + for (let i = 0; i < typeParamCount; i++) { + typeArgs.push(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); + } + + const qName = ts.createQualifiedName(ts.createIdentifier('ng'), name); + return ts.createTypeReferenceNode(qName, typeParamCount > 0 ? typeArgs : undefined); + } + + getPreludeStatements(): ts.Statement[] { return []; } + + static newFake(config: TypeCheckingConfig): Environment { + return new FakeEnvironment(config) as Environment; + } +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index d215831881..971c806f2e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -6,14 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {CssSelector, R3TargetBinder, SelectorMatcher, parseTemplate} from '@angular/compiler'; -import * as ts from 'typescript'; +import {TypeCheckingConfig} from '../src/api'; -import {Reference} from '../../imports'; -import {ClassDeclaration, isNamedClassDeclaration} from '../../reflection'; -import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../src/api'; -import {Environment} from '../src/environment'; -import {generateTypeCheckBlock} from '../src/type_check_block'; +import {TestDeclaration, TestDirective, tcb} from './test_utils'; describe('type check blocks', () => { @@ -272,116 +267,3 @@ it('should generate circular references between two directives correctly', () => 'var _t3 = DirB.ngTypeCtor({ inputA: (null!) }); ' + 'var _t2 = DirA.ngTypeCtor({ inputA: _t3 });'); }); - -function getClass(sf: ts.SourceFile, name: string): ClassDeclaration { - for (const stmt of sf.statements) { - if (isNamedClassDeclaration(stmt) && stmt.name.text === name) { - return stmt; - } - } - throw new Error(`Class ${name} not found in file`); -} - -// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead. -type TestDirective = - Partial>>& - {selector: string, name: string, type: 'directive'}; -type TestPipe = { - name: string, - pipeName: string, - type: 'pipe', -}; - -type TestDeclaration = TestDirective | TestPipe; - -function tcb( - template: string, declarations: TestDeclaration[] = [], config?: TypeCheckingConfig): string { - const classes = ['Test', ...declarations.map(decl => decl.name)]; - const code = classes.map(name => `class ${name} {}`).join('\n'); - - const sf = ts.createSourceFile('synthetic.ts', code, ts.ScriptTarget.Latest, true); - const clazz = getClass(sf, 'Test'); - const {nodes} = parseTemplate(template, 'synthetic.html'); - const matcher = new SelectorMatcher(); - - for (const decl of declarations) { - if (decl.type !== 'directive') { - continue; - } - const selector = CssSelector.parse(decl.selector); - const meta: TypeCheckableDirectiveMeta = { - name: decl.name, - ref: new Reference(getClass(sf, decl.name)), - exportAs: decl.exportAs || null, - hasNgTemplateContextGuard: decl.hasNgTemplateContextGuard || false, - inputs: decl.inputs || {}, - isComponent: decl.isComponent || false, - ngTemplateGuards: decl.ngTemplateGuards || [], - outputs: decl.outputs || {}, - queries: decl.queries || [], - }; - matcher.addSelectables(selector, meta); - } - - const binder = new R3TargetBinder(matcher); - const boundTarget = binder.bind({template: nodes}); - - const pipes = new Map>>(); - for (const decl of declarations) { - if (decl.type === 'pipe') { - pipes.set(decl.pipeName, new Reference(getClass(sf, decl.name))); - } - } - - const meta: TypeCheckBlockMetadata = {boundTarget, pipes}; - - config = config || { - applyTemplateContextGuards: true, - checkQueries: false, - checkTypeOfBindings: true, - checkTypeOfPipes: true, - checkTemplateBodies: true, - strictSafeNavigationTypes: true, - }; - - const tcb = generateTypeCheckBlock( - FakeEnvironment.newFake(config), new Reference(clazz), ts.createIdentifier('Test_TCB'), meta); - - const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tcb, sf); - return res.replace(/\s+/g, ' '); -} - -class FakeEnvironment /* implements Environment */ { - constructor(readonly config: TypeCheckingConfig) {} - - typeCtorFor(dir: TypeCheckableDirectiveMeta): ts.Expression { - return ts.createPropertyAccess(ts.createIdentifier(dir.name), 'ngTypeCtor'); - } - - pipeInst(ref: Reference>): ts.Expression { - return ts.createParen(ts.createAsExpression(ts.createNull(), this.referenceType(ref))); - } - - reference(ref: Reference>): ts.Expression { - return ref.node.name; - } - - referenceType(ref: Reference>): ts.TypeNode { - return ts.createTypeReferenceNode(ref.node.name, /* typeArguments */ undefined); - } - - referenceCoreType(name: string, typeParamCount: number = 0): ts.TypeNode { - const typeArgs: ts.TypeNode[] = []; - for (let i = 0; i < typeParamCount; i++) { - typeArgs.push(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); - } - - const qName = ts.createQualifiedName(ts.createIdentifier('ng'), name); - return ts.createTypeReferenceNode(qName, typeParamCount > 0 ? typeArgs : undefined); - } - getPreludeStatements(): ts.Statement[] { return []; } - - static newFake(config: TypeCheckingConfig): Environment { - return new FakeEnvironment(config) as Environment; - } -}