refactor(compiler-cli): don't use FakeEnvironment for tcb tests (#41043)

For the tests in //packages/compiler-cli/src/ngtsc/typecheck, this
commits uses a `TypeCheckFile` for the environment, rather than a
`FakeEnvironment`. Using a real environment gives us more flexibility
with testing.

PR Close #41043
This commit is contained in:
Zach Arend 2021-02-24 14:21:59 -08:00 committed by Misko Hevery
parent 02e8901d9e
commit c267c680d8
6 changed files with 96 additions and 63 deletions

View File

@ -385,7 +385,8 @@ export class TypeCheckContextImpl implements TypeCheckContext {
path: pendingShimData.file.fileName,
templates: pendingShimData.templates,
});
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
updates.set(
pendingShimData.file.fileName, pendingShimData.file.render(false /* removeComments */));
}
}

View File

@ -49,12 +49,12 @@ export class TypeCheckFile extends Environment {
this.tcbStatements.push(fn);
}
render(): string {
render(removeComments: boolean): string {
let source: string = this.importManager.getAllImports(this.contextFile.fileName)
.map(i => `import * as ${i.qualifier.text} from '${i.specifier}';`)
.join('\n') +
'\n\n';
const printer = ts.createPrinter();
const printer = ts.createPrinter({removeComments});
source += '\n';
for (const stmt of this.pipeInstStatements) {
source += printer.printNode(ts.EmitHint.Unspecified, stmt, this.contextFile) + '\n';

View File

@ -6,9 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {initMockFileSystem} from '../../file_system/testing';
import {tcb, TestDeclaration} from './test_utils';
describe('type check blocks diagnostics', () => {
beforeEach(() => initMockFileSystem('Native'));
describe('parse spans', () => {
it('should annotate unary ops', () => {
expect(tcbWithSpans('{{ -a }}')).toContain('(-((ctx).a /*4,5*/) /*4,5*/) /*3,5*/');
@ -146,8 +149,9 @@ describe('type check blocks diagnostics', () => {
pipeName: 'test',
}];
const block = tcbWithSpans(TEMPLATE, PIPES);
expect(block).toContain('var _pipe1: i0.TestPipe = null!');
expect(block).toContain(
'((null as TestPipe).transform /*7,11*/(((ctx).a /*3,4*/) /*3,4*/, ((ctx).b /*12,13*/) /*12,13*/) /*3,13*/);');
'(_pipe1.transform /*7,11*/(((ctx).a /*3,4*/) /*3,4*/, ((ctx).b /*12,13*/) /*12,13*/) /*3,13*/);');
});
describe('attaching multiple comments for multiple references', () => {

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError, LogicalFileSystem} from '../../file_system';
import {TestFile} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter, RelativePathStrategy} from '../../imports';
import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata';
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
@ -28,6 +28,7 @@ import {Environment} from '../src/environment';
import {OutOfBandDiagnosticRecorder} from '../src/oob';
import {TypeCheckShimGenerator} from '../src/shim';
import {generateTypeCheckBlock} from '../src/type_check_block';
import {TypeCheckFile} from '../src/type_check_file';
export function typescriptLibDts(): TestFile {
return {
@ -201,6 +202,7 @@ export type TestDirective = Partial<Pick<
coercedInputFields?: string[], restrictedInputFields?: string[],
stringLiteralInputFields?: string[], undeclaredInputFields?: string[], isGeneric?: boolean;
};
export type TestPipe = {
name: string,
file?: AbsoluteFsPath, pipeName: string, type: 'pipe',
@ -212,9 +214,14 @@ export function tcb(
template: string, declarations: TestDeclaration[] = [], config?: TypeCheckingConfig,
options?: {emitSpans?: boolean}): string {
const classes = ['Test', ...declarations.map(decl => decl.name)];
const code = classes.map(name => `class ${name}<T extends string> {}`).join('\n');
const code = classes.map(name => `export class ${name}<T extends string> {}`).join('\n');
const sf = ts.createSourceFile('synthetic.ts', code, ts.ScriptTarget.Latest, true);
const rootFilePath = absoluteFrom('/synthetic.ts');
const {program, host} = makeProgram([
{name: rootFilePath, contents: code, isRoot: true},
]);
const sf = getSourceFileOrError(program, rootFilePath);
const clazz = getClass(sf, 'Test');
const templateUrl = 'synthetic.html';
const {nodes} = parseTemplate(template, templateUrl);
@ -251,13 +258,25 @@ export function tcb(
emitSpans: false,
};
const tcb = generateTypeCheckBlock(
FakeEnvironment.newFake(config), new Reference(clazz), ts.createIdentifier('Test_TCB'), meta,
new NoopSchemaChecker(), new NoopOobRecorder());
const fileName = absoluteFrom('/type-check-file.ts');
const removeComments = !options.emitSpans;
const res = ts.createPrinter({removeComments}).printNode(ts.EmitHint.Unspecified, tcb, sf);
return res.replace(/\s+/g, ' ');
const reflectionHost = new TypeScriptReflectionHost(program.getTypeChecker());
const refEmmiter: ReferenceEmitter = new ReferenceEmitter(
[new LocalIdentifierStrategy(), new RelativePathStrategy(reflectionHost)]);
const env = new TypeCheckFile(fileName, config, refEmmiter, reflectionHost, host);
const ref = new Reference(clazz);
const tcb = generateTypeCheckBlock(
env, ref, ts.createIdentifier('Test_TCB'), meta, new NoopSchemaChecker(),
new NoopOobRecorder());
env.addTypeCheckBlock(ref, meta, new NoopSchemaChecker(), new NoopOobRecorder());
const rendered = env.render(!options.emitSpans /* removeComments */);
return rendered.replace(/\s+/g, ' ');
}
/**

View File

@ -6,12 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {initMockFileSystem} from '../../file_system/testing';
import {TypeCheckingConfig} from '../api';
import {ALL_ENABLED_CONFIG, tcb, TestDeclaration, TestDirective} from './test_utils';
describe('type check blocks', () => {
beforeEach(() => initMockFileSystem('Native'));
it('should generate a basic block for a binding', () => {
expect(tcb('{{hello}} {{world}}')).toContain('"" + (((ctx).hello)) + (((ctx).world));');
});
@ -60,7 +63,7 @@ describe('type check blocks', () => {
selector: '[dir]',
inputs: {inputA: 'inputA'},
}];
expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t1: DirA = null!; _t1.inputA = ("value");');
expect(tcb(TEMPLATE, DIRECTIVES)).toContain('_t1: i0.DirA = null!; _t1.inputA = ("value");');
});
it('should handle multiple bindings to the same property', () => {
@ -133,9 +136,11 @@ describe('type check blocks', () => {
},
isGeneric: true,
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1 = Dir.ngTypeCtor({ "fieldA": (((ctx).foo)), "fieldB": null as any });');
const actual = tcb(TEMPLATE, DIRECTIVES);
expect(actual).toContain(
'const _ctor1: <T extends string = any>(init: Pick<i0.Dir<T>, "fieldA" | "fieldB">) => i0.Dir<T> = null!;');
expect(actual).toContain(
'var _t1 = _ctor1({ "fieldA": (((ctx).foo)), "fieldB": null as any });');
});
it('should handle multiple bindings to the same property', () => {
@ -183,11 +188,14 @@ describe('type check blocks', () => {
inputs: {input: 'input'},
isGeneric: true,
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t2 = Dir.ngTypeCtor({ "input": (null!) }); ' +
'var _t1 = _t2; ' +
'_t2.input = (_t1);');
const actual = tcb(TEMPLATE, DIRECTIVES);
expect(actual).toContain(
'const _ctor1: <T extends string = any>(init: Pick<i0.Dir<T>, "input">) => i0.Dir<T> = null!;');
expect(actual).toContain(
'var _t2 = _ctor1({ "input": (null!) }); ' +
'var _t1 = _t2; ' +
'_t2.input = (_t1);');
});
it('should generate circular references between two directives correctly', () => {
@ -213,14 +221,16 @@ describe('type check blocks', () => {
isGeneric: true,
}
];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t4 = DirA.ngTypeCtor({ "inputA": (null!) }); ' +
'var _t3 = _t4; ' +
'var _t2 = DirB.ngTypeCtor({ "inputB": (_t3) }); ' +
'var _t1 = _t2; ' +
'_t4.inputA = (_t1); ' +
'_t2.inputB = (_t3);');
const actual = tcb(TEMPLATE, DIRECTIVES);
expect(actual).toContain(
'const _ctor1: <T extends string = any>(init: Pick<i0.DirA<T>, "inputA">) => i0.DirA<T> = null!; const _ctor2: <T extends string = any>(init: Pick<i0.DirB<T>, "inputB">) => i0.DirB<T> = null!;');
expect(actual).toContain(
'var _t4 = _ctor1({ "inputA": (null!) }); ' +
'var _t3 = _t4; ' +
'var _t2 = _ctor2({ "inputB": (_t3) }); ' +
'var _t1 = _t2; ' +
'_t4.inputA = (_t1); ' +
'_t2.inputB = (_t3);');
});
it('should handle empty bindings', () => {
@ -261,7 +271,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: typeof Dir.ngAcceptInputType_fieldA = null!; ' +
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
});
});
@ -321,11 +331,11 @@ describe('type check blocks', () => {
},
];
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1: HasInput = null!');
expect(block).toContain('var _t1: i0.HasInput = null!');
expect(block).toContain('_t1.input = (((ctx).value));');
expect(block).toContain('var _t2: HasOutput = null!');
expect(block).toContain('var _t2: i0.HasOutput = null!');
expect(block).toContain('_t2["output"]');
expect(block).toContain('var _t4: HasReference = null!');
expect(block).toContain('var _t4: i0.HasReference = null!');
expect(block).toContain('var _t3 = _t4;');
expect(block).toContain('(_t3).a');
expect(block).not.toContain('NoBindings');
@ -355,7 +365,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t2: Dir = null!; ' +
'var _t2: i0.Dir = null!; ' +
'var _t1 = _t2; ' +
'"" + (((_t1).value));');
});
@ -403,7 +413,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t2: Dir = null!; ' +
'var _t2: i0.Dir = null!; ' +
'var _t1 = _t2; ' +
'_t2.input = (_t1);');
});
@ -431,9 +441,9 @@ describe('type check blocks', () => {
];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t2: DirB = null!; ' +
'var _t2: i0.DirB = null!; ' +
'var _t1 = _t2; ' +
'var _t3: DirA = null!; ' +
'var _t3: i0.DirA = null!; ' +
'_t3.inputA = (_t1); ' +
'var _t4 = _t3; ' +
'_t2.inputA = (_t4);');
@ -468,7 +478,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'var _t2: typeof _t1["fieldA"] = null!; ' +
'_t2 = (((ctx).foo)); ');
});
@ -487,7 +497,7 @@ describe('type check blocks', () => {
}];
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'_t1["some-input.xs"] = (((ctx).foo)); ');
});
@ -504,7 +514,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'_t1.field2 = _t1.field1 = (((ctx).foo));');
});
@ -523,8 +533,8 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: typeof Dir.ngAcceptInputType_field1 = null!; ' +
'var _t2: Dir = null!; ' +
'var _t1: typeof i0.Dir.ngAcceptInputType_field1 = null!; ' +
'var _t2: i0.Dir = null!; ' +
'_t2.field2 = _t1 = (((ctx).foo));');
});
@ -543,7 +553,7 @@ describe('type check blocks', () => {
}];
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'_t1.field2 = (((ctx).foo));');
});
@ -561,7 +571,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).not.toContain('var _t1: Dir = null!;');
expect(block).toContain(
'var _t1: typeof Dir.ngAcceptInputType_fieldA = null!; ' +
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
});
@ -580,7 +590,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).not.toContain('var _t1: Dir = null!;');
expect(block).toContain(
'var _t1: typeof Dir.ngAcceptInputType_fieldA = null!; ' +
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
});
@ -619,7 +629,7 @@ describe('type check blocks', () => {
}];
const TEMPLATE = `<div *ngIf="person">{{person.name}}</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('if (NgIf.ngTemplateGuard_ngIf(_t1, ((ctx).person)))');
expect(block).toContain('if (i0.NgIf.ngTemplateGuard_ngIf(_t1, ((ctx).person)))');
});
it('should emit binding guards', () => {
@ -672,8 +682,7 @@ describe('type check blocks', () => {
it('should emit a listener function with AnimationEvent for animation events', () => {
const TEMPLATE = `<div (@animation.done)="foo($event)"></div>`;
const block = tcb(TEMPLATE);
expect(block).toContain(
'function ($event: animations.AnimationEvent): any { (ctx).foo($event); }');
expect(block).toContain('function ($event: i1.AnimationEvent): any { (ctx).foo($event); }');
});
it('should emit addEventListener calls for unclaimed outputs', () => {
@ -740,7 +749,7 @@ describe('type check blocks', () => {
describe('config.applyTemplateContextGuards', () => {
const TEMPLATE = `<div *dir>{{ value }}</div>`;
const GUARD_APPLIED = 'if (Dir.ngTemplateContextGuard(';
const GUARD_APPLIED = 'if (i0.Dir.ngTemplateContextGuard(';
it('should apply template context guards when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
@ -769,13 +778,13 @@ describe('type check blocks', () => {
it('generates a references var when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1 = (_t2 as any as core.TemplateRef<any>);');
expect(block).toContain('var _t1 = (_t2 as any as i1.TemplateRef<any>);');
});
it('generates a reference var when disabled', () => {
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTemplateBodies: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('var _t1 = (_t2 as any as core.TemplateRef<any>);');
expect(block).toContain('var _t1 = (_t2 as any as i1.TemplateRef<any>);');
});
});
@ -848,8 +857,7 @@ describe('type check blocks', () => {
it('should check types of animation events when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'function ($event: animations.AnimationEvent): any { (ctx).foo($event); }');
expect(block).toContain('function ($event: i1.AnimationEvent): any { (ctx).foo($event); }');
});
it('should not check types of animation events when disabled', () => {
const DISABLED_CONFIG:
@ -919,7 +927,7 @@ describe('type check blocks', () => {
it('should trace references to an <ng-template> when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'var _t3 = (_t4 as any as core.TemplateRef<any>); ' +
'var _t3 = (_t4 as any as i1.TemplateRef<any>); ' +
'"" + (((_t3).value2));');
});
@ -968,13 +976,14 @@ describe('type check blocks', () => {
it('should check types of pipes when enabled', () => {
const block = tcb(TEMPLATE, PIPES);
expect(block).toContain('(null as TestPipe).transform(((ctx).a), ((ctx).b), ((ctx).c))');
expect(block).toContain('var _pipe1: i0.TestPipe = null!;');
expect(block).toContain('(_pipe1.transform(((ctx).a), ((ctx).b), ((ctx).c)));');
});
it('should not check types of pipes when disabled', () => {
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfPipes: false};
const block = tcb(TEMPLATE, PIPES, DISABLED_CONFIG);
expect(block).toContain(
'((null as TestPipe) as any).transform(((ctx).a), ((ctx).b), ((ctx).c))');
expect(block).toContain('var _pipe1: i0.TestPipe = null!;');
expect(block).toContain('((_pipe1 as any).transform(((ctx).a), ((ctx).b), ((ctx).c)));');
});
});
@ -1016,13 +1025,13 @@ describe('type check blocks', () => {
it('should use the generic type of the context when enabled', () => {
const block = tcb(TEMPLATE);
expect(block).toContain('function Test_TCB<T extends string>(ctx: Test<T>)');
expect(block).toContain('function _tcb1<T extends string>(ctx: i0.Test<T>)');
});
it('should use any for the context generic type when disabled', () => {
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, useContextGenericType: false};
const block = tcb(TEMPLATE, undefined, DISABLED_CONFIG);
expect(block).toContain('function Test_TCB(ctx: Test<any>)');
expect(block).toContain('function _tcb1(ctx: i0.Test<any>)');
});
});
@ -1045,7 +1054,7 @@ describe('type check blocks', () => {
TypeCheckingConfig = {...BASE_CONFIG, honorAccessModifiersForInputBindings: true};
const block = tcb(TEMPLATE, DIRECTIVES, enableChecks);
expect(block).toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'_t1["some-input.xs"] = (((ctx).foo)); ');
});
@ -1063,7 +1072,7 @@ describe('type check blocks', () => {
TypeCheckingConfig = {...BASE_CONFIG, honorAccessModifiersForInputBindings: true};
const block = tcb(TEMPLATE, DIRECTIVES, enableChecks);
expect(block).toContain(
'var _t1: Dir = null!; ' +
'var _t1: i0.Dir = null!; ' +
'_t1.fieldA = (((ctx).foo)); ');
});
});

View File

@ -43,7 +43,7 @@ runInEachFileSystem(() => {
const file = new TypeCheckFile(
_('/_typecheck_.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([]),
/* reflector */ null!, host);
const sf = file.render();
const sf = file.render(false /* removeComments */);
expect(sf).toContain('export const IS_A_MODULE = true;');
});