ripple 0.2.116 → 0.2.118

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.
Files changed (86) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/3-transform/segments.js +99 -57
  4. package/src/compiler/phases/3-transform/server/index.js +21 -11
  5. package/src/runtime/index-client.js +1 -1
  6. package/src/runtime/index-server.js +24 -0
  7. package/src/runtime/internal/client/runtime.js +10 -0
  8. package/src/runtime/internal/client/utils.js +0 -8
  9. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  10. package/tests/client/_etc.test.ripple +5 -0
  11. package/tests/client/array/array.copy-within.test.ripple +120 -0
  12. package/tests/client/array/array.derived.test.ripple +495 -0
  13. package/tests/client/array/array.iteration.test.ripple +115 -0
  14. package/tests/client/array/array.mutations.test.ripple +385 -0
  15. package/tests/client/array/array.static.test.ripple +237 -0
  16. package/tests/client/array/array.to-methods.test.ripple +93 -0
  17. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  18. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  19. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  20. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  21. package/tests/client/basic/basic.collections.test.ripple +94 -0
  22. package/tests/client/basic/basic.components.test.ripple +225 -0
  23. package/tests/client/basic/basic.errors.test.ripple +126 -0
  24. package/tests/client/basic/basic.events.test.ripple +222 -0
  25. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  26. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  27. package/tests/client/basic/basic.styling.test.ripple +63 -0
  28. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  29. package/tests/client/boundaries.test.ripple +2 -21
  30. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  31. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  32. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  33. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  34. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  35. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  36. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  37. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  38. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  39. package/tests/client/composite/composite.generics.test.ripple +211 -0
  40. package/tests/client/composite/composite.props.test.ripple +106 -0
  41. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  42. package/tests/client/composite/composite.render.test.ripple +84 -0
  43. package/tests/client/computed-properties.test.ripple +2 -21
  44. package/tests/client/context.test.ripple +5 -22
  45. package/tests/client/date.test.ripple +1 -20
  46. package/tests/client/dynamic-elements.test.ripple +16 -24
  47. package/tests/client/for.test.ripple +4 -23
  48. package/tests/client/head.test.ripple +11 -23
  49. package/tests/client/html.test.ripple +1 -20
  50. package/tests/client/input-value.test.ripple +11 -31
  51. package/tests/client/map.test.ripple +3 -22
  52. package/tests/client/media-query.test.ripple +10 -23
  53. package/tests/client/object.test.ripple +5 -24
  54. package/tests/client/portal.test.ripple +2 -19
  55. package/tests/client/ref.test.ripple +8 -26
  56. package/tests/client/set.test.ripple +3 -22
  57. package/tests/client/svg.test.ripple +1 -22
  58. package/tests/client/switch.test.ripple +6 -25
  59. package/tests/client/tracked-expression.test.ripple +2 -21
  60. package/tests/client/typescript-generics.test.ripple +0 -21
  61. package/tests/client/url/url.derived.test.ripple +83 -0
  62. package/tests/client/url/url.parsing.test.ripple +165 -0
  63. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  64. package/tests/client/url/url.reactivity.test.ripple +449 -0
  65. package/tests/client/url/url.serialization.test.ripple +50 -0
  66. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  67. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  68. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  69. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  70. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  71. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  72. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  73. package/tests/client.d.ts +12 -0
  74. package/tests/server/if.test.ripple +66 -0
  75. package/tests/setup-client.js +28 -0
  76. package/tsconfig.json +4 -2
  77. package/types/index.d.ts +5 -1
  78. package/LICENSE +0 -21
  79. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  80. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  81. package/tests/client/array.test.ripple +0 -1455
  82. package/tests/client/basic.test.ripple +0 -1892
  83. package/tests/client/compiler.test.ripple +0 -541
  84. package/tests/client/composite.test.ripple +0 -692
  85. package/tests/client/url-search-params.test.ripple +0 -912
  86. package/tests/client/url.test.ripple +0 -954
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.116",
6
+ "version": "0.2.118",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -63,21 +63,21 @@
63
63
  "#public": "./types/index.d.ts"
64
64
  },
