tutuca 0.9.21 → 0.9.23

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.
@@ -67,13 +67,10 @@ MODULE CONVENTION
67
67
  -> Component[] // results of tutuca's component()
68
68
 
69
69
  export function getExamples() // required for render/doctor
70
- -> {
71
- title: string,
72
- description?: string,
73
- groups?: [{ title, description?, items: Example[] }],
74
- items: Example[] // at least one of items/groups
75
- }
76
- where Example = { title, description?, value, view? }
70
+ -> Section | Section[] // single section or an array
71
+ where Section = { title: string, description?: string,
72
+ items: Example[] }
73
+ and Example = { title, description?, value, view? }
77
74
  and value = an instance returned by <Component>.make(...)
78
75
  and view = a view name (defaults to "main")
79
76
 
@@ -85,21 +82,20 @@ MODULE CONVENTION
85
82
 
86
83
  export function getRoot() // optional; returned by info
87
84
 
88
- The legacy flat \`getExamples() -> Example[]\` shape and the legacy
89
- \`getStoryBookSection()\` name both fail fast with EXAMPLES_SHAPE_MISMATCH.
85
+ The legacy \`getStoryBookSection()\` name fails fast with
86
+ EXAMPLES_SHAPE_MISMATCH rename it to \`getExamples\`.
90
87
 
91
88
  COMMANDS (require <module-path>)
92
89
  info
93
90
  Summarize which getX() exports are present and count components,
94
- macros, request handlers, examples, and groups. Good first step.
91
+ macros, request handlers, examples, and sections. Good first step.
95
92
 
96
93
  list
97
94
  List each component with its declared views and fields (name, type).
98
95
 
99
96
  examples
100
- Print the module's example section: title, description, flat items
101
- and/or grouped items. Each item shows its resolved component name
102
- and view.
97
+ Print the module's example sections: title, description, items.
98
+ Each item shows its resolved component name and view.
103
99
 
104
100
  docs [name]
105
101
  Generate API docs (methods, input handlers, fields with their
@@ -219,8 +215,8 @@ class ComponentList {
219
215
  }
220
216
 
221
217
  class ExampleIndex {
222
- constructor({ section }) {
223
- this.section = section;
218
+ constructor({ sections }) {
219
+ this.sections = sections;
224
220
  }
225
221
  }
226
222
 
@@ -231,10 +227,11 @@ class ComponentDocs {
231
227
  }
232
228
 
233
229
  class LintFinding {
234
- constructor({ id, level, info }) {
230
+ constructor({ id, level, info, context = {} }) {
235
231
  this.id = id;
236
232
  this.level = level;
237
233
  this.info = info;
234
+ this.context = context;
238
235
  }
239
236
  }
240
237
 
@@ -268,7 +265,6 @@ class LintReport {
268
265
 
269
266
  class RenderedExample {
270
267
  constructor({
271
- groupTitle = null,
272
268
  title,
273
269
  description = null,
274
270
  componentName,
@@ -276,7 +272,6 @@ class RenderedExample {
276
272
  html,
277
273
  error = null
278
274
  }) {
279
- this.groupTitle = groupTitle;
280
275
  this.title = title;
281
276
  this.description = description;
282
277
  this.componentName = componentName;
@@ -286,13 +281,20 @@ class RenderedExample {
286
281
  }
287
282
  }
288
283
 
289
- class RenderBatch {
290
- constructor({ section, items }) {
291
- this.section = section;
284
+ class RenderedSection {
285
+ constructor({ title, description = null, items }) {
286
+ this.title = title;
287
+ this.description = description;
292
288
  this.items = items;
293
289
  }
290
+ }
291
+
292
+ class RenderBatch {
293
+ constructor({ sections }) {
294
+ this.sections = sections;
295
+ }
294
296
  get hasErrors() {
295
- return this.items.some((i) => i.error !== null);
297
+ return this.sections.some((s) => s.items.some((i) => i.error !== null));
296
298
  }
297
299
  }
298
300
 
@@ -326,59 +328,41 @@ function resolveComponentName(value, components) {
326
328
  return null;
327
329
  }
328
330
 
329
- class ExampleGroup {
330
- constructor({ title, description = null, items }) {
331
- this.title = title;
332
- this.description = description;
333
- this.items = items;
334
- }
335
- }
336
-
337
331
  class ExampleSection {
338
- constructor({ title, description = null, groups = [], items = [] }) {
332
+ constructor({ title, description = null, items = [] }) {
339
333
  this.title = title;
340
334
  this.description = description;
341
- this.groups = groups;
342
335
  this.items = items;
343
336
  }
344
- *flatten() {
345
- for (const item of this.items) {
346
- yield { groupTitle: null, example: item };
347
- }
348
- for (const group of this.groups) {
349
- for (const item of group.items) {
350
- yield { groupTitle: group.title, example: item };
351
- }
352
- }
353
- }
354
337
  }
355
338
 
356
339
  class NormalizedModule {
357
- constructor({ mod, path = null, components, macros, requestHandlers, section, root }) {
340
+ constructor({ mod, path = null, components, macros, requestHandlers, sections, root }) {
358
341
  this.mod = mod;
359
342
  this.path = path;
360
343
  this.components = components;
361
344
  this.macros = macros;
362
345
  this.requestHandlers = requestHandlers;
363
- this.section = section;
346
+ this.sections = sections;
364
347
  this.root = root;
365
348
  }
366
349
  }
367
- function parseExample(raw, index, components) {
350
+ function shapeError(message, where) {
351
+ const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: ${message}`);
352
+ err.code = EXAMPLES_SHAPE_MISMATCH;
353
+ err.where = where;
354
+ return err;
355
+ }
356
+ function parseExample(raw, index, components, parentPath) {
357
+ const where = `${parentPath}.items[${index}]`;
368
358
  if (!raw || typeof raw !== "object") {
369
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: example at index ${index} is not an object`);
370
- err.code = EXAMPLES_SHAPE_MISMATCH;
371
- throw err;
359
+ throw shapeError(`example at ${where} is not an object`, where);
372
360
  }
373
361
  if (raw.value === undefined && raw.item !== undefined) {
374
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: example at index ${index} uses legacy "item" key; rename to "value"`);
375
- err.code = EXAMPLES_SHAPE_MISMATCH;
376
- throw err;
362
+ throw shapeError(`example at ${where} uses legacy "item" key; rename to "value"`, where);
377
363
  }
