tutuca 0.9.92 → 0.9.94

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.
@@ -12684,6 +12684,8 @@ var PLACEHOLDERLESS_TEMPLATE_STRING = "PLACEHOLDERLESS_TEMPLATE_STRING";
12684
12684
  var UNKNOWN_COMPONENT_SPEC_KEY = "UNKNOWN_COMPONENT_SPEC_KEY";
12685
12685
  var COMP_FIELD_BAD_SHAPE = "COMP_FIELD_BAD_SHAPE";
12686
12686
  var ASYNC_HANDLER = "ASYNC_HANDLER";
12687
+ var TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE = "TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE";
12688
+ var GLOBAL_SELECTOR_IN_SCOPED_STYLE = "GLOBAL_SELECTOR_IN_SCOPED_STYLE";
12687
12689
  var X_KNOWN_OP_NAMES = new Set([
12688
12690
  "slot",
12689
12691
  "text",
@@ -12775,6 +12777,7 @@ function checkComponent(Comp, lx = new LintContext, { wellKnownExtras = EMPTY_SE
12775
12777
  checkProvidesAreAddressable(lx, Comp);
12776
12778
  checkLookupShapes(lx, Comp);
12777
12779
  checkHandlersNotAsync(lx, Comp);
12780
+ checkScopedStyleTopLevel(lx, Comp);
12778
12781
  const referencedAlters = new Set;
12779
12782
  const referencedInputs = new Set;
12780
12783
  const referencedDynamics = new Set;
@@ -13292,6 +13295,69 @@ function checkHandlersNotAsync(lx, Comp) {
13292
13295
  }
13293
13296
  }
13294
13297
  }
13298
+ var NON_NESTABLE_AT_RULE = /(?:^|[\s;{}])@(?:-[a-z]+-)?(charset|import|namespace|font-face|keyframes|page|property|counter-style|font-feature-values|font-palette-values|view-transition)\b/gi;
13299
+ var GLOBAL_LEADING_SELECTOR = /(?:^|[{}])\s*(html|body|:root)\b[^{};]*\{/gi;
13300
+ var IGNORE_DIRECTIVE = /\/\*\s*tutuca-lint-ignore\s*\*\//g;
13301
+ var blankRun = (m) => m.replace(/[^\n]/g, " ");
13302
+ function blankCssNoise(css) {
13303
+ return css.replace(/\/\*[\s\S]*?\*\//g, blankRun).replace(/"(?:[^"\\\n]|\\.)*"/g, blankRun).replace(/'(?:[^'\\\n]|\\.)*'/g, blankRun);
13304
+ }
13305
+ function offsetToLineCol2(str, index) {
13306
+ let line = 1;
13307
+ let lineStart = 0;
13308
+ for (let i = 0;i < index; i++) {
13309
+ if (str[i] === `
13310
+ `) {
13311
+ line++;
13312
+ lineStart = i + 1;
13313
+ }
13314
+ }
13315
+ return { line, column: index - lineStart + 1 };
13316
+ }
13317
+ function suppressedLines(css) {
13318
+ const lines = new Set;
13319
+ IGNORE_DIRECTIVE.lastIndex = 0;
13320
+ for (let m = IGNORE_DIRECTIVE.exec(css);m !== null; m = IGNORE_DIRECTIVE.exec(css)) {
13321
+ lines.add(offsetToLineCol2(css, m.index).line);
13322
+ }
13323
+ return lines;
13324
+ }
13325
+ var STYLE_TO_GLOBAL_HELP = "Move it to the component's 'globalStyle' key, which is injected without the " + "component-scoping wrapper. To suppress a false positive, put a " + "/* tutuca-lint-ignore */ comment on the same line.";
13326
+ function scanScopedCss(lx, css, key) {
13327
+ if (!css)
13328
+ return;
13329
+ const ignore = suppressedLines(css);
13330
+ const clean = blankCssNoise(css);
13331
+ const report = (id, info) => {
13332
+ if (ignore.has(info.location.line))
13333
+ return;
13334
+ lx.error(id, { key, ...info }, { kind: "rephrase", text: STYLE_TO_GLOBAL_HELP });
13335
+ };
13336
+ NON_NESTABLE_AT_RULE.lastIndex = 0;
13337
+ for (let m = NON_NESTABLE_AT_RULE.exec(clean);m !== null; m = NON_NESTABLE_AT_RULE.exec(clean)) {
13338
+ const at = m.index + m[0].indexOf("@");
13339
+ report(TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE, {
13340
+ atRule: m[1].toLowerCase(),
13341
+ location: offsetToLineCol2(clean, at)
13342
+ });
13343
+ }
13344
+ GLOBAL_LEADING_SELECTOR.lastIndex = 0;
13345
+ for (let m = GLOBAL_LEADING_SELECTOR.exec(clean);m !== null; m = GLOBAL_LEADING_SELECTOR.exec(clean)) {
13346
+ const at = m.index + m[0].indexOf(m[1]);
13347
+ report(GLOBAL_SELECTOR_IN_SCOPED_STYLE, {
13348
+ selector: m[1].toLowerCase(),
13349
+ location: offsetToLineCol2(clean, at)
13350
+ });
13351
+ }
13352
+ }
13353
+ function checkScopedStyleTopLevel(lx, Comp) {
13354
+ scanScopedCss(lx, Comp.commonStyle, "commonStyle");
13355
+ for (const name in Comp.views) {
13356
+ const { style } = Comp.views[name];
13357
+ if (style)
13358
+ lx.push({ viewName: name }, () => scanScopedCss(lx, style, "style"));
13359
+ }
13360
+ }
13295
13361
  function checkProvidesAreAddressable(lx, Comp) {
13296
13362
  for (const name in Comp._rawProvide) {
13297
13363
  if (Comp.provide[name] === undefined) {
@@ -13385,1382 +13451,1546 @@ class LintParseContext extends ParseContext {
13385
13451
  }
13386
13452
  }
13387
13453
 
13388
- // tools/core/results.js
13389
- class ModuleInfo {
13390
- constructor({ path = null, present = new Set, counts = {}, warnings = [] }) {
13391
- this.path = path;
13392
- this.present = present;
13393
- this.counts = counts;
13394
- this.warnings = warnings;
13395
- }
13396
- }
13397
-
13398
- class ComponentSummary {
13399
- constructor({ name, views, fields }) {
13400
- this.name = name;
13401
- this.views = views;
13402
- this.fields = fields;
13403
- }
13404
- }
13405
-
13406
- class ComponentList {
13407
- constructor({ items, total = null, truncated = false }) {
13408
- this.items = items;
13409
- this.total = total ?? items.length;
13410
- this.truncated = truncated;
13454
+ // src/components.js
13455
+ class Components {
13456
+ constructor() {
13457
+ this.getComponentSymbol = Symbol("getComponent");
13458
+ this.byId = new Map;
13411
13459
  }
13412
- }
13413
-
13414
- class ExampleIndex {
13415
- constructor({ sections, total = null, truncated = false }) {
13416
- this.sections = sections;
13417
- this.total = total ?? sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
13418
- this.truncated = truncated;
13460
+ registerComponent(comp) {
13461
+ comp.Class.prototype[this.getComponentSymbol] = () => comp;
13462
+ this.byId.set(comp.id, comp);
13419
13463
  }
13420
- }
13421
-
13422
- class ComponentDocs {
13423
- constructor({ items }) {
13424
- this.items = items;
13464
+ getComponentForId(id) {
13465
+ return this.byId.get(id) ?? null;
13425
13466
  }
13426
- }
13427
-
13428
- class LintFinding {
13429
- constructor({ id, level, info, context = {}, suggestion = null }) {
13430
- this.id = id;
13431
- this.level = level;
13432
- this.info = info;
13433
- this.context = context;
13434
- this.suggestion = suggestion;
13467
+ getCompFor(v) {
13468
+ return v?.[this.getComponentSymbol]?.() ?? null;
13435
13469
  }
13436
- }
13437
-
13438
- class LintComponentResult {
13439
- constructor({ componentName, findings }) {
13440
- this.componentName = componentName;
13441
- this.findings = findings;
13470
+ getHandlerFor(v, name, key) {
13471
+ return this.getCompFor(v)?.[key][name] ?? null;
13442
13472
  }
13443
- get errorCount() {
13444
- return this.findings.filter((f) => f.level === "error").length;
13473
+ getRequestFor(v, name) {
13474
+ return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
13445
13475
  }
13446
- get warnCount() {
13447
- return this.findings.filter((f) => f.level === "warn").length;
13476
+ compileStyles() {
13477
+ const styles2 = [];
13478
+ for (const comp of this.byId.values())
13479
+ styles2.push(comp.compileStyle());
13480
+ return styles2.join(`
13481
+ `);
13448
13482
  }
13449
13483
  }
13450
13484
 
13451
- class LintReport {
13452
- constructor({ components }) {
13453
- this.components = components;
13485
+ class ComponentStack {
13486
+ constructor(comps = new Components, parent = null) {
13487
+ this.comps = comps;
13488
+ this.parent = parent;
13489
+ this.byName = {};
13490
+ this.reqsByName = {};
13491
+ this.macros = {};
13454
13492
  }
13455
- get hasErrors() {
13456
- return this.components.some((c) => c.errorCount > 0);
13493
+ enter() {
13494
+ return new ComponentStack(this.comps, this);
13457
13495
  }
13458
- get totalErrors() {
13459
- return this.components.reduce((n, c) => n + c.errorCount, 0);
13496
+ registerComponents(comps, opts) {
13497
+ const { aliases: aliases2 = {} } = opts ?? {};
13498
+ for (let i = 0;i < comps.length; i++) {
13499
+ const comp = comps[i];
13500
+ comp.scope = this.enter();
13501
+ comp.Class.scope = comp.scope;
13502
+ this.comps.registerComponent(comp);
13503
+ this.byName[comp.name] = comp;
13504
+ }
13505
+ for (const alias in aliases2) {
13506
+ const comp = this.byName[aliases2[alias]];
13507
+ console.assert(this.byName[alias] === undefined, "alias overrides component", alias);
13508
+ if (comp !== undefined)
13509
+ this.byName[alias] = comp;
13510
+ else
13511
+ console.warn("alias", alias, "to inexistent component", aliases2[alias]);
13512
+ }
13460
13513
  }
13461
- get totalWarnings() {
13462
- return this.components.reduce((n, c) => n + c.warnCount, 0);
13514
+ registerMacros(macros) {
13515
+ for (const key in macros) {
13516
+ const lower = key.toLowerCase();
13517
+ console.assert(this.macros[lower] === undefined, "macro key collision", lower);
13518
+ this.macros[lower] = macros[key];
13519
+ }
13463
13520
  }
13464
- }
13465
-
13466
- class RenderedExample {
13467
- constructor({ title, description = null, componentName, view, html, error = null }) {
13468
- this.title = title;
13469
- this.description = description;
13470
- this.componentName = componentName;
13471
- this.view = view;
13472
- this.html = html;
13473
- this.error = error;
13521
+ getCompFor(v) {
13522
+ return this.comps.getCompFor(v);
13474
13523
  }
13475
- }
13476
-
13477
- class RenderedSection {
13478
- constructor({ title, description = null, items }) {
13479
- this.title = title;
13480
- this.description = description;
13481
- this.items = items;
13524
+ registerRequestHandlers(handlers) {
13525
+ for (const name in handlers)
13526
+ this.reqsByName[name] = new RequestHandler(name, handlers[name]);
13482
13527
  }
13483
- }
13484
-
13485
- class RenderBatch {
13486
- constructor({ sections }) {
13487
- this.sections = sections;
13528
+ lookupRequest(name) {
13529
+ return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
13488
13530
  }
13489
- get hasErrors() {
13490
- return this.sections.some((s) => s.items.some((i) => i.error !== null));
13531
+ lookupComponent(name) {
13532
+ return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
13491
13533
  }
13492
- }
13493
-
13494
- class TestResult {
13495
- constructor({ title, fullPath, componentName = null, status, durationMs = 0, error = null }) {
13496
- this.title = title;
13497
- this.fullPath = fullPath;
13498
- this.componentName = componentName;
13499
- this.status = status;
13500
- this.durationMs = durationMs;
13501
- this.error = error;
13534
+ lookupMacro(name) {
13535
+ return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
13502
13536
  }
13503
13537
  }
13504
13538
 
13505
- class DescribeResult {
13506
- constructor({ title, componentName = null, children = [] }) {
13507
- this.title = title;
13508
- this.componentName = componentName;
13509
- this.children = children;
13539
+ class ProvideInfo {
13540
+ constructor(name, val, symbol) {
13541
+ this.name = name;
13542
+ this.val = val;
13543
+ this.symbol = symbol;
13510
13544
  }
13511
13545
  }
13512
13546
 
13513
- class ModuleTestReport {
13514
- constructor({ path = null, suites = [], counts = { pass: 0, fail: 0, skip: 0, total: 0 } }) {
13515
- this.path = path;
13516
- this.suites = suites;
13517
- this.counts = counts;
13547
+ class LookupInfo {
13548
+ constructor(name, compName, provideName, val) {
13549
+ this.name = name;
13550
+ this.compName = compName;
13551
+ this.provideName = provideName;
13552
+ this.val = val;
13553
+ this._sym = undefined;
13554
+ }
13555
+ getProducerSymbol(stack) {
13556
+ if (this._sym === undefined)
13557
+ this._sym = stack.lookupType(this.compName)?.provide?.[this.provideName]?.symbol ?? null;
13558
+ return this._sym;
13518
13559
  }
13519
13560
  }
13561
+ var isString = (v) => typeof v === "string";
13562
+ var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
13563
+ var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
13564
+ var _compId = 0;
13520
13565
 
13521
- class TestReport {
13522
- constructor({ modules = [] }) {
13523
- this.modules = modules;
13524
- }
13525
- get totals() {
13526
- return this.modules.reduce((acc, m) => ({
13527
- pass: acc.pass + m.counts.pass,
13528
- fail: acc.fail + m.counts.fail,
13529
- skip: acc.skip + m.counts.skip,
13530
- total: acc.total + m.counts.total
13531
- }), { pass: 0, fail: 0, skip: 0, total: 0 });
13566
+ class Component {
13567
+ constructor(Class, o) {
13568
+ this.id = _compId++;
13569
+ this.name = o.name ?? "UnkComp";
13570
+ this.Class = Class;
13571
+ this.views = { main: new View("main", o.view, o.style) };
13572
+ this.commonStyle = o.commonStyle ?? "";
13573
+ this.globalStyle = o.globalStyle ?? "";
13574
+ this.input = o.input ?? {};
13575
+ this.receive = o.receive ?? {};
13576
+ this.bubble = o.bubble ?? {};
13577
+ this.response = o.response ?? {};
13578
+ this.alter = o.alter ?? {};
13579
+ for (const name in o.views ?? {}) {
13580
+ const v = o.views[name];
13581
+ const { view, style } = isString(v) ? { view: v } : v;
13582
+ this.views[name] = new View(name, view, style);
13583
+ }
13584
+ this._rawProvide = o.provide ?? {};
13585
+ this._rawLookup = o.lookup ?? {};
13586
+ this.provide = {};
13587
+ this.lookup = {};
13588
+ this.scope = null;
13589
+ this.spec = o;
13590
+ this.extra = {};
13591
+ for (const key of Object.keys(o))
13592
+ if (!KNOWN_SPEC_KEYS.has(key))
13593
+ this.extra[key] = o[key];
13532
13594
  }
13533
- get hasFailures() {
13534
- return this.modules.some((m) => m.counts.fail > 0);
13595
+ clone() {
13596
+ return Component.fromSpec(this.spec);
13535
13597
  }
13536
- }
13537
-
13538
- // tools/core/tests.js
13539
- class Describe {
13540
- constructor({ title, componentName = null, parent = null }) {
13541
- this.title = title;
13542
- this.componentName = componentName;
13543
- this.parent = parent;
13544
- this.children = [];
13598
+ compile(ParseContext2) {
13599
+ for (const name in this.views)
13600
+ this.views[name].compile(new ParseContext2, this.scope, this.id);
13601
+ const ctx = this.views.main.ctx;
13602
+ for (const key in this._rawProvide) {
13603
+ const val = vp.parseProvide(this._rawProvide[key], ctx);
13604
+ if (val)
13605
+ this.provide[key] = new ProvideInfo(key, val, Symbol(key));
13606
+ }
13607
+ for (const key in this._rawLookup) {
13608
+ const linfo = this._rawLookup[key];
13609
+ const forStr = isString(linfo) ? linfo : isString(linfo?.for) ? linfo.for : null;
13610
+ const [compName, provideName] = forStr === null ? [] : forStr.split(".");
13611
+ if (!isString(compName) || !isString(provideName))
13612
+ continue;
13613
+ const defStr = isString(linfo?.default) ? linfo.default : null;
13614
+ const val = defStr === null ? null : vp.parseField(defStr, ctx);
13615
+ this.lookup[key] = new LookupInfo(key, compName, provideName, val);
13616
+ }
13617
+ for (const key in this.lookup)
13618
+ if (this.provide[key] !== undefined)
13619
+ console.warn("name declared in both provide and lookup", this.name, key);
13545
13620
  }
13546
- }
13547
-
13548
- class Test {
13549
- constructor({ title, fn, componentName = null, parent = null }) {
13550
- this.title = title;
13551
- this.fn = fn;
13552
- this.componentName = componentName;
13553
- this.parent = parent;
13621
+ make(args, opts) {
13622
+ return this.Class.make(args, opts ?? { scope: this.scope });
13554
13623
  }
13555
- }
13556
-
13557
- class ModuleTests {
13558
- constructor({ path = null, suites = [] } = {}) {
13559
- this.path = path;
13560
- this.suites = suites;
13624
+ getView(name) {
13625
+ return this.views[name] ?? this.views.main;
13626
+ }
13627
+ getEventForId(id, name = "main") {
13628
+ return this.getView(name).ctx.getEventForId(id);
13629
+ }
13630
+ getNodeForId(id, name = "main") {
13631
+ return this.getView(name).ctx.getNodeForId(id);
13632
+ }
13633
+ compileStyle() {
13634
+ const { id, commonStyle, globalStyle, views } = this;
13635
+ const styles2 = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
13636
+ if (globalStyle !== "")
13637
+ styles2.push(globalStyle);
13638
+ for (const name in views) {
13639
+ const { style } = views[name];
13640
+ if (style !== "")
13641
+ styles2.push(`[data-cid="${id}"][data-vid="${name}"]{${style}}`);
13642
+ }
13643
+ return styles2.join(`
13644
+ `);
13561
13645
  }
13562
13646
  }
13563
13647
 
13564
- class TestIndex {
13565
- constructor({ modules = [] } = {}) {
13566
- this.modules = modules;
13567
- }
13648
+ // src/on.js
13649
+ var OP_KINDS = ["send", "bubble", "request", "input"];
13650
+ function phaseOps(phase) {
13651
+ const ops = [];
13652
+ for (const type3 of OP_KINDS)
13653
+ for (const a of phase[type3] ?? [])
13654
+ ops.push({ type: type3, ...a });
13655
+ for (const a of phase.do ?? [])
13656
+ ops.push(a);
13657
+ return ops;
13568
13658
  }
13569
- function isComponentObject(x) {
13570
- return x !== null && typeof x === "object" && typeof x.name === "string" && typeof x.Class === "function";
13659
+ function resolveArgs(args, self) {
13660
+ return typeof args === "function" ? args(self) ?? [] : args ?? [];
13571
13661
  }
13572
- function resolveComponentName(arg, components) {
13573
- if (isComponentObject(arg))
13574
- return arg.name;
13575
- if (typeof arg === "function") {
13576
- for (const c of components)
13577
- if (c.Class === arg)
13578
- return c.name;
13662
+ function dispatchPhase(dispatcher, targetPath, phase, self) {
13663
+ if (!phase)
13664
+ return;
13665
+ for (const op of phaseOps(phase)) {
13666
+ const args = resolveArgs(op.args, self);
13667
+ switch (op.type) {
13668
+ case "send":
13669
+ dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
13670
+ break;
13671
+ case "bubble":
13672
+ dispatcher.sendAtPath(targetPath, op.name, args, {
13673
+ skipSelf: true,
13674
+ bubbles: true,
13675
+ ...op.opts
13676
+ });
13677
+ break;
13678
+ case "request":
13679
+ dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
13680
+ break;
13681
+ case "input":
13682
+ dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
13683
+ break;
13684
+ }
13579
13685
  }
13580
- return null;
13581
13686
  }
13582
- function titleFromArg(arg) {
13583
- if (typeof arg === "string")
13584
- return arg;
13585
- if (isComponentObject(arg))
13586
- return arg.name;
13587
- if (typeof arg === "function")
13588
- return arg.name || "(anonymous)";
13589
- return String(arg);
13687
+
13688
+ // src/stack.js
13689
+ var STOP = Symbol("STOP");
13690
+ var NEXT = Symbol("NEXT");
13691
+ function lookup(chain, name, dv = null) {
13692
+ let n = chain;
13693
+ while (n !== null) {
13694
+ const r = n[0].lookup(name);
13695
+ if (r === STOP)
13696
+ return dv;
13697
+ if (r !== NEXT)
13698
+ return r;
13699
+ n = n[1];
13700
+ }
13701
+ return dv;
13590
13702
  }
13591
- function makeCollector({ path = null, components = [] } = {}) {
13592
- const moduleTests = new ModuleTests({ path, suites: [] });
13593
- const stack = [];
13594
- function describe(...args) {
13595
- let head;
13596
- let options = null;
13597
- let fn;
13598
- if (args.length === 2) {
13599
- [head, fn] = args;
13600
- } else if (args.length === 3) {
13601
- [head, options, fn] = args;
13602
- } else {
13603
- throw new Error(`describe() expects 2 or 3 arguments, got ${args.length}`);
13604
- }
13605
- if (typeof fn !== "function") {
13606
- throw new Error(`describe(${JSON.stringify(titleFromArg(head))}): final argument must be a function`);
13607
- }
13608
- let componentName = null;
13609
- if (typeof head !== "string") {
13610
- componentName = resolveComponentName(head, components);
13611
- }
13612
- if (componentName === null && options && options.component != null) {
13613
- componentName = resolveComponentName(options.component, components);
13614
- }
13615
- if (componentName === null) {
13616
- const parent2 = stack.length ? stack[stack.length - 1] : null;
13617
- if (parent2)
13618
- componentName = parent2.componentName;
13619
- }
13620
- const parent = stack.length ? stack[stack.length - 1] : null;
13621
- const node = new Describe({
13622
- title: titleFromArg(head),
13623
- componentName,
13624
- parent
13625
- });
13626
- if (parent)
13627
- parent.children.push(node);
13628
- else
13629
- moduleTests.suites.push(node);
13630
- stack.push(node);
13631
- try {
13632
- fn();
13633
- } finally {
13634
- stack.pop();
13635
- }
13703
+
13704
+ class BindFrame {
13705
+ constructor(it, binds, isFrame) {
13706
+ this.it = it;
13707
+ this.binds = binds;
13708
+ this.isFrame = isFrame;
13636
13709
  }
13637
- function test2(title, fn) {
13638
- if (typeof title !== "string") {
13639
- throw new Error("test(title, fn): title must be a string");
13640
- }
13641
- if (typeof fn !== "function") {
13642
- throw new Error(`test(${JSON.stringify(title)}): fn must be a function`);
13643
- }
13644
- const parent = stack.length ? stack[stack.length - 1] : null;
13645
- if (!parent) {
13646
- throw new Error(`test(${JSON.stringify(title)}) must be called inside a describe()`);
13647
- }
13648
- parent.children.push(new Test({
13649
- title,
13650
- fn,
13651
- componentName: parent.componentName,
13652
- parent
13653
- }));
13710
+ lookup(name) {
13711
+ const v = this.binds[name];
13712
+ return v === undefined ? this.isFrame ? STOP : NEXT : v;
13654
13713
  }
13655
- return { describe, test: test2, moduleTests };
13656
13714
  }
13657
13715
 
13658
- // tools/core/test.js
13659
- function buildPath(node) {
13660
- const parts = [];
13661
- let cur = node;
13662
- while (cur) {
13663
- parts.unshift(cur.title);
13664
- cur = cur.parent;
13716
+ class ObjectFrame {
13717
+ constructor(binds) {
13718
+ this.binds = binds;
13719
+ }
13720
+ lookup(key) {
13721
+ const v = this.binds[key];
13722
+ return v === undefined ? NEXT : v;
13665
13723
  }
13666
- return parts.join(" > ");
13667
13724
  }
13668
- function captureError(e) {
13669
- const out = { message: e.message, stack: e.stack };
13670
- if ("expected" in e)
13671
- out.expected = e.expected;
13672
- if ("actual" in e)
13673
- out.actual = e.actual;
13674
- return out;
13725
+ function computeViewsId(views) {
13726
+ let s = "";
13727
+ let n = views;
13728
+ while (n !== null) {
13729
+ s += n[0];
13730
+ n = n[1];
13731
+ }
13732
+ return s === "main" ? "" : s;
13675
13733
  }
13676
- async function runTests({
13677
- getTests,
13678
- components = [],
13679
- path = null,
13680
- expect: expect2,
13681
- name = null,
13682
- grep = null,
13683
- bail = false
13684
- } = {}) {
13685
- const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
13686
- if (typeof getTests !== "function") {
13687
- return new TestReport({
13688
- modules: [new ModuleTestReport({ path, suites: [], counts })]
13689
- });
13734
+
13735
+ class Stack2 {
13736
+ constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
13737
+ this.comps = comps;
13738
+ this.it = it;
13739
+ this.binds = binds;
13740
+ this.dynBinds = dynBinds;
13741
+ this.views = views;
13742
+ this.viewsId = viewsId;
13743
+ this.ctx = ctx;
13690
13744
  }
13691
- if (typeof expect2 !== "function") {
13692
- throw new Error("runTests: expect must be provided (e.g. chai's expect)");
13745
+ _pushProvides() {
13746
+ const provide = this.comps.getCompFor(this.it)?.provide;
13747
+ if (provide == null)
13748
+ return this;
13749
+ const dynObj = {};
13750
+ let has2 = false;
13751
+ for (const k in provide) {
13752
+ dynObj[provide[k].symbol] = provide[k].val.eval(this);
13753
+ has2 = true;
13754
+ }
13755
+ if (!has2)
13756
+ return this;
13757
+ const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
13758
+ const { comps, it, binds, views, viewsId, ctx } = this;
13759
+ return new Stack2(comps, it, binds, newDynBinds, views, viewsId, ctx);
13693
13760
  }
13694
- const { describe, test: test2, moduleTests } = makeCollector({ path, components });
13695
- await getTests({ describe, test: test2, expect: expect2 });
13696
- let bailed = false;
13697
- async function visit(node) {
13698
- if (node instanceof Test) {
13699
- if (name !== null && node.componentName !== name)
13700
- return null;
13701
- const fullPath = buildPath(node);
13702
- if (grep !== null && !fullPath.includes(grep))
13703
- return null;
13704
- counts.total++;
13705
- if (bailed) {
13706
- counts.skip++;
13707
- return new TestResult({
13708
- title: node.title,
13709
- fullPath,
13710
- componentName: node.componentName,
13711
- status: "skip"
13712
- });
13713
- }
13714
- const start = performance.now();
13715
- try {
13716
- await node.fn();
13717
- counts.pass++;
13718
- return new TestResult({
13719
- title: node.title,
13720
- fullPath,
13721
- componentName: node.componentName,
13722
- status: "pass",
13723
- durationMs: performance.now() - start
13724
- });
13725
- } catch (e) {
13726
- counts.fail++;
13727
- if (bail)
13728
- bailed = true;
13729
- return new TestResult({
13730
- title: node.title,
13731
- fullPath,
13732
- componentName: node.componentName,
13733
- status: "fail",
13734
- durationMs: performance.now() - start,
13735
- error: captureError(e)
13736
- });
13737
- }
13738
- }
13739
- const childResults = [];
13740
- for (const child of node.children) {
13741
- const r = await visit(child);
13742
- if (r !== null)
13743
- childResults.push(r);
13744
- }
13745
- if (childResults.length === 0)
13761
+ static root(comps, it, ctx) {
13762
+ const binds = [new BindFrame(it, { it }, true), null];
13763
+ const dynBinds = [new ObjectFrame({}), null];
13764
+ const views = ["main", null];
13765
+ return new Stack2(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
13766
+ }
13767
+ enter(it, bindings = {}, isFrame = true) {
13768
+ const { comps, binds, dynBinds, views, viewsId, ctx } = this;
13769
+ const newBinds = [new BindFrame(it, bindings, isFrame), binds];
13770
+ const stack = new Stack2(comps, it, newBinds, dynBinds, views, viewsId, ctx);
13771
+ return isFrame ? stack._pushProvides() : stack;
13772
+ }
13773
+ pushViewName(name) {
13774
+ const { comps, it, binds, dynBinds, views, ctx } = this;
13775
+ const newViews = [name, views];
13776
+ return new Stack2(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
13777
+ }
13778
+ _pushDynBindValuesToArray(arr, comp) {
13779
+ for (const k in comp.provide)
13780
+ arr.push(this._lookupProvide(comp.provide[k]));
13781
+ for (const k in comp.lookup)
13782
+ arr.push(this._lookupAlias(comp.lookup[k]));
13783
+ }
13784
+ _lookupProvide(p) {
13785
+ return lookup(this.dynBinds, p.symbol) ?? p.val.eval(this) ?? null;
13786
+ }
13787
+ _lookupAlias(lk) {
13788
+ const sym = lk.getProducerSymbol(this);
13789
+ return (sym != null ? lookup(this.dynBinds, sym) : null) ?? lk.val?.eval(this) ?? null;
13790
+ }
13791
+ lookupDynamic(name) {
13792
+ const comp = this.comps.getCompFor(this.it);
13793
+ if (comp == null)
13746
13794
  return null;
13747
- return new DescribeResult({
13748
- title: node.title,
13749
- componentName: node.componentName,
13750
- children: childResults
13751
- });
13795
+ const lk = comp.lookup[name];
13796
+ if (lk !== undefined)
13797
+ return this._lookupAlias(lk);
13798
+ const p = comp.provide[name];
13799
+ return p !== undefined ? this._lookupProvide(p) : null;
13752
13800
  }
13753
- const suiteResults = [];
13754
- for (const suite of moduleTests.suites) {
13755
- const r = await visit(suite);
13756
- if (r !== null)
13757
- suiteResults.push(r);
13801
+ lookupBind(name) {
13802
+ return lookup(this.binds, name);
13758
13803
  }
13759
- return new TestReport({
13760
- modules: [new ModuleTestReport({ path, suites: suiteResults, counts })]
13761
- });
13762
- }
13763
-
13764
- // tools/core/test-console.js
13765
- var PASS = "color: #0a0; font-weight: bold";
13766
- var FAIL = "color: #c00; font-weight: bold";
13767
- var SKIP = "color: #888";
13768
- var DIM = "color: #888";
13769
- var RESET = "color: inherit; font-weight: normal";
13770
- function reportTestNode(node) {
13771
- if (node.children) {
13772
- const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
13773
- console.group(label);
13774
- for (const child of node.children)
13775
- reportTestNode(child);
13776
- console.groupEnd();
13777
- return;
13804
+ lookupType(name) {
13805
+ return this.comps.getCompFor(this.it).scope.lookupComponent(name);
13778
13806
  }
13779
- const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
13780
- if (node.status === "pass") {
13781
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
13782
- } else if (node.status === "skip") {
13783
- console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
13784
- } else {
13785
- console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
13786
- console.error(node.error?.message ?? "(no error message)");
13787
- if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
13788
- console.log("expected:", node.error.expected);
13789
- console.log("actual: ", node.error.actual);
13790
- }
13791
- if (node.error?.stack)
13792
- console.log(node.error.stack);
13793
- console.groupEnd();
13807
+ lookupFieldRaw(name) {
13808
+ return this.it[name] ?? null;
13794
13809
  }
13795
- }
13796
- function reportTestReportToConsole(report) {
13797
- for (const m of report.modules) {
13798
- const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
13799
- console.group(label);
13800
- if (m.suites.length === 0) {
13801
- console.log("(no tests)");
13802
- } else {
13803
- for (const s of m.suites)
13804
- reportTestNode(s);
13810
+ lookupMethod(name) {
13811
+ const fn = this.it[name];
13812
+ return fn instanceof Function ? fn.call(this.it) : null;
13813
+ }
13814
+ lookupName(name) {
13815
+ return this.ctx.lookupName(name);
13816
+ }
13817
+ getHandlerFor(name, key) {
13818
+ return this.comps.getHandlerFor(this.it, name, key);
13819
+ }
13820
+ lookupRequest(name) {
13821
+ return this.comps.getRequestFor(this.it, name);
13822
+ }
13823
+ lookupBestView(views, defaultViewName) {
13824
+ let n = this.views;
13825
+ while (n !== null) {
13826
+ const view = views[n[0]];
13827
+ if (view !== undefined)
13828
+ return view;
13829
+ n = n[1];
13805
13830
  }
13806
- const c = m.counts;
13807
- const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
13808
- if (c.fail > 0)
13809
- console.error(`%c${summary}`, FAIL);
13810
- else if (c.total === 0)
13811
- console.log(`%c${summary}`, DIM);
13812
- else
13813
- console.log(`%c${summary}`, PASS);
13814
- console.groupEnd();
13831
+ return views[defaultViewName];
13815
13832
  }
13816
- return report;
13817
13833
  }
13818
13834
 
13819
- // tools/format/lint.js
13820
- var UNSUPPORTED_EXPR_LABEL = {
13821
- ternary: "ternary expression",
13822
- comparison: "comparison",
13823
- logical: "logical expression",
13824
- "call-with-args": "method call with arguments"
13825
- };
13826
- function unsupportedExprMessage(info) {
13827
- const v = JSON.stringify(info.value);
13828
- const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
13829
- switch (info.role) {
13830
- case "attr":
13831
- return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
13832
- case "directive":
13833
- return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
13834
- case "if":
13835
- return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
13836
- case "x-op":
13837
- return `Unsupported ${label} ${v} in <x ${info.op}>`;
13838
- default:
13839
- return `Unsupported ${label} ${v}`;
13835
+ // src/transactor.js
13836
+ class State2 {
13837
+ constructor(val) {
13838
+ this.val = val;
13839
+ this.changeSubs = [];
13840
13840
  }
13841
- }
13842
- function badValueMessage(info) {
13843
- const v = JSON.stringify(info.value);
13844
- switch (info.role) {
13845
- case "attr":
13846
- return `Cannot parse value ${v} for attribute ':${info.attr}'`;
13847
- case "directive":
13848
- return `Cannot parse value ${v} for directive '@${info.directive}'`;
13849
- case "if":
13850
- return `Cannot parse condition ${v} for '@if.${info.attr}'`;
13851
- case "x-op":
13852
- return `Cannot parse value ${v} for <x ${info.op}>`;
13853
- case "handler-name":
13854
- return `Cannot parse handler name ${v}`;
13855
- case "handler-arg":
13856
- return `Cannot parse handler argument ${v}`;
13857
- case "macro-var":
13858
- return `Macro variable '^${info.name}' is not defined`;
13859
- default:
13860
- return `Cannot parse value ${v}`;
13841
+ onChange(cb) {
13842
+ this.changeSubs.push(cb);
13861
13843
  }
13862
- }
13863
- function tagDisplay(tag) {
13864
- return tag ? String(tag).toLowerCase() : null;
13865
- }
13866
- function fmtTagSuffix(info) {
13867
- const t = tagDisplay(info?.tag);
13868
- return t && t !== "x" ? ` on <${t}>` : "";
13869
- }
13870
- function fmtOriginSuffix(info) {
13871
- if (!info)
13872
- return "";
13873
- const parts = [];
13874
- if (info.originAttr) {
13875
- const branch = info.branch ? `[${info.branch}]` : "";
13876
- parts.push(`in ${info.originAttr}${branch}`);
13844
+ set(val, info) {
13845
+ const old = this.val;
13846
+ this.val = val;
13847
+ for (const sub of this.changeSubs)
13848
+ sub({ val, old, info, timestamp: Date.now() });
13877
13849
  }
13878
- if (info.handlerName) {
13879
- parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
13850
+ update(fn, info) {
13851
+ return this.set(fn(this.val), info);
13880
13852
  }
13881
- const t = tagDisplay(info.tag);
13882
- if (t && t !== "x")
13883
- parts.push(`on <${t}>`);
13884
- return parts.length ? ` (${parts.join(", ")})` : "";
13885
13853
  }
13886
- function fmtEventSuffix(info) {
13887
- if (info?.originAttr)
13888
- return ` in ${info.originAttr}`;
13889
- if (info?.eventName)
13890
- return ` in @on.${info.eventName}`;
13891
- return "";
13892
- }
13893
- function lintIdToMessage(id, info) {
13894
- switch (id) {
13895
- case "RENDER_IT_OUTSIDE_OF_LOOP":
13896
- return "<x render-it> used outside of a loop";
13897
- case "UNKNOWN_EVENT_MODIFIER": {
13898
- const mods = info.handler?.modifiers ?? [info.modifier];
13899
- const written = `@on.${info.name}+${mods.join("+")}`;
13900
- return `Unknown modifier '+${info.modifier}' in '${written}'`;
13854
+
13855
+ class Transactor {
13856
+ constructor(comps, rootValue) {
13857
+ this.comps = comps;
13858
+ this.transactions = [];
13859
+ this.state = new State2(rootValue);
13860
+ this.onTransactionPushed = () => {};
13861
+ this._inflight = new Set;
13862
+ }
13863
+ pushTransaction(t) {
13864
+ this.transactions.push(t);
13865
+ this.onTransactionPushed(t);
13866
+ }
13867
+ _link(child, parent) {
13868
+ if (parent) {
13869
+ const release = parent.completion.track();
13870
+ child.completion.whenSubtreeSettled().then(release);
13901
13871
  }
13902
- case "UNKNOWN_HANDLER_ARG_NAME":
13903
- return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
13904
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
13905
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
13906
- case "INPUT_HANDLER_NOT_REFERENCED":
13907
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
13908
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
13909
- return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
13910
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
13911
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
13912
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
13913
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
13914
- case "FIELD_VAL_NOT_DEFINED":
13915
- return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
13916
- case "FIELD_VAL_IS_METHOD":
13917
- return `'.${info.name}' reads a field, but '${info.name}' is defined as a method — use '$${info.name}'${fmtOriginSuffix(info)}`;
13918
- case "METHOD_VAL_NOT_DEFINED":
13919
- return `Method '$${info.name}' is not defined${fmtOriginSuffix(info)}`;
13920
- case "METHOD_VAL_IS_FIELD":
13921
- return `'$${info.name}' calls a method, but '${info.name}' is defined as a field — use '.${info.name}'${fmtOriginSuffix(info)}`;
13922
- case "DUPLICATE_ATTR_DEFINITION": {
13923
- const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
13924
- const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
13925
- return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
13872
+ return child;
13873
+ }
13874
+ pushSend(path, name, args = [], opts = {}, parent = null) {
13875
+ const t = new SendEvent(path, this, name, args, parent, opts);
13876
+ this.pushTransaction(t);
13877
+ return this._link(t, parent);
13878
+ }
13879
+ pushInput(path, name, args = [], opts = {}, parent = null) {
13880
+ const t = new InputDispatchEvent(path, this, name, args, parent, opts);
13881
+ this.pushTransaction(t);
13882
+ return this._link(t, parent);
13883
+ }
13884
+ pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
13885
+ const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
13886
+ const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
13887
+ this.pushTransaction(t);
13888
+ return this._link(t, parent);
13889
+ }
13890
+ pushRequest(path, name, args = [], opts = {}, parent = null) {
13891
+ const release = parent ? parent.completion.track() : null;
13892
+ const p = this._runRequest(path, name, args, opts, parent, release);
13893
+ this._inflight.add(p);
13894
+ p.finally(() => this._inflight.delete(p));
13895
+ return p;
13896
+ }
13897
+ async settle(maxTurns = 1e4) {
13898
+ while ((this.hasPendingTransactions || this._inflight.size) && maxTurns-- > 0) {
13899
+ while (this.hasPendingTransactions)
13900
+ this.transactNext();
13901
+ if (this._inflight.size)
13902
+ await Promise.allSettled([...this._inflight]);
13903
+ }
13904
+ }
13905
+ async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
13906
+ let released = false;
13907
+ const transfer = (t) => {
13908
+ if (release) {
13909
+ released = true;
13910
+ t.completion.whenSubtreeSettled().then(release);
13911
+ }
13912
+ };
13913
+ try {
13914
+ const curRoot = this.state.val;
13915
+ const txnPath = path.toTransactionPath();
13916
+ const curLeaf = txnPath.lookup(curRoot);
13917
+ const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
13918
+ const reqCtx = new RequestContext(path, this, parent, curRoot);
13919
+ const resHandlerName = opts?.onResName ?? name;
13920
+ const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
13921
+ const push = (specificName, baseName, singleArg, result, error) => {
13922
+ const resArgs = specificName ? [singleArg] : [result, error];
13923
+ const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
13924
+ transfer(t);
13925
+ this.pushTransaction(t);
13926
+ };
13927
+ try {
13928
+ const result = await handler.fn.apply(null, [...args, reqCtx]);
13929
+ push(opts?.onOkName, resHandlerName, result, result, null);
13930
+ } catch (error) {
13931
+ push(opts?.onErrorName, resHandlerName, error, null, error);
13932
+ }
13933
+ } finally {
13934
+ if (release && !released)
13935
+ release();
13926
13936
  }
13927
- case "IF_NO_BRANCH_SET":
13928
- return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
13929
- case "UNKNOWN_REQUEST_NAME":
13930
- return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
13931
- case "UNKNOWN_COMPONENT_NAME":
13932
- return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
13933
- case "ALT_HANDLER_NOT_DEFINED":
13934
- return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
13935
- case "ALT_HANDLER_NOT_REFERENCED":
13936
- return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
13937
- case "DYN_VAL_NOT_DEFINED":
13938
- return `Dynamic variable '*${info.name}' is not defined${fmtOriginSuffix(info)}`;
13939
- case "DYN_ALIAS_NOT_REFERENCED":
13940
- return `Lookup '${info.name}' is defined but never used — remove it or reference it as '*${info.name}' in a view`;
13941
- case "PROVIDE_NOT_ADDRESSABLE":
13942
- return `Provide '${info.name}' value '${info.value}' must be a field ('.f') or seq-access ('.s[.k]') — a method/constant can't be a render target`;
13943
- case "LOOKUP_BAD_SHAPE":
13944
- return `Lookup '${info.name}' has an invalid shape: ${info.problem}`;
13945
- case "LOOKUP_TARGET_MALFORMED":
13946
- return `Lookup '${info.name}' target '${info.target}' must be 'Producer.provideName' (a string, or the 'for' of { for, default })`;
13947
- case "UNKNOWN_MACRO_ARG":
13948
- return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
13949
- case "UNKNOWN_DIRECTIVE":
13950
- return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
13951
- case "UNKNOWN_X_OP":
13952
- return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
13953
- case "UNKNOWN_X_ATTR":
13954
- return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
13955
- case "MAYBE_DROP_AT_PREFIX": {
13956
- const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
13957
- return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
13937
+ }
13938
+ get hasPendingTransactions() {
13939
+ return this.transactions.length > 0;
13940
+ }
13941
+ transactNext() {
13942
+ if (this.hasPendingTransactions)
13943
+ this.transact(this.transactions.shift());
13944
+ }
13945
+ transact(transaction) {
13946
+ try {
13947
+ const curState = this.state.val;
13948
+ const newState = transaction.run(curState, this.comps);
13949
+ if (newState !== undefined) {
13950
+ this.state.set(newState, { transaction });
13951
+ transaction.afterTransaction();
13952
+ } else
13953
+ console.warn("undefined new state", { curState, transaction });
13954
+ } finally {
13955
+ transaction._completion?.ensureSelfSettled();
13956
+ transaction._completion?.releaseSelf();
13958
13957
  }
13959
- case "MAYBE_ADD_AT_PREFIX":
13960
- return `'${info.name}' on <${(info.tag ?? "").toLowerCase()}> is a plain attribute, but '@${info.name}' is a directive — add the leading '@'`;
13961
- case "BAD_VALUE":
13962
- return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
13963
- case "UNSUPPORTED_EXPR_SYNTAX":
13964
- return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
13965
- case "REDUNDANT_TEMPLATE_STRING":
13966
- return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
13967
- case "PLACEHOLDERLESS_TEMPLATE_STRING":
13968
- return `Template string has no dynamic parts — use the string literal ${info.literal} instead${fmtOriginSuffix(info)}`;
13969
- case "UNKNOWN_COMPONENT_SPEC_KEY":
13970
- return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
13971
- case "COMP_FIELD_BAD_SHAPE":
13972
- return info.kind === "args-not-object" ? `Field '${info.fieldName}': in { component, args }, 'args' must be a plain object, got ${info.got}` : `Field '${info.fieldName}': in { component, args }, 'component' must be the component name as a string, got ${info.gotName ? `the ${info.gotName} class` : info.got}`;
13973
- case "HTML_TAG_NAME_HAS_UPPERCASE":
13974
- return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
13975
- case "HTML_SVG_TAG_WILL_LOWERCASE":
13976
- return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
13977
- case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
13978
- return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
13979
- case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
13980
- return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
13981
- case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
13982
- return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
13983
- case "HTML_DUPLICATE_FORM":
13984
- return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
13985
- case "HTML_NESTED_INTERACTIVE":
13986
- return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
13987
- case "HTML_MISNESTED_FORMATTING":
13988
- return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
13989
- case "HTML_UNEXPECTED_END_TAG":
13990
- return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
13991
- case "HTML_UNCLOSED_BEFORE_END":
13992
- return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
13993
- case "HTML_DUPLICATE_ATTRIBUTE":
13994
- return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
13995
- case "HTML_ATTRIBUTES_ON_END_TAG":
13996
- return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
13997
- case "HTML_SELF_CLOSING_END_TAG":
13998
- return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
13999
- case "HTML_MISSING_ATTRIBUTE_VALUE":
14000
- return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
14001
- case "HTML_CDATA_IN_HTML_NAMESPACE":
14002
- return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
14003
- case "HTML_BOGUS_COMMENT":
14004
- return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
14005
- case "HTML_SVG_ATTR_WILL_LOWERCASE":
14006
- return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
14007
- case "HTML_MATHML_ATTR_WILL_LOWERCASE":
14008
- return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
14009
- case "ASYNC_HANDLER":
14010
- return `Handler '${info.name}' in '${info.channel}' is an async function — handlers must be synchronous and return the updated state (an async function returns a Promise the framework won't await)`;
14011
- case "LINT_ERROR":
14012
- return info.message;
14013
- default:
14014
- return id;
14015
13958
  }
14016
- }
14017
- function suggestionToMessage(suggestion) {
14018
- if (!suggestion)
14019
- return null;
14020
- switch (suggestion.kind) {
14021
- case "replace-name":
14022
- return `did you mean '${suggestion.to}'?`;
14023
- case "drop-prefix":
14024
- return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
14025
- case "add-prefix":
14026
- return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
14027
- case "remove":
14028
- return `remove ${suggestion.what}`;
14029
- case "rewrite":
14030
- return `use '${suggestion.to}' instead of '${suggestion.from}'`;
14031
- case "wrap":
14032
- return `wrap it in ${suggestion.to}`;
14033
- case "rephrase":
14034
- return suggestion.text ?? null;
14035
- default:
14036
- return null;
13959
+ transactInputNow(path, event, eventHandler, dragInfo) {
13960
+ this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
14037
13961
  }
14038
13962
  }
14039
- function fmtLocationSuffix(info) {
14040
- const loc = info?.location;
14041
- if (!loc)
14042
- return "";
14043
- return ` at line ${loc.line}, col ${loc.column}`;
13963
+ function mkReq404(name) {
13964
+ const fn = () => {
13965
+ throw new Error(`Request not found: ${name}`);
13966
+ };
13967
+ return { fn };
14044
13968
  }
14045
- function htmlActionPhrase(action, tag, parent) {
14046
- switch (action) {
14047
- case "ignored":
14048
- return `the parser will drop this <${tag}>`;
14049
- case "drop":
14050
- return `the parser will drop this <${tag}>`;
14051
- case "auto-close-implicit":
14052
- return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
14053
- case "foster-parent":
14054
- return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
14055
- case "foreign-breakout":
14056
- return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
14057
- default:
14058
- return `parser action: ${action}`;
14059
- }
13969
+ function nullHandler() {
13970
+ return this;
14060
13971
  }
14061
13972
 
14062
- // src/components.js
14063
- class Components {
14064
- constructor() {
14065
- this.getComponentSymbol = Symbol("getComponent");
14066
- this.byId = new Map;
14067
- }
14068
- registerComponent(comp) {
14069
- comp.Class.prototype[this.getComponentSymbol] = () => comp;
14070
- this.byId.set(comp.id, comp);
13973
+ class Transaction {
13974
+ constructor(path, transactor, parentTransaction = null) {
13975
+ this.path = path;
13976
+ this.transactor = transactor;
13977
+ this.parentTransaction = parentTransaction;
13978
+ this._completion = null;
14071
13979
  }
14072
- getComponentForId(id) {
14073
- return this.byId.get(id) ?? null;
13980
+ get completion() {
13981
+ this._completion ??= new Completion;
13982
+ return this._completion;
14074
13983
  }
14075
- getCompFor(v) {
14076
- return v?.[this.getComponentSymbol]?.() ?? null;
13984
+ whenSettled() {
13985
+ return this.completion.whenSettled();
14077
13986
  }
14078
- getHandlerFor(v, name, key) {
14079
- return this.getCompFor(v)?.[key][name] ?? null;
13987
+ whenSubtreeSettled() {
13988
+ return this.completion.whenSubtreeSettled();
14080
13989
  }
14081
- getRequestFor(v, name) {
14082
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
13990
+ run(rootValue, comps) {
13991
+ return this.updateRootValue(rootValue, comps);
14083
13992
  }
14084
- compileStyles() {
14085
- const styles2 = [];
14086
- for (const comp of this.byId.values())
14087
- styles2.push(comp.compileStyle());
14088
- return styles2.join(`
14089
- `);
13993
+ afterTransaction() {}
13994
+ buildRootStack(root, comps) {
13995
+ return Stack2.root(comps, root);
14090
13996
  }
14091
- }
14092
-
14093
- class ComponentStack {
14094
- constructor(comps = new Components, parent = null) {
14095
- this.comps = comps;
14096
- this.parent = parent;
14097
- this.byName = {};
14098
- this.reqsByName = {};
14099
- this.macros = {};
13997
+ buildStack(root, comps) {
13998
+ return this.path.toTransactionPath().buildStack(this.buildRootStack(root, comps));
14100
13999
  }
14101
- enter() {
14102
- return new ComponentStack(this.comps, this);
14000
+ callHandler(root, instance, comps) {
14001
+ const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
14002
+ return handler.apply(instance, args);
14103
14003
  }
14104
- registerComponents(comps, opts) {
14105
- const { aliases: aliases2 = {} } = opts ?? {};
14106
- for (let i = 0;i < comps.length; i++) {
14107
- const comp = comps[i];
14108
- comp.scope = this.enter();
14109
- comp.Class.scope = comp.scope;
14110
- this.comps.registerComponent(comp);
14111
- this.byName[comp.name] = comp;
14112
- }
14113
- for (const alias in aliases2) {
14114
- const comp = this.byName[aliases2[alias]];
14115
- console.assert(this.byName[alias] === undefined, "alias overrides component", alias);
14116
- if (comp !== undefined)
14117
- this.byName[alias] = comp;
14118
- else
14119
- console.warn("alias", alias, "to inexistent component", aliases2[alias]);
14004
+ getHandlerAndArgs(_root, _instance, _comps) {
14005
+ return null;
14006
+ }
14007
+ getTransactionPath() {
14008
+ return this.path.toTransactionPath();
14009
+ }
14010
+ updateRootValue(curRoot, comps) {
14011
+ const txnPath = this.getTransactionPath();
14012
+ const curLeaf = txnPath.lookup(curRoot);
14013
+ const newLeaf = this.callHandler(curRoot, curLeaf, comps);
14014
+ this._completion?.markSelfSettled({ value: newLeaf, old: curLeaf });
14015
+ return curLeaf !== newLeaf ? txnPath.setValue(curRoot, newLeaf) : curRoot;
14016
+ }
14017
+ lookupName(_name) {
14018
+ return null;
14019
+ }
14020
+ }
14021
+ var toNullIfNaN = (v) => Number.isNaN(v) ? null : v;
14022
+ function getValue(e) {
14023
+ return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
14024
+ }
14025
+
14026
+ class InputEvent extends Transaction {
14027
+ constructor(path, e, handler, transactor, dragInfo) {
14028
+ super(path, transactor);
14029
+ this.e = e;
14030
+ this.handler = handler;
14031
+ this.dragInfo = dragInfo;
14032
+ this._dispatchPath = null;
14033
+ }
14034
+ get dispatchPath() {
14035
+ this._dispatchPath ??= this.path.compact();
14036
+ return this._dispatchPath;
14037
+ }
14038
+ buildRootStack(root, comps) {
14039
+ return Stack2.root(comps, root, this);
14040
+ }
14041
+ getHandlerAndArgs(root, _instance, comps) {
14042
+ const stack = this.buildStack(root, comps);
14043
+ const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
14044
+ const path = this.dispatchPath;
14045
+ let dispatcher;
14046
+ for (let i = 0;i < args.length; i++) {
14047
+ if (args[i]?.toHandlerArg) {
14048
+ dispatcher ??= new Dispatcher(path, this.transactor, this);
14049
+ args[i] = args[i].toHandlerArg(dispatcher);
14050
+ }
14120
14051
  }
14052
+ args.push(new EventContext(path, this.transactor, this));
14053
+ return [handler, args];
14121
14054
  }
14122
- registerMacros(macros) {
14123
- for (const key in macros) {
14124
- const lower = key.toLowerCase();
14125
- console.assert(this.macros[lower] === undefined, "macro key collision", lower);
14126
- this.macros[lower] = macros[key];
14055
+ lookupName(name) {
14056
+ const { e } = this;
14057
+ switch (name) {
14058
+ case "value":
14059
+ return getValue(e);
14060
+ case "valueAsInt":
14061
+ return toNullIfNaN(parseInt(getValue(e), 10));
14062
+ case "valueAsFloat":
14063
+ return toNullIfNaN(parseFloat(getValue(e)));
14064
+ case "target":
14065
+ return e.target;
14066
+ case "event":
14067
+ return e;
14068
+ case "isAlt":
14069
+ return e.altKey;
14070
+ case "isShift":
14071
+ return e.shiftKey;
14072
+ case "isCtrl":
14073
+ case "isCmd":
14074
+ return isMac && e.metaKey || e.ctrlKey;
14075
+ case "key":
14076
+ return e.key;
14077
+ case "keyCode":
14078
+ return e.keyCode;
14079
+ case "isUpKey":
14080
+ return e.key === "ArrowUp";
14081
+ case "isDownKey":
14082
+ return e.key === "ArrowDown";
14083
+ case "isSend":
14084
+ return e.key === "Enter";
14085
+ case "isCancel":
14086
+ return e.key === "Escape";
14087
+ case "isTabKey":
14088
+ return e.key === "Tab";
14089
+ case "ctx":
14090
+ return new EventContext(this.dispatchPath, this.transactor, this);
14091
+ case "dragInfo":
14092
+ return this.dragInfo;
14127
14093
  }
14094
+ return null;
14128
14095
  }
14129
- getCompFor(v) {
14130
- return this.comps.getCompFor(v);
14096
+ }
14097
+
14098
+ class NameArgsTransaction extends Transaction {
14099
+ constructor(path, transactor, name, args, parentTransaction, opts = {}) {
14100
+ super(path, transactor, parentTransaction);
14101
+ this.name = name;
14102
+ this.args = args;
14103
+ this.opts = opts;
14104
+ this.targetPath = path;
14131
14105
  }
14132
- registerRequestHandlers(handlers) {
14133
- for (const name in handlers)
14134
- this.reqsByName[name] = new RequestHandler(name, handlers[name]);
14106
+ handlerProp = null;
14107
+ getHandlerForName(comp) {
14108
+ const handlers = comp?.[this.handlerProp];
14109
+ return handlers?.[this.name] ?? handlers?.$unknown ?? nullHandler;
14135
14110
  }
14136
- lookupRequest(name) {
14137
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
14111
+ getHandlerAndArgs(_root, instance, comps) {
14112
+ const handler = this.getHandlerForName(comps.getCompFor(instance));
14113
+ return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
14138
14114
  }
14139
- lookupComponent(name) {
14140
- return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
14115
+ }
14116
+
14117
+ class ResponseEvent extends NameArgsTransaction {
14118
+ handlerProp = "response";
14119
+ constructor(path, transactor, name, args, parent, txnPath = null) {
14120
+ super(path, transactor, name, args, parent);
14121
+ this._txnPath = txnPath;
14141
14122
  }
14142
- lookupMacro(name) {
14143
- return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
14123
+ getTransactionPath() {
14124
+ return this._txnPath ?? super.getTransactionPath();
14144
14125
  }
14145
14126
  }
14146
14127
 
14147
- class ProvideInfo {
14148
- constructor(name, val, symbol) {
14149
- this.name = name;
14150
- this.val = val;
14151
- this.symbol = symbol;
14128
+ class SendEvent extends NameArgsTransaction {
14129
+ handlerProp = "receive";
14130
+ run(rootVal, comps) {
14131
+ return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
14132
+ }
14133
+ afterTransaction() {
14134
+ const { path, name, args, opts, targetPath } = this;
14135
+ if (opts.bubbles && path.steps.length > 0)
14136
+ this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
14152
14137
  }
14153
14138
  }
14154
14139
 
14155
- class LookupInfo {
14156
- constructor(name, compName, provideName, val) {
14157
- this.name = name;
14158
- this.compName = compName;
14159
- this.provideName = provideName;
14160
- this.val = val;
14161
- this._sym = undefined;
14140
+ class BubbleEvent extends SendEvent {
14141
+ handlerProp = "bubble";
14142
+ constructor(path, transactor, name, args, parent, opts, targetPath) {
14143
+ super(path, transactor, name, args, parent, opts);
14144
+ this.targetPath = targetPath ?? path;
14162
14145
  }
14163
- getProducerSymbol(stack) {
14164
- if (this._sym === undefined)
14165
- this._sym = stack.lookupType(this.compName)?.provide?.[this.provideName]?.symbol ?? null;
14166
- return this._sym;
14146
+ stopPropagation() {
14147
+ this.opts.bubbles = false;
14167
14148
  }
14168
14149
  }
14169
- var isString = (v) => typeof v === "string";
14170
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
14171
- var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
14172
- var _compId = 0;
14173
14150
 
14174
- class Component {
14175
- constructor(Class, o) {
14176
- this.id = _compId++;
14177
- this.name = o.name ?? "UnkComp";
14178
- this.Class = Class;
14179
- this.views = { main: new View("main", o.view, o.style) };
14180
- this.commonStyle = o.commonStyle ?? "";
14181
- this.globalStyle = o.globalStyle ?? "";
14182
- this.input = o.input ?? {};
14183
- this.receive = o.receive ?? {};
14184
- this.bubble = o.bubble ?? {};
14185
- this.response = o.response ?? {};
14186
- this.alter = o.alter ?? {};
14187
- for (const name in o.views ?? {}) {
14188
- const v = o.views[name];
14189
- const { view, style } = isString(v) ? { view: v } : v;
14190
- this.views[name] = new View(name, view, style);
14191
- }
14192
- this._rawProvide = o.provide ?? {};
14193
- this._rawLookup = o.lookup ?? {};
14194
- this.provide = {};
14195
- this.lookup = {};
14196
- this.scope = null;
14197
- this.spec = o;
14198
- this.extra = {};
14199
- for (const key of Object.keys(o))
14200
- if (!KNOWN_SPEC_KEYS.has(key))
14201
- this.extra[key] = o[key];
14151
+ class InputDispatchEvent extends NameArgsTransaction {
14152
+ handlerProp = "input";
14153
+ }
14154
+
14155
+ class Completion {
14156
+ constructor() {
14157
+ this.val = undefined;
14158
+ this.selfSettled = false;
14159
+ this.subtreeSettled = false;
14160
+ this.pending = 1;
14161
+ this._selfResolve = null;
14162
+ this._selfPromise = null;
14163
+ this._subtreeResolve = null;
14164
+ this._subtreePromise = null;
14165
+ this._selfReleased = false;
14166
+ }
14167
+ whenSettled() {
14168
+ if (this.selfSettled)
14169
+ return Promise.resolve(this.val);
14170
+ this._selfPromise ??= new Promise((res) => {
14171
+ this._selfResolve = res;
14172
+ });
14173
+ return this._selfPromise;
14202
14174
  }
14203
- clone() {
14204
- return Component.fromSpec(this.spec);
14175
+ whenSubtreeSettled() {
14176
+ if (this.subtreeSettled)
14177
+ return Promise.resolve(this.val);
14178
+ this._subtreePromise ??= new Promise((res) => {
14179
+ this._subtreeResolve = res;
14180
+ });
14181
+ return this._subtreePromise;
14205
14182
  }
14206
- compile(ParseContext2) {
14207
- for (const name in this.views)
14208
- this.views[name].compile(new ParseContext2, this.scope, this.id);
14209
- const ctx = this.views.main.ctx;
14210
- for (const key in this._rawProvide) {
14211
- const val = vp.parseProvide(this._rawProvide[key], ctx);
14212
- if (val)
14213
- this.provide[key] = new ProvideInfo(key, val, Symbol(key));
14214
- }
14215
- for (const key in this._rawLookup) {
14216
- const linfo = this._rawLookup[key];
14217
- const forStr = isString(linfo) ? linfo : isString(linfo?.for) ? linfo.for : null;
14218
- const [compName, provideName] = forStr === null ? [] : forStr.split(".");
14219
- if (!isString(compName) || !isString(provideName))
14220
- continue;
14221
- const defStr = isString(linfo?.default) ? linfo.default : null;
14222
- const val = defStr === null ? null : vp.parseField(defStr, ctx);
14223
- this.lookup[key] = new LookupInfo(key, compName, provideName, val);
14224
- }
14225
- for (const key in this.lookup)
14226
- if (this.provide[key] !== undefined)
14227
- console.warn("name declared in both provide and lookup", this.name, key);
14183
+ markSelfSettled(val) {
14184
+ if (this.selfSettled)
14185
+ return;
14186
+ this.selfSettled = true;
14187
+ this.val = val;
14188
+ this._selfResolve?.(val);
14228
14189
  }
14229
- make(args, opts) {
14230
- return this.Class.make(args, opts ?? { scope: this.scope });
14190
+ ensureSelfSettled() {
14191
+ if (!this.selfSettled)
14192
+ this.markSelfSettled(this.val);
14231
14193
  }
14232
- getView(name) {
14233
- return this.views[name] ?? this.views.main;
14194
+ track() {
14195
+ this.pending++;
14196
+ let done = false;
14197
+ return () => {
14198
+ if (done)
14199
+ return;
14200
+ done = true;
14201
+ this._release();
14202
+ };
14234
14203
  }
14235
- getEventForId(id, name = "main") {
14236
- return this.getView(name).ctx.getEventForId(id);
14204
+ releaseSelf() {
14205
+ if (this._selfReleased)
14206
+ return;
14207
+ this._selfReleased = true;
14208
+ this._release();
14237
14209
  }
14238
- getNodeForId(id, name = "main") {
14239
- return this.getView(name).ctx.getNodeForId(id);
14210
+ _release() {
14211
+ if (--this.pending === 0) {
14212
+ this.subtreeSettled = true;
14213
+ this._subtreeResolve?.(this.val);
14214
+ }
14240
14215
  }
14241
- compileStyle() {
14242
- const { id, commonStyle, globalStyle, views } = this;
14243
- const styles2 = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
14244
- if (globalStyle !== "")
14245
- styles2.push(globalStyle);
14246
- for (const name in views) {
14247
- const { style } = views[name];
14248
- if (style !== "")
14249
- styles2.push(`[data-cid="${id}"][data-vid="${name}"]{${style}}`);
14250
- }
14251
- return styles2.join(`
14252
- `);
14253
- }
14254
- }
14255
-
14256
- // src/stack.js
14257
- var STOP = Symbol("STOP");
14258
- var NEXT = Symbol("NEXT");
14259
- function lookup(chain, name, dv = null) {
14260
- let n = chain;
14261
- while (n !== null) {
14262
- const r = n[0].lookup(name);
14263
- if (r === STOP)
14264
- return dv;
14265
- if (r !== NEXT)
14266
- return r;
14267
- n = n[1];
14268
- }
14269
- return dv;
14270
14216
  }
14271
14217
 
14272
- class BindFrame {
14273
- constructor(it, binds, isFrame) {
14274
- this.it = it;
14275
- this.binds = binds;
14276
- this.isFrame = isFrame;
14218
+ class Dispatcher {
14219
+ constructor(path, transactor, parentTransaction, root = transactor.state.val) {
14220
+ this.path = path;
14221
+ this.transactor = transactor;
14222
+ this.parent = parentTransaction;
14223
+ this.root = root;
14277
14224
  }
14278
- lookup(name) {
14279
- const v = this.binds[name];
14280
- return v === undefined ? this.isFrame ? STOP : NEXT : v;
14225
+ walkPath(callback) {
14226
+ const comps = this.transactor.comps;
14227
+ const chain = this.path.toTransactionPath().resolveChain(this.root);
14228
+ for (let i = chain.length - 1;i >= 0; i--) {
14229
+ const comp = comps.getCompFor(chain[i]);
14230
+ if (comp && callback(comp, chain[i]) === false)
14231
+ return;
14232
+ }
14281
14233
  }
14282
- }
14283
-
14284
- class ObjectFrame {
14285
- constructor(binds) {
14286
- this.binds = binds;
14234
+ get at() {
14235
+ return new PathChanges(this);
14287
14236
  }
14288
- lookup(key) {
14289
- const v = this.binds[key];
14290
- return v === undefined ? NEXT : v;
14237
+ send(name, args, opts) {
14238
+ return this.sendAtPath(this.path, name, args, opts);
14291
14239
  }
14292
- }
14293
- function computeViewsId(views) {
14294
- let s = "";
14295
- let n = views;
14296
- while (n !== null) {
14297
- s += n[0];
14298
- n = n[1];
14240
+ bubble(name, args, opts) {
14241
+ return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
14299
14242
  }
14300
- return s === "main" ? "" : s;
14301
- }
14302
-
14303
- class Stack2 {
14304
- constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
14305
- this.comps = comps;
14306
- this.it = it;
14307
- this.binds = binds;
14308
- this.dynBinds = dynBinds;
14309
- this.views = views;
14310
- this.viewsId = viewsId;
14311
- this.ctx = ctx;
14243
+ sendAtPath(path, name, args, opts) {
14244
+ return this.transactor.pushSend(path, name, args, opts, this.parent);
14312
14245
  }
14313
- _pushProvides() {
14314
- const provide = this.comps.getCompFor(this.it)?.provide;
14315
- if (provide == null)
14316
- return this;
14317
- const dynObj = {};
14318
- let has2 = false;
14319
- for (const k in provide) {
14320
- dynObj[provide[k].symbol] = provide[k].val.eval(this);
14321
- has2 = true;
14322
- }
14323
- if (!has2)
14324
- return this;
14325
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
14326
- const { comps, it, binds, views, viewsId, ctx } = this;
14327
- return new Stack2(comps, it, binds, newDynBinds, views, viewsId, ctx);
14246
+ request(name, args, opts) {
14247
+ return this.requestAtPath(this.path, name, args, opts);
14328
14248
  }
14329
- static root(comps, it, ctx) {
14330
- const binds = [new BindFrame(it, { it }, true), null];
14331
- const dynBinds = [new ObjectFrame({}), null];
14332
- const views = ["main", null];
14333
- return new Stack2(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
14249
+ requestAtPath(path, name, args, opts) {
14250
+ return this.transactor.pushRequest(path, name, args, opts, this.parent);
14334
14251
  }
14335
- enter(it, bindings = {}, isFrame = true) {
14336
- const { comps, binds, dynBinds, views, viewsId, ctx } = this;
14337
- const newBinds = [new BindFrame(it, bindings, isFrame), binds];
14338
- const stack = new Stack2(comps, it, newBinds, dynBinds, views, viewsId, ctx);
14339
- return isFrame ? stack._pushProvides() : stack;
14252
+ inputAtPath(path, name, args, opts) {
14253
+ return this.transactor.pushInput(path, name, args, opts, this.parent);
14340
14254
  }
14341
- pushViewName(name) {
14342
- const { comps, it, binds, dynBinds, views, ctx } = this;
14343
- const newViews = [name, views];
14344
- return new Stack2(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
14255
+ lookupTypeFor(name, inst) {
14256
+ return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
14345
14257
  }
14346
- _pushDynBindValuesToArray(arr, comp) {
14347
- for (const k in comp.provide)
14348
- arr.push(this._lookupProvide(comp.provide[k]));
14349
- for (const k in comp.lookup)
14350
- arr.push(this._lookupAlias(comp.lookup[k]));
14258
+ }
14259
+
14260
+ class EventContext extends Dispatcher {
14261
+ get name() {
14262
+ return this.parent?.name ?? null;
14351
14263
  }
14352
- _lookupProvide(p) {
14353
- return lookup(this.dynBinds, p.symbol) ?? p.val.eval(this) ?? null;
14264
+ get targetPath() {
14265
+ return this.parent.targetPath;
14354
14266
  }
14355
- _lookupAlias(lk) {
14356
- const sym = lk.getProducerSymbol(this);
14357
- return (sym != null ? lookup(this.dynBinds, sym) : null) ?? lk.val?.eval(this) ?? null;
14267
+ stopPropagation() {
14268
+ return this.parent.stopPropagation();
14358
14269
  }
14359
- lookupDynamic(name) {
14360
- const comp = this.comps.getCompFor(this.it);
14361
- if (comp == null)
14362
- return null;
14363
- const lk = comp.lookup[name];
14364
- if (lk !== undefined)
14365
- return this._lookupAlias(lk);
14366
- const p = comp.provide[name];
14367
- return p !== undefined ? this._lookupProvide(p) : null;
14270
+ }
14271
+
14272
+ class RequestContext extends Dispatcher {
14273
+ }
14274
+
14275
+ class PathChanges extends PathBuilder {
14276
+ constructor(dispatcher) {
14277
+ super();
14278
+ this.dispatcher = dispatcher;
14368
14279
  }
14369
- lookupBind(name) {
14370
- return lookup(this.binds, name);
14280
+ send(name, args, opts) {
14281
+ return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
14371
14282
  }
14372
- lookupType(name) {
14373
- return this.comps.getCompFor(this.it).scope.lookupComponent(name);
14283
+ bubble(name, args, opts) {
14284
+ return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
14374
14285
  }
14375
- lookupFieldRaw(name) {
14376
- return this.it[name] ?? null;
14286
+ buildPath() {
14287
+ return this.dispatcher.path.concat(this.pathChanges);
14377
14288
  }
14378
- lookupMethod(name) {
14379
- const fn = this.it[name];
14380
- return fn instanceof Function ? fn.call(this.it) : null;
14289
+ }
14290
+ function rootDispatcher(transactor) {
14291
+ return new Dispatcher(new Path([]), transactor, null);
14292
+ }
14293
+
14294
+ // tools/core/results.js
14295
+ class ModuleInfo {
14296
+ constructor({ path = null, present = new Set, counts = {}, warnings = [] }) {
14297
+ this.path = path;
14298
+ this.present = present;
14299
+ this.counts = counts;
14300
+ this.warnings = warnings;
14381
14301
  }
14382
- lookupName(name) {
14383
- return this.ctx.lookupName(name);
14302
+ }
14303
+
14304
+ class ComponentSummary {
14305
+ constructor({ name, views, fields }) {
14306
+ this.name = name;
14307
+ this.views = views;
14308
+ this.fields = fields;
14384
14309
  }
14385
- getHandlerFor(name, key) {
14386
- return this.comps.getHandlerFor(this.it, name, key);
14310
+ }
14311
+
14312
+ class ComponentList {
14313
+ constructor({ items, total = null, truncated = false }) {
14314
+ this.items = items;
14315
+ this.total = total ?? items.length;
14316
+ this.truncated = truncated;
14387
14317
  }
14388
- lookupRequest(name) {
14389
- return this.comps.getRequestFor(this.it, name);
14318
+ }
14319
+
14320
+ class ExampleIndex {
14321
+ constructor({ sections, total = null, truncated = false }) {
14322
+ this.sections = sections;
14323
+ this.total = total ?? sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
14324
+ this.truncated = truncated;
14390
14325
  }
14391
- lookupBestView(views, defaultViewName) {
14392
- let n = this.views;
14393
- while (n !== null) {
14394
- const view = views[n[0]];
14395
- if (view !== undefined)
14396
- return view;
14397
- n = n[1];
14398
- }
14399
- return views[defaultViewName];
14326
+ }
14327
+
14328
+ class ComponentDocs {
14329
+ constructor({ items }) {
14330
+ this.items = items;
14400
14331
  }
14401
14332
  }
14402
14333
 
14403
- // src/transactor.js
14404
- class State2 {
14405
- constructor(val) {
14406
- this.val = val;
14407
- this.changeSubs = [];
14334
+ class LintFinding {
14335
+ constructor({ id, level, info, context = {}, suggestion = null }) {
14336
+ this.id = id;
14337
+ this.level = level;
14338
+ this.info = info;
14339
+ this.context = context;
14340
+ this.suggestion = suggestion;
14408
14341
  }
14409
- onChange(cb) {
14410
- this.changeSubs.push(cb);
14342
+ }
14343
+
14344
+ class LintComponentResult {
14345
+ constructor({ componentName, findings }) {
14346
+ this.componentName = componentName;
14347
+ this.findings = findings;
14411
14348
  }
14412
- set(val, info) {
14413
- const old = this.val;
14414
- this.val = val;
14415
- for (const sub of this.changeSubs)
14416
- sub({ val, old, info, timestamp: Date.now() });
14349
+ get errorCount() {
14350
+ return this.findings.filter((f) => f.level === "error").length;
14417
14351
  }
14418
- update(fn, info) {
14419
- return this.set(fn(this.val), info);
14352
+ get warnCount() {
14353
+ return this.findings.filter((f) => f.level === "warn").length;
14420
14354
  }
14421
14355
  }
14422
14356
 
14423
- class Transactor {
14424
- constructor(comps, rootValue) {
14425
- this.comps = comps;
14426
- this.transactions = [];
14427
- this.state = new State2(rootValue);
14428
- this.onTransactionPushed = () => {};
14429
- }
14430
- pushTransaction(t) {
14431
- this.transactions.push(t);
14432
- this.onTransactionPushed(t);
14433
- }
14434
- pushSend(path, name, args = [], opts = {}, parent = null) {
14435
- this.pushTransaction(new SendEvent(path, this, name, args, parent, opts));
14436
- }
14437
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
14438
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
14439
- this.pushTransaction(new BubbleEvent(path, this, name, args, parent, newOpts, targetPath));
14440
- }
14441
- async pushRequest(path, name, args = [], opts = {}, parent = null) {
14442
- const curRoot = this.state.val;
14443
- const txnPath = path.toTransactionPath();
14444
- const curLeaf = txnPath.lookup(curRoot);
14445
- const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
14446
- const reqCtx = new RequestContext(path, this, parent, curRoot);
14447
- const resHandlerName = opts?.onResName ?? name;
14448
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
14449
- const push = (specificName, baseName, singleArg, result, error) => {
14450
- const resArgs = specificName ? [singleArg] : [result, error];
14451
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
14452
- this.pushTransaction(t);
14453
- };
14454
- try {
14455
- const result = await handler.fn.apply(null, [...args, reqCtx]);
14456
- push(opts?.onOkName, resHandlerName, result, result, null);
14457
- } catch (error) {
14458
- push(opts?.onErrorName, resHandlerName, error, null, error);
14459
- }
14460
- }
14461
- get hasPendingTransactions() {
14462
- return this.transactions.length > 0;
14357
+ class LintReport {
14358
+ constructor({ components }) {
14359
+ this.components = components;
14463
14360
  }
14464
- transactNext() {
14465
- if (this.hasPendingTransactions)
14466
- this.transact(this.transactions.shift());
14361
+ get hasErrors() {
14362
+ return this.components.some((c) => c.errorCount > 0);
14467
14363
  }
14468
- transact(transaction) {
14469
- const curState = this.state.val;
14470
- const newState = transaction.run(curState, this.comps);
14471
- if (newState !== undefined) {
14472
- this.state.set(newState, { transaction });
14473
- transaction.afterTransaction();
14474
- } else
14475
- console.warn("undefined new state", { curState, transaction });
14364
+ get totalErrors() {
14365
+ return this.components.reduce((n, c) => n + c.errorCount, 0);
14476
14366
  }
14477
- transactInputNow(path, event, eventHandler, dragInfo) {
14478
- this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
14367
+ get totalWarnings() {
14368
+ return this.components.reduce((n, c) => n + c.warnCount, 0);
14479
14369
  }
14480
14370
  }
14481
- function mkReq404(name) {
14482
- const fn = () => {
14483
- throw new Error(`Request not found: ${name}`);
14484
- };
14485
- return { fn };
14486
- }
14487
- function nullHandler() {
14488
- return this;
14489
- }
14490
14371
 
14491
- class Transaction {
14492
- constructor(path, transactor, parentTransaction = null) {
14493
- this.path = path;
14494
- this.transactor = transactor;
14495
- this.parentTransaction = parentTransaction;
14496
- this._task = null;
14497
- }
14498
- get task() {
14499
- this._task ??= new Task;
14500
- return this._task;
14501
- }
14502
- getCompletionPromise() {
14503
- return this.task.promise;
14504
- }
14505
- setParent(parentTransaction) {
14506
- this.parentTransaction = parentTransaction;
14507
- parentTransaction.task.addDep(this.task);
14508
- }
14509
- run(rootValue, comps) {
14510
- return this.updateRootValue(rootValue, comps);
14511
- }
14512
- afterTransaction() {}
14513
- buildRootStack(root, comps) {
14514
- return Stack2.root(comps, root);
14515
- }
14516
- buildStack(root, comps) {
14517
- return this.path.toTransactionPath().buildStack(this.buildRootStack(root, comps));
14518
- }
14519
- callHandler(root, instance, comps) {
14520
- const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
14521
- return handler.apply(instance, args);
14522
- }
14523
- getHandlerAndArgs(_root, _instance, _comps) {
14524
- return null;
14525
- }
14526
- getTransactionPath() {
14527
- return this.path.toTransactionPath();
14528
- }
14529
- updateRootValue(curRoot, comps) {
14530
- const txnPath = this.getTransactionPath();
14531
- const curLeaf = txnPath.lookup(curRoot);
14532
- const newLeaf = this.callHandler(curRoot, curLeaf, comps);
14533
- this._task?.complete?.({ value: newLeaf, old: curLeaf });
14534
- return curLeaf !== newLeaf ? txnPath.setValue(curRoot, newLeaf) : curRoot;
14535
- }
14536
- lookupName(_name) {
14537
- return null;
14372
+ class RenderedExample {
14373
+ constructor({ title, description = null, componentName, view, html, error = null }) {
14374
+ this.title = title;
14375
+ this.description = description;
14376
+ this.componentName = componentName;
14377
+ this.view = view;
14378
+ this.html = html;
14379
+ this.error = error;
14538
14380
  }
14539
14381
  }
14540
- var toNullIfNaN = (v) => Number.isNaN(v) ? null : v;
14541
- function getValue(e) {
14542
- return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
14543
- }
14544
14382
 
14545
- class InputEvent extends Transaction {
14546
- constructor(path, e, handler, transactor, dragInfo) {
14547
- super(path, transactor);
14548
- this.e = e;
14549
- this.handler = handler;
14550
- this.dragInfo = dragInfo;
14551
- this._dispatchPath = null;
14552
- }
14553
- get dispatchPath() {
14554
- this._dispatchPath ??= this.path.compact();
14555
- return this._dispatchPath;
14556
- }
14557
- buildRootStack(root, comps) {
14558
- return Stack2.root(comps, root, this);
14559
- }
14560
- getHandlerAndArgs(root, _instance, comps) {
14561
- const stack = this.buildStack(root, comps);
14562
- const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
14563
- const path = this.dispatchPath;
14564
- let dispatcher;
14565
- for (let i = 0;i < args.length; i++) {
14566
- if (args[i]?.toHandlerArg) {
14567
- dispatcher ??= new Dispatcher(path, this.transactor, this);
14568
- args[i] = args[i].toHandlerArg(dispatcher);
14569
- }
14570
- }
14571
- args.push(new EventContext(path, this.transactor, this));
14572
- return [handler, args];
14573
- }
14574
- lookupName(name) {
14575
- const { e } = this;
14576
- switch (name) {
14577
- case "value":
14578
- return getValue(e);
14579
- case "valueAsInt":
14580
- return toNullIfNaN(parseInt(getValue(e), 10));
14581
- case "valueAsFloat":
14582
- return toNullIfNaN(parseFloat(getValue(e)));
14583
- case "target":
14584
- return e.target;
14585
- case "event":
14586
- return e;
14587
- case "isAlt":
14588
- return e.altKey;
14589
- case "isShift":
14590
- return e.shiftKey;
14591
- case "isCtrl":
14592
- case "isCmd":
14593
- return isMac && e.metaKey || e.ctrlKey;
14594
- case "key":
14595
- return e.key;
14596
- case "keyCode":
14597
- return e.keyCode;
14598
- case "isUpKey":
14599
- return e.key === "ArrowUp";
14600
- case "isDownKey":
14601
- return e.key === "ArrowDown";
14602
- case "isSend":
14603
- return e.key === "Enter";
14604
- case "isCancel":
14605
- return e.key === "Escape";
14606
- case "isTabKey":
14607
- return e.key === "Tab";
14608
- case "ctx":
14609
- return new EventContext(this.dispatchPath, this.transactor, this);
14610
- case "dragInfo":
14611
- return this.dragInfo;
14612
- }
14613
- return null;
14383
+ class RenderedSection {
14384
+ constructor({ title, description = null, items }) {
14385
+ this.title = title;
14386
+ this.description = description;
14387
+ this.items = items;
14614
14388
  }
14615
14389
  }
14616
14390
 
14617
- class NameArgsTransaction extends Transaction {
14618
- constructor(path, transactor, name, args, parentTransaction, opts = {}) {
14619
- super(path, transactor, parentTransaction);
14620
- this.name = name;
14621
- this.args = args;
14622
- this.opts = opts;
14623
- this.targetPath = path;
14624
- }
14625
- handlerProp = null;
14626
- getHandlerForName(comp) {
14627
- const handlers = comp?.[this.handlerProp];
14628
- return handlers?.[this.name] ?? handlers?.$unknown ?? nullHandler;
14391
+ class RenderBatch {
14392
+ constructor({ sections }) {
14393
+ this.sections = sections;
14629
14394
  }
14630
- getHandlerAndArgs(_root, instance, comps) {
14631
- const handler = this.getHandlerForName(comps.getCompFor(instance));
14632
- return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
14395
+ get hasErrors() {
14396
+ return this.sections.some((s) => s.items.some((i) => i.error !== null));
14633
14397
  }
14634
14398
  }
14635
14399
 
14636
- class ResponseEvent extends NameArgsTransaction {
14637
- handlerProp = "response";
14638
- constructor(path, transactor, name, args, parent, txnPath = null) {
14639
- super(path, transactor, name, args, parent);
14640
- this._txnPath = txnPath;
14641
- }
14642
- getTransactionPath() {
14643
- return this._txnPath ?? super.getTransactionPath();
14400
+ class TestResult {
14401
+ constructor({ title, fullPath, componentName = null, status, durationMs = 0, error = null }) {
14402
+ this.title = title;
14403
+ this.fullPath = fullPath;
14404
+ this.componentName = componentName;
14405
+ this.status = status;
14406
+ this.durationMs = durationMs;
14407
+ this.error = error;
14644
14408
  }
14645
14409
  }
14646
14410
 
14647
- class SendEvent extends NameArgsTransaction {
14648
- handlerProp = "receive";
14649
- run(rootVal, comps) {
14650
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
14651
- }
14652
- afterTransaction() {
14653
- const { path, name, args, opts, targetPath } = this;
14654
- if (opts.bubbles && path.steps.length > 0)
14655
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
14411
+ class DescribeResult {
14412
+ constructor({ title, componentName = null, children = [] }) {
14413
+ this.title = title;
14414
+ this.componentName = componentName;
14415
+ this.children = children;
14656
14416
  }
14657
14417
  }
14658
14418
 
14659
- class BubbleEvent extends SendEvent {
14660
- handlerProp = "bubble";
14661
- constructor(path, transactor, name, args, parent, opts, targetPath) {
14662
- super(path, transactor, name, args, parent, opts);
14663
- this.targetPath = targetPath ?? path;
14664
- }
14665
- stopPropagation() {
14666
- this.opts.bubbles = false;
14419
+ class ModuleTestReport {
14420
+ constructor({ path = null, suites = [], counts = { pass: 0, fail: 0, skip: 0, total: 0 } }) {
14421
+ this.path = path;
14422
+ this.suites = suites;
14423
+ this.counts = counts;
14667
14424
  }
14668
14425
  }
14669
14426
 
14670
- class Task {
14671
- constructor() {
14672
- this.deps = [];
14673
- this.val = this.resolve = this.reject = null;
14674
- this.promise = new Promise((res, rej) => {
14675
- this.resolve = res;
14676
- this.reject = rej;
14677
- });
14678
- this.isCompleted = false;
14427
+ class TestReport {
14428
+ constructor({ modules = [] }) {
14429
+ this.modules = modules;
14679
14430
  }
14680
- addDep(task) {
14681
- console.assert(!this.isCompleted, "addDep for completed task", this, task);
14682
- this.deps.push(task);
14683
- task.promise.then((_) => this._check());
14431
+ get totals() {
14432
+ return this.modules.reduce((acc, m) => ({
14433
+ pass: acc.pass + m.counts.pass,
14434
+ fail: acc.fail + m.counts.fail,
14435
+ skip: acc.skip + m.counts.skip,
14436
+ total: acc.total + m.counts.total
14437
+ }), { pass: 0, fail: 0, skip: 0, total: 0 });
14684
14438
  }
14685
- complete(val) {
14686
- this.val = val;
14687
- this._check();
14439
+ get hasFailures() {
14440
+ return this.modules.some((m) => m.counts.fail > 0);
14688
14441
  }
14689
- _check() {
14690
- if (this.deps.every((task) => task.isCompleted)) {
14691
- this.isCompleted = true;
14692
- this.resolve(this);
14693
- }
14442
+ }
14443
+
14444
+ // tools/core/tests.js
14445
+ class Describe {
14446
+ constructor({ title, componentName = null, parent = null }) {
14447
+ this.title = title;
14448
+ this.componentName = componentName;
14449
+ this.parent = parent;
14450
+ this.children = [];
14694
14451
  }
14695
14452
  }
14696
14453
 
14697
- class Dispatcher {
14698
- constructor(path, transactor, parentTransaction, root = transactor.state.val) {
14699
- this.path = path;
14700
- this.transactor = transactor;
14701
- this.parent = parentTransaction;
14702
- this.root = root;
14703
- }
14704
- walkPath(callback) {
14705
- const comps = this.transactor.comps;
14706
- const chain = this.path.toTransactionPath().resolveChain(this.root);
14707
- for (let i = chain.length - 1;i >= 0; i--) {
14708
- const comp = comps.getCompFor(chain[i]);
14709
- if (comp && callback(comp, chain[i]) === false)
14710
- return;
14711
- }
14712
- }
14713
- get at() {
14714
- return new PathChanges(this);
14715
- }
14716
- send(name, args, opts) {
14717
- return this.sendAtPath(this.path, name, args, opts);
14454
+ class Test {
14455
+ constructor({ title, fn, componentName = null, parent = null }) {
14456
+ this.title = title;
14457
+ this.fn = fn;
14458
+ this.componentName = componentName;
14459
+ this.parent = parent;
14718
14460
  }
14719
- bubble(name, args, opts) {
14720
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
14461
+ }
14462
+
14463
+ class ModuleTests {
14464
+ constructor({ path = null, suites = [] } = {}) {
14465
+ this.path = path;
14466
+ this.suites = suites;
14721
14467
  }
14722
- sendAtPath(path, name, args, opts) {
14723
- return this.transactor.pushSend(path, name, args, opts, this.parent);
14468
+ }
14469
+
14470
+ class TestIndex {
14471
+ constructor({ modules = [] } = {}) {
14472
+ this.modules = modules;
14724
14473
  }
14725
- request(name, args, opts) {
14726
- return this.requestAtPath(this.path, name, args, opts);
14474
+ }
14475
+ function isComponentObject(x) {
14476
+ return x !== null && typeof x === "object" && typeof x.name === "string" && typeof x.Class === "function";
14477
+ }
14478
+ function resolveComponentName(arg, components) {
14479
+ if (isComponentObject(arg))
14480
+ return arg.name;
14481
+ if (typeof arg === "function") {
14482
+ for (const c of components)
14483
+ if (c.Class === arg)
14484
+ return c.name;
14727
14485
  }
14728
- requestAtPath(path, name, args, opts) {
14729
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
14486
+ return null;
14487
+ }
14488
+ function titleFromArg(arg) {
14489
+ if (typeof arg === "string")
14490
+ return arg;
14491
+ if (isComponentObject(arg))
14492
+ return arg.name;
14493
+ if (typeof arg === "function")
14494
+ return arg.name || "(anonymous)";
14495
+ return String(arg);
14496
+ }
14497
+ function makeCollector({ path = null, components = [] } = {}) {
14498
+ const moduleTests = new ModuleTests({ path, suites: [] });
14499
+ const stack = [];
14500
+ function describe(...args) {
14501
+ let head;
14502
+ let options = null;
14503
+ let fn;
14504
+ if (args.length === 2) {
14505
+ [head, fn] = args;
14506
+ } else if (args.length === 3) {
14507
+ [head, options, fn] = args;
14508
+ } else {
14509
+ throw new Error(`describe() expects 2 or 3 arguments, got ${args.length}`);
14510
+ }
14511
+ if (typeof fn !== "function") {
14512
+ throw new Error(`describe(${JSON.stringify(titleFromArg(head))}): final argument must be a function`);
14513
+ }
14514
+ let componentName = null;
14515
+ if (typeof head !== "string") {
14516
+ componentName = resolveComponentName(head, components);
14517
+ }
14518
+ if (componentName === null && options && options.component != null) {
14519
+ componentName = resolveComponentName(options.component, components);
14520
+ }
14521
+ if (componentName === null) {
14522
+ const parent2 = stack.length ? stack[stack.length - 1] : null;
14523
+ if (parent2)
14524
+ componentName = parent2.componentName;
14525
+ }
14526
+ const parent = stack.length ? stack[stack.length - 1] : null;
14527
+ const node = new Describe({
14528
+ title: titleFromArg(head),
14529
+ componentName,
14530
+ parent
14531
+ });
14532
+ if (parent)
14533
+ parent.children.push(node);
14534
+ else
14535
+ moduleTests.suites.push(node);
14536
+ stack.push(node);
14537
+ try {
14538
+ fn();
14539
+ } finally {
14540
+ stack.pop();
14541
+ }
14730
14542
  }
14731
- lookupTypeFor(name, inst) {
14732
- return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
14543
+ function test2(title, fn) {
14544
+ if (typeof title !== "string") {
14545
+ throw new Error("test(title, fn): title must be a string");
14546
+ }
14547
+ if (typeof fn !== "function") {
14548
+ throw new Error(`test(${JSON.stringify(title)}): fn must be a function`);
14549
+ }
14550
+ const parent = stack.length ? stack[stack.length - 1] : null;
14551
+ if (!parent) {
14552
+ throw new Error(`test(${JSON.stringify(title)}) must be called inside a describe()`);
14553
+ }
14554
+ parent.children.push(new Test({
14555
+ title,
14556
+ fn,
14557
+ componentName: parent.componentName,
14558
+ parent
14559
+ }));
14733
14560
  }
14561
+ return { describe, test: test2, moduleTests };
14734
14562
  }
14735
14563
 
14736
- class EventContext extends Dispatcher {
14737
- get name() {
14738
- return this.parent?.name ?? null;
14739
- }
14740
- get targetPath() {
14741
- return this.parent.targetPath;
14742
- }
14743
- stopPropagation() {
14744
- return this.parent.stopPropagation();
14564
+ // tools/core/test.js
14565
+ function buildPath(node) {
14566
+ const parts = [];
14567
+ let cur = node;
14568
+ while (cur) {
14569
+ parts.unshift(cur.title);
14570
+ cur = cur.parent;
14745
14571
  }
14572
+ return parts.join(" > ");
14746
14573
  }
14747
-
14748
- class RequestContext extends Dispatcher {
14574
+ function captureError(e) {
14575
+ const out = { message: e.message, stack: e.stack };
14576
+ if ("expected" in e)
14577
+ out.expected = e.expected;
14578
+ if ("actual" in e)
14579
+ out.actual = e.actual;
14580
+ return out;
14749
14581
  }
14750
-
14751
- class PathChanges extends PathBuilder {
14752
- constructor(dispatcher) {
14753
- super();
14754
- this.dispatcher = dispatcher;
14582
+ async function runTests({
14583
+ getTests,
14584
+ components = [],
14585
+ path = null,
14586
+ expect: expect2,
14587
+ name = null,
14588
+ grep = null,
14589
+ bail = false,
14590
+ requestHandlers = null,
14591
+ macros = null
14592
+ } = {}) {
14593
+ const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
14594
+ if (typeof getTests !== "function") {
14595
+ return new TestReport({
14596
+ modules: [new ModuleTestReport({ path, suites: [], counts })]
14597
+ });
14755
14598
  }
14756
- send(name, args, opts) {
14757
- return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
14599
+ if (typeof expect2 !== "function") {
14600
+ throw new Error("runTests: expect must be provided (e.g. chai's expect)");
14758
14601
  }
14759
- bubble(name, args, opts) {
14760
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
14602
+ const { describe, test: test2, moduleTests } = makeCollector({ path, components });
14603
+ await getTests({ describe, test: test2, expect: expect2, drive });
14604
+ let _stack = null;
14605
+ function getStack() {
14606
+ if (_stack)
14607
+ return _stack;
14608
+ _stack = new ComponentStack;
14609
+ _stack.registerComponents(components);
14610
+ if (macros)
14611
+ _stack.registerMacros(macros);
14612
+ if (requestHandlers)
14613
+ _stack.registerRequestHandlers(requestHandlers);
14614
+ return _stack;
14615
+ }
14616
+ async function drive(value, phase, opts = {}) {
14617
+ const transactor = new Transactor(getStack().comps, value);
14618
+ if (opts.onMessage)
14619
+ transactor.state.onChange(({ val, old, info }) => {
14620
+ const t = info?.transaction;
14621
+ opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
14622
+ });
14623
+ dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
14624
+ await transactor.settle();
14625
+ return transactor.state.val;
14761
14626
  }
14762
- buildPath() {
14763
- return this.dispatcher.path.concat(this.pathChanges);
14627
+ let bailed = false;
14628
+ async function visit(node) {
14629
+ if (node instanceof Test) {
14630
+ if (name !== null && node.componentName !== name)
14631
+ return null;
14632
+ const fullPath = buildPath(node);
14633
+ if (grep !== null && !fullPath.includes(grep))
14634
+ return null;
14635
+ counts.total++;
14636
+ if (bailed) {
14637
+ counts.skip++;
14638
+ return new TestResult({
14639
+ title: node.title,
14640
+ fullPath,
14641
+ componentName: node.componentName,
14642
+ status: "skip"
14643
+ });
14644
+ }
14645
+ const start = performance.now();
14646
+ try {
14647
+ await node.fn();
14648
+ counts.pass++;
14649
+ return new TestResult({
14650
+ title: node.title,
14651
+ fullPath,
14652
+ componentName: node.componentName,
14653
+ status: "pass",
14654
+ durationMs: performance.now() - start
14655
+ });
14656
+ } catch (e) {
14657
+ counts.fail++;
14658
+ if (bail)
14659
+ bailed = true;
14660
+ return new TestResult({
14661
+ title: node.title,
14662
+ fullPath,
14663
+ componentName: node.componentName,
14664
+ status: "fail",
14665
+ durationMs: performance.now() - start,
14666
+ error: captureError(e)
14667
+ });
14668
+ }
14669
+ }
14670
+ const childResults = [];
14671
+ for (const child of node.children) {
14672
+ const r = await visit(child);
14673
+ if (r !== null)
14674
+ childResults.push(r);
14675
+ }
14676
+ if (childResults.length === 0)
14677
+ return null;
14678
+ return new DescribeResult({
14679
+ title: node.title,
14680
+ componentName: node.componentName,
14681
+ children: childResults
14682
+ });
14683
+ }
14684
+ const suiteResults = [];
14685
+ for (const suite of moduleTests.suites) {
14686
+ const r = await visit(suite);
14687
+ if (r !== null)
14688
+ suiteResults.push(r);
14689
+ }
14690
+ return new TestReport({
14691
+ modules: [new ModuleTestReport({ path, suites: suiteResults, counts })]
14692
+ });
14693
+ }
14694
+
14695
+ // tools/core/test-console.js
14696
+ var PASS = "color: #0a0; font-weight: bold";
14697
+ var FAIL = "color: #c00; font-weight: bold";
14698
+ var SKIP = "color: #888";
14699
+ var DIM = "color: #888";
14700
+ var RESET = "color: inherit; font-weight: normal";
14701
+ function reportTestNode(node) {
14702
+ if (node.children) {
14703
+ const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
14704
+ console.group(label);
14705
+ for (const child of node.children)
14706
+ reportTestNode(child);
14707
+ console.groupEnd();
14708
+ return;
14709
+ }
14710
+ const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
14711
+ if (node.status === "pass") {
14712
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
14713
+ } else if (node.status === "skip") {
14714
+ console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
14715
+ } else {
14716
+ console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
14717
+ console.error(node.error?.message ?? "(no error message)");
14718
+ if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
14719
+ console.log("expected:", node.error.expected);
14720
+ console.log("actual: ", node.error.actual);
14721
+ }
14722
+ if (node.error?.stack)
14723
+ console.log(node.error.stack);
14724
+ console.groupEnd();
14725
+ }
14726
+ }
14727
+ function reportTestReportToConsole(report) {
14728
+ for (const m of report.modules) {
14729
+ const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
14730
+ console.group(label);
14731
+ if (m.suites.length === 0) {
14732
+ console.log("(no tests)");
14733
+ } else {
14734
+ for (const s of m.suites)
14735
+ reportTestNode(s);
14736
+ }
14737
+ const c = m.counts;
14738
+ const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
14739
+ if (c.fail > 0)
14740
+ console.error(`%c${summary}`, FAIL);
14741
+ else if (c.total === 0)
14742
+ console.log(`%c${summary}`, DIM);
14743
+ else
14744
+ console.log(`%c${summary}`, PASS);
14745
+ console.groupEnd();
14746
+ }
14747
+ return report;
14748
+ }
14749
+
14750
+ // tools/format/lint.js
14751
+ var UNSUPPORTED_EXPR_LABEL = {
14752
+ ternary: "ternary expression",
14753
+ comparison: "comparison",
14754
+ logical: "logical expression",
14755
+ "call-with-args": "method call with arguments"
14756
+ };
14757
+ function unsupportedExprMessage(info) {
14758
+ const v = JSON.stringify(info.value);
14759
+ const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
14760
+ switch (info.role) {
14761
+ case "attr":
14762
+ return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
14763
+ case "directive":
14764
+ return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
14765
+ case "if":
14766
+ return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
14767
+ case "x-op":
14768
+ return `Unsupported ${label} ${v} in <x ${info.op}>`;
14769
+ default:
14770
+ return `Unsupported ${label} ${v}`;
14771
+ }
14772
+ }
14773
+ function badValueMessage(info) {
14774
+ const v = JSON.stringify(info.value);
14775
+ switch (info.role) {
14776
+ case "attr":
14777
+ return `Cannot parse value ${v} for attribute ':${info.attr}'`;
14778
+ case "directive":
14779
+ return `Cannot parse value ${v} for directive '@${info.directive}'`;
14780
+ case "if":
14781
+ return `Cannot parse condition ${v} for '@if.${info.attr}'`;
14782
+ case "x-op":
14783
+ return `Cannot parse value ${v} for <x ${info.op}>`;
14784
+ case "handler-name":
14785
+ return `Cannot parse handler name ${v}`;
14786
+ case "handler-arg":
14787
+ return `Cannot parse handler argument ${v}`;
14788
+ case "macro-var":
14789
+ return `Macro variable '^${info.name}' is not defined`;
14790
+ default:
14791
+ return `Cannot parse value ${v}`;
14792
+ }
14793
+ }
14794
+ function tagDisplay(tag) {
14795
+ return tag ? String(tag).toLowerCase() : null;
14796
+ }
14797
+ function fmtTagSuffix(info) {
14798
+ const t = tagDisplay(info?.tag);
14799
+ return t && t !== "x" ? ` on <${t}>` : "";
14800
+ }
14801
+ function fmtOriginSuffix(info) {
14802
+ if (!info)
14803
+ return "";
14804
+ const parts = [];
14805
+ if (info.originAttr) {
14806
+ const branch = info.branch ? `[${info.branch}]` : "";
14807
+ parts.push(`in ${info.originAttr}${branch}`);
14808
+ }
14809
+ if (info.handlerName) {
14810
+ parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
14811
+ }
14812
+ const t = tagDisplay(info.tag);
14813
+ if (t && t !== "x")
14814
+ parts.push(`on <${t}>`);
14815
+ return parts.length ? ` (${parts.join(", ")})` : "";
14816
+ }
14817
+ function fmtEventSuffix(info) {
14818
+ if (info?.originAttr)
14819
+ return ` in ${info.originAttr}`;
14820
+ if (info?.eventName)
14821
+ return ` in @on.${info.eventName}`;
14822
+ return "";
14823
+ }
14824
+ function lintIdToMessage(id, info) {
14825
+ switch (id) {
14826
+ case "RENDER_IT_OUTSIDE_OF_LOOP":
14827
+ return "<x render-it> used outside of a loop";
14828
+ case "UNKNOWN_EVENT_MODIFIER": {
14829
+ const mods = info.handler?.modifiers ?? [info.modifier];
14830
+ const written = `@on.${info.name}+${mods.join("+")}`;
14831
+ return `Unknown modifier '+${info.modifier}' in '${written}'`;
14832
+ }
14833
+ case "UNKNOWN_HANDLER_ARG_NAME":
14834
+ return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
14835
+ case "INPUT_HANDLER_NOT_IMPLEMENTED":
14836
+ return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
14837
+ case "INPUT_HANDLER_NOT_REFERENCED":
14838
+ return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
14839
+ case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
14840
+ return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
14841
+ case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
14842
+ return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
14843
+ case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
14844
+ return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
14845
+ case "FIELD_VAL_NOT_DEFINED":
14846
+ return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
14847
+ case "FIELD_VAL_IS_METHOD":
14848
+ return `'.${info.name}' reads a field, but '${info.name}' is defined as a method — use '$${info.name}'${fmtOriginSuffix(info)}`;
14849
+ case "METHOD_VAL_NOT_DEFINED":
14850
+ return `Method '$${info.name}' is not defined${fmtOriginSuffix(info)}`;
14851
+ case "METHOD_VAL_IS_FIELD":
14852
+ return `'$${info.name}' calls a method, but '${info.name}' is defined as a field — use '.${info.name}'${fmtOriginSuffix(info)}`;
14853
+ case "DUPLICATE_ATTR_DEFINITION": {
14854
+ const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
14855
+ const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
14856
+ return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
14857
+ }
14858
+ case "IF_NO_BRANCH_SET":
14859
+ return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
14860
+ case "UNKNOWN_REQUEST_NAME":
14861
+ return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
14862
+ case "UNKNOWN_COMPONENT_NAME":
14863
+ return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
14864
+ case "ALT_HANDLER_NOT_DEFINED":
14865
+ return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
14866
+ case "ALT_HANDLER_NOT_REFERENCED":
14867
+ return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
14868
+ case "DYN_VAL_NOT_DEFINED":
14869
+ return `Dynamic variable '*${info.name}' is not defined${fmtOriginSuffix(info)}`;
14870
+ case "DYN_ALIAS_NOT_REFERENCED":
14871
+ return `Lookup '${info.name}' is defined but never used — remove it or reference it as '*${info.name}' in a view`;
14872
+ case "PROVIDE_NOT_ADDRESSABLE":
14873
+ return `Provide '${info.name}' value '${info.value}' must be a field ('.f') or seq-access ('.s[.k]') — a method/constant can't be a render target`;
14874
+ case "LOOKUP_BAD_SHAPE":
14875
+ return `Lookup '${info.name}' has an invalid shape: ${info.problem}`;
14876
+ case "LOOKUP_TARGET_MALFORMED":
14877
+ return `Lookup '${info.name}' target '${info.target}' must be 'Producer.provideName' (a string, or the 'for' of { for, default })`;
14878
+ case "UNKNOWN_MACRO_ARG":
14879
+ return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
14880
+ case "UNKNOWN_DIRECTIVE":
14881
+ return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
14882
+ case "UNKNOWN_X_OP":
14883
+ return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
14884
+ case "UNKNOWN_X_ATTR":
14885
+ return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
14886
+ case "MAYBE_DROP_AT_PREFIX": {
14887
+ const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
14888
+ return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
14889
+ }
14890
+ case "MAYBE_ADD_AT_PREFIX":
14891
+ return `'${info.name}' on <${(info.tag ?? "").toLowerCase()}> is a plain attribute, but '@${info.name}' is a directive — add the leading '@'`;
14892
+ case "BAD_VALUE":
14893
+ return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
14894
+ case "UNSUPPORTED_EXPR_SYNTAX":
14895
+ return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
14896
+ case "REDUNDANT_TEMPLATE_STRING":
14897
+ return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
14898
+ case "PLACEHOLDERLESS_TEMPLATE_STRING":
14899
+ return `Template string has no dynamic parts — use the string literal ${info.literal} instead${fmtOriginSuffix(info)}`;
14900
+ case "UNKNOWN_COMPONENT_SPEC_KEY":
14901
+ return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
14902
+ case "COMP_FIELD_BAD_SHAPE":
14903
+ return info.kind === "args-not-object" ? `Field '${info.fieldName}': in { component, args }, 'args' must be a plain object, got ${info.got}` : `Field '${info.fieldName}': in { component, args }, 'component' must be the component name as a string, got ${info.gotName ? `the ${info.gotName} class` : info.got}`;
14904
+ case "HTML_TAG_NAME_HAS_UPPERCASE":
14905
+ return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
14906
+ case "HTML_SVG_TAG_WILL_LOWERCASE":
14907
+ return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
14908
+ case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
14909
+ return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
14910
+ case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
14911
+ return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
14912
+ case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
14913
+ return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
14914
+ case "HTML_DUPLICATE_FORM":
14915
+ return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
14916
+ case "HTML_NESTED_INTERACTIVE":
14917
+ return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
14918
+ case "HTML_MISNESTED_FORMATTING":
14919
+ return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
14920
+ case "HTML_UNEXPECTED_END_TAG":
14921
+ return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
14922
+ case "HTML_UNCLOSED_BEFORE_END":
14923
+ return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
14924
+ case "HTML_DUPLICATE_ATTRIBUTE":
14925
+ return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
14926
+ case "HTML_ATTRIBUTES_ON_END_TAG":
14927
+ return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
14928
+ case "HTML_SELF_CLOSING_END_TAG":
14929
+ return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
14930
+ case "HTML_MISSING_ATTRIBUTE_VALUE":
14931
+ return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
14932
+ case "HTML_CDATA_IN_HTML_NAMESPACE":
14933
+ return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
14934
+ case "HTML_BOGUS_COMMENT":
14935
+ return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
14936
+ case "HTML_SVG_ATTR_WILL_LOWERCASE":
14937
+ return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
14938
+ case "HTML_MATHML_ATTR_WILL_LOWERCASE":
14939
+ return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
14940
+ case "ASYNC_HANDLER":
14941
+ return `Handler '${info.name}' in '${info.channel}' is an async function — handlers must be synchronous and return the updated state (an async function returns a Promise the framework won't await)`;
14942
+ case "TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE":
14943
+ return `'@${info.atRule}' is a top-level-only at-rule, but '${info.key}' is wrapped in a component-scoped selector ([data-cid=…]{…}) where it is invalid and silently dropped — move it to 'globalStyle'${fmtLocationSuffix(info)}`;
14944
+ case "GLOBAL_SELECTOR_IN_SCOPED_STYLE":
14945
+ return `Selector '${info.selector}' targets the document root, but '${info.key}' is wrapped in a component-scoped selector, so it becomes a descendant selector that never matches — move it to 'globalStyle'${fmtLocationSuffix(info)}`;
14946
+ case "LINT_ERROR":
14947
+ return info.message;
14948
+ default:
14949
+ return id;
14950
+ }
14951
+ }
14952
+ function suggestionToMessage(suggestion) {
14953
+ if (!suggestion)
14954
+ return null;
14955
+ switch (suggestion.kind) {
14956
+ case "replace-name":
14957
+ return `did you mean '${suggestion.to}'?`;
14958
+ case "drop-prefix":
14959
+ return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
14960
+ case "add-prefix":
14961
+ return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
14962
+ case "remove":
14963
+ return `remove ${suggestion.what}`;
14964
+ case "rewrite":
14965
+ return `use '${suggestion.to}' instead of '${suggestion.from}'`;
14966
+ case "wrap":
14967
+ return `wrap it in ${suggestion.to}`;
14968
+ case "rephrase":
14969
+ return suggestion.text ?? null;
14970
+ default:
14971
+ return null;
14972
+ }
14973
+ }
14974
+ function fmtLocationSuffix(info) {
14975
+ const loc = info?.location;
14976
+ if (!loc)
14977
+ return "";
14978
+ return ` at line ${loc.line}, col ${loc.column}`;
14979
+ }
14980
+ function htmlActionPhrase(action, tag, parent) {
14981
+ switch (action) {
14982
+ case "ignored":
14983
+ return `the parser will drop this <${tag}>`;
14984
+ case "drop":
14985
+ return `the parser will drop this <${tag}>`;
14986
+ case "auto-close-implicit":
14987
+ return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
14988
+ case "foster-parent":
14989
+ return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
14990
+ case "foreign-breakout":
14991
+ return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
14992
+ default:
14993
+ return `parser action: ${action}`;
14764
14994
  }
14765
14995
  }
14766
14996
 
@@ -15732,9 +15962,11 @@ export {
15732
15962
  setIn$1 as setIn,
15733
15963
  set2 as set,
15734
15964
  runTests,
15965
+ resolveArgs,
15735
15966
  reportTestReportToConsole,
15736
15967
  removeIn,
15737
15968
  remove,
15969
+ phaseOps,
15738
15970
  mergeWith$1 as mergeWith,
15739
15971
  mergeDeepWith$1 as mergeDeepWith,
15740
15972
  mergeDeep$1 as mergeDeep,
@@ -15771,6 +16003,7 @@ export {
15771
16003
  get2 as get,
15772
16004
  fromJS,
15773
16005
  docComponents,
16006
+ dispatchPhase,
15774
16007
  css,
15775
16008
  component,
15776
16009
  compileClassesToStyleText,
@@ -15792,6 +16025,7 @@ export {
15792
16025
  TestReport,
15793
16026
  TestIndex,
15794
16027
  Test,
16028
+ TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE,
15795
16029
  Stack,
15796
16030
  Set2 as Set,
15797
16031
  Seq,
@@ -15836,6 +16070,7 @@ export {
15836
16070
  INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
15837
16071
  Map2 as IMap,
15838
16072
  IF_NO_BRANCH_SET,
16073
+ GLOBAL_SELECTOR_IN_SCOPED_STYLE,
15839
16074
  FIELD_VAL_NOT_DEFINED,
15840
16075
  FIELD_VAL_IS_METHOD,
15841
16076
  FIELD_CLASS,