Skip to content

Commit 72534e2

Browse files
JeanMechethePunderWoman
authored andcommitted
feat(compiler): Add support for the instanceof binary operator
Because why not ? fixes #59975
1 parent 9273f1c commit 72534e2

File tree

21 files changed

+147
-22
lines changed

21 files changed

+147
-22
lines changed

adev/src/content/guide/templates/expression-syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Angular supports the following operators from standard JavaScript.
6868
| typeof | `typeof 42` |
6969
| void | `void 1` |
7070
| in | `'model' in car` |
71+
| instanceof | `car instanceof Automobile` |
7172
| Assignment | `a = b` |
7273
| Addition Assignment | `a += b` |
7374
| Subtraction Assignment | `a -= b` |
@@ -100,7 +101,6 @@ NOTE: Optional chaining behaves differently from the standard JavaScript version
100101
| Object destructuring | `const { name } = person` |
101102
| Array destructuring | `const [firstItem] = items` |
102103
| Comma operator | `x = (x++, x)` |
103-
| instanceof | `car instanceof Automobile` |
104104
| new | `new Car()` |
105105

106106
## Lexical context for expressions

packages/compiler-cli/src/ngtsc/translator/src/api/ast_factory.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ export type BinaryOperator =
333333
| '||'
334334
| '+'
335335
| '??'
336-
| 'in'
337336
| '='
338337
| '+='
339338
| '-='
@@ -343,7 +342,9 @@ export type BinaryOperator =
343342
| '**='
344343
| '&&='
345344
| '||='
346-
| '??=';
345+
| '??='
346+
| 'in'
347+
| 'instanceof';
347348

348349
/**
349350
* The original location of the start or end of a node created by the `AstFactory`.

packages/compiler-cli/src/ngtsc/translator/src/translator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const BINARY_OPERATORS = /* @__PURE__ */ new Map<o.BinaryOperator, BinaryOperato
4747
[o.BinaryOperator.NullishCoalesce, '??'],
4848
[o.BinaryOperator.Exponentiation, '**'],
4949
[o.BinaryOperator.In, 'in'],
50+
[o.BinaryOperator.InstanceOf, 'instanceof'],
5051
[o.BinaryOperator.Assign, '='],
5152
[o.BinaryOperator.AdditionAssignment, '+='],
5253
[o.BinaryOperator.SubtractionAssignment, '-='],

packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export class TypeScriptAstFactory implements AstFactory<ts.Statement, ts.Express
6868
'||': ts.SyntaxKind.BarBarToken,
6969
'+': ts.SyntaxKind.PlusToken,
7070
'??': ts.SyntaxKind.QuestionQuestionToken,
71-
'in': ts.SyntaxKind.InKeyword,
7271
'=': ts.SyntaxKind.EqualsToken,
7372
'+=': ts.SyntaxKind.PlusEqualsToken,
7473
'-=': ts.SyntaxKind.MinusEqualsToken,
@@ -79,6 +78,8 @@ export class TypeScriptAstFactory implements AstFactory<ts.Statement, ts.Express
7978
'&&=': ts.SyntaxKind.AmpersandAmpersandEqualsToken,
8079
'||=': ts.SyntaxKind.BarBarEqualsToken,
8180
'??=': ts.SyntaxKind.QuestionQuestionEqualsToken,
81+
'in': ts.SyntaxKind.InKeyword,
82+
'instanceof': ts.SyntaxKind.InstanceOfKeyword,
8283
}))();
8384

8485
private readonly VAR_TYPES: Record<VariableDeclarationType, ts.NodeFlags> =

packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ class AstTranslator implements AstVisitor {
106106
['&', ts.SyntaxKind.AmpersandToken],
107107
['|', ts.SyntaxKind.BarToken],
108108
['??', ts.SyntaxKind.QuestionQuestionToken],
109-
['in', ts.SyntaxKind.InKeyword],
110109
['=', ts.SyntaxKind.EqualsToken],
111110
['+=', ts.SyntaxKind.PlusEqualsToken],
112111
['-=', ts.SyntaxKind.MinusEqualsToken],
@@ -117,6 +116,8 @@ class AstTranslator implements AstVisitor {
117116
['&&=', ts.SyntaxKind.AmpersandAmpersandEqualsToken],
118117
['||=', ts.SyntaxKind.BarBarEqualsToken],
119118
['??=', ts.SyntaxKind.QuestionQuestionEqualsToken],
119+
['in', ts.SyntaxKind.InKeyword],
120+
['instanceof', ts.SyntaxKind.InstanceOfKeyword],
120121
]);
121122

