ripple 0.3.13 → 0.3.15
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/CHANGELOG.md +35 -0
- package/package.json +5 -30
- package/src/runtime/array.js +38 -38
- package/src/runtime/create-subscriber.js +2 -2
- package/src/runtime/internal/client/bindings.js +4 -6
- package/src/runtime/internal/client/events.js +8 -3
- package/src/runtime/internal/client/hmr.js +5 -17
- package/src/runtime/internal/client/runtime.js +1 -0
- package/src/runtime/internal/server/blocks.js +7 -9
- package/src/runtime/internal/server/index.js +14 -22
- package/src/runtime/media-query.js +34 -33
- package/src/runtime/object.js +7 -10
- package/src/runtime/proxy.js +2 -3
- package/src/runtime/reactive-value.js +23 -21
- package/src/utils/ast.js +1 -1
- package/src/utils/attributes.js +43 -0
- package/src/utils/builders.js +2 -2
- package/tests/client/basic/basic.components.test.rsrx +103 -1
- package/tests/client/basic/basic.errors.test.rsrx +1 -1
- package/tests/client/basic/basic.styling.test.rsrx +1 -1
- package/tests/client/compiler/compiler.assignments.test.rsrx +1 -1
- package/tests/client/compiler/compiler.attributes.test.rsrx +1 -1
- package/tests/client/compiler/compiler.basic.test.rsrx +51 -14
- package/tests/client/compiler/compiler.tracked-access.test.rsrx +1 -1
- package/tests/client/compiler/compiler.try-in-function.test.rsrx +1 -1
- package/tests/client/compiler/compiler.typescript.test.rsrx +1 -1
- package/tests/client/css/global-additional-cases.test.rsrx +1 -1
- package/tests/client/css/global-advanced-selectors.test.rsrx +1 -1
- package/tests/client/css/global-at-rules.test.rsrx +1 -1
- package/tests/client/css/global-basic.test.rsrx +1 -1
- package/tests/client/css/global-classes-ids.test.rsrx +1 -1
- package/tests/client/css/global-combinators.test.rsrx +1 -1
- package/tests/client/css/global-complex-nesting.test.rsrx +1 -1
- package/tests/client/css/global-edge-cases.test.rsrx +1 -1
- package/tests/client/css/global-keyframes.test.rsrx +1 -1
- package/tests/client/css/global-nested.test.rsrx +1 -1
- package/tests/client/css/global-pseudo.test.rsrx +1 -1
- package/tests/client/css/global-scoping.test.rsrx +1 -1
- package/tests/client/css/style-identifier.test.rsrx +1 -1
- package/tests/client/return.test.rsrx +1 -1
- package/tests/hydration/build-components.js +1 -1
- package/tests/server/basic.components.test.rsrx +114 -0
- package/tests/server/compiler.test.rsrx +38 -1
- package/tests/server/style-identifier.test.rsrx +1 -1
- package/tests/setup-server.js +1 -1
- package/tests/utils/compiler-compat-config.test.js +1 -1
- package/types/index.d.ts +1 -1
- package/src/compiler/comment-utils.js +0 -91
- package/src/compiler/errors.js +0 -77
- package/src/compiler/identifier-utils.js +0 -80
- package/src/compiler/index.d.ts +0 -127
- package/src/compiler/index.js +0 -89
- package/src/compiler/phases/1-parse/index.js +0 -3007
- package/src/compiler/phases/1-parse/style.js +0 -704
- package/src/compiler/phases/2-analyze/css-analyze.js +0 -160
- package/src/compiler/phases/2-analyze/index.js +0 -2208
- package/src/compiler/phases/2-analyze/prune.js +0 -1131
- package/src/compiler/phases/2-analyze/validation.js +0 -168
- package/src/compiler/phases/3-transform/client/index.js +0 -5264
- package/src/compiler/phases/3-transform/segments.js +0 -2125
- package/src/compiler/phases/3-transform/server/index.js +0 -1749
- package/src/compiler/phases/3-transform/stylesheet.js +0 -545
- package/src/compiler/scope.js +0 -476
- package/src/compiler/source-map-utils.js +0 -358
- package/src/compiler/types/acorn.d.ts +0 -11
- package/src/compiler/types/estree-jsx.d.ts +0 -11
- package/src/compiler/types/estree.d.ts +0 -11
- package/src/compiler/types/index.d.ts +0 -1411
- package/src/compiler/types/parse.d.ts +0 -1723
- package/src/compiler/utils.js +0 -1258
package/src/compiler/utils.js
DELETED
|
@@ -1,1258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@import * as AST from 'estree';
|
|
3
|
-
@import { CommonContext, NameSpace, ScopeInterface, Binding } from '#compiler';
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { build_assignment_value, extract_paths } from '../utils/ast.js';
|
|
7
|
-
import * as b from '../utils/builders.js';
|
|
8
|
-
import { is_capture_event, is_non_delegated, normalize_event_name } from '../utils/events.js';
|
|
9
|
-
|
|
10
|
-
export { hash } from '../utils/hashing.js';
|
|
11
|
-
|
|
12
|
-
const VOID_ELEMENT_NAMES = [
|
|
13
|
-
'area',
|
|
14
|
-
'base',
|
|
15
|
-
'br',
|
|
16
|
-
'col',
|
|
17
|
-
'command',
|
|
18
|
-
'embed',
|
|
19
|
-
'hr',
|
|
20
|
-
'img',
|
|
21
|
-
'input',
|
|
22
|
-
'keygen',
|
|
23
|
-
'link',
|
|
24
|
-
'meta',
|
|
25
|
-
'param',
|
|
26
|
-
'source',
|
|
27
|
-
'track',
|
|
28
|
-
'wbr',
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Returns `true` if `name` is of a void element
|
|
33
|
-
* @param {string} name
|
|
34
|
-
*/
|
|
35
|
-
/**
|
|
36
|
-
* Returns true if name is a void element
|
|
37
|
-
* @param {string} name
|
|
38
|
-
* @returns {boolean}
|
|
39
|
-
*/
|
|
40
|
-
export function is_void_element(name) {
|
|
41
|
-
return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const RESERVED_WORDS = [
|
|
45
|
-
'arguments',
|
|
46
|
-
'await',
|
|
47
|
-
'break',
|
|
48
|
-
'case',
|
|
49
|
-
'catch',
|
|
50
|
-
'class',
|
|
51
|
-
'const',
|
|
52
|
-
'continue',
|
|
53
|
-
'debugger',
|
|
54
|
-
'default',
|
|
55
|
-
'delete',
|
|
56
|
-
'do',
|
|
57
|
-
'else',
|
|
58
|
-
'enum',
|
|
59
|
-
'eval',
|
|
60
|
-
'export',
|
|
61
|
-
'extends',
|
|
62
|
-
'false',
|
|
63
|
-
'finally',
|
|
64
|
-
'for',
|
|
65
|
-
'function',
|
|
66
|
-
'if',
|
|
67
|
-
'implements',
|
|
68
|
-
'import',
|
|
69
|
-
'in',
|
|
70
|
-
'instanceof',
|
|
71
|
-
'interface',
|
|
72
|
-
'let',
|
|
73
|
-
'new',
|
|
74
|
-
'null',
|
|
75
|
-
'package',
|
|
76
|
-
'private',
|
|
77
|
-
'protected',
|
|
78
|
-
'public',
|
|
79
|
-
'return',
|
|
80
|
-
'static',
|
|
81
|
-
'super',
|
|
82
|
-
'switch',
|
|
83
|
-
'this',
|
|
84
|
-
'throw',
|
|
85
|
-
'true',
|
|
86
|
-
'try',
|
|
87
|
-
'typeof',
|
|
88
|
-
'var',
|
|
89
|
-
'void',
|
|
90
|
-
'while',
|
|
91
|
-
'with',
|
|
92
|
-
'yield',
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Returns true if word is a reserved JS keyword
|
|
97
|
-
* @param {string} word
|
|
98
|
-
* @returns {boolean}
|
|
99
|
-
*/
|
|
100
|
-
export function is_reserved(word) {
|
|
101
|
-
return RESERVED_WORDS.includes(word);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Attributes that are boolean, i.e. they are present or not present.
|
|
106
|
-
*/
|
|
107
|
-
const DOM_BOOLEAN_ATTRIBUTES = [
|
|
108
|
-
'allowfullscreen',
|
|
109
|
-
'async',
|
|
110
|
-
'autofocus',
|
|
111
|
-
'autoplay',
|
|
112
|
-
'checked',
|
|
113
|
-
'controls',
|
|
114
|
-
'default',
|
|
115
|
-
'disabled',
|
|
116
|
-
'formnovalidate',
|
|
117
|
-
'hidden',
|
|
118
|
-
'indeterminate',
|
|
119
|
-
'inert',
|
|
120
|
-
'ismap',
|
|
121
|
-
'loop',
|
|
122
|
-
'multiple',
|
|
123
|
-
'muted',
|
|
124
|
-
'nomodule',
|
|
125
|
-
'novalidate',
|
|
126
|
-
'open',
|
|
127
|
-
'playsinline',
|
|
128
|
-
'readonly',
|
|
129
|
-
'required',
|
|
130
|
-
'reversed',
|
|
131
|
-
'seamless',
|
|
132
|
-
'selected',
|
|
133
|
-
'webkitdirectory',
|
|
134
|
-
'defer',
|
|
135
|
-
'disablepictureinpicture',
|
|
136
|
-
'disableremoteplayback',
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Returns true if name is a boolean DOM attribute
|
|
141
|
-
* @param {string} name
|
|
142
|
-
* @returns {boolean}
|
|
143
|
-
*/
|
|
144
|
-
export function is_boolean_attribute(name) {
|
|
145
|
-
return DOM_BOOLEAN_ATTRIBUTES.includes(name);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const DOM_PROPERTIES = [
|
|
149
|
-
...DOM_BOOLEAN_ATTRIBUTES,
|
|
150
|
-
'formNoValidate',
|
|
151
|
-
'isMap',
|
|
152
|
-
'noModule',
|
|
153
|
-
'playsInline',
|
|
154
|
-
'readOnly',
|
|
155
|
-
'value',
|
|
156
|
-
'volume',
|
|
157
|
-
'defaultValue',
|
|
158
|
-
'defaultChecked',
|
|
159
|
-
'srcObject',
|
|
160
|
-
'noValidate',
|
|
161
|
-
'allowFullscreen',
|
|
162
|
-
'disablePictureInPicture',
|
|
163
|
-
'disableRemotePlayback',
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
// Omits track, trackSplit and trackAsync are they're handled separately
|
|
167
|
-
/** @type {Record<string, {name: string, requiresBlock?: boolean}>} */
|
|
168
|
-
const RIPPLE_IMPORT_CALL_NAME = {
|
|
169
|
-
RippleArray: { name: 'ripple_array', requiresBlock: true },
|
|
170
|
-
RippleObject: { name: 'ripple_object', requiresBlock: true },
|
|
171
|
-
RippleURL: { name: 'ripple_url', requiresBlock: true },
|
|
172
|
-
RippleURLSearchParams: { name: 'ripple_url_search_params', requiresBlock: true },
|
|
173
|
-
RippleDate: { name: 'ripple_date', requiresBlock: true },
|
|
174
|
-
RippleMap: { name: 'ripple_map', requiresBlock: true },
|
|
175
|
-
RippleSet: { name: 'ripple_set', requiresBlock: true },
|
|
176
|
-
MediaQuery: { name: 'media_query', requiresBlock: true },
|
|
177
|
-
Context: { name: 'context' },
|
|
178
|
-
effect: { name: 'effect' },
|
|
179
|
-
untrack: { name: 'untrack' },
|
|
180
|
-
trackPending: { name: 'is_tracked_pending' },
|
|
181
|
-
peek: { name: 'peek_tracked' },
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Returns true if name is a DOM property
|
|
186
|
-
* @param {string} name
|
|
187
|
-
* @returns {boolean}
|
|
188
|
-
*/
|
|
189
|
-
export function is_dom_property(name) {
|
|
190
|
-
return DOM_PROPERTIES.includes(name);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Determines if an event handler can be delegated
|
|
195
|
-
* @param {string} event_name
|
|
196
|
-
* @param {AST.Node} handler
|
|
197
|
-
* @param {CommonContext} context
|
|
198
|
-
* @returns {boolean}
|
|
199
|
-
*/
|
|
200
|
-
export function is_delegated_event(event_name, handler, context) {
|
|
201
|
-
// Handle delegated event handlers. Bail out if not a delegated event.
|
|
202
|
-
if (
|
|
203
|
-
!handler ||
|
|
204
|
-
is_capture_event(event_name) ||
|
|
205
|
-
is_non_delegated(normalize_event_name(event_name)) ||
|
|
206
|
-
(handler.type !== 'FunctionExpression' &&
|
|
207
|
-
handler.type !== 'ArrowFunctionExpression' &&
|
|
208
|
-
!is_declared_function_within_component(/** @type {AST.Identifier}*/ (handler), context))
|
|
209
|
-
) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Returns true if context is inside a Component node
|
|
217
|
-
* @param {CommonContext} context
|
|
218
|
-
* @param {boolean} [includes_functions=false]
|
|
219
|
-
* @returns {AST.Component | undefined}
|
|
220
|
-
*/
|
|
221
|
-
export function is_inside_component(context, includes_functions = false) {
|
|
222
|
-
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
223
|
-
const context_node = context.path[i];
|
|
224
|
-
const type = context_node.type;
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
!includes_functions &&
|
|
228
|
-
(type === 'FunctionExpression' ||
|
|
229
|
-
type === 'ArrowFunctionExpression' ||
|
|
230
|
-
type === 'FunctionDeclaration')
|
|
231
|
-
) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (context_node.type === 'Component') {
|
|
235
|
-
return context_node;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Returns true if context is inside a component-level function
|
|
243
|
-
* @param {CommonContext} context
|
|
244
|
-
* @returns {boolean}
|
|
245
|
-
*/
|
|
246
|
-
export function is_component_level_function(context) {
|
|
247
|
-
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
248
|
-
const context_node = context.path[i];
|
|
249
|
-
const type = context_node.type;
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
type === 'BlockStatement' &&
|
|
253
|
-
context_node.body.find((n) => /** @type {AST.Node} */ (n).type === 'Component')
|
|
254
|
-
) {
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
type === 'FunctionExpression' ||
|
|
260
|
-
type === 'ArrowFunctionExpression' ||
|
|
261
|
-
type === 'FunctionDeclaration'
|
|
262
|
-
) {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Returns the matched Ripple tracking call name
|
|
271
|
-
* @param {AST.Expression | AST.Super} callee
|
|
272
|
-
* @param {CommonContext} context
|
|
273
|
-
* @returns {'track' | 'trackAsync' | null}
|
|
274
|
-
*/
|
|
275
|
-
export function is_ripple_track_call(callee, context) {
|
|
276
|
-
// Super expressions cannot be Ripple track calls
|
|
277
|
-
if (callee.type === 'Super') return null;
|
|
278
|
-
|
|
279
|
-
if (callee.type === 'Identifier' && (callee.name === 'track' || callee.name === 'trackAsync')) {
|
|
280
|
-
return is_ripple_import(callee, context) ? callee.name : null;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
callee.type === 'MemberExpression' &&
|
|
285
|
-
callee.object.type === 'Identifier' &&
|
|
286
|
-
callee.property.type === 'Identifier' &&
|
|
287
|
-
(callee.property.name === 'track' || callee.property.name === 'trackAsync') &&
|
|
288
|
-
!callee.computed &&
|
|
289
|
-
is_ripple_import(callee, context)
|
|
290
|
-
) {
|
|
291
|
-
return callee.property.name;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Returns true if context is inside a call expression
|
|
299
|
-
* @param {CommonContext} context
|
|
300
|
-
* @returns {boolean}
|
|
301
|
-
*/
|
|
302
|
-
export function is_inside_call_expression(context) {
|
|
303
|
-
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
304
|
-
const context_node = context.path[i];
|
|
305
|
-
const type = context_node.type;
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
type === 'FunctionExpression' ||
|
|
309
|
-
type === 'ArrowFunctionExpression' ||
|
|
310
|
-
type === 'FunctionDeclaration'
|
|
311
|
-
) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
if (type === 'CallExpression') {
|
|
315
|
-
const callee = context_node.callee;
|
|
316
|
-
if (is_ripple_track_call(callee, context)) {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Returns true if node is a static value (Literal, ArrayExpression, etc)
|
|
327
|
-
* @param {AST.Node} node
|
|
328
|
-
* @returns {boolean}
|
|
329
|
-
*/
|
|
330
|
-
export function is_value_static(node) {
|
|
331
|
-
if (node.type === 'Literal') {
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
if (node.type === 'ArrayExpression') {
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
if (node.type === 'NewExpression') {
|
|
338
|
-
if (node.callee.type === 'Identifier' && node.callee.name === 'Array') {
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
return false;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Returns true if callee is a Ripple import
|
|
349
|
-
* @param {AST.Expression} callee
|
|
350
|
-
* @param {CommonContext} context
|
|
351
|
-
* @returns {boolean}
|
|
352
|
-
*/
|
|
353
|
-
export function is_ripple_import(callee, context) {
|
|
354
|
-
if (callee.type === 'Identifier') {
|
|
355
|
-
const binding = context.state.scope.get(callee.name);
|
|
356
|
-
|
|
357
|
-
return (
|
|
358
|
-
binding?.declaration_kind === 'import' &&
|
|
359
|
-
binding.initial !== null &&
|
|
360
|
-
binding.initial.type === 'ImportDeclaration' &&
|
|
361
|
-
binding.initial.source.type === 'Literal' &&
|
|
362
|
-
binding.initial.source.value === 'ripple'
|
|
363
|
-
);
|
|
364
|
-
} else if (
|
|
365
|
-
callee.type === 'MemberExpression' &&
|
|
366
|
-
callee.object.type === 'Identifier' &&
|
|
367
|
-
!callee.computed
|
|
368
|
-
) {
|
|
369
|
-
const binding = context.state.scope.get(callee.object.name);
|
|
370
|
-
|
|
371
|
-
return (
|
|
372
|
-
binding?.declaration_kind === 'import' &&
|
|
373
|
-
binding.initial !== null &&
|
|
374
|
-
binding.initial.type === 'ImportDeclaration' &&
|
|
375
|
-
binding.initial.source.type === 'Literal' &&
|
|
376
|
-
binding.initial.source.value === 'ripple'
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Returns true if node is a function declared within a component
|
|
385
|
-
* @param {AST.Node} node
|
|
386
|
-
* @param {CommonContext} context
|
|
387
|
-
* @returns {boolean}
|
|
388
|
-
*/
|
|
389
|
-
export function is_declared_function_within_component(node, context) {
|
|
390
|
-
const component = context.path?.find((n) => n.type === 'Component');
|
|
391
|
-
|
|
392
|
-
if (node.type === 'Identifier' && component) {
|
|
393
|
-
const binding = context.state.scope.get(node.name);
|
|
394
|
-
const component_scope = context.state.scopes.get(component);
|
|
395
|
-
|
|
396
|
-
if (binding !== null && component_scope !== undefined) {
|
|
397
|
-
if (
|
|
398
|
-
binding.declaration_kind !== 'function' &&
|
|
399
|
-
binding.initial?.type !== 'FunctionDeclaration' &&
|
|
400
|
-
binding.initial?.type !== 'ArrowFunctionExpression' &&
|
|
401
|
-
binding.initial?.type !== 'FunctionExpression'
|
|
402
|
-
) {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
/** @type {ScopeInterface | null} */
|
|
406
|
-
let scope = binding.scope;
|
|
407
|
-
|
|
408
|
-
while (scope !== null) {
|
|
409
|
-
if (scope === component_scope) {
|
|
410
|
-
return true;
|
|
411
|
-
}
|
|
412
|
-
scope = scope.parent;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Visits and transforms an assignment expression
|
|
421
|
-
* @param {AST.AssignmentExpression} node
|
|
422
|
-
* @param {CommonContext} context
|
|
423
|
-
* @param {Function} build_assignment
|
|
424
|
-
* @returns {AST.Expression | AST.AssignmentExpression | null}
|
|
425
|
-
*/
|
|
426
|
-
export function visit_assignment_expression(node, context, build_assignment) {
|
|
427
|
-
if (
|
|
428
|
-
node.left.type === 'ArrayPattern' ||
|
|
429
|
-
node.left.type === 'ObjectPattern' ||
|
|
430
|
-
node.left.type === 'RestElement'
|
|
431
|
-
) {
|
|
432
|
-
const value = /** @type {AST.Expression} */ (context.visit(node.right));
|
|
433
|
-
const should_cache = value.type !== 'Identifier';
|
|
434
|
-
const rhs = should_cache ? b.id('$$value') : value;
|
|
435
|
-
|
|
436
|
-
let changed = false;
|
|
437
|
-
|
|
438
|
-
const assignments = extract_paths(node.left).map((path) => {
|
|
439
|
-
const value = path.expression?.(rhs);
|
|
440
|
-
|
|
441
|
-
let assignment = build_assignment('=', path.node, value, context);
|
|
442
|
-
if (assignment !== null) changed = true;
|
|
443
|
-
|
|
444
|
-
return (
|
|
445
|
-
assignment ??
|
|
446
|
-
b.assignment(
|
|
447
|
-
'=',
|
|
448
|
-
/** @type {AST.Pattern} */ (context.visit(path.node)),
|
|
449
|
-
/** @type {AST.Expression} */ (context.visit(value)),
|
|
450
|
-
)
|
|
451
|
-
);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
if (!changed) {
|
|
455
|
-
// No change to output -> nothing to transform -> we can keep the original assignment
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const is_standalone = context.path.at(-1)?.type.endsWith('Statement');
|
|
460
|
-
const sequence = b.sequence(assignments);
|
|
461
|
-
|
|
462
|
-
if (!is_standalone) {
|
|
463
|
-
// this is part of an expression, we need the sequence to end with the value
|
|
464
|
-
sequence.expressions.push(rhs);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (should_cache) {
|
|
468
|
-
// the right hand side is a complex expression, wrap in an IIFE to cache it
|
|
469
|
-
const iife = b.arrow([rhs], sequence);
|
|
470
|
-
|
|
471
|
-
return b.call(iife, value);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return sequence;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
|
|
478
|
-
throw new Error(`Unexpected assignment type ${node.left.type}`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const transformed = build_assignment(node.operator, node.left, node.right, context);
|
|
482
|
-
|
|
483
|
-
if (transformed === node.left) {
|
|
484
|
-
return node;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return transformed;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Builds an assignment node, possibly transforming for reactivity
|
|
492
|
-
* @param {AST.AssignmentOperator} operator
|
|
493
|
-
* @param {AST.Pattern} left
|
|
494
|
-
* @param {AST.Expression} right
|
|
495
|
-
* @param {CommonContext} context
|
|
496
|
-
* @returns {AST.Expression | null}
|
|
497
|
-
*/
|
|
498
|
-
export function build_assignment(operator, left, right, context) {
|
|
499
|
-
let object = left;
|
|
500
|
-
|
|
501
|
-
while (object.type === 'MemberExpression') {
|
|
502
|
-
// @ts-expect-error
|
|
503
|
-
object = object.object;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (object.type !== 'Identifier') {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const binding = context.state.scope.get(object.name);
|
|
511
|
-
if (!binding) return null;
|
|
512
|
-
|
|
513
|
-
const transform = binding.transform;
|
|
514
|
-
|
|
515
|
-
// reassignment
|
|
516
|
-
if (object === left || (left.type === 'MemberExpression' && left.computed && operator === '=')) {
|
|
517
|
-
const assign_fn = transform?.assign;
|
|
518
|
-
if (assign_fn) {
|
|
519
|
-
let value = /** @type {AST.Expression} */ (
|
|
520
|
-
context.visit(build_assignment_value(operator, left, right))
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
return assign_fn(object, value);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const ATTR_REGEX = /[&"<]/g;
|
|
531
|
-
const CONTENT_REGEX = /[&<]/g;
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Escapes HTML special characters in a string
|
|
535
|
-
* @param {string | number | bigint | boolean | RegExp | null | undefined} value
|
|
536
|
-
* @param {boolean} [is_attr=false]
|
|
537
|
-
* @returns {string}
|
|
538
|
-
*/
|
|
539
|
-
export function escape_html(value, is_attr = false) {
|
|
540
|
-
const str = String(value ?? '');
|
|
541
|
-
|
|
542
|
-
const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
|
|
543
|
-
pattern.lastIndex = 0;
|
|
544
|
-
|
|
545
|
-
let escaped = '';
|
|
546
|
-
let last = 0;
|
|
547
|
-
|
|
548
|
-
while (pattern.test(str)) {
|
|
549
|
-
const i = pattern.lastIndex - 1;
|
|
550
|
-
const ch = str[i];
|
|
551
|
-
escaped += str.substring(last, i) + (ch === '&' ? '&' : ch === '"' ? '"' : '<');
|
|
552
|
-
last = i + 1;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
return escaped + str.substring(last);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Returns true if node is a DOM element (not a component)
|
|
560
|
-
* @param {AST.Node} node
|
|
561
|
-
* @returns {boolean}
|
|
562
|
-
*/
|
|
563
|
-
export function is_element_dom_element(node) {
|
|
564
|
-
const id = /** @type {AST.Element} */ (node).id;
|
|
565
|
-
return (
|
|
566
|
-
id.type === 'Identifier' &&
|
|
567
|
-
id.name[0].toLowerCase() === id.name[0] &&
|
|
568
|
-
id.name !== 'children' &&
|
|
569
|
-
!id.tracked
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Returns true if element is a dynamic element
|
|
575
|
-
* @param {AST.Element} node
|
|
576
|
-
* @returns {boolean}
|
|
577
|
-
*/
|
|
578
|
-
export function is_element_dynamic(node) {
|
|
579
|
-
return is_id_dynamic(node.id);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* @param {AST.Identifier | AST.MemberExpression | AST.Literal} node
|
|
584
|
-
* @returns {boolean}
|
|
585
|
-
*/
|
|
586
|
-
function is_id_dynamic(node) {
|
|
587
|
-
if (node.type === 'Identifier') {
|
|
588
|
-
return !!node.tracked;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Normalizes children nodes (merges adjacent text, removes empty)
|
|
596
|
-
* @param {AST.Node[]} children
|
|
597
|
-
* @param {CommonContext} context
|
|
598
|
-
* @returns {AST.Node[]}
|
|
599
|
-
*/
|
|
600
|
-
export function normalize_children(children, context) {
|
|
601
|
-
/** @type {AST.Node[]} */
|
|
602
|
-
const normalized = [];
|
|
603
|
-
|
|
604
|
-
for (const node of children) {
|
|
605
|
-
normalize_child(node, normalized, context);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
for (let i = normalized.length - 1; i >= 0; i--) {
|
|
609
|
-
const child = normalized[i];
|
|
610
|
-
const prev_child = normalized[i - 1];
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
(child.type === 'RippleExpression' || child.type === 'Text') &&
|
|
614
|
-
(prev_child?.type === 'RippleExpression' || prev_child?.type === 'Text')
|
|
615
|
-
) {
|
|
616
|
-
if (
|
|
617
|
-
(child.type === 'RippleExpression' &&
|
|
618
|
-
is_children_template_expression(child.expression, context.state.scope)) ||
|
|
619
|
-
(prev_child.type === 'RippleExpression' &&
|
|
620
|
-
is_children_template_expression(prev_child.expression, context.state.scope))
|
|
621
|
-
) {
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (prev_child.type === 'Text' || child.type === 'Text') {
|
|
626
|
-
prev_child.type = 'Text';
|
|
627
|
-
}
|
|
628
|
-
if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
|
|
629
|
-
prev_child.expression = b.literal(
|
|
630
|
-
prev_child.expression.value + String(child.expression.value),
|
|
631
|
-
);
|
|
632
|
-
} else {
|
|
633
|
-
prev_child.expression = b.binary(
|
|
634
|
-
'+',
|
|
635
|
-
prev_child.expression,
|
|
636
|
-
b.call('String', child.expression),
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
normalized.splice(i, 1);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return normalized;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
/**
|
|
647
|
-
* @param {AST.Expression} expression
|
|
648
|
-
* @returns {AST.Expression}
|
|
649
|
-
*/
|
|
650
|
-
export function unwrap_template_expression(expression) {
|
|
651
|
-
/** @type {AST.Expression} */
|
|
652
|
-
let node = expression;
|
|
653
|
-
|
|
654
|
-
while (true) {
|
|
655
|
-
if (
|
|
656
|
-
node.type === 'ParenthesizedExpression' ||
|
|
657
|
-
node.type === 'TSAsExpression' ||
|
|
658
|
-
node.type === 'TSSatisfiesExpression' ||
|
|
659
|
-
node.type === 'TSNonNullExpression' ||
|
|
660
|
-
node.type === 'TSInstantiationExpression'
|
|
661
|
-
) {
|
|
662
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (node.type === 'ChainExpression') {
|
|
667
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
return node;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* @param {AST.Expression} expression
|
|
679
|
-
* @param {ScopeInterface | null | undefined} scope
|
|
680
|
-
* @param {ScopeInterface | null} [component_scope]
|
|
681
|
-
* @returns {boolean}
|
|
682
|
-
*/
|
|
683
|
-
export function is_children_template_expression(expression, scope, component_scope = null) {
|
|
684
|
-
if (scope == null) {
|
|
685
|
-
return false;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const unwrapped = unwrap_template_expression(expression);
|
|
689
|
-
|
|
690
|
-
if (unwrapped.type === 'MemberExpression') {
|
|
691
|
-
let property_name = null;
|
|
692
|
-
|
|
693
|
-
if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
|
|
694
|
-
property_name = unwrapped.property.name;
|
|
695
|
-
} else if (
|
|
696
|
-
unwrapped.computed &&
|
|
697
|
-
unwrapped.property.type === 'Literal' &&
|
|
698
|
-
typeof unwrapped.property.value === 'string'
|
|
699
|
-
) {
|
|
700
|
-
property_name = unwrapped.property.value;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
if (property_name === 'children') {
|
|
704
|
-
const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
|
|
705
|
-
|
|
706
|
-
if (target.type === 'Identifier') {
|
|
707
|
-
const binding = scope.get(target.name);
|
|
708
|
-
return (
|
|
709
|
-
binding?.declaration_kind === 'param' &&
|
|
710
|
-
(component_scope === null || binding.scope === component_scope)
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const binding = scope.get(unwrapped.name);
|
|
721
|
-
return (
|
|
722
|
-
(binding?.declaration_kind === 'param' ||
|
|
723
|
-
binding?.kind === 'prop' ||
|
|
724
|
-
binding?.kind === 'prop_fallback' ||
|
|
725
|
-
binding?.kind === 'lazy' ||
|
|
726
|
-
binding?.kind === 'lazy_fallback') &&
|
|
727
|
-
(component_scope === null || binding.scope === component_scope)
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* @param {AST.Node} node
|
|
733
|
-
* @param {AST.Node[]} normalized
|
|
734
|
-
* @param {CommonContext} context
|
|
735
|
-
*/
|
|
736
|
-
function normalize_child(node, normalized, context) {
|
|
737
|
-
if (node.type === 'EmptyStatement') {
|
|
738
|
-
return;
|
|
739
|
-
} else if (
|
|
740
|
-
node.type === 'Element' &&
|
|
741
|
-
node.id.type === 'Identifier' &&
|
|
742
|
-
((node.id.name === 'style' &&
|
|
743
|
-
!context.state.inside_head &&
|
|
744
|
-
!context.state.keep_component_style) ||
|
|
745
|
-
node.id.name === 'head' ||
|
|
746
|
-
(node.id.name === 'title' && context.state.inside_head))
|
|
747
|
-
) {
|
|
748
|
-
return;
|
|
749
|
-
} else {
|
|
750
|
-
normalized.push(node);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Replaces any lazy subpatterns in a parameter pattern with their generated identifiers.
|
|
756
|
-
* This is used by client and server transforms so nested lazy destructuring can coexist
|
|
757
|
-
* with otherwise normal object/array params.
|
|
758
|
-
* @param {AST.Pattern} pattern
|
|
759
|
-
* @returns {AST.Pattern}
|
|
760
|
-
*/
|
|
761
|
-
export function replace_lazy_param_pattern(pattern) {
|
|
762
|
-
switch (pattern.type) {
|
|
763
|
-
case 'AssignmentPattern':
|
|
764
|
-
return { ...pattern, left: replace_lazy_param_pattern(pattern.left) };
|
|
765
|
-
|
|
766
|
-
case 'ObjectPattern':
|
|
767
|
-
if (pattern.lazy && pattern.metadata?.lazy_id) {
|
|
768
|
-
return /** @type {AST.Pattern} */ (b.id(pattern.metadata.lazy_id));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
return {
|
|
772
|
-
...pattern,
|
|
773
|
-
properties: pattern.properties.map((property) =>
|
|
774
|
-
property.type === 'RestElement'
|
|
775
|
-
? { ...property, argument: replace_lazy_param_pattern(property.argument) }
|
|
776
|
-
: { ...property, value: replace_lazy_param_pattern(property.value) },
|
|
777
|
-
),
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
case 'ArrayPattern':
|
|
781
|
-
if (pattern.lazy && pattern.metadata?.lazy_id) {
|
|
782
|
-
return /** @type {AST.Pattern} */ (b.id(pattern.metadata.lazy_id));
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
return {
|
|
786
|
-
...pattern,
|
|
787
|
-
elements: pattern.elements.map((element) =>
|
|
788
|
-
element === null ? null : replace_lazy_param_pattern(element),
|
|
789
|
-
),
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
case 'RestElement':
|
|
793
|
-
return { ...pattern, argument: replace_lazy_param_pattern(pattern.argument) };
|
|
794
|
-
|
|
795
|
-
default:
|
|
796
|
-
return pattern;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* @param {CommonContext} context
|
|
802
|
-
*/
|
|
803
|
-
export function get_parent_block_node(context) {
|
|
804
|
-
const path = context.path;
|
|
805
|
-
|
|
806
|
-
for (let i = path.length - 1; i >= 0; i -= 1) {
|
|
807
|
-
const context_node = path[i];
|
|
808
|
-
if (
|
|
809
|
-
context_node.type === 'IfStatement' ||
|
|
810
|
-
context_node.type === 'ForOfStatement' ||
|
|
811
|
-
context_node.type === 'SwitchStatement' ||
|
|
812
|
-
context_node.type === 'TryStatement' ||
|
|
813
|
-
context_node.type === 'Component'
|
|
814
|
-
) {
|
|
815
|
-
return context_node;
|
|
816
|
-
}
|
|
817
|
-
if (
|
|
818
|
-
context_node.type === 'FunctionExpression' ||
|
|
819
|
-
context_node.type === 'ArrowFunctionExpression' ||
|
|
820
|
-
context_node.type === 'FunctionDeclaration'
|
|
821
|
-
) {
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
/**
|
|
829
|
-
* Builds a getter for a tracked identifier
|
|
830
|
-
* @param {AST.Identifier} node
|
|
831
|
-
* @param {CommonContext} context
|
|
832
|
-
* @returns {AST.Expression | AST.Identifier}
|
|
833
|
-
*/
|
|
834
|
-
export function build_getter(node, context) {
|
|
835
|
-
const state = context.state;
|
|
836
|
-
|
|
837
|
-
if (!context.path) return node;
|
|
838
|
-
|
|
839
|
-
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
840
|
-
const binding = state.scope.get(node.name);
|
|
841
|
-
const transform = binding?.transform;
|
|
842
|
-
|
|
843
|
-
// don't transform the declaration itself
|
|
844
|
-
if (node !== binding?.node) {
|
|
845
|
-
const read_fn = transform?.read;
|
|
846
|
-
|
|
847
|
-
if (read_fn) {
|
|
848
|
-
return read_fn(node);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
return node;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Determines the namespace for child elements
|
|
858
|
-
* @param {string} element_name
|
|
859
|
-
* @param {NameSpace} current_namespace
|
|
860
|
-
* @returns {NameSpace}
|
|
861
|
-
*/
|
|
862
|
-
export function determine_namespace_for_children(element_name, current_namespace) {
|
|
863
|
-
if (element_name === 'foreignObject') {
|
|
864
|
-
return 'html';
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (element_name === 'svg') {
|
|
868
|
-
return 'svg';
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (element_name === 'math') {
|
|
872
|
-
return 'mathml';
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return current_namespace;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* Converts and index to a key string, where the starting character is a
|
|
880
|
-
* letter.
|
|
881
|
-
* @param {number} index
|
|
882
|
-
*/
|
|
883
|
-
export function index_to_key(index) {
|
|
884
|
-
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
885
|
-
let key = '';
|
|
886
|
-
|
|
887
|
-
do {
|
|
888
|
-
key = letters[index % 26] + key;
|
|
889
|
-
index = Math.floor(index / 26) - 1;
|
|
890
|
-
} while (index >= 0);
|
|
891
|
-
|
|
892
|
-
return key;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Check if a binding ultimately refers to a function, following reference chains
|
|
897
|
-
* @param {Binding} binding
|
|
898
|
-
* @param {ScopeInterface} scope
|
|
899
|
-
* @param {Set<Binding>} visited
|
|
900
|
-
* @returns {boolean}
|
|
901
|
-
*/
|
|
902
|
-
export function is_binding_function(binding, scope, visited = new Set()) {
|
|
903
|
-
if (!binding || visited.has(binding)) {
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
visited.add(binding);
|
|
907
|
-
|
|
908
|
-
const initial = binding.initial;
|
|
909
|
-
if (!initial) {
|
|
910
|
-
return false;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Direct function
|
|
914
|
-
if (
|
|
915
|
-
initial.type === 'FunctionDeclaration' ||
|
|
916
|
-
initial.type === 'FunctionExpression' ||
|
|
917
|
-
initial.type === 'ArrowFunctionExpression'
|
|
918
|
-
) {
|
|
919
|
-
return true;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// Follow identifier references (e.g., const alias = myFunc)
|
|
923
|
-
if (initial.type === 'Identifier') {
|
|
924
|
-
const next_binding = scope.get(initial.name);
|
|
925
|
-
if (next_binding) {
|
|
926
|
-
return is_binding_function(next_binding, scope, visited);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
return false;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* @param {AST.TryStatement} try_parent_stmt
|
|
935
|
-
* @param {CommonContext} context
|
|
936
|
-
* @returns {boolean}
|
|
937
|
-
*/
|
|
938
|
-
export function is_inside_try_block(try_parent_stmt, context) {
|
|
939
|
-
/** @type {AST.BlockStatement | null} */
|
|
940
|
-
let block_node = null;
|
|
941
|
-
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
942
|
-
const context_node = context.path[i];
|
|
943
|
-
|
|
944
|
-
if (context_node.type === 'BlockStatement') {
|
|
945
|
-
block_node = /** @type {AST.BlockStatement} */ (context_node);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
if (context_node === try_parent_stmt) {
|
|
949
|
-
break;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
return block_node !== null && try_parent_stmt.block === block_node;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* Checks if a node is used as the left side of an assignment or update expression.
|
|
958
|
-
* @param {AST.Node} node
|
|
959
|
-
* @returns {boolean}
|
|
960
|
-
*/
|
|
961
|
-
export function is_inside_left_side_assignment(node) {
|
|
962
|
-
const path = node.metadata?.path;
|
|
963
|
-
if (!path || path.length === 0) {
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/** @type {AST.Node} */
|
|
968
|
-
let current = node;
|
|
969
|
-
|
|
970
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
971
|
-
const parent = path[i];
|
|
972
|
-
|
|
973
|
-
switch (parent.type) {
|
|
974
|
-
case 'AssignmentExpression':
|
|
975
|
-
case 'AssignmentPattern':
|
|
976
|
-
if (parent.right === current) {
|
|
977
|
-
return false;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (parent.left === current) {
|
|
981
|
-
return true;
|
|
982
|
-
}
|
|
983
|
-
current = parent;
|
|
984
|
-
continue;
|
|
985
|
-
case 'UpdateExpression':
|
|
986
|
-
return true;
|
|
987
|
-
case 'MemberExpression':
|
|
988
|
-
// In obj[computeKey()] = 10, computeKey() is evaluated to determine
|
|
989
|
-
// which property to assign to, but is not itself an assignment target
|
|
990
|
-
if (parent.computed && parent.property === current) {
|
|
991
|
-
return false;
|
|
992
|
-
}
|
|
993
|
-
current = parent;
|
|
994
|
-
continue;
|
|
995
|
-
case 'Property':
|
|
996
|
-
// exit here to stop promoting current to parent
|
|
997
|
-
// and thus reaching VariableDeclarator, causing an erroneous truthy result
|
|
998
|
-
// e.g. const { [computeKey()]: value } = obj; where node = computeKey:
|
|
999
|
-
if (parent.key === current) {
|
|
1000
|
-
return false;
|
|
1001
|
-
}
|
|
1002
|
-
current = parent;
|
|
1003
|
-
continue;
|
|
1004
|
-
case 'VariableDeclarator':
|
|
1005
|
-
return parent.id === current;
|
|
1006
|
-
case 'ForInStatement':
|
|
1007
|
-
case 'ForOfStatement':
|
|
1008
|
-
return parent.left === current;
|
|
1009
|
-
|
|
1010
|
-
case 'Program':
|
|
1011
|
-
case 'FunctionDeclaration':
|
|
1012
|
-
case 'FunctionExpression':
|
|
1013
|
-
case 'ArrowFunctionExpression':
|
|
1014
|
-
case 'ClassDeclaration':
|
|
1015
|
-
case 'ClassExpression':
|
|
1016
|
-
case 'MethodDefinition':
|
|
1017
|
-
case 'PropertyDefinition':
|
|
1018
|
-
case 'StaticBlock':
|
|
1019
|
-
case 'Component':
|
|
1020
|
-
case 'Element':
|
|
1021
|
-
return false;
|
|
1022
|
-
|
|
1023
|
-
default:
|
|
1024
|
-
current = parent;
|
|
1025
|
-
continue;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
/**
|
|
1033
|
-
* Flattens top-level BlockStatements in switch case consequents so that
|
|
1034
|
-
* BreakStatements and elements inside block-scoped cases are properly handled.
|
|
1035
|
-
* e.g. `case 1: { <div /> break; }` → `[Element, BreakStatement]`
|
|
1036
|
-
* @param {AST.Node[]} consequent
|
|
1037
|
-
* @returns {AST.Node[]}
|
|
1038
|
-
*/
|
|
1039
|
-
export function flatten_switch_consequent(consequent) {
|
|
1040
|
-
/** @type {AST.Node[]} */
|
|
1041
|
-
const result = [];
|
|
1042
|
-
for (const node of consequent) {
|
|
1043
|
-
if (node.type === 'BlockStatement') {
|
|
1044
|
-
result.push(.../** @type {AST.BlockStatement} */ (node).body);
|
|
1045
|
-
} else {
|
|
1046
|
-
result.push(node);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
return result;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
/**
|
|
1053
|
-
* @param {string | null | undefined} name
|
|
1054
|
-
* @returns {string | null}
|
|
1055
|
-
*/
|
|
1056
|
-
export function get_ripple_namespace_call_name(name) {
|
|
1057
|
-
return name == null ? null : (RIPPLE_IMPORT_CALL_NAME[name]?.name ?? null);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* Returns true if the given import name requires a __block parameter
|
|
1062
|
-
* @param {string} name
|
|
1063
|
-
* @returns {boolean}
|
|
1064
|
-
*/
|
|
1065
|
-
export function ripple_import_requires_block(name) {
|
|
1066
|
-
return name == null ? false : (RIPPLE_IMPORT_CALL_NAME[name]?.requiresBlock ?? false);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* @param {AST.ClassDeclaration | AST.ClassExpression} node
|
|
1071
|
-
* @param {CommonContext} context
|
|
1072
|
-
* @returns {void}
|
|
1073
|
-
*/
|
|
1074
|
-
export function strip_class_typescript_syntax(node, context) {
|
|
1075
|
-
delete node.typeParameters;
|
|
1076
|
-
delete node.superTypeParameters;
|
|
1077
|
-
delete node.implements;
|
|
1078
|
-
|
|
1079
|
-
if (node.superClass?.type === 'TSInstantiationExpression') {
|
|
1080
|
-
node.superClass = /** @type {AST.Expression} */ (context.visit(node.superClass.expression));
|
|
1081
|
-
} else if (node.superClass && 'typeArguments' in node.superClass) {
|
|
1082
|
-
delete node.superClass.typeArguments;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
/**
|
|
1087
|
-
* Converts a JSXMemberExpression to an AST MemberExpression.
|
|
1088
|
-
* e.g., <Foo.Bar.Baz> → MemberExpression(MemberExpression(Foo, Bar), Baz)
|
|
1089
|
-
* @param {import('estree-jsx').JSXMemberExpression} jsx_member
|
|
1090
|
-
* @returns {AST.MemberExpression}
|
|
1091
|
-
*/
|
|
1092
|
-
function jsx_member_expression_to_member_expression(jsx_member) {
|
|
1093
|
-
/** @type {AST.Expression} */
|
|
1094
|
-
let object;
|
|
1095
|
-
|
|
1096
|
-
if (jsx_member.object.type === 'JSXMemberExpression') {
|
|
1097
|
-
// Recursively convert nested member expressions
|
|
1098
|
-
object = jsx_member_expression_to_member_expression(jsx_member.object);
|
|
1099
|
-
} else {
|
|
1100
|
-
// Base case: JSXIdentifier
|
|
1101
|
-
object = /** @type {AST.Identifier} */ ({
|
|
1102
|
-
type: 'Identifier',
|
|
1103
|
-
name: jsx_member.object.name,
|
|
1104
|
-
start: jsx_member.object.start,
|
|
1105
|
-
end: jsx_member.object.end,
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
return /** @type {AST.MemberExpression} */ ({
|
|
1110
|
-
type: 'MemberExpression',
|
|
1111
|
-
object,
|
|
1112
|
-
property: /** @type {AST.Identifier} */ ({
|
|
1113
|
-
type: 'Identifier',
|
|
1114
|
-
name: jsx_member.property.name,
|
|
1115
|
-
start: jsx_member.property.start,
|
|
1116
|
-
end: jsx_member.property.end,
|
|
1117
|
-
}),
|
|
1118
|
-
computed: false,
|
|
1119
|
-
optional: false,
|
|
1120
|
-
start: jsx_member.start,
|
|
1121
|
-
end: jsx_member.end,
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Converts a JSX AST node (JSXElement, JSXText, etc.) to a Ripple AST node
|
|
1127
|
-
* (Element, Text, RippleExpression) for processing inside `<tsx>` blocks.
|
|
1128
|
-
* @param {AST.Node} node
|
|
1129
|
-
* @returns {AST.Node | AST.Node[] | null}
|
|
1130
|
-
*/
|
|
1131
|
-
export function jsx_to_ripple_node(node) {
|
|
1132
|
-
if (node.type === 'JSXElement') {
|
|
1133
|
-
const opening = node.openingElement;
|
|
1134
|
-
const name = opening.name;
|
|
1135
|
-
|
|
1136
|
-
/** @type {AST.Identifier | AST.MemberExpression} */
|
|
1137
|
-
let id;
|
|
1138
|
-
|
|
1139
|
-
if (name.type === 'JSXIdentifier') {
|
|
1140
|
-
id = /** @type {AST.Identifier} */ ({
|
|
1141
|
-
type: 'Identifier',
|
|
1142
|
-
name: name.name,
|
|
1143
|
-
start: name.start,
|
|
1144
|
-
end: name.end,
|
|
1145
|
-
});
|
|
1146
|
-
} else if (name.type === 'JSXMemberExpression') {
|
|
1147
|
-
// Convert JSXMemberExpression to MemberExpression
|
|
1148
|
-
// e.g., <Foo.Bar.Baz> → MemberExpression(MemberExpression(Foo, Bar), Baz)
|
|
1149
|
-
id = jsx_member_expression_to_member_expression(name);
|
|
1150
|
-
} else if (name.type === 'JSXNamespacedName') {
|
|
1151
|
-
// For JSXNamespacedName like <namespace:element>, create an identifier with the full name
|
|
1152
|
-
id = /** @type {AST.Identifier} */ ({
|
|
1153
|
-
type: 'Identifier',
|
|
1154
|
-
name: name.namespace.name + ':' + name.name.name,
|
|
1155
|
-
start: name.start,
|
|
1156
|
-
end: name.end,
|
|
1157
|
-
});
|
|
1158
|
-
} else {
|
|
1159
|
-
// Fallback - should not reach here
|
|
1160
|
-
id = /** @type {AST.Identifier} */ ({
|
|
1161
|
-
type: 'Identifier',
|
|
1162
|
-
name: 'unknown',
|
|
1163
|
-
start: /** @type {any} */ (name).start,
|
|
1164
|
-
end: /** @type {any} */ (name).end,
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const attributes = opening.attributes
|
|
1169
|
-
.map((attr) => {
|
|
1170
|
-
if (attr.type === 'JSXAttribute') {
|
|
1171
|
-
const is_dynamic = attr.value && attr.value.type === 'JSXExpressionContainer';
|
|
1172
|
-
return /** @type {AST.Node} */ ({
|
|
1173
|
-
type: 'Attribute',
|
|
1174
|
-
name: {
|
|
1175
|
-
type: 'Identifier',
|
|
1176
|
-
name:
|
|
1177
|
-
attr.name.type === 'JSXIdentifier'
|
|
1178
|
-
? attr.name.name
|
|
1179
|
-
: attr.name.namespace.name + ':' + attr.name.name.name,
|
|
1180
|
-
tracked: is_dynamic,
|
|
1181
|
-
start: attr.name.start,
|
|
1182
|
-
end: attr.name.end,
|
|
1183
|
-
},
|
|
1184
|
-
value: attr.value
|
|
1185
|
-
? attr.value.type === 'JSXExpressionContainer'
|
|
1186
|
-
? attr.value.expression
|
|
1187
|
-
: attr.value
|
|
1188
|
-
: null,
|
|
1189
|
-
shorthand: false,
|
|
1190
|
-
start: attr.start,
|
|
1191
|
-
end: attr.end,
|
|
1192
|
-
});
|
|
1193
|
-
} else if (attr.type === 'JSXSpreadAttribute') {
|
|
1194
|
-
return /** @type {AST.Node} */ ({
|
|
1195
|
-
type: 'SpreadAttribute',
|
|
1196
|
-
argument: attr.argument,
|
|
1197
|
-
start: attr.start,
|
|
1198
|
-
end: attr.end,
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
return null;
|
|
1202
|
-
})
|
|
1203
|
-
.filter(Boolean);
|
|
1204
|
-
|
|
1205
|
-
const children = /** @type {AST.Node[]} */ (
|
|
1206
|
-
/** @type {AST.Node[]} */ (node.children).map(jsx_to_ripple_node).flat().filter(Boolean)
|
|
1207
|
-
);
|
|
1208
|
-
|
|
1209
|
-
return /** @type {AST.Element} */ (
|
|
1210
|
-
/** @type {unknown} */ ({
|
|
1211
|
-
type: 'Element',
|
|
1212
|
-
id,
|
|
1213
|
-
attributes,
|
|
1214
|
-
children,
|
|
1215
|
-
selfClosing: opening.selfClosing,
|
|
1216
|
-
metadata: { scoped: false, path: /** @type {string[]} */ ([]) },
|
|
1217
|
-
start: node.start,
|
|
1218
|
-
end: node.end,
|
|
1219
|
-
})
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
if (node.type === 'JSXText') {
|
|
1224
|
-
if (node.value.trim() === '') return null;
|
|
1225
|
-
return /** @type {AST.Node} */ ({
|
|
1226
|
-
type: 'Text',
|
|
1227
|
-
expression: {
|
|
1228
|
-
type: 'Literal',
|
|
1229
|
-
value: node.value,
|
|
1230
|
-
raw: JSON.stringify(node.value),
|
|
1231
|
-
start: node.start,
|
|
1232
|
-
end: node.end,
|
|
1233
|
-
},
|
|
1234
|
-
metadata: {},
|
|
1235
|
-
start: node.start,
|
|
1236
|
-
end: node.end,
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (node.type === 'JSXExpressionContainer') {
|
|
1241
|
-
if (node.expression.type === 'JSXEmptyExpression') return null;
|
|
1242
|
-
return /** @type {AST.Node} */ ({
|
|
1243
|
-
type: 'RippleExpression',
|
|
1244
|
-
expression: node.expression,
|
|
1245
|
-
metadata: {},
|
|
1246
|
-
start: node.start,
|
|
1247
|
-
end: node.end,
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
if (node.type === 'JSXFragment') {
|
|
1252
|
-
return /** @type {AST.Node[]} */ (
|
|
1253
|
-
/** @type {AST.Node[]} */ (node.children).map(jsx_to_ripple_node).flat().filter(Boolean)
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
return node;
|
|
1258
|
-
}
|