ripple 0.3.9 → 0.3.11

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 (70) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +2 -2
  3. package/src/compiler/errors.js +1 -1
  4. package/src/compiler/index.d.ts +3 -1
  5. package/src/compiler/phases/1-parse/index.js +195 -23
  6. package/src/compiler/phases/2-analyze/index.js +266 -108
  7. package/src/compiler/phases/2-analyze/prune.js +13 -5
  8. package/src/compiler/phases/3-transform/client/index.js +304 -80
  9. package/src/compiler/phases/3-transform/server/index.js +108 -43
  10. package/src/compiler/types/index.d.ts +28 -3
  11. package/src/compiler/types/parse.d.ts +3 -1
  12. package/src/compiler/utils.js +275 -1
  13. package/src/runtime/element.js +39 -0
  14. package/src/runtime/index-client.js +14 -4
  15. package/src/runtime/internal/client/composite.js +10 -6
  16. package/src/runtime/internal/client/expression.js +280 -0
  17. package/src/runtime/internal/client/index.js +4 -0
  18. package/src/runtime/internal/client/portal.js +12 -6
  19. package/src/runtime/internal/server/index.js +26 -1
  20. package/src/utils/builders.js +30 -0
  21. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +1 -0
  22. package/tests/client/basic/basic.components.test.ripple +85 -87
  23. package/tests/client/basic/basic.errors.test.ripple +4 -8
  24. package/tests/client/basic/basic.rendering.test.ripple +27 -10
  25. package/tests/client/capture-error.js +12 -0
  26. package/tests/client/compiler/compiler.basic.test.ripple +76 -6
  27. package/tests/client/composite/composite.props.test.ripple +1 -3
  28. package/tests/client/composite/composite.render.test.ripple +91 -13
  29. package/tests/client/css/global-additional-cases.test.ripple +3 -3
  30. package/tests/client/return.test.ripple +101 -0
  31. package/tests/client/svg.test.ripple +4 -4
  32. package/tests/client/tsx.test.ripple +486 -0
  33. package/tests/hydration/basic.test.js +23 -0
  34. package/tests/hydration/compiled/client/basic.js +111 -75
  35. package/tests/hydration/compiled/client/composite.js +81 -46
  36. package/tests/hydration/compiled/client/events.js +18 -63
  37. package/tests/hydration/compiled/client/for.js +90 -183
  38. package/tests/hydration/compiled/client/head.js +10 -25
  39. package/tests/hydration/compiled/client/hmr.js +10 -13
  40. package/tests/hydration/compiled/client/html.js +251 -380
  41. package/tests/hydration/compiled/client/if-children.js +35 -45
  42. package/tests/hydration/compiled/client/if.js +2 -2
  43. package/tests/hydration/compiled/client/mixed-control-flow.js +24 -72
  44. package/tests/hydration/compiled/client/nested-control-flow.js +115 -391
  45. package/tests/hydration/compiled/client/portal.js +8 -20
  46. package/tests/hydration/compiled/client/reactivity.js +14 -47
  47. package/tests/hydration/compiled/client/return.js +2 -5
  48. package/tests/hydration/compiled/client/try.js +4 -4
  49. package/tests/hydration/compiled/server/basic.js +64 -31
  50. package/tests/hydration/compiled/server/composite.js +62 -29
  51. package/tests/hydration/compiled/server/hmr.js +24 -37
  52. package/tests/hydration/compiled/server/html.js +472 -611
  53. package/tests/hydration/compiled/server/if-children.js +77 -103
  54. package/tests/hydration/compiled/server/portal.js +8 -8
  55. package/tests/hydration/components/basic.ripple +15 -5
  56. package/tests/hydration/components/composite.ripple +13 -1
  57. package/tests/hydration/components/hmr.ripple +1 -3
  58. package/tests/hydration/components/html.ripple +13 -35
  59. package/tests/hydration/components/if-children.ripple +4 -8
  60. package/tests/hydration/composite.test.js +11 -0
  61. package/tests/server/basic.attributes.test.ripple +50 -0
  62. package/tests/server/basic.components.test.ripple +22 -28
  63. package/tests/server/basic.test.ripple +12 -0
  64. package/tests/server/compiler.test.ripple +25 -8
  65. package/tests/server/composite.props.test.ripple +1 -3
  66. package/tests/server/style-identifier.test.ripple +2 -4
  67. package/tests/utils/compiler-compat-config.test.js +38 -0
  68. package/tests/utils/vite-plugin-config.test.js +113 -0
  69. package/tsconfig.typecheck.json +2 -1
  70. package/types/index.d.ts +8 -11
