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,566 @@
1
+ import { hash } from "../../utils.js";
2
+
3
+ const REGEX_COMMENT_CLOSE = /\*\//;
4
+ const REGEX_HTML_COMMENT_CLOSE = /-->/;
5
+ const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
6
+ const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
7
+ const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
8
+ const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
9
+ const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
10
+ const REGEX_NTH_OF =
11
+ /^(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of\s+)/;
12
+
13
+ const regex_whitespace = /\s/;
14
+
15
+ class Parser {
16
+ index = 0;
17
+
18
+ constructor(template, loose) {
19
+ if (typeof template !== 'string') {
20
+ throw new TypeError('Template must be a string');
21
+ }
22
+
23
+ this.loose = loose;
24
+ this.template_untrimmed = template;
25
+ this.template = template.trimEnd();
26
+ }
27
+
28
+ match(str) {
29
+ const length = str.length;
30
+ if (length === 1) {
31
+ // more performant than slicing
32
+ return this.template[this.index] === str;
33
+ }
34
+
35
+ return this.template.slice(this.index, this.index + length) === str;
36
+ }
37
+
38
+ eat(str, required = false, required_in_loose = true) {
39
+ if (this.match(str)) {
40
+ this.index += str.length;
41
+ return true;
42
+ }
43
+
44
+ if (required && (!this.loose || required_in_loose)) {
45
+ throw new Error(`Expected ${str}`);
46
+ }
47
+
48
+ return false;
49
+ }
50
+
51
+ match_regex(pattern) {
52
+ const match = pattern.exec(this.template.slice(this.index));
53
+ if (!match || match.index !== 0) return null;
54
+
55
+ return match[0];
56
+ }
57
+
58
+ read(pattern) {
59
+ const result = this.match_regex(pattern);
60
+ if (result) this.index += result.length;
61
+ return result;
62
+ }
63
+
64
+ allow_whitespace() {
65
+ while (this.index < this.template.length && regex_whitespace.test(this.template[this.index])) {
66
+ this.index++;
67
+ }
68
+ }
69
+
70
+ read_until(pattern) {
71
+ if (this.index >= this.template.length) {
72
+ if (this.loose) return '';
73
+ throw new Error('Unexpected end of input');
74
+ }
75
+
76
+ const start = this.index;
77
+ const match = pattern.exec(this.template.slice(start));
78
+
79
+ if (match) {
80
+ this.index = start + match.index;
81
+ return this.template.slice(start, this.index);
82
+ }
83
+
84
+ this.index = this.template.length;
85
+ return this.template.slice(start);
86
+ }
87
+ }
88
+
89
+ export function parse_style(content) {
90
+ const parser = new Parser(content, false);
91
+
92
+ return {
93
+ source: content,
94
+ hash: `ripple-${hash(content)}`,
95
+ type: 'StyleSheet',
96
+ body: read_body(parser)
97
+ };
98
+ }
99
+
100
+ function allow_comment_or_whitespace(parser) {
101
+ parser.allow_whitespace();
102
+ while (parser.match('/*') || parser.match('<!--')) {
103
+ if (parser.eat('/*')) {
104
+ parser.read_until(REGEX_COMMENT_CLOSE);
105
+ parser.eat('*/', true);
106
+ }
107
+
108
+ if (parser.eat('<!--')) {
109
+ parser.read_until(REGEX_HTML_COMMENT_CLOSE);
110
+ parser.eat('-->', true);
111
+ }
112
+
113
+ parser.allow_whitespace();
114
+ }
115
+ }
116
+
117
+ function read_body(parser) {
118
+ const children = [];
119
+
120
+ while (parser.index < parser.template.length) {
121
+ allow_comment_or_whitespace(parser);
122
+
123
+ if (parser.match('@')) {
124
+ children.push(read_at_rule(parser));
125
+ } else {
126
+ children.push(read_rule(parser));
127
+ }
128
+ }
129
+
130
+ return children;
131
+ }
132
+
133
+ function read_at_rule(parser) {
134
+ debugger;
135
+ }
136
+
137
+ function read_rule(parser) {
138
+ const start = parser.index;
139
+
140
+ return {
141
+ type: 'Rule',
142
+ prelude: read_selector_list(parser),
143
+ block: read_block(parser),
144
+ start,
145
+ end: parser.index,
146
+ metadata: {
147
+ parent_rule: null,
148
+ has_local_selectors: false,
149
+ is_global_block: false
150
+ }
151
+ };
152
+ }
153
+
154
+ function read_block(parser) {
155
+ const start = parser.index;
156
+
157
+ parser.eat('{', true);
158
+
159
+ /** @type {Array<AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule>} */
160
+ const children = [];
161
+
162
+ while (parser.index < parser.template.length) {
163
+ allow_comment_or_whitespace(parser);
164
+
165
+ if (parser.match('}')) {
166
+ break;
167
+ } else {
168
+ children.push(read_block_item(parser));
169
+ }
170
+ }
171
+
172
+ parser.eat('}', true);
173
+
174
+ return {
175
+ type: 'Block',
176
+ start,
177
+ end: parser.index,
178
+ children
179
+ };
180
+ }
181
+
182
+ function read_block_item(parser) {
183
+ if (parser.match('@')) {
184
+ return read_at_rule(parser);
185
+ }
186
+
187
+ // read ahead to understand whether we're dealing with a declaration or a nested rule.
188
+ // this involves some duplicated work, but avoids a try-catch that would disguise errors
189
+ const start = parser.index;
190
+ read_value(parser);
191
+ const char = parser.template[parser.index];
192
+ parser.index = start;
193
+
194
+ return char === '{' ? read_rule(parser) : read_declaration(parser);
195
+ }
196
+
197
+ function read_declaration(parser) {
198
+ const start = parser.index;
199
+
200
+ const property = parser.read_until(REGEX_WHITESPACE_OR_COLON);
201
+ parser.allow_whitespace();
202
+ parser.eat(':');
203
+ let index = parser.index;
204
+ parser.allow_whitespace();
205
+
206
+ const value = read_value(parser);
207
+
208
+ if (!value && !property.startsWith('--')) {
209
+ e.css_empty_declaration({ start, end: index });
210
+ }
211
+
212
+ const end = parser.index;
213
+
214
+ if (!parser.match('}')) {
215
+ parser.eat(';', true);
216
+ }
217
+
218
+ return {
219
+ type: 'Declaration',
220
+ start,
221
+ end,
222
+ property,
223
+ value
224
+ };
225
+ }
226
+
227
+ function read_value(parser) {
228
+ let value = '';
229
+ let escaped = false;
230
+ let in_url = false;
231
+
232
+ /** @type {null | '"' | "'"} */
233
+ let quote_mark = null;
234
+
235
+ while (parser.index < parser.template.length) {
236
+ const char = parser.template[parser.index];
237
+
238
+ if (escaped) {
239
+ value += '\\' + char;
240
+ escaped = false;
241
+ } else if (char === '\\') {
242
+ escaped = true;
243
+ } else if (char === quote_mark) {
244
+ quote_mark = null;
245
+ } else if (char === ')') {
246
+ in_url = false;
247
+ } else if (quote_mark === null && (char === '"' || char === "'")) {
248
+ quote_mark = char;
249
+ } else if (char === '(' && value.slice(-3) === 'url') {
250
+ in_url = true;
251
+ } else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
252
+ return value.trim();
253
+ }
254
+
255
+ value += char;
256
+
257
+ parser.index++;
258
+ }
259
+
260
+ throw new Error('Unexpected end of input');
261
+ }
262
+
263
+ function read_selector_list(parser, inside_pseudo_class = false) {
264
+ /** @type {AST.CSS.ComplexSelector[]} */
265
+ const children = [];
266
+
267
+ allow_comment_or_whitespace(parser);
268
+
269
+ const start = parser.index;
270
+
271
+ while (parser.index < parser.template.length) {
272
+ children.push(read_selector(parser, inside_pseudo_class));
273
+
274
+ const end = parser.index;
275
+
276
+ allow_comment_or_whitespace(parser);
277
+
278
+ if (inside_pseudo_class ? parser.match(')') : parser.match('{')) {
279
+ return {
280
+ type: 'SelectorList',
281
+ start,
282
+ end,
283
+ children
284
+ };
285
+ } else {
286
+ parser.eat(',', true);
287
+ allow_comment_or_whitespace(parser);
288
+ }
289
+ }
290
+
291
+ throw new Error('Unexpected end of input');
292
+ }
293
+
294
+ function read_combinator(parser) {
295
+ const start = parser.index;
296
+ parser.allow_whitespace();
297
+
298
+ const index = parser.index;
299
+ const name = parser.read(REGEX_COMBINATOR);
300
+
301
+ if (name) {
302
+ const end = parser.index;
303
+ parser.allow_whitespace();
304
+
305
+ return {
306
+ type: 'Combinator',
307
+ name,
308
+ start: index,
309
+ end
310
+ };
311
+ }
312
+
313
+ if (parser.index !== start) {
314
+ return {
315
+ type: 'Combinator',
316
+ name: ' ',
317
+ start,
318
+ end: parser.index
319
+ };
320
+ }
321
+
322
+ return null;
323
+ }
324
+
325
+ function read_selector(parser, inside_pseudo_class = false) {
326
+ const list_start = parser.index;
327
+
328
+ /** @type {AST.CSS.RelativeSelector[]} */
329
+ const children = [];
330
+
331
+ /**
332
+ * @param {AST.CSS.Combinator | null} combinator
333
+ * @param {number} start
334
+ * @returns {AST.CSS.RelativeSelector}
335
+ */
336
+ function create_selector(combinator, start) {
337
+ return {
338
+ type: 'RelativeSelector',
339
+ combinator,
340
+ selectors: [],
341
+ start,
342
+ end: -1,
343
+ metadata: {
344
+ is_global: false,
345
+ is_global_like: false,
346
+ scoped: false
347
+ }
348
+ };
349
+ }
350
+
351
+ /** @type {AST.CSS.RelativeSelector} */
352
+ let relative_selector = create_selector(null, parser.index);
353
+
354
+ while (parser.index < parser.template.length) {
355
+ let start = parser.index;
356
+
357
+ if (parser.eat('&')) {
358
+ relative_selector.selectors.push({
359
+ type: 'NestingSelector',
360
+ name: '&',
361
+ start,
362
+ end: parser.index
363
+ });
364
+ } else if (parser.eat('*')) {
365
+ let name = '*';
366
+
367
+ if (parser.eat('|')) {
368
+ // * is the namespace (which we ignore)
369
+ name = read_identifier(parser);
370
+ }
371
+
372
+ relative_selector.selectors.push({
373
+ type: 'TypeSelector',
374
+ name,
375
+ start,
376
+ end: parser.index
377
+ });
378
+ } else if (parser.eat('#')) {
379
+ relative_selector.selectors.push({
380
+ type: 'IdSelector',
381
+ name: read_identifier(parser),
382
+ start,
383
+ end: parser.index
384
+ });
385
+ } else if (parser.eat('.')) {
386
+ relative_selector.selectors.push({
387
+ type: 'ClassSelector',
388
+ name: read_identifier(parser),
389
+ start,
390
+ end: parser.index
391
+ });
392
+ } else if (parser.eat('::')) {
393
+ relative_selector.selectors.push({
394
+ type: 'PseudoElementSelector',
395
+ name: read_identifier(parser),
396
+ start,
397
+ end: parser.index
398
+ });
399
+ // We read the inner selectors of a pseudo element to ensure it parses correctly,
400
+ // but we don't do anything with the result.
401
+ if (parser.eat('(')) {
402
+ read_selector_list(parser, true);
403
+ parser.eat(')', true);
404
+ }
405
+ } else if (parser.eat(':')) {
406
+ const name = read_identifier(parser);
407
+
408
+ /** @type {null | AST.CSS.SelectorList} */
409
+ let args = null;
410
+
411
+ if (parser.eat('(')) {
412
+ args = read_selector_list(parser, true);
413
+ parser.eat(')', true);
414
+ }
415
+
416
+ relative_selector.selectors.push({
417
+ type: 'PseudoClassSelector',
418
+ name,
419
+ args,
420
+ start,
421
+ end: parser.index
422
+ });
423
+ } else if (parser.eat('[')) {
424
+ parser.allow_whitespace();
425
+ const name = read_identifier(parser);
426
+ parser.allow_whitespace();
427
+
428
+ /** @type {string | null} */
429
+ let value = null;
430
+
431
+ const matcher = parser.read(REGEX_MATCHER);
432
+
433
+ if (matcher) {
434
+ parser.allow_whitespace();
435
+ value = read_attribute_value(parser);
436
+ }
437
+
438
+ parser.allow_whitespace();
439
+
440
+ const flags = parser.read(REGEX_ATTRIBUTE_FLAGS);
441
+
442
+ parser.allow_whitespace();
443
+ parser.eat(']', true);
444
+
445
+ relative_selector.selectors.push({
446
+ type: 'AttributeSelector',
447
+ start,
448
+ end: parser.index,
449
+ name,
450
+ matcher,
451
+ value,
452
+ flags
453
+ });
454
+ } else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
455
+ // nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator
456
+
457
+ relative_selector.selectors.push({
458
+ type: 'Nth',
459
+ value: /**@type {string} */ (parser.read(REGEX_NTH_OF)),
460
+ start,
461
+ end: parser.index
462
+ });
463
+ } else if (parser.match_regex(REGEX_PERCENTAGE)) {
464
+ relative_selector.selectors.push({
465
+ type: 'Percentage',
466
+ value: /** @type {string} */ (parser.read(REGEX_PERCENTAGE)),
467
+ start,
468
+ end: parser.index
469
+ });
470
+ } else if (!parser.match_regex(REGEX_COMBINATOR)) {
471
+ let name = read_identifier(parser);
472
+
473
+ if (parser.eat('|')) {
474
+ // we ignore the namespace when trying to find matching element classes
475
+ name = read_identifier(parser);
476
+ }
477
+
478
+ relative_selector.selectors.push({
479
+ type: 'TypeSelector',
480
+ name,
481
+ start,
482
+ end: parser.index
483
+ });
484
+ }
485
+
486
+ const index = parser.index;
487
+ allow_comment_or_whitespace(parser);
488
+
489
+ if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
490
+ // rewind, so we know whether to continue building the selector list
491
+ parser.index = index;
492
+
493
+ relative_selector.end = index;
494
+ children.push(relative_selector);
495
+
496
+ return {
497
+ type: 'ComplexSelector',
498
+ start: list_start,
499
+ end: index,
500
+ children,
501
+ metadata: {
502
+ rule: null,
503
+ used: false
504
+ }
505
+ };
506
+ }
507
+
508
+ parser.index = index;
509
+ const combinator = read_combinator(parser);
510
+
511
+ if (combinator) {
512
+ if (relative_selector.selectors.length > 0) {
513
+ relative_selector.end = index;
514
+ children.push(relative_selector);
515
+ }
516
+
517
+ // ...and start a new one
518
+ relative_selector = create_selector(combinator, combinator.start);
519
+
520
+ parser.allow_whitespace();
521
+
522
+ if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
523
+ e.css_selector_invalid(parser.index);
524
+ }
525
+ }
526
+ }
527
+
528
+ throw new Error('Unexpected end of input');
529
+ }
530
+
531
+ function read_identifier(parser) {
532
+ const start = parser.index;
533
+
534
+ let identifier = '';
535
+
536
+ if (parser.match_regex(REGEX_LEADING_HYPHEN_OR_DIGIT)) {
537
+ throw new Error('Unexpected CSS identifier');
538
+ }
539
+
540
+ let escaped = false;
541
+
542
+ while (parser.index < parser.template.length) {
543
+ const char = parser.template[parser.index];
544
+ if (escaped) {
545
+ identifier += '\\' + char;
546
+ escaped = false;
547
+ } else if (char === '\\') {
548
+ escaped = true;
549
+ } else if (
550
+ /** @type {number} */ (char.codePointAt(0)) >= 160 ||
551
+ REGEX_VALID_IDENTIFIER_CHAR.test(char)
552
+ ) {
553
+ identifier += char;
554
+ } else {
555
+ break;
556
+ }
557
+
558
+ parser.index++;
559
+ }
560
+
561
+ if (identifier === '') {
562
+ throw new Error('Expected identifier');
563
+ }
564
+
565
+ return identifier;
566
+ }