feat(compiler-cli): Support template binding to protected component members (#45823)

This commit adds support for creating template bindings to protected members within the component class.

PR Close #45823
This commit is contained in:
Zack Elliott 2022-04-29 14:47:21 -07:00 committed by Dylan Hunn
parent f0b5e830a5
commit 752ddbc165
6 changed files with 168 additions and 144 deletions

View file

@ -24,7 +24,7 @@ TCBs are not ever emitted, nor are they referenced from any other code (they're
Given a component `SomeCmp`, its TCB takes the form of a function:
```typescript
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// TCB code
}
```
@ -38,15 +38,15 @@ The component context is the theoretical component instance associated with the
For example, if `SomeCmp`'s template has an interpolation expression `{{foo.bar}}`, this suggests that `SomeCmp` has a property `foo`, and that `foo` itself is an object with a property `bar` (or in a type sense, that the type of `SomeCmp.foo` has a property `bar`).
Such a binding is expressed in the TCB function, using the `ctx` parameter as the component instance:
Such a binding is expressed in the TCB function, using the `this` parameter as the component instance:
```typescript
function tcb(ctx: SomeCmp): void {
'' + ctx.foo.bar;
function tcb(this: SomeCmp): void {
'' + this.foo.bar;
}
```
If `SomeCmp` does not have a `foo` property, then TypeScript will produce a type error/diagnostic for the expression `ctx.foo`. If `ctx.foo` does exist, but is not of a type that has a `bar` property, then TypeScript will catch that too. By mapping the template expression `{{foo.bar}}` to TypeScript code, the compiler has captured its _intent_ in the TCB in a way that TypeScript can validate.
If `SomeCmp` does not have a `foo` property, then TypeScript will produce a type error/diagnostic for the expression `this.foo`. If `this.foo` does exist, but is not of a type that has a `bar` property, then TypeScript will catch that too. By mapping the template expression `{{foo.bar}}` to TypeScript code, the compiler has captured its _intent_ in the TCB in a way that TypeScript can validate.
#### Types of template declarations
@ -62,7 +62,7 @@ declares a single `<input>` element with a local ref `#name`, meaning that withi
Within the TCB, the `<input>` element is treated as a declaration, and the compiler leverages the powerful type inference of `document.createElement`:
```typescript
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
var _t1 = document.createElement('input');
'' + _t1.value;
}
@ -83,13 +83,13 @@ Just like with HTML elements, directives present on elements within the template
The TCB for this template looks like:
```typescript
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
var _t1: OtherCmp = null!;
_t1.foo = ctx.bar;
_t1.foo = this.bar;
}
```
Since `<other-cmp>` is a component, the TCB declares `_t1` to be of that component's type. This allows for the binding `[foo]="bar"` to be expressed in TypeScript as `_t1.foo = ctx.bar` - an assignment to `OtherCmp`'s `@Input` for `foo` of the `bar` property from the template's context. TypeScript can then type check this operation and produce diagnostics if the type of `ctx.bar` is not assignable to the `_t1.foo` property which backs the `@Input`.
Since `<other-cmp>` is a component, the TCB declares `_t1` to be of that component's type. This allows for the binding `[foo]="bar"` to be expressed in TypeScript as `_t1.foo = this.bar` - an assignment to `OtherCmp`'s `@Input` for `foo` of the `bar` property from the template's context. TypeScript can then type check this operation and produce diagnostics if the type of `this.bar` is not assignable to the `_t1.foo` property which backs the `@Input`.
#### Generic directives & type constructors
@ -147,9 +147,9 @@ This type constructor can then be used to infer the instance type of a usage of
Would use the above type constructor in its TCB:
```typescript
function tcb(ctx: SomeCmp): void {
var _t1 = ctor1({ngForOf: ctx.users});
// Assuming ctx.users is User[], then _t1 is inferred as NgFor<User>.
function tcb(this: SomeCmp): void {
var _t1 = ctor1({ngForOf: this.users});
// Assuming this.users is User[], then _t1 is inferred as NgFor<User>.
}
```
@ -180,9 +180,9 @@ In the TCB, the template context of this nested template is itself a declaration
```typescript
declare function ctor1(inputs: {ngForOf?: Iterable<T>}): NgFor<T>;
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// _t1 is the NgFor directive instance, inferred as NgFor<User>.
var _t1 = ctor1({ngForOf: ctx.users});
var _t1 = ctor1({ngForOf: this.users});
// _t2 is the context type for the embedded views created by the NgFor structural directive.
var _t2: any;
@ -221,9 +221,9 @@ The `typecheck` system is aware of the presence of this method on any structural
```typescript
declare function ctor1(inputs: {ngForOf?: Iterable<T>}): NgFor<T>;
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// _t1 is the NgFor directive instance, inferred as NgFor<User>.
var _t1 = ctor1({ngForOf: ctx.users});
var _t1 = ctor1({ngForOf: this.users});
// _t2 is the context type for the embedded views created by the NgFor structural directive.
var _t2: any;
@ -258,15 +258,15 @@ Because the `NgFor` directive _declared_ to the template type checking engine wh
Obviously, if `user` is potentially `null`, then this `NgIf` is intended to only show the `<div>` when `user` actually has a value. However, from a type-checking perspective, the expression `user.name` is not legal if `user` is potentially `null`. So if this template was rendered into a TCB as:
```typescript
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// Type of the NgIf directive instance.
var _t1: NgIf;
// Binding *ngIf="user != null".
_t1.ngIf = ctx.user !== null;
_t1.ngIf = this.user !== null;
// Nested template interpolation `{{user.name}}`
'' + ctx.user.name;
'' + this.user.name;
}
```
@ -286,23 +286,23 @@ export class NgIf {
The presence and type of this static property tells the template type-checking engine to reflect the bound expression for its `ngIf` input as a guard for any embedded views created by the structural directive. This produces a TCB:
```typescript
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// Type of the NgIf directive instance.
var _t1: NgIf;
// Binding *ngIf="user != null".
_t1.ngIf = ctx.user !== null;
_t1.ngIf = this.user !== null;
// Guard generated due to the `ngTemplateGuard_ngIf` declaration by the NgIf directive.
if (user !== null) {
// Nested template interpolation `{{user.name}}`.
// `ctx.user` here is appropriately narrowed to be non-nullable.
'' + ctx.user.name;
// `this.user` here is appropriately narrowed to be non-nullable.
'' + this.user.name;
}
}
```
The guard expression causes TypeScript to narrow the type of `ctx.user` within the `if` block and identify that `ctx.user` cannot be `null` within the embedded view context, just as `NgIf` itself does during rendering.
The guard expression causes TypeScript to narrow the type of `this.user` within the `if` block and identify that `this.user` cannot be `null` within the embedded view context, just as `NgIf` itself does during rendering.
### Generation process
@ -393,7 +393,7 @@ Here, type-checking the `[in]` binding requires knowing the type of `ref`, which
```typescript
declare function ctor1<T>(inputs: {in?: T}): GenericCmp<T>;
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// Not legal: cannot refer to t1 (ref) before its declaration.
var t1 = ctor1({in: t1.value});
@ -408,7 +408,7 @@ To get around this, `TcbOp`s may optionally provide a fallback value via a `circ
```typescript
declare function ctor1<T>(inputs: {in?: T}): GenericCmp<T>;
function tcb(ctx: SomeCmp): void {
function tcb(this: SomeCmp): void {
// Generated to break the cycle for `ref` - infers a placeholder
// type for the component without using any of its input bindings.
var t1 = ctor1(null!);
@ -445,16 +445,16 @@ Consider a template expression of the form:
The generated TCB code for this expression would look like:
```typescript
'' + ctx.foo.bar;
'' + this.foo.bar;
```
What actually gets generated for this expression looks more like:
```typescript
'' + (ctx.foo /* 3,5 */).bar /* 3,9 */;
'' + (this.foo /* 3,5 */).bar /* 3,9 */;
```
The trailing comment for each node in the TCB indicates the template offsets for the corresponding template nodes. If for example TypeScript returns a diagnostic for the `ctx.foo` part of the expression (such as if `foo` is not a valid property on the component context), the attached comment can be used to map this diagnostic back to the original template's `foo` node.
The trailing comment for each node in the TCB indicates the template offsets for the corresponding template nodes. If for example TypeScript returns a diagnostic for the `this.foo` part of the expression (such as if `foo` is not a valid property on the component context), the attached comment can be used to map this diagnostic back to the original template's `foo` node.
#### `TemplateId`
@ -548,7 +548,7 @@ Additions of such inline type checking code have significant ramifications on th
A similar problem exists for generic components and the declaration of TCBs. A TCB function must also copy the generic bounds of its context component:
```typescript
function tcb<T extends string>(ctx: SomeCmp<T>): void {
function tcb<T extends string>(this: SomeCmp<T>): void {
/* tcb code */
}
```

View file

@ -117,7 +117,7 @@ export function generateTypeCheckBlock(
}
}
const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName, typeArguments)];
const paramList = [tcbThisParam(ref.node, ctxRawType.typeName, typeArguments)];
const scopeStatements = scope.render();
const innerBody = ts.factory.createBlock([
@ -1099,7 +1099,7 @@ class TcbUnclaimedOutputsOp extends TcbOp {
/**
* A `TcbOp` which generates a completion point for the component context.
*
* This completion point looks like `ctx. ;` in the TCB output, and does not produce diagnostics.
* This completion point looks like `this. ;` in the TCB output, and does not produce diagnostics.
* TypeScript autocompletion APIs can be used at this completion point (after the '.') to produce
* autocompletion results of properties and methods from the template's component context.
*/
@ -1111,7 +1111,7 @@ class TcbComponentContextCompletionOp extends TcbOp {
override readonly optional = false;
override execute(): null {
const ctx = ts.factory.createIdentifier('ctx');
const ctx = ts.factory.createThis();
const ctxDot = ts.factory.createPropertyAccessExpression(ctx, '');
markIgnoreDiagnostics(ctxDot);
addExpressionIdentifier(ctxDot, ExpressionIdentifier.COMPONENT_COMPLETION);
@ -1636,9 +1636,10 @@ interface TcbBoundInput {
}
/**
* Create the `ctx` parameter to the top-level TCB function, with the given generic type arguments.
* Create the `this` parameter to the top-level TCB function, with the given generic type
* arguments.
*/
function tcbCtxParam(
function tcbThisParam(
node: ClassDeclaration<ts.ClassDeclaration>, name: ts.EntityName,
typeArguments: ts.TypeNode[]|undefined): ts.ParameterDeclaration {
const type = ts.factory.createTypeReferenceNode(name, typeArguments);
@ -1646,7 +1647,7 @@ function tcbCtxParam(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ 'ctx',
/* name */ 'this',
/* questionToken */ undefined,
/* type */ type,
/* initializer */ undefined);
@ -1708,7 +1709,7 @@ class TcbExpressionTranslator {
// Therefore if `resolve` is called on an `ImplicitReceiver`, it's because no outer
// PropertyRead/Call resolved to a variable or reference, and therefore this is a
// property read or method call on the component context itself.
return ts.factory.createIdentifier('ctx');
return ts.factory.createThis();
} else if (ast instanceof BindingPipe) {
const expr = this.translate(ast.exp);
const pipeRef = this.tcb.getPipeByName(ast.name);
@ -1985,13 +1986,13 @@ function tcbCreateEventHandler(
/* type */ eventParamType);
addExpressionIdentifier(eventParam, ExpressionIdentifier.EVENT_PARAMETER);
return ts.factory.createFunctionExpression(
/* modifier */ undefined,
/* asteriskToken */ undefined,
/* name */ undefined,
// Return an arrow function instead of a function expression to preserve the `this` context.
return ts.factory.createArrowFunction(
/* modifiers */ undefined,
/* typeParameters */ undefined,
/* parameters */[eventParam],
/* type */ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
/* equalsGreaterThanToken */ undefined,
/* body */ ts.factory.createBlock([body]));
}

View file

@ -585,6 +585,29 @@ class TestComponent {
expect(messages).toEqual(
[`TestComponent.html(3, 30): Type 'HTMLElement' is not assignable to type 'string'.`]);
});
it('allows access to protected members', () => {
const messages = diagnose(`<button (click)="doFoo()">{{ message }}</button>`, `
class TestComponent {
protected message = 'Hello world';
protected doFoo(): void {}
}`);
expect(messages).toEqual([]);
});
it('disallows access to private members', () => {
const messages = diagnose(`<button (click)="doFoo()">{{ message }}</button>`, `
class TestComponent {
private message = 'Hello world';
private doFoo(): void {}
}`);
expect(messages).toEqual([
`TestComponent.html(1, 18): Property 'doFoo' is private and only accessible within class 'TestComponent'.`,
`TestComponent.html(1, 30): Property 'message' is private and only accessible within class 'TestComponent'.`
]);
});
});
describe('method call spans', () => {

View file

@ -14,23 +14,24 @@ describe('type check blocks diagnostics', () => {
describe('parse spans', () => {
it('should annotate unary ops', () => {
expect(tcbWithSpans('{{ -a }}')).toContain('(-((ctx).a /*4,5*/) /*4,5*/) /*3,5*/');
expect(tcbWithSpans('{{ -a }}')).toContain('(-((this).a /*4,5*/) /*4,5*/) /*3,5*/');
});
it('should annotate binary ops', () => {
expect(tcbWithSpans('{{ a + b }}'))
.toContain('(((ctx).a /*3,4*/) /*3,4*/) + (((ctx).b /*7,8*/) /*7,8*/) /*3,8*/');
.toContain('(((this).a /*3,4*/) /*3,4*/) + (((this).b /*7,8*/) /*7,8*/) /*3,8*/');
});
it('should annotate conditions', () => {
expect(tcbWithSpans('{{ a ? b : c }}'))
.toContain(
'(((ctx).a /*3,4*/) /*3,4*/ ? ((ctx).b /*7,8*/) /*7,8*/ : (((ctx).c /*11,12*/) /*11,12*/)) /*3,12*/');
'(((this).a /*3,4*/) /*3,4*/ ? ((this).b /*7,8*/) /*7,8*/ : (((this).c /*11,12*/) /*11,12*/)) /*3,12*/');
});
it('should annotate interpolations', () => {
expect(tcbWithSpans('{{ hello }} {{ world }}'))
.toContain('"" + (((ctx).hello /*3,8*/) /*3,8*/) + (((ctx).world /*15,20*/) /*15,20*/)');
.toContain(
'"" + (((this).hello /*3,8*/) /*3,8*/) + (((this).world /*15,20*/) /*15,20*/)');
});
it('should annotate literal map expressions', () => {
@ -39,7 +40,7 @@ describe('type check blocks diagnostics', () => {
const TEMPLATE = '{{ m({foo: a, bar: b}) }}';
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(ctx).m /*3,4*/({ "foo": ((ctx).a /*11,12*/) /*11,12*/, "bar": ((ctx).b /*19,20*/) /*19,20*/ } /*5,21*/) /*3,22*/');
'(this).m /*3,4*/({ "foo": ((this).a /*11,12*/) /*11,12*/, "bar": ((this).b /*19,20*/) /*19,20*/ } /*5,21*/) /*3,22*/');
});
it('should annotate literal map expressions with shorthand declarations', () => {
@ -48,13 +49,13 @@ describe('type check blocks diagnostics', () => {
const TEMPLATE = '{{ m({a, b}) }}';
expect(tcbWithSpans(TEMPLATE))
.toContain(
'((ctx).m /*3,4*/({ "a": ((ctx).a /*6,7*/) /*6,7*/, "b": ((ctx).b /*9,10*/) /*9,10*/ } /*5,11*/) /*3,12*/)');
'((this).m /*3,4*/({ "a": ((this).a /*6,7*/) /*6,7*/, "b": ((this).b /*9,10*/) /*9,10*/ } /*5,11*/) /*3,12*/)');
});
it('should annotate literal array expressions', () => {
const TEMPLATE = '{{ [a, b] }}';
expect(tcbWithSpans(TEMPLATE))
.toContain('[((ctx).a /*4,5*/) /*4,5*/, ((ctx).b /*7,8*/) /*7,8*/] /*3,9*/');
.toContain('[((this).a /*4,5*/) /*4,5*/, ((this).b /*7,8*/) /*7,8*/] /*3,9*/');
});
it('should annotate literals', () => {
@ -64,104 +65,104 @@ describe('type check blocks diagnostics', () => {
it('should annotate non-null assertions', () => {
const TEMPLATE = `{{ a! }}`;
expect(tcbWithSpans(TEMPLATE)).toContain('(((ctx).a /*3,4*/) /*3,4*/)! /*3,5*/');
expect(tcbWithSpans(TEMPLATE)).toContain('(((this).a /*3,4*/) /*3,4*/)! /*3,5*/');
});
it('should annotate prefix not', () => {
const TEMPLATE = `{{ !a }}`;
expect(tcbWithSpans(TEMPLATE)).toContain('!(((ctx).a /*4,5*/) /*4,5*/) /*3,5*/');
expect(tcbWithSpans(TEMPLATE)).toContain('!(((this).a /*4,5*/) /*4,5*/) /*3,5*/');
});
it('should annotate method calls', () => {
const TEMPLATE = `{{ method(a, b) }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(ctx).method /*3,9*/(((ctx).a /*10,11*/) /*10,11*/, ((ctx).b /*13,14*/) /*13,14*/) /*3,15*/');
'(this).method /*3,9*/(((this).a /*10,11*/) /*10,11*/, ((this).b /*13,14*/) /*13,14*/) /*3,15*/');
});
it('should annotate safe calls', () => {
const TEMPLATE = `{{ method?.(a, b) }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'((null as any ? (((ctx).method /*3,9*/) /*3,9*/)!(((ctx).a /*12,13*/) /*12,13*/, ((ctx).b /*15,16*/) /*15,16*/) : undefined) /*3,17*/)');
'((null as any ? (((this).method /*3,9*/) /*3,9*/)!(((this).a /*12,13*/) /*12,13*/, ((this).b /*15,16*/) /*15,16*/) : undefined) /*3,17*/)');
});
it('should annotate method calls of variables', () => {
const TEMPLATE = `<ng-template let-method>{{ method(a, b) }}</ng-template>`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'_t2 /*27,33*/(((ctx).a /*34,35*/) /*34,35*/, ((ctx).b /*37,38*/) /*37,38*/) /*27,39*/');
'_t2 /*27,33*/(((this).a /*34,35*/) /*34,35*/, ((this).b /*37,38*/) /*37,38*/) /*27,39*/');
});
it('should annotate function calls', () => {
const TEMPLATE = `{{ method(a)(b, c) }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(ctx).method /*3,9*/(((ctx).a /*10,11*/) /*10,11*/) /*3,12*/(((ctx).b /*13,14*/) /*13,14*/, ((ctx).c /*16,17*/) /*16,17*/) /*3,18*/');
'(this).method /*3,9*/(((this).a /*10,11*/) /*10,11*/) /*3,12*/(((this).b /*13,14*/) /*13,14*/, ((this).c /*16,17*/) /*16,17*/) /*3,18*/');
});
it('should annotate property access', () => {
const TEMPLATE = `{{ a.b.c }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain('((((((ctx).a /*3,4*/) /*3,4*/).b /*5,6*/) /*3,6*/).c /*7,8*/) /*3,8*/');
.toContain('((((((this).a /*3,4*/) /*3,4*/).b /*5,6*/) /*3,6*/).c /*7,8*/) /*3,8*/');
});
it('should annotate property writes', () => {
const TEMPLATE = `<div (click)='a.b.c = d'></div>`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(((((((ctx).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,23*/ = (((ctx).d /*22,23*/) /*22,23*/)) /*14,23*/');
'(((((((this).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,23*/ = (((this).d /*22,23*/) /*22,23*/)) /*14,23*/');
});
it('should $event property writes', () => {
const TEMPLATE = `<div (click)='a = $event'></div>`;
expect(tcbWithSpans(TEMPLATE))
.toContain('(((ctx).a /*14,15*/) /*14,24*/ = ($event /*18,24*/)) /*14,24*/;');
.toContain('(((this).a /*14,15*/) /*14,24*/ = ($event /*18,24*/)) /*14,24*/;');
});
it('should annotate keyed property access', () => {
const TEMPLATE = `{{ a[b] }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain('(((ctx).a /*3,4*/) /*3,4*/)[((ctx).b /*5,6*/) /*5,6*/] /*3,7*/');
.toContain('(((this).a /*3,4*/) /*3,4*/)[((this).b /*5,6*/) /*5,6*/] /*3,7*/');
});
it('should annotate keyed property writes', () => {
const TEMPLATE = `<div (click)="a[b] = c"></div>`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'((((ctx).a /*14,15*/) /*14,15*/)[((ctx).b /*16,17*/) /*16,17*/] = (((ctx).c /*21,22*/) /*21,22*/)) /*14,22*/');
'((((this).a /*14,15*/) /*14,15*/)[((this).b /*16,17*/) /*16,17*/] = (((this).c /*21,22*/) /*21,22*/)) /*14,22*/');
});
it('should annotate safe property access', () => {
const TEMPLATE = `{{ a?.b }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain('(null as any ? (((ctx).a /*3,4*/) /*3,4*/)!.b /*6,7*/ : undefined) /*3,7*/');
.toContain('(null as any ? (((this).a /*3,4*/) /*3,4*/)!.b /*6,7*/ : undefined) /*3,7*/');
});
it('should annotate safe method calls', () => {
const TEMPLATE = `{{ a?.method(b) }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'((null as any ? (null as any ? (((ctx).a /*3,4*/) /*3,4*/)!.method /*6,12*/ : undefined) /*3,12*/!(((ctx).b /*13,14*/) /*13,14*/) : undefined) /*3,15*/)');
'((null as any ? (null as any ? (((this).a /*3,4*/) /*3,4*/)!.method /*6,12*/ : undefined) /*3,12*/!(((this).b /*13,14*/) /*13,14*/) : undefined) /*3,15*/)');
});
it('should annotate safe keyed reads', () => {
const TEMPLATE = `{{ a?.[0] }}`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(null as any ? (((ctx).a /*3,4*/) /*3,4*/)![0 /*7,8*/] /*3,9*/ : undefined) /*3,9*/');
'(null as any ? (((this).a /*3,4*/) /*3,4*/)![0 /*7,8*/] /*3,9*/ : undefined) /*3,9*/');
});
it('should annotate $any casts', () => {
const TEMPLATE = `{{ $any(a) }}`;
expect(tcbWithSpans(TEMPLATE)).toContain('(((ctx).a /*8,9*/) /*8,9*/ as any) /*3,10*/');
expect(tcbWithSpans(TEMPLATE)).toContain('(((this).a /*8,9*/) /*8,9*/ as any) /*3,10*/');
});
it('should annotate chained expressions', () => {
const TEMPLATE = `<div (click)='a; b; c'></div>`;
expect(tcbWithSpans(TEMPLATE))
.toContain(
'(((ctx).a /*14,15*/) /*14,15*/, ((ctx).b /*17,18*/) /*17,18*/, ((ctx).c /*20,21*/) /*20,21*/) /*14,21*/');
'(((this).a /*14,15*/) /*14,15*/, ((this).b /*17,18*/) /*17,18*/, ((this).c /*20,21*/) /*20,21*/) /*14,21*/');
});
it('should annotate pipe usages', () => {
@ -174,7 +175,7 @@ describe('type check blocks diagnostics', () => {
const block = tcbWithSpans(TEMPLATE, PIPES);
expect(block).toContain('var _pipe1: i0.TestPipe = null!');
expect(block).toContain(
'(_pipe1.transform /*7,11*/(((ctx).a /*3,4*/) /*3,4*/, ((ctx).b /*12,13*/) /*12,13*/) /*3,13*/);');
'(_pipe1.transform /*7,11*/(((this).a /*3,4*/) /*3,4*/, ((this).b /*12,13*/) /*12,13*/) /*3,13*/);');
});
describe('attaching multiple comments for multiple references', () => {

View file

@ -15,22 +15,22 @@ 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));');
expect(tcb('{{hello}} {{world}}')).toContain('"" + (((this).hello)) + (((this).world));');
});
it('should generate literal map expressions', () => {
const TEMPLATE = '{{ method({foo: a, bar: b}) }}';
expect(tcb(TEMPLATE)).toContain('(ctx).method({ "foo": ((ctx).a), "bar": ((ctx).b) })');
expect(tcb(TEMPLATE)).toContain('(this).method({ "foo": ((this).a), "bar": ((this).b) })');
});
it('should generate literal array expressions', () => {
const TEMPLATE = '{{ method([a, b]) }}';
expect(tcb(TEMPLATE)).toContain('(ctx).method([((ctx).a), ((ctx).b)])');
expect(tcb(TEMPLATE)).toContain('(this).method([((this).a), ((this).b)])');
});
it('should handle non-null assertions', () => {
const TEMPLATE = `{{a!}}`;
expect(tcb(TEMPLATE)).toContain('((((ctx).a))!)');
expect(tcb(TEMPLATE)).toContain('((((this).a))!)');
});
it('should handle unary - operator', () => {
@ -40,20 +40,20 @@ describe('type check blocks', () => {
it('should handle keyed property access', () => {
const TEMPLATE = `{{a[b]}}`;
expect(tcb(TEMPLATE)).toContain('(((ctx).a))[((ctx).b)]');
expect(tcb(TEMPLATE)).toContain('(((this).a))[((this).b)]');
});
it('should handle nested ternary expressions', () => {
const TEMPLATE = `{{a ? b : c ? d : e}}`;
expect(tcb(TEMPLATE))
.toContain('(((ctx).a) ? ((ctx).b) : ((((ctx).c) ? ((ctx).d) : (((ctx).e)))))');
.toContain('(((this).a) ? ((this).b) : ((((this).c) ? ((this).d) : (((this).e)))))');
});
it('should handle nullish coalescing operator', () => {
expect(tcb('{{ a ?? b }}')).toContain('((((ctx).a)) ?? (((ctx).b)))');
expect(tcb('{{ a ?? b ?? c }}')).toContain('(((((ctx).a)) ?? (((ctx).b))) ?? (((ctx).c)))');
expect(tcb('{{ a ?? b }}')).toContain('((((this).a)) ?? (((this).b)))');
expect(tcb('{{ a ?? b ?? c }}')).toContain('(((((this).a)) ?? (((this).b))) ?? (((this).c)))');
expect(tcb('{{ (a ?? b) + (c ?? e) }}'))
.toContain('(((((ctx).a)) ?? (((ctx).b))) + ((((ctx).c)) ?? (((ctx).e))))');
.toContain('(((((this).a)) ?? (((this).b))) + ((((this).c)) ?? (((this).e))))');
});
it('should handle attribute values for directive inputs', () => {
@ -141,7 +141,7 @@ describe('type check blocks', () => {
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 });');
'var _t1 = _ctor1({ "fieldA": (((this).foo)), "fieldB": null as any });');
});
it('should handle multiple bindings to the same property', () => {
@ -273,7 +273,7 @@ describe('type check blocks', () => {
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
'_t1 = (((this).foo));');
});
});
@ -333,7 +333,7 @@ describe('type check blocks', () => {
];
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1: i0.HasInput = null!');
expect(block).toContain('_t1.input = (((ctx).value));');
expect(block).toContain('_t1.input = (((this).value));');
expect(block).toContain('var _t2: i0.HasOutput = null!');
expect(block).toContain('_t2["output"]');
expect(block).toContain('var _t4: i0.HasReference = null!');
@ -376,7 +376,7 @@ describe('type check blocks', () => {
<div [style]="a" [class]="b"></div>
`;
const block = tcb(TEMPLATE);
expect(block).toContain('((ctx).a); ((ctx).b);');
expect(block).toContain('((this).a); ((this).b);');
// There should be no assignments to the class or style properties.
expect(block).not.toContain('.class = ');
@ -463,7 +463,7 @@ describe('type check blocks', () => {
}];
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).not.toContain('var _t1: Dir = null!;');
expect(block).toContain('(((ctx).foo)); ');
expect(block).toContain('(((this).foo)); ');
});
it('should assign restricted properties to temp variables by default', () => {
@ -481,7 +481,7 @@ describe('type check blocks', () => {
.toContain(
'var _t1: i0.Dir = null!; ' +
'var _t2: (typeof _t1)["fieldA"] = null!; ' +
'_t2 = (((ctx).foo)); ');
'_t2 = (((this).foo)); ');
});
it('should assign properties via element access for field names that are not JS identifiers',
@ -499,7 +499,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'var _t1: i0.Dir = null!; ' +
'_t1["some-input.xs"] = (((ctx).foo)); ');
'_t1["some-input.xs"] = (((this).foo)); ');
});
it('should handle a single property bound to multiple fields', () => {
@ -516,7 +516,7 @@ describe('type check blocks', () => {
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: i0.Dir = null!; ' +
'_t1.field2 = _t1.field1 = (((ctx).foo));');
'_t1.field2 = _t1.field1 = (((this).foo));');
});
it('should handle a single property bound to multiple fields, where one of them is coerced',
@ -536,7 +536,7 @@ describe('type check blocks', () => {
.toContain(
'var _t1: typeof i0.Dir.ngAcceptInputType_field1 = null!; ' +
'var _t2: i0.Dir = null!; ' +
'_t2.field2 = _t1 = (((ctx).foo));');
'_t2.field2 = _t1 = (((this).foo));');
});
it('should handle a single property bound to multiple fields, where one of them is undeclared',
@ -555,7 +555,7 @@ describe('type check blocks', () => {
expect(tcb(TEMPLATE, DIRECTIVES))
.toContain(
'var _t1: i0.Dir = null!; ' +
'_t1.field2 = (((ctx).foo));');
'_t1.field2 = (((this).foo));');
});
it('should use coercion types if declared', () => {
@ -573,7 +573,7 @@ describe('type check blocks', () => {
expect(block).not.toContain('var _t1: Dir = null!;');
expect(block).toContain(
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
'_t1 = (((this).foo));');
});
it('should use coercion types if declared, even when backing field is not declared', () => {
@ -592,25 +592,25 @@ describe('type check blocks', () => {
expect(block).not.toContain('var _t1: Dir = null!;');
expect(block).toContain(
'var _t1: typeof i0.Dir.ngAcceptInputType_fieldA = null!; ' +
'_t1 = (((ctx).foo));');
'_t1 = (((this).foo));');
});
it('should handle $any casts', () => {
const TEMPLATE = `{{$any(a)}}`;
const block = tcb(TEMPLATE);
expect(block).toContain('(((ctx).a) as any)');
expect(block).toContain('(((this).a) as any)');
});
it('should handle $any accessed through `this`', () => {
const TEMPLATE = `{{this.$any(a)}}`;
const block = tcb(TEMPLATE);
expect(block).toContain('((ctx).$any(((ctx).a)))');
expect(block).toContain('((this).$any(((this).a)))');
});
it('should handle $any accessed through a property read', () => {
const TEMPLATE = `{{foo.$any(a)}}`;
const block = tcb(TEMPLATE);
expect(block).toContain('((((ctx).foo)).$any(((ctx).a)))');
expect(block).toContain('((((this).foo)).$any(((this).a)))');
});
describe('experimental DOM checking via lib.dom.d.ts', () => {
@ -636,7 +636,7 @@ describe('type check blocks', () => {
}];
const TEMPLATE = `<div *ngIf="person">{{person.name}}</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('if (i0.NgIf.ngTemplateGuard_ngIf(_t1, ((ctx).person)))');
expect(block).toContain('if (i0.NgIf.ngTemplateGuard_ngIf(_t1, ((this).person)))');
});
it('should emit binding guards', () => {
@ -652,7 +652,7 @@ describe('type check blocks', () => {
}];
const TEMPLATE = `<div *ngIf="person !== null">{{person.name}}</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('if ((((ctx).person)) !== (null))');
expect(block).toContain('if ((((this).person)) !== (null))');
});
it('should not emit guards when the child scope is empty', () => {
@ -683,34 +683,33 @@ describe('type check blocks', () => {
const TEMPLATE = `<div dir (dirOutput)="foo($event)"></div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });');
'_t1["outputField"].subscribe(($event): any => { (this).foo($event); });');
});
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: i1.AnimationEvent): any { (ctx).foo($event); }');
expect(block).toContain('($event: i1.AnimationEvent): any => { (this).foo($event); }');
});
it('should emit addEventListener calls for unclaimed outputs', () => {
const TEMPLATE = `<div (event)="foo($event)"></div>`;
const block = tcb(TEMPLATE);
expect(block).toContain(
'_t1.addEventListener("event", function ($event): any { (ctx).foo($event); });');
'_t1.addEventListener("event", ($event): any => { (this).foo($event); });');
});
it('should allow to cast $event using $any', () => {
const TEMPLATE = `<div (event)="foo($any($event))"></div>`;
const block = tcb(TEMPLATE);
expect(block).toContain(
'_t1.addEventListener("event", function ($event): any { (ctx).foo(($event as any)); });');
'_t1.addEventListener("event", ($event): any => { (this).foo(($event as any)); });');
});
it('should detect writes to template variables', () => {
const TEMPLATE = `<ng-template let-v><div (event)="v = 3"></div></ng-template>`;
const block = tcb(TEMPLATE);
expect(block).toContain(
'_t3.addEventListener("event", function ($event): any { (_t2 = 3); });');
expect(block).toContain('_t3.addEventListener("event", ($event): any => { (_t2 = 3); });');
});
it('should ignore accesses to $event through `this`', () => {
@ -718,7 +717,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE);
expect(block).toContain(
'_t1.addEventListener("event", function ($event): any { (ctx).foo(((ctx).$event)); });');
'_t1.addEventListener("event", ($event): any => { (this).foo(((this).$event)); });');
});
});
@ -777,12 +776,12 @@ describe('type check blocks', () => {
it('should descend into template bodies when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('((ctx).a)');
expect(block).toContain('((this).a)');
});
it('should not descend into template bodies when disabled', () => {
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTemplateBodies: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).not.toContain('((ctx).a)');
expect(block).not.toContain('((this).a)');
});
it('generates a references var when enabled', () => {
@ -802,15 +801,15 @@ describe('type check blocks', () => {
it('should include null and undefined when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('_t1.dirInput = (((ctx).a));');
expect(block).toContain('((ctx).b);');
expect(block).toContain('_t1.dirInput = (((this).a));');
expect(block).toContain('((this).b);');
});
it('should use the non-null assertion operator when disabled', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, strictNullInputBindings: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('_t1.dirInput = (((ctx).a)!);');
expect(block).toContain('((ctx).b)!;');
expect(block).toContain('_t1.dirInput = (((this).a)!);');
expect(block).toContain('((this).b)!;');
});
});
@ -818,8 +817,8 @@ describe('type check blocks', () => {
it('should check types of bindings when enabled', () => {
const TEMPLATE = `<div dir [dirInput]="a" [nonDirInput]="b"></div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('_t1.dirInput = (((ctx).a));');
expect(block).toContain('((ctx).b);');
expect(block).toContain('_t1.dirInput = (((this).a));');
expect(block).toContain('((this).b);');
});
it('should not check types of bindings when disabled', () => {
@ -827,8 +826,8 @@ describe('type check blocks', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('_t1.dirInput = ((((ctx).a) as any));');
expect(block).toContain('(((ctx).b) as any);');
expect(block).toContain('_t1.dirInput = ((((this).a) as any));');
expect(block).toContain('(((this).b) as any);');
});
it('should wrap the cast to any in parentheses when required', () => {
@ -836,7 +835,7 @@ describe('type check blocks', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('_t1.dirInput = ((((((ctx).a)) === (((ctx).b))) as any));');
expect(block).toContain('_t1.dirInput = ((((((this).a)) === (((this).b))) as any));');
});
});
@ -846,18 +845,18 @@ describe('type check blocks', () => {
it('should check types of directive outputs when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });');
'_t1["outputField"].subscribe(($event): any => { (this).foo($event); });');
expect(block).toContain(
'_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });');
'_t2.addEventListener("nonDirOutput", ($event): any => { (this).foo($event); });');
});
it('should not check types of directive outputs when disabled', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfOutputEvents: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('function ($event: any): any { (ctx).foo($event); }');
expect(block).toContain('($event: any): any => { (this).foo($event); }');
// Note that DOM events are still checked, that is controlled by `checkTypeOfDomEvents`
expect(block).toContain(
'addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });');
'addEventListener("nonDirOutput", ($event): any => { (this).foo($event); });');
});
});
@ -866,13 +865,13 @@ describe('type check blocks', () => {
it('should check types of animation events when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('function ($event: i1.AnimationEvent): any { (ctx).foo($event); }');
expect(block).toContain('($event: i1.AnimationEvent): any => { (this).foo($event); }');
});
it('should not check types of animation events when disabled', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfAnimationEvents: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('function ($event: any): any { (ctx).foo($event); }');
expect(block).toContain('($event: any): any => { (this).foo($event); }');
});
});
@ -882,9 +881,9 @@ describe('type check blocks', () => {
it('should check types of DOM events when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });');
'_t1["outputField"].subscribe(($event): any => { (this).foo($event); });');
expect(block).toContain(
'_t2.addEventListener("nonDirOutput", function ($event): any { (ctx).foo($event); });');
'_t2.addEventListener("nonDirOutput", ($event): any => { (this).foo($event); });');
});
it('should not check types of DOM events when disabled', () => {
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfDomEvents: false};
@ -892,8 +891,8 @@ describe('type check blocks', () => {
// Note that directive outputs are still checked, that is controlled by
// `checkTypeOfOutputEvents`
expect(block).toContain(
'_t1["outputField"].subscribe(function ($event): any { (ctx).foo($event); });');
expect(block).toContain('function ($event: any): any { (ctx).foo($event); }');
'_t1["outputField"].subscribe(($event): any => { (this).foo($event); });');
expect(block).toContain('($event: any): any => { (this).foo($event); }');
});
});
@ -986,13 +985,13 @@ describe('type check blocks', () => {
it('should check types of pipes when enabled', () => {
const block = tcb(TEMPLATE, PIPES);
expect(block).toContain('var _pipe1: i0.TestPipe = null!;');
expect(block).toContain('(_pipe1.transform(((ctx).a), ((ctx).b), ((ctx).c)));');
expect(block).toContain('(_pipe1.transform(((this).a), ((this).b), ((this).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('var _pipe1: i0.TestPipe = null!;');
expect(block).toContain('((_pipe1.transform as any)(((ctx).a), ((ctx).b), ((ctx).c))');
expect(block).toContain('((_pipe1.transform as any)(((this).a), ((this).b), ((this).c))');
});
});
@ -1002,19 +1001,19 @@ describe('type check blocks', () => {
it('should use undefined for safe navigation operations when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain(
'(null as any ? (null as any ? (((ctx).a))!.method : undefined)!() : undefined)');
expect(block).toContain('(null as any ? (((ctx).a))!.b : undefined)');
expect(block).toContain('(null as any ? (((ctx).a))![0] : undefined)');
expect(block).toContain('(null as any ? (((((ctx).a)).optionalMethod))!() : undefined)');
'(null as any ? (null as any ? (((this).a))!.method : undefined)!() : undefined)');
expect(block).toContain('(null as any ? (((this).a))!.b : undefined)');
expect(block).toContain('(null as any ? (((this).a))![0] : undefined)');
expect(block).toContain('(null as any ? (((((this).a)).optionalMethod))!() : undefined)');
});
it('should use an \'any\' type for safe navigation operations when disabled', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, strictSafeNavigationTypes: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('((((((ctx).a))!.method as any) as any)())');
expect(block).toContain('((((ctx).a))!.b as any)');
expect(block).toContain('(((((ctx).a))![0] as any)');
expect(block).toContain('((((((ctx).a)).optionalMethod))!() as any)');
expect(block).toContain('((((((this).a))!.method as any) as any)())');
expect(block).toContain('((((this).a))!.b as any)');
expect(block).toContain('(((((this).a))![0] as any)');
expect(block).toContain('((((((this).a)).optionalMethod))!() as any)');
});
});
@ -1023,21 +1022,21 @@ describe('type check blocks', () => {
`{{a.method()?.b}} {{a()?.method()}} {{a.method()?.[0]}} {{a.method()?.otherMethod?.()}}`;
it('should check the presence of a property/method on the receiver when enabled', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('(null as any ? ((((ctx).a)).method())!.b : undefined)');
expect(block).toContain('(null as any ? ((((this).a)).method())!.b : undefined)');
expect(block).toContain(
'(null as any ? (null as any ? ((ctx).a())!.method : undefined)!() : undefined)');
expect(block).toContain('(null as any ? ((((ctx).a)).method())![0] : undefined)');
'(null as any ? (null as any ? ((this).a())!.method : undefined)!() : undefined)');
expect(block).toContain('(null as any ? ((((this).a)).method())![0] : undefined)');
expect(block).toContain(
'(null as any ? ((null as any ? ((((ctx).a)).method())!.otherMethod : undefined))!() : undefined)');
'(null as any ? ((null as any ? ((((this).a)).method())!.otherMethod : undefined))!() : undefined)');
});
it('should not check the presence of a property/method on the receiver when disabled', () => {
const DISABLED_CONFIG:
TypeCheckingConfig = {...BASE_CONFIG, strictSafeNavigationTypes: false};
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
expect(block).toContain('(((((ctx).a)).method()) as any).b');
expect(block).toContain('(((((ctx).a()) as any).method as any)())');
expect(block).toContain('(((((ctx).a)).method()) as any)[0]');
expect(block).toContain('(((((((ctx).a)).method()) as any).otherMethod)!() as any)');
expect(block).toContain('(((((this).a)).method()) as any).b');
expect(block).toContain('(((((this).a()) as any).method as any)())');
expect(block).toContain('(((((this).a)).method()) as any)[0]');
expect(block).toContain('(((((((this).a)).method()) as any).otherMethod)!() as any)');
});
});
@ -1046,13 +1045,13 @@ describe('type check blocks', () => {
it('should use the generic type of the context when enabled', () => {
const block = tcb(TEMPLATE);
expect(block).toContain('function _tcb1<T extends string>(ctx: i0.Test<T>)');
expect(block).toContain('function _tcb1<T extends string>(this: 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 _tcb1(ctx: i0.Test<any>)');
expect(block).toContain('function _tcb1(this: i0.Test<any>)');
});
});
@ -1076,7 +1075,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES, enableChecks);
expect(block).toContain(
'var _t1: i0.Dir = null!; ' +
'_t1["some-input.xs"] = (((ctx).foo)); ');
'_t1["some-input.xs"] = (((this).foo)); ');
});
it('should assign restricted properties via property access', () => {
@ -1094,7 +1093,7 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES, enableChecks);
expect(block).toContain(
'var _t1: i0.Dir = null!; ' +
'_t1.fieldA = (((ctx).foo)); ');
'_t1.fieldA = (((this).foo)); ');
});
});
});
@ -1126,7 +1125,7 @@ describe('type check blocks', () => {
const renderedTcb = tcb(template, declarations, {useInlineTypeConstructors: false});
expect(renderedTcb).toContain(`var _t1: i0.Dir<any, any> = null!;`);
expect(renderedTcb).toContain(`_t1.inputA = (((ctx).foo));`);
expect(renderedTcb).toContain(`_t1.inputB = (((ctx).bar));`);
expect(renderedTcb).toContain(`_t1.inputA = (((this).foo));`);
expect(renderedTcb).toContain(`_t1.inputB = (((this).bar));`);
});
});

View file

@ -26,7 +26,7 @@ runInEachFileSystem(() => {
if (!ts.isExpressionStatement(node)) {
return fail(`Expected a ts.ExpressionStatement`);
}
expect(node.expression.getText()).toEqual('ctx.');
expect(node.expression.getText()).toEqual('this.');
// The position should be between the '.' and a following space.
expect(tcbSf.text.slice(positionInFile - 1, positionInFile + 1)).toEqual('. ');
});