65
65
  "dependencies": {
66
- "@jridgewell/sourcemap-codec": "^1.5.5",
67
- "@sveltejs/acorn-typescript": "^1.0.6",
68
- "acorn": "^8.15.0",
69
- "clsx": "^2.1.1",
70
- "devalue": "^5.3.2",
71
- "esm-env": "^1.2.2",
72
- "esrap": "^2.1.0",
73
- "is-reference": "^3.0.3",
74
- "magic-string": "^0.30.18",
75
- "muggle-string": "^0.4.1",
76
- "zimmerframe": "^1.1.2"
66
+ "@jridgewell/sourcemap-codec": "catalog:default",
67
+ "@sveltejs/acorn-typescript": "catalog:default",
68
+ "acorn": "catalog:default",
69
+ "clsx": "catalog:default",
70
+ "devalue": "catalog:default",
71
+ "esm-env": "catalog:default",
72
+ "esrap": "catalog:default",
73
+ "is-reference": "catalog:default",
74
+ "magic-string": "catalog:default",
75
+ "muggle-string": "catalog:default",
76
+ "zimmerframe": "catalog:default"
77
77
  },
78
78
  "devDependencies": {
79
- "@types/estree": "^1.0.8",
80
- "@types/node": "^24.3.0",
81
- "typescript": "^5.9.2"
79
+ "@types/estree": "catalog:default",
80
+ "@types/node": "catalog:default",
81
+ "typescript": "catalog:default"
82
82
  }
