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.
@@ -5033,6 +5033,8 @@ var PLACEHOLDERLESS_TEMPLATE_STRING = "PLACEHOLDERLESS_TEMPLATE_STRING";
5033
5033
  var UNKNOWN_COMPONENT_SPEC_KEY = "UNKNOWN_COMPONENT_SPEC_KEY";
5034
5034
  var COMP_FIELD_BAD_SHAPE = "COMP_FIELD_BAD_SHAPE";
5035
5035
  var ASYNC_HANDLER = "ASYNC_HANDLER";
5036
+ var TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE = "TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE";
5037
+ var GLOBAL_SELECTOR_IN_SCOPED_STYLE = "GLOBAL_SELECTOR_IN_SCOPED_STYLE";
5036
5038
  var X_KNOWN_OP_NAMES = new Set([
5037
5039
  "slot",
5038
5040
  "text",
@@ -5124,6 +5126,7 @@ function checkComponent(Comp, lx = new LintContext, { wellKnownExtras = EMPTY_SE
5124
5126
  checkProvidesAreAddressable(lx, Comp);
5125
5127
  checkLookupShapes(lx, Comp);
5126
5128
  checkHandlersNotAsync(lx, Comp);
5129
+ checkScopedStyleTopLevel(lx, Comp);
5127
5130
  const referencedAlters = new Set;
5128
5131
  const referencedInputs = new Set;
5129
5132
  const referencedDynamics = new Set;
@@ -5641,6 +5644,69 @@ function checkHandlersNotAsync(lx, Comp) {
5641
5644
  }
5642
5645
  }
5643
5646
  }
5647
+ 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;
5648
+ var GLOBAL_LEADING_SELECTOR = /(?:^|[{}])\s*(html|body|:root)\b[^{};]*\{/gi;
5649
+ var IGNORE_DIRECTIVE = /\/\*\s*tutuca-lint-ignore\s*\*\//g;
5650
+ var blankRun = (m) => m.replace(/[^\n]/g, " ");
5651
+ function blankCssNoise(css) {
5652
+ return css.replace(/\/\*[\s\S]*?\*\//g, blankRun).replace(/"(?:[^"\\\n]|\\.)*"/g, blankRun).replace(/'(?:[^'\\\n]|\\.)*'/g, blankRun);
5653
+ }
5654
+ function offsetToLineCol2(str, index) {
5655
+ let line = 1;
5656
+ let lineStart = 0;
5657
+ for (let i = 0;i < index; i++) {
5658
+ if (str[i] === `
5659
+ `) {
5660
+ line++;
5661
+ lineStart = i + 1;
5662
+ }
5663
+ }
5664
+ return { line, column: index - lineStart + 1 };
5665
+ }
5666
+ function suppressedLines(css) {
5667
+ const lines = new Set;
5668
+ IGNORE_DIRECTIVE.lastIndex = 0;
5669
+ for (let m = IGNORE_DIRECTIVE.exec(css);m !== null; m = IGNORE_DIRECTIVE.exec(css)) {
5670
+ lines.add(offsetToLineCol2(css, m.index).line);
5671
+ }
5672
+ return lines;
5673
+ }
5674
+ 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.";
5675
+ function scanScopedCss(lx, css, key) {
5676
+ if (!css)
5677
+ return;
5678
+ const ignore = suppressedLines(css);
5679
+ const clean = blankCssNoise(css);
5680
+ const report = (id, info) => {
5681
+ if (ignore.has(info.location.line))
5682
+ return;
5683
+ lx.error(id, { key, ...info }, { kind: "rephrase", text: STYLE_TO_GLOBAL_HELP });
5684
+ };
5685
+ NON_NESTABLE_AT_RULE.lastIndex = 0;
5686
+ for (let m = NON_NESTABLE_AT_RULE.exec(clean);m !== null; m = NON_NESTABLE_AT_RULE.exec(clean)) {
5687
+ const at = m.index + m[0].indexOf("@");
5688
+ report(TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE, {
5689
+ atRule: m[1].toLowerCase(),
5690
+ location: offsetToLineCol2(clean, at)
5691
+ });
5692
+ }
5693
+ GLOBAL_LEADING_SELECTOR.lastIndex = 0;
5694
+ for (let m = GLOBAL_LEADING_SELECTOR.exec(clean);m !== null; m = GLOBAL_LEADING_SELECTOR.exec(clean)) {
5695
+ const at = m.index + m[0].indexOf(m[1]);
5696
+ report(GLOBAL_SELECTOR_IN_SCOPED_STYLE, {
5697
+ selector: m[1].toLowerCase(),
5698
+ location: offsetToLineCol2(clean, at)
5699
+ });
5700
+ }
5701
+ }
5702
+ function checkScopedStyleTopLevel(lx, Comp) {
5703
+ scanScopedCss(lx, Comp.commonStyle, "commonStyle");
5704
+ for (const name in Comp.views) {
5705
+ const { style } = Comp.views[name];
5706
+ if (style)
5707
+ lx.push({ viewName: name }, () => scanScopedCss(lx, style, "style"));
5708
+ }
5709
+ }
5644
5710
  function checkProvidesAreAddressable(lx, Comp) {
5645
5711
  for (const name in Comp._rawProvide) {
5646
5712
  if (Comp.provide[name] === undefined) {
@@ -5734,1382 +5800,1546 @@ class LintParseContext extends ParseContext {
5734
5800
  }
5735
5801
  }
5736
5802
 
5737
- // tools/core/results.js
5738
- class ModuleInfo {
5739
- constructor({ path = null, present = new Set, counts = {}, warnings = [] }) {
5740
- this.path = path;
5741
- this.present = present;
5742
- this.counts = counts;
5743
- this.warnings = warnings;
5744
- }
5745
- }
5746
-
5747
- class ComponentSummary {
5748
- constructor({ name, views, fields }) {
5749
- this.name = name;
5750
- this.views = views;
5751
- this.fields = fields;
5752
- }
5753
- }
5754
-
5755
- class ComponentList {
5756
- constructor({ items, total = null, truncated = false }) {
5757
- this.items = items;
5758
- this.total = total ?? items.length;
5759
- this.truncated = truncated;
5803
+ // src/components.js
5804
+ class Components {
5805
+ constructor() {
5806
+ this.getComponentSymbol = Symbol("getComponent");
5807
+ this.byId = new Map;
5760
5808
  }
5761
- }
5762
-
5763
- class ExampleIndex {
5764
- constructor({ sections, total = null, truncated = false }) {
5765
- this.sections = sections;
5766
- this.total = total ?? sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
5767
- this.truncated = truncated;
5809
+ registerComponent(comp) {
5810
+ comp.Class.prototype[this.getComponentSymbol] = () => comp;
5811
+ this.byId.set(comp.id, comp);
5768
5812
  }
5769
- }
5770
-
5771
- class ComponentDocs {
5772
- constructor({ items }) {
5773
- this.items = items;
5813
+ getComponentForId(id) {
5814
+ return this.byId.get(id) ?? null;
5774
5815
  }
5775
- }
5776
-
5777
- class LintFinding {
5778
- constructor({ id, level, info, context = {}, suggestion = null }) {
5779
- this.id = id;
5780
- this.level = level;
5781
- this.info = info;
5782
- this.context = context;
5783
- this.suggestion = suggestion;
5816
+ getCompFor(v) {
5817
+ return v?.[this.getComponentSymbol]?.() ?? null;
5784
5818
  }
5785
- }
5786
-
5787
- class LintComponentResult {
5788
- constructor({ componentName, findings }) {
5789
- this.componentName = componentName;
5790
- this.findings = findings;
5819
+ getHandlerFor(v, name, key) {
5820
+ return this.getCompFor(v)?.[key][name] ?? null;
5791
5821
  }
5792
- get errorCount() {
5793
- return this.findings.filter((f) => f.level === "error").length;
5822
+ getRequestFor(v, name) {
5823
+ return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
5794
5824
  }
5795
- get warnCount() {
5796
- return this.findings.filter((f) => f.level === "warn").length;
5825
+ compileStyles() {
5826
+ const styles = [];
5827
+ for (const comp of this.byId.values())
5828
+ styles.push(comp.compileStyle());
5829
+ return styles.join(`
5830
+ `);
5797
5831
  }
5798
5832
  }
5799
5833
 
5800
- class LintReport {
5801
- constructor({ components }) {
5802
- this.components = components;
5834
+ class ComponentStack {
5835
+ constructor(comps = new Components, parent = null) {
5836
+ this.comps = comps;
5837
+ this.parent = parent;
5838
+ this.byName = {};
5839
+ this.reqsByName = {};
5840
+ this.macros = {};
5803
5841
  }
5804
- get hasErrors() {
5805
- return this.components.some((c) => c.errorCount > 0);
5842
+ enter() {
5843
+ return new ComponentStack(this.comps, this);
5806
5844
  }
5807
- get totalErrors() {
5808
- return this.components.reduce((n, c) => n + c.errorCount, 0);
5845
+ registerComponents(comps, opts) {
5846
+ const { aliases = {} } = opts ?? {};
5847
+ for (let i = 0;i < comps.length; i++) {
5848
+ const comp = comps[i];
5849
+ comp.scope = this.enter();
5850
+ comp.Class.scope = comp.scope;
5851
+ this.comps.registerComponent(comp);
5852
+ this.byName[comp.name] = comp;
5853
+ }
5854
+ for (const alias in aliases) {
5855
+ const comp = this.byName[aliases[alias]];
5856
+ console.assert(this.byName[alias] === undefined, "alias overrides component", alias);
5857
+ if (comp !== undefined)
5858
+ this.byName[alias] = comp;
5859
+ else
5860
+ console.warn("alias", alias, "to inexistent component", aliases[alias]);
5861
+ }
5809
5862
  }
5810
- get totalWarnings() {
5811
- return this.components.reduce((n, c) => n + c.warnCount, 0);
5863
+ registerMacros(macros) {
5864
+ for (const key in macros) {
5865
+ const lower = key.toLowerCase();
5866
+ console.assert(this.macros[lower] === undefined, "macro key collision", lower);
5867
+ this.macros[lower] = macros[key];
5868
+ }
5812
5869
  }
5813
- }
5814
-
5815
- class RenderedExample {
5816
- constructor({ title, description = null, componentName, view, html, error = null }) {
5817
- this.title = title;
5818
- this.description = description;
5819
- this.componentName = componentName;
5820
- this.view = view;
5821
- this.html = html;
5822
- this.error = error;
5870
+ getCompFor(v) {
5871
+ return this.comps.getCompFor(v);
5823
5872
  }
5824
- }
5825
-
5826
- class RenderedSection {
5827
- constructor({ title, description = null, items }) {
5828
- this.title = title;
5829
- this.description = description;
5830
- this.items = items;
5873
+ registerRequestHandlers(handlers) {
5874
+ for (const name in handlers)
5875
+ this.reqsByName[name] = new RequestHandler(name, handlers[name]);
5831
5876
  }
5832
- }
5833
-
5834
- class RenderBatch {
5835
- constructor({ sections }) {
5836
- this.sections = sections;
5877
+ lookupRequest(name) {
5878
+ return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
5837
5879
  }
5838
- get hasErrors() {
5839
- return this.sections.some((s) => s.items.some((i) => i.error !== null));
5880
+ lookupComponent(name) {
5881
+ return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
5840
5882
  }
5841
- }
5842
-
5843
- class TestResult {
5844
- constructor({ title, fullPath, componentName = null, status, durationMs = 0, error = null }) {
5845
- this.title = title;
5846
- this.fullPath = fullPath;
5847
- this.componentName = componentName;
5848
- this.status = status;
5849
- this.durationMs = durationMs;
5850
- this.error = error;
5883
+ lookupMacro(name) {
5884
+ return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
5851
5885
  }
5852
5886
  }
5853
5887
 
5854
- class DescribeResult {
5855
- constructor({ title, componentName = null, children = [] }) {
5856
- this.title = title;
5857
- this.componentName = componentName;
5858
- this.children = children;
5888
+ class ProvideInfo {
5889
+ constructor(name, val, symbol) {
5890
+ this.name = name;
5891
+ this.val = val;
5892
+ this.symbol = symbol;
5859
5893
  }
5860
5894
  }
5861
5895
 
5862
- class ModuleTestReport {
5863
- constructor({ path = null, suites = [], counts = { pass: 0, fail: 0, skip: 0, total: 0 } }) {
5864
- this.path = path;
5865
- this.suites = suites;
5866
- this.counts = counts;
5896
+ class LookupInfo {
5897
+ constructor(name, compName, provideName, val) {
5898
+ this.name = name;
5899
+ this.compName = compName;
5900
+ this.provideName = provideName;
5901
+ this.val = val;
5902
+ this._sym = undefined;
5903
+ }
5904
+ getProducerSymbol(stack) {
5905
+ if (this._sym === undefined)
5906
+ this._sym = stack.lookupType(this.compName)?.provide?.[this.provideName]?.symbol ?? null;
5907
+ return this._sym;
5867
5908
  }
5868
5909
  }
5910
+ var isString = (v) => typeof v === "string";
5911
+ var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
5912
+ var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
5913
+ var _compId = 0;
5869
5914
 
5870
- class TestReport {
5871
- constructor({ modules = [] }) {
5872
- this.modules = modules;
5873
- }
5874
- get totals() {
5875
- return this.modules.reduce((acc, m) => ({
5876
- pass: acc.pass + m.counts.pass,
5877
- fail: acc.fail + m.counts.fail,
5878
- skip: acc.skip + m.counts.skip,
5879
- total: acc.total + m.counts.total
5880
- }), { pass: 0, fail: 0, skip: 0, total: 0 });
5915
+ class Component {
5916
+ constructor(Class, o) {
5917
+ this.id = _compId++;
5918
+ this.name = o.name ?? "UnkComp";
5919
+ this.Class = Class;
5920
+ this.views = { main: new View("main", o.view, o.style) };
5921
+ this.commonStyle = o.commonStyle ?? "";
5922
+ this.globalStyle = o.globalStyle ?? "";
5923
+ this.input = o.input ?? {};
5924
+ this.receive = o.receive ?? {};
5925
+ this.bubble = o.bubble ?? {};
5926
+ this.response = o.response ?? {};
5927
+ this.alter = o.alter ?? {};
5928
+ for (const name in o.views ?? {}) {
5929
+ const v = o.views[name];
5930
+ const { view, style } = isString(v) ? { view: v } : v;
5931
+ this.views[name] = new View(name, view, style);
5932
+ }
5933
+ this._rawProvide = o.provide ?? {};
5934
+ this._rawLookup = o.lookup ?? {};
5935
+ this.provide = {};
5936
+ this.lookup = {};
5937
+ this.scope = null;
5938
+ this.spec = o;
5939
+ this.extra = {};
5940
+ for (const key of Object.keys(o))
5941
+ if (!KNOWN_SPEC_KEYS.has(key))
5942
+ this.extra[key] = o[key];
5881
5943
  }
5882
- get hasFailures() {
5883
- return this.modules.some((m) => m.counts.fail > 0);
5944
+ clone() {
5945
+ return Component.fromSpec(this.spec);
5884
5946
  }
5885
- }
5886
-
5887
- // tools/core/tests.js
5888
- class Describe {
5889
- constructor({ title, componentName = null, parent = null }) {
5890
- this.title = title;
5891
- this.componentName = componentName;
5892
- this.parent = parent;
5893
- this.children = [];
5947
+ compile(ParseContext2) {
5948
+ for (const name in this.views)
5949
+ this.views[name].compile(new ParseContext2, this.scope, this.id);
5950
+ const ctx = this.views.main.ctx;
5951
+ for (const key in this._rawProvide) {
5952
+ const val = vp.parseProvide(this._rawProvide[key], ctx);
5953
+ if (val)
5954
+ this.provide[key] = new ProvideInfo(key, val, Symbol(key));
5955
+ }
5956
+ for (const key in this._rawLookup) {
5957
+ const linfo = this._rawLookup[key];
5958
+ const forStr = isString(linfo) ? linfo : isString(linfo?.for) ? linfo.for : null;
5959
+ const [compName, provideName] = forStr === null ? [] : forStr.split(".");
5960
+ if (!isString(compName) || !isString(provideName))
5961
+ continue;
5962
+ const defStr = isString(linfo?.default) ? linfo.default : null;
5963
+ const val = defStr === null ? null : vp.parseField(defStr, ctx);
5964
+ this.lookup[key] = new LookupInfo(key, compName, provideName, val);
5965
+ }
5966
+ for (const key in this.lookup)
5967
+ if (this.provide[key] !== undefined)
5968
+ console.warn("name declared in both provide and lookup", this.name, key);
5894
5969
  }
5895
- }
5896
-
5897
- class Test {
5898
- constructor({ title, fn, componentName = null, parent = null }) {
5899
- this.title = title;
5900
- this.fn = fn;
5901
- this.componentName = componentName;
5902
- this.parent = parent;
5970
+ make(args, opts) {
5971
+ return this.Class.make(args, opts ?? { scope: this.scope });
5903
5972
  }
5904
- }
5905
-
5906
- class ModuleTests {
5907
- constructor({ path = null, suites = [] } = {}) {
5908
- this.path = path;
5909
- this.suites = suites;
5973
+ getView(name) {
5974
+ return this.views[name] ?? this.views.main;
5975
+ }
5976
+ getEventForId(id, name = "main") {
5977
+ return this.getView(name).ctx.getEventForId(id);
5978
+ }
5979
+ getNodeForId(id, name = "main") {
5980
+ return this.getView(name).ctx.getNodeForId(id);
5981
+ }
5982
+ compileStyle() {
5983
+ const { id, commonStyle, globalStyle, views } = this;
5984
+ const styles = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
5985
+ if (globalStyle !== "")
5986
+ styles.push(globalStyle);
5987
+ for (const name in views) {
5988
+ const { style } = views[name];
5989
+ if (style !== "")
5990
+ styles.push(`[data-cid="${id}"][data-vid="${name}"]{${style}}`);
5991
+ }
5992
+ return styles.join(`
5993
+ `);
5910
5994
  }
5911
5995
  }
5912
5996
 
5913
- class TestIndex {
5914
- constructor({ modules = [] } = {}) {
5915
- this.modules = modules;
5916
- }
5997
+ // src/on.js
5998
+ var OP_KINDS = ["send", "bubble", "request", "input"];
5999
+ function phaseOps(phase) {
6000
+ const ops = [];
6001
+ for (const type of OP_KINDS)
6002
+ for (const a of phase[type] ?? [])
6003
+ ops.push({ type, ...a });
6004
+ for (const a of phase.do ?? [])
6005
+ ops.push(a);
6006
+ return ops;
5917
6007
  }
5918
- function isComponentObject(x) {
5919
- return x !== null && typeof x === "object" && typeof x.name === "string" && typeof x.Class === "function";
6008
+ function resolveArgs(args, self) {
6009
+ return typeof args === "function" ? args(self) ?? [] : args ?? [];
5920
6010
  }
5921
- function resolveComponentName(arg, components) {
5922
- if (isComponentObject(arg))
5923
- return arg.name;
5924
- if (typeof arg === "function") {
5925
- for (const c of components)
5926
- if (c.Class === arg)
5927
- return c.name;
6011
+ function dispatchPhase(dispatcher, targetPath, phase, self) {
6012
+ if (!phase)
6013
+ return;
6014
+ for (const op of phaseOps(phase)) {
6015
+ const args = resolveArgs(op.args, self);
6016
+ switch (op.type) {
6017
+ case "send":
6018
+ dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
6019
+ break;
6020
+ case "bubble":
6021
+ dispatcher.sendAtPath(targetPath, op.name, args, {
6022
+ skipSelf: true,
6023
+ bubbles: true,
6024
+ ...op.opts
6025
+ });
6026
+ break;
6027
+ case "request":
6028
+ dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
6029
+ break;
6030
+ case "input":
6031
+ dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
6032
+ break;
6033
+ }
5928
6034
  }
5929
- return null;
5930
6035
  }
5931
- function titleFromArg(arg) {
5932
- if (typeof arg === "string")
5933
- return arg;
5934
- if (isComponentObject(arg))
5935
- return arg.name;
5936
- if (typeof arg === "function")
5937
- return arg.name || "(anonymous)";
5938
- return String(arg);
6036
+
6037
+ // src/stack.js
6038
+ var STOP = Symbol("STOP");
6039
+ var NEXT = Symbol("NEXT");
6040
+ function lookup(chain, name, dv = null) {
6041
+ let n = chain;
6042
+ while (n !== null) {
6043
+ const r = n[0].lookup(name);
6044
+ if (r === STOP)
6045
+ return dv;
6046
+ if (r !== NEXT)
6047
+ return r;
6048
+ n = n[1];
6049
+ }
6050
+ return dv;
5939
6051
  }
5940
- function makeCollector({ path = null, components = [] } = {}) {
5941
- const moduleTests = new ModuleTests({ path, suites: [] });
5942
- const stack = [];
5943
- function describe(...args) {
5944
- let head;
5945
- let options = null;
5946
- let fn;
5947
- if (args.length === 2) {
5948
- [head, fn] = args;
5949
- } else if (args.length === 3) {
5950
- [head, options, fn] = args;
5951
- } else {
5952
- throw new Error(`describe() expects 2 or 3 arguments, got ${args.length}`);
5953
- }
5954
- if (typeof fn !== "function") {
5955
- throw new Error(`describe(${JSON.stringify(titleFromArg(head))}): final argument must be a function`);
5956
- }
5957
- let componentName = null;
5958
- if (typeof head !== "string") {
5959
- componentName = resolveComponentName(head, components);
5960
- }
5961
- if (componentName === null && options && options.component != null) {
5962
- componentName = resolveComponentName(options.component, components);
5963
- }
5964
- if (componentName === null) {
5965
- const parent2 = stack.length ? stack[stack.length - 1] : null;
5966
- if (parent2)
5967
- componentName = parent2.componentName;
5968
- }
5969
- const parent = stack.length ? stack[stack.length - 1] : null;
5970
- const node = new Describe({
5971
- title: titleFromArg(head),
5972
- componentName,
5973
- parent
5974
- });
5975
- if (parent)
5976
- parent.children.push(node);
5977
- else
5978
- moduleTests.suites.push(node);
5979
- stack.push(node);
5980
- try {
5981
- fn();
5982
- } finally {
5983
- stack.pop();
5984
- }
6052
+
6053
+ class BindFrame {
6054
+ constructor(it, binds, isFrame) {
6055
+ this.it = it;
6056
+ this.binds = binds;
6057
+ this.isFrame = isFrame;
5985
6058
  }
5986
- function test(title, fn) {
5987
- if (typeof title !== "string") {
5988
- throw new Error("test(title, fn): title must be a string");
5989
- }
5990
- if (typeof fn !== "function") {
5991
- throw new Error(`test(${JSON.stringify(title)}): fn must be a function`);
5992
- }
5993
- const parent = stack.length ? stack[stack.length - 1] : null;
5994
- if (!parent) {
5995
- throw new Error(`test(${JSON.stringify(title)}) must be called inside a describe()`);
5996
- }
5997
- parent.children.push(new Test({
5998
- title,
5999
- fn,
6000
- componentName: parent.componentName,
6001
- parent
6002
- }));
6059
+ lookup(name) {
6060
+ const v = this.binds[name];
6061
+ return v === undefined ? this.isFrame ? STOP : NEXT : v;
6003
6062
  }
6004
- return { describe, test, moduleTests };
6005
6063
  }
6006
6064
 
6007
- // tools/core/test.js
6008
- function buildPath(node) {
6009
- const parts = [];
6010
- let cur = node;
6011
- while (cur) {
6012
- parts.unshift(cur.title);
6013
- cur = cur.parent;
6065
+ class ObjectFrame {
6066
+ constructor(binds) {
6067
+ this.binds = binds;
6068
+ }
6069
+ lookup(key) {
6070
+ const v = this.binds[key];
6071
+ return v === undefined ? NEXT : v;
6014
6072
  }
6015
- return parts.join(" > ");
6016
6073
  }
6017
- function captureError(e) {
6018
- const out = { message: e.message, stack: e.stack };
6019
- if ("expected" in e)
6020
- out.expected = e.expected;
6021
- if ("actual" in e)
6022
- out.actual = e.actual;
6023
- return out;
6074
+ function computeViewsId(views) {
6075
+ let s = "";
6076
+ let n = views;
6077
+ while (n !== null) {
6078
+ s += n[0];
6079
+ n = n[1];
6080
+ }
6081
+ return s === "main" ? "" : s;
6024
6082
  }
6025
- async function runTests({
6026
- getTests,
6027
- components = [],
6028
- path = null,
6029
- expect: expect2,
6030
- name = null,
6031
- grep = null,
6032
- bail = false
6033
- } = {}) {
6034
- const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
6035
- if (typeof getTests !== "function") {
6036
- return new TestReport({
6037
- modules: [new ModuleTestReport({ path, suites: [], counts })]
6038
- });
6083
+
6084
+ class Stack {
6085
+ constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
6086
+ this.comps = comps;
6087
+ this.it = it;
6088
+ this.binds = binds;
6089
+ this.dynBinds = dynBinds;
6090
+ this.views = views;
6091
+ this.viewsId = viewsId;
6092
+ this.ctx = ctx;
6039
6093
  }
6040
- if (typeof expect2 !== "function") {
6041
- throw new Error("runTests: expect must be provided (e.g. chai's expect)");
6094
+ _pushProvides() {
6095
+ const provide = this.comps.getCompFor(this.it)?.provide;
6096
+ if (provide == null)
6097
+ return this;
6098
+ const dynObj = {};
6099
+ let has = false;
6100
+ for (const k in provide) {
6101
+ dynObj[provide[k].symbol] = provide[k].val.eval(this);
6102
+ has = true;
6103
+ }
6104
+ if (!has)
6105
+ return this;
6106
+ const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
6107
+ const { comps, it, binds, views, viewsId, ctx } = this;
6108
+ return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
6042
6109
  }
6043
- const { describe, test, moduleTests } = makeCollector({ path, components });
6044
- await getTests({ describe, test, expect: expect2 });
6045
- let bailed = false;
6046
- async function visit(node) {
6047
- if (node instanceof Test) {
6048
- if (name !== null && node.componentName !== name)
6049
- return null;
6050
- const fullPath = buildPath(node);
6051
- if (grep !== null && !fullPath.includes(grep))
6052
- return null;
6053
- counts.total++;
6054
- if (bailed) {
6055
- counts.skip++;
6056
- return new TestResult({
6057
- title: node.title,
6058
- fullPath,
6059
- componentName: node.componentName,
6060
- status: "skip"
6061
- });
6062
- }
6063
- const start = performance.now();
6064
- try {
6065
- await node.fn();
6066
- counts.pass++;
6067
- return new TestResult({
6068
- title: node.title,
6069
- fullPath,
6070
- componentName: node.componentName,
6071
- status: "pass",
6072
- durationMs: performance.now() - start
6073
- });
6074
- } catch (e) {
6075
- counts.fail++;
6076
- if (bail)
6077
- bailed = true;
6078
- return new TestResult({
6079
- title: node.title,
6080
- fullPath,
6081
- componentName: node.componentName,
6082
- status: "fail",
6083
- durationMs: performance.now() - start,
6084
- error: captureError(e)
6085
- });
6086
- }
6087
- }
6088
- const childResults = [];
6089
- for (const child of node.children) {
6090
- const r = await visit(child);
6091
- if (r !== null)
6092
- childResults.push(r);
6093
- }
6094
- if (childResults.length === 0)
6110
+ static root(comps, it, ctx) {
6111
+ const binds = [new BindFrame(it, { it }, true), null];
6112
+ const dynBinds = [new ObjectFrame({}), null];
6113
+ const views = ["main", null];
6114
+ return new Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
6115
+ }
6116
+ enter(it, bindings = {}, isFrame = true) {
6117
+ const { comps, binds, dynBinds, views, viewsId, ctx } = this;
6118
+ const newBinds = [new BindFrame(it, bindings, isFrame), binds];
6119
+ const stack = new Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
6120
+ return isFrame ? stack._pushProvides() : stack;
6121
+ }
6122
+ pushViewName(name) {
6123
+ const { comps, it, binds, dynBinds, views, ctx } = this;
6124
+ const newViews = [name, views];
6125
+ return new Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
6126
+ }
6127
+ _pushDynBindValuesToArray(arr, comp) {
6128
+ for (const k in comp.provide)
6129
+ arr.push(this._lookupProvide(comp.provide[k]));
6130
+ for (const k in comp.lookup)
6131
+ arr.push(this._lookupAlias(comp.lookup[k]));
6132
+ }
6133
+ _lookupProvide(p) {
6134
+ return lookup(this.dynBinds, p.symbol) ?? p.val.eval(this) ?? null;
6135
+ }
6136
+ _lookupAlias(lk) {
6137
+ const sym = lk.getProducerSymbol(this);
6138
+ return (sym != null ? lookup(this.dynBinds, sym) : null) ?? lk.val?.eval(this) ?? null;
6139
+ }
6140
+ lookupDynamic(name) {
6141
+ const comp = this.comps.getCompFor(this.it);
6142
+ if (comp == null)
6095
6143
  return null;
6096
- return new DescribeResult({
6097
- title: node.title,
6098
- componentName: node.componentName,
6099
- children: childResults
6100
- });
6144
+ const lk = comp.lookup[name];
6145
+ if (lk !== undefined)
6146
+ return this._lookupAlias(lk);
6147
+ const p = comp.provide[name];
6148
+ return p !== undefined ? this._lookupProvide(p) : null;
6101
6149
  }
6102
- const suiteResults = [];
6103
- for (const suite of moduleTests.suites) {
6104
- const r = await visit(suite);
6105
- if (r !== null)
6106
- suiteResults.push(r);
6150
+ lookupBind(name) {
6151
+ return lookup(this.binds, name);
6107
6152
  }
6108
- return new TestReport({
6109
- modules: [new ModuleTestReport({ path, suites: suiteResults, counts })]
6110
- });
6111
- }
6112
-
6113
- // tools/core/test-console.js
6114
- var PASS = "color: #0a0; font-weight: bold";
6115
- var FAIL = "color: #c00; font-weight: bold";
6116
- var SKIP = "color: #888";
6117
- var DIM = "color: #888";
6118
- var RESET = "color: inherit; font-weight: normal";
6119
- function reportTestNode(node) {
6120
- if (node.children) {
6121
- const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
6122
- console.group(label);
6123
- for (const child of node.children)
6124
- reportTestNode(child);
6125
- console.groupEnd();
6126
- return;
6153
+ lookupType(name) {
6154
+ return this.comps.getCompFor(this.it).scope.lookupComponent(name);
6127
6155
  }
6128
- const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
6129
- if (node.status === "pass") {
6130
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
6131
- } else if (node.status === "skip") {
6132
- console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
6133
- } else {
6134
- console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
6135
- console.error(node.error?.message ?? "(no error message)");
6136
- if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
6137
- console.log("expected:", node.error.expected);
6138
- console.log("actual: ", node.error.actual);
6139
- }
6140
- if (node.error?.stack)
6141
- console.log(node.error.stack);
6142
- console.groupEnd();
6156
+ lookupFieldRaw(name) {
6157
+ return this.it[name] ?? null;
6143
6158
  }
6144
- }
6145
- function reportTestReportToConsole(report) {
6146
- for (const m of report.modules) {
6147
- const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
6148
- console.group(label);
6149
- if (m.suites.length === 0) {
6150
- console.log("(no tests)");
6151
- } else {
6152
- for (const s of m.suites)
6153
- reportTestNode(s);
6159
+ lookupMethod(name) {
6160
+ const fn = this.it[name];
6161
+ return fn instanceof Function ? fn.call(this.it) : null;
6162
+ }
6163
+ lookupName(name) {
6164
+ return this.ctx.lookupName(name);
6165
+ }
6166
+ getHandlerFor(name, key) {
6167
+ return this.comps.getHandlerFor(this.it, name, key);
6168
+ }
6169
+ lookupRequest(name) {
6170
+ return this.comps.getRequestFor(this.it, name);
6171
+ }
6172
+ lookupBestView(views, defaultViewName) {
6173
+ let n = this.views;
6174
+ while (n !== null) {
6175
+ const view = views[n[0]];
6176
+ if (view !== undefined)
6177
+ return view;
6178
+ n = n[1];
6154
6179
  }
6155
- const c = m.counts;
6156
- const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
6157
- if (c.fail > 0)
6158
- console.error(`%c${summary}`, FAIL);
6159
- else if (c.total === 0)
6160
- console.log(`%c${summary}`, DIM);
6161
- else
6162
- console.log(`%c${summary}`, PASS);
6163
- console.groupEnd();
6180
+ return views[defaultViewName];
6164
6181
  }
6165
- return report;
6166
6182
  }
6167
6183
 
6168
- // tools/format/lint.js
6169
- var UNSUPPORTED_EXPR_LABEL = {
6170
- ternary: "ternary expression",
6171
- comparison: "comparison",
6172
- logical: "logical expression",
6173
- "call-with-args": "method call with arguments"
6174
- };
6175
- function unsupportedExprMessage(info) {
6176
- const v = JSON.stringify(info.value);
6177
- const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
6178
- switch (info.role) {
6179
- case "attr":
6180
- return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
6181
- case "directive":
6182
- return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
6183
- case "if":
6184
- return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
6185
- case "x-op":
6186
- return `Unsupported ${label} ${v} in <x ${info.op}>`;
6187
- default:
6188
- return `Unsupported ${label} ${v}`;
6184
+ // src/transactor.js
6185
+ class State2 {
6186
+ constructor(val) {
6187
+ this.val = val;
6188
+ this.changeSubs = [];
6189
6189
  }
6190
- }
6191
- function badValueMessage(info) {
6192
- const v = JSON.stringify(info.value);
6193
- switch (info.role) {
6194
- case "attr":
6195
- return `Cannot parse value ${v} for attribute ':${info.attr}'`;
6196
- case "directive":
6197
- return `Cannot parse value ${v} for directive '@${info.directive}'`;
6198
- case "if":
6199
- return `Cannot parse condition ${v} for '@if.${info.attr}'`;
6200
- case "x-op":
6201
- return `Cannot parse value ${v} for <x ${info.op}>`;
6202
- case "handler-name":
6203
- return `Cannot parse handler name ${v}`;
6204
- case "handler-arg":
6205
- return `Cannot parse handler argument ${v}`;
6206
- case "macro-var":
6207
- return `Macro variable '^${info.name}' is not defined`;
6208
- default:
6209
- return `Cannot parse value ${v}`;
6190
+ onChange(cb) {
6191
+ this.changeSubs.push(cb);
6210
6192
  }
6211
- }
6212
- function tagDisplay(tag) {
6213
- return tag ? String(tag).toLowerCase() : null;
6214
- }
6215
- function fmtTagSuffix(info) {
6216
- const t = tagDisplay(info?.tag);
6217
- return t && t !== "x" ? ` on <${t}>` : "";
6218
- }
6219
- function fmtOriginSuffix(info) {
6220
- if (!info)
6221
- return "";
6222
- const parts = [];
6223
- if (info.originAttr) {
6224
- const branch = info.branch ? `[${info.branch}]` : "";
6225
- parts.push(`in ${info.originAttr}${branch}`);
6193
+ set(val, info) {
6194
+ const old = this.val;
6195
+ this.val = val;
6196
+ for (const sub of this.changeSubs)
6197
+ sub({ val, old, info, timestamp: Date.now() });
6226
6198
  }
6227
- if (info.handlerName) {
6228
- parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
6199
+ update(fn, info) {
6200
+ return this.set(fn(this.val), info);
6229
6201
  }
6230
- const t = tagDisplay(info.tag);
6231
- if (t && t !== "x")
6232
- parts.push(`on <${t}>`);
6233
- return parts.length ? ` (${parts.join(", ")})` : "";
6234
6202
  }
6235
- function fmtEventSuffix(info) {
6236
- if (info?.originAttr)
6237
- return ` in ${info.originAttr}`;
6238
- if (info?.eventName)
6239
- return ` in @on.${info.eventName}`;
6240
- return "";
6241
- }
6242
- function lintIdToMessage(id, info) {
6243
- switch (id) {
6244
- case "RENDER_IT_OUTSIDE_OF_LOOP":
6245
- return "<x render-it> used outside of a loop";
6246
- case "UNKNOWN_EVENT_MODIFIER": {
6247
- const mods = info.handler?.modifiers ?? [info.modifier];
6248
- const written = `@on.${info.name}+${mods.join("+")}`;
6249
- return `Unknown modifier '+${info.modifier}' in '${written}'`;
6203
+
6204
+ class Transactor {
6205
+ constructor(comps, rootValue) {
6206
+ this.comps = comps;
6207
+ this.transactions = [];
6208
+ this.state = new State2(rootValue);
6209
+ this.onTransactionPushed = () => {};
6210
+ this._inflight = new Set;
6211
+ }
6212
+ pushTransaction(t) {
6213
+ this.transactions.push(t);
6214
+ this.onTransactionPushed(t);
6215
+ }
6216
+ _link(child, parent) {
6217
+ if (parent) {
6218
+ const release = parent.completion.track();
6219
+ child.completion.whenSubtreeSettled().then(release);
6250
6220
  }
6251
- case "UNKNOWN_HANDLER_ARG_NAME":
6252
- return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
6253
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
6254
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
6255
- case "INPUT_HANDLER_NOT_REFERENCED":
6256
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
6257
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
6258
- return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
6259
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
6260
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
6261
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
6262
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
6263
- case "FIELD_VAL_NOT_DEFINED":
6264
- return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
6265
- case "FIELD_VAL_IS_METHOD":
6266
- return `'.${info.name}' reads a field, but '${info.name}' is defined as a method — use '$${info.name}'${fmtOriginSuffix(info)}`;
6267
- case "METHOD_VAL_NOT_DEFINED":
6268
- return `Method '$${info.name}' is not defined${fmtOriginSuffix(info)}`;
6269
- case "METHOD_VAL_IS_FIELD":
6270
- return `'$${info.name}' calls a method, but '${info.name}' is defined as a field — use '.${info.name}'${fmtOriginSuffix(info)}`;
6271
- case "DUPLICATE_ATTR_DEFINITION": {
6272
- const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
6273
- const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
6274
- return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
6221
+ return child;
6222
+ }
6223
+ pushSend(path, name, args = [], opts = {}, parent = null) {
6224
+ const t = new SendEvent(path, this, name, args, parent, opts);
6225
+ this.pushTransaction(t);
6226
+ return this._link(t, parent);
6227
+ }
6228
+ pushInput(path, name, args = [], opts = {}, parent = null) {
6229
+ const t = new InputDispatchEvent(path, this, name, args, parent, opts);
6230
+ this.pushTransaction(t);
6231
+ return this._link(t, parent);
6232
+ }
6233
+ pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
6234
+ const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
6235
+ const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
6236
+ this.pushTransaction(t);
6237
+ return this._link(t, parent);
6238
+ }
6239
+ pushRequest(path, name, args = [], opts = {}, parent = null) {
6240
+ const release = parent ? parent.completion.track() : null;
6241
+ const p = this._runRequest(path, name, args, opts, parent, release);
6242
+ this._inflight.add(p);
6243
+ p.finally(() => this._inflight.delete(p));
6244
+ return p;
6245
+ }
6246
+ async settle(maxTurns = 1e4) {
6247
+ while ((this.hasPendingTransactions || this._inflight.size) && maxTurns-- > 0) {
6248
+ while (this.hasPendingTransactions)
6249
+ this.transactNext();
6250
+ if (this._inflight.size)
6251
+ await Promise.allSettled([...this._inflight]);
6252
+ }
6253
+ }
6254
+ async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
6255
+ let released = false;
6256
+ const transfer = (t) => {
6257
+ if (release) {
6258
+ released = true;
6259
+ t.completion.whenSubtreeSettled().then(release);
6260
+ }
6261
+ };
6262
+ try {
6263
+ const curRoot = this.state.val;
6264
+ const txnPath = path.toTransactionPath();
6265
+ const curLeaf = txnPath.lookup(curRoot);
6266
+ const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
6267
+ const reqCtx = new RequestContext(path, this, parent, curRoot);
6268
+ const resHandlerName = opts?.onResName ?? name;
6269
+ const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
6270
+ const push = (specificName, baseName, singleArg, result, error) => {
6271
+ const resArgs = specificName ? [singleArg] : [result, error];
6272
+ const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
6273
+ transfer(t);
6274
+ this.pushTransaction(t);
6275
+ };
6276
+ try {
6277
+ const result = await handler.fn.apply(null, [...args, reqCtx]);
6278
+ push(opts?.onOkName, resHandlerName, result, result, null);
6279
+ } catch (error) {
6280
+ push(opts?.onErrorName, resHandlerName, error, null, error);
6281
+ }
6282
+ } finally {
6283
+ if (release && !released)
6284
+ release();
6275
6285
  }
6276
- case "IF_NO_BRANCH_SET":
6277
- return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
6278
- case "UNKNOWN_REQUEST_NAME":
6279
- return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
6280
- case "UNKNOWN_COMPONENT_NAME":
6281
- return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
6282
- case "ALT_HANDLER_NOT_DEFINED":
6283
- return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
6284
- case "ALT_HANDLER_NOT_REFERENCED":
6285
- return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
6286
- case "DYN_VAL_NOT_DEFINED":
6287
- return `Dynamic variable '*${info.name}' is not defined${fmtOriginSuffix(info)}`;
6288
- case "DYN_ALIAS_NOT_REFERENCED":
6289
- return `Lookup '${info.name}' is defined but never used — remove it or reference it as '*${info.name}' in a view`;
6290
- case "PROVIDE_NOT_ADDRESSABLE":
6291
- 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`;
6292
- case "LOOKUP_BAD_SHAPE":
6293
- return `Lookup '${info.name}' has an invalid shape: ${info.problem}`;
6294
- case "LOOKUP_TARGET_MALFORMED":
6295
- return `Lookup '${info.name}' target '${info.target}' must be 'Producer.provideName' (a string, or the 'for' of { for, default })`;
6296
- case "UNKNOWN_MACRO_ARG":
6297
- return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
6298
- case "UNKNOWN_DIRECTIVE":
6299
- return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
6300
- case "UNKNOWN_X_OP":
6301
- return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
6302
- case "UNKNOWN_X_ATTR":
6303
- return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
6304
- case "MAYBE_DROP_AT_PREFIX": {
6305
- const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
6306
- return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
6286
+ }
6287
+ get hasPendingTransactions() {
6288
+ return this.transactions.length > 0;
6289
+ }
6290
+ transactNext() {
6291
+ if (this.hasPendingTransactions)
6292
+ this.transact(this.transactions.shift());
6293
+ }
6294
+ transact(transaction) {
6295
+ try {
6296
+ const curState = this.state.val;
6297
+ const newState = transaction.run(curState, this.comps);
6298
+ if (newState !== undefined) {
6299
+ this.state.set(newState, { transaction });
6300
+ transaction.afterTransaction();
6301
+ } else
6302
+ console.warn("undefined new state", { curState, transaction });
6303
+ } finally {
6304
+ transaction._completion?.ensureSelfSettled();
6305
+ transaction._completion?.releaseSelf();
6307
6306
  }
6308
- case "MAYBE_ADD_AT_PREFIX":
6309
- return `'${info.name}' on <${(info.tag ?? "").toLowerCase()}> is a plain attribute, but '@${info.name}' is a directive — add the leading '@'`;
6310
- case "BAD_VALUE":
6311
- return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
6312
- case "UNSUPPORTED_EXPR_SYNTAX":
6313
- return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
6314
- case "REDUNDANT_TEMPLATE_STRING":
6315
- return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
6316
- case "PLACEHOLDERLESS_TEMPLATE_STRING":
6317
- return `Template string has no dynamic parts — use the string literal ${info.literal} instead${fmtOriginSuffix(info)}`;
6318
- case "UNKNOWN_COMPONENT_SPEC_KEY":
6319
- return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
6320
- case "COMP_FIELD_BAD_SHAPE":
6321
- 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}`;
6322
- case "HTML_TAG_NAME_HAS_UPPERCASE":
6323
- return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
6324
- case "HTML_SVG_TAG_WILL_LOWERCASE":
6325
- return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
6326
- case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
6327
- return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
6328
- case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
6329
- return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
6330
- case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
6331
- return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
6332
- case "HTML_DUPLICATE_FORM":
6333
- return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
6334
- case "HTML_NESTED_INTERACTIVE":
6335
- return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
6336
- case "HTML_MISNESTED_FORMATTING":
6337
- return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
6338
- case "HTML_UNEXPECTED_END_TAG":
6339
- return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
6340
- case "HTML_UNCLOSED_BEFORE_END":
6341
- return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
6342
- case "HTML_DUPLICATE_ATTRIBUTE":
6343
- return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
6344
- case "HTML_ATTRIBUTES_ON_END_TAG":
6345
- return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
6346
- case "HTML_SELF_CLOSING_END_TAG":
6347
- return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
6348
- case "HTML_MISSING_ATTRIBUTE_VALUE":
6349
- return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
6350
- case "HTML_CDATA_IN_HTML_NAMESPACE":
6351
- return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
6352
- case "HTML_BOGUS_COMMENT":
6353
- return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
6354
- case "HTML_SVG_ATTR_WILL_LOWERCASE":
6355
- return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
6356
- case "HTML_MATHML_ATTR_WILL_LOWERCASE":
6357
- return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
6358
- case "ASYNC_HANDLER":
6359
- 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)`;
6360
- case "LINT_ERROR":
6361
- return info.message;
6362
- default:
6363
- return id;
6364
6307
  }
6365
- }
6366
- function suggestionToMessage(suggestion) {
6367
- if (!suggestion)
6368
- return null;
6369
- switch (suggestion.kind) {
6370
- case "replace-name":
6371
- return `did you mean '${suggestion.to}'?`;
6372
- case "drop-prefix":
6373
- return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
6374
- case "add-prefix":
6375
- return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
6376
- case "remove":
6377
- return `remove ${suggestion.what}`;
6378
- case "rewrite":
6379
- return `use '${suggestion.to}' instead of '${suggestion.from}'`;
6380
- case "wrap":
6381
- return `wrap it in ${suggestion.to}`;
6382
- case "rephrase":
6383
- return suggestion.text ?? null;
6384
- default:
6385
- return null;
6308
+ transactInputNow(path, event, eventHandler, dragInfo) {
6309
+ this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
6386
6310
  }
6387
6311
  }
6388
- function fmtLocationSuffix(info) {
6389
- const loc = info?.location;
6390
- if (!loc)
6391
- return "";
6392
- return ` at line ${loc.line}, col ${loc.column}`;
6312
+ function mkReq404(name) {
6313
+ const fn = () => {
6314
+ throw new Error(`Request not found: ${name}`);
6315
+ };
6316
+ return { fn };
6393
6317
  }
6394
- function htmlActionPhrase(action, tag, parent) {
6395
- switch (action) {
6396
- case "ignored":
6397
- return `the parser will drop this <${tag}>`;
6398
- case "drop":
6399
- return `the parser will drop this <${tag}>`;
6400
- case "auto-close-implicit":
6401
- return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
6402
- case "foster-parent":
6403
- return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
6404
- case "foreign-breakout":
6405
- return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
6406
- default:
6407
- return `parser action: ${action}`;
6408
- }
6318
+ function nullHandler() {
6319
+ return this;
6409
6320
  }
6410
6321
 
6411
- // src/components.js
6412
- class Components {
6413
- constructor() {
6414
- this.getComponentSymbol = Symbol("getComponent");
6415
- this.byId = new Map;
6416
- }
6417
- registerComponent(comp) {
6418
- comp.Class.prototype[this.getComponentSymbol] = () => comp;
6419
- this.byId.set(comp.id, comp);
6322
+ class Transaction {
6323
+ constructor(path, transactor, parentTransaction = null) {
6324
+ this.path = path;
6325
+ this.transactor = transactor;
6326
+ this.parentTransaction = parentTransaction;
6327
+ this._completion = null;
6420
6328
  }
6421
- getComponentForId(id) {
6422
- return this.byId.get(id) ?? null;
6329
+ get completion() {
6330
+ this._completion ??= new Completion;
6331
+ return this._completion;
6423
6332
  }
6424
- getCompFor(v) {
6425
- return v?.[this.getComponentSymbol]?.() ?? null;
6333
+ whenSettled() {
6334
+ return this.completion.whenSettled();
6426
6335
  }
6427
- getHandlerFor(v, name, key) {
6428
- return this.getCompFor(v)?.[key][name] ?? null;
6336
+ whenSubtreeSettled() {
6337
+ return this.completion.whenSubtreeSettled();
6429
6338
  }
6430
- getRequestFor(v, name) {
6431
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
6339
+ run(rootValue, comps) {
6340
+ return this.updateRootValue(rootValue, comps);
6432
6341
  }
6433
- compileStyles() {
6434
- const styles = [];
6435
- for (const comp of this.byId.values())
6436
- styles.push(comp.compileStyle());
6437
- return styles.join(`
6438
- `);
6342
+ afterTransaction() {}
6343
+ buildRootStack(root, comps) {
6344
+ return Stack.root(comps, root);
6439
6345
  }
6440
- }
6441
-
6442
- class ComponentStack {
6443
- constructor(comps = new Components, parent = null) {
6444
- this.comps = comps;
6445
- this.parent = parent;
6446
- this.byName = {};
6447
- this.reqsByName = {};
6448
- this.macros = {};
6346
+ buildStack(root, comps) {
6347
+ return this.path.toTransactionPath().buildStack(this.buildRootStack(root, comps));
6449
6348
  }
6450
- enter() {
6451
- return new ComponentStack(this.comps, this);
6349
+ callHandler(root, instance, comps) {
6350
+ const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
6351
+ return handler.apply(instance, args);
6452
6352
  }
6453
- registerComponents(comps, opts) {
6454
- const { aliases = {} } = opts ?? {};
6455
- for (let i = 0;i < comps.length; i++) {
6456
- const comp = comps[i];
6457
- comp.scope = this.enter();
6458
- comp.Class.scope = comp.scope;
6459
- this.comps.registerComponent(comp);
6460
- this.byName[comp.name] = comp;
6461
- }
6462
- for (const alias in aliases) {
6463
- const comp = this.byName[aliases[alias]];
6464
- console.assert(this.byName[alias] === undefined, "alias overrides component", alias);
6465
- if (comp !== undefined)
6466
- this.byName[alias] = comp;
6467
- else
6468
- console.warn("alias", alias, "to inexistent component", aliases[alias]);
6353
+ getHandlerAndArgs(_root, _instance, _comps) {
6354
+ return null;
6355
+ }
6356
+ getTransactionPath() {
6357
+ return this.path.toTransactionPath();
6358
+ }
6359
+ updateRootValue(curRoot, comps) {
6360
+ const txnPath = this.getTransactionPath();
6361
+ const curLeaf = txnPath.lookup(curRoot);
6362
+ const newLeaf = this.callHandler(curRoot, curLeaf, comps);
6363
+ this._completion?.markSelfSettled({ value: newLeaf, old: curLeaf });
6364
+ return curLeaf !== newLeaf ? txnPath.setValue(curRoot, newLeaf) : curRoot;
6365
+ }
6366
+ lookupName(_name) {
6367
+ return null;
6368
+ }
6369
+ }
6370
+ var toNullIfNaN = (v) => Number.isNaN(v) ? null : v;
6371
+ function getValue(e) {
6372
+ return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
6373
+ }
6374
+
6375
+ class InputEvent extends Transaction {
6376
+ constructor(path, e, handler, transactor, dragInfo) {
6377
+ super(path, transactor);
6378
+ this.e = e;
6379
+ this.handler = handler;
6380
+ this.dragInfo = dragInfo;
6381
+ this._dispatchPath = null;
6382
+ }
6383
+ get dispatchPath() {
6384
+ this._dispatchPath ??= this.path.compact();
6385
+ return this._dispatchPath;
6386
+ }
6387
+ buildRootStack(root, comps) {
6388
+ return Stack.root(comps, root, this);
6389
+ }
6390
+ getHandlerAndArgs(root, _instance, comps) {
6391
+ const stack = this.buildStack(root, comps);
6392
+ const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
6393
+ const path = this.dispatchPath;
6394
+ let dispatcher;
6395
+ for (let i = 0;i < args.length; i++) {
6396
+ if (args[i]?.toHandlerArg) {
6397
+ dispatcher ??= new Dispatcher(path, this.transactor, this);
6398
+ args[i] = args[i].toHandlerArg(dispatcher);
6399
+ }
6469
6400
  }
6401
+ args.push(new EventContext(path, this.transactor, this));
6402
+ return [handler, args];
6470
6403
  }
6471
- registerMacros(macros) {
6472
- for (const key in macros) {
6473
- const lower = key.toLowerCase();
6474
- console.assert(this.macros[lower] === undefined, "macro key collision", lower);
6475
- this.macros[lower] = macros[key];
6404
+ lookupName(name) {
6405
+ const { e } = this;
6406
+ switch (name) {
6407
+ case "value":
6408
+ return getValue(e);
6409
+ case "valueAsInt":
6410
+ return toNullIfNaN(parseInt(getValue(e), 10));
6411
+ case "valueAsFloat":
6412
+ return toNullIfNaN(parseFloat(getValue(e)));
6413
+ case "target":
6414
+ return e.target;
6415
+ case "event":
6416
+ return e;
6417
+ case "isAlt":
6418
+ return e.altKey;
6419
+ case "isShift":
6420
+ return e.shiftKey;
6421
+ case "isCtrl":
6422
+ case "isCmd":
6423
+ return isMac && e.metaKey || e.ctrlKey;
6424
+ case "key":
6425
+ return e.key;
6426
+ case "keyCode":
6427
+ return e.keyCode;
6428
+ case "isUpKey":
6429
+ return e.key === "ArrowUp";
6430
+ case "isDownKey":
6431
+ return e.key === "ArrowDown";
6432
+ case "isSend":
6433
+ return e.key === "Enter";
6434
+ case "isCancel":
6435
+ return e.key === "Escape";
6436
+ case "isTabKey":
6437
+ return e.key === "Tab";
6438
+ case "ctx":
6439
+ return new EventContext(this.dispatchPath, this.transactor, this);
6440
+ case "dragInfo":
6441
+ return this.dragInfo;
6476
6442
  }
6443
+ return null;
6477
6444
  }
6478
- getCompFor(v) {
6479
- return this.comps.getCompFor(v);
6445
+ }
6446
+
6447
+ class NameArgsTransaction extends Transaction {
6448
+ constructor(path, transactor, name, args, parentTransaction, opts = {}) {
6449
+ super(path, transactor, parentTransaction);
6450
+ this.name = name;
6451
+ this.args = args;
6452
+ this.opts = opts;
6453
+ this.targetPath = path;
6480
6454
  }
6481
- registerRequestHandlers(handlers) {
6482
- for (const name in handlers)
6483
- this.reqsByName[name] = new RequestHandler(name, handlers[name]);
6455
+ handlerProp = null;
6456
+ getHandlerForName(comp) {
6457
+ const handlers = comp?.[this.handlerProp];
6458
+ return handlers?.[this.name] ?? handlers?.$unknown ?? nullHandler;
6484
6459
  }
6485
- lookupRequest(name) {
6486
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
6460
+ getHandlerAndArgs(_root, instance, comps) {
6461
+ const handler = this.getHandlerForName(comps.getCompFor(instance));
6462
+ return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
6487
6463
  }
6488
- lookupComponent(name) {
6489
- return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
6464
+ }
6465
+
6466
+ class ResponseEvent extends NameArgsTransaction {
6467
+ handlerProp = "response";
6468
+ constructor(path, transactor, name, args, parent, txnPath = null) {
6469
+ super(path, transactor, name, args, parent);
6470
+ this._txnPath = txnPath;
6490
6471
  }
6491
- lookupMacro(name) {
6492
- return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
6472
+ getTransactionPath() {
6473
+ return this._txnPath ?? super.getTransactionPath();
6493
6474
  }
6494
6475
  }
6495
6476
 
6496
- class ProvideInfo {
6497
- constructor(name, val, symbol) {
6498
- this.name = name;
6499
- this.val = val;
6500
- this.symbol = symbol;
6477
+ class SendEvent extends NameArgsTransaction {
6478
+ handlerProp = "receive";
6479
+ run(rootVal, comps) {
6480
+ return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
6481
+ }
6482
+ afterTransaction() {
6483
+ const { path, name, args, opts, targetPath } = this;
6484
+ if (opts.bubbles && path.steps.length > 0)
6485
+ this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
6501
6486
  }
6502
6487
  }
6503
6488
 
6504
- class LookupInfo {
6505
- constructor(name, compName, provideName, val) {
6506
- this.name = name;
6507
- this.compName = compName;
6508
- this.provideName = provideName;
6509
- this.val = val;
6510
- this._sym = undefined;
6489
+ class BubbleEvent extends SendEvent {
6490
+ handlerProp = "bubble";
6491
+ constructor(path, transactor, name, args, parent, opts, targetPath) {
6492
+ super(path, transactor, name, args, parent, opts);
6493
+ this.targetPath = targetPath ?? path;
6511
6494
  }
6512
- getProducerSymbol(stack) {
6513
- if (this._sym === undefined)
6514
- this._sym = stack.lookupType(this.compName)?.provide?.[this.provideName]?.symbol ?? null;
6515
- return this._sym;
6495
+ stopPropagation() {
6496
+ this.opts.bubbles = false;
6516
6497
  }
6517
6498
  }
6518
- var isString = (v) => typeof v === "string";
6519
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
6520
- var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
6521
- var _compId = 0;
6522
6499
 
6523
- class Component {
6524
- constructor(Class, o) {
6525
- this.id = _compId++;
6526
- this.name = o.name ?? "UnkComp";
6527
- this.Class = Class;
6528
- this.views = { main: new View("main", o.view, o.style) };
6529
- this.commonStyle = o.commonStyle ?? "";
6530
- this.globalStyle = o.globalStyle ?? "";
6531
- this.input = o.input ?? {};
6532
- this.receive = o.receive ?? {};
6533
- this.bubble = o.bubble ?? {};
6534
- this.response = o.response ?? {};
6535
- this.alter = o.alter ?? {};
6536
- for (const name in o.views ?? {}) {
6537
- const v = o.views[name];
6538
- const { view, style } = isString(v) ? { view: v } : v;
6539
- this.views[name] = new View(name, view, style);
6540
- }
6541
- this._rawProvide = o.provide ?? {};
6542
- this._rawLookup = o.lookup ?? {};
6543
- this.provide = {};
6544
- this.lookup = {};
6545
- this.scope = null;
6546
- this.spec = o;
6547
- this.extra = {};
6548
- for (const key of Object.keys(o))
6549
- if (!KNOWN_SPEC_KEYS.has(key))
6550
- this.extra[key] = o[key];
6500
+ class InputDispatchEvent extends NameArgsTransaction {
6501
+ handlerProp = "input";
6502
+ }
6503
+
6504
+ class Completion {
6505
+ constructor() {
6506
+ this.val = undefined;
6507
+ this.selfSettled = false;
6508
+ this.subtreeSettled = false;
6509
+ this.pending = 1;
6510
+ this._selfResolve = null;
6511
+ this._selfPromise = null;
6512
+ this._subtreeResolve = null;
6513
+ this._subtreePromise = null;
6514
+ this._selfReleased = false;
6515
+ }
6516
+ whenSettled() {
6517
+ if (this.selfSettled)
6518
+ return Promise.resolve(this.val);
6519
+ this._selfPromise ??= new Promise((res) => {
6520
+ this._selfResolve = res;
6521
+ });
6522
+ return this._selfPromise;
6551
6523
  }
6552
- clone() {
6553
- return Component.fromSpec(this.spec);
6524
+ whenSubtreeSettled() {
6525
+ if (this.subtreeSettled)
6526
+ return Promise.resolve(this.val);
6527
+ this._subtreePromise ??= new Promise((res) => {
6528
+ this._subtreeResolve = res;
6529
+ });
6530
+ return this._subtreePromise;
6554
6531
  }
6555
- compile(ParseContext2) {
6556
- for (const name in this.views)
6557
- this.views[name].compile(new ParseContext2, this.scope, this.id);
6558
- const ctx = this.views.main.ctx;
6559
- for (const key in this._rawProvide) {
6560
- const val = vp.parseProvide(this._rawProvide[key], ctx);
6561
- if (val)
6562
- this.provide[key] = new ProvideInfo(key, val, Symbol(key));
6563
- }
6564
- for (const key in this._rawLookup) {
6565
- const linfo = this._rawLookup[key];
6566
- const forStr = isString(linfo) ? linfo : isString(linfo?.for) ? linfo.for : null;
6567
- const [compName, provideName] = forStr === null ? [] : forStr.split(".");
6568
- if (!isString(compName) || !isString(provideName))
6569
- continue;
6570
- const defStr = isString(linfo?.default) ? linfo.default : null;
6571
- const val = defStr === null ? null : vp.parseField(defStr, ctx);
6572
- this.lookup[key] = new LookupInfo(key, compName, provideName, val);
6573
- }
6574
- for (const key in this.lookup)
6575
- if (this.provide[key] !== undefined)
6576
- console.warn("name declared in both provide and lookup", this.name, key);
6532
+ markSelfSettled(val) {
6533
+ if (this.selfSettled)
6534
+ return;
6535
+ this.selfSettled = true;
6536
+ this.val = val;
6537
+ this._selfResolve?.(val);
6577
6538
  }
6578
- make(args, opts) {
6579
- return this.Class.make(args, opts ?? { scope: this.scope });
6539
+ ensureSelfSettled() {
6540
+ if (!this.selfSettled)
6541
+ this.markSelfSettled(this.val);
6580
6542
  }
6581
- getView(name) {
6582
- return this.views[name] ?? this.views.main;
6543
+ track() {
6544
+ this.pending++;
6545
+ let done = false;
6546
+ return () => {
6547
+ if (done)
6548
+ return;
6549
+ done = true;
6550
+ this._release();
6551
+ };
6583
6552
  }
6584
- getEventForId(id, name = "main") {
6585
- return this.getView(name).ctx.getEventForId(id);
6553
+ releaseSelf() {
6554
+ if (this._selfReleased)
6555
+ return;
6556
+ this._selfReleased = true;
6557
+ this._release();
6586
6558
  }
6587
- getNodeForId(id, name = "main") {
6588
- return this.getView(name).ctx.getNodeForId(id);
6559
+ _release() {
6560
+ if (--this.pending === 0) {
6561
+ this.subtreeSettled = true;
6562
+ this._subtreeResolve?.(this.val);
6563
+ }
6589
6564
  }
6590
- compileStyle() {
6591
- const { id, commonStyle, globalStyle, views } = this;
6592
- const styles = commonStyle ? [`[data-cid="${id}"]{${commonStyle}}`] : [];
6593
- if (globalStyle !== "")
6594
- styles.push(globalStyle);
6595
- for (const name in views) {
6596
- const { style } = views[name];
6597
- if (style !== "")
6598
- styles.push(`[data-cid="${id}"][data-vid="${name}"]{${style}}`);
6599
- }
6600
- return styles.join(`
6601
- `);
6602
- }
6603
- }
6604
-
6605
- // src/stack.js
6606
- var STOP = Symbol("STOP");
6607
- var NEXT = Symbol("NEXT");
6608
- function lookup(chain, name, dv = null) {
6609
- let n = chain;
6610
- while (n !== null) {
6611
- const r = n[0].lookup(name);
6612
- if (r === STOP)
6613
- return dv;
6614
- if (r !== NEXT)
6615
- return r;
6616
- n = n[1];
6617
- }
6618
- return dv;
6619
6565
  }
6620
6566
 
6621
- class BindFrame {
6622
- constructor(it, binds, isFrame) {
6623
- this.it = it;
6624
- this.binds = binds;
6625
- this.isFrame = isFrame;
6567
+ class Dispatcher {
6568
+ constructor(path, transactor, parentTransaction, root = transactor.state.val) {
6569
+ this.path = path;
6570
+ this.transactor = transactor;
6571
+ this.parent = parentTransaction;
6572
+ this.root = root;
6626
6573
  }
6627
- lookup(name) {
6628
- const v = this.binds[name];
6629
- return v === undefined ? this.isFrame ? STOP : NEXT : v;
6574
+ walkPath(callback) {
6575
+ const comps = this.transactor.comps;
6576
+ const chain = this.path.toTransactionPath().resolveChain(this.root);
6577
+ for (let i = chain.length - 1;i >= 0; i--) {
6578
+ const comp = comps.getCompFor(chain[i]);
6579
+ if (comp && callback(comp, chain[i]) === false)
6580
+ return;
6581
+ }
6630
6582
  }
6631
- }
6632
-
6633
- class ObjectFrame {
6634
- constructor(binds) {
6635
- this.binds = binds;
6583
+ get at() {
6584
+ return new PathChanges(this);
6636
6585
  }
6637
- lookup(key) {
6638
- const v = this.binds[key];
6639
- return v === undefined ? NEXT : v;
6586
+ send(name, args, opts) {
6587
+ return this.sendAtPath(this.path, name, args, opts);
6640
6588
  }
6641
- }
6642
- function computeViewsId(views) {
6643
- let s = "";
6644
- let n = views;
6645
- while (n !== null) {
6646
- s += n[0];
6647
- n = n[1];
6589
+ bubble(name, args, opts) {
6590
+ return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6648
6591
  }
6649
- return s === "main" ? "" : s;
6650
- }
6651
-
6652
- class Stack {
6653
- constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
6654
- this.comps = comps;
6655
- this.it = it;
6656
- this.binds = binds;
6657
- this.dynBinds = dynBinds;
6658
- this.views = views;
6659
- this.viewsId = viewsId;
6660
- this.ctx = ctx;
6592
+ sendAtPath(path, name, args, opts) {
6593
+ return this.transactor.pushSend(path, name, args, opts, this.parent);
6661
6594
  }
6662
- _pushProvides() {
6663
- const provide = this.comps.getCompFor(this.it)?.provide;
6664
- if (provide == null)
6665
- return this;
6666
- const dynObj = {};
6667
- let has = false;
6668
- for (const k in provide) {
6669
- dynObj[provide[k].symbol] = provide[k].val.eval(this);
6670
- has = true;
6671
- }
6672
- if (!has)
6673
- return this;
6674
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
6675
- const { comps, it, binds, views, viewsId, ctx } = this;
6676
- return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
6595
+ request(name, args, opts) {
6596
+ return this.requestAtPath(this.path, name, args, opts);
6677
6597
  }
6678
- static root(comps, it, ctx) {
6679
- const binds = [new BindFrame(it, { it }, true), null];
6680
- const dynBinds = [new ObjectFrame({}), null];
6681
- const views = ["main", null];
6682
- return new Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
6598
+ requestAtPath(path, name, args, opts) {
6599
+ return this.transactor.pushRequest(path, name, args, opts, this.parent);
6683
6600
  }
6684
- enter(it, bindings = {}, isFrame = true) {
6685
- const { comps, binds, dynBinds, views, viewsId, ctx } = this;
6686
- const newBinds = [new BindFrame(it, bindings, isFrame), binds];
6687
- const stack = new Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
6688
- return isFrame ? stack._pushProvides() : stack;
6601
+ inputAtPath(path, name, args, opts) {
6602
+ return this.transactor.pushInput(path, name, args, opts, this.parent);
6689
6603
  }
6690
- pushViewName(name) {
6691
- const { comps, it, binds, dynBinds, views, ctx } = this;
6692
- const newViews = [name, views];
6693
- return new Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
6604
+ lookupTypeFor(name, inst) {
6605
+ return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
6694
6606
  }
6695
- _pushDynBindValuesToArray(arr, comp) {
6696
- for (const k in comp.provide)
6697
- arr.push(this._lookupProvide(comp.provide[k]));
6698
- for (const k in comp.lookup)
6699
- arr.push(this._lookupAlias(comp.lookup[k]));
6607
+ }
6608
+
6609
+ class EventContext extends Dispatcher {
6610
+ get name() {
6611
+ return this.parent?.name ?? null;
6700
6612
  }
6701
- _lookupProvide(p) {
6702
- return lookup(this.dynBinds, p.symbol) ?? p.val.eval(this) ?? null;
6613
+ get targetPath() {
6614
+ return this.parent.targetPath;
6703
6615
  }
6704
- _lookupAlias(lk) {
6705
- const sym = lk.getProducerSymbol(this);
6706
- return (sym != null ? lookup(this.dynBinds, sym) : null) ?? lk.val?.eval(this) ?? null;
6616
+ stopPropagation() {
6617
+ return this.parent.stopPropagation();
6707
6618
  }
6708
- lookupDynamic(name) {
6709
- const comp = this.comps.getCompFor(this.it);
6710
- if (comp == null)
6711
- return null;
6712
- const lk = comp.lookup[name];
6713
- if (lk !== undefined)
6714
- return this._lookupAlias(lk);
6715
- const p = comp.provide[name];
6716
- return p !== undefined ? this._lookupProvide(p) : null;
6619
+ }
6620
+
6621
+ class RequestContext extends Dispatcher {
6622
+ }
6623
+
6624
+ class PathChanges extends PathBuilder {
6625
+ constructor(dispatcher) {
6626
+ super();
6627
+ this.dispatcher = dispatcher;
6717
6628
  }
6718
- lookupBind(name) {
6719
- return lookup(this.binds, name);
6629
+ send(name, args, opts) {
6630
+ return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
6720
6631
  }
6721
- lookupType(name) {
6722
- return this.comps.getCompFor(this.it).scope.lookupComponent(name);
6632
+ bubble(name, args, opts) {
6633
+ return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6723
6634
  }
6724
- lookupFieldRaw(name) {
6725
- return this.it[name] ?? null;
6635
+ buildPath() {
6636
+ return this.dispatcher.path.concat(this.pathChanges);
6726
6637
  }
6727
- lookupMethod(name) {
6728
- const fn = this.it[name];
6729
- return fn instanceof Function ? fn.call(this.it) : null;
6638
+ }
6639
+ function rootDispatcher(transactor) {
6640
+ return new Dispatcher(new Path([]), transactor, null);
6641
+ }
6642
+
6643
+ // tools/core/results.js
6644
+ class ModuleInfo {
6645
+ constructor({ path = null, present = new Set, counts = {}, warnings = [] }) {
6646
+ this.path = path;
6647
+ this.present = present;
6648
+ this.counts = counts;
6649
+ this.warnings = warnings;
6730
6650
  }
6731
- lookupName(name) {
6732
- return this.ctx.lookupName(name);
6651
+ }
6652
+
6653
+ class ComponentSummary {
6654
+ constructor({ name, views, fields }) {
6655
+ this.name = name;
6656
+ this.views = views;
6657
+ this.fields = fields;
6733
6658
  }
6734
- getHandlerFor(name, key) {
6735
- return this.comps.getHandlerFor(this.it, name, key);
6659
+ }
6660
+
6661
+ class ComponentList {
6662
+ constructor({ items, total = null, truncated = false }) {
6663
+ this.items = items;
6664
+ this.total = total ?? items.length;
6665
+ this.truncated = truncated;
6736
6666
  }
6737
- lookupRequest(name) {
6738
- return this.comps.getRequestFor(this.it, name);
6667
+ }
6668
+
6669
+ class ExampleIndex {
6670
+ constructor({ sections, total = null, truncated = false }) {
6671
+ this.sections = sections;
6672
+ this.total = total ?? sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
6673
+ this.truncated = truncated;
6739
6674
  }
6740
- lookupBestView(views, defaultViewName) {
6741
- let n = this.views;
6742
- while (n !== null) {
6743
- const view = views[n[0]];
6744
- if (view !== undefined)
6745
- return view;
6746
- n = n[1];
6747
- }
6748
- return views[defaultViewName];
6675
+ }
6676
+
6677
+ class ComponentDocs {
6678
+ constructor({ items }) {
6679
+ this.items = items;
6749
6680
  }
6750
6681
  }
6751
6682
 
6752
- // src/transactor.js
6753
- class State2 {
6754
- constructor(val) {
6755
- this.val = val;
6756
- this.changeSubs = [];
6683
+ class LintFinding {
6684
+ constructor({ id, level, info, context = {}, suggestion = null }) {
6685
+ this.id = id;
6686
+ this.level = level;
6687
+ this.info = info;
6688
+ this.context = context;
6689
+ this.suggestion = suggestion;
6757
6690
  }
6758
- onChange(cb) {
6759
- this.changeSubs.push(cb);
6691
+ }
6692
+
6693
+ class LintComponentResult {
6694
+ constructor({ componentName, findings }) {
6695
+ this.componentName = componentName;
6696
+ this.findings = findings;
6760
6697
  }
6761
- set(val, info) {
6762
- const old = this.val;
6763
- this.val = val;
6764
- for (const sub of this.changeSubs)
6765
- sub({ val, old, info, timestamp: Date.now() });
6698
+ get errorCount() {
6699
+ return this.findings.filter((f) => f.level === "error").length;
6766
6700
  }
6767
- update(fn, info) {
6768
- return this.set(fn(this.val), info);
6701
+ get warnCount() {
6702
+ return this.findings.filter((f) => f.level === "warn").length;
6769
6703
  }
6770
6704
  }
6771
6705
 
6772
- class Transactor {
6773
- constructor(comps, rootValue) {
6774
- this.comps = comps;
6775
- this.transactions = [];
6776
- this.state = new State2(rootValue);
6777
- this.onTransactionPushed = () => {};
6778
- }
6779
- pushTransaction(t) {
6780
- this.transactions.push(t);
6781
- this.onTransactionPushed(t);
6782
- }
6783
- pushSend(path, name, args = [], opts = {}, parent = null) {
6784
- this.pushTransaction(new SendEvent(path, this, name, args, parent, opts));
6785
- }
6786
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
6787
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
6788
- this.pushTransaction(new BubbleEvent(path, this, name, args, parent, newOpts, targetPath));
6789
- }
6790
- async pushRequest(path, name, args = [], opts = {}, parent = null) {
6791
- const curRoot = this.state.val;
6792
- const txnPath = path.toTransactionPath();
6793
- const curLeaf = txnPath.lookup(curRoot);
6794
- const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
6795
- const reqCtx = new RequestContext(path, this, parent, curRoot);
6796
- const resHandlerName = opts?.onResName ?? name;
6797
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
6798
- const push = (specificName, baseName, singleArg, result, error) => {
6799
- const resArgs = specificName ? [singleArg] : [result, error];
6800
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
6801
- this.pushTransaction(t);
6802
- };
6803
- try {
6804
- const result = await handler.fn.apply(null, [...args, reqCtx]);
6805
- push(opts?.onOkName, resHandlerName, result, result, null);
6806
- } catch (error) {
6807
- push(opts?.onErrorName, resHandlerName, error, null, error);
6808
- }
6809
- }
6810
- get hasPendingTransactions() {
6811
- return this.transactions.length > 0;
6706
+ class LintReport {
6707
+ constructor({ components }) {
6708
+ this.components = components;
6812
6709
  }
6813
- transactNext() {
6814
- if (this.hasPendingTransactions)
6815
- this.transact(this.transactions.shift());
6710
+ get hasErrors() {
6711
+ return this.components.some((c) => c.errorCount > 0);
6816
6712
  }
6817
- transact(transaction) {
6818
- const curState = this.state.val;
6819
- const newState = transaction.run(curState, this.comps);
6820
- if (newState !== undefined) {
6821
- this.state.set(newState, { transaction });
6822
- transaction.afterTransaction();
6823
- } else
6824
- console.warn("undefined new state", { curState, transaction });
6713
+ get totalErrors() {
6714
+ return this.components.reduce((n, c) => n + c.errorCount, 0);
6825
6715
  }
6826
- transactInputNow(path, event, eventHandler, dragInfo) {
6827
- this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
6716
+ get totalWarnings() {
6717
+ return this.components.reduce((n, c) => n + c.warnCount, 0);
6828
6718
  }
6829
6719
  }
6830
- function mkReq404(name) {
6831
- const fn = () => {
6832
- throw new Error(`Request not found: ${name}`);
6833
- };
6834
- return { fn };
6835
- }
6836
- function nullHandler() {
6837
- return this;
6838
- }
6839
6720
 
6840
- class Transaction {
6841
- constructor(path, transactor, parentTransaction = null) {
6842
- this.path = path;
6843
- this.transactor = transactor;
6844
- this.parentTransaction = parentTransaction;
6845
- this._task = null;
6846
- }
6847
- get task() {
6848
- this._task ??= new Task;
6849
- return this._task;
6850
- }
6851
- getCompletionPromise() {
6852
- return this.task.promise;
6853
- }
6854
- setParent(parentTransaction) {
6855
- this.parentTransaction = parentTransaction;
6856
- parentTransaction.task.addDep(this.task);
6857
- }
6858
- run(rootValue, comps) {
6859
- return this.updateRootValue(rootValue, comps);
6860
- }
6861
- afterTransaction() {}
6862
- buildRootStack(root, comps) {
6863
- return Stack.root(comps, root);
6864
- }
6865
- buildStack(root, comps) {
6866
- return this.path.toTransactionPath().buildStack(this.buildRootStack(root, comps));
6867
- }
6868
- callHandler(root, instance, comps) {
6869
- const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
6870
- return handler.apply(instance, args);
6871
- }
6872
- getHandlerAndArgs(_root, _instance, _comps) {
6873
- return null;
6874
- }
6875
- getTransactionPath() {
6876
- return this.path.toTransactionPath();
6877
- }
6878
- updateRootValue(curRoot, comps) {
6879
- const txnPath = this.getTransactionPath();
6880
- const curLeaf = txnPath.lookup(curRoot);
6881
- const newLeaf = this.callHandler(curRoot, curLeaf, comps);
6882
- this._task?.complete?.({ value: newLeaf, old: curLeaf });
6883
- return curLeaf !== newLeaf ? txnPath.setValue(curRoot, newLeaf) : curRoot;
6884
- }
6885
- lookupName(_name) {
6886
- return null;
6721
+ class RenderedExample {
6722
+ constructor({ title, description = null, componentName, view, html, error = null }) {
6723
+ this.title = title;
6724
+ this.description = description;
6725
+ this.componentName = componentName;
6726
+ this.view = view;
6727
+ this.html = html;
6728
+ this.error = error;
6887
6729
  }
6888
6730
  }
6889
- var toNullIfNaN = (v) => Number.isNaN(v) ? null : v;
6890
- function getValue(e) {
6891
- return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
6892
- }
6893
6731
 
6894
- class InputEvent extends Transaction {
6895
- constructor(path, e, handler, transactor, dragInfo) {
6896
- super(path, transactor);
6897
- this.e = e;
6898
- this.handler = handler;
6899
- this.dragInfo = dragInfo;
6900
- this._dispatchPath = null;
6901
- }
6902
- get dispatchPath() {
6903
- this._dispatchPath ??= this.path.compact();
6904
- return this._dispatchPath;
6905
- }
6906
- buildRootStack(root, comps) {
6907
- return Stack.root(comps, root, this);
6908
- }
6909
- getHandlerAndArgs(root, _instance, comps) {
6910
- const stack = this.buildStack(root, comps);
6911
- const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
6912
- const path = this.dispatchPath;
6913
- let dispatcher;
6914
- for (let i = 0;i < args.length; i++) {
6915
- if (args[i]?.toHandlerArg) {
6916
- dispatcher ??= new Dispatcher(path, this.transactor, this);
6917
- args[i] = args[i].toHandlerArg(dispatcher);
6918
- }
6919
- }
6920
- args.push(new EventContext(path, this.transactor, this));
6921
- return [handler, args];
6922
- }
6923
- lookupName(name) {
6924
- const { e } = this;
6925
- switch (name) {
6926
- case "value":
6927
- return getValue(e);
6928
- case "valueAsInt":
6929
- return toNullIfNaN(parseInt(getValue(e), 10));
6930
- case "valueAsFloat":
6931
- return toNullIfNaN(parseFloat(getValue(e)));
6932
- case "target":
6933
- return e.target;
6934
- case "event":
6935
- return e;
6936
- case "isAlt":
6937
- return e.altKey;
6938
- case "isShift":
6939
- return e.shiftKey;
6940
- case "isCtrl":
6941
- case "isCmd":
6942
- return isMac && e.metaKey || e.ctrlKey;
6943
- case "key":
6944
- return e.key;
6945
- case "keyCode":
6946
- return e.keyCode;
6947
- case "isUpKey":
6948
- return e.key === "ArrowUp";
6949
- case "isDownKey":
6950
- return e.key === "ArrowDown";
6951
- case "isSend":
6952
- return e.key === "Enter";
6953
- case "isCancel":
6954
- return e.key === "Escape";
6955
- case "isTabKey":
6956
- return e.key === "Tab";
6957
- case "ctx":
6958
- return new EventContext(this.dispatchPath, this.transactor, this);
6959
- case "dragInfo":
6960
- return this.dragInfo;
6961
- }
6962
- return null;
6732
+ class RenderedSection {
6733
+ constructor({ title, description = null, items }) {
6734
+ this.title = title;
6735
+ this.description = description;
6736
+ this.items = items;
6963
6737
  }
6964
6738
  }
6965
6739
 
6966
- class NameArgsTransaction extends Transaction {
6967
- constructor(path, transactor, name, args, parentTransaction, opts = {}) {
6968
- super(path, transactor, parentTransaction);
6969
- this.name = name;
6970
- this.args = args;
6971
- this.opts = opts;
6972
- this.targetPath = path;
6973
- }
6974
- handlerProp = null;
6975
- getHandlerForName(comp) {
6976
- const handlers = comp?.[this.handlerProp];
6977
- return handlers?.[this.name] ?? handlers?.$unknown ?? nullHandler;
6740
+ class RenderBatch {
6741
+ constructor({ sections }) {
6742
+ this.sections = sections;
6978
6743
  }
6979
- getHandlerAndArgs(_root, instance, comps) {
6980
- const handler = this.getHandlerForName(comps.getCompFor(instance));
6981
- return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
6744
+ get hasErrors() {
6745
+ return this.sections.some((s) => s.items.some((i) => i.error !== null));
6982
6746
  }
6983
6747
  }
6984
6748
 
6985
- class ResponseEvent extends NameArgsTransaction {
6986
- handlerProp = "response";
6987
- constructor(path, transactor, name, args, parent, txnPath = null) {
6988
- super(path, transactor, name, args, parent);
6989
- this._txnPath = txnPath;
6990
- }
6991
- getTransactionPath() {
6992
- return this._txnPath ?? super.getTransactionPath();
6749
+ class TestResult {
6750
+ constructor({ title, fullPath, componentName = null, status, durationMs = 0, error = null }) {
6751
+ this.title = title;
6752
+ this.fullPath = fullPath;
6753
+ this.componentName = componentName;
6754
+ this.status = status;
6755
+ this.durationMs = durationMs;
6756
+ this.error = error;
6993
6757
  }
6994
6758
  }
6995
6759
 
6996
- class SendEvent extends NameArgsTransaction {
6997
- handlerProp = "receive";
6998
- run(rootVal, comps) {
6999
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
7000
- }
7001
- afterTransaction() {
7002
- const { path, name, args, opts, targetPath } = this;
7003
- if (opts.bubbles && path.steps.length > 0)
7004
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
6760
+ class DescribeResult {
6761
+ constructor({ title, componentName = null, children = [] }) {
6762
+ this.title = title;
6763
+ this.componentName = componentName;
6764
+ this.children = children;
7005
6765
  }
7006
6766
  }
7007
6767
 
7008
- class BubbleEvent extends SendEvent {
7009
- handlerProp = "bubble";
7010
- constructor(path, transactor, name, args, parent, opts, targetPath) {
7011
- super(path, transactor, name, args, parent, opts);
7012
- this.targetPath = targetPath ?? path;
7013
- }
7014
- stopPropagation() {
7015
- this.opts.bubbles = false;
6768
+ class ModuleTestReport {
6769
+ constructor({ path = null, suites = [], counts = { pass: 0, fail: 0, skip: 0, total: 0 } }) {
6770
+ this.path = path;
6771
+ this.suites = suites;
6772
+ this.counts = counts;
7016
6773
  }
7017
6774
  }
7018
6775
 
7019
- class Task {
7020
- constructor() {
7021
- this.deps = [];
7022
- this.val = this.resolve = this.reject = null;
7023
- this.promise = new Promise((res, rej) => {
7024
- this.resolve = res;
7025
- this.reject = rej;
7026
- });
7027
- this.isCompleted = false;
6776
+ class TestReport {
6777
+ constructor({ modules = [] }) {
6778
+ this.modules = modules;
7028
6779
  }
7029
- addDep(task) {
7030
- console.assert(!this.isCompleted, "addDep for completed task", this, task);
7031
- this.deps.push(task);
7032
- task.promise.then((_) => this._check());
6780
+ get totals() {
6781
+ return this.modules.reduce((acc, m) => ({
6782
+ pass: acc.pass + m.counts.pass,
6783
+ fail: acc.fail + m.counts.fail,
6784
+ skip: acc.skip + m.counts.skip,
6785
+ total: acc.total + m.counts.total
6786
+ }), { pass: 0, fail: 0, skip: 0, total: 0 });
7033
6787
  }
7034
- complete(val) {
7035
- this.val = val;
7036
- this._check();
6788
+ get hasFailures() {
6789
+ return this.modules.some((m) => m.counts.fail > 0);
7037
6790
  }
7038
- _check() {
7039
- if (this.deps.every((task) => task.isCompleted)) {
7040
- this.isCompleted = true;
7041
- this.resolve(this);
7042
- }
6791
+ }
6792
+
6793
+ // tools/core/tests.js
6794
+ class Describe {
6795
+ constructor({ title, componentName = null, parent = null }) {
6796
+ this.title = title;
6797
+ this.componentName = componentName;
6798
+ this.parent = parent;
6799
+ this.children = [];
7043
6800
  }
7044
6801
  }
7045
6802
 
7046
- class Dispatcher {
7047
- constructor(path, transactor, parentTransaction, root = transactor.state.val) {
7048
- this.path = path;
7049
- this.transactor = transactor;
7050
- this.parent = parentTransaction;
7051
- this.root = root;
7052
- }
7053
- walkPath(callback) {
7054
- const comps = this.transactor.comps;
7055
- const chain = this.path.toTransactionPath().resolveChain(this.root);
7056
- for (let i = chain.length - 1;i >= 0; i--) {
7057
- const comp = comps.getCompFor(chain[i]);
7058
- if (comp && callback(comp, chain[i]) === false)
7059
- return;
7060
- }
7061
- }
7062
- get at() {
7063
- return new PathChanges(this);
7064
- }
7065
- send(name, args, opts) {
7066
- return this.sendAtPath(this.path, name, args, opts);
6803
+ class Test {
6804
+ constructor({ title, fn, componentName = null, parent = null }) {
6805
+ this.title = title;
6806
+ this.fn = fn;
6807
+ this.componentName = componentName;
6808
+ this.parent = parent;
7067
6809
  }
7068
- bubble(name, args, opts) {
7069
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6810
+ }
6811
+
6812
+ class ModuleTests {
6813
+ constructor({ path = null, suites = [] } = {}) {
6814
+ this.path = path;
6815
+ this.suites = suites;
7070
6816
  }
7071
- sendAtPath(path, name, args, opts) {
7072
- return this.transactor.pushSend(path, name, args, opts, this.parent);
6817
+ }
6818
+
6819
+ class TestIndex {
6820
+ constructor({ modules = [] } = {}) {
6821
+ this.modules = modules;
7073
6822
  }
7074
- request(name, args, opts) {
7075
- return this.requestAtPath(this.path, name, args, opts);
6823
+ }
6824
+ function isComponentObject(x) {
6825
+ return x !== null && typeof x === "object" && typeof x.name === "string" && typeof x.Class === "function";
6826
+ }
6827
+ function resolveComponentName(arg, components) {
6828
+ if (isComponentObject(arg))
6829
+ return arg.name;
6830
+ if (typeof arg === "function") {
6831
+ for (const c of components)
6832
+ if (c.Class === arg)
6833
+ return c.name;
7076
6834
  }
7077
- requestAtPath(path, name, args, opts) {
7078
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
6835
+ return null;
6836
+ }
6837
+ function titleFromArg(arg) {
6838
+ if (typeof arg === "string")
6839
+ return arg;
6840
+ if (isComponentObject(arg))
6841
+ return arg.name;
6842
+ if (typeof arg === "function")
6843
+ return arg.name || "(anonymous)";
6844
+ return String(arg);
6845
+ }
6846
+ function makeCollector({ path = null, components = [] } = {}) {
6847
+ const moduleTests = new ModuleTests({ path, suites: [] });
6848
+ const stack = [];
6849
+ function describe(...args) {
6850
+ let head;
6851
+ let options = null;
6852
+ let fn;
6853
+ if (args.length === 2) {
6854
+ [head, fn] = args;
6855
+ } else if (args.length === 3) {
6856
+ [head, options, fn] = args;
6857
+ } else {
6858
+ throw new Error(`describe() expects 2 or 3 arguments, got ${args.length}`);
6859
+ }
6860
+ if (typeof fn !== "function") {
6861
+ throw new Error(`describe(${JSON.stringify(titleFromArg(head))}): final argument must be a function`);
6862
+ }
6863
+ let componentName = null;
6864
+ if (typeof head !== "string") {
6865
+ componentName = resolveComponentName(head, components);
6866
+ }
6867
+ if (componentName === null && options && options.component != null) {
6868
+ componentName = resolveComponentName(options.component, components);
6869
+ }
6870
+ if (componentName === null) {
6871
+ const parent2 = stack.length ? stack[stack.length - 1] : null;
6872
+ if (parent2)
6873
+ componentName = parent2.componentName;
6874
+ }
6875
+ const parent = stack.length ? stack[stack.length - 1] : null;
6876
+ const node = new Describe({
6877
+ title: titleFromArg(head),
6878
+ componentName,
6879
+ parent
6880
+ });
6881
+ if (parent)
6882
+ parent.children.push(node);
6883
+ else
6884
+ moduleTests.suites.push(node);
6885
+ stack.push(node);
6886
+ try {
6887
+ fn();
6888
+ } finally {
6889
+ stack.pop();
6890
+ }
7079
6891
  }
7080
- lookupTypeFor(name, inst) {
7081
- return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
6892
+ function test(title, fn) {
6893
+ if (typeof title !== "string") {
6894
+ throw new Error("test(title, fn): title must be a string");
6895
+ }
6896
+ if (typeof fn !== "function") {
6897
+ throw new Error(`test(${JSON.stringify(title)}): fn must be a function`);
6898
+ }
6899
+ const parent = stack.length ? stack[stack.length - 1] : null;
6900
+ if (!parent) {
6901
+ throw new Error(`test(${JSON.stringify(title)}) must be called inside a describe()`);
6902
+ }
6903
+ parent.children.push(new Test({
6904
+ title,
6905
+ fn,
6906
+ componentName: parent.componentName,
6907
+ parent
6908
+ }));
7082
6909
  }
6910
+ return { describe, test, moduleTests };
7083
6911
  }
7084
6912
 
7085
- class EventContext extends Dispatcher {
7086
- get name() {
7087
- return this.parent?.name ?? null;
7088
- }
7089
- get targetPath() {
7090
- return this.parent.targetPath;
7091
- }
7092
- stopPropagation() {
7093
- return this.parent.stopPropagation();
6913
+ // tools/core/test.js
6914
+ function buildPath(node) {
6915
+ const parts = [];
6916
+ let cur = node;
6917
+ while (cur) {
6918
+ parts.unshift(cur.title);
6919
+ cur = cur.parent;
7094
6920
  }
6921
+ return parts.join(" > ");
7095
6922
  }
7096
-
7097
- class RequestContext extends Dispatcher {
6923
+ function captureError(e) {
6924
+ const out = { message: e.message, stack: e.stack };
6925
+ if ("expected" in e)
6926
+ out.expected = e.expected;
6927
+ if ("actual" in e)
6928
+ out.actual = e.actual;
6929
+ return out;
7098
6930
  }
7099
-
7100
- class PathChanges extends PathBuilder {
7101
- constructor(dispatcher) {
7102
- super();
7103
- this.dispatcher = dispatcher;
6931
+ async function runTests({
6932
+ getTests,
6933
+ components = [],
6934
+ path = null,
6935
+ expect: expect2,
6936
+ name = null,
6937
+ grep = null,
6938
+ bail = false,
6939
+ requestHandlers = null,
6940
+ macros = null
6941
+ } = {}) {
6942
+ const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
6943
+ if (typeof getTests !== "function") {
6944
+ return new TestReport({
6945
+ modules: [new ModuleTestReport({ path, suites: [], counts })]
6946
+ });
7104
6947
  }
7105
- send(name, args, opts) {
7106
- return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
6948
+ if (typeof expect2 !== "function") {
6949
+ throw new Error("runTests: expect must be provided (e.g. chai's expect)");
7107
6950
  }
7108
- bubble(name, args, opts) {
7109
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6951
+ const { describe, test, moduleTests } = makeCollector({ path, components });
6952
+ await getTests({ describe, test, expect: expect2, drive });
6953
+ let _stack = null;
6954
+ function getStack() {
6955
+ if (_stack)
6956
+ return _stack;
6957
+ _stack = new ComponentStack;
6958
+ _stack.registerComponents(components);
6959
+ if (macros)
6960
+ _stack.registerMacros(macros);
6961
+ if (requestHandlers)
6962
+ _stack.registerRequestHandlers(requestHandlers);
6963
+ return _stack;
6964
+ }
6965
+ async function drive(value, phase, opts = {}) {
6966
+ const transactor = new Transactor(getStack().comps, value);
6967
+ if (opts.onMessage)
6968
+ transactor.state.onChange(({ val, old, info }) => {
6969
+ const t = info?.transaction;
6970
+ opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
6971
+ });
6972
+ dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
6973
+ await transactor.settle();
6974
+ return transactor.state.val;
7110
6975
  }
7111
- buildPath() {
7112
- return this.dispatcher.path.concat(this.pathChanges);
6976
+ let bailed = false;
6977
+ async function visit(node) {
6978
+ if (node instanceof Test) {
6979
+ if (name !== null && node.componentName !== name)
6980
+ return null;
6981
+ const fullPath = buildPath(node);
6982
+ if (grep !== null && !fullPath.includes(grep))
6983
+ return null;
6984
+ counts.total++;
6985
+ if (bailed) {
6986
+ counts.skip++;
6987
+ return new TestResult({
6988
+ title: node.title,
6989
+ fullPath,
6990
+ componentName: node.componentName,
6991
+ status: "skip"
6992
+ });
6993
+ }
6994
+ const start = performance.now();
6995
+ try {
6996
+ await node.fn();
6997
+ counts.pass++;
6998
+ return new TestResult({
6999
+ title: node.title,
7000
+ fullPath,
7001
+ componentName: node.componentName,
7002
+ status: "pass",
7003
+ durationMs: performance.now() - start
7004
+ });
7005
+ } catch (e) {
7006
+ counts.fail++;
7007
+ if (bail)
7008
+ bailed = true;
7009
+ return new TestResult({
7010
+ title: node.title,
7011
+ fullPath,
7012
+ componentName: node.componentName,
7013
+ status: "fail",
7014
+ durationMs: performance.now() - start,
7015
+ error: captureError(e)
7016
+ });
7017
+ }
7018
+ }
7019
+ const childResults = [];
7020
+ for (const child of node.children) {
7021
+ const r = await visit(child);
7022
+ if (r !== null)
7023
+ childResults.push(r);
7024
+ }
7025
+ if (childResults.length === 0)
7026
+ return null;
7027
+ return new DescribeResult({
7028
+ title: node.title,
7029
+ componentName: node.componentName,
7030
+ children: childResults
7031
+ });
7032
+ }
7033
+ const suiteResults = [];
7034
+ for (const suite of moduleTests.suites) {
7035
+ const r = await visit(suite);
7036
+ if (r !== null)
7037
+ suiteResults.push(r);
7038
+ }
7039
+ return new TestReport({
7040
+ modules: [new ModuleTestReport({ path, suites: suiteResults, counts })]
7041
+ });
7042
+ }
7043
+
7044
+ // tools/core/test-console.js
7045
+ var PASS = "color: #0a0; font-weight: bold";
7046
+ var FAIL = "color: #c00; font-weight: bold";
7047
+ var SKIP = "color: #888";
7048
+ var DIM = "color: #888";
7049
+ var RESET = "color: inherit; font-weight: normal";
7050
+ function reportTestNode(node) {
7051
+ if (node.children) {
7052
+ const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
7053
+ console.group(label);
7054
+ for (const child of node.children)
7055
+ reportTestNode(child);
7056
+ console.groupEnd();
7057
+ return;
7058
+ }
7059
+ const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
7060
+ if (node.status === "pass") {
7061
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
7062
+ } else if (node.status === "skip") {
7063
+ console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
7064
+ } else {
7065
+ console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
7066
+ console.error(node.error?.message ?? "(no error message)");
7067
+ if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
7068
+ console.log("expected:", node.error.expected);
7069
+ console.log("actual: ", node.error.actual);
7070
+ }
7071
+ if (node.error?.stack)
7072
+ console.log(node.error.stack);
7073
+ console.groupEnd();
7074
+ }
7075
+ }
7076
+ function reportTestReportToConsole(report) {
7077
+ for (const m of report.modules) {
7078
+ const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
7079
+ console.group(label);
7080
+ if (m.suites.length === 0) {
7081
+ console.log("(no tests)");
7082
+ } else {
7083
+ for (const s of m.suites)
7084
+ reportTestNode(s);
7085
+ }
7086
+ const c = m.counts;
7087
+ const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
7088
+ if (c.fail > 0)
7089
+ console.error(`%c${summary}`, FAIL);
7090
+ else if (c.total === 0)
7091
+ console.log(`%c${summary}`, DIM);
7092
+ else
7093
+ console.log(`%c${summary}`, PASS);
7094
+ console.groupEnd();
7095
+ }
7096
+ return report;
7097
+ }
7098
+
7099
+ // tools/format/lint.js
7100
+ var UNSUPPORTED_EXPR_LABEL = {
7101
+ ternary: "ternary expression",
7102
+ comparison: "comparison",
7103
+ logical: "logical expression",
7104
+ "call-with-args": "method call with arguments"
7105
+ };
7106
+ function unsupportedExprMessage(info) {
7107
+ const v = JSON.stringify(info.value);
7108
+ const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
7109
+ switch (info.role) {
7110
+ case "attr":
7111
+ return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
7112
+ case "directive":
7113
+ return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
7114
+ case "if":
7115
+ return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
7116
+ case "x-op":
7117
+ return `Unsupported ${label} ${v} in <x ${info.op}>`;
7118
+ default:
7119
+ return `Unsupported ${label} ${v}`;
7120
+ }
7121
+ }
7122
+ function badValueMessage(info) {
7123
+ const v = JSON.stringify(info.value);
7124
+ switch (info.role) {
7125
+ case "attr":
7126
+ return `Cannot parse value ${v} for attribute ':${info.attr}'`;
7127
+ case "directive":
7128
+ return `Cannot parse value ${v} for directive '@${info.directive}'`;
7129
+ case "if":
7130
+ return `Cannot parse condition ${v} for '@if.${info.attr}'`;
7131
+ case "x-op":
7132
+ return `Cannot parse value ${v} for <x ${info.op}>`;
7133
+ case "handler-name":
7134
+ return `Cannot parse handler name ${v}`;
7135
+ case "handler-arg":
7136
+ return `Cannot parse handler argument ${v}`;
7137
+ case "macro-var":
7138
+ return `Macro variable '^${info.name}' is not defined`;
7139
+ default:
7140
+ return `Cannot parse value ${v}`;
7141
+ }
7142
+ }
7143
+ function tagDisplay(tag) {
7144
+ return tag ? String(tag).toLowerCase() : null;
7145
+ }
7146
+ function fmtTagSuffix(info) {
7147
+ const t = tagDisplay(info?.tag);
7148
+ return t && t !== "x" ? ` on <${t}>` : "";
7149
+ }
7150
+ function fmtOriginSuffix(info) {
7151
+ if (!info)
7152
+ return "";
7153
+ const parts = [];
7154
+ if (info.originAttr) {
7155
+ const branch = info.branch ? `[${info.branch}]` : "";
7156
+ parts.push(`in ${info.originAttr}${branch}`);
7157
+ }
7158
+ if (info.handlerName) {
7159
+ parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
7160
+ }
7161
+ const t = tagDisplay(info.tag);
7162
+ if (t && t !== "x")
7163
+ parts.push(`on <${t}>`);
7164
+ return parts.length ? ` (${parts.join(", ")})` : "";
7165
+ }
7166
+ function fmtEventSuffix(info) {
7167
+ if (info?.originAttr)
7168
+ return ` in ${info.originAttr}`;
7169
+ if (info?.eventName)
7170
+ return ` in @on.${info.eventName}`;
7171
+ return "";
7172
+ }
7173
+ function lintIdToMessage(id, info) {
7174
+ switch (id) {
7175
+ case "RENDER_IT_OUTSIDE_OF_LOOP":
7176
+ return "<x render-it> used outside of a loop";
7177
+ case "UNKNOWN_EVENT_MODIFIER": {
7178
+ const mods = info.handler?.modifiers ?? [info.modifier];
7179
+ const written = `@on.${info.name}+${mods.join("+")}`;
7180
+ return `Unknown modifier '+${info.modifier}' in '${written}'`;
7181
+ }
7182
+ case "UNKNOWN_HANDLER_ARG_NAME":
7183
+ return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
7184
+ case "INPUT_HANDLER_NOT_IMPLEMENTED":
7185
+ return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
7186
+ case "INPUT_HANDLER_NOT_REFERENCED":
7187
+ return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
7188
+ case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
7189
+ return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
7190
+ case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
7191
+ return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
7192
+ case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
7193
+ return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
7194
+ case "FIELD_VAL_NOT_DEFINED":
7195
+ return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
7196
+ case "FIELD_VAL_IS_METHOD":
7197
+ return `'.${info.name}' reads a field, but '${info.name}' is defined as a method — use '$${info.name}'${fmtOriginSuffix(info)}`;
7198
+ case "METHOD_VAL_NOT_DEFINED":
7199
+ return `Method '$${info.name}' is not defined${fmtOriginSuffix(info)}`;
7200
+ case "METHOD_VAL_IS_FIELD":
7201
+ return `'$${info.name}' calls a method, but '${info.name}' is defined as a field — use '.${info.name}'${fmtOriginSuffix(info)}`;
7202
+ case "DUPLICATE_ATTR_DEFINITION": {
7203
+ const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
7204
+ const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
7205
+ return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
7206
+ }
7207
+ case "IF_NO_BRANCH_SET":
7208
+ return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
7209
+ case "UNKNOWN_REQUEST_NAME":
7210
+ return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
7211
+ case "UNKNOWN_COMPONENT_NAME":
7212
+ return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
7213
+ case "ALT_HANDLER_NOT_DEFINED":
7214
+ return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
7215
+ case "ALT_HANDLER_NOT_REFERENCED":
7216
+ return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
7217
+ case "DYN_VAL_NOT_DEFINED":
7218
+ return `Dynamic variable '*${info.name}' is not defined${fmtOriginSuffix(info)}`;
7219
+ case "DYN_ALIAS_NOT_REFERENCED":
7220
+ return `Lookup '${info.name}' is defined but never used — remove it or reference it as '*${info.name}' in a view`;
7221
+ case "PROVIDE_NOT_ADDRESSABLE":
7222
+ 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`;
7223
+ case "LOOKUP_BAD_SHAPE":
7224
+ return `Lookup '${info.name}' has an invalid shape: ${info.problem}`;
7225
+ case "LOOKUP_TARGET_MALFORMED":
7226
+ return `Lookup '${info.name}' target '${info.target}' must be 'Producer.provideName' (a string, or the 'for' of { for, default })`;
7227
+ case "UNKNOWN_MACRO_ARG":
7228
+ return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
7229
+ case "UNKNOWN_DIRECTIVE":
7230
+ return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
7231
+ case "UNKNOWN_X_OP":
7232
+ return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
7233
+ case "UNKNOWN_X_ATTR":
7234
+ return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
7235
+ case "MAYBE_DROP_AT_PREFIX": {
7236
+ const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
7237
+ return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
7238
+ }
7239
+ case "MAYBE_ADD_AT_PREFIX":
7240
+ return `'${info.name}' on <${(info.tag ?? "").toLowerCase()}> is a plain attribute, but '@${info.name}' is a directive — add the leading '@'`;
7241
+ case "BAD_VALUE":
7242
+ return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
7243
+ case "UNSUPPORTED_EXPR_SYNTAX":
7244
+ return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
7245
+ case "REDUNDANT_TEMPLATE_STRING":
7246
+ return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
7247
+ case "PLACEHOLDERLESS_TEMPLATE_STRING":
7248
+ return `Template string has no dynamic parts — use the string literal ${info.literal} instead${fmtOriginSuffix(info)}`;
7249
+ case "UNKNOWN_COMPONENT_SPEC_KEY":
7250
+ return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
7251
+ case "COMP_FIELD_BAD_SHAPE":
7252
+ 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}`;
7253
+ case "HTML_TAG_NAME_HAS_UPPERCASE":
7254
+ return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
7255
+ case "HTML_SVG_TAG_WILL_LOWERCASE":
7256
+ return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
7257
+ case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
7258
+ return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
7259
+ case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
7260
+ return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
7261
+ case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
7262
+ return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
7263
+ case "HTML_DUPLICATE_FORM":
7264
+ return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
7265
+ case "HTML_NESTED_INTERACTIVE":
7266
+ return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
7267
+ case "HTML_MISNESTED_FORMATTING":
7268
+ return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
7269
+ case "HTML_UNEXPECTED_END_TAG":
7270
+ return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
7271
+ case "HTML_UNCLOSED_BEFORE_END":
7272
+ return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
7273
+ case "HTML_DUPLICATE_ATTRIBUTE":
7274
+ return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
7275
+ case "HTML_ATTRIBUTES_ON_END_TAG":
7276
+ return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
7277
+ case "HTML_SELF_CLOSING_END_TAG":
7278
+ return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
7279
+ case "HTML_MISSING_ATTRIBUTE_VALUE":
7280
+ return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
7281
+ case "HTML_CDATA_IN_HTML_NAMESPACE":
7282
+ return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
7283
+ case "HTML_BOGUS_COMMENT":
7284
+ return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
7285
+ case "HTML_SVG_ATTR_WILL_LOWERCASE":
7286
+ return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
7287
+ case "HTML_MATHML_ATTR_WILL_LOWERCASE":
7288
+ return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
7289
+ case "ASYNC_HANDLER":
7290
+ 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)`;
7291
+ case "TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE":
7292
+ 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)}`;
7293
+ case "GLOBAL_SELECTOR_IN_SCOPED_STYLE":
7294
+ 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)}`;
7295
+ case "LINT_ERROR":
7296
+ return info.message;
7297
+ default:
7298
+ return id;
7299
+ }
7300
+ }
7301
+ function suggestionToMessage(suggestion) {
7302
+ if (!suggestion)
7303
+ return null;
7304
+ switch (suggestion.kind) {
7305
+ case "replace-name":
7306
+ return `did you mean '${suggestion.to}'?`;
7307
+ case "drop-prefix":
7308
+ return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
7309
+ case "add-prefix":
7310
+ return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
7311
+ case "remove":
7312
+ return `remove ${suggestion.what}`;
7313
+ case "rewrite":
7314
+ return `use '${suggestion.to}' instead of '${suggestion.from}'`;
7315
+ case "wrap":
7316
+ return `wrap it in ${suggestion.to}`;
7317
+ case "rephrase":
7318
+ return suggestion.text ?? null;
7319
+ default:
7320
+ return null;
7321
+ }
7322
+ }
7323
+ function fmtLocationSuffix(info) {
7324
+ const loc = info?.location;
7325
+ if (!loc)
7326
+ return "";
7327
+ return ` at line ${loc.line}, col ${loc.column}`;
7328
+ }
7329
+ function htmlActionPhrase(action, tag, parent) {
7330
+ switch (action) {
7331
+ case "ignored":
7332
+ return `the parser will drop this <${tag}>`;
7333
+ case "drop":
7334
+ return `the parser will drop this <${tag}>`;
7335
+ case "auto-close-implicit":
7336
+ return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
7337
+ case "foster-parent":
7338
+ return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
7339
+ case "foreign-breakout":
7340
+ return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
7341
+ default:
7342
+ return `parser action: ${action}`;
7113
7343
  }
7114
7344
  }
7115
7345
 
@@ -8138,9 +8368,11 @@ export {
8138
8368
  setIn,
8139
8369
  set,
8140
8370
  runTests,
8371
+ resolveArgs,
8141
8372
  reportTestReportToConsole,
8142
8373
  removeIn,
8143
8374
  remove,
8375
+ phaseOps,
8144
8376
  mergeWith,
8145
8377
  mergeDeepWith,
8146
8378
  mergeDeep,
@@ -8177,6 +8409,7 @@ export {
8177
8409
  get,
8178
8410
  fromJS,
8179
8411
  docComponents,
8412
+ dispatchPhase,
8180
8413
  css,
8181
8414
  component,
8182
8415
  compileClassesToStyleText,
@@ -8198,6 +8431,7 @@ export {
8198
8431
  TestReport,
8199
8432
  TestIndex,
8200
8433
  Test,
8434
+ TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE,
8201
8435
  Stack2 as Stack,
8202
8436
  Set2 as Set,
8203
8437
  Seq,
@@ -8242,6 +8476,7 @@ export {
8242
8476
  INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
8243
8477
  Map3 as IMap,
8244
8478
  IF_NO_BRANCH_SET,
8479
+ GLOBAL_SELECTOR_IN_SCOPED_STYLE,
8245
8480
  FIELD_VAL_NOT_DEFINED,
8246
8481
  FIELD_VAL_IS_METHOD,
8247
8482
  FIELD_CLASS,