mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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:
parent
f0b5e830a5
commit
752ddbc165
6 changed files with 168 additions and 144 deletions
|
|
@ -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 */
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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));`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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('. ');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue