ripple 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +56 -24
  3. package/src/ai.js +292 -0
  4. package/src/compiler/errors.js +26 -0
  5. package/src/compiler/index.js +26 -0
  6. package/src/compiler/phases/1-parse/index.js +543 -0
  7. package/src/compiler/phases/1-parse/style.js +566 -0
  8. package/src/compiler/phases/2-analyze/index.js +509 -0
  9. package/src/compiler/phases/2-analyze/prune.js +572 -0
  10. package/src/compiler/phases/3-transform/index.js +1572 -0
  11. package/src/compiler/phases/3-transform/segments.js +91 -0
  12. package/src/compiler/phases/3-transform/stylesheet.js +372 -0
  13. package/src/compiler/scope.js +421 -0
  14. package/src/compiler/utils.js +552 -0
  15. package/src/constants.js +4 -0
  16. package/src/jsx-runtime.d.ts +94 -0
  17. package/src/jsx-runtime.js +46 -0
  18. package/src/runtime/array.js +215 -0
  19. package/src/runtime/index.js +39 -0
  20. package/src/runtime/internal/client/blocks.js +247 -0
  21. package/src/runtime/internal/client/constants.js +23 -0
  22. package/src/runtime/internal/client/events.js +223 -0
  23. package/src/runtime/internal/client/for.js +388 -0
  24. package/src/runtime/internal/client/if.js +35 -0
  25. package/src/runtime/internal/client/index.js +53 -0
  26. package/src/runtime/internal/client/operations.js +72 -0
  27. package/src/runtime/internal/client/portal.js +33 -0
  28. package/src/runtime/internal/client/render.js +156 -0
  29. package/src/runtime/internal/client/runtime.js +909 -0
  30. package/src/runtime/internal/client/template.js +51 -0
  31. package/src/runtime/internal/client/try.js +139 -0
  32. package/src/runtime/internal/client/utils.js +16 -0
  33. package/src/utils/ast.js +214 -0
  34. package/src/utils/builders.js +733 -0
  35. package/src/utils/patterns.js +23 -0
  36. package/src/utils/sanitize_template_string.js +7 -0
  37. package/test-mappings.js +0 -0
  38. package/types/index.d.ts +2 -0
  39. package/.npmignore +0 -2
  40. package/History.md +0 -3
  41. package/Readme.md +0 -151
  42. package/lib/exec/index.js +0 -60
  43. package/ripple.js +0 -645
@@ -0,0 +1,91 @@
1
+ import { decode } from '@jridgewell/sourcemap-codec';
2
+
3
+ export const defaultMappingData = {
4
+ verification: true,
5
+ completion: true,
6
+ semantic: true,
7
+ navigation: true
8
+ };
9
+
10
+ /**
11
+ * Convert esrap SourceMap to Volar mappings using Svelte's approach
12
+ * Based on: https://github.com/volarjs/svelte-language-tools/blob/master/packages/language-server/src/language.ts#L45-L88
13
+ * @param {object} source_map - esrap SourceMap object
14
+ * @param {string} source - Original Ripple source
15
+ * @param {string} generated_code - Generated TypeScript code
16
+ * @returns {object} Object with code and mappings for Volar
17
+ */
18
+ export function convert_source_map_to_mappings(source_map, source, generated_code) {
19
+ const mappings = [];
20
+
21
+ // Decode the VLQ mappings from esrap
22
+ const decoded_mappings = decode(source_map.mappings);
23
+
24
+ let generated_offset = 0;
25
+ const generated_lines = generated_code.split('\n');
26
+
27
+ // Process each line of generated code
28
+ for (let generated_line = 0; generated_line < generated_lines.length; generated_line++) {
29
+ const line = generated_lines[generated_line];
30
+ const line_mappings = decoded_mappings[generated_line] || [];
31
+
32
+ // Process mappings for this line
33
+ for (const mapping of line_mappings) {
34
+ const [generated_column, source_file_index, source_line, source_column] = mapping;
35
+
36
+ // Skip mappings without source information
37
+ if (source_file_index == null || source_line == null || source_column == null) {
38
+ continue;
39
+ }
40
+
41
+ // Calculate source offset
42
+ const source_lines = source.split('\n');
43
+ let source_offset = 0;
44
+ for (let i = 0; i < Math.min(source_line, source_lines.length - 1); i++) {
45
+ source_offset += source_lines[i].length + 1; // +1 for newline
46
+ }
47
+ source_offset += source_column;
48
+
49
+ // Calculate generated offset
50
+ const current_generated_offset = generated_offset + generated_column;
51
+
52
+ // Determine segment length (look ahead to next mapping or end of line)
53
+ const next_mapping = line_mappings[line_mappings.indexOf(mapping) + 1];
54
+ let segment_length = next_mapping ? next_mapping[0] - generated_column : Math.max(1, line.length - generated_column);
55
+
56
+ // Determine the actual segment content
57
+ const generated_content = generated_code.substring(current_generated_offset, current_generated_offset + segment_length);
58
+ const source_content = source.substring(source_offset, source_offset + segment_length);
59
+
60
+ // Fix for $children mapping: when generated content is "$children",
61
+ // it should only map to the component name in the source, not include attributes
62
+ if (generated_content === '$children') {
63
+ // Look for the component name in the source content
64
+ const component_name_match = source_content.match(/^(\w+)/);
65
+ if (component_name_match) {
66
+ const component_name = component_name_match[1];
67
+ segment_length = component_name.length;
68
+ }
69
+ }
70
+
71
+ // Create Volar mapping with default mapping data
72
+ mappings.push({
73
+ sourceOffsets: [source_offset],
74
+ generatedOffsets: [current_generated_offset],
75
+ lengths: [segment_length],
76
+ data: defaultMappingData
77
+ });
78
+ }
79
+
80
+ // Add line length + 1 for newline (except for last line)
81
+ generated_offset += line.length;
82
+ if (generated_line < generated_lines.length - 1) {
83
+ generated_offset += 1; // newline character
84
+ }
85
+ }
86
+
87
+ return {
88
+ code: generated_code,
89
+ mappings
90
+ };
91
+ }
@@ -0,0 +1,372 @@
1
+ import MagicString from 'magic-string';
2
+ import { walk } from 'zimmerframe';
3
+
4
+ const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
5
+
6
+ const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes';
7
+
8
+ function remove_css_prefix(name) {
9
+ return name.replace(regex_css_browser_prefix, '');
10
+ }
11
+
12
+ function remove_preceding_whitespace(end, state) {
13
+ let start = end;
14
+ while (/\s/.test(state.code.original[start - 1])) start--;
15
+ if (start < end) state.code.remove(start, end);
16
+ }
17
+
18
+ function is_used(rule) {
19
+ return rule.prelude.children.some((selector) => selector.metadata.used);
20
+ }
21
+
22
+ function is_in_global_block(path) {
23
+ return path.some((node) => node.type === 'Rule' && node.metadata.is_global_block);
24
+ }
25
+
26
+ function remove_global_pseudo_class(selector, combinator, state) {
27
+ if (selector.args === null) {
28
+ let start = selector.start;
29
+ if (combinator?.name === ' ') {
30
+ // div :global.x becomes div.x
31
+ while (/\s/.test(state.code.original[start - 1])) start--;
32
+ }
33
+ state.code.remove(start, selector.start + ':global'.length);
34
+ } else {
35
+ state.code
36
+ .remove(selector.start, selector.start + ':global('.length)
37
+ .remove(selector.end - 1, selector.end);
38
+ }
39
+ }
40
+
41
+ function escape_comment_close(node, code) {
42
+ let escaped = false;
43
+ let in_comment = false;
44
+
45
+ for (let i = node.start; i < node.end; i++) {
46
+ if (escaped) {
47
+ escaped = false;
48
+ } else {
49
+ const char = code.original[i];
50
+ if (in_comment) {
51
+ if (char === '*' && code.original[i + 1] === '/') {
52
+ code.prependRight(++i, '\\');
53
+ in_comment = false;
54
+ }
55
+ } else if (char === '\\') {
56
+ escaped = true;
57
+ } else if (char === '/' && code.original[++i] === '*') {
58
+ in_comment = true;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @param {AST.CSS.Rule} rule
66
+ * @param {boolean} is_in_global_block
67
+ */
68
+ function is_empty(rule, is_in_global_block) {
69
+ if (rule.metadata.is_global_block) {
70
+ return rule.block.children.length === 0;
71
+ }
72
+
73
+ for (const child of rule.block.children) {
74
+ if (child.type === 'Declaration') {
75
+ return false;
76
+ }
77
+
78
+ if (child.type === 'Rule') {
79
+ if ((is_used(child) || is_in_global_block) && !is_empty(child, is_in_global_block)) {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ if (child.type === 'Atrule') {
85
+ if (child.block === null || child.block.children.length > 0) return false;
86
+ }
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ const visitors = {
93
+ _: (node, context) => {
94
+ context.state.code.addSourcemapLocation(node.start);
95
+ context.state.code.addSourcemapLocation(node.end);
96
+ context.next();
97
+ },
98
+ Atrule(node, { state, next, path }) {
99
+ if (is_keyframes_node(node)) {
100
+ let start = node.start + node.name.length + 1;
101
+ while (state.code.original[start] === ' ') start += 1;
102
+ let end = start;
103
+ while (state.code.original[end] !== '{' && state.code.original[end] !== ' ') end += 1;
104
+
105
+ if (node.prelude.startsWith('-global-')) {
106
+ state.code.remove(start, start + 8);
107
+ } else if (!is_in_global_block(path)) {
108
+ state.code.prependRight(start, `${state.hash}-`);
109
+ }
110
+
111
+ return; // don't transform anything within
112
+ }
113
+
114
+ next();
115
+ },
116
+ Declaration(node, { state }) {
117
+ const property = node.property && remove_css_prefix(node.property.toLowerCase());
118
+ if (property === 'animation' || property === 'animation-name') {
119
+ let index = node.start + node.property.length + 1;
120
+ let name = '';
121
+
122
+ while (index < state.code.original.length) {
123
+ const character = state.code.original[index];
124
+
125
+ if (regex_css_name_boundary.test(character)) {
126
+ if (state.keyframes.includes(name)) {
127
+ state.code.prependRight(index - name.length, `${state.hash}-`);
128
+ }
129
+
130
+ if (character === ';' || character === '}') {
131
+ break;
132
+ }
133
+
134
+ name = '';
135
+ } else {
136
+ name += character;
137
+ }
138
+
139
+ index++;
140
+ }
141
+ }
142
+ },
143
+ Rule(node, { state, next, visit, path }) {
144
+ if (is_empty(node, is_in_global_block(path))) {
145
+ state.code.prependRight(node.start, '/* (empty) ');
146
+ state.code.appendLeft(node.end, '*/');
147
+ escape_comment_close(node, state.code);
148
+
149
+ return;
150
+ }
151
+
152
+ if (!is_used(node) && !is_in_global_block(path)) {
153
+ state.code.prependRight(node.start, '/* (unused) ');
154
+ state.code.appendLeft(node.end, '*/');
155
+ escape_comment_close(node, state.code);
156
+
157
+ return;
158
+ }
159
+
160
+ if (node.metadata.is_global_block) {
161
+ const selector = node.prelude.children[0];
162
+
163
+ if (selector.children.length === 1 && selector.children[0].selectors.length === 1) {
164
+ // `:global {...}`
165
+ if (state.minify) {
166
+ state.code.remove(node.start, node.block.start + 1);
167
+ state.code.remove(node.block.end - 1, node.end);
168
+ } else {
169
+ state.code.prependRight(node.start, '/* ');
170
+ state.code.appendLeft(node.block.start + 1, '*/');
171
+
172
+ state.code.prependRight(node.block.end - 1, '/*');
173
+ state.code.appendLeft(node.block.end, '*/');
174
+ }
175
+
176
+ // don't recurse into selectors but visit the body
177
+ visit(node.block);
178
+ return;
179
+ }
180
+ }
181
+
182
+ next();
183
+ },
184
+ SelectorList(node, { state, next, path }) {
185
+ // Only add comments if we're not inside a complex selector that itself is unused or a global block
186
+ if (
187
+ !is_in_global_block(path) &&
188
+ !path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)
189
+ ) {
190
+ const children = node.children;
191
+ let pruning = false;
192
+ let prune_start = children[0].start;
193
+ let last = prune_start;
194
+ let has_previous_used = false;
195
+
196
+ for (let i = 0; i < children.length; i += 1) {
197
+ const selector = children[i];
198
+
199
+ if (selector.metadata.used === pruning) {
200
+ if (pruning) {
201
+ let i = selector.start;
202
+ while (state.code.original[i] !== ',') i--;
203
+
204
+ if (state.minify) {
205
+ state.code.remove(prune_start, has_previous_used ? i : i + 1);
206
+ } else {
207
+ state.code.appendRight(has_previous_used ? i : i + 1, '*/');
208
+ }
209
+ } else {
210
+ if (i === 0) {
211
+ if (state.minify) {
212
+ prune_start = selector.start;
213
+ } else {
214
+ state.code.prependRight(selector.start, '/* (unused) ');
215
+ }
216
+ } else {
217
+ if (state.minify) {
218
+ prune_start = last;
219
+ } else {
220
+ state.code.overwrite(last, selector.start, ` /* (unused) `);
221
+ }
222
+ }
223
+ }
224
+
225
+ pruning = !pruning;
226
+ }
227
+
228
+ if (!pruning && selector.metadata.used) {
229
+ has_previous_used = true;
230
+ }
231
+
232
+ last = selector.end;
233
+ }
234
+
235
+ if (pruning) {
236
+ if (state.minify) {
237
+ state.code.remove(prune_start, last);
238
+ } else {
239
+ state.code.appendLeft(last, '*/');
240
+ }
241
+ }
242
+ }
243
+
244
+ // if we're in a `:is(...)` or whatever, keep existing specificity bump state
245
+ let specificity = state.specificity;
246
+
247
+ // if this selector list belongs to a rule, require a specificity bump for the
248
+ // first scoped selector but only if we're at the top level
249
+ let parent = path.at(-1);
250
+ if (parent?.type === 'Rule') {
251
+ specificity = { bumped: false };
252
+
253
+ /** @type {AST.CSS.Rule | null} */
254
+ let rule = parent.metadata.parent_rule;
255
+
256
+ while (rule) {
257
+ if (rule.metadata.has_local_selectors) {
258
+ specificity = { bumped: true };
259
+ break;
260
+ }
261
+ rule = rule.metadata.parent_rule;
262
+ }
263
+ }
264
+
265
+ next({ ...state, specificity });
266
+ },
267
+ ComplexSelector(node, context) {
268
+ const before_bumped = context.state.specificity.bumped;
269
+
270
+ for (const relative_selector of node.children) {
271
+ if (relative_selector.metadata.is_global) {
272
+ const global = /** @type {AST.CSS.PseudoClassSelector} */ (relative_selector.selectors[0]);
273
+ remove_global_pseudo_class(global, relative_selector.combinator, context.state);
274
+
275
+ if (
276
+ node.metadata.rule?.metadata.parent_rule &&
277
+ global.args === null &&
278
+ relative_selector.combinator === null
279
+ ) {
280
+ // div { :global.x { ... } } becomes div { &.x { ... } }
281
+ context.state.code.prependRight(global.start, '&');
282
+ }
283
+ continue;
284
+ } else {
285
+ // for any :global() or :global at the middle of compound selector
286
+ for (const selector of relative_selector.selectors) {
287
+ if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
288
+ remove_global_pseudo_class(selector, null, context.state);
289
+ }
290
+ }
291
+ }
292
+
293
+ if (relative_selector.metadata.scoped) {
294
+ if (relative_selector.selectors.length === 1) {
295
+ // skip standalone :is/:where/& selectors
296
+ const selector = relative_selector.selectors[0];
297
+ if (
298
+ selector.type === 'PseudoClassSelector' &&
299
+ (selector.name === 'is' || selector.name === 'where')
300
+ ) {
301
+ continue;
302
+ }
303
+ }
304
+
305
+ if (relative_selector.selectors.some((s) => s.type === 'NestingSelector')) {
306
+ continue;
307
+ }
308
+
309
+ // for the first occurrence, we use a classname selector, so that every
310
+ // encapsulated selector gets a +0-1-0 specificity bump. thereafter,
311
+ // we use a `:where` selector, which does not affect specificity
312
+ let modifier = context.state.selector;
313
+ if (context.state.specificity.bumped) modifier = `:where(${modifier})`;
314
+
315
+ context.state.specificity.bumped = true;
316
+
317
+ let i = relative_selector.selectors.length;
318
+ while (i--) {
319
+ const selector = relative_selector.selectors[i];
320
+
321
+ if (
322
+ selector.type === 'PseudoElementSelector' ||
323
+ selector.type === 'PseudoClassSelector'
324
+ ) {
325
+ if (selector.name !== 'root' && selector.name !== 'host') {
326
+ if (i === 0) context.state.code.prependRight(selector.start, modifier);
327
+ }
328
+ continue;
329
+ }
330
+
331
+ if (selector.type === 'TypeSelector' && selector.name === '*') {
332
+ context.state.code.update(selector.start, selector.end, modifier);
333
+ } else {
334
+ context.state.code.appendLeft(selector.end, modifier);
335
+ }
336
+
337
+ break;
338
+ }
339
+ }
340
+ }
341
+
342
+ context.next();
343
+
344
+ context.state.specificity.bumped = before_bumped;
345
+ },
346
+ PseudoClassSelector(node, context) {
347
+ if (node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not') {
348
+ context.next();
349
+ }
350
+ }
351
+ };
352
+
353
+ export function render_stylesheets(stylesheets) {
354
+ let css = '';
355
+
356
+ for (const stylesheet of stylesheets) {
357
+ const code = new MagicString(stylesheet.source);
358
+ const state = {
359
+ code,
360
+ hash: stylesheet.hash,
361
+ selector: `.${stylesheet.hash}`,
362
+ specificity: {
363
+ bumped: false
364
+ }
365
+ };
366
+
367
+ walk(stylesheet, state, visitors);
368
+ css += code.toString();
369
+ }
370
+
371
+ return css;
372
+ }