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 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.192",
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.192"
95
+ "ripple": "0.2.194"
96
96
  }
97
97
  }
@@ -1,26 +1,45 @@
1
- /** @import * as AST from 'estree' */
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
- let errorMessage = message;
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
- if (node && node.loc) {
13
- // Use GitHub-style range format: filename#L39C24-L39C32
14
- const startLine = node.loc.start.line;
15
- const startColumn = node.loc.start.column;
16
- const endLine = node.loc.end.line;
17
- const endColumn = node.loc.end.column;
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
- const rangeInfo = `${filename}#L${startLine}C${startColumn}-L${endLine}C${endColumn}`;
20
- errorMessage += ` (${rangeInfo})`;
21
- } else {
22
- errorMessage += ` (${filename})`;
37
+ if (errors) {
38
+ error.type = 'usage';
39
+ errors.push(error);
40
+ return;
23
41
  }
24
42
 
25
- throw new Error(errorMessage);
43
+ error.type = 'fatal';
44
+ throw error;
26
45
  }
@@ -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
  /**
@@ -54,14 +54,16 @@ export function compile_to_volar_mappings(source, filename, options) {
54
54
  options?.minify_css ?? false,
55
55
  );
56
56
 
57
- // Create volar mappings with esrap source map for accurate positioning
58
- return 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
- );
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, { ...context.state, inside_server_block: true });
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.inside_server_block &&
141
- context.state.scope.server_block
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
- /** @type {ScopeInterface | null} */
151
- const parent_scope = current_scope.parent;
152
- if (parent_scope === null) {
153
- error(
154
- `Cannot reference client-side variable "${node.name}" from a server block`,
155
- context.state.analysis.module.filename,
156
- node,
157
- );
158
- }
159
- current_scope = parent_scope;
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
- return error(
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
- if (!context.state.loose) {
455
- for (const [className, property] of metadata.styleClasses) {
456
- if (!topScopedClasses?.has(className)) {
457
- return error(
458
- `CSS class ".${className}" does not exist in ${node.id?.name ? node.id.name : "this component's"} <style> block`,
459
- context.state.analysis.module.filename,
460
- property,
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
- node,
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
- if (!context.state.inside_server_block) {
610
+ const server_block = context.state.ancestor_server_block;
611
+
612
+ if (!server_block) {
594
613
  return context.next();
595
614
  }
596
- const server_block = /** @type {AST.ServerBlock} */ (
597
- context.path.find((n) => n.type === 'ServerBlock')
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
- server_block.metadata.exports.push(declaration.id.name);
620
+ exports.add(declaration.id.name);
603
621
  } else if (declaration && declaration.type === 'Component') {
604
- // Handle exported components in server blocks
605
- if (server_block) {
606
- server_block.metadata.exports.push(/** @type {AST.Identifier} */ (declaration.id).name);
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
- // TODO
610
- throw new Error('Not implemented: Exported declaration type not supported in server blocks.');
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 && !node.metadata.has_await) {
733
+ if (!node.metadata.has_template) {
642
734
  error(
643
- 'Component if statements must contain a template or an await expression in their "then" body. Move the if statement into an effect if it does not render anything.',
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 && !node.metadata.has_await) {
747
+ if (!node.metadata.has_template) {
655
748
  error(
656
- 'Component if statements must contain a template or an await expression in their "else" body. Move the if statement into an effect if it does not render anything.',
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 && !state.loose) {
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 && !state.loose) {
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
- let implicit_children = false;
871
- let explicit_children = false;
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 = true;
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 = true;
887
- if (explicit_children) {
888
- error(
889
- 'Cannot have both implicit and explicit children',
890
- state.analysis.module.filename,
891
- node,
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.inside_server_block === false) {
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
- node,
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
- inside_server_block: options.mode === 'server',
1098
+ ancestor_server_block: undefined,
985
1099
  to_ts: options.to_ts ?? false,
986
1100
  loose: options.loose ?? false,
987
1101
  metadata: {},