@@ -22,15 +22,24 @@ import { COMMENT_NODE, HYDRATION_START } from '../constants.js';
22
22
  // Re-export JSX runtime functions for jsxImportSource: "ripple"
23
23
  export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
24
24
 
25
+ /**
26
+ * @returns {CompatOptions | undefined}
27
+ */
28
+ function get_default_compat() {
29
+ return /** @type {typeof globalThis & { __RIPPLE_COMPAT__?: CompatOptions }} */ (globalThis)
30
+ .__RIPPLE_COMPAT__;
31
+ }
32
+
25
33
  /**
26
34
  * @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
27
- * @param {{ props?: Record<string, any>, target: HTMLElement, compat?: CompatOptions }} options
35
+ * @param {{ props?: Record<string, any>, target: HTMLElement }} options
28
36
  * @returns {() => void}
29
37
  */
30
38
  export function mount(component, options) {
31
39
  init_operations();
32
40
  remove_ssr_css();
33
41
 
42
+ const compat = get_default_compat();
34
43
  const props = options.props || {};
35
44
  const target = options.target;
36
45
  const anchor = create_anchor();
@@ -46,7 +55,7 @@ export function mount(component, options) {
46
55
 
47
56
  const _root = root(() => {
48
57
  component(anchor, props, active_block);
49
- }, options.compat);
58
+ }, compat);
50
59
 
