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' */
22/** @import { Context, Visitor } from 'zimmerframe' */
33/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
44import is_reference from 'is-reference' ;
@@ -18,8 +18,71 @@ import { validate_identifier_name } from './2-analyze/visitors/shared/utils.js';
1818
1919const UNKNOWN = Symbol ( 'unknown' ) ;
2020/** 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+ } ;
2386
2487export class Binding {
2588 /** @type {Scope } */
@@ -107,7 +170,7 @@ export class Binding {
107170
108171class Evaluation {
109172 /** @type {Set<any> } */
110- values = new Set ( ) ;
173+ values ;
111174
112175 /**
113176 * True if there is exactly one possible value
@@ -147,8 +210,11 @@ class Evaluation {
147210 *
148211 * @param {Scope } scope
149212 * @param {Expression } expression
213+ * @param {Set<any> } values
150214 */
151- constructor ( scope , expression ) {
215+ constructor ( scope , expression , values ) {
216+ this . values = values ;
217+
152218 switch ( expression . type ) {
153219 case 'Literal' : {
154220 this . values . add ( expression . value ) ;
@@ -172,15 +238,18 @@ class Evaluation {
172238 binding . kind === 'rest_prop' ||
173239 binding . kind === 'bindable_prop' ;
174240
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 ) ;
180243 break ;
181244 }
182245
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 ;
184253 }
185254
186255 // TODO glean what we can from reassignments
@@ -336,6 +405,101 @@ class Evaluation {
336405 break ;
337406 }
338407
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+
339503 default : {
340504 this . values . add ( UNKNOWN ) ;
341505 }
@@ -548,10 +712,10 @@ export class Scope {
548712 * Only call this once scope has been fully generated in a first pass,
549713 * else this evaluates on incomplete data and may yield wrong results.
550714 * @param {Expression } expression
551- * @param {Set<any> } values
715+ * @param {Set<any> } [ values]
552716 */
553717 evaluate ( expression , values = new Set ( ) ) {
554- return new Evaluation ( this , expression ) ;
718+ return new Evaluation ( this , expression , values ) ;
555719 }
556720}
557721
@@ -1115,7 +1279,19 @@ export function get_rune(node, scope) {
11151279 if ( ! node ) return null ;
11161280 if ( node . type !== 'CallExpression' ) return null ;
11171281
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 ;
11191295
11201296 let joined = '' ;
11211297
@@ -1133,12 +1309,8 @@ export function get_rune(node, scope) {
11331309
11341310 if ( n . type !== 'Identifier' ) return null ;
11351311
1136- joined = n . name + joined ;
1137-
1138- if ( ! is_rune ( joined ) ) return null ;
1139-
11401312 const binding = scope . get ( n . name ) ;
11411313 if ( binding !== null ) return null ; // rune name, but references a variable or store
11421314
1143- return joined ;
1315+ return n . name + joined ;
11441316}
0 commit comments