122123
constructor(

packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ describe('type check blocks', () => {
114114
expect(tcb(`{{!('bar' in {bar: 'bar'}) }}`)).toContain(`!((("bar") in ({ "bar": "bar" })))`);
115115
});
116116

117+
it('should handle "instanceof" expressions', () => {
118+
expect(tcb(`{{obj instanceof MyClass}}`)).toContain(
119+
`((((this).obj)) instanceof (((this).MyClass)))`,
120+
);
121+
expect(tcb(`{{!(obj instanceof MyClass)}}`)).toContain(
122+
`!(((((this).obj)) instanceof (((this).MyClass)))))`,
123+
);
124+
});
125+
117126
it('should handle attribute values for directive inputs', () => {
118127
const TEMPLATE = `<div dir inputA="value"></div>`;
119128
const DIRECTIVES: TestDeclaration[] = [

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/GOLDEN_PARTIAL.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
8080
type: Pipe,
8181
args: [{ name: 'identity' }]
8282
}] });
83+
export class Bar {
84+
}
8385
export class MyApp {
8486
foo = { bar: 'baz' };
8587
number = 1;
88+
bar = new Bar();
89+
Bar = Bar;
8690
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
8791
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
8892
{{ 1 + 2 }}
@@ -95,6 +99,7 @@ export class MyApp {
9599
{{ void 'test' }}
96100
{{ (-1) ** 3 }}
97101
{{ 'bar' in foo }}
102+
{{ bar instanceof Bar }}
98103
<button (click)="number += 1"></button>
99104
<button (click)="number -= 1"></button>
100105
<button (click)="number *= 1"></button>
@@ -120,6 +125,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
120125
{{ void 'test' }}
121126
{{ (-1) ** 3 }}
122127
{{ 'bar' in foo }}
128+
{{ bar instanceof Bar }}
123129
<button (click)="number += 1"></button>
124130
<button (click)="number -= 1"></button>
125131
<button (click)="number *= 1"></button>
@@ -143,11 +149,15 @@ export declare class IdentityPipe {
143149
static ɵfac: i0.ɵɵFactoryDeclaration<IdentityPipe, never>;
144150
static ɵpipe: i0.ɵɵPipeDeclaration<IdentityPipe, "identity", true>;
145151
}
152+
export declare class Bar {
153+
}
146154
export declare class MyApp {
147155
foo: {
148156
bar?: string;
149157
};
150158
number: number;
159+
bar: Bar;
160+
Bar: typeof Bar;
151161
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
152162
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
153163
}

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/operators.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, Pipe} from '@angular/core';
1+
import { Component, Pipe } from '@angular/core';
22

33
@Pipe({name: 'identity'})
44
export class IdentityPipe {
@@ -7,6 +7,8 @@ export class IdentityPipe {
77
}
88
}
99

10+
export class Bar {}
11+
1012
@Component({
1113
template: `
1214
{{ 1 + 2 }}
@@ -19,6 +21,7 @@ export class IdentityPipe {
1921
{{ void 'test' }}
2022
{{ (-1) ** 3 }}
2123
{{ 'bar' in foo }}
24+
{{ bar instanceof Bar }}
2225
<button (click)="number += 1"></button>
2326
<button (click)="number -= 1"></button>
2427
<button (click)="number *= 1"></button>
@@ -34,4 +37,6 @@ export class IdentityPipe {
3437
export class MyApp {
3538
foo: {bar?: string} = {bar: 'baz'};
3639
number = 1;
40+
bar = new Bar();
41+
Bar = Bar;
3742
}

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/operators_template.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ template: function MyApp_Template(rf, $ctx$) {
2525
1 + 2, " ",
2626
1 % 2 + 3 / 4 * 5 ** 6, " ",
2727
+1, " ",
28-
typeof $r3$.ɵɵpureFunction0(12, _c0) === "object", " ",
29-
!(typeof $r3$.ɵɵpureFunction0(13, _c0) === "object"), " ",
28+
typeof $r3$.ɵɵpureFunction0(13, _c0) === "object", " ",
29+
!(typeof $r3$.ɵɵpureFunction0(14, _c0) === "object"), " ",
3030
typeof ($ctx$.foo == null ? null : $ctx$.foo.bar) === "string", " ",
31-
$r3$.ɵɵpipeBind1(1, 10, typeof ($ctx$.foo == null ? null : $ctx$.foo.bar)), " ",
31+
$r3$.ɵɵpipeBind1(1, 11, typeof ($ctx$.foo == null ? null : $ctx$.foo.bar)), " ",
3232
void "test", " ",
3333
(-1) ** 3, " ",
34-
"bar" in $ctx$.foo, " "
34+
"bar" in $ctx$.foo, " ",
35+
$ctx$.bar instanceof $ctx$.Bar, " "
3536
]);
3637
}
3738
}
38-

packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,26 @@ runInEachFileSystem(() => {
816816
expect(diags[0].messageText).toContain(`Type 'string' is not assignable to type 'object'`);
817817
});
818818

819+
it('should error on invalid instanceof binary expressions', () => {
820+
env.write(
821+
'test.ts',
822+
`
823+
import {Component} from '@angular/core';
824+
@Component({
825+
template: \` {{'foo' instanceof String}} \`,
826+
})
827+
class TestCmp {
828+
}
829+
`,
830+
);
831+
832+
const diags = env.driveDiagnostics();
833+
expect(diags.length).toBe(2);
834+
expect(diags[0].messageText).toContain(
835+
`The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter.`,
836+
);
837+
});
838+
819839
describe('strictInputTypes', () => {
820840
beforeEach(() => {
821841
env.write(

0 commit comments

Comments
 (0)