378
364
  if (raw.value === undefined) {
379
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: example at index ${index} missing "value"`);
380
- err.code = EXAMPLES_SHAPE_MISMATCH;
381
- throw err;
365
+ throw shapeError(`example at ${where} missing "value"`, where);
382
366
  }
383
367
  return new Example({
384
368
  title: raw.title ?? `Example ${index + 1}`,
@@ -388,42 +372,29 @@ function parseExample(raw, index, components) {
388
372
  componentName: resolveComponentName(raw.value, components)
389
373
  });
390
374
  }
391
- function parseGroup(raw, index, components) {
392
- if (!raw || typeof raw !== "object" || !Array.isArray(raw.items)) {
393
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: group at index ${index} must have an "items" array`);
394
- err.code = EXAMPLES_SHAPE_MISMATCH;
395
- throw err;
375
+ function parseSection(raw, components, where) {
376
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
377
+ throw shapeError(`section at ${where} must be an object { title, description?, items }`, where);
378
+ }
379
+ const items = Array.isArray(raw.items) ? raw.items.map((e, i) => parseExample(e, i, components, where)) : [];
380
+ if (items.length === 0) {
381
+ throw shapeError(`section at ${where} has no items`, where);
396
382
  }
397
- return new ExampleGroup({
398
- title: raw.title ?? `Group ${index + 1}`,
383
+ return new ExampleSection({
384
+ title: raw.title ?? "Examples",
399
385
  description: raw.description ?? null,
400
- items: raw.items.map((e, i) => parseExample(e, i, components))
386
+ items
401
387
  });
402
388
  }
403
- function parseSection(raw, components) {
389
+ function parseSections(raw, components) {
390
+ const where = "examples";
404
391
  if (Array.isArray(raw)) {
405
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: getExamples() returned a flat array; expected a section ` + `object { title, description?, groups?, items }.`);
406
- err.code = EXAMPLES_SHAPE_MISMATCH;
407
- throw err;
392
+ return raw.map((r, i) => parseSection(r, components, `${where}[${i}]`));
408
393
  }
409
394
  if (!raw || typeof raw !== "object") {
410
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: getExamples() must return a section object`);
411
- err.code = EXAMPLES_SHAPE_MISMATCH;
412
- throw err;
413
- }
414
- const items = Array.isArray(raw.items) ? raw.items.map((e, i) => parseExample(e, i, components)) : [];
415
- const groups = Array.isArray(raw.groups) ? raw.groups.map((g, i) => parseGroup(g, i, components)) : [];
416
- if (items.length === 0 && groups.length === 0) {
417
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: getExamples() returned a section with no items or groups`);
418
- err.code = EXAMPLES_SHAPE_MISMATCH;
419
- throw err;
395
+ throw shapeError("getExamples() must return a section object or an array of section objects", where);
420
396
  }
421
- return new ExampleSection({
422
- title: raw.title ?? "Examples",
423
- description: raw.description ?? null,
424
- groups,
425
- items
426
- });
397
+ return [parseSection(raw, components, where)];
427
398
  }
428
399
  function normalizeModule(mod, { path = null } = {}) {
429
400
  const present = new Set;
@@ -439,15 +410,13 @@ function normalizeModule(mod, { path = null } = {}) {
439
410
  present.add(key);
440
411
  }
441
412
  if (present.has("getStoryBookSection") && !present.has("getExamples")) {
442
- const err = new Error(`${EXAMPLES_SHAPE_MISMATCH}: module exports getStoryBookSection; rename it to getExamples.`);
443
- err.code = EXAMPLES_SHAPE_MISMATCH;
444
- throw err;
413
+ throw shapeError("module exports getStoryBookSection; rename it to getExamples.", "module");
445
414
  }
446
415
  const components = present.has("getComponents") ? mod.getComponents() : [];
447
416
  const macros = present.has("getMacros") ? mod.getMacros() : null;
448
417
  const requestHandlers = present.has("getRequestHandlers") ? mod.getRequestHandlers() : null;
449
418
  const root = present.has("getRoot") ? mod.getRoot() : null;
450
- const section = present.has("getExamples") ? parseSection(mod.getExamples(), components) : null;
419
+ const sections = present.has("getExamples") ? parseSections(mod.getExamples(), components) : [];
451
420
  return {
452
421
  normalized: new NormalizedModule({
453
422
  mod,
@@ -455,7 +424,7 @@ function normalizeModule(mod, { path = null } = {}) {
455
424
  components,
456
425
  macros,
457
426
  requestHandlers,
458
- section,
427
+ sections,
459
428
  root
460
429
  }),
461
430
  present
@@ -469,14 +438,17 @@ function describeModule(mod, { path = null } = {}) {
469
438
  components: normalized.components.length,
470
439
  macros: normalized.macros ? Object.keys(normalized.macros).length : 0,
471
440
  requestHandlers: normalized.requestHandlers ? Object.keys(normalized.requestHandlers).length : 0,
472
- examples: normalized.section ? normalized.section.items.length + normalized.section.groups.reduce((n, g) => n + g.items.length, 0) : 0,
473
- groups: normalized.section ? normalized.section.groups.length : 0
441
+ examples: normalized.sections.reduce((n, s) => n + s.items.length, 0),
442
+ sections: normalized.sections.length
474
443
  };
475
444
  const warnings = [];
476
445
  if (!normalized.components.length)
477
446
  warnings.push("module exports no components");
478
- if (!normalized.section)
447
+ if (!present.has("getExamples")) {
479
448
  warnings.push("module exports no getExamples()");
449
+ } else if (normalized.sections.length === 0) {
450
+ warnings.push("getExamples() returned no sections");
451
+ }
480
452
  return new ModuleInfo({ path, present, counts, warnings });
481
453
  }
482
454
 
@@ -517,7 +489,7 @@ function fmtModuleInfo(info) {
517
489
  lines.push(`Components: ${info.counts.components}`);
518
490
  lines.push(`Macros: ${info.counts.macros}`);
519
491
  lines.push(`Request handlers: ${info.counts.requestHandlers}`);
520
- lines.push(`Examples: ${info.counts.examples} (groups: ${info.counts.groups})`);
492
+ lines.push(`Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
521
493
  if (info.warnings.length) {
522
494
  lines.push("");
523
495
  lines.push("Warnings:");
@@ -541,18 +513,13 @@ function fmtComponentList(list) {
541
513
  `);
542
514
  }
543
515
  function fmtExampleIndex(idx) {
544
- if (!idx.section)
516
+ if (idx.sections.length === 0)
545
517
  return "(no examples)";
546
518
  const lines = [];
547
- const s = idx.section;
548
- lines.push(`${s.title}${s.description ? ` — ${s.description}` : ""}`);
549
- for (const item of s.items) {
550
- lines.push(` - ${item.title}${item.description ? ` — ${item.description}` : ""} [${item.componentName}${item.view !== "main" ? `/${item.view}` : ""}]`);
551
- }
552
- for (const group of s.groups) {
553
- lines.push(` [${group.title}]${group.description ? ` — ${group.description}` : ""}`);
554
- for (const item of group.items) {
555
- lines.push(` - ${item.title}${item.description ? ` — ${item.description}` : ""} [${item.componentName}${item.view !== "main" ? `/${item.view}` : ""}]`);
519
+ for (const s of idx.sections) {
520
+ lines.push(`${s.title}${s.description ? ` — ${s.description}` : ""}`);
521
+ for (const item of s.items) {
522
+ lines.push(` - ${item.title}${item.description ? ` — ${item.description}` : ""} [${item.componentName}${item.view !== "main" ? `/${item.view}` : ""}]`);
556
523
  }
557
524
  }
558
525
  return lines.join(`
@@ -604,8 +571,9 @@ function fmtLintReport(rep) {
604
571
  lines.push(`${c.componentName}:`);
605
572
  for (const f of c.findings) {
606
573
  const tag = f.level.toUpperCase();
574
+ const view = f.context?.viewName ? ` view=${f.context.viewName}` : "";
607
575
  const extra = fmtFindingInfo(f.info);
608
- lines.push(` [${tag}] ${f.id}${extra ? ` (${extra})` : ""}`);
576
+ lines.push(` [${tag}] ${f.id}${view}${extra ? ` (${extra})` : ""}`);
609
577
  }
610
578
  }
611
579
  lines.push("");
@@ -614,21 +582,16 @@ function fmtLintReport(rep) {
614
582
  `);
615
583
  }
616
584
  function fmtRenderBatch(batch) {
617
- if (batch.items.length === 0)
585
+ const totalItems = batch.sections.reduce((n, s) => n + s.items.length, 0);
586
+ if (totalItems === 0)
618
587
  return "(no examples rendered)";
619
588
  const lines = [];
620
- if (batch.section) {
621
- lines.push(`${batch.section.title}${batch.section.description ? ` — ${batch.section.description}` : ""}`);
622
- }
623
- let currentGroup = undefined;
624
- for (const item of batch.items) {
625
- if (item.groupTitle !== currentGroup) {
626
- currentGroup = item.groupTitle;
627
- if (currentGroup)
628
- lines.push(` [${currentGroup}]`);
589
+ for (const section of batch.sections) {
590
+ lines.push(`${section.title}${section.description ? ` — ${section.description}` : ""}`);
591
+ for (const item of section.items) {
592
+ const status = item.error ? `ERROR: ${item.error.message}` : `${item.html.length} bytes`;
593
+ lines.push(` ${item.title} [${item.componentName}] ${status}`);
629
594
  }
630
- const status = item.error ? `ERROR: ${item.error.message}` : `${item.html.length} bytes`;
631
- lines.push(` ${item.title} [${item.componentName}] — ${status}`);
632
595
  }
633
596
  return lines.join(`
634
597
  `);
@@ -711,67 +674,53 @@ function fmtComponentDocs2(docs) {
711
674
  async function fmtRenderBatch2(batch, { pretty = false } = {}) {
712
675
  const prettify = pretty ? (await import("prettier")).format : null;
713
676
  const lines = [];
714
- if (batch.section) {
715
- lines.push(`# ${batch.section.title}`);
716
- if (batch.section.description)
677
+ for (const section of batch.sections) {
678
+ lines.push(`# ${section.title}`);
679
+ if (section.description)
717
680
  lines.push(`
718
- ${batch.section.description}`);
681
+ ${section.description}`);
719
682
  lines.push("");
720
- }
721
- let currentGroup = undefined;
722
- for (const item of batch.items) {
723
- if (item.groupTitle !== currentGroup) {
724
- currentGroup = item.groupTitle;
725
- if (currentGroup)
726
- lines.push(`## ${currentGroup}
727
- `);
728
- }
729
- const depth = currentGroup ? "###" : "##";
730
- lines.push(`${depth} ${item.title}
683
+ for (const item of section.items) {
684
+ lines.push(`## ${item.title}
731
685
  `);
732
- if (item.description)
733
- lines.push(`${item.description}
686
+ if (item.description)
687
+ lines.push(`${item.description}
734
688
  `);
735
- if (item.error) {
736
- lines.push("```");
737
- lines.push(`ERROR: ${item.error.message}`);
689
+ if (item.error) {
690
+ lines.push("```");
691
+ lines.push(`ERROR: ${item.error.message}`);
692
+ lines.push("```\n");
693
+ continue;
694
+ }
695
+ let html = item.html;
696
+ if (prettify) {
697
+ try {
698
+ html = (await prettify(html, { parser: "html" })).trimEnd();
699
+ } catch {}
700
+ }
701
+ lines.push("```html");
702
+ lines.push(html);
738
703
  lines.push("```\n");
739
- continue;
740
- }
741
- let html = item.html;
742
- if (prettify) {
743
- try {
744
- html = (await prettify(html, { parser: "html" })).trimEnd();
745
- } catch {}
746
704
  }
747
- lines.push("```html");
748
- lines.push(html);
749
- lines.push("```\n");
750
705
  }
751
706
  return lines.join(`
752
707
  `);
753
708
  }
754
709
  function fmtExampleIndex2(idx) {
755
- if (!idx.section)
710
+ if (idx.sections.length === 0)
756
711
  return "_(no examples)_";
757
- const s = idx.section;
758
- const lines = [`# ${s.title}`];
759
- if (s.description)
760
- lines.push("", s.description);
761
- if (s.items.length) {
762
- lines.push("");
763
- for (const item of s.items) {
764
- lines.push(`- **${item.title}** \`${item.componentName}\`${item.description ? ` — ${item.description}` : ""}`);
712
+ const lines = [];
713
+ for (const s of idx.sections) {
714
+ lines.push(`# ${s.title}`);
715
+ if (s.description)
716
+ lines.push("", s.description);
717
+ if (s.items.length) {
718
+ lines.push("");
719
+ for (const item of s.items) {
720
+ lines.push(`- **${item.title}** — \`${item.componentName}\`${item.description ? ` — ${item.description}` : ""}`);
721
+ }
765
722
  }
766
- }
767
- for (const group of s.groups) {
768
- lines.push("", `## ${group.title}`);
769
- if (group.description)
770
- lines.push("", group.description);
771
723
  lines.push("");
772
- for (const item of group.items) {
773
- lines.push(`- **${item.title}** — \`${item.componentName}\`${item.description ? ` — ${item.description}` : ""}`);
774
- }
775
724
  }
776
725
  return lines.join(`
777
726
  `);
@@ -785,7 +734,8 @@ function fmtLintReport2(rep) {
785
734
  continue;
786
735
  }
787
736
  for (const f of c.findings) {
788
- lines.push(`- **${f.level.toUpperCase()}** \`${f.id}\``);
737
+ const view = f.context?.viewName ? ` — view: \`${f.context.viewName}\`` : "";
738
+ lines.push(`- **${f.level.toUpperCase()}** \`${f.id}\`${view}`);
789
739
  }
790
740
  lines.push("");
791
741
  }
@@ -799,7 +749,7 @@ function fmtModuleInfo2(info) {
799
749
  lines.push(`- Components: ${info.counts.components}`);
800
750
  lines.push(`- Macros: ${info.counts.macros}`);
801
751
  lines.push(`- Request handlers: ${info.counts.requestHandlers}`);
802
- lines.push(`- Examples: ${info.counts.examples} (groups: ${info.counts.groups})`);
752
+ lines.push(`- Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
803
753
  if (info.warnings.length) {
804
754
  lines.push("", "## Warnings", "");
805
755
  for (const w of info.warnings)
@@ -875,19 +825,22 @@ var supports4 = new Set(["RenderBatch"]);
875
825
  async function format4(result, { pretty = false } = {}) {
876
826
  const prettify = pretty ? (await import("prettier")).format : null;
877
827
  const parts = [];
878
- for (const item of result.items) {
879
- if (item.error) {
880
- parts.push(`<!-- ERROR ${item.title}: ${item.error.message} -->`);
881
- continue;
882
- }
883
- let html = item.html;
884
- if (prettify) {
885
- try {
886
- html = (await prettify(html, { parser: "html" })).trimEnd();
887
- } catch {}
828
+ for (const section of result.sections) {
829
+ parts.push(`<!-- === ${section.title} === -->`);
830
+ for (const item of section.items) {
831
+ if (item.error) {
832
+ parts.push(`<!-- ERROR ${item.title}: ${item.error.message} -->`);
833
+ continue;
834
+ }
835
+ let html = item.html;
836
+ if (prettify) {
837
+ try {
838
+ html = (await prettify(html, { parser: "html" })).trimEnd();
839
+ } catch {}
840
+ }
841
+ parts.push(`<!-- ${item.title} -->`);
842
+ parts.push(html);
888
843
  }
889
- parts.push(`<!-- ${item.title} -->`);
890
- parts.push(html);
891
844
  }
892
845
  return parts.join(`
893
846
  `);
@@ -1000,10 +953,7 @@ __export(exports_examples, {
1000
953
 
1001
954
  // tools/core/list-examples.js
1002
955
  function listExamples(normalized) {
1003
- if (!normalized.section) {
1004
- return new ExampleIndex({ section: null });
1005
- }
1006
- return new ExampleIndex({ section: normalized.section });
956
+ return new ExampleIndex({ sections: normalized.sections });
1007
957
  }
1008
958
 
1009
959
  // tools/cli/commands/examples.js
@@ -2895,18 +2845,20 @@ var LEVEL_WARN = "warn";
2895
2845
  var LEVEL_ERROR = "error";
2896
2846
  var LEVEL_HINT = "hint";
2897
2847
  function checkComponent(Comp, lx = new LintContext) {
2898
- const referencedAlters = new Set;
2899
- const referencedInputs = new Set;
2900
- const referencedComputed = new Set;
2901
- checkEventHandlersHaveImpls(lx, Comp, referencedInputs);
2902
- checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed);
2903
- for (const name in Comp.views) {
2904
- checkView(lx, Comp.views[name], Comp, referencedAlters, referencedComputed);
2905
- }
2906
- checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
2907
- checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
2908
- checkUnreferencedComputed(lx, Comp, referencedComputed);
2909
- return lx;
2848
+ return lx.push({ componentName: Comp.name }, () => {
2849
+ const referencedAlters = new Set;
2850
+ const referencedInputs = new Set;
2851
+ const referencedComputed = new Set;
2852
+ checkEventHandlersHaveImpls(lx, Comp, referencedInputs);
2853
+ checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed);
2854
+ for (const name in Comp.views) {
2855
+ lx.push({ viewName: name }, () => checkView(lx, Comp.views[name], Comp, referencedAlters, referencedComputed));
2856
+ }
2857
+ checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
2858
+ checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
2859
+ checkUnreferencedComputed(lx, Comp, referencedComputed);
2860
+ return lx;
2861
+ });
2910
2862
  }
2911
2863
  function checkView(lx, view, Comp, referencedAlters, referencedComputed) {
2912
2864
  checkRenderItInLoop(lx, view);
@@ -2914,17 +2866,45 @@ function checkView(lx, view, Comp, referencedAlters, referencedComputed) {
2914
2866
  checkKnownHandlerNames(lx, view, Comp, referencedAlters, referencedComputed);
2915
2867
  }
2916
2868
  function checkRenderItInLoop(lx, view) {
2917
- const { nodes } = view.ctx;
2918
- for (let i = 0;i < nodes.length; i++) {
2919
- const node = nodes[i];
2869
+ let hasRenderIt = false;
2870
+ for (const node of view.ctx.nodes) {
2920
2871
  if (node.constructor.name === "RenderItNode") {
2921
- const next = nodes[i + 1];
2922
- const nextName = next?.constructor.name;
2923
- if (nextName !== "EachNode" && nextName !== "RenderEachNode") {
2924
- lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node });
2925
- }
2872
+ hasRenderIt = true;
2873
+ break;
2926
2874
  }
2927
2875
  }
2876
+ if (!hasRenderIt)
2877
+ return;
2878
+ walkForRenderIt(lx, view.anode, 0);
2879
+ }
2880
+ function walkForRenderIt(lx, node, loopDepth) {
2881
+ if (node === null || node === undefined)
2882
+ return;
2883
+ switch (node.constructor.name) {
2884
+ case "RenderItNode":
2885
+ if (loopDepth === 0)
2886
+ lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node });
2887
+ return;
2888
+ case "EachNode":
2889
+ walkForRenderIt(lx, node.node, loopDepth + 1);
2890
+ return;
2891
+ case "ShowNode":
2892
+ case "HideNode":
2893
+ case "ScopeNode":
2894
+ case "SlotNode":
2895
+ case "PushViewNameNode":
2896
+ case "MacroNode":
2897
+ case "RenderOnceNode":
2898
+ walkForRenderIt(lx, node.node, loopDepth);
2899
+ return;
2900
+ case "DomNode":
2901
+ case "FragmentNode":
2902
+ for (const child of node.childs)
2903
+ walkForRenderIt(lx, child, loopDepth);
2904
+ return;
2905
+ default:
2906
+ return;
2907
+ }
2928
2908
  }
2929
2909
  var NO_WRAPPERS = {};
2930
2910
  function checkEventModifiers(lx, view) {
@@ -2981,46 +2961,48 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
2981
2961
  const { input, views, Class } = Comp;
2982
2962
  const { prototype: proto } = Class;
2983
2963
  for (const viewName in views) {
2984
- const view = views[viewName];
2985
- for (const event of view.ctx.events) {
2986
- for (const handler of event.handlers) {
2987
- const { handlerVal } = handler.handlerCall;
2988
- const hvName = handlerVal?.constructor.name;
2989
- if (hvName === "InputHandlerNameVal") {
2990
- referencedInputs?.add(handlerVal.name);
2991
- if (input[handlerVal.name] === undefined) {
2992
- lx.warn(INPUT_HANDLER_NOT_IMPLEMENTED, {
2993
- name: handlerVal.name,
2994
- handler,
2995
- event
2996
- });
2997
- if (proto[handlerVal.name] !== undefined) {
2998
- lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, {
2964
+ lx.push({ viewName }, () => {
2965
+ const view = views[viewName];
2966
+ for (const event of view.ctx.events) {
2967
+ for (const handler of event.handlers) {
2968
+ const { handlerVal } = handler.handlerCall;
2969
+ const hvName = handlerVal?.constructor.name;
2970
+ if (hvName === "InputHandlerNameVal") {
2971
+ referencedInputs?.add(handlerVal.name);
2972
+ if (input[handlerVal.name] === undefined) {
2973
+ lx.warn(INPUT_HANDLER_NOT_IMPLEMENTED, {
2999
2974
  name: handlerVal.name,
3000
2975
  handler,
3001
2976
  event
3002
2977
  });
2978
+ if (proto[handlerVal.name] !== undefined) {
2979
+ lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, {
2980
+ name: handlerVal.name,
2981
+ handler,
2982
+ event
2983
+ });
2984
+ }
3003
2985
  }
3004
- }
3005
- } else if (hvName === "RawFieldVal") {
3006
- referencedInputs?.add(handlerVal.name);
3007
- if (proto[handlerVal.name] === undefined) {
3008
- lx.warn(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, {
3009
- name: handlerVal.name,
3010
- handler,
3011
- event
3012
- });
3013
- if (input[handlerVal.name] !== undefined) {
3014
- lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, {
2986
+ } else if (hvName === "RawFieldVal") {
2987
+ referencedInputs?.add(handlerVal.name);
2988
+ if (proto[handlerVal.name] === undefined) {
2989
+ lx.warn(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, {
3015
2990
  name: handlerVal.name,
3016
2991
  handler,
3017
2992
  event
3018
2993
  });
2994
+ if (input[handlerVal.name] !== undefined) {
2995
+ lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, {
2996
+ name: handlerVal.name,
2997
+ handler,
2998
+ event
2999
+ });
3000
+ }
3019
3001
  }
3020
3002
  }
3021
3003
  }
3022
3004
  }
3023
- }
3005
+ });
3024
3006
  }
3025
3007
  }
3026
3008
  function checkConsistentAttrVal(lx, val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed) {
@@ -3069,39 +3051,41 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed) {
3069
3051
  const { prototype: proto } = Class;
3070
3052
  const { fields } = Class.getMetaClass();
3071
3053
  for (const viewName in views) {
3072
- const view = views[viewName];
3073
- for (const attr of view.ctx.attrs) {
3074
- const { attrs, wrapperAttrs, textChild } = attr;
3075
- if (attrs?.constructor.name === "DynAttrs") {
3076
- for (const attr2 of attrs.items) {
3077
- if (attr2?.constructor.name === "Attr") {
3078
- checkConsistentAttrVal(lx, attr2.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3054
+ lx.push({ viewName }, () => {
3055
+ const view = views[viewName];
3056
+ for (const attr of view.ctx.attrs) {
3057
+ const { attrs, wrapperAttrs, textChild } = attr;
3058
+ if (attrs?.constructor.name === "DynAttrs") {
3059
+ for (const attr2 of attrs.items) {
3060
+ if (attr2?.constructor.name === "Attr") {
3061
+ checkConsistentAttrVal(lx, attr2.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3062
+ }
3079
3063
  }
3080
3064
  }
3081
- }
3082
- if (wrapperAttrs !== null) {
3083
- for (const w of wrapperAttrs) {
3084
- if (w.name === "each") {
3085
- if (w.whenVal)
3086
- checkConsistentAttrVal(lx, w.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3087
- if (w.enrichWithVal)
3088
- checkConsistentAttrVal(lx, w.enrichWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3089
- if (w.loopWithVal)
3090
- checkConsistentAttrVal(lx, w.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3091
- } else if (w.name !== "scope") {
3092
- checkConsistentAttrVal(lx, w.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3065
+ if (wrapperAttrs !== null) {
3066
+ for (const w of wrapperAttrs) {
3067
+ if (w.name === "each") {
3068
+ if (w.whenVal)
3069
+ checkConsistentAttrVal(lx, w.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3070
+ if (w.enrichWithVal)
3071
+ checkConsistentAttrVal(lx, w.enrichWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3072
+ if (w.loopWithVal)
3073
+ checkConsistentAttrVal(lx, w.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3074
+ } else if (w.name !== "scope") {
3075
+ checkConsistentAttrVal(lx, w.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3076
+ }
3093
3077
  }
3094
3078
  }
3079
+ if (textChild) {
3080
+ checkConsistentAttrVal(lx, textChild, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3081
+ }
3095
3082
  }
3096
- if (textChild) {
3097
- checkConsistentAttrVal(lx, textChild, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3098
- }
3099
- }
3100
- for (const node of view.ctx.nodes) {
3101
- if (node.val) {
3102
- checkConsistentAttrVal(lx, node.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3083
+ for (const node of view.ctx.nodes) {
3084
+ if (node.val) {
3085
+ checkConsistentAttrVal(lx, node.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
3086
+ }
3103
3087
  }
3104
- }
3088
+ });
3105
3089
  }
3106
3090
  }
3107
3091
  function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
@@ -3129,6 +3113,16 @@ function checkUnreferencedComputed(lx, Comp, referencedComputed) {
3129
3113
  class LintContext {
3130
3114
  constructor() {
3131
3115
  this.reports = [];
3116
+ this.frame = {};
3117
+ }
3118
+ push(patch, fn) {
3119
+ const prev = this.frame;
3120
+ this.frame = { ...prev, ...patch };
3121
+ try {
3122
+ return fn();
3123
+ } finally {
3124
+ this.frame = prev;
3125
+ }
3132
3126
  }
3133
3127
  error(id, info) {
3134
3128
  this.report(id, info, LEVEL_ERROR);
@@ -3140,7 +3134,7 @@ class LintContext {
3140
3134
  this.report(id, info, LEVEL_HINT);
3141
3135
  }
3142
3136
  report(id, info = {}, level = LEVEL_ERROR) {
3143
- this.reports.push({ id, info, level });
3137
+ this.reports.push({ id, info, level, context: { ...this.frame } });
3144
3138
  }
3145
3139
  }
3146
3140
 
@@ -8810,36 +8804,39 @@ function renderToHTML(document2, components, macros, rootState, ParseContext2) {
8810
8804
 
8811
8805
  // tools/core/render.js
8812
8806
  function renderExamples(normalized, env, { name = null, title = null, view = null } = {}) {
8813
- const { section } = normalized;
8814
- if (!section) {
8815
- return new RenderBatch({ section: null, items: [] });
8816
- }
8817
- const items = [];
8818
- for (const { groupTitle, example } of section.flatten()) {
8819
- const componentName = example.componentName;
8820
- if (name !== null && componentName !== name)
8821
- continue;
8822
- if (title !== null && example.title !== title)
8823
- continue;
8824
- const viewName = view ?? example.view ?? "main";
8825
- let html = "";
8826
- let error = null;
8827
- try {
8828
- html = renderToHTML(env.document, normalized.components, normalized.macros, example.value, env.ParseContext);
8829
- } catch (e) {
8830
- error = { message: e.message, stack: e.stack };
8831
- }
8832
- items.push(new RenderedExample({
8833
- groupTitle,
8834
- title: example.title,
8835
- description: example.description,
8836
- componentName,
8837
- view: viewName,
8838
- html,
8839
- error
8807
+ const sections = [];
8808
+ for (const section of normalized.sections) {
8809
+ const items = [];
8810
+ for (const example of section.items) {
8811
+ const componentName = example.componentName;
8812
+ if (name !== null && componentName !== name)
8813
+ continue;
8814
+ if (title !== null && example.title !== title)
8815
+ continue;
8816
+ const viewName = view ?? example.view ?? "main";
8817
+ let html = "";
8818
+ let error = null;
8819
+ try {
8820
+ html = renderToHTML(env.document, normalized.components, normalized.macros, example.value, env.ParseContext);
8821
+ } catch (e) {
8822
+ error = { message: e.message, stack: e.stack };
8823
+ }
8824
+ items.push(new RenderedExample({
8825
+ title: example.title,
8826
+ description: example.description,
8827
+ componentName,
8828
+ view: viewName,
8829
+ html,
8830
+ error
8831
+ }));
8832
+ }
8833
+ sections.push(new RenderedSection({
8834
+ title: section.title,
8835
+ description: section.description,
8836
+ items
8840
8837
  }));
8841
8838
  }
8842
- return new RenderBatch({ section, items });
8839
+ return new RenderBatch({ sections });
8843
8840
  }
8844
8841
 
8845
8842
  // tools/cli/commands/render.js
@@ -8965,7 +8962,18 @@ async function main() {
8965
8962
  try {
8966
8963
  await cmd.run(commandArgs, opts);
8967
8964
  } catch (e) {
8968
- if (e?.code === "EXAMPLES_SHAPE_MISMATCH" || e?.code?.startsWith?.("ERR_PARSE_ARGS_")) {
8965
+ if (e?.code === "EXAMPLES_SHAPE_MISMATCH") {
8966
+ const parts = [];
8967
+ if (opts.module)
8968
+ parts.push(opts.module);
8969
+ if (e.where)
8970
+ parts.push(`@ ${e.where}`);
8971
+ const prefix = parts.length ? `[${parts.join(" ")}] ` : "";
8972
+ process.stderr.write(`tutuca: ${prefix}${e.message}
8973
+ `);
8974
+ process.exit(1);
8975
+ }
8976
+ if (e?.code?.startsWith?.("ERR_PARSE_ARGS_")) {
8969
8977
  process.stderr.write(`tutuca: ${e.message}
8970
8978
  `);
8971
8979
  process.exit(1);