ripple 0.2.68 → 0.2.69

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