ripple 0.2.182 → 0.2.183

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.
@@ -1,25 +1,58 @@
1
+ /** @import * as AST from 'estree' */
2
+ /** @import { Visitors } from '#compiler' */
3
+ /** @typedef {0 | 1} Direction */
4
+
1
5
  import { walk } from 'zimmerframe';
2
6
  import { is_element_dom_element } from '../../utils.js';
3
7
 
4
8
  const seen = new Set();
5
9
  const regex_backslash_and_following_character = /\\(.)/g;
10
+ /** @type {Direction} */
6
11
  const FORWARD = 0;
12
+ /** @type {Direction} */
7
13
  const BACKWARD = 1;
8
14
 
9
15
  // CSS selector constants
10
- const descendant_combinator = { name: ' ', type: 'Combinator' };
11
- const nesting_selector = {
12
- type: 'NestingSelector',
13
- name: '&',
14
- selectors: [],
15
- metadata: { scoped: false },
16
- };
17
- const any_selector = {
18
- type: 'RelativeSelector',
19
- selectors: [{ type: 'TypeSelector', name: '*' }],
20
- combinator: null,
21
- metadata: { scoped: false },
22
- };
16
+ /**
17
+ * @param {number} start
18
+ * @param {number} end
19
+ * @returns {AST.CSS.Combinator}
20
+ */
21
+ function create_descendant_combinator(start, end) {
22
+ return { name: ' ', type: 'Combinator', start, end };
23
+ }
24
+
25
+ /**
26
+ * @param {number} start
27
+ * @param {number} end
28
+ * @returns {AST.CSS.RelativeSelector}
29
+ */
30
+ function create_nesting_selector(start, end) {
31
+ return {
32
+ type: 'RelativeSelector',
33
+ selectors: [{ type: 'NestingSelector', name: '&', start, end }],
34
+ combinator: null,
35
+ metadata: { is_global: false, is_global_like: false, scoped: false },
36
+ start,
37
+ end,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * @param {number} start
43
+ * @param {number} end
44
+ * @returns {AST.CSS.RelativeSelector}
45
+ */
46
+ function create_any_selector(start, end) {
47
+ return {
48
+ type: 'RelativeSelector',
49
+ selectors: [{ type: 'TypeSelector', name: '*', start, end }],
50
+ combinator: null,
51
+ metadata: { is_global: false, is_global_like: false, scoped: false },
52
+ start,
53
+ end,
54
+ };
55
+ }
23
56
 
24
57
  // Whitelist for attribute selectors on specific elements
25
58
  const whitelist_attribute_selector = new Map([
@@ -72,6 +105,9 @@ const whitelist_attribute_selector = new Map([
72
105
  ['video', ['autoplay', 'controls', 'loop', 'muted', 'playsinline']],
73
106
  ]);
74
107
 
108
+ /**
109
+ * @param {AST.CSS.ComplexSelector} node
110
+ */
75
111
  function get_relative_selectors(node) {
76
112
  const selectors = truncate(node);
77
113
 
@@ -80,12 +116,15 @@ function get_relative_selectors(node) {
80
116
 
81
117
  // nesting could be inside pseudo classes like :is, :has or :where
82
118
  for (let selector of selectors) {
83
- walk(selector, null, {
84
- // @ts-ignore
85
- NestingSelector() {
86
- has_explicit_nesting_selector = true;
87
- },
88
- });
119
+ walk(
120
+ selector,
121
+ null,
122
+ /** @type {Visitors<AST.CSS.Node, null>} */ ({
123
+ NestingSelector() {
124
+ has_explicit_nesting_selector = true;
125
+ },
126
+ }),
127
+ );
89
128
 
90
129
  // if we found one we can break from the others
91
130
  if (has_explicit_nesting_selector) break;
@@ -95,17 +134,22 @@ function get_relative_selectors(node) {
95
134
  if (selectors[0].combinator === null) {
96
135
  selectors[0] = {
97
136
  ...selectors[0],
98
- combinator: descendant_combinator,
137
+ combinator: create_descendant_combinator(selectors[0].start, selectors[0].end),
99
138
  };
100
139
  }
101
140
 
102
- selectors.unshift(nesting_selector);
141
+ selectors.unshift(create_nesting_selector(selectors[0].start, selectors[0].end));
103
142
  }
104
143
  }
105
144
 
106
145
  return selectors;
107
146
  }
108
147
 
148
+ /**
149
+ *
150
+ * @param {AST.CSS.ComplexSelector} node
151
+ * @returns {AST.CSS.RelativeSelector[]}
152
+ */
109
153
  function truncate(node) {
110
154
  const i = node.children.findLastIndex(({ metadata, selectors }) => {
111
155
  const first = selectors[0];
@@ -133,6 +177,13 @@ function truncate(node) {
133
177
  });
134
178
  }
135
179
 
180
+ /**
181
+ * @param {AST.CSS.RelativeSelector[]} relative_selectors
182
+ * @param {AST.CSS.Rule} rule
183
+ * @param {AST.Element} element
184
+ * @param {Direction} direction
185
+ * @returns {boolean}
186
+ */
136
187
  function apply_selector(relative_selectors, rule, element, direction) {
137
188
  const rest_selectors = relative_selectors.slice();
138
189
  const relative_selector = direction === FORWARD ? rest_selectors.shift() : rest_selectors.pop();
@@ -153,7 +204,13 @@ function apply_selector(relative_selectors, rule, element, direction) {
153
204
  return matched;
154
205
  }
155
206
 
156
- function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
207
+ /**
208
+ * @param {AST.Element} node
209
+ * @param {boolean} adjacent_only
210
+ * @returns {AST.Element[]}
211
+ */
212
+ function get_ancestor_elements(node, adjacent_only) {
213
+ /** @type {AST.Element[]} */
157
214
  const ancestors = [];
158
215
 
159
216
  const path = node.metadata.path;
@@ -173,9 +230,20 @@ function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
173
230
  return ancestors;
174
231
  }
175
232
 
233
+ /**
234
+ * @param {AST.Element} node
235
+ * @param {boolean} adjacent_only
236
+ * @returns {AST.Element[]}
237
+ */
176
238
  function get_descendant_elements(node, adjacent_only) {
239
+ /** @type {AST.Element[]} */
177
240
  const descendants = [];
178
241
 
242
+ /**
243
+ * @param {AST.Node} current_node
244
+ * @param {number} depth
245
+ * @returns {void}
246
+ */
179
247
  function visit(current_node, depth = 0) {
180
248
  if (current_node.type === 'Element' && current_node !== node) {
181
249
  descendants.push(current_node);
@@ -183,21 +251,24 @@ function get_descendant_elements(node, adjacent_only) {
183
251
  }
184
252
 
185
253
  // Visit children based on Ripple's AST structure
186
- if (current_node.children) {
187
- for (const child of current_node.children) {
254
+ if (/** @type {AST.Element} */ (current_node).children) {
255
+ for (const child of /** @type {AST.Element} */ (current_node).children) {
188
256
  visit(child, depth + 1);
189
257
  }
190
258
  }
191
259
 
192
- if (current_node.body) {
193
- for (const child of current_node.body) {
260
+ if (/** @type {AST.Component} */ (current_node).body) {
261
+ for (const child of /** @type {AST.Component} */ (current_node).body) {
194
262
  visit(child, depth + 1);
195
263
  }
196
264
  }
197
265
 
198
266
  // For template nodes and text interpolations
199
- if (current_node.expression && typeof current_node.expression === 'object') {
200
- visit(current_node.expression, depth + 1);
267
+ if (
268
+ /** @type {AST.TextNode} */ (current_node).expression &&
269
+ typeof (/** @type {AST.TextNode} */ (current_node).expression) === 'object'
270
+ ) {
271
+ visit(/** @type {AST.TextNode} */ (current_node).expression, depth + 1);
201
272
  }
202
273
  }
203
274
 
@@ -208,18 +279,12 @@ function get_descendant_elements(node, adjacent_only) {
208
279
  }
209
280
  }
210
281
 
211
- if (node.body) {
212
- for (const child of node.body) {
213
- visit(child);
214
- }
215
- }
216
-
217
282
  return descendants;
218
283
  }
219
284
 
220
285
  /**
221
286
  * Check if an element can render dynamic content that might affect CSS matching
222
- * @param {any} element
287
+ * @param {AST.Node} element
223
288
  * @param {boolean} check_classes - Whether to check for dynamic class attributes
224
289
  * @returns {boolean}
225
290
  */
@@ -230,14 +295,14 @@ function can_render_dynamic_content(element, check_classes = false) {
230
295
 
231
296
  // Either a dynamic element or component (only can tell at runtime)
232
297
  // But dynamic elements should return false ideally
233
- if (element.id?.tracked) {
298
+ if (/** @type {AST.Element} */ (element).id.tracked) {
234
299
  return true;
235
300
  }
236
301
 
237
302
  // Check for dynamic class attributes if requested (for class-based selectors)
238
- if (check_classes && element.attributes) {
239
- for (const attr of element.attributes) {
240
- if (attr.type === 'Attribute' && attr.name?.name === 'class') {
303
+ if (check_classes && /** @type {AST.Element} */ (element).attributes) {
304
+ for (const attr of /** @type {AST.Element} */ (element).attributes) {
305
+ if (attr.type === 'Attribute' && attr.name.name === 'class') {
241
306
  // Check if class value is an expression (not a static string)
242
307
  if (attr.value && typeof attr.value === 'object') {
243
308
  // If it's a CallExpression or other dynamic value, it's dynamic
@@ -252,8 +317,15 @@ function can_render_dynamic_content(element, check_classes = false) {
252
317
  return false;
253
318
  }
254
319
 
320
+ /**
321
+ * @param {AST.Node} node
322
+ * @param {Direction} direction
323
+ * @param {boolean} adjacent_only
324
+ * @returns {Map<AST.Element, boolean>}
325
+ */
255
326
  function get_possible_element_siblings(node, direction, adjacent_only) {
256
327
  const siblings = new Map();
328
+ // Parent has to be an Element not a Component
257
329
  const parent = get_element_parent(node);
258
330
 
259
331
  if (!parent) {
@@ -261,7 +333,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
261
333
  }
262
334
 
263
335
  // Get the container that holds the siblings
264
- const container = parent.children || parent.body || [];
336
+ const container = parent.children || [];
265
337
  const node_index = container.indexOf(node);
266
338
 
267
339
  if (node_index === -1) return siblings;
@@ -292,7 +364,13 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
292
364
  }
293
365
  }
294
366
  // Stop at non-whitespace text nodes for adjacent selectors
295
- else if (adjacent_only && sibling.type === 'Text' && sibling.value?.trim()) {
367
+ else if (
368
+ adjacent_only &&
369
+ sibling.type === 'Text' &&
370
+ sibling.expression.type === 'Literal' &&
371
+ typeof sibling.expression.value === 'string' &&
372
+ sibling.expression.value.trim()
373
+ ) {
296
374
  break;
297
375
  }
298
376
  }
@@ -300,6 +378,14 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
300
378
  return siblings;
301
379
  }
302
380
 
381
+ /**
382
+ * @param {AST.CSS.RelativeSelector} relative_selector
383
+ * @param {AST.CSS.RelativeSelector[]} rest_selectors
384
+ * @param {AST.CSS.Rule} rule
385
+ * @param {AST.Element} node
386
+ * @param {Direction} direction
387
+ * @returns {boolean}
388
+ */
303
389
  function apply_combinator(relative_selector, rest_selectors, rule, node, direction) {
304
390
  const combinator =
305
391
  direction == FORWARD ? rest_selectors[0]?.combinator : relative_selector.combinator;
@@ -358,7 +444,7 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
358
444
  // Check if there are any elements after this component that could match the remaining selectors
359
445
  const parent = get_element_parent(node);
360
446
  if (parent) {
361
- const container = parent.children || parent.body || [];
447
+ const container = parent.children || [];
362
448
  const component_index = container.indexOf(possible_sibling);
363
449
 
364
450
  // For adjacent combinator, only check immediate next element
@@ -411,7 +497,10 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
411
497
  return true;
412
498
  }
413
499
  }
414
-
500
+ /**
501
+ * @param {AST.Node} node
502
+ * @returns {AST.Element | null}
503
+ */
415
504
  function get_element_parent(node) {
416
505
  // Check if metadata and path exist
417
506
  if (!node.metadata || !node.metadata.path) {
@@ -434,7 +523,8 @@ function get_element_parent(node) {
434
523
 
435
524
  /**
436
525
  * `true` if is a pseudo class that cannot be or is not scoped
437
- * @param {Compiler.AST.CSS.SimpleSelector} selector
526
+ * @param {AST.CSS.SimpleSelector} selector
527
+ * @returns {boolean}
438
528
  */
439
529
  function is_unscoped_pseudo_class(selector) {
440
530
  return (
@@ -457,7 +547,7 @@ function is_unscoped_pseudo_class(selector) {
457
547
 
458
548
  /**
459
549
  * True if is `:global(...)` or `:global` and no pseudo class that is scoped.
460
- * @param {Compiler.AST.CSS.RelativeSelector} relative_selector
550
+ * @param {AST.CSS.RelativeSelector} relative_selector
461
551
  */
462
552
  function is_global_simple(relative_selector) {
463
553
  const first = relative_selector.selectors[0];
@@ -475,6 +565,11 @@ function is_global_simple(relative_selector) {
475
565
  );
476
566
  }
477
567
 
568
+ /**
569
+ * @param {AST.CSS.RelativeSelector} selector
570
+ * @param {AST.CSS.Rule} rule
571
+ * @return {boolean}
572
+ */
478
573
  function is_global(selector, rule) {
479
574
  if (selector.metadata.is_global || selector.metadata.is_global_like) {
480
575
  return true;
@@ -483,7 +578,7 @@ function is_global(selector, rule) {
483
578
  let explicitly_global = false;
484
579
 
485
580
  for (const s of selector.selectors) {
486
- /** @type {Compiler.AST.CSS.SelectorList | null} */
581
+ /** @type {AST.CSS.SelectorList | null} */
487
582
  let selector_list = null;
488
583
  let can_be_global = false;
489
584
  let owner = rule;
@@ -497,7 +592,7 @@ function is_global(selector, rule) {
497
592
  }
498
593
 
499
594
  if (s.type === 'NestingSelector') {
500
- owner = /** @type {Compiler.AST.CSS.Rule} */ (rule.metadata.parent_rule);
595
+ owner = /** @type {AST.CSS.Rule} */ (rule.metadata.parent_rule);
501
596
  selector_list = owner.prelude;
502
597
  }
503
598
 
@@ -516,10 +611,21 @@ function is_global(selector, rule) {
516
611
  return explicitly_global || selector.selectors.length === 0;
517
612
  }
518
613
 
614
+ /**
615
+ * @param {AST.Attribute} attribute
616
+ * @returns {attribute is AST.Attribute & { value: AST.Literal & { value: string } }}
617
+ */
519
618
  function is_text_attribute(attribute) {
520
- return attribute.value.type === 'Literal';
619
+ return attribute.value?.type === 'Literal' && typeof attribute.value.value === 'string';
521
620
  }
522
621
 
622
+ /**
623
+ * @param {string | null} operator
624
+ * @param {string} expected_value
625
+ * @param {boolean} case_insensitive
626
+ * @param {string} value
627
+ * @returns {boolean}
628
+ */
523
629
  function test_attribute(operator, expected_value, case_insensitive, value) {
524
630
  if (case_insensitive) {
525
631
  expected_value = expected_value.toLowerCase();
@@ -543,6 +649,14 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
543
649
  }
544
650
  }
545
651
 
652
+ /**
653
+ * @param {AST.Element} node
654
+ * @param {string} name
655
+ * @param {string | null} expected_value
656
+ * @param {string | null} operator
657
+ * @param {boolean} case_insensitive
658
+ * @returns {boolean}
659
+ */
546
660
  function attribute_matches(node, name, expected_value, operator, case_insensitive) {
547
661
  for (const attribute of node.attributes) {
548
662
  if (attribute.type === 'SpreadAttribute') return true;
@@ -564,6 +678,10 @@ function attribute_matches(node, name, expected_value, operator, case_insensitiv
564
678
  return false;
565
679
  }
566
680
 
681
+ /**
682
+ * @param {AST.CSS.RelativeSelector} relative_selector
683
+ * @returns {boolean}
684
+ */
567
685
  function is_outer_global(relative_selector) {
568
686
  const first = relative_selector.selectors[0];
569
687
 
@@ -581,6 +699,13 @@ function is_outer_global(relative_selector) {
581
699
  );
582
700
  }
583
701
 
702
+ /**
703
+ * @param {AST.CSS.RelativeSelector} relative_selector
704
+ * @param {AST.CSS.Rule} rule
705
+ * @param {AST.Element} element
706
+ * @param {Direction} direction
707
+ * @return {boolean}
708
+ */
584
709
  function relative_selector_might_apply_to_node(relative_selector, rule, element, direction) {
585
710
  // Sort :has(...) selectors in one bucket and everything else into another
586
711
  const has_selectors = [];
@@ -617,8 +742,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
617
742
  // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
618
743
  // selector in a way that is similar to ancestor matching. In a sense, we're treating `.x:has(.y)` as `.x .y`.
619
744
  for (const has_selector of has_selectors) {
620
- const complex_selectors = /** @type {Compiler.AST.CSS.SelectorList} */ (has_selector.args)
621
- .children;
745
+ const complex_selectors = /** @type {AST.CSS.SelectorList} */ (has_selector.args).children;
622
746
  let matched = false;
623
747
 
624
748
  for (const complex_selector of complex_selectors) {
@@ -642,8 +766,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
642
766
  }
643
767
 
644
768
  const selector_excluding_self = [
645
- any_selector,
646
- first.combinator ? first : { ...first, combinator: descendant_combinator },
769
+ create_any_selector(first.start, first.end),
770
+ first.combinator
771
+ ? first
772
+ : { ...first, combinator: create_descendant_combinator(first.start, first.end) },
647
773
  ...rest,
648
774
  ];
649
775
  if (apply_selector(selector_excluding_self, rule, element, FORWARD)) {
@@ -701,6 +827,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
701
827
  selector.metadata.scoped = true;
702
828
  }
703
829
 
830
+ /** @type {AST.Element | null} */
704
831
  let el = element;
705
832
  while (el) {
706
833
  el.metadata.scoped = true;
@@ -797,7 +924,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
797
924
  case 'NestingSelector': {
798
925
  let matched = false;
799
926
 
800
- const parent = /** @type {Compiler.AST.CSS.Rule} */ (rule.metadata.parent_rule);
927
+ const parent = /** @type {AST.CSS.Rule} */ (rule.metadata.parent_rule);
801
928
 
802
929
  for (const complex_selector of parent.prelude.children) {
803
930
  if (
@@ -822,7 +949,10 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
822
949
  return true;
823
950
  }
824
951
 
825
- // Utility functions for parsing CSS values
952
+ /**
953
+ * @param {string} str
954
+ * @returns {string}
955
+ */
826
956
  function unquote(str) {
827
957
  if (
828
958
  (str[0] === '"' && str[str.length - 1] === '"') ||
@@ -833,6 +963,10 @@ function unquote(str) {
833
963
  return str;
834
964
  }
835
965
 
966
+ /**
967
+ * @param {AST.CSS.Rule} rule
968
+ * @returns {AST.CSS.Rule[]}
969
+ */
836
970
  function get_parent_rules(rule) {
837
971
  const rules = [rule];
838
972
  let current = rule;
@@ -847,7 +981,7 @@ function get_parent_rules(rule) {
847
981
 
848
982
  /**
849
983
  * Check if a CSS rule contains animation or animation-name properties
850
- * @param {Compiler.AST.CSS.Rule} rule
984
+ * @param {AST.CSS.Rule} rule
851
985
  * @returns {boolean}
852
986
  */
853
987
  function rule_has_animation(rule) {
@@ -865,8 +999,14 @@ function rule_has_animation(rule) {
865
999
  return false;
866
1000
  }
867
1001
 
1002
+ /**
1003
+ * @param {AST.CSS.StyleSheet} css
1004
+ * @param {AST.Element} element
1005
+ * @return {void}
1006
+ */
868
1007
  export function prune_css(css, element) {
869
- walk(css, null, {
1008
+ /** @type {Visitors<AST.CSS.Node, null>} */
1009
+ const visitors = {
870
1010
  Rule(node, context) {
871
1011
  if (node.metadata.is_global_block) {
872
1012
  context.visit(node.prelude);
@@ -879,7 +1019,7 @@ export function prune_css(css, element) {
879
1019
 
880
1020
  seen.clear();
881
1021
 
882
- const rule = /** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule);
1022
+ const rule = /** @type {AST.CSS.Rule} */ (node.metadata.rule);
883
1023
 
884
1024
  if (apply_selector(selectors, rule, element, BACKWARD) || rule_has_animation(rule)) {
885
1025
  node.metadata.used = true;
@@ -899,5 +1039,7 @@ export function prune_css(css, element) {
899
1039
  context.next();
900
1040
  }
901
1041
  },
902
- });
1042
+ };
1043
+
1044
+ walk(css, null, visitors);
903
1045
  }
@@ -1,3 +1,6 @@
1
+ /** @import * as AST from 'estree' */
2
+ /** @import {AnalysisContext} from '#compiler' */
3
+
1
4
  import { error } from '../../errors.js';
2
5
 
3
6
  const invalid_nestings = {
@@ -113,7 +116,7 @@ const invalid_nestings = {
113
116
  };
114
117
 
115
118
  /**
116
- * @param {any} element
119
+ * @param {AST.Element} element
117
120
  * @returns {string | null}
118
121
  */
119
122
  function get_element_tag(element) {
@@ -121,11 +124,10 @@ function get_element_tag(element) {
121
124
  }
122
125
 
123
126
  /**
124
- * @param {any} element
125
- * @param {any} state
126
- * @param {any} context
127
+ * @param {AST.Element} element
128
+ * @param {AnalysisContext} context
127
129
  */
128
- export function validate_nesting(element, state, context) {
130
+ export function validate_nesting(element, context) {
129
131
  const tag = get_element_tag(element);
130
132
 
131
133
  if (tag === null) {
@@ -146,8 +148,8 @@ export function validate_nesting(element, state, context) {
146
148
  if (validation_set.has(tag)) {
147
149
  error(
148
150
  `Invalid HTML nesting: <${tag}> cannot be a descendant of <${parent_tag}>.`,
149
- state.analysis.module.filename,
150
- context,
151
+ context.state.analysis.module.filename,
152
+ element,
151
153
  );
152
154
  }
153
155
  }