surplus 0.3.0

Core library for the Surplus JSX compiler
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
use std::cell::Cell;

use oxc::{
	allocator::{CloneIn, Vec},
	ast::ast::{
		Argument, ArrowFunctionExpression, AssignmentExpression, AssignmentOperator,
		AssignmentTarget, BinaryExpression, BinaryOperator, BindingIdentifier, BindingPattern,
		BindingPatternKind, CallExpression, Expression, ExpressionStatement, FormalParameter,
		FormalParameterKind, FormalParameters, FunctionBody, IdentifierName, IdentifierReference,
		ObjectExpression, ObjectPropertyKind, Statement, StaticMemberExpression,
		VariableDeclaration, VariableDeclarationKind, VariableDeclarator,
	},
	semantic::{NodeId, ReferenceFlags, ScopeFlags, SymbolFlags},
	span::Atom,
	syntax::number::NumberBase,
};
use oxc_traverse::TraverseCtx;

use crate::constants::{
	K, NEW_PREV, PREV_ATTRS_IDENT, REMOVE_ATTRIBUTE, SET_ATTRIBUTE, UNDEFINED, V, VAL,
};

impl<'a> super::SurplusTraverser<'a> {
	pub fn inner_exit_jsx_spread_attribute(
		&mut self,
		node: &mut oxc::ast::ast::JSXSpreadAttribute<'a>,
		ctx: &mut TraverseCtx<'a>,
	) {
		if self
			.element_stack
			.last()
			.unwrap()
			.construction_obj
			.is_some()
		{
			self.element_stack
				.last_mut()
				.unwrap()
				.construction_obj
				.as_mut()
				.unwrap()
				.properties
				.push(ObjectPropertyKind::SpreadProperty(ctx.alloc(
					oxc::ast::ast::SpreadElement {
						span: node.span,
						argument: node.argument.clone_in(self.allocator),
					},
				)));
		} else {
			// DOM element: Create unique symbol for prev_keys.
			let prev_ident_str = PREV_ATTRS_IDENT;
			let prev_span = node.span;
			let prev_sym = ctx.scoping_mut().create_symbol(
				prev_span,
				prev_ident_str,
				SymbolFlags::Variable,
				self.element_stack.last().unwrap().scope,
				NodeId::DUMMY,
			);
			let prev_ident_binding = BindingIdentifier {
				span: prev_span,
				name: Atom::new_const(prev_ident_str),
				symbol_id: Cell::new(Some(prev_sym)),
			};

			let read_ref_id =
				ctx.create_reference(prev_ident_str, Some(prev_sym), ReferenceFlags::Read);
			let write_ref_id =
				ctx.create_reference(prev_ident_str, Some(prev_sym), ReferenceFlags::Write);

			let elem = self.element_stack.last().unwrap();

			let mut new_statements = Vec::new_in(self.allocator);

			// Add `let prev = {};` to statements.
			new_statements.push(Statement::VariableDeclaration(ctx.alloc(
				VariableDeclaration {
					span: prev_span,
					kind: VariableDeclarationKind::Let,
					declare: false,
					declarations: Vec::from_array_in(
						[VariableDeclarator {
							definite: false,
							span: prev_span,
							kind: VariableDeclarationKind::Let,
							id: BindingPattern {
								kind: BindingPatternKind::BindingIdentifier(
									ctx.alloc(prev_ident_binding.clone()),
								),
								type_annotation: None,
								optional: false,
							},
							init: Some(Expression::ObjectExpression(ctx.alloc(ObjectExpression {
								span: prev_span,
								properties: Vec::new_in(self.allocator),
								trailing_comma: None,
							}))),
						}],
						self.allocator,
					),
				},
			)));

			// Build the inner logic for the S computation.
			let v_ident = Atom::new_const(V);
			let val_ident = Atom::new_const(VAL);
			let new_prev_ident = Atom::new_const(NEW_PREV);
			let k_ident = Atom::new_const(K);

			// Arrow function scope.
			let arrow_scope = ctx.create_child_scope_of_current(ScopeFlags::Arrow);

			// Create scope for blocks with declarations.
			let block_scope = ctx.create_child_scope_of_current(ScopeFlags::empty());

			// Statements inside the arrow function.
			let arrow_statements = Vec::from_array_in(
				[
					// let new_prev = {};
					Statement::VariableDeclaration(ctx.alloc(VariableDeclaration {
						span: node.span,
						kind: VariableDeclarationKind::Let,
						declare: false,
						declarations: Vec::from_array_in(
							[VariableDeclarator {
								definite: false,
								span: node.span,
								kind: VariableDeclarationKind::Let,
								id: BindingPattern {
									kind: BindingPatternKind::BindingIdentifier(ctx.alloc(
										BindingIdentifier {
											span: node.span,
											name: new_prev_ident,
											symbol_id: Cell::new(None),
										},
									)),
									type_annotation: None,
									optional: false,
								},
								init: Some(Expression::ObjectExpression(ctx.alloc(
									ObjectExpression {
										span: node.span,
										properties: Vec::new_in(self.allocator),
										trailing_comma: None,
									},
								))),
							}],
							self.allocator,
						),
					})),
					// for (let k in v) { ... }
					{
						let for_in_scope = ctx.create_child_scope_of_current(ScopeFlags::empty());
						Statement::ForInStatement(ctx.alloc(oxc::ast::ast::ForInStatement {
                            span: node.span,
                            left: oxc::ast::ast::ForStatementLeft::VariableDeclaration(ctx.alloc(VariableDeclaration {
                                span: node.span,
                                kind: VariableDeclarationKind::Let,
                                declare: false,
                                declarations: Vec::from_array_in(
                                    [VariableDeclarator {
                                        definite: false,
                                        span: node.span,
                                        kind: VariableDeclarationKind::Let,
                                        id: BindingPattern {
                                            kind: BindingPatternKind::BindingIdentifier(ctx.alloc(BindingIdentifier {
                                                span: node.span,
                                                name: k_ident,
                                                symbol_id: Cell::new(None),
                                            })),
                                            type_annotation: None,
                                            optional: false,
                                        },
                                        init: None,
                                    }],
                                    self.allocator,
                                ),
                            })),
                            right: Expression::Identifier(ctx.alloc(IdentifierReference {
                                span: node.span,
                                name: v_ident,
                                reference_id: Cell::new(None),
                            })),
                            body: Statement::BlockStatement(ctx.alloc(oxc::ast::ast::BlockStatement {
                                span: node.span,
                                body: Vec::from_array_in(
                                    [
                                        // let val = v[k];
                                        Statement::VariableDeclaration(ctx.alloc(VariableDeclaration {
                                            span: node.span,
                                            kind: VariableDeclarationKind::Let,
                                            declare: false,
                                            declarations: Vec::from_array_in(
                                                [VariableDeclarator {
                                                    definite: false,
                                                    span: node.span,
                                                    kind: VariableDeclarationKind::Let,
                                                    id: BindingPattern {
                                                        kind: BindingPatternKind::BindingIdentifier(ctx.alloc(BindingIdentifier {
                                                            span: node.span,
                                                            name: val_ident,
                                                            symbol_id: Cell::new(None),
                                                        })),
                                                        type_annotation: None,
                                                        optional: false,
                                                    },
                                                    init: Some(Expression::ComputedMemberExpression(ctx.alloc(oxc::ast::ast::ComputedMemberExpression {
                                                        span: node.span,
                                                        object: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                            span: node.span,
                                                            name: v_ident,
                                                            reference_id: Cell::new(None),
                                                        })),
                                                        expression: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                            span: node.span,
                                                            name: k_ident,
                                                            reference_id: Cell::new(None),
                                                        })),
                                                        optional: false,
                                                    }))),
                                                }],
                                                self.allocator,
                                            ),
                                        })),
                                        // if (val !== undefined) { elem.setAttribute(k, val); new_prev[k] = 1; } else { elem.removeAttribute(k); }
                                        Statement::IfStatement(ctx.alloc(oxc::ast::ast::IfStatement {
                                            span: node.span,
                                            test: Expression::BinaryExpression(ctx.alloc(BinaryExpression {
                                                span: node.span,
                                                left: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                    span: node.span,
                                                    name: val_ident,
                                                    reference_id: Cell::new(None),
                                                })),
                                                operator: BinaryOperator::StrictInequality,
                                                right: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                    span: node.span,
                                                    name: Atom::new_const(UNDEFINED),
                                                    reference_id: Cell::new(None),
                                                })),
                                            })),
                                            consequent: Statement::BlockStatement(ctx.alloc(oxc::ast::ast::BlockStatement {
                                                span: node.span,
                                                body: Vec::from_array_in(
                                                    [
                                                        // elem.setAttribute(k, val);
                                                        Statement::ExpressionStatement(ctx.alloc(ExpressionStatement {
                                                            span: node.span,
                                                            expression: Expression::CallExpression(ctx.alloc(CallExpression {
                                                                span: node.span,
                                                                callee: Expression::StaticMemberExpression(ctx.alloc(StaticMemberExpression {
                                                                    span: node.span,
                                                                    object: elem.elem.as_ref().unwrap().ident.clone_in(self.allocator),
                                                                    property: IdentifierName {
                                                                        span: node.span,
                                                                        name: Atom::new_const(SET_ATTRIBUTE),
                                                                    },
                                                                    optional: false,
                                                                })),
                                                                type_arguments: None,
                                                                arguments: Vec::from_array_in(
                                                                    [
                                                                        Argument::Identifier(ctx.alloc(IdentifierReference {
                                                                            span: node.span,
                                                                            name: k_ident,
                                                                            reference_id: Cell::new(None),
                                                                        })),
                                                                        Argument::Identifier(ctx.alloc(IdentifierReference {
                                                                            span: node.span,
                                                                            name: val_ident,
                                                                            reference_id: Cell::new(None),
                                                                        })),
                                                                    ],
                                                                    self.allocator,
                                                                ),
                                                                optional: false,
                                                                pure: false,
                                                            })),
                                                        })),
                                                        // new_prev[k] = 1;
                                                        Statement::ExpressionStatement(ctx.alloc(ExpressionStatement {
                                                            span: node.span,
                                                            expression: Expression::AssignmentExpression(ctx.alloc(AssignmentExpression {
                                                                span: node.span,
                                                                operator: AssignmentOperator::Assign,
                                                                left: AssignmentTarget::ComputedMemberExpression(ctx.alloc(oxc::ast::ast::ComputedMemberExpression {
                                                                    span: node.span,
                                                                    object: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                                        span: node.span,
                                                                        name: new_prev_ident,
                                                                        reference_id: Cell::new(None),
                                                                    })),
                                                                    expression: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                                        span: node.span,
                                                                        name: k_ident,
                                                                        reference_id: Cell::new(None),
                                                                    })),
                                                                    optional: false,
                                                                })),
                                                                right: Expression::NumericLiteral(ctx.alloc(oxc::ast::ast::NumericLiteral {
                                                                    span: node.span,
                                                                    value: 1.0,
                                                                    raw: None,
                                                                    base: NumberBase::Decimal,
                                                                })),
                                                            })),
                                                        })),
                                                    ],
                                                    self.allocator,
                                                ),
                                                scope_id: Cell::new(None),
                                            })),
                                            alternate: Some(Statement::BlockStatement(ctx.alloc(oxc::ast::ast::BlockStatement {
                                                span: node.span,
                                                body: Vec::from_array_in(
                                                    [
                                                        // elem.removeAttribute(k);
                                                        Statement::ExpressionStatement(ctx.alloc(ExpressionStatement {
                                                            span: node.span,
                                                            expression: Expression::CallExpression(ctx.alloc(CallExpression {
                                                                span: node.span,
                                                                callee: Expression::StaticMemberExpression(ctx.alloc(StaticMemberExpression {
                                                                    span: node.span,
                                                                    object: elem.elem.as_ref().unwrap().ident.clone_in(self.allocator),
                                                                    property: IdentifierName {
                                                                        span: node.span,
                                                                        name: Atom::new_const(REMOVE_ATTRIBUTE),
                                                                    },
                                                                    optional: false,
                                                                })),
                                                                type_arguments: None,
                                                                arguments: Vec::from_array_in(
                                                                    [Argument::Identifier(ctx.alloc(IdentifierReference {
                                                                        span: node.span,
                                                                        name: k_ident,
                                                                        reference_id: Cell::new(None),
                                                                    }))],
                                                                    self.allocator,
                                                                ),
                                                                optional: false,
                                                                pure: false,
                                                            })),
                                                        })),
                                                    ],
                                                    self.allocator,
                                                ),
                                                scope_id: Cell::new(None),
                                            }))),
                                        })),
                                    ],
                                    self.allocator,
                                ),
                                scope_id: Cell::new(Some(block_scope)),
                            })),
                            scope_id: Cell::new(Some(for_in_scope)),
                        }))
					},
					// for (let k in prev) { if (!(k in new_prev)) { elem.removeAttribute(k); } }
					{
						let for_in_scope = ctx.create_child_scope_of_current(ScopeFlags::empty());
						Statement::ForInStatement(ctx.alloc(oxc::ast::ast::ForInStatement {
                            span: node.span,
                            left: oxc::ast::ast::ForStatementLeft::VariableDeclaration(ctx.alloc(VariableDeclaration {
                                span: node.span,
                                kind: VariableDeclarationKind::Let,
                                declare: false,
                                declarations: Vec::from_array_in(
                                    [VariableDeclarator {
                                        definite: false,
                                        span: node.span,
                                        kind: VariableDeclarationKind::Let,
                                        id: BindingPattern {
                                            kind: BindingPatternKind::BindingIdentifier(ctx.alloc(BindingIdentifier {
                                                span: node.span,
                                                name: k_ident,
                                                symbol_id: Cell::new(None),
                                            })),
                                            type_annotation: None,
                                            optional: false,
                                        },
                                        init: None,
                                    }],
                                    self.allocator,
                                ),
                            })),
                            right: Expression::Identifier(ctx.alloc(IdentifierReference {
                                span: node.span,
                                name: Atom::new_const(PREV_ATTRS_IDENT),
                                reference_id: Cell::new(Some(read_ref_id)),
                            })),
                            body: Statement::BlockStatement(ctx.alloc(oxc::ast::ast::BlockStatement {
                                span: node.span,
                                body: Vec::from_array_in(
                                    [Statement::IfStatement(ctx.alloc(oxc::ast::ast::IfStatement {
                                        span: node.span,
                                        test: Expression::UnaryExpression(ctx.alloc(oxc::ast::ast::UnaryExpression {
                                            span: node.span,
                                            operator: oxc::ast::ast::UnaryOperator::LogicalNot,
                                            argument: Expression::BinaryExpression(ctx.alloc(BinaryExpression {
                                                span: node.span,
                                                left: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                    span: node.span,
                                                    name: k_ident,
                                                    reference_id: Cell::new(None),
                                                })),
                                                operator: BinaryOperator::In,
                                                right: Expression::Identifier(ctx.alloc(IdentifierReference {
                                                    span: node.span,
                                                    name: new_prev_ident,
                                                    reference_id: Cell::new(None),
                                                })),
                                            })),
                                        })),
                                        consequent: Statement::BlockStatement(ctx.alloc(oxc::ast::ast::BlockStatement {
                                            span: node.span,
                                            body: Vec::from_array_in(
                                                [Statement::ExpressionStatement(ctx.alloc(ExpressionStatement {
                                                    span: node.span,
                                                    expression: Expression::CallExpression(ctx.alloc(CallExpression {
                                                        span: node.span,
                                                        callee: Expression::StaticMemberExpression(ctx.alloc(StaticMemberExpression {
                                                            span: node.span,
                                                            object: elem.elem.as_ref().unwrap().ident.clone_in(self.allocator),
                                                            property: IdentifierName {
                                                                span: node.span,
                                                                name: Atom::new_const(REMOVE_ATTRIBUTE),
                                                            },
                                                            optional: false,
                                                        })),
                                                        type_arguments: None,
                                                        arguments: Vec::from_array_in(
                                                            [Argument::Identifier(ctx.alloc(IdentifierReference {
                                                                span: node.span,
                                                                name: k_ident,
                                                                reference_id: Cell::new(None),
                                                            }))],
                                                            self.allocator,
                                                        ),
                                                        optional: false,
                                                        pure: false,
                                                    })),
                                                }))],
                                                self.allocator,
                                            ),
                                            scope_id: Cell::new(None),
                                        })),
                                        alternate: None,
                                    }))],
                                    self.allocator,
                                ),
                                scope_id: Cell::new(None),
                            })),
                            scope_id: Cell::new(Some(for_in_scope)),
                        }))
					},
					// prev = new_prev;
					Statement::ExpressionStatement(ctx.alloc(ExpressionStatement {
						span: node.span,
						expression: Expression::AssignmentExpression(ctx.alloc(
							AssignmentExpression {
								span: node.span,
								operator: AssignmentOperator::Assign,
								left: AssignmentTarget::AssignmentTargetIdentifier(ctx.alloc(
									IdentifierReference {
										span: node.span,
										name: Atom::new_const(PREV_ATTRS_IDENT),
										reference_id: Cell::new(Some(write_ref_id)),
									},
								)),
								right: Expression::Identifier(ctx.alloc(IdentifierReference {
									span: node.span,
									name: new_prev_ident,
									reference_id: Cell::new(None),
								})),
							},
						)),
					})),
				],
				self.allocator,
			);

			// Arrow function: (v) => { ... }
			let arrow = ctx.alloc(ArrowFunctionExpression {
				span: node.span,
				expression: false,
				r#async: false,
				type_parameters: None,
				params: ctx.alloc(FormalParameters {
					span: node.span,
					kind: FormalParameterKind::ArrowFormalParameters,
					items: Vec::from_array_in(
						[FormalParameter {
							accessibility: None,
							r#override: false,
							decorators: Vec::new_in(self.allocator),
							readonly: false,
							span: node.span,
							pattern: BindingPattern {
								optional: false,
								type_annotation: None,
								kind: BindingPatternKind::BindingIdentifier(ctx.alloc(
									BindingIdentifier {
										span: node.span,
										name: v_ident,
										symbol_id: Cell::new(None),
									},
								)),
							},
						}],
						self.allocator,
					),
					rest: None,
				}),
				return_type: None,
				body: ctx.alloc(FunctionBody {
					span: node.span,
					directives: Vec::new_in(self.allocator),
					statements: arrow_statements,
				}),
				scope_id: Cell::new(Some(arrow_scope)),
				pure: false,
			});

			// S(arrow)
			let s_call = self.s_expression(
				ctx,
				node.span,
				Expression::CallExpression(ctx.alloc(CallExpression {
					span: node.span,
					callee: Expression::ArrowFunctionExpression(arrow),
					type_arguments: None,
					arguments: Vec::new_in(self.allocator),
					optional: false,
					pure: false,
				})),
			);

			// Add S(...)(spread_expr) to statements.
			new_statements.push(Statement::ExpressionStatement(ctx.alloc(
				ExpressionStatement {
					span: node.span,
					expression: Expression::CallExpression(ctx.alloc(CallExpression {
						span: node.span,
						callee: s_call,
						type_arguments: None,
						arguments: Vec::from_array_in(
							[node.argument.clone_in(self.allocator).into()],
							self.allocator,
						),
						optional: false,
						pure: false,
					})),
				},
			)));

			self.element_stack
				.last_mut()
				.unwrap()
				.statements
				.extend(new_statements);
		}
	}
}