ripple 0.1.1 → 0.2.0
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.
- package/LICENSE +21 -0
- package/package.json +56 -24
- package/src/ai.js +292 -0
- package/src/compiler/errors.js +26 -0
- package/src/compiler/index.js +26 -0
- package/src/compiler/phases/1-parse/index.js +543 -0
- package/src/compiler/phases/1-parse/style.js +566 -0
- package/src/compiler/phases/2-analyze/index.js +509 -0
- package/src/compiler/phases/2-analyze/prune.js +572 -0
- package/src/compiler/phases/3-transform/index.js +1572 -0
- package/src/compiler/phases/3-transform/segments.js +91 -0
- package/src/compiler/phases/3-transform/stylesheet.js +372 -0
- package/src/compiler/scope.js +421 -0
- package/src/compiler/utils.js +552 -0
- package/src/constants.js +4 -0
- package/src/jsx-runtime.d.ts +94 -0
- package/src/jsx-runtime.js +46 -0
- package/src/runtime/array.js +215 -0
- package/src/runtime/index.js +39 -0
- package/src/runtime/internal/client/blocks.js +247 -0
- package/src/runtime/internal/client/constants.js +23 -0
- package/src/runtime/internal/client/events.js +223 -0
- package/src/runtime/internal/client/for.js +388 -0
- package/src/runtime/internal/client/if.js +35 -0
- package/src/runtime/internal/client/index.js +53 -0
- package/src/runtime/internal/client/operations.js +72 -0
- package/src/runtime/internal/client/portal.js +33 -0
- package/src/runtime/internal/client/render.js +156 -0
- package/src/runtime/internal/client/runtime.js +909 -0
- package/src/runtime/internal/client/template.js +51 -0
- package/src/runtime/internal/client/try.js +139 -0
- package/src/runtime/internal/client/utils.js +16 -0
- package/src/utils/ast.js +214 -0
- package/src/utils/builders.js +733 -0
- package/src/utils/patterns.js +23 -0
- package/src/utils/sanitize_template_string.js +7 -0
- package/test-mappings.js +0 -0
- package/types/index.d.ts +2 -0
- package/.npmignore +0 -2
- package/History.md +0 -3
- package/Readme.md +0 -151
- package/lib/exec/index.js +0 -60
- package/ripple.js +0 -645
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import { build_assignment_value } from '../utils/ast.js';
|
|
2
|
+
import * as b from '../utils/builders.js';
|
|
3
|
+
|
|
4
|
+
const regex_return_characters = /\r/g;
|
|
5
|
+
|
|
6
|
+
const RESERVED_WORDS = [
|
|
7
|
+
'arguments',
|
|
8
|
+
'await',
|
|
9
|
+
'break',
|
|
10
|
+
'case',
|
|
11
|
+
'catch',
|
|
12
|
+
'class',
|
|
13
|
+
'const',
|
|
14
|
+
'continue',
|
|
15
|
+
'debugger',
|
|
16
|
+
'default',
|
|
17
|
+
'delete',
|
|
18
|
+
'do',
|
|
19
|
+
'else',
|
|
20
|
+
'enum',
|
|
21
|
+
'eval',
|
|
22
|
+
'export',
|
|
23
|
+
'extends',
|
|
24
|
+
'false',
|
|
25
|
+
'finally',
|
|
26
|
+
'for',
|
|
27
|
+
'function',
|
|
28
|
+
'if',
|
|
29
|
+
'implements',
|
|
30
|
+
'import',
|
|
31
|
+
'in',
|
|
32
|
+
'instanceof',
|
|
33
|
+
'interface',
|
|
34
|
+
'let',
|
|
35
|
+
'new',
|
|
36
|
+
'null',
|
|
37
|
+
'package',
|
|
38
|
+
'private',
|
|
39
|
+
'protected',
|
|
40
|
+
'public',
|
|
41
|
+
'return',
|
|
42
|
+
'static',
|
|
43
|
+
'super',
|
|
44
|
+
'switch',
|
|
45
|
+
'this',
|
|
46
|
+
'throw',
|
|
47
|
+
'true',
|
|
48
|
+
'try',
|
|
49
|
+
'typeof',
|
|
50
|
+
'var',
|
|
51
|
+
'void',
|
|
52
|
+
'while',
|
|
53
|
+
'with',
|
|
54
|
+
'yield'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export function is_reserved(word) {
|
|
58
|
+
return RESERVED_WORDS.includes(word);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Attributes that are boolean, i.e. they are present or not present.
|
|
63
|
+
*/
|
|
64
|
+
const DOM_BOOLEAN_ATTRIBUTES = [
|
|
65
|
+
'allowfullscreen',
|
|
66
|
+
'async',
|
|
67
|
+
'autofocus',
|
|
68
|
+
'autoplay',
|
|
69
|
+
'checked',
|
|
70
|
+
'controls',
|
|
71
|
+
'default',
|
|
72
|
+
'disabled',
|
|
73
|
+
'formnovalidate',
|
|
74
|
+
'hidden',
|
|
75
|
+
'indeterminate',
|
|
76
|
+
'inert',
|
|
77
|
+
'ismap',
|
|
78
|
+
'loop',
|
|
79
|
+
'multiple',
|
|
80
|
+
'muted',
|
|
81
|
+
'nomodule',
|
|
82
|
+
'novalidate',
|
|
83
|
+
'open',
|
|
84
|
+
'playsinline',
|
|
85
|
+
'readonly',
|
|
86
|
+
'required',
|
|
87
|
+
'reversed',
|
|
88
|
+
'seamless',
|
|
89
|
+
'selected',
|
|
90
|
+
'webkitdirectory',
|
|
91
|
+
'defer',
|
|
92
|
+
'disablepictureinpicture',
|
|
93
|
+
'disableremoteplayback'
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export function is_boolean_attribute(name) {
|
|
97
|
+
return DOM_BOOLEAN_ATTRIBUTES.includes(name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const DOM_PROPERTIES = [
|
|
101
|
+
...DOM_BOOLEAN_ATTRIBUTES,
|
|
102
|
+
'formNoValidate',
|
|
103
|
+
'isMap',
|
|
104
|
+
'noModule',
|
|
105
|
+
'playsInline',
|
|
106
|
+
'readOnly',
|
|
107
|
+
'value',
|
|
108
|
+
'volume',
|
|
109
|
+
'defaultValue',
|
|
110
|
+
'defaultChecked',
|
|
111
|
+
'srcObject',
|
|
112
|
+
'noValidate',
|
|
113
|
+
'allowFullscreen',
|
|
114
|
+
'disablePictureInPicture',
|
|
115
|
+
'disableRemotePlayback'
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
export function is_dom_property(name) {
|
|
119
|
+
return DOM_PROPERTIES.includes(name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** List of Element events that will be delegated */
|
|
123
|
+
const DELEGATED_EVENTS = [
|
|
124
|
+
'beforeinput',
|
|
125
|
+
'click',
|
|
126
|
+
'change',
|
|
127
|
+
'dblclick',
|
|
128
|
+
'contextmenu',
|
|
129
|
+
'focusin',
|
|
130
|
+
'focusout',
|
|
131
|
+
'input',
|
|
132
|
+
'keydown',
|
|
133
|
+
'keyup',
|
|
134
|
+
'mousedown',
|
|
135
|
+
'mousemove',
|
|
136
|
+
'mouseout',
|
|
137
|
+
'mouseover',
|
|
138
|
+
'mouseup',
|
|
139
|
+
'pointerdown',
|
|
140
|
+
'pointermove',
|
|
141
|
+
'pointerout',
|
|
142
|
+
'pointerover',
|
|
143
|
+
'pointerup',
|
|
144
|
+
'touchend',
|
|
145
|
+
'touchmove',
|
|
146
|
+
'touchstart'
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
export function is_delegated(event_name) {
|
|
150
|
+
return DELEGATED_EVENTS.includes(event_name);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const PASSIVE_EVENTS = ['touchstart', 'touchmove'];
|
|
154
|
+
|
|
155
|
+
export function is_passive_event(name) {
|
|
156
|
+
return PASSIVE_EVENTS.includes(name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function is_event_attribute(attr) {
|
|
160
|
+
return attr.startsWith('on') && attr.length > 2 && attr[2] === attr[2].toUpperCase();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const unhoisted = { hoisted: false };
|
|
164
|
+
|
|
165
|
+
export function get_delegated_event(event_name, handler, state) {
|
|
166
|
+
// Handle delegated event handlers. Bail out if not a delegated event.
|
|
167
|
+
if (!handler || !is_delegated(event_name)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */
|
|
172
|
+
let target_function = null;
|
|
173
|
+
let binding = null;
|
|
174
|
+
|
|
175
|
+
if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
|
|
176
|
+
target_function = handler;
|
|
177
|
+
} else if (handler.type === 'Identifier') {
|
|
178
|
+
binding = state.scope.get(handler.name);
|
|
179
|
+
|
|
180
|
+
if (state.analysis.module.scope.references.has(handler.name)) {
|
|
181
|
+
// If a binding with the same name is referenced in the module scope (even if not declared there), bail out
|
|
182
|
+
return unhoisted;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (binding != null) {
|
|
186
|
+
for (const { path } of binding.references) {
|
|
187
|
+
const parent = path.at(-1);
|
|
188
|
+
if (parent === undefined) return unhoisted;
|
|
189
|
+
|
|
190
|
+
const grandparent = path.at(-2);
|
|
191
|
+
|
|
192
|
+
/** @type {AST.RegularElement | null} */
|
|
193
|
+
let element = null;
|
|
194
|
+
/** @type {string | null} */
|
|
195
|
+
let event_name = null;
|
|
196
|
+
if (
|
|
197
|
+
parent.type === 'ExpressionTag' &&
|
|
198
|
+
grandparent?.type === 'Attribute' &&
|
|
199
|
+
is_event_attribute(grandparent)
|
|
200
|
+
) {
|
|
201
|
+
element = /** @type {AST.RegularElement} */ (path.at(-3));
|
|
202
|
+
const attribute = /** @type {AST.Attribute} */ (grandparent);
|
|
203
|
+
event_name = get_attribute_event_name(attribute.name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (element && event_name) {
|
|
207
|
+
if (
|
|
208
|
+
element.type !== 'Element' ||
|
|
209
|
+
element.metadata.has_spread ||
|
|
210
|
+
!is_delegated(event_name)
|
|
211
|
+
) {
|
|
212
|
+
return unhoisted;
|
|
213
|
+
}
|
|
214
|
+
} else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') {
|
|
215
|
+
return unhoisted;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// If the binding is exported, bail out
|
|
221
|
+
if (state.analysis.exports.find((node) => node.name === handler.name)) {
|
|
222
|
+
return unhoisted;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (binding !== null && binding.initial !== null && !binding.updated && !binding.is_called) {
|
|
226
|
+
const binding_type = binding.initial.type;
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
binding_type === 'ArrowFunctionExpression' ||
|
|
230
|
+
binding_type === 'FunctionDeclaration' ||
|
|
231
|
+
binding_type === 'FunctionExpression'
|
|
232
|
+
) {
|
|
233
|
+
target_function = binding.initial;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If we can't find a function, or the function has multiple parameters, bail out
|
|
239
|
+
if (target_function == null || target_function.params.length > 1) {
|
|
240
|
+
return unhoisted;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const visited_references = new Set();
|
|
244
|
+
const scope = target_function.metadata.scope;
|
|
245
|
+
for (const [reference] of scope.references) {
|
|
246
|
+
// Bail out if the arguments keyword is used or $host is referenced
|
|
247
|
+
if (reference === 'arguments') return unhoisted;
|
|
248
|
+
|
|
249
|
+
const binding = scope.get(reference);
|
|
250
|
+
const local_binding = state.scope.get(reference);
|
|
251
|
+
|
|
252
|
+
// If we are referencing a binding that is shadowed in another scope then bail out.
|
|
253
|
+
if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
|
|
254
|
+
return unhoisted;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
binding !== null &&
|
|
259
|
+
// Bail out if the the binding is a rest param
|
|
260
|
+
(binding.declaration_kind === 'rest_param' || // or any normal not reactive bindings that are mutated.
|
|
261
|
+
// Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
|
|
262
|
+
(binding.kind === 'normal' && binding.updated))
|
|
263
|
+
) {
|
|
264
|
+
return unhoisted;
|
|
265
|
+
}
|
|
266
|
+
visited_references.add(reference);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { hoisted: true, function: target_function };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function get_hoisted_params(node, context) {
|
|
273
|
+
const scope = context.state.scope;
|
|
274
|
+
|
|
275
|
+
/** @type {Identifier[]} */
|
|
276
|
+
const params = [];
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* We only want to push if it's not already present to avoid name clashing
|
|
280
|
+
* @param {Identifier} id
|
|
281
|
+
*/
|
|
282
|
+
function push_unique(id) {
|
|
283
|
+
if (!params.find((param) => param.name === id.name)) {
|
|
284
|
+
params.push(id);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const [reference] of scope.references) {
|
|
289
|
+
let binding = scope.get(reference);
|
|
290
|
+
|
|
291
|
+
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
|
|
292
|
+
if (binding.kind === 'prop') {
|
|
293
|
+
debugger;
|
|
294
|
+
} else if (
|
|
295
|
+
// imports don't need to be hoisted
|
|
296
|
+
binding.declaration_kind !== 'import'
|
|
297
|
+
) {
|
|
298
|
+
// create a copy to remove start/end tags which would mess up source maps
|
|
299
|
+
push_unique(b.id(binding.node.name));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return params;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function build_hoisted_params(node, context) {
|
|
307
|
+
const hoisted_params = get_hoisted_params(node, context);
|
|
308
|
+
node.metadata.hoisted_params = hoisted_params;
|
|
309
|
+
|
|
310
|
+
/** @type {Pattern[]} */
|
|
311
|
+
const params = [];
|
|
312
|
+
|
|
313
|
+
if (node.params.length === 0) {
|
|
314
|
+
if (hoisted_params.length > 0) {
|
|
315
|
+
// For the event object
|
|
316
|
+
params.push(b.id(context.state.scope.generate('_')));
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
for (const param of node.params) {
|
|
320
|
+
params.push(/** @type {Pattern} */ (context.visit(param)));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
params.push(...hoisted_params, b.id('__block'));
|
|
325
|
+
return params;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function is_inside_component(context) {
|
|
329
|
+
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
330
|
+
const context_node = context.path[i];
|
|
331
|
+
const type = context_node.type;
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
type === 'FunctionExpression' ||
|
|
335
|
+
type === 'ArrowFunctionExpression' ||
|
|
336
|
+
type === 'FunctionDeclaration'
|
|
337
|
+
) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (type === 'Component') {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function is_inside_call_expression(context) {
|
|
348
|
+
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
349
|
+
const context_node = context.path[i];
|
|
350
|
+
const type = context_node.type;
|
|
351
|
+
|
|
352
|
+
if (
|
|
353
|
+
type === 'FunctionExpression' ||
|
|
354
|
+
type === 'ArrowFunctionExpression' ||
|
|
355
|
+
type === 'FunctionDeclaration'
|
|
356
|
+
) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
if (type === 'CallExpression') {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function is_tracked_name(name) {
|
|
367
|
+
return (
|
|
368
|
+
typeof name === 'string' &&
|
|
369
|
+
name.startsWith('$') &&
|
|
370
|
+
name.length > 1 &&
|
|
371
|
+
name[1] !== '$' &&
|
|
372
|
+
name !== '$length'
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function is_svelte_import(callee, context) {
|
|
377
|
+
if (callee.type === 'Identifier') {
|
|
378
|
+
const binding = context.state.scope.get(callee.name);
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
binding?.declaration_kind === 'import' &&
|
|
382
|
+
binding.initial.source.type === 'Literal' &&
|
|
383
|
+
binding.initial.source.value === 'ripple'
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function is_declared_within_component(node, context) {
|
|
391
|
+
const component = context.path.find((n) => n.type === 'Component');
|
|
392
|
+
|
|
393
|
+
if (node.type === 'Identifier' && component) {
|
|
394
|
+
const binding = context.state.scope.get(node.name);
|
|
395
|
+
const component_scope = context.state.scopes.get(component);
|
|
396
|
+
|
|
397
|
+
if (binding !== null && component_scope !== null) {
|
|
398
|
+
let scope = binding.scope;
|
|
399
|
+
|
|
400
|
+
while (scope !== null) {
|
|
401
|
+
if (scope === component_scope) {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
scope = scope.parent;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function is_non_coercive_operator(operator) {
|
|
413
|
+
return ['=', '||=', '&&=', '??='].includes(operator);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function visit_assignment_expression(node, context, build_assignment) {
|
|
417
|
+
if (
|
|
418
|
+
node.left.type === 'ArrayPattern' ||
|
|
419
|
+
node.left.type === 'ObjectPattern' ||
|
|
420
|
+
node.left.type === 'RestElement'
|
|
421
|
+
) {
|
|
422
|
+
const value = /** @type {Expression} */ (context.visit(node.right));
|
|
423
|
+
const should_cache = value.type !== 'Identifier';
|
|
424
|
+
const rhs = should_cache ? b.id('$$value') : value;
|
|
425
|
+
|
|
426
|
+
let changed = false;
|
|
427
|
+
|
|
428
|
+
const assignments = extract_paths(node.left).map((path) => {
|
|
429
|
+
const value = path.expression?.(rhs);
|
|
430
|
+
|
|
431
|
+
let assignment = build_assignment('=', path.node, value, context);
|
|
432
|
+
if (assignment !== null) changed = true;
|
|
433
|
+
|
|
434
|
+
return (
|
|
435
|
+
assignment ??
|
|
436
|
+
b.assignment(
|
|
437
|
+
'=',
|
|
438
|
+
/** @type {Pattern} */ (context.visit(path.node)),
|
|
439
|
+
/** @type {Expression} */ (context.visit(value))
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (!changed) {
|
|
445
|
+
// No change to output -> nothing to transform -> we can keep the original assignment
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
|
|
450
|
+
const sequence = b.sequence(assignments);
|
|
451
|
+
|
|
452
|
+
if (!is_standalone) {
|
|
453
|
+
// this is part of an expression, we need the sequence to end with the value
|
|
454
|
+
sequence.expressions.push(rhs);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (should_cache) {
|
|
458
|
+
// the right hand side is a complex expression, wrap in an IIFE to cache it
|
|
459
|
+
const iife = b.arrow([rhs], sequence);
|
|
460
|
+
|
|
461
|
+
const iife_is_async =
|
|
462
|
+
is_expression_async(value) ||
|
|
463
|
+
assignments.some((assignment) => is_expression_async(assignment));
|
|
464
|
+
|
|
465
|
+
return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return sequence;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
|
|
472
|
+
throw new Error(`Unexpected assignment type ${node.left.type}`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return build_assignment(node.operator, node.left, node.right, context);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function build_assignment(operator, left, right, context) {
|
|
479
|
+
let object = left;
|
|
480
|
+
|
|
481
|
+
while (object.type === 'MemberExpression') {
|
|
482
|
+
// @ts-expect-error
|
|
483
|
+
object = object.object;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (object.type !== 'Identifier') {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const binding = context.state.scope.get(object.name);
|
|
491
|
+
if (!binding) return null;
|
|
492
|
+
|
|
493
|
+
const transform = binding.transform;
|
|
494
|
+
|
|
495
|
+
const path = context.path.map((node) => node.type);
|
|
496
|
+
|
|
497
|
+
// reassignment
|
|
498
|
+
if (object === left && transform?.assign) {
|
|
499
|
+
let value = /** @type {Expression} */ (
|
|
500
|
+
context.visit(build_assignment_value(operator, left, right))
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return transform.assign(object, value);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// mutation
|
|
507
|
+
if (transform?.mutate) {
|
|
508
|
+
return transform.mutate(
|
|
509
|
+
object,
|
|
510
|
+
b.assignment(
|
|
511
|
+
operator,
|
|
512
|
+
/** @type {Pattern} */ (context.visit(left)),
|
|
513
|
+
/** @type {Expression} */ (context.visit(right))
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const ATTR_REGEX = /[&"<]/g;
|
|
522
|
+
const CONTENT_REGEX = /[&<]/g;
|
|
523
|
+
|
|
524
|
+
export function escape_html(value, is_attr) {
|
|
525
|
+
const str = String(value ?? '');
|
|
526
|
+
|
|
527
|
+
const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
|
|
528
|
+
pattern.lastIndex = 0;
|
|
529
|
+
|
|
530
|
+
let escaped = '';
|
|
531
|
+
let last = 0;
|
|
532
|
+
|
|
533
|
+
while (pattern.test(str)) {
|
|
534
|
+
const i = pattern.lastIndex - 1;
|
|
535
|
+
const ch = str[i];
|
|
536
|
+
escaped += str.substring(last, i) + (ch === '&' ? '&' : ch === '"' ? '"' : '<');
|
|
537
|
+
last = i + 1;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return escaped + str.substring(last);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function hash(str) {
|
|
544
|
+
str = str.replace(regex_return_characters, '');
|
|
545
|
+
let hash = 5381;
|
|
546
|
+
let i = str.length;
|
|
547
|
+
|
|
548
|
+
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
|
549
|
+
return (hash >>> 0).toString(36);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ripple JSX Runtime Type Definitions
|
|
3
|
+
* Ripple components are imperative and don't return JSX elements
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Ripple components don't return JSX elements - they're imperative
|
|
7
|
+
export type ComponentType<P = {}> = (props: P) => void;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a JSX element (for elements with children)
|
|
11
|
+
* In Ripple, this doesn't return anything - components are imperative
|
|
12
|
+
*/
|
|
13
|
+
export function jsx(
|
|
14
|
+
type: string | ComponentType<any>,
|
|
15
|
+
props?: any,
|
|
16
|
+
key?: string | number | null
|
|
17
|
+
): void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a JSX element with static children (optimization for multiple children)
|
|
21
|
+
* In Ripple, this doesn't return anything - components are imperative
|
|
22
|
+
*/
|
|
23
|
+
export function jsxs(
|
|
24
|
+
type: string | ComponentType<any>,
|
|
25
|
+
props?: any,
|
|
26
|
+
key?: string | number | null
|
|
27
|
+
): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* JSX Fragment component
|
|
31
|
+
* In Ripple, fragments are imperative and don't return anything
|
|
32
|
+
*/
|
|
33
|
+
export function Fragment(props: { $children?: any }): void;
|
|
34
|
+
|
|
35
|
+
// Base HTML attributes
|
|
36
|
+
interface HTMLAttributes {
|
|
37
|
+
class?: string;
|
|
38
|
+
className?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
style?: string | Record<string, string | number>;
|
|
41
|
+
onClick?: (event: MouseEvent) => void;
|
|
42
|
+
onInput?: (event: InputEvent) => void;
|
|
43
|
+
onChange?: (event: Event) => void;
|
|
44
|
+
$children?: any;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Global JSX namespace for TypeScript
|
|
49
|
+
declare global {
|
|
50
|
+
namespace JSX {
|
|
51
|
+
// In Ripple, JSX expressions don't return elements - they're imperative
|
|
52
|
+
type Element = void;
|
|
53
|
+
|
|
54
|
+
interface IntrinsicElements {
|
|
55
|
+
// HTML elements with basic attributes
|
|
56
|
+
div: HTMLAttributes;
|
|
57
|
+
span: HTMLAttributes;
|
|
58
|
+
p: HTMLAttributes;
|
|
59
|
+
h1: HTMLAttributes;
|
|
60
|
+
h2: HTMLAttributes;
|
|
61
|
+
h3: HTMLAttributes;
|
|
62
|
+
h4: HTMLAttributes;
|
|
63
|
+
h5: HTMLAttributes;
|
|
64
|
+
h6: HTMLAttributes;
|
|
65
|
+
button: HTMLAttributes & {
|
|
66
|
+
type?: 'button' | 'submit' | 'reset';
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
};
|
|
69
|
+
input: HTMLAttributes & {
|
|
70
|
+
type?: string;
|
|
71
|
+
value?: string | number;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
disabled?: boolean;
|
|
74
|
+
};
|
|
75
|
+
form: HTMLAttributes;
|
|
76
|
+
a: HTMLAttributes & {
|
|
77
|
+
href?: string;
|
|
78
|
+
target?: string;
|
|
79
|
+
};
|
|
80
|
+
img: HTMLAttributes & {
|
|
81
|
+
src?: string;
|
|
82
|
+
alt?: string;
|
|
83
|
+
width?: string | number;
|
|
84
|
+
height?: string | number;
|
|
85
|
+
};
|
|
86
|
+
// Add more as needed...
|
|
87
|
+
[elemName: string]: HTMLAttributes;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface ElementChildrenAttribute {
|
|
91
|
+
$children: {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ripple JSX Runtime
|
|
3
|
+
* This module provides the JSX runtime functions that TypeScript will automatically import
|
|
4
|
+
* when using jsxImportSource: "ripple/jsx-runtime"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a JSX element (for elements with children)
|
|
9
|
+
* In Ripple, components don't return values - they imperatively render to the DOM
|
|
10
|
+
* @param {string | Function} type - Element type (tag name or component function)
|
|
11
|
+
* @param {object} props - Element properties
|
|
12
|
+
* @param {string} key - Element key (optional)
|
|
13
|
+
* @returns {void} Ripple components don't return anything
|
|
14
|
+
*/
|
|
15
|
+
export function jsx(type, props, key) {
|
|
16
|
+
// Ripple components are imperative - they don't return JSX elements
|
|
17
|
+
// This is a placeholder for the actual Ripple rendering logic
|
|
18
|
+
if (typeof type === 'function') {
|
|
19
|
+
// Call the Ripple component function
|
|
20
|
+
type(props);
|
|
21
|
+
} else {
|
|
22
|
+
// Handle DOM elements
|
|
23
|
+
console.warn('DOM element rendering not implemented in jsx runtime:', type, props);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a JSX element with static children (optimization for multiple children)
|
|
29
|
+
* @param {string | Function} type - Element type (tag name or component function)
|
|
30
|
+
* @param {object} props - Element properties
|
|
31
|
+
* @param {string} key - Element key (optional)
|
|
32
|
+
* @returns {void} Ripple components don't return anything
|
|
33
|
+
*/
|
|
34
|
+
export function jsxs(type, props, key) {
|
|
35
|
+
return jsx(type, props, key);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* JSX Fragment component
|
|
40
|
+
* @param {object} props - Fragment props (should contain children)
|
|
41
|
+
* @returns {void} Ripple fragments don't return anything
|
|
42
|
+
*/
|
|
43
|
+
export function Fragment(props) {
|
|
44
|
+
// Ripple fragments are imperative
|
|
45
|
+
console.warn('Fragment rendering not implemented in jsx runtime:', props);
|
|
46
|
+
}
|