@@ -99,6 +99,13 @@ export function generateTypeCheckBlock(
99
99
* `ts.Expression` which can be used to reference the operation's result.
100
100
*/
101
101
abstract class TcbOp {
102
+ /**
103
+ * Set to true if this operation can be considered optional. Optional operations are only executed
104
+ * when depended upon by other operations, otherwise they are disregarded. This allows for less
105
+ * code to generate, parse and type-check, overall positively contributing to performance.
106
+ */
107
+ abstract readonly optional : boolean ;
108
+
102
109
abstract execute ( ) : ts . Expression | null ;
103
110
104
111
/**
@@ -125,6 +132,13 @@ class TcbElementOp extends TcbOp {
125
132
super ( ) ;
126
133
}
127
134
135
+ get optional ( ) {
136
+ // The statement generated by this operation is only used for type-inference of the DOM
137
+ // element's type and won't report diagnostics by itself, so the operation is marked as optional
138
+ // to avoid generating statements for DOM elements that are never referenced.
139
+ return true ;
140
+ }
141
+
128
142
execute ( ) : ts . Identifier {
129
143
const id = this . tcb . allocateId ( ) ;
130
144
// Add the declaration of the element using document.createElement.
@@ -148,6 +162,10 @@ class TcbVariableOp extends TcbOp {
148
162
super ( ) ;
149
163
}
150
164
165
+ get optional ( ) {
166
+ return false ;
167
+ }
168
+
151
169
execute ( ) : ts . Identifier {
152
170
// Look for a context variable for the template.
153
171
const ctx = this . scope . resolve ( this . template ) ;
@@ -176,6 +194,10 @@ class TcbTemplateContextOp extends TcbOp {
176
194
super ( ) ;
177
195
}
178
196
197
+ get optional ( ) {
198
+ return false ;
199
+ }
200
+
179
201
execute ( ) : ts . Identifier {
180
202
// Allocate a template ctx variable and declare it with an 'any' type. The type of this variable
181
203
// may be narrowed as a result of template guard conditions.
@@ -198,6 +220,10 @@ class TcbTemplateBodyOp extends TcbOp {
198
220
super ( ) ;
199
221
}
200
222
223
+ get optional ( ) {
224
+ return false ;
225
+ }
226
+
201
227
execute ( ) : null {
202
228
// An `if` will be constructed, within which the template's children will be type checked. The
203
229
// `if` is used for two reasons: it creates a new syntactic scope, isolating variables declared
@@ -301,6 +327,10 @@ class TcbTextInterpolationOp extends TcbOp {
301
327
super ( ) ;
302
328
}
303
329
330
+ get optional ( ) {
331
+ return false ;
332
+ }
333
+
304
334
execute ( ) : null {
305
335
const expr = tcbExpression ( this . binding . value , this . tcb , this . scope ) ;
306
336
this . scope . addStatement ( ts . createExpressionStatement ( expr ) ) ;
@@ -324,6 +354,10 @@ class TcbDirectiveTypeOp extends TcbOp {
324
354
super ( ) ;
325
355
}
326
356
357
+ get optional ( ) {
358
+ return false ;
359
+ }
360
+
327
361
execute ( ) : ts . Identifier {
328
362
const id = this . tcb . allocateId ( ) ;
329
363
@@ -352,6 +386,10 @@ class TcbDirectiveCtorOp extends TcbOp {
352
386
super ( ) ;
353
387
}
354
388
389
+ get optional ( ) {
390
+ return false ;
391
+ }
392
+
355
393
execute ( ) : ts . Identifier {
356
394
const id = this . tcb . allocateId ( ) ;
357
395
@@ -409,6 +447,10 @@ class TcbDirectiveInputsOp extends TcbOp {
409
447
super ( ) ;
410
448
}
411
449
450
+ get optional ( ) {
451
+ return false ;
452
+ }
453
+
412
454
execute ( ) : null {
413
455
const dirId = this . scope . resolve ( this . node , this . dir ) ;
414
456
@@ -514,6 +556,10 @@ class TcbDirectiveCtorCircularFallbackOp extends TcbOp {
514
556
super ( ) ;
515
557
}
516
558
559
+ get optional ( ) {
560
+ return false ;
561
+ }
562
+
517
563
execute ( ) : ts . Identifier {
518
564
const id = this . tcb . allocateId ( ) ;
519
565
const typeCtor = this . tcb . env . typeCtorFor ( this . dir ) ;
@@ -541,6 +587,10 @@ class TcbDomSchemaCheckerOp extends TcbOp {
541
587
super ( ) ;
542
588
}
543
589
590
+ get optional ( ) {
591
+ return false ;
592
+ }
593
+
544
594
execute ( ) : ts . Expression | null {
545
595
if ( this . checkElement ) {
546
596
this . tcb . domSchemaChecker . checkElement ( this . tcb . id , this . element , this . tcb . schemas ) ;
@@ -597,10 +647,14 @@ class TcbUnclaimedInputsOp extends TcbOp {
597
647
super ( ) ;
598
648
}
599
649
650
+ get optional ( ) {
651
+ return false ;
652
+ }
653
+
600
654
execute ( ) : null {
601
655
// `this.inputs` contains only those bindings not matched by any directive. These bindings go to
602
656
// the element itself.
603
- const elId = this . scope . resolve ( this . element ) ;
657
+ let elId : ts . Expression | null = null ;
604
658
605
659
// TODO(alxhub): this could be more efficient.
606
660
for ( const binding of this . element . inputs ) {
@@ -622,6 +676,9 @@ class TcbUnclaimedInputsOp extends TcbOp {
622
676
623
677
if ( this . tcb . env . config . checkTypeOfDomBindings && binding . type === BindingType . Property ) {
624
678
if ( binding . name !== 'style' && binding . name !== 'class' ) {
679
+ if ( elId === null ) {
680
+ elId = this . scope . resolve ( this . element ) ;
681
+ }
625
682
// A direct binding to a property.
626
683
const propertyName = ATTR_TO_PROP [ binding . name ] || binding . name ;
627
684
const prop = ts . createElementAccess ( elId , ts . createStringLiteral ( propertyName ) ) ;
@@ -656,6 +713,10 @@ class TcbDirectiveOutputsOp extends TcbOp {
656
713
super ( ) ;
657
714
}
658
715
716
+ get optional ( ) {
717
+ return false ;
718
+ }
719
+
659
720
execute ( ) : null {
660
721
const dirId = this . scope . resolve ( this . node , this . dir ) ;
661
722
@@ -723,8 +784,12 @@ class TcbUnclaimedOutputsOp extends TcbOp {
723
784
super ( ) ;
724
785
}
725
786
787
+ get optional ( ) {
788
+ return false ;
789
+ }
790
+
726
791
execute ( ) : null {
727
- const elId = this . scope . resolve ( this . element ) ;
792
+ let elId : ts . Expression | null = null ;
728
793
729
794
// TODO(alxhub): this could be more efficient.
730
795
for ( const output of this . element . outputs ) {
@@ -749,6 +814,9 @@ class TcbUnclaimedOutputsOp extends TcbOp {
749
814
// base `Event` type.
750
815
const handler = tcbCreateEventHandler ( output , this . tcb , this . scope , EventParamType . Infer ) ;
751
816
817
+ if ( elId === null ) {
818
+ elId = this . scope . resolve ( this . element ) ;
819
+ }
752
820
const call = ts . createCall (
753
821
/* expression */ ts . createPropertyAccess ( elId , 'addEventListener' ) ,
754
822
/* typeArguments */ undefined ,
@@ -965,7 +1033,7 @@ class Scope {
965
1033
*/
966
1034
render ( ) : ts . Statement [ ] {
967
1035
for ( let i = 0 ; i < this . opQueue . length ; i ++ ) {
968
- this . executeOp ( i ) ;
1036
+ this . executeOp ( i , /* skipOptional */ true ) ;
969
1037
}
970
1038
return this . statements ;
971
1039
}
@@ -1031,7 +1099,7 @@ class Scope {
1031
1099
* Like `executeOp`, but assert that the operation actually returned `ts.Expression`.
1032
1100
*/
1033
1101
private resolveOp ( opIndex : number ) : ts . Expression {
1034
- const res = this . executeOp ( opIndex ) ;
1102
+ const res = this . executeOp ( opIndex , /* skipOptional */ false ) ;
1035
1103
if ( res === null ) {
1036
1104
throw new Error ( `Error resolving operation, got null` ) ;
1037
1105
}
@@ -1045,12 +1113,16 @@ class Scope {
1045
1113
* and also protects against a circular dependency from the operation to itself by temporarily
1046
1114
* setting the operation's result to a special expression.
1047
1115
*/
1048
- private executeOp ( opIndex : number ) : ts . Expression | null {
1116
+ private executeOp ( opIndex : number , skipOptional : boolean ) : ts . Expression | null {
1049
1117
const op = this . opQueue [ opIndex ] ;
1050
1118
if ( ! ( op instanceof TcbOp ) ) {
1051
1119
return op ;
1052
1120
}
1053
1121
1122
+ if ( skipOptional && op . optional ) {
1123
+ return null ;
1124
+ }
1125
+
1054
1126
// Set the result of the operation in the queue to its circular fallback. If executing this
1055
1127
// operation results in a circular dependency, this will prevent an infinite loop and allow for
1056
1128
// the resolution of such cycles.
0 commit comments