51
60
  return () => {
52
61
  cleanup_events();
@@ -56,13 +65,14 @@ export function mount(component, options) {
56
65
 
57
66
  /**
58
67
  * @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
59
- * @param {{ props?: Record<string, any>, target: HTMLElement, compat?: CompatOptions }} options
68
+ * @param {{ props?: Record<string, any>, target: HTMLElement }} options
60
69
  * @returns {() => void}
61
70
  */
62
71
  export function hydrate(component, options) {
63
72
  init_operations();
64
73
  remove_ssr_css();
65
74
 
75
+ const compat = get_default_compat();
66
76
  const props = options.props || {};
67
77
  const target = options.target;
68
78
  const was_hydrating = hydrating;
@@ -86,7 +96,7 @@ export function hydrate(component, options) {
86
96
 
87
97
  _root = root(() => {
88
98
  component(/** @type {Comment} */ (anchor), props, active_block);
89
- }, options.compat);
99
+ }, compat);
90
100
  } catch (e) {
91
101
  throw e;
92
102
  } finally {
@@ -5,6 +5,7 @@ import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.j
5
5
  import { hydrate_next, hydrating } from './hydration.js';
6
6
  import { active_block, active_namespace, get, with_ns } from './runtime.js';
7
7
  import { top_element_to_ns } from './utils.js';
8
+ import { is_ripple_element } from '../../element.js';
8
9
 
9
10
  /**
10
11
  * @typedef {((anchor: Node, props: Record<string, any>, block: Block | null) => void)} ComponentFunction
@@ -45,13 +46,14 @@ export function composite(get_component, node, props) {
45
46
  });
46
47
  } else if (component != null) {
47
48
  // Custom element - only create if component is not null/undefined
49
+ const ns = top_element_to_ns(component, active_namespace);
48
50
  var run = () => {
49
51
  var block = /** @type {Block} */ (active_block);
50
52
 
51
53
  var element =
52
- active_namespace !== DEFAULT_NAMESPACE
54
+ ns !== DEFAULT_NAMESPACE
53
55
  ? document.createElementNS(
54
- NAMESPACE_URI[active_namespace],
56
+ NAMESPACE_URI[ns],
55
57
  /** @type {keyof HTMLElementTagNameMap} */ (component),
56
58
  )
57
59
  : document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
@@ -67,16 +69,18 @@ export function composite(get_component, node, props) {
67
69
 
68
70
  render_spread(element, () => props || {});
69
71
 
70
- if (typeof props?.children === 'function') {
72
+ if (is_ripple_element(props?.children)) {
71
73
  var child_anchor = document.createComment('');
72
74
  element.appendChild(child_anchor);
73
75
 
74
- props?.children?.(child_anchor, {}, block);
76
+ if (ns !== DEFAULT_NAMESPACE) {
77
+ with_ns(ns, () => props.children.render(child_anchor, block));
78
+ } else {
79
+ props.children.render(child_anchor, block);
80
+ }
75
81
  }
76
82
  };
77
83
 
78
- const ns = top_element_to_ns(component, active_namespace);
79
-
80
84
  if (ns !== active_namespace) {
81
85
  // support top-level dynamic element svg/math <@tag />
82
86
  b = branch(() => with_ns(ns, run));
@@ -0,0 +1,280 @@
1
+ /** @import { Block } from '#client' */
2
+
3
+ import { branch, destroy_block, render } from './blocks.js';
4
+ import { BRANCH_BLOCK, UNINITIALIZED } from './constants.js';
5
+ import { create_text, get_next_sibling } from './operations.js';
6
+ import { active_block } from './runtime.js';
7
+ import { hydrating, set_hydrate_node } from './hydration.js';
8
+ import { COMMENT_NODE, HYDRATION_END, HYDRATION_START, TEXT_NODE } from '../../../constants.js';
9
+ import { is_ripple_element } from '../../element.js';
10
+
11
+ /**
12
+ * Finds the nearest enclosing BRANCH_BLOCK in the block hierarchy.
13
+ * @param {Block | null} block
14
+ * @returns {Block | null}
15
+ */
16
+ function find_enclosing_branch(block) {
17
+ while (block !== null) {
18
+ if ((block.f & BRANCH_BLOCK) !== 0) {
19
+ return block;
20
+ }
21
+ block = block.p;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * @param {Node} node
28
+ * @param {() => any} get_value
29
+ * @returns {void}
30
+ */
31
+ export function expression(node, get_value) {
32
+ var anchor = /** @type {ChildNode} */ (node);
33
+ /** @type {Block | null} */
34
+ var child_block = null;
35
+ /** @type {Comment | null} */
36
+ var end = null;
37
+ /** @type {Text | null} */
38
+ var text = null;
39
+ /** @type {string | import('../../element.js').RippleElement | typeof UNINITIALIZED} */
40
+ var value = UNINITIALIZED;
41
+ var is_element = false;
42
+ var initialized = false;
43
+ /** @type {Block | null} */
44
+ var modified_parent_branch = null;
45
+ /** @type {Node | null} */
46
+ var original_parent_start = null;
47
+
48
+ render(() => {
49
+ var next_value = get_value();
50
+ var next_is_element = is_ripple_element(next_value);
51
+ var is_hydration_marker = hydrating && anchor.nodeType === COMMENT_NODE;
52
+
53
+ if (is_hydration_marker) {
54
+ end ??= ensure_expression_end(anchor);
55
+ }
56
+
57
+ if (next_is_element) {
58
+ if (initialized && is_element && value === next_value) {
59
+ if (end !== null) {
60
+ advance_hydration(end);
61
+ }
62
+ return;
63
+ }
64
+
65
+ if (anchor.nodeType === TEXT_NODE) {
66
+ /** @type {Text} */ (anchor).nodeValue = '';
67
+ } else if (text !== null) {
68
+ text.remove();
69
+ text = null;
70
+ }
71
+
72
+ if (child_block !== null) {
73
+ destroy_block(child_block);
74
+ child_block = null;
75
+ // Restore parent branch's start since we may update it again below
76
+ if (modified_parent_branch !== null && modified_parent_branch.s !== null) {
77
+ modified_parent_branch.s.start = original_parent_start;
78
+ modified_parent_branch = null;
79
+ original_parent_start = null;
80
+ }
81
+ }
82
+
83
+ if (end !== null && (initialized || !hydrating)) {
84
+ clear_expression_range(anchor, end);
85
+ }
86
+
87
+ if (is_hydration_marker) {
88
+ set_hydrate_node(get_next_sibling(anchor) ?? end);
89
+ }
90
+
91
+ // Find the enclosing branch block BEFORE creating child_block
92
+ // so we can update its s.start to include content inserted before anchor
93
+ var parent_branch = find_enclosing_branch(active_block);
94
+
95
+ child_block = branch(() => {
96
+ var block = active_block;
97
+ next_value.render(end ?? anchor, block);
98
+ });
99
+
100
+ // Update parent branch's s.start to include content inserted before anchor.
101
+ // This ensures that when the parent branch is destroyed, the full DOM range
102
+ // (including RippleElement content) is removed.
103
+ if (
104
+ parent_branch !== null &&
105
+ parent_branch.s !== null &&
106
+ child_block.s !== null &&
107
+ child_block.s.start !== null
108
+ ) {
109
+ // The child inserted content before the anchor. Update parent's start
110
+ // to encompass this content.
111
+ var child_start = child_block.s.start;
112
+ var parent_start = parent_branch.s.start;
113
+
114
+ // If parent's start is the anchor (or comes after child's start),
115
+ // update it to include the child's content
116
+ if (parent_start === anchor || parent_start === end) {
117
+ // Save original so we can restore it when switching to non-RippleElement
118
+ if (modified_parent_branch === null) {
119
+ modified_parent_branch = parent_branch;
120
+ original_parent_start = parent_start;
121
+ }
122
+ parent_branch.s.start = child_start;
123
+ }
124
+ }
125
+
126
+ value = next_value;
127
+ is_element = true;
128
+ initialized = true;
129
+ if (end !== null) {
130
+ advance_hydration(end);
131
+ }
132
+ return;
133
+ }
134
+
135
+ var next_text = (next_value ?? '') + '';
136
+
137
+ if (initialized && !is_element && value === next_text) {
138
+ if (end !== null) {
139
+ advance_hydration(end);
140
+ }
141
+ return;
142
+ }
143
+
144
+ if (child_block !== null) {
145
+ destroy_block(child_block);
146
+ child_block = null;
147
+ // Restore parent branch's start to original value since the child's DOM nodes
148
+ // have been removed and the old start reference would be stale
149
+ if (modified_parent_branch !== null && modified_parent_branch.s !== null) {
150
+ modified_parent_branch.s.start = original_parent_start;
151
+ modified_parent_branch = null;
152
+ original_parent_start = null;
153
+ }
154
+ }
155
+
156
+ if (is_hydration_marker) {
157
+ text = get_hydrated_text(anchor, /** @type {Comment} */ (end));
158
+
159
+ if (next_text === '') {
160
+ if (text !== null) {
161
+ text.remove();
162
+ text = null;
163
+ }
164
+ } else if (text === null) {
165
+ text = create_text(next_text);
166
+ /** @type {Comment} */ (end).before(text);
167
+ } else if (text.nodeValue !== next_text) {
168
+ text.nodeValue = next_text;
169
+ }
170
+ } else if (anchor.nodeType === COMMENT_NODE) {
171
+ if (next_text === '') {
172
+ if (text !== null) {
173
+ text.remove();
174
+ text = null;
175
+ }
176
+ } else if (text === null) {
177
+ text = create_text(next_text);
178
+ (end ?? anchor).before(text);
179
+ } else if (text.nodeValue !== next_text) {
180
+ text.nodeValue = next_text;
181
+ }
182
+ } else if (anchor.nodeType === TEXT_NODE) {
183
+ /** @type {Text} */ (anchor).nodeValue = next_text;
184
+ }
185
+
186
+ value = next_text;
187
+ is_element = false;
188
+ initialized = true;
189
+ if (end !== null) {
190
+ advance_hydration(end);
191
+ }
192
+ });
193
+ }
194
+
195
+ /**
196
+ * @param {Node} anchor
197
+ * @returns {Comment}
198
+ */
199
+ function ensure_expression_end(anchor) {
200
+ if (hydrating) {
201
+ /** @type {Node | null} */
202
+ var current = get_next_sibling(anchor);
203
+ var depth = 0;
204
+
205
+ while (current !== null) {
206
+ if (current.nodeType === COMMENT_NODE) {
207
+ var data = /** @type {Comment} */ (current).data;
208
+
209
+ if (data === HYDRATION_START) {
210
+ depth += 1;
211
+ } else if (data === HYDRATION_END) {
212
+ if (depth === 0) {
213
+ return /** @type {Comment} */ (current);
214
+ }
215
+
216
+ depth -= 1;
217
+ }
218
+ }
219
+
220
+ current = get_next_sibling(current);
221
+ }
222
+
223
+ throw new Error('Hydration mismatch: expected end marker for expression block');
224
+ }
225
+
226
+ var end = document.createComment(HYDRATION_END);
227
+ /** @type {ChildNode} */ (anchor).after(end);
228
+ return end;
229
+ }
230
+
231
+ /**
232
+ * @param {Node} anchor
233
+ * @param {Node} end
234
+ * @returns {Text | null}
235
+ */
236
+ function get_hydrated_text(anchor, end) {
237
+ var first = get_next_sibling(anchor);
238
+
239
+ if (first === end) {
240
+ return null;
241
+ }
242
+
243
+ if (first?.nodeType === TEXT_NODE && get_next_sibling(first) === end) {
244
+ return /** @type {Text} */ (first);
245
+ }
246
+
247
+ clear_expression_range(anchor, end);
248
+ return null;
249
+ }
250
+
251
+ /**
252
+ * @param {Node} anchor
253
+ * @param {Node} end
254
+ * @returns {void}
255
+ */
256
+ function clear_expression_range(anchor, end) {
257
+ var current = get_next_sibling(anchor);
258
+
259
+ while (current !== null && current !== end) {
260
+ var next = get_next_sibling(current);
261
+ /** @type {ChildNode} */ (current).remove();
262
+ current = next;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * @param {Comment} end
268
+ * @returns {void}
269
+ */
270
+ function advance_hydration(end) {
271
+ if (!hydrating) {
272
+ return;
273
+ }
274
+
275
+ var next = get_next_sibling(end);
276
+
277
+ if (next !== null) {
278
+ set_hydrate_node(next);
279
+ }
280
+ }
@@ -105,6 +105,8 @@ export { script } from './script.js';
105
105
 
106
106
  export { html } from './html.js';
107
107
 
108
+ export { expression } from './expression.js';
109
+
108
110
  export { rpc } from './rpc.js';
109
111
 
110
112
  export { tsx_compat } from './compat.js';
@@ -114,3 +116,5 @@ export { TRY_BLOCK, HMR } from './constants.js';
114
116
  export { hmr } from './hmr.js';
115
117
 
116
118
  export { pop, next } from './hydration.js';
119
+
120
+ export { ripple_element, normalize_children } from '../../element.js';
@@ -12,10 +12,11 @@ import {
12
12
  set_hydrating,
13
13
  set_hydrate_node,
14
14
  } from './hydration.js';
15
+ import { is_ripple_element } from '../../element.js';
15
16
 
16
17
  /**
17
18
  * @param {any} _
18
- * @param {{ target: Element, children: (anchor: Node, props: {}, block: Block) => void }} props
19
+ * @param {{ target: Element, children: import('../../element.js').RippleElement }} props
19
20
  * @returns {void}
20
21
  */
21
22
  export function Portal(_, props) {
@@ -26,7 +27,7 @@ export function Portal(_, props) {
26
27
 
27
28
  /** @type {Element | symbol} */
28
29
  let target = UNINITIALIZED;
29
- /** @type {((anchor: Node, props: {}, block: Block) => void) | symbol} */
30
+ /** @type {import('../../element.js').RippleElement | symbol} */
30
31
  let children = UNINITIALIZED;
31
32
  /** @type {Block | null} */
32
33
  var b = null;
@@ -44,8 +45,13 @@ export function Portal(_, props) {
44
45
 
45
46
  try {
46
47
  render(() => {
47
- if (target === (target = props.target)) return;
48
- if (children === (children = props.children)) return;
48
+ const next_target = props.target;
49
+ const next_children = props.children;
50
+
51
+ if (target === next_target && children === next_children) return;
52
+
53
+ target = next_target;
54
+ children = next_children;
49
55
 
50
56
  if (b !== null) {
51
57
  destroy_block(b);
@@ -65,8 +71,8 @@ export function Portal(_, props) {
65
71
  var block = /** @type {Block} */ (active_block);
66
72
 
67
73
  b = branch(() => {
68
- if (typeof children === 'function') {
69
- children(/** @type {Text} */ (anchor), {}, block);
74
+ if (is_ripple_element(children)) {
75
+ children.render(/** @type {Text} */ (anchor), block);
70
76
  }
71
77
  });
72
78
 
@@ -17,6 +17,7 @@ import { is_boolean_attribute } from '../../../compiler/utils.js';
17
17
  import { clsx } from 'clsx';
18
18
  import { normalize_css_property_name } from '../../../utils/normalize_css_property_name.js';
19
19
  import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../constants.js';
20
+ import { is_ripple_element, normalize_children, ripple_element } from '../../element.js';
20
21
  import {
21
22
  is_tag_valid_with_parent,
22
23
  is_tag_valid_with_ancestor,
@@ -27,6 +28,30 @@ export { register_component_css as register_css } from './css-registry.js';
27
28
  export { hash } from '../../../utils/hashing.js';
28
29
  export { context } from './context.js';
29
30
  export { array_slice };
31
+ export { ripple_element, normalize_children };
32
+
33
+ /**
34
+ * @param {Output} output
35
+ * @param {any} value
36
+ * @returns {void}
37
+ */
38
+ export function render_expression(output, value) {
39
+ output.push(BLOCK_OPEN);
40
+
41
+ if (is_ripple_element(value)) {
42
+ var result = value.render(output, {});
43
+
44
+ if (result && typeof result.then === 'function') {
45
+ return result.then(() => {
46
+ output.push(BLOCK_CLOSE);
47
+ });
48
+ }
49
+ } else {
50
+ output.push(escape(value ?? ''));
51
+ }
52
+
53
+ output.push(BLOCK_CLOSE);
54
+ }
30
55
 
31
56
  /** @type {null | Component} */
32
57
  export let active_component = null;
@@ -596,7 +621,7 @@ export function spread_attrs(attrs, css_hash) {
596
621
  for (name in attrs) {
597
622
  var value = attrs[name];
598
623
 
599
- if (typeof value === 'function') continue;
624
+ if (name === 'children' || typeof value === 'function' || is_ripple_element(value)) continue;
600
625
 
601
626
  if (is_ripple_object(value)) {
602
627
  value = get(value);
@@ -513,6 +513,36 @@ export function ts_intersection_type(types, loc_info) {
513
513
  return set_location(node, loc_info);
514
514
  }
515
515
 
516
+ /**
517
+ * @param {'string' | 'number' | 'boolean' | 'any' | 'void' | 'null' | 'undefined' | 'never' | 'unknown' | 'bigint' | 'symbol' | 'object'} keyword
518
+ * @param {AST.NodeWithLocation} [loc_info]
519
+ * @returns {AST.TypeNode}
520
+ */
521
+ export function ts_keyword_type(keyword, loc_info) {
522
+ /** @type {Record<string, string>} */
523
+ const keyword_to_type = {
524
+ string: 'TSStringKeyword',
525
+ number: 'TSNumberKeyword',
526
+ boolean: 'TSBooleanKeyword',
527
+ any: 'TSAnyKeyword',
528
+ void: 'TSVoidKeyword',
529
+ null: 'TSNullKeyword',
530
+ undefined: 'TSUndefinedKeyword',
531
+ never: 'TSNeverKeyword',
532
+ unknown: 'TSUnknownKeyword',
533
+ bigint: 'TSBigIntKeyword',
534
+ symbol: 'TSSymbolKeyword',
535
+ object: 'TSObjectKeyword',
536
+ };
537
+
538
+ const node = /** @type {AST.TypeNode} */ ({
539
+ type: keyword_to_type[keyword],
540
+ metadata: { path: [] },
541
+ });
542
+
543
+ return set_location(node, loc_info);
544
+ }
545
+
516
546
  /**
517
547
  * @param {AST.Node} type_annotation
518
548
  * @param {AST.NodeWithLocation} [loc_info]
@@ -52,6 +52,7 @@ exports[`basic client > rendering & text > should handle lexical scopes correctl
52
52
  <div>
53
53
  <section>
54
54
  Nested scope variable
55
+ <!---->
55
56
  </section>
56
57
 
57
58
  </div>