83
- }
83
+ }
@@ -41,9 +41,28 @@ export function compile(source, filename, options = {}) {
41
41
  export function compile_to_volar_mappings(source, filename) {
42
42
  // Parse and transform
43
43
  const ast = parse_module(source);
44
+
45
+ // Add unique IDs to import declarations before transformation
46
+ // This allows us to match source imports with generated imports reliably
47
+ // This strategy can potentially be used for other node types in the future
48
+ let gen_id = 0;
49
+ const source_import_map = new Map();
50
+ for (const node of ast.body) {
51
+ if (node.type === 'ImportDeclaration') {
52
+ const start = /** @type {any} */ (node).start;
53
+ const end = /** @type {any} */ (node).end;
54
+ if (start !== undefined && end !== undefined) {
55
+ // Add a unique ID as a string property that will be copied during transformation
56
+ const id = `__volar_import_${gen_id++}__`;
57
+ /** @type {any} */ (node).__volar_id = id;
58
+ source_import_map.set(id, { start, end });
59
+ }
60
+ }
61
+ }
62
+
44
63
  const analysis = analyze(ast, filename);
45
64
  const transformed = transform_client(filename, source, analysis, true);
46
65
 
47
66
  // Create volar mappings directly from the AST instead of relying on esrap's sourcemap
48
- return convert_source_map_to_mappings(transformed.ast, source, transformed.js.code);
67
+ return convert_source_map_to_mappings(transformed.ast, source, transformed.js.code, source_import_map);
49
68
  }
@@ -20,22 +20,23 @@ export const mapping_data = {
20
20
  * @param {any} ast - The transformed AST
21
21
  * @param {string} source - Original source code
22
22
  * @param {string} generated_code - Generated code from esrap
23
+ * @param {Map<string, {start: number, end: number}>} [source_import_map] - Map of __volar_id strings to source positions
23
24
  * @returns {object}
24
25
  */
25
- export function convert_source_map_to_mappings(ast, source, generated_code) {
26
+ export function convert_source_map_to_mappings(ast, source, generated_code, source_import_map) {
26
27
  /** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
27
28
  const mappings = [];
28
29
 
29
30
  // Maintain indices that walk through source and generated code
30
- let sourceIndex = 0;
31
- let generatedIndex = 0;
31
+ let source_index = 0;
32
+ let generated_index = 0;
32
33
 
33
34
  // Map to track capitalized names: original name -> capitalized name
34
35
  /** @type {Map<string, string>} */
35
- const capitalizedNames = new Map();
36
+ const capitalized_names = new Map();
36
37
  // Reverse map: capitalized name -> original name
37
38
  /** @type {Map<string, string>} */
38
- const reverseCapitalizedNames = new Map();
39
+ const reverse_capitalized_names = new Map();
39
40
 
40
41
  // Pre-walk to collect capitalized names from JSXElement nodes (transformed AST)
41
42
  // These are identifiers that are used as dynamic components/elements
@@ -43,8 +44,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
43
44
  _(node, { next }) {
44
45
  // Check JSXElement nodes with metadata (preserved from Element nodes)
45
46
  if (node.type === 'JSXElement' && node.metadata?.ts_name && node.metadata?.original_name) {
46
- capitalizedNames.set(node.metadata.original_name, node.metadata.ts_name);
47
- reverseCapitalizedNames.set(node.metadata.ts_name, node.metadata.original_name);
47
+ capitalized_names.set(node.metadata.original_name, node.metadata.ts_name);
48
+ reverse_capitalized_names.set(node.metadata.ts_name, node.metadata.original_name);
48
49
  }
49
50
  next();
50
51
  }
@@ -55,7 +56,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
55
56
  * @param {string} char
56
57
  * @returns {boolean}
57
58
  */
58
- const isWordBoundary = (char) => {
59
+ const is_word_boundary = (char) => {
59
60
  return char === undefined || !/[a-zA-Z0-9_$]/.test(char);
60
61
  };
61
62
 
@@ -64,7 +65,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
64
65
  * @param {number} pos - Position to check
65
66
  * @returns {boolean}
66
67
  */
67
- const isInComment = (pos) => {
68
+ const is_in_comment = (pos) => {
68
69
  // Check for single-line comment: find start of line and check if there's // before this position
69
70
  let lineStart = source.lastIndexOf('\n', pos - 1) + 1;
70
71
  const lineBeforePos = source.substring(lineStart, pos);
@@ -87,8 +88,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
87
88
  * @param {string} text - Text to find
88
89
  * @returns {number|null} - Source position or null
89
90
  */
90
- const findInSource = (text) => {
91
- for (let i = sourceIndex; i <= source.length - text.length; i++) {
91
+ const find_in_source = (text) => {
92
+ for (let i = source_index; i <= source.length - text.length; i++) {
92
93
  let match = true;
93
94
  for (let j = 0; j < text.length; j++) {
94
95
  if (source[i + j] !== text[j]) {
@@ -98,7 +99,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
98
99
  }
99
100
  if (match) {
100
101
  // Skip if this match is inside a comment
101
- if (isInComment(i)) {
102
+ if (is_in_comment(i)) {
102
103
  continue;
103
104
  }
104
105
 
@@ -107,12 +108,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
107
108
  if (isIdentifierLike) {
108
109
  const charBefore = source[i - 1];
109
110
  const charAfter = source[i + text.length];
110
- if (!isWordBoundary(charBefore) || !isWordBoundary(charAfter)) {
111
+ if (!is_word_boundary(charBefore) || !is_word_boundary(charAfter)) {
111
112
  continue; // Not a whole word match, keep searching
112
113
  }
113
114
  }
114
115
 
115
- sourceIndex = i + text.length;
116
+ source_index = i + text.length;
116
117
  return i;
117
118
  }
118
119
  }
@@ -120,12 +121,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
120
121
  };
121
122
 
122
123
  /**
123
- * Find text in generated code, searching character by character from generatedIndex
124
+ * Find text in generated code, searching character by character from generated_index
124
125
  * @param {string} text - Text to find
125
126
  * @returns {number|null} - Generated position or null
126
127
  */
127
- const findInGenerated = (text) => {
128
- for (let i = generatedIndex; i <= generated_code.length - text.length; i++) {
128
+ const find_in_generated = (text) => {
129
+ for (let i = generated_index; i <= generated_code.length - text.length; i++) {
129
130
  let match = true;
130
131
  for (let j = 0; j < text.length; j++) {
131
132
  if (generated_code[i + j] !== text[j]) {
@@ -139,12 +140,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
139
140
  if (isIdentifierLike) {
140
141
  const charBefore = generated_code[i - 1];
141
142
  const charAfter = generated_code[i + text.length];
142
- if (!isWordBoundary(charBefore) || !isWordBoundary(charAfter)) {
143
+ if (!is_word_boundary(charBefore) || !is_word_boundary(charAfter)) {
143
144
  continue; // Not a whole word match, keep searching
144
145
  }
145
146
  }
146
147
 
147
- generatedIndex = i + text.length;
148
+ generated_index = i + text.length;
148
149
  return i;
149
150
  }
150
151
  }
@@ -157,8 +158,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
157
158
  const tokens = [];
158
159
 
159
160
  // Collect import declarations for full-statement mappings
160
- /** @type {Array<{start: number, end: number}>} */
161
- const importDeclarations = [];
161
+ /** @type {Array<{id: string, node: any}>} */
162
+ const import_declarations = [];
162
163
 
163
164
  // We have to visit everything in generated order to maintain correct indices
164
165
  walk(ast, null, {
@@ -172,17 +173,23 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
172
173
  tokens.push({ source: node.metadata.tracked_shorthand, generated: node.name });
173
174
  } else {
174
175
  // Check if this identifier was capitalized (reverse lookup)
175
- const originalName = reverseCapitalizedNames.get(node.name);
176
- if (originalName) {
176
+ const original_name = reverse_capitalized_names.get(node.name);
177
+ if (original_name) {
177
178
  // This is a capitalized name in generated code, map to lowercase in source
178
- tokens.push({ source: originalName, generated: node.name });
179
+ tokens.push({ source: original_name, generated: node.name });
179
180
  } else {
180
181
  // Check if this identifier should be capitalized (forward lookup)
181
- const capitalizedName = capitalizedNames.get(node.name);
182
- if (capitalizedName) {
183
- tokens.push({ source: node.name, generated: capitalizedName });
182
+ const cap_name = capitalized_names.get(node.name);
183
+ if (cap_name) {
184
+ tokens.push({ source: node.name, generated: cap_name });
184
185
  } else {
185
- tokens.push(node.name);
186
+ // Check if this identifier should be capitalized (forward lookup)
187
+ const cap_name = capitalized_names.get(node.name);
188
+ if (cap_name) {
189
+ tokens.push({ source: node.name, generated: cap_name });
190
+ } else {
191
+ tokens.push(node.name);
192
+ }
186
193
  }
187
194
  }
188
195
  }
@@ -191,12 +198,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
191
198
  } else if (node.type === 'JSXIdentifier' && node.name) {
192
199
  if (node.loc) {
193
200
  // Check if this was capitalized (reverse lookup)
194
- const originalName = reverseCapitalizedNames.get(node.name);
201
+ const originalName = reverse_capitalized_names.get(node.name);
195
202
  if (originalName) {
196
203
  tokens.push({ source: originalName, generated: node.name });
197
204
  } else {
198
205
  // Check if this should be capitalized (forward lookup)
199
- const capitalizedName = capitalizedNames.get(node.name);
206
+ const capitalizedName = capitalized_names.get(node.name);
200
207
  if (capitalizedName) {
201
208
  tokens.push({ source: node.name, generated: capitalizedName });
202
209
  } else {
@@ -211,10 +218,16 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
211
218
  }
212
219
  return; // Leaf node, don't traverse further
213
220
  } else if (node.type === 'ImportDeclaration') {
214
- // Collect import declaration range for full-statement mapping
221
+ // Collect import declaration for full-statement mapping
215
222
  // TypeScript reports unused imports with diagnostics covering the entire statement
216
- if (node.start !== undefined && node.end !== undefined) {
217
- importDeclarations.push({ start: node.start, end: node.end });
223
+ // Store the __volar_id - we'll find the generated position later by searching
224
+ const volar_id = /** @type {any} */ (node).__volar_id;
225
+ if (volar_id) {
226
+ import_declarations.push({
227
+ id: volar_id,
228
+ // We'll calculate genStart/genEnd later by searching in generated code
229
+ node: node
230
+ });
218
231
  }
219
232
 
220
233
  // Visit specifiers in source order
@@ -315,12 +328,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
315
328
  if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
316
329
  const closingName = node.closingElement.name.name;
317
330
  // Check if this was capitalized (reverse lookup)
318
- const originalName = reverseCapitalizedNames.get(closingName);
331
+ const originalName = reverse_capitalized_names.get(closingName);
319
332
  if (originalName) {
320
333
  tokens.push({ source: originalName, generated: closingName });
321
334
  } else {
322
335
  // Check if this should be capitalized (forward lookup)
323
- const capitalizedName = capitalizedNames.get(closingName);
336
+ const capitalizedName = capitalized_names.get(closingName);
324
337
  if (capitalizedName) {
325
338
  tokens.push({ source: closingName, generated: capitalizedName });
326
339
  } else {
@@ -1106,25 +1119,25 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
1106
1119
 
1107
1120
  // Process each token in order
1108
1121
  for (const token of tokens) {
1109
- let sourceText, generatedText;
1122
+ let source_text, generated_text;
1110
1123
 
1111
1124
  if (typeof token === 'string') {
1112
- sourceText = token;
1113
- generatedText = token;
1125
+ source_text = token;
1126
+ generated_text = token;
1114
1127
  } else {
1115
1128
  // Token with different source and generated names
1116
- sourceText = token.source;
1117
- generatedText = token.generated;
1129
+ source_text = token.source;
1130
+ generated_text = token.generated;
1118
1131
  }
1119
1132
 
1120
- const sourcePos = findInSource(sourceText);
1121
- const genPos = findInGenerated(generatedText);
1133
+ const source_pos = find_in_source(source_text);
1134
+ const gen_pos = find_in_generated(generated_text);
1122
1135
 
1123
- if (sourcePos !== null && genPos !== null) {
1136
+ if (source_pos !== null && gen_pos !== null) {
1124
1137
  mappings.push({
1125
- sourceOffsets: [sourcePos],
1126
- generatedOffsets: [genPos],
1127
- lengths: [sourceText.length],
1138
+ sourceOffsets: [source_pos],
1139
+ generatedOffsets: [gen_pos],
1140
+ lengths: [source_text.length],
1128
1141
  data: mapping_data,
1129
1142
  });
1130
1143
  }
@@ -1133,17 +1146,46 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
1133
1146
  // Add full-statement mappings for import declarations
1134
1147
  // TypeScript reports unused import diagnostics covering the entire import statement
1135
1148
  // Use verification-only mapping to avoid duplicate hover/completion
1136
- for (const importDecl of importDeclarations) {
1137
- const length = importDecl.end - importDecl.start;
1138
- mappings.push({
1139
- sourceOffsets: [importDecl.start],
1140
- generatedOffsets: [importDecl.start], // Same position in generated code
1141
- lengths: [length],
1142
- data: {
1143
- // only verification (diagnostics) to avoid duplicate hover/completion
1144
- verification: true
1145
- },
1146
- });
1149
+
1150
+ // Use the source import map from the original AST (before transformation)
1151
+ // The __volar_id property is preserved through transformation via object spread
1152
+ if (source_import_map && import_declarations.length > 0) {
1153
+ // We need to find where each import appears in the generated code
1154
+ // Search for "import" keywords and match them to our collected imports
1155
+ let gen_search_index = 0;
1156
+
1157
+ for (const import_decl of import_declarations) {
1158
+ // Look up the source position using the __volar_id
1159
+ const source_range = source_import_map.get(import_decl.id);
1160
+ if (!source_range) continue; // Skip if we don't have source info for this ID
1161
+
1162
+ // Find this import statement in the generated code
1163
+ // Search for "import " starting from our last position
1164
+ const import_keyword_index = generated_code.indexOf('import ', gen_search_index);
1165
+ if (import_keyword_index === -1) continue; // Couldn't find it
1166
+
1167
+ // Find the semicolon or end of line for this import
1168
+ let gen_end = generated_code.indexOf(';', import_keyword_index);
1169
+ if (gen_end === -1) gen_end = generated_code.indexOf('\n', import_keyword_index);
1170
+ if (gen_end === -1) gen_end = generated_code.length;
1171
+ else gen_end += 1; // Include the semicolon
1172
+
1173
+ const get_start = import_keyword_index;
1174
+ gen_search_index = gen_end; // Next search starts after this import
1175
+
1176
+ const source_length = source_range.end - source_range.start;
1177
+ const get_length = gen_end - get_start;
1178
+
1179
+ mappings.push({
1180
+ sourceOffsets: [source_range.start],
1181
+ generatedOffsets: [get_start],
1182
+ lengths: [Math.min(source_length, get_length)],
1183
+ data: {
1184
+ // only verification (diagnostics) to avoid duplicate hover/completion
1185
+ verification: true
1186
+ },
1187
+ });
1188
+ }
1147
1189
  }
1148
1190
 
1149
1191
  // Sort mappings by source offset
@@ -525,18 +525,28 @@ const visitors = {
525
525
  return;
526
526
  }
527
527
 
528
- // TODO: alternative (else if / else)
529
- context.state.init.push(
530
- b.if(
531
- context.visit(node.test),
532
- b.block(
533
- transform_body(node.consequent.body, {
534
- ...context,
535
- state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
536
- }),
537
- ),
538
- ),
528
+ const consequent = b.block(
529
+ transform_body(node.consequent.body, {
530
+ ...context,
531
+ state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
532
+ }),
539
533
  );
534
+
535
+ let alternate = null;
536
+ if (node.alternate) {
537
+ const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
538
+ const alternate_body_nodes =
539
+ node.alternate.type === 'IfStatement' ? [node.alternate] : node.alternate.body;
540
+
541
+ alternate = b.block(
542
+ transform_body(alternate_body_nodes, {
543
+ ...context,
544
+ state: { ...context.state, scope: alternate_scope },
545
+ }),
546
+ );
547
+ }
548
+
549
+ context.state.init.push(b.if(context.visit(node.test), consequent, alternate));
540
550
  },
541
551
 
542
552
  Identifier(node, context) {
@@ -74,7 +74,7 @@ export { user_effect as effect } from './internal/client/blocks.js';
74
74
 
75
75
  export { Portal } from './internal/client/portal.js';
76
76
 
77
- export { ref_prop as createRefKey } from './internal/client/runtime.js';
77
+ export { ref_prop as createRefKey, get, public_set as set } from './internal/client/runtime.js';
78
78
 
79
79
  export { on } from './internal/client/events.js';
80
80
 
@@ -12,6 +12,30 @@ export function effect() {
12
12
 
13
13
  var empty_get_set = { get: undefined, set: undefined };
14
14
 
15
+ /**
16
+ * @param {Derived | Tracked} tracked
17
+ * @returns {any}
18
+ */
19
+ export function get(tracked) {
20
+ if (!is_tracked_object(tracked)) {
21
+ return tracked;
22
+ }
23
+
24
+ var g = tracked.a.get;
25
+
26
+ return g ? g(tracked.v) : tracked.v;
27
+ }
28
+
29
+ /**
30
+ * @param {Tracked} tracked
31
+ * @param {any} value
32
+ */
33
+ export function set(tracked, value) {
34
+ var s = tracked.a.set;
35
+
36
+ tracked.v = s ? s(value, tracked.v) : value;
37
+ }
38
+
15
39
  /**
16
40
  *
17
41
  * @param {any} v
@@ -768,6 +768,16 @@ export function get_tracked(tracked) {
768
768
  return value;
769
769
  }
770
770
 
771
+ /**
772
+ * Exposed version of `set` to avoid internal bugs
773
+ * since block is required on the internal `set`
774
+ * @param {Derived | Tracked} tracked
775
+ * @param {any} value
776
+ */
777
+ export function public_set(tracked, value) {
778
+ set(tracked, value, safe_scope());
779
+ }
780
+
771
781
  /**
772
782
  * @param {Derived | Tracked} tracked
773
783
  * @param {any} value
@@ -35,14 +35,6 @@ export function create_anchor() {
35
35
  return t;
36
36
  }
37
37
 
38
- /**
39
- * @param {any} value
40
- * @returns {boolean}
41
- */
42
- export function is_positive_integer(value) {
43
- return Number.isInteger(value) && /**@type {number} */ (value) >= 0;
44
- }
45
-
46
38
  /**
47
39
  * Checks if an object is a tracked object (has a numeric 'f' property).
48
40
  * @param {any} v - The object to check.
@@ -317,3 +317,83 @@ exports[`for statements > render a simple static array 1`] = `
317
317
 
318
318
  </div>
319
319
  `;
320
+
321
+ exports[`for statements > renders a simple dynamic array 1`] = `
322
+ <div>
323
+ <!---->
324
+ <div
325
+ class="Item 1"
326
+ >
327
+ Item 1
328
+ </div>
329
+ <div
330
+ class="Item 2"
331
+ >
332
+ Item 2
333
+ </div>
334
+ <div
335
+ class="Item 3"
336
+ >
337
+ Item 3
338
+ </div>
339
+ <!---->
340
+ <button>
341
+ Add Item
342
+ </button>
343
+
344
+ </div>
345
+ `;
346
+
347
+ exports[`for statements > renders a simple dynamic array 2`] = `
348
+ <div>
349
+ <!---->
350
+ <div
351
+ class="Item 1"
352
+ >
353
+ Item 1
354
+ </div>
355
+ <div
356
+ class="Item 2"
357
+ >
358
+ Item 2
359
+ </div>
360
+ <div
361
+ class="Item 3"
362
+ >
363
+ Item 3
364
+ </div>
365
+ <div
366
+ class="Item 4"
367
+ >
368
+ Item 4
369
+ </div>
370
+ <!---->
371
+ <button>
372
+ Add Item
373
+ </button>
374
+
375
+ </div>
376
+ `;
377
+
378
+ exports[`for statements > renders a simple static array 1`] = `
379
+ <div>
380
+ <!---->
381
+ <div
382
+ class="Item 1"
383
+ >
384
+ Item 1
385
+ </div>
386
+ <div
387
+ class="Item 2"
388
+ >
389
+ Item 2
390
+ </div>
391
+ <div
392
+ class="Item 3"
393
+ >
394
+ Item 3
395
+ </div>
396
+ <!---->
397
+
398
+ </div>
399
+ `;
@@ -0,0 +1,5 @@
1
+ // This file is for tests that don't fit anywhere else
2
+
3
+ describe('etc', () => {
4
+ it.skip('this is here so the empty test suite does not fail');
5
+ });