ripple 0.2.192 → 0.2.194
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.
- package/package.json +2 -2
- package/src/compiler/errors.js +33 -14
- package/src/compiler/index.d.ts +10 -0
- package/src/compiler/index.js +12 -10
- package/src/compiler/phases/1-parse/index.js +24 -0
- package/src/compiler/phases/2-analyze/index.js +184 -70
- package/src/compiler/phases/2-analyze/validation.js +8 -3
- package/src/compiler/phases/3-transform/client/index.js +177 -69
- package/src/compiler/phases/3-transform/segments.js +126 -53
- package/src/compiler/phases/3-transform/server/index.js +89 -6
- package/src/compiler/source-map-utils.js +96 -7
- package/src/compiler/types/index.d.ts +7 -2
- package/src/compiler/types/parse.d.ts +14 -0
- package/src/compiler/utils.js +40 -6
- package/src/utils/builders.js +72 -25
- package/tests/server/__snapshots__/compiler.test.ripple.snap +56 -5
- package/tests/server/await.test.ripple +54 -50
- package/tests/server/compiler.test.ripple +87 -3
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.
|
|
6
|
+
"version": "0.2.194",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -92,6 +92,6 @@
|
|
|
92
92
|
"vscode-languageserver-types": "^3.17.5"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"ripple": "0.2.
|
|
95
|
+
"ripple": "0.2.194"
|
|
96
96
|
}
|
|
97
97
|
}
|
package/src/compiler/errors.js
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
@import * as AST from 'estree';
|
|
3
|
+
@import { RippleCompileError } from 'ripple/compiler';
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
*
|
|
5
8
|
* @param {string} message
|
|
6
9
|
* @param {string} filename
|
|
7
10
|
* @param {AST.Node} node
|
|
11
|
+
* @param {RippleCompileError[]} [errors]
|
|
12
|
+
* @returns {void}
|
|
8
13
|
*/
|
|
9
|
-
export function error(message, filename, node) {
|
|
10
|
-
|
|
14
|
+
export function error(message, filename, node, errors) {
|
|
15
|
+
const error = /** @type {RippleCompileError} */ (new Error(message));
|
|
16
|
+
|
|
17
|
+
// same as the acorn compiler error
|
|
18
|
+
error.pos = node.start ?? undefined;
|
|
19
|
+
error.raisedAt = node.end ?? undefined;
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
// custom properties
|
|
22
|
+
error.fileName = filename;
|
|
23
|
+
error.end = node.end ?? undefined;
|
|
24
|
+
error.loc = !node.loc
|
|
25
|
+
? undefined
|
|
26
|
+
: {
|
|
27
|
+
start: {
|
|
28
|
+
line: node.loc.start.line,
|
|
29
|
+
column: node.loc.start.column,
|
|
30
|
+
},
|
|
31
|
+
end: {
|
|
32
|
+
line: node.loc.end.line,
|
|
33
|
+
column: node.loc.end.column,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
if (errors) {
|
|
38
|
+
error.type = 'usage';
|
|
39
|
+
errors.push(error);
|
|
40
|
+
return;
|
|
23
41
|
}
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
error.type = 'fatal';
|
|
44
|
+
throw error;
|
|
26
45
|
}
|
package/src/compiler/index.d.ts
CHANGED
|
@@ -79,6 +79,16 @@ export interface VolarMappingsResult {
|
|
|
79
79
|
code: string;
|
|
80
80
|
mappings: CodeMapping[];
|
|
81
81
|
cssMappings: CodeMapping[];
|
|
82
|
+
errors: RippleCompileError[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RippleCompileError extends Error {
|
|
86
|
+
pos: number | undefined;
|
|
87
|
+
raisedAt: number | undefined;
|
|
88
|
+
end: number | undefined;
|
|
89
|
+
loc: AST.SourceLocation | undefined;
|
|
90
|
+
fileName: string | undefined;
|
|
91
|
+
type: 'fatal' | 'usage';
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
/**
|
package/src/compiler/index.js
CHANGED
|
@@ -54,14 +54,16 @@ export function compile_to_volar_mappings(source, filename, options) {
|
|
|
54
54
|
options?.minify_css ?? false,
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
return {
|
|
58
|
+
...convert_source_map_to_mappings(
|
|
59
|
+
transformed.ast,
|
|
60
|
+
ast,
|
|
61
|
+
source,
|
|
62
|
+
transformed.js.code,
|
|
63
|
+
transformed.js.map,
|
|
64
|
+
/** @type {PostProcessingChanges} */ (transformed.js.post_processing_changes),
|
|
65
|
+
/** @type {LineOffsets} */ (transformed.js.line_offsets),
|
|
66
|
+
),
|
|
67
|
+
errors: transformed.errors,
|
|
68
|
+
};
|
|
67
69
|
}
|
|
@@ -762,6 +762,29 @@ function RipplePlugin(config) {
|
|
|
762
762
|
return super.checkLValSimple(expr, bindingType, checkClashes);
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Override checkLocalExport to check all scopes in the scope stack.
|
|
767
|
+
* This is needed because server blocks create nested scopes, but exports
|
|
768
|
+
* from within server blocks should still be valid if the identifier is
|
|
769
|
+
* declared in the server block's scope (not just the top-level module scope).
|
|
770
|
+
* @type {Parse.Parser['checkLocalExport']}
|
|
771
|
+
*/
|
|
772
|
+
checkLocalExport(id) {
|
|
773
|
+
const { name } = id;
|
|
774
|
+
if (this.hasImport(name)) return;
|
|
775
|
+
// Check all scopes in the scope stack, not just the top-level scope
|
|
776
|
+
for (let i = this.scopeStack.length - 1; i >= 0; i--) {
|
|
777
|
+
const scope = this.scopeStack[i];
|
|
778
|
+
if (scope.lexical.indexOf(name) !== -1 || scope.var.indexOf(name) !== -1) {
|
|
779
|
+
// Found in a scope, remove from undefinedExports if it was added
|
|
780
|
+
delete this.undefinedExports[name];
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Not found in any scope, add to undefinedExports for later error
|
|
785
|
+
this.undefinedExports[name] = id;
|
|
786
|
+
}
|
|
787
|
+
|
|
765
788
|
/**
|
|
766
789
|
* @type {Parse.Parser['parseServerBlock']}
|
|
767
790
|
*/
|
|
@@ -2106,6 +2129,7 @@ function RipplePlugin(config) {
|
|
|
2106
2129
|
body.push(node);
|
|
2107
2130
|
}
|
|
2108
2131
|
} else {
|
|
2132
|
+
skipWhitespace(this);
|
|
2109
2133
|
const node = this.parseStatement(null);
|
|
2110
2134
|
body.push(node);
|
|
2111
2135
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
is_ripple_track_call,
|
|
24
24
|
is_void_element,
|
|
25
25
|
normalize_children,
|
|
26
|
+
is_binding_function,
|
|
26
27
|
} from '../../utils.js';
|
|
27
28
|
import { extract_paths } from '../../../utils/ast.js';
|
|
28
29
|
import is_reference from 'is-reference';
|
|
@@ -125,9 +126,12 @@ const visitors = {
|
|
|
125
126
|
ServerBlock(node, context) {
|
|
126
127
|
node.metadata = {
|
|
127
128
|
...node.metadata,
|
|
128
|
-
exports:
|
|
129
|
+
exports: new Set(),
|
|
129
130
|
};
|
|
130
|
-
context.visit(node.body, {
|
|
131
|
+
context.visit(node.body, {
|
|
132
|
+
...context.state,
|
|
133
|
+
ancestor_server_block: node,
|
|
134
|
+
});
|
|
131
135
|
},
|
|
132
136
|
|
|
133
137
|
Identifier(node, context) {
|
|
@@ -137,26 +141,28 @@ const visitors = {
|
|
|
137
141
|
if (
|
|
138
142
|
is_reference(node, /** @type {AST.Node} */ (parent)) &&
|
|
139
143
|
binding &&
|
|
140
|
-
context.state.
|
|
141
|
-
|
|
144
|
+
context.state.ancestor_server_block &&
|
|
145
|
+
binding.node !== node // Don't check the declaration itself
|
|
142
146
|
) {
|
|
143
147
|
/** @type {ScopeInterface | null} */
|
|
144
148
|
let current_scope = binding.scope;
|
|
149
|
+
let found_server_block = false;
|
|
145
150
|
|
|
146
151
|
while (current_scope !== null) {
|
|
147
152
|
if (current_scope.server_block) {
|
|
153
|
+
found_server_block = true;
|
|
148
154
|
break;
|
|
149
155
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
current_scope = current_scope.parent;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!found_server_block) {
|
|
160
|
+
error(
|
|
161
|
+
`Cannot reference client-side "${node.name}" from a server block. Server blocks can only access variables and imports declared inside them.`,
|
|
162
|
+
context.state.analysis.module.filename,
|
|
163
|
+
node,
|
|
164
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
165
|
+
);
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -207,10 +213,11 @@ const visitors = {
|
|
|
207
213
|
const component = is_inside_component(context, true);
|
|
208
214
|
|
|
209
215
|
if (!component) {
|
|
210
|
-
|
|
216
|
+
error(
|
|
211
217
|
'`#style` can only be used within a component',
|
|
212
218
|
context.state.analysis.module.filename,
|
|
213
219
|
node,
|
|
220
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
214
221
|
);
|
|
215
222
|
} else {
|
|
216
223
|
component.metadata.styleIdentifierPresent = true;
|
|
@@ -235,6 +242,7 @@ const visitors = {
|
|
|
235
242
|
'`#style` property access must use a dot property or static string for css class name, not a dynamic expression',
|
|
236
243
|
context.state.analysis.module.filename,
|
|
237
244
|
node.property,
|
|
245
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
238
246
|
);
|
|
239
247
|
}
|
|
240
248
|
|
|
@@ -265,6 +273,7 @@ const visitors = {
|
|
|
265
273
|
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`get(${node.object.name})\` or \`@${node.object.name}\` instead.`,
|
|
266
274
|
context.state.analysis.module.filename,
|
|
267
275
|
node.property,
|
|
276
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
268
277
|
);
|
|
269
278
|
}
|
|
270
279
|
}
|
|
@@ -277,7 +286,8 @@ const visitors = {
|
|
|
277
286
|
error(
|
|
278
287
|
`Accessing a tracked object directly is not allowed, use the \`@\` prefix to read the value inside a tracked object - for example \`@${node.object.name}${node.property.type === 'Identifier' ? `.${node.property.name}` : ''}\``,
|
|
279
288
|
context.state.analysis.module.filename,
|
|
280
|
-
node,
|
|
289
|
+
node.object,
|
|
290
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
281
291
|
);
|
|
282
292
|
}
|
|
283
293
|
}
|
|
@@ -301,7 +311,8 @@ const visitors = {
|
|
|
301
311
|
error(
|
|
302
312
|
'`track` can only be used within a reactive context, such as a component, function or class that is used or created from a component',
|
|
303
313
|
context.state.analysis.module.filename,
|
|
304
|
-
node,
|
|
314
|
+
node.callee,
|
|
315
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
305
316
|
);
|
|
306
317
|
}
|
|
307
318
|
|
|
@@ -324,7 +335,8 @@ const visitors = {
|
|
|
324
335
|
error(
|
|
325
336
|
'`var` declarations are not allowed in components, use let or const instead',
|
|
326
337
|
state.analysis.module.filename,
|
|
327
|
-
declarator,
|
|
338
|
+
declarator.id,
|
|
339
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
328
340
|
);
|
|
329
341
|
}
|
|
330
342
|
const metadata = { tracking: false, await: false };
|
|
@@ -354,6 +366,7 @@ const visitors = {
|
|
|
354
366
|
'Variables cannot be reactively referenced using @',
|
|
355
367
|
state.analysis.module.filename,
|
|
356
368
|
path.node,
|
|
369
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
357
370
|
);
|
|
358
371
|
}
|
|
359
372
|
}
|
|
@@ -412,6 +425,7 @@ const visitors = {
|
|
|
412
425
|
'Props are always an object, use destructured props with default values instead',
|
|
413
426
|
context.state.analysis.module.filename,
|
|
414
427
|
props,
|
|
428
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
415
429
|
);
|
|
416
430
|
}
|
|
417
431
|
}
|
|
@@ -451,15 +465,14 @@ const visitors = {
|
|
|
451
465
|
if (metadata.styleClasses.size > 0) {
|
|
452
466
|
node.metadata.styleClasses = metadata.styleClasses;
|
|
453
467
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
468
|
+
for (const [className, property] of metadata.styleClasses) {
|
|
469
|
+
if (!topScopedClasses?.has(className)) {
|
|
470
|
+
error(
|
|
471
|
+
`CSS class ".${className}" does not exist as a stand-alone class in ${node.id?.name ? node.id.name : "this component's"} <style> block`,
|
|
472
|
+
context.state.analysis.module.filename,
|
|
473
|
+
property,
|
|
474
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
475
|
+
);
|
|
463
476
|
}
|
|
464
477
|
}
|
|
465
478
|
}
|
|
@@ -477,6 +490,8 @@ const visitors = {
|
|
|
477
490
|
|
|
478
491
|
ForStatement(node, context) {
|
|
479
492
|
if (is_inside_component(context)) {
|
|
493
|
+
// TODO: it's a fatal error for now but
|
|
494
|
+
// we could implement the for loop for the ts mode only
|
|
480
495
|
error(
|
|
481
496
|
'For loops are not supported in components. Use for...of instead.',
|
|
482
497
|
context.state.analysis.module.filename,
|
|
@@ -512,7 +527,8 @@ const visitors = {
|
|
|
512
527
|
error(
|
|
513
528
|
'Component switch statements must contain a template or an await expression in each of their cases. Move the switch statement into an effect if it does not render anything.',
|
|
514
529
|
context.state.analysis.module.filename,
|
|
515
|
-
|
|
530
|
+
switch_case,
|
|
531
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
516
532
|
);
|
|
517
533
|
}
|
|
518
534
|
}
|
|
@@ -584,30 +600,106 @@ const visitors = {
|
|
|
584
600
|
error(
|
|
585
601
|
'Component for...of loops must contain a template or an await expression in their body. Move the for loop into an effect if it does not render anything.',
|
|
586
602
|
context.state.analysis.module.filename,
|
|
587
|
-
node,
|
|
603
|
+
node.body,
|
|
604
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
588
605
|
);
|
|
589
606
|
}
|
|
590
607
|
},
|
|
591
608
|
|
|
592
609
|
ExportNamedDeclaration(node, context) {
|
|
593
|
-
|
|
610
|
+
const server_block = context.state.ancestor_server_block;
|
|
611
|
+
|
|
612
|
+
if (!server_block) {
|
|
594
613
|
return context.next();
|
|
595
614
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
);
|
|
615
|
+
|
|
616
|
+
const exports = server_block.metadata.exports;
|
|
599
617
|
const declaration = /** @type {AST.RippleExportNamedDeclaration} */ (node).declaration;
|
|
600
618
|
|
|
601
619
|
if (declaration && declaration.type === 'FunctionDeclaration') {
|
|
602
|
-
|
|
620
|
+
exports.add(declaration.id.name);
|
|
603
621
|
} else if (declaration && declaration.type === 'Component') {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
622
|
+
error(
|
|
623
|
+
'Not implemented: Exported component declaration not supported in server blocks.',
|
|
624
|
+
context.state.analysis.module.filename,
|
|
625
|
+
/** @type {AST.Identifier} */ (declaration.id),
|
|
626
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
627
|
+
);
|
|
628
|
+
// TODO: the client and server rendering doesn't currently support components
|
|
629
|
+
// If we're going to support this, we need to account also for anonymous object declaration
|
|
630
|
+
// and specifiers
|
|
631
|
+
// exports.add(/** @type {AST.Identifier} */ (declaration.id).name);
|
|
632
|
+
} else if (declaration && declaration.type === 'VariableDeclaration') {
|
|
633
|
+
for (const decl of declaration.declarations) {
|
|
634
|
+
if (decl.init !== undefined && decl.init !== null) {
|
|
635
|
+
if (decl.id.type === 'Identifier') {
|
|
636
|
+
if (
|
|
637
|
+
decl.init.type === 'FunctionExpression' ||
|
|
638
|
+
decl.init.type === 'ArrowFunctionExpression'
|
|
639
|
+
) {
|
|
640
|
+
exports.add(decl.id.name);
|
|
641
|
+
continue;
|
|
642
|
+
} else if (decl.init.type === 'Identifier') {
|
|
643
|
+
const name = decl.init.name;
|
|
644
|
+
const binding = context.state.scope.get(name);
|
|
645
|
+
if (binding && is_binding_function(binding, context.state.scope)) {
|
|
646
|
+
exports.add(decl.id.name);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
} else if (decl.init.type === 'MemberExpression') {
|
|
650
|
+
error(
|
|
651
|
+
'Not implemented: Exported member expressions are not supported in server blocks.',
|
|
652
|
+
context.state.analysis.module.filename,
|
|
653
|
+
decl.init,
|
|
654
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
655
|
+
);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
} else if (decl.id.type === 'ObjectPattern' || decl.id.type === 'ArrayPattern') {
|
|
659
|
+
const paths = extract_paths(decl.id);
|
|
660
|
+
for (const path of paths) {
|
|
661
|
+
error(
|
|
662
|
+
'Not implemented: Exported object or array patterns are not supported in server blocks.',
|
|
663
|
+
context.state.analysis.module.filename,
|
|
664
|
+
path.node,
|
|
665
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// TODO: allow exporting consts when hydration is supported
|
|
671
|
+
error(
|
|
672
|
+
`Not implemented: Exported '${decl.id.type}' type is not supported in server blocks.`,
|
|
673
|
+
context.state.analysis.module.filename,
|
|
674
|
+
decl,
|
|
675
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
} else if (node.specifiers) {
|
|
679
|
+
for (const specifier of node.specifiers) {
|
|
680
|
+
const name = /** @type {AST.Identifier} */ (specifier.local).name;
|
|
681
|
+
const binding = context.state.scope.get(name);
|
|
682
|
+
const is_function = binding && is_binding_function(binding, context.state.scope);
|
|
683
|
+
|
|
684
|
+
if (is_function) {
|
|
685
|
+
exports.add(name);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
error(
|
|
690
|
+
`Not implemented: Exported specifier type not supported in server blocks.`,
|
|
691
|
+
context.state.analysis.module.filename,
|
|
692
|
+
specifier,
|
|
693
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
694
|
+
);
|
|
607
695
|
}
|
|
608
696
|
} else {
|
|
609
|
-
|
|
610
|
-
|
|
697
|
+
error(
|
|
698
|
+
'Not implemented: Exported declaration type not supported in server blocks.',
|
|
699
|
+
context.state.analysis.module.filename,
|
|
700
|
+
node,
|
|
701
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
702
|
+
);
|
|
611
703
|
}
|
|
612
704
|
|
|
613
705
|
return context.next();
|
|
@@ -638,11 +730,12 @@ const visitors = {
|
|
|
638
730
|
|
|
639
731
|
context.visit(node.consequent, context.state);
|
|
640
732
|
|
|
641
|
-
if (!node.metadata.has_template
|
|
733
|
+
if (!node.metadata.has_template) {
|
|
642
734
|
error(
|
|
643
|
-
'Component if statements must contain a template
|
|
735
|
+
'Component if statements must contain a template in their "then" body. Move the if statement into an effect if it does not render anything.',
|
|
644
736
|
context.state.analysis.module.filename,
|
|
645
|
-
node,
|
|
737
|
+
node.consequent,
|
|
738
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
646
739
|
);
|
|
647
740
|
}
|
|
648
741
|
|
|
@@ -651,11 +744,12 @@ const visitors = {
|
|
|
651
744
|
node.metadata.has_await = false;
|
|
652
745
|
context.visit(node.alternate, context.state);
|
|
653
746
|
|
|
654
|
-
if (!node.metadata.has_template
|
|
747
|
+
if (!node.metadata.has_template) {
|
|
655
748
|
error(
|
|
656
|
-
'Component if statements must contain a template
|
|
749
|
+
'Component if statements must contain a template in their "else" body. Move the if statement into an effect if it does not render anything.',
|
|
657
750
|
context.state.analysis.module.filename,
|
|
658
|
-
node,
|
|
751
|
+
node.alternate,
|
|
752
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
659
753
|
);
|
|
660
754
|
}
|
|
661
755
|
}
|
|
@@ -680,11 +774,12 @@ const visitors = {
|
|
|
680
774
|
|
|
681
775
|
context.visit(node.block, state);
|
|
682
776
|
|
|
683
|
-
if (!node.metadata.has_template
|
|
777
|
+
if (!node.metadata.has_template) {
|
|
684
778
|
error(
|
|
685
779
|
'Component try statements must contain a template in their main body. Move the try statement into an effect if it does not render anything.',
|
|
686
780
|
state.analysis.module.filename,
|
|
687
|
-
node,
|
|
781
|
+
node.block,
|
|
782
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
688
783
|
);
|
|
689
784
|
}
|
|
690
785
|
|
|
@@ -695,11 +790,12 @@ const visitors = {
|
|
|
695
790
|
|
|
696
791
|
context.visit(node.pending, state);
|
|
697
792
|
|
|
698
|
-
if (!node.metadata.has_template
|
|
793
|
+
if (!node.metadata.has_template) {
|
|
699
794
|
error(
|
|
700
795
|
'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
|
|
701
796
|
state.analysis.module.filename,
|
|
702
|
-
node,
|
|
797
|
+
node.pending,
|
|
798
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
703
799
|
);
|
|
704
800
|
}
|
|
705
801
|
}
|
|
@@ -711,6 +807,8 @@ const visitors = {
|
|
|
711
807
|
|
|
712
808
|
ForInStatement(node, context) {
|
|
713
809
|
if (is_inside_component(context)) {
|
|
810
|
+
// TODO: it's a fatal error for now but
|
|
811
|
+
// we could implement the for in loop for the ts mode only to make it a usage error
|
|
714
812
|
error(
|
|
715
813
|
'For...in loops are not supported in components. Use for...of instead.',
|
|
716
814
|
context.state.analysis.module.filename,
|
|
@@ -727,6 +825,7 @@ const visitors = {
|
|
|
727
825
|
if (inside_tsx_compat) {
|
|
728
826
|
return context.next();
|
|
729
827
|
}
|
|
828
|
+
// TODO: could compile it as something to avoid a fatal error
|
|
730
829
|
error(
|
|
731
830
|
'Elements cannot be used as generic expressions, only as statements within a component',
|
|
732
831
|
context.state.analysis.module.filename,
|
|
@@ -777,9 +876,11 @@ const visitors = {
|
|
|
777
876
|
if (node.id.name === 'head') {
|
|
778
877
|
// head validation
|
|
779
878
|
if (node.attributes.length > 0) {
|
|
879
|
+
// TODO: could transform attributes as something, e.g. Text Node, and avoid a fatal error
|
|
780
880
|
error('<head> cannot have any attributes', state.analysis.module.filename, node);
|
|
781
881
|
}
|
|
782
882
|
if (node.children.length === 0) {
|
|
883
|
+
// TODO: could transform children as something, e.g. Text Node, and avoid a fatal error
|
|
783
884
|
error('<head> must have children', state.analysis.module.filename, node);
|
|
784
885
|
}
|
|
785
886
|
|
|
@@ -794,6 +895,7 @@ const visitors = {
|
|
|
794
895
|
const children = normalize_children(node.children, context);
|
|
795
896
|
|
|
796
897
|
if (children.length !== 1 || children[0].type !== 'Text') {
|
|
898
|
+
// TODO: could transform children as something, e.g. Text Node, and avoid a fatal error
|
|
797
899
|
error(
|
|
798
900
|
'<title> must have only contain text nodes',
|
|
799
901
|
state.analysis.module.filename,
|
|
@@ -804,6 +906,7 @@ const visitors = {
|
|
|
804
906
|
|
|
805
907
|
// check for invalid elements in head
|
|
806
908
|
if (!valid_in_head.has(node.id.name)) {
|
|
909
|
+
// TODO: could transform invalid elements as something, e.g. Text Node, and avoid a fatal error
|
|
807
910
|
error(`<${node.id.name}> cannot be used in <head>`, state.analysis.module.filename, node);
|
|
808
911
|
}
|
|
809
912
|
}
|
|
@@ -824,6 +927,7 @@ const visitors = {
|
|
|
824
927
|
'The `key` attribute is not a thing in Ripple, and cannot be used on DOM elements. If you are using a for loop, then use the `for (let item of items; key item.id)` syntax.',
|
|
825
928
|
state.analysis.module.filename,
|
|
826
929
|
attr,
|
|
930
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
827
931
|
);
|
|
828
932
|
}
|
|
829
933
|
|
|
@@ -850,6 +954,7 @@ const visitors = {
|
|
|
850
954
|
`The <${node.id.name}> element is a void element and cannot have children`,
|
|
851
955
|
state.analysis.module.filename,
|
|
852
956
|
node,
|
|
957
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
853
958
|
);
|
|
854
959
|
}
|
|
855
960
|
} else {
|
|
@@ -867,30 +972,31 @@ const visitors = {
|
|
|
867
972
|
visit(attr.argument, state);
|
|
868
973
|
}
|
|
869
974
|
}
|
|
870
|
-
|
|
871
|
-
let
|
|
975
|
+
/** @type {(AST.Node | AST.Expression)[]} */
|
|
976
|
+
let implicit_children = [];
|
|
977
|
+
/** @type {AST.Identifier[]} */
|
|
978
|
+
let explicit_children = [];
|
|
872
979
|
|
|
873
980
|
for (const child of node.children) {
|
|
874
981
|
if (child.type === 'Component') {
|
|
875
982
|
if (child.id?.name === 'children') {
|
|
876
|
-
explicit_children
|
|
877
|
-
if (implicit_children) {
|
|
878
|
-
error(
|
|
879
|
-
'Cannot have both implicit and explicit children',
|
|
880
|
-
state.analysis.module.filename,
|
|
881
|
-
node,
|
|
882
|
-
);
|
|
883
|
-
}
|
|
983
|
+
explicit_children.push(child.id);
|
|
884
984
|
}
|
|
885
985
|
} else if (child.type !== 'EmptyStatement') {
|
|
886
|
-
implicit_children
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
986
|
+
implicit_children.push(
|
|
987
|
+
child.type === 'Text' || child.type === 'Html' ? child.expression : child,
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (implicit_children.length > 0 && explicit_children.length > 0) {
|
|
993
|
+
for (const item of [...explicit_children, ...implicit_children]) {
|
|
994
|
+
error(
|
|
995
|
+
'Cannot have both implicit and explicit children',
|
|
996
|
+
state.analysis.module.filename,
|
|
997
|
+
item,
|
|
998
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
999
|
+
);
|
|
894
1000
|
}
|
|
895
1001
|
}
|
|
896
1002
|
}
|
|
@@ -904,6 +1010,7 @@ const visitors = {
|
|
|
904
1010
|
'Cannot have a `children` prop on an element',
|
|
905
1011
|
state.analysis.module.filename,
|
|
906
1012
|
attribute,
|
|
1013
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
907
1014
|
);
|
|
908
1015
|
}
|
|
909
1016
|
}
|
|
@@ -929,11 +1036,17 @@ const visitors = {
|
|
|
929
1036
|
}
|
|
930
1037
|
|
|
931
1038
|
if (parent_block !== null && parent_block.type !== 'Component') {
|
|
932
|
-
if (context.state.
|
|
1039
|
+
if (!context.state.ancestor_server_block) {
|
|
1040
|
+
// we want the error to live on the `await` keyword vs the whole expression
|
|
1041
|
+
const adjusted_node /** @type {AST.AwaitExpression} */ = {
|
|
1042
|
+
...node,
|
|
1043
|
+
end: /** @type {AST.NodeWithLocation} */ (node).start + 'await'.length,
|
|
1044
|
+
};
|
|
933
1045
|
error(
|
|
934
1046
|
'`await` is not allowed in client-side control-flow statements',
|
|
935
1047
|
context.state.analysis.module.filename,
|
|
936
|
-
|
|
1048
|
+
adjusted_node,
|
|
1049
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
937
1050
|
);
|
|
938
1051
|
}
|
|
939
1052
|
}
|
|
@@ -971,6 +1084,7 @@ export function analyze(ast, filename, options = {}) {
|
|
|
971
1084
|
metadata: {
|
|
972
1085
|
serverIdentifierPresent: false,
|
|
973
1086
|
},
|
|
1087
|
+
errors: [],
|
|
974
1088
|
});
|
|
975
1089
|
|
|
976
1090
|
walk(
|
|
@@ -981,7 +1095,7 @@ export function analyze(ast, filename, options = {}) {
|
|
|
981
1095
|
scopes,
|
|
982
1096
|
analysis,
|
|
983
1097
|
inside_head: false,
|
|
984
|
-
|
|
1098
|
+
ancestor_server_block: undefined,
|
|
985
1099
|
to_ts: options.to_ts ?? false,
|
|
986
1100
|
loose: options.loose ?? false,
|
|
987
1101
|
metadata: {},
|