Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: make Binding a class #15359

Merged
merged 3 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 65 additions & 17 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */
/** @import { Context, Visitor } from 'zimmerframe' */
/** @import { AST, Binding, DeclarationKind } from '#compiler' */
/** @import { AST, BindingKind, DeclarationKind } from '#compiler' */
import is_reference from 'is-reference';
import { walk } from 'zimmerframe';
import { create_expression_metadata } from './nodes.js';
Expand All @@ -16,6 +16,69 @@ import { is_reserved, is_rune } from '../../utils.js';
import { determine_slot } from '../utils/slot.js';
import { validate_identifier_name } from './2-analyze/visitors/shared/utils.js';

export class Binding {
/** @type {Scope} */
scope;

/** @type {Identifier} */
node;

/** @type {BindingKind} */
kind;

/** @type {DeclarationKind} */
declaration_kind;

/**
* What the value was initialized with.
* For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()`
* @type {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration | AST.EachBlock | AST.SnippetBlock}
*/
initial;

/** @type {Array<{ node: Identifier; path: AST.SvelteNode[] }>} */
references = [];

/**
* For `legacy_reactive`: its reactive dependencies
* @type {Binding[]}
*/
legacy_dependencies = [];

/**
* Legacy props: the `class` in `{ export klass as class}`. $props(): The `class` in { class: klass } = $props()
* @type {string | null}
*/
prop_alias = null;

/**
* Additional metadata, varies per binding type
* @type {null | { inside_rest?: boolean }}
*/
metadata = null;

is_called = false;
mutated = false;
reassigned = false;
updated = false;

/**
*
* @param {Scope} scope
* @param {Identifier} node
* @param {BindingKind} kind
* @param {DeclarationKind} declaration_kind
* @param {Binding['initial']} initial
*/
constructor(scope, node, kind, declaration_kind, initial) {
this.scope = scope;
this.node = node;
this.initial = initial;
this.kind = kind;
this.declaration_kind = declaration_kind;
}
}

export class Scope {
/** @type {ScopeRoot} */
root;
Expand Down Expand Up @@ -100,22 +163,7 @@ export class Scope {
e.declaration_duplicate(node, node.name);
}

/** @type {Binding} */
const binding = {
node,
references: [],
legacy_dependencies: [],
initial,
reassigned: false,
mutated: false,
updated: false,
scope: this,
kind,
declaration_kind,
is_called: false,
prop_alias: null,
metadata: null
};
const binding = new Binding(this, node, kind, declaration_kind, initial);

validate_identifier_name(binding, this.function_depth);

Expand Down
85 changes: 17 additions & 68 deletions packages/svelte/src/compiler/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type {
ClassDeclaration,
Expression,
FunctionDeclaration,
Identifier,
ImportDeclaration
} from 'estree';
import type { SourceMap } from 'magic-string';
import type { Scope } from '../phases/scope.js';
import type { Binding } from '../phases/scope.js';
import type { AST, Namespace } from './template.js';
import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js';

Expand Down Expand Up @@ -241,6 +234,20 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions &
hmr: CompileOptions['hmr'];
};

export type BindingKind =
| 'normal' // A variable that is not in any way special
| 'prop' // A normal prop (possibly reassigned or mutated)
| 'bindable_prop' // A prop one can `bind:` to (possibly reassigned or mutated)
| 'rest_prop' // A rest prop
| 'raw_state' // A state variable
| 'state' // A deeply reactive state variable
| 'derived' // A derived variable
| 'each' // An each block parameter
| 'snippet' // A snippet parameter
| 'store_sub' // A $store value
| 'legacy_reactive' // A `$:` declaration
| 'template'; // A binding declared in the template, e.g. in an `await` block or `const` tag

export type DeclarationKind =
| 'var'
| 'let'
Expand All @@ -251,66 +258,6 @@ export type DeclarationKind =
| 'rest_param'
| 'synthetic';

export interface Binding {
node: Identifier;
/**
* - `normal`: A variable that is not in any way special
* - `prop`: A normal prop (possibly reassigned or mutated)
* - `bindable_prop`: A prop one can `bind:` to (possibly reassigned or mutated)
* - `rest_prop`: A rest prop
* - `state`: A state variable
* - `derived`: A derived variable
* - `each`: An each block parameter
* - `snippet`: A snippet parameter
* - `store_sub`: A $store value
* - `legacy_reactive`: A `$:` declaration
* - `template`: A binding declared in the template, e.g. in an `await` block or `const` tag
*/
kind:
| 'normal'
| 'prop'
| 'bindable_prop'
| 'rest_prop'
| 'state'
| 'raw_state'
| 'derived'
| 'each'
| 'snippet'
| 'store_sub'
| 'legacy_reactive'
| 'template'
| 'snippet';
declaration_kind: DeclarationKind;
/**
* What the value was initialized with.
* For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()`
*/
initial:
| null
| Expression
| FunctionDeclaration
| ClassDeclaration
| ImportDeclaration
| AST.EachBlock
| AST.SnippetBlock;
is_called: boolean;
references: { node: Identifier; path: AST.SvelteNode[] }[];
mutated: boolean;
reassigned: boolean;
/** `true` if mutated _or_ reassigned */
updated: boolean;
scope: Scope;
/** For `legacy_reactive`: its reactive dependencies */
legacy_dependencies: Binding[];
/** Legacy props: the `class` in `{ export klass as class}`. $props(): The `class` in { class: klass } = $props() */
prop_alias: string | null;
/** Additional metadata, varies per binding type */
metadata: {
/** `true` if is (inside) a rest parameter */
inside_rest?: boolean;
} | null;
}

export interface ExpressionMetadata {
/** All the bindings that are referenced inside this expression */
dependencies: Set<Binding>;
Expand All @@ -322,5 +269,7 @@ export interface ExpressionMetadata {

export * from './template.js';

export { Binding, Scope } from '../phases/scope.js';

// TODO this chain is a bit weird
export { ReactiveStatement } from '../phases/types.js';
2 changes: 1 addition & 1 deletion packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,8 @@ declare module 'svelte/animate' {
}

declare module 'svelte/compiler' {
import type { Expression, Identifier, ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, MemberExpression, Node, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression, SequenceExpression } from 'estree';
import type { SourceMap } from 'magic-string';
import type { ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, Expression, Identifier, MemberExpression, Node, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression, SequenceExpression } from 'estree';
import type { Location } from 'locate-character';
/**
* `compile` converts your `.svelte` source code into a JavaScript module that exports a component
Expand Down