1
- /** @import { ArrowFunctionExpression, BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator } from 'estree' */
1
+ /** @import { ArrowFunctionExpression, BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator, Super } from 'estree' */
2
2
/** @import { Context, Visitor } from 'zimmerframe' */
3
3
/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
4
4
import is_reference from 'is-reference' ;
@@ -18,8 +18,71 @@ import { validate_identifier_name } from './2-analyze/visitors/shared/utils.js';
18
18
19
19
const UNKNOWN = Symbol ( 'unknown' ) ;
20
20
/** Includes `BigInt` */
21
- const NUMBER = Symbol ( 'number' ) ;
22
- const STRING = Symbol ( 'string' ) ;
21
+ export const NUMBER = Symbol ( 'number' ) ;
22
+ export const STRING = Symbol ( 'string' ) ;
23
+
24
+ /** @type {Record<string, [type: NUMBER | STRING | UNKNOWN, fn?: Function]> } */
25
+ const globals = {
26
+ BigInt : [ NUMBER , BigInt ] ,
27
+ 'Math.min' : [ NUMBER , Math . min ] ,
28
+ 'Math.max' : [ NUMBER , Math . max ] ,
29
+ 'Math.random' : [ NUMBER ] ,
30
+ 'Math.floor' : [ NUMBER , Math . floor ] ,
31
+ // @ts -expect-error
32
+ 'Math.f16round' : [ NUMBER , Math . f16round ] ,
33
+ 'Math.round' : [ NUMBER , Math . round ] ,
34
+ 'Math.abs' : [ NUMBER , Math . abs ] ,
35
+ 'Math.acos' : [ NUMBER , Math . acos ] ,
36
+ 'Math.asin' : [ NUMBER , Math . asin ] ,
37
+ 'Math.atan' : [ NUMBER , Math . atan ] ,
38
+ 'Math.atan2' : [ NUMBER , Math . atan2 ] ,
39
+ 'Math.ceil' : [ NUMBER , Math . ceil ] ,
40
+ 'Math.cos' : [ NUMBER , Math . cos ] ,
41
+ 'Math.sin' : [ NUMBER , Math . sin ] ,
42
+ 'Math.tan' : [ NUMBER , Math . tan ] ,
43
+ 'Math.exp' : [ NUMBER , Math . exp ] ,
44
+ 'Math.log' : [ NUMBER , Math . log ] ,
45
+ 'Math.pow' : [ NUMBER , Math . pow ] ,
46
+ 'Math.sqrt' : [ NUMBER , Math . sqrt ] ,
47
+ 'Math.clz32' : [ NUMBER , Math . clz32 ] ,
48
+ 'Math.imul' : [ NUMBER , Math . imul ] ,
49
+ 'Math.sign' : [ NUMBER , Math . sign ] ,
50
+ 'Math.log10' : [ NUMBER , Math . log10 ] ,
51
+ 'Math.log2' : [ NUMBER , Math . log2 ] ,
52
+ 'Math.log1p' : [ NUMBER , Math . log1p ] ,
53
+ 'Math.expm1' : [ NUMBER , Math . expm1 ] ,
54
+ 'Math.cosh' : [ NUMBER , Math . cosh ] ,
55
+ 'Math.sinh' : [ NUMBER , Math . sinh ] ,
56
+ 'Math.tanh' : [ NUMBER , Math . tanh ] ,
57
+ 'Math.acosh' : [ NUMBER , Math . acosh ] ,
58
+ 'Math.asinh' : [ NUMBER , Math . asinh ] ,
59
+ 'Math.atanh' : [ NUMBER , Math . atanh ] ,
60
+ 'Math.trunc' : [ NUMBER , Math . trunc ] ,
61
+ 'Math.fround' : [ NUMBER , Math . fround ] ,
62
+ 'Math.cbrt' : [ NUMBER , Math . cbrt ] ,
63
+ Number : [ NUMBER , Number ] ,
64
+ 'Number.isInteger' : [ NUMBER , Number . isInteger ] ,
65
+ 'Number.isFinite' : [ NUMBER , Number . isFinite ] ,
66
+ 'Number.isNaN' : [ NUMBER , Number . isNaN ] ,
67
+ 'Number.isSafeInteger' : [ NUMBER , Number . isSafeInteger ] ,
68
+ 'Number.parseFloat' : [ NUMBER , Number . parseFloat ] ,
69
+ 'Number.parseInt' : [ NUMBER , Number . parseInt ] ,
70
+ String : [ STRING , String ] ,
71
+ 'String.fromCharCode' : [ STRING , String . fromCharCode ] ,
72
+ 'String.fromCodePoint' : [ STRING , String . fromCodePoint ]
73
+ } ;
74
+
75
+ /** @type {Record<string, any> } */
76
+ const global_constants = {
77
+ 'Math.PI' : Math . PI ,
78
+ 'Math.E' : Math . E ,
79
+ 'Math.LN10' : Math . LN10 ,
80
+ 'Math.LN2' : Math . LN2 ,
81
+ 'Math.LOG10E' : Math . LOG10E ,
82
+ 'Math.LOG2E' : Math . LOG2E ,
83
+ 'Math.SQRT2' : Math . SQRT2 ,
84
+ 'Math.SQRT1_2' : Math . SQRT1_2
85
+ } ;
23
86
24
87
export class Binding {
25
88
/** @type {Scope } */
@@ -107,7 +170,7 @@ export class Binding {
107
170
108
171
class Evaluation {
109
172
/** @type {Set<any> } */
110
- values = new Set ( ) ;
173
+ values ;
111
174
112
175
/**
113
176
* True if there is exactly one possible value
@@ -147,8 +210,11 @@ class Evaluation {
147
210
*
148
211
* @param {Scope } scope
149
212
* @param {Expression } expression
213
+ * @param {Set<any> } values
150
214
*/
151
- constructor ( scope , expression ) {
215
+ constructor ( scope , expression , values ) {
216
+ this . values = values ;
217
+
152
218
switch ( expression . type ) {
153
219
case 'Literal' : {
154
220
this . values . add ( expression . value ) ;
@@ -172,15 +238,18 @@ class Evaluation {
172
238
binding . kind === 'rest_prop' ||
173
239
binding . kind === 'bindable_prop' ;
174
240
175
- if ( ! binding . updated && binding . initial !== null && ! is_prop ) {
176
- const evaluation = binding . scope . evaluate ( /** @type {Expression } */ ( binding . initial ) ) ;
177
- for ( const value of evaluation . values ) {
178
- this . values . add ( value ) ;
179
- }
241
+ if ( binding . initial ?. type === 'EachBlock' && binding . initial . index === expression . name ) {
242
+ this . values . add ( NUMBER ) ;
180
243
break ;
181
244
}
182
245
183
- // TODO each index is always defined
246
+ if ( ! binding . updated && binding . initial !== null && ! is_prop ) {
247
+ binding . scope . evaluate ( /** @type {Expression } */ ( binding . initial ) , this . values ) ;
248
+ break ;
249
+ }
250
+ } else if ( expression . name === 'undefined' ) {
251
+ this . values . add ( undefined ) ;
252
+ break ;
184
253
}
185
254
186
255
// TODO glean what we can from reassignments
@@ -336,6 +405,101 @@ class Evaluation {
336
405
break ;
337
406
}
338
407
408
+ case 'CallExpression' : {
409
+ const keypath = get_global_keypath ( expression . callee , scope ) ;
410
+
411
+ if ( keypath ) {
412
+ if ( is_rune ( keypath ) ) {
413
+ const arg = /** @type {Expression | undefined } */ ( expression . arguments [ 0 ] ) ;
414
+
415
+ switch ( keypath ) {
416
+ case '$state' :
417
+ case '$state.raw' :
418
+ case '$derived' :
419
+ if ( arg ) {
420
+ scope . evaluate ( arg , this . values ) ;
421
+ } else {
422
+ this . values . add ( undefined ) ;
423
+ }
424
+ break ;
425
+
426
+ case '$props.id' :
427
+ this . values . add ( STRING ) ;
428
+ break ;
429
+
430
+ case '$effect.tracking' :
431
+ this . values . add ( false ) ;
432
+ this . values . add ( true ) ;
433
+ break ;
434
+
435
+ case '$derived.by' :
436
+ if ( arg ?. type === 'ArrowFunctionExpression' && arg . body . type !== 'BlockStatement' ) {
437
+ scope . evaluate ( arg . body , this . values ) ;
438
+ break ;
439
+ }
440
+
441
+ this . values . add ( UNKNOWN ) ;
442
+ break ;
443
+
444
+ default : {
445
+ this . values . add ( UNKNOWN ) ;
446
+ }
447
+ }
448
+
449
+ break ;
450
+ }
451
+
452
+ if (
453
+ Object . hasOwn ( globals , keypath ) &&
454
+ expression . arguments . every ( ( arg ) => arg . type !== 'SpreadElement' )
455
+ ) {
456
+ const [ type , fn ] = globals [ keypath ] ;
457
+ const values = expression . arguments . map ( ( arg ) => scope . evaluate ( arg ) ) ;
458
+
459
+ if ( fn && values . every ( ( e ) => e . is_known ) ) {
460
+ this . values . add ( fn ( ...values . map ( ( e ) => e . value ) ) ) ;
461
+ } else {
462
+ this . values . add ( type ) ;
463
+ }
464
+
465
+ break ;
466
+ }
467
+ }
468
+
469
+ this . values . add ( UNKNOWN ) ;
470
+ break ;
471
+ }
472
+
473
+ case 'TemplateLiteral' : {
474
+ let result = expression . quasis [ 0 ] . value . cooked ;
475
+
476
+ for ( let i = 0 ; i < expression . expressions . length ; i += 1 ) {
477
+ const e = scope . evaluate ( expression . expressions [ i ] ) ;
478
+
479
+ if ( e . is_known ) {
480
+ result += e . value + expression . quasis [ i + 1 ] . value . cooked ;
481
+ } else {
482
+ this . values . add ( STRING ) ;
483
+ break ;
484
+ }
485
+ }
486
+
487
+ this . values . add ( result ) ;
488
+ break ;
489
+ }
490
+
491
+ case 'MemberExpression' : {
492
+ const keypath = get_global_keypath ( expression , scope ) ;
493
+
494
+ if ( keypath && Object . hasOwn ( global_constants , keypath ) ) {
495
+ this . values . add ( global_constants [ keypath ] ) ;
496
+ break ;
497
+ }
498
+
499
+ this . values . add ( UNKNOWN ) ;
500
+ break ;
501
+ }
502
+
339
503
default : {
340
504
this . values . add ( UNKNOWN ) ;
341
505
}
@@ -548,10 +712,10 @@ export class Scope {
548
712
* Only call this once scope has been fully generated in a first pass,
549
713
* else this evaluates on incomplete data and may yield wrong results.
550
714
* @param {Expression } expression
551
- * @param {Set<any> } values
715
+ * @param {Set<any> } [ values]
552
716
*/
553
717
evaluate ( expression , values = new Set ( ) ) {
554
- return new Evaluation ( this , expression ) ;
718
+ return new Evaluation ( this , expression , values ) ;
555
719
}
556
720
}
557
721
@@ -1115,7 +1279,19 @@ export function get_rune(node, scope) {
1115
1279
if ( ! node ) return null ;
1116
1280
if ( node . type !== 'CallExpression' ) return null ;
1117
1281
1118
- let n = node . callee ;
1282
+ const keypath = get_global_keypath ( node . callee , scope ) ;
1283
+
1284
+ if ( ! keypath || ! is_rune ( keypath ) ) return null ;
1285
+ return keypath ;
1286
+ }
1287
+
1288
+ /**
1289
+ * Returns the name of the rune if the given expression is a `CallExpression` using a rune.
1290
+ * @param {Expression | Super } node
1291
+ * @param {Scope } scope
1292
+ */
1293
+ function get_global_keypath ( node , scope ) {
1294
+ let n = node ;
1119
1295
1120
1296
let joined = '' ;
1121
1297
@@ -1133,12 +1309,8 @@ export function get_rune(node, scope) {
1133
1309
1134
1310
if ( n . type !== 'Identifier' ) return null ;
1135
1311
1136
- joined = n . name + joined ;
1137
-
1138
- if ( ! is_rune ( joined ) ) return null ;
1139
-
1140
1312
const binding = scope . get ( n . name ) ;
1141
1313
if ( binding !== null ) return null ; // rune name, but references a variable or store
1142
1314
1143
- return joined ;
1315
+ return n . name + joined ;
1144
1316
}
0 commit comments