ripple 0.2.90 → 0.2.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
- /** @import * as ESTree from 'estree' */
1
+ /** @import { ClassDeclaration, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator, Super } from 'estree' */
2
+ /** @import { DeclarationKind, BindingKind, Component, Element } from '#compiler' */
3
+ /** @import { RippleNode } from '#compiler' */
2
4
 
3
5
  import is_reference from 'is-reference';
4
6
  import { extract_identifiers, object, unwrap_pattern } from '../utils/ast.js';
@@ -6,414 +8,486 @@ import { walk } from 'zimmerframe';
6
8
  import { is_reserved } from './utils.js';
7
9
  import * as b from '../utils/builders.js';
8
10
 
11
+ export class Binding {
12
+ /** @type {Scope} */
13
+ scope;
14
+
15
+ /** @type {Identifier} */
16
+ node;
17
+
18
+ /** @type {BindingKind} */
19
+ kind;
20
+
21
+ /** @type {DeclarationKind} */
22
+ declaration_kind;
23
+
24
+ /**
25
+ * What the value was initialized with
26
+ * @type {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration}
27
+ */
28
+ initial = null;
29
+
30
+ /** @type {Array<{ node: Identifier; path: RippleNode[] }>} */
31
+ references = [];
32
+
33
+ mutated = false;
34
+ reassigned = false;
35
+ is_called = false;
36
+ metadata = null;
37
+
38
+ /**
39
+ * @param {Scope} scope
40
+ * @param {Identifier} node
41
+ * @param {BindingKind} kind
42
+ * @param {DeclarationKind} declaration_kind
43
+ * @param {Binding['initial']} initial
44
+ */
45
+ constructor(scope, node, kind, declaration_kind, initial) {
46
+ this.scope = scope;
47
+ this.node = node;
48
+ this.initial = initial;
49
+ this.kind = kind;
50
+ this.declaration_kind = declaration_kind;
51
+ }
52
+
53
+ get updated() {
54
+ return this.mutated || this.reassigned;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Create scopes for an AST
60
+ * @param {Node} ast - The AST to create scopes for
61
+ * @param {ScopeRoot} root - Root scope manager
62
+ * @param {Scope | null} parent - Parent scope
63
+ * @returns {{ scope: Scope, scopes: Map<RippleNode, Scope> }} Scope information
64
+ */
9
65
  export function create_scopes(ast, root, parent) {
10
- const scopes = new Map();
11
- const scope = new Scope(root, parent, false);
12
- scopes.set(ast, scope);
13
-
14
- const state = { scope };
15
- const references = [];
16
- const updates = [];
17
-
18
- function add_params(scope, params) {
19
- for (const param of params) {
20
- for (const node of extract_identifiers(param)) {
21
- scope.declare(node, 'normal', param.type === 'RestElement' ? 'rest_param' : 'param');
22
- }
23
- }
24
- }
25
-
26
- const create_block_scope = (node, { state, next }) => {
27
- const scope = state.scope.child(true);
28
- scopes.set(node, scope);
29
-
30
- if (node.type === 'ForOfStatement') {
31
- if (node.index) {
32
- state.scope.declare(node.index, 'normal', 'let');
33
- }
34
- }
35
-
36
- next({ scope });
37
- };
38
-
39
- walk(ast, state, {
40
- // references
41
- Identifier(node, { path, state }) {
42
- const parent = path.at(-1);
43
- if (
44
- parent &&
45
- is_reference(node, /** @type {Node} */ (parent)) &&
46
- // TSTypeAnnotation, TSInterfaceDeclaration etc - these are normally already filtered out,
47
- // but for the migration they aren't, so we need to filter them out here
48
- // TODO -> once migration script is gone we can remove this check
49
- !parent.type.startsWith('TS')
50
- ) {
51
- references.push([state.scope, { node, path: path.slice() }]);
52
- }
53
- },
54
-
55
- AssignmentExpression(node, { state, next }) {
56
- updates.push([state.scope, node.left]);
57
- next();
58
- },
59
-
60
- UpdateExpression(node, { state, next }) {
61
- updates.push([state.scope, /** @type {Identifier | MemberExpression} */ (node.argument)]);
62
- next();
63
- },
64
-
65
- ImportDeclaration(node, { state }) {
66
- for (const specifier of node.specifiers) {
67
- state.scope.declare(specifier.local, 'normal', 'import', node);
68
- }
69
- },
70
-
71
- Component(node, { state, next }) {
72
- const scope = state.scope.child();
73
- scopes.set(node, scope);
74
-
75
- if (node.id) scope.declare(node.id, 'normal', 'component');
76
-
77
- add_params(scope, node.params);
78
- next({ scope });
79
- },
80
-
81
- Element(node, { state, next }) {
82
- const scope = state.scope.child();
83
- scopes.set(node, scope);
84
-
85
- next({ scope });
86
- },
87
-
88
- FunctionExpression(node, { state, next }) {
89
- const scope = state.scope.child();
90
- scopes.set(node, scope);
91
-
92
- if (node.id) scope.declare(node.id, 'normal', 'function');
93
-
94
- add_params(scope, node.params);
95
- next({ scope });
96
- },
97
-
98
- FunctionDeclaration(node, { state, next }) {
99
- if (node.id) state.scope.declare(node.id, 'normal', 'function', node);
100
-
101
- const scope = state.scope.child();
102
- scopes.set(node, scope);
103
-
104
- add_params(scope, node.params);
105
- next({ scope });
106
- },
107
-
108
- ArrowFunctionExpression(node, { state, next }) {
109
- const scope = state.scope.child();
110
- scopes.set(node, scope);
111
-
112
- add_params(scope, node.params);
113
- next({ scope });
114
- },
115
-
116
- ForStatement: create_block_scope,
117
- ForInStatement: create_block_scope,
118
- ForOfStatement: create_block_scope,
119
- SwitchStatement: create_block_scope,
120
- BlockStatement(node, context) {
121
- const parent = context.path.at(-1);
122
- if (
123
- parent?.type === 'FunctionDeclaration' ||
124
- parent?.type === 'FunctionExpression' ||
125
- parent?.type === 'ArrowFunctionExpression'
126
- ) {
127
- // We already created a new scope for the function
128
- context.next();
129
- } else {
130
- create_block_scope(node, context);
131
- }
132
- },
133
-
134
- Eval(node, context) {
135
- const finalizer = node.finalizer;
136
-
137
- if (finalizer !== null) {
138
- for (const node of finalizer.body) {
139
- context.visit(node);
140
- }
141
- }
142
- },
143
-
144
- ClassDeclaration(node, { state, next }) {
145
- if (node.id) state.scope.declare(node.id, 'normal', 'let', node);
146
- next();
147
- },
148
-
149
- VariableDeclaration(node, { state, path, next }) {
150
- for (const declarator of node.declarations) {
151
- /** @type {Binding[]} */
152
- const bindings = [];
153
-
154
- state.scope.declarators.set(declarator, bindings);
155
-
156
- for (const id of extract_identifiers(declarator.id)) {
157
- const binding = state.scope.declare(id, 'normal', node.kind, declarator.init);
158
- bindings.push(binding);
159
- }
160
- }
161
-
162
- next();
163
- },
164
-
165
- CatchClause(node, { state, next }) {
166
- if (node.param) {
167
- const scope = state.scope.child(true);
168
- scopes.set(node, scope);
169
-
170
- for (const id of extract_identifiers(node.param)) {
171
- scope.declare(id, 'normal', 'let');
172
- }
173
-
174
- next({ scope });
175
- } else {
176
- next();
177
- }
178
- },
179
- });
180
-
181
- for (const [scope, { node, path }] of references) {
182
- scope.reference(node, path);
183
- }
184
-
185
- for (const [scope, node] of updates) {
186
- for (const expression of unwrap_pattern(node)) {
187
- const left = object(expression);
188
- const binding = left && scope.get(left.name);
189
-
190
- if (binding !== null && left !== binding.node) {
191
- binding.updated = true;
192
-
193
- if (left === expression) {
194
- binding.reassigned = true;
195
- } else {
196
- binding.mutated = true;
197
- }
198
- }
199
- }
200
- }
201
-
202
- return {
203
- scope,
204
- scopes,
205
- };
66
+ /** @typedef {{ scope: Scope }} State */
67
+
68
+ /** @type {Map<RippleNode, Scope>} */
69
+ const scopes = new Map();
70
+ const scope = new Scope(root, parent, false);
71
+ scopes.set(ast, scope);
72
+
73
+ /** @type {State} */
74
+ const state = { scope };
75
+ /** @type {Array<[Scope, { node: Identifier, path: RippleNode[] }]>} */
76
+ const references = [];
77
+ /** @type {Array<[Scope, Pattern | MemberExpression]>} */
78
+ const updates = [];
79
+
80
+ /**
81
+ * Add parameters to scope
82
+ * @param {Scope} scope - The scope to add parameters to
83
+ * @param {Pattern[]} params - Parameter nodes
84
+ */
85
+ function add_params(scope, params) {
86
+ for (const param of params) {
87
+ for (const node of extract_identifiers(param)) {
88
+ scope.declare(node, 'normal', param.type === 'RestElement' ? 'rest_param' : 'param');
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Create a block scope
95
+ * @param {any} node - AST node
96
+ * @param {{ state: any, next: Function }} context - Visitor context
97
+ */
98
+ const create_block_scope = (node, { state, next }) => {
99
+ const scope = state.scope.child(true);
100
+ scopes.set(node, scope);
101
+
102
+ if (node.type === 'ForOfStatement') {
103
+ if (node.index) {
104
+ state.scope.declare(node.index, 'normal', 'let');
105
+ }
106
+ }
107
+
108
+ next({ scope });
109
+ };
110
+
111
+ walk(/** @type {RippleNode} */ (ast), state, {
112
+ // references
113
+ Identifier(node, { path, state }) {
114
+ const parent = path.at(-1);
115
+ if (
116
+ parent &&
117
+ is_reference(node, /** @type {Node} */ (parent)) &&
118
+ // TSTypeAnnotation, TSInterfaceDeclaration etc - these are normally already filtered out,
119
+ // but for the migration they aren't, so we need to filter them out here
120
+ // TODO -> once migration script is gone we can remove this check
121
+ !parent.type.startsWith('TS')
122
+ ) {
123
+ references.push([state.scope, { node, path: path.slice() }]);
124
+ }
125
+ },
126
+
127
+ AssignmentExpression(node, { state, next }) {
128
+ updates.push([state.scope, node.left]);
129
+ next();
130
+ },
131
+
132
+ UpdateExpression(node, { state, next }) {
133
+ updates.push([state.scope, /** @type {Identifier | MemberExpression} */ (node.argument)]);
134
+ next();
135
+ },
136
+
137
+ ImportDeclaration(node, { state }) {
138
+ for (const specifier of node.specifiers) {
139
+ state.scope.declare(specifier.local, 'normal', 'import', node);
140
+ }
141
+ },
142
+
143
+ /**
144
+ * @param {Component} node
145
+ * @param {Object} context
146
+ * @param {any} context.state
147
+ * @param {Function} context.next
148
+ */
149
+ Component(node, { state, next }) {
150
+ const scope = state.scope.child();
151
+ scopes.set(node, scope);
152
+
153
+ scope.declare(node.id, 'normal', 'component');
154
+
155
+ add_params(scope, node.params);
156
+ next({ scope });
157
+ },
158
+
159
+ /**
160
+ * @param {Element} node
161
+ * @param {Object} context
162
+ * @param {any} context.state
163
+ * @param {Function} context.next
164
+ */
165
+ Element(node, { state, next }) {
166
+ const scope = state.scope.child();
167
+ scopes.set(node, scope);
168
+
169
+ next({ scope });
170
+ },
171
+
172
+ FunctionExpression(node, { state, next }) {
173
+ const scope = state.scope.child();
174
+ scopes.set(node, scope);
175
+
176
+ if (node.id) scope.declare(node.id, 'normal', 'function');
177
+
178
+ add_params(scope, node.params);
179
+ next({ scope });
180
+ },
181
+
182
+ FunctionDeclaration(node, { state, next }) {
183
+ if (node.id) state.scope.declare(node.id, 'normal', 'function', node);
184
+
185
+ const scope = state.scope.child();
186
+ scopes.set(node, scope);
187
+
188
+ add_params(scope, node.params);
189
+ next({ scope });
190
+ },
191
+
192
+ ArrowFunctionExpression(node, { state, next }) {
193
+ const scope = state.scope.child();
194
+ scopes.set(node, scope);
195
+
196
+ add_params(scope, node.params);
197
+ next({ scope });
198
+ },
199
+
200
+ ForStatement: create_block_scope,
201
+ ForInStatement: create_block_scope,
202
+ ForOfStatement: create_block_scope,
203
+ SwitchStatement: create_block_scope,
204
+ BlockStatement(node, context) {
205
+ const parent = context.path.at(-1);
206
+ if (
207
+ parent?.type === 'FunctionDeclaration' ||
208
+ parent?.type === 'FunctionExpression' ||
209
+ parent?.type === 'ArrowFunctionExpression'
210
+ ) {
211
+ // We already created a new scope for the function
212
+ context.next();
213
+ } else {
214
+ create_block_scope(node, context);
215
+ }
216
+ },
217
+
218
+ ClassDeclaration(node, { state, next }) {
219
+ if (node.id) state.scope.declare(node.id, 'normal', 'let', node);
220
+ next();
221
+ },
222
+
223
+ VariableDeclaration(node, { state, path, next }) {
224
+ for (const declarator of node.declarations) {
225
+ /** @type {Binding[]} */
226
+ const bindings = [];
227
+
228
+ state.scope.declarators.set(declarator, bindings);
229
+
230
+ for (const id of extract_identifiers(declarator.id)) {
231
+ const binding = state.scope.declare(id, 'normal', node.kind, declarator.init);
232
+ bindings.push(binding);
233
+ }
234
+ }
235
+
236
+ next();
237
+ },
238
+
239
+ CatchClause(node, { state, next }) {
240
+ if (node.param) {
241
+ const scope = state.scope.child(true);
242
+ scopes.set(node, scope);
243
+
244
+ for (const id of extract_identifiers(node.param)) {
245
+ scope.declare(id, 'normal', 'let');
246
+ }
247
+
248
+ next({ scope });
249
+ } else {
250
+ next();
251
+ }
252
+ },
253
+ });
254
+
255
+ for (const [scope, { node, path }] of references) {
256
+ scope.reference(node, path);
257
+ }
258
+
259
+ for (const [scope, node] of updates) {
260
+ for (const expression of unwrap_pattern(node)) {
261
+ const left = object(expression);
262
+ const binding = left && scope.get(left.name);
263
+
264
+ if (binding !== null && left !== binding.node) {
265
+ binding.updated = true;
266
+
267
+ if (left === expression) {
268
+ binding.reassigned = true;
269
+ } else {
270
+ binding.mutated = true;
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ return {
277
+ scope,
278
+ scopes,
279
+ };
206
280
  }
207
281
 
208
282
  export class Scope {
209
- /** @type {ScopeRoot} */
210
- root;
211
-
212
- /**
213
- * The immediate parent scope
214
- * @type {Scope | null}
215
- */
216
- parent;
217
-
218
- /**
219
- * Whether or not `var` declarations are contained by this scope
220
- * @type {boolean}
221
- */
222
- #porous;
223
-
224
- /**
225
- * A map of every identifier declared by this scope, and all the
226
- * identifiers that reference it
227
- * @type {Map<string, Binding>}
228
- */
229
- declarations = new Map();
230
-
231
- /**
232
- * A map of declarators to the bindings they declare
233
- * @type {Map<VariableDeclarator | AST.LetDirective, Binding[]>}
234
- */
235
- declarators = new Map();
236
-
237
- /**
238
- * A set of all the names referenced with this scope
239
- * — useful for generating unique names
240
- * @type {Map<string, { node: Identifier; path: AST.Node[] }[]>}
241
- */
242
- references = new Map();
243
-
244
- /**
245
- * The scope depth allows us to determine if a state variable is referenced in its own scope,
246
- * which is usually an error. Block statements do not increase this value
247
- */
248
- function_depth = 0;
249
-
250
- /**
251
- * If tracing of reactive dependencies is enabled for this scope
252
- * @type {null | Expression}
253
- */
254
- tracing = null;
255
-
256
- /**
257
- *
258
- * @param {ScopeRoot} root
259
- * @param {Scope | null} parent
260
- * @param {boolean} porous
261
- */
262
- constructor(root, parent, porous) {
263
- this.root = root;
264
- this.parent = parent;
265
- this.#porous = porous;
266
- this.function_depth = parent ? parent.function_depth + (porous ? 0 : 1) : 0;
267
- }
268
-
269
- /**
270
- * @param {Identifier} node
271
- * @param {Binding['kind']} kind
272
- * @param {DeclarationKind} declaration_kind
273
- * @param {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration} initial
274
- * @returns {Binding}
275
- */
276
- declare(node, kind, declaration_kind, initial = null) {
277
- if (this.parent) {
278
- if (declaration_kind === 'var' && this.#porous) {
279
- return this.parent.declare(node, kind, declaration_kind);
280
- }
281
-
282
- if (declaration_kind === 'import') {
283
- return this.parent.declare(node, kind, declaration_kind, initial);
284
- }
285
- }
286
-
287
- if (node.name === '_$_') {
288
- throw new Error('Cannot declare a variable named "_$_" as it is a reserved identifier');
289
- }
290
-
291
- if (this.declarations.has(node.name)) {
292
- throw new Error(`'${node.name}' has already been declared in the current scope`);
293
- }
294
-
295
- /** @type {Binding} */
296
- const binding = {
297
- node,
298
- references: [],
299
- initial,
300
- reassigned: false,
301
- mutated: false,
302
- updated: false,
303
- scope: this,
304
- kind,
305
- declaration_kind,
306
- is_called: false,
307
- metadata: null,
308
- };
309
-
310
- this.declarations.set(node.name, binding);
311
- this.root.conflicts.add(node.name);
312
- return binding;
313
- }
314
-
315
- child(porous = false) {
316
- return new Scope(this.root, this, porous);
317
- }
318
-
319
- /**
320
- * @param {string} preferred_name
321
- * @returns {string}
322
- */
323
- generate(preferred_name) {
324
- if (this.#porous) {
325
- return /** @type {Scope} */ (this.parent).generate(preferred_name);
326
- }
327
-
328
- preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_').replace(/^[0-9]/, '_');
329
- let name = preferred_name;
330
- let n = 1;
331
-
332
- while (
333
- this.references.has(name) ||
334
- this.declarations.has(name) ||
335
- this.root.conflicts.has(name) ||
336
- is_reserved(name)
337
- ) {
338
- name = `${preferred_name}_${n++}`;
339
- }
340
-
341
- this.references.set(name, []);
342
- this.root.conflicts.add(name);
343
- return name;
344
- }
345
-
346
- /**
347
- * @param {string} name
348
- * @returns {Binding | null}
349
- */
350
- get(name) {
351
- return this.declarations.get(name) ?? this.parent?.get(name) ?? null;
352
- }
353
-
354
- /**
355
- * @param {VariableDeclarator | AST.LetDirective} node
356
- * @returns {Binding[]}
357
- */
358
- get_bindings(node) {
359
- const bindings = this.declarators.get(node);
360
- if (!bindings) {
361
- throw new Error('No binding found for declarator');
362
- }
363
- return bindings;
364
- }
365
-
366
- /**
367
- * @param {string} name
368
- * @returns {Scope | null}
369
- */
370
- owner(name) {
371
- return this.declarations.has(name) ? this : this.parent && this.parent.owner(name);
372
- }
373
-
374
- /**
375
- * @param {Identifier} node
376
- * @param {AST.Node[]} path
377
- */
378
- reference(node, path) {
379
- path = [...path]; // ensure that mutations to path afterwards don't affect this reference
380
- let references = this.references.get(node.name);
381
-
382
- if (!references) this.references.set(node.name, (references = []));
383
-
384
- references.push({ node, path });
385
-
386
- const binding = this.declarations.get(node.name);
387
- if (binding) {
388
- binding.references.push({ node, path });
389
- } else if (this.parent) {
390
- this.parent.reference(node, path);
391
- } else {
392
- // no binding was found, and this is the top level scope,
393
- // which means this is a global
394
- this.root.conflicts.add(node.name);
395
- }
396
- }
283
+ /** @type {ScopeRoot} */
284
+ root;
285
+
286
+ /**
287
+ * The immediate parent scope
288
+ * @type {Scope | null}
289
+ */
290
+ parent;
291
+
292
+ /**
293
+ * Whether or not `var` declarations are contained by this scope
294
+ * @type {boolean}
295
+ */
296
+ #porous;
297
+
298
+ /**
299
+ * A map of every identifier declared by this scope, and all the
300
+ * identifiers that reference it
301
+ * @type {Map<string, Binding>}
302
+ */
303
+ declarations = new Map();
304
+
305
+ /**
306
+ * A map of declarators to the bindings they declare
307
+ * @type {Map<VariableDeclarator, Binding[]>}
308
+ */
309
+ declarators = new Map();
310
+
311
+ /**
312
+ * A set of all the names referenced with this scope
313
+ * — useful for generating unique names
314
+ * @type {Map<string, { node: Identifier; path: RippleNode[] }[]>}
315
+ */
316
+ references = new Map();
317
+
318
+ /**
319
+ * The scope depth allows us to determine if a state variable is referenced in its own scope,
320
+ * which is usually an error. Block statements do not increase this value
321
+ */
322
+ function_depth = 0;
323
+
324
+ /**
325
+ * If tracing of reactive dependencies is enabled for this scope
326
+ * @type {null | Expression}
327
+ */
328
+ tracing = null;
329
+
330
+ /**
331
+ *
332
+ * @param {ScopeRoot} root
333
+ * @param {Scope | null} parent
334
+ * @param {boolean} porous
335
+ */
336
+ constructor(root, parent, porous) {
337
+ this.root = root;
338
+ this.parent = parent;
339
+ this.#porous = porous;
340
+ this.function_depth = parent ? parent.function_depth + (porous ? 0 : 1) : 0;
341
+ }
342
+
343
+ /**
344
+ * @param {Identifier} node
345
+ * @param {Binding['kind']} kind
346
+ * @param {DeclarationKind} declaration_kind
347
+ * @param {null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration} initial
348
+ * @returns {Binding}
349
+ */
350
+ declare(node, kind, declaration_kind, initial = null) {
351
+ if (this.parent) {
352
+ if (declaration_kind === 'var' && this.#porous) {
353
+ return this.parent.declare(node, kind, declaration_kind);
354
+ }
355
+
356
+ if (declaration_kind === 'import') {
357
+ return this.parent.declare(node, kind, declaration_kind, initial);
358
+ }
359
+ }
360
+
361
+ if (node.name === '_$_') {
362
+ throw new Error('Cannot declare a variable named "_$_" as it is a reserved identifier');
363
+ }
364
+
365
+ if (this.declarations.has(node.name)) {
366
+ throw new Error(`'${node.name}' has already been declared in the current scope`);
367
+ }
368
+
369
+ /** @type {Binding} */
370
+ const binding = {
371
+ node,
372
+ references: [],
373
+ initial,
374
+ reassigned: false,
375
+ mutated: false,
376
+ updated: false,
377
+ scope: this,
378
+ kind,
379
+ declaration_kind,
380
+ is_called: false,
381
+ metadata: null,
382
+ };
383
+
384
+ this.declarations.set(node.name, binding);
385
+ this.root.conflicts.add(node.name);
386
+ return binding;
387
+ }
388
+
389
+ child(porous = false) {
390
+ return new Scope(this.root, this, porous);
391
+ }
392
+
393
+ /**
394
+ * @param {string} preferred_name
395
+ * @returns {string}
396
+ */
397
+ generate(preferred_name) {
398
+ if (this.#porous) {
399
+ return /** @type {Scope} */ (this.parent).generate(preferred_name);
400
+ }
401
+
402
+ preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_').replace(/^[0-9]/, '_');
403
+ let name = preferred_name;
404
+ let n = 1;
405
+
406
+ while (
407
+ this.references.has(name) ||
408
+ this.declarations.has(name) ||
409
+ this.root.conflicts.has(name) ||
410
+ is_reserved(name)
411
+ ) {
412
+ name = `${preferred_name}_${n++}`;
413
+ }
414
+
415
+ this.references.set(name, []);
416
+ this.root.conflicts.add(name);
417
+ return name;
418
+ }
419
+
420
+ /**
421
+ * @param {string} name
422
+ * @returns {Binding | null}
423
+ */
424
+ get(name) {
425
+ return this.declarations.get(name) ?? this.parent?.get(name) ?? null;
426
+ }
427
+
428
+ /**
429
+ * @param {VariableDeclarator} node
430
+ * @returns {Binding[]}
431
+ */
432
+ get_bindings(node) {
433
+ const bindings = this.declarators.get(node);
434
+ if (!bindings) {
435
+ throw new Error('No binding found for declarator');
436
+ }
437
+ return bindings;
438
+ }
439
+
440
+ /**
441
+ * @param {string} name
442
+ * @returns {Scope | null}
443
+ */
444
+ owner(name) {
445
+ return this.declarations.has(name) ? this : this.parent && this.parent.owner(name);
446
+ }
447
+
448
+ /**
449
+ * @param {Identifier} node
450
+ * @param {RippleNode[]} path
451
+ */
452
+ reference(node, path) {
453
+ path = [...path]; // ensure that mutations to path afterwards don't affect this reference
454
+ let references = this.references.get(node.name);
455
+
456
+ if (!references) this.references.set(node.name, (references = []));
457
+
458
+ references.push({ node, path });
459
+
460
+ const binding = this.declarations.get(node.name);
461
+ if (binding) {
462
+ binding.references.push({ node, path });
463
+ } else if (this.parent) {
464
+ this.parent.reference(node, path);
465
+ } else {
466
+ // no binding was found, and this is the top level scope,
467
+ // which means this is a global
468
+ this.root.conflicts.add(node.name);
469
+ }
470
+ }
397
471
  }
398
472
 
399
473
  export class ScopeRoot {
400
- /** @type {Set<string>} */
401
- conflicts = new Set();
402
-
403
- /**
404
- * @param {string} preferred_name
405
- */
406
- unique(preferred_name) {
407
- preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_');
408
- let final_name = preferred_name;
409
- let n = 1;
410
-
411
- while (this.conflicts.has(final_name)) {
412
- final_name = `${preferred_name}_${n++}`;
413
- }
414
-
415
- this.conflicts.add(final_name);
416
- const id = b.id(final_name);
417
- return id;
418
- }
474
+ /** @type {Set<string>} */
475
+ conflicts = new Set();
476
+
477
+ /**
478
+ * @param {string} preferred_name
479
+ */
480
+ unique(preferred_name) {
481
+ preferred_name = preferred_name.replace(/[^a-zA-Z0-9_$]/g, '_');
482
+ let final_name = preferred_name;
483
+ let n = 1;
484
+
485
+ while (this.conflicts.has(final_name)) {
486
+ final_name = `${preferred_name}_${n++}`;
487
+ }
488
+
489
+ this.conflicts.add(final_name);
490
+ const id = b.id(final_name);
491
+ return id;
492
+ }
419
493
  }