@@ -16,6 +16,10 @@ import { is_reserved, is_rune } from '../../utils.js';
1616import { determine_slot } from '../utils/slot.js' ;
1717import { validate_identifier_name } from './2-analyze/visitors/shared/utils.js' ;
1818
19+ export const UNKNOWN = Symbol ( ) ;
20+ export const NUMBER = Symbol ( ) ;
21+ export const STRING = Symbol ( ) ;
22+
1923export class Binding {
2024 /** @type {Scope } */
2125 scope ;
@@ -34,7 +38,7 @@ export class Binding {
3438 * For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()`
3539 * @type {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration | AST.EachBlock | AST.SnippetBlock }
3640 */
37- initial ;
41+ initial = null ;
3842
3943 /** @type {Array<{ node: Identifier; path: AST.SvelteNode[] }> } */
4044 references = [ ] ;
@@ -279,6 +283,114 @@ export class Scope {
279283 this . root . conflicts . add ( node . name ) ;
280284 }
281285 }
286+
287+ /**
288+ *
289+ * @param {Expression } expression
290+ * @param {Set<any> } values
291+ */
292+ evaluate ( expression , values = new Set ( ) ) {
293+ switch ( expression . type ) {
294+ case 'Literal' :
295+ values . add ( expression . value ) ;
296+ break ;
297+
298+ case 'Identifier' :
299+ const binding = this . get ( expression . name ) ;
300+ if ( binding && ! binding . updated && binding . initial !== null ) {
301+ this . evaluate ( /** @type {Expression } */ ( binding . initial ) , values ) ;
302+ break ;
303+ }
304+
305+ values . add ( UNKNOWN ) ;
306+ break ;
307+
308+ case 'BinaryExpression' :
309+ switch ( expression . operator ) {
310+ case '!=' :
311+ case '!==' :
312+ case '<' :
313+ case '<=' :
314+ case '>' :
315+ case '>=' :
316+ case '==' :
317+ case '===' :
318+ case 'in' :
319+ case 'instanceof' :
320+ values . add ( true ) ;
321+ values . add ( false ) ;
322+ break ;
323+
324+ case '%' :
325+ case '&' :
326+ case '*' :
327+ case '**' :
328+ case '-' :
329+ case '/' :
330+ case '<<' :
331+ case '>>' :
332+ case '>>>' :
333+ case '^' :
334+ case '|' :
335+ values . add ( NUMBER ) ;
336+ break ;
337+
338+ case '+' :
339+ const a = Array . from ( this . evaluate ( /** @type {Expression } */ ( expression . left ) ) ) ; // `left` cannot be `PrivateIdentifier` unless operator is `in`
340+ const b = Array . from ( this . evaluate ( expression . right ) ) ;
341+
342+ if (
343+ a . every ( ( v ) => v === STRING || typeof v === 'string' ) ||
344+ b . every ( ( v ) => v === STRING || typeof v === 'string' )
345+ ) {
346+ // concatenating strings
347+ if (
348+ a . includes ( STRING ) ||
349+ b . includes ( STRING ) ||
350+ a . length > 1 ||
351+ b . length > 1 ||
352+ typeof a [ 0 ] === 'symbol' ||
353+ typeof b [ 0 ] === 'symbol'
354+ ) {
355+ values . add ( STRING ) ;
356+ break ;
357+ }
358+
359+ values . add ( a [ 0 ] + b [ 0 ] ) ;
360+ break ;
361+ }
362+
363+ if (
364+ a . every ( ( v ) => v === NUMBER || typeof v === 'number' ) ||
365+ b . every ( ( v ) => v === NUMBER || typeof v === 'number' )
366+ ) {
367+ // adding numbers
368+ if ( a . includes ( NUMBER ) || b . includes ( NUMBER ) || a . length > 1 || b . length > 1 ) {
369+ values . add ( NUMBER ) ;
370+ break ;
371+ }
372+
373+ values . add ( a [ 0 ] + b [ 0 ] ) ;
374+ break ;
375+ }
376+
377+ values . add ( STRING ) ;
378+ values . add ( NUMBER ) ;
379+ break ;
380+
381+ default :
382+ values . add ( UNKNOWN ) ;
383+ }
384+ break ;
385+
386+ // TODO others (LogicalExpression, ConditionalExpression, Identifier when we know something about the binding, etc)
387+
388+ default :
389+ values . add ( UNKNOWN ) ;
390+ }
391+
392+ return values ;
393+ }
282394}
283395
284396export class ScopeRoot {
0 commit comments