tutuca 0.9.32 → 0.9.34

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.
@@ -293,9 +293,11 @@ class ValParser {
293
293
  const charCode = s.charCodeAt(0);
294
294
  switch (charCode) {
295
295
  case 94: {
296
- const newS = px.frame.macroVars?.[s.slice(1)];
296
+ const name = s.slice(1);
297
+ const newS = px.frame.macroVars?.[name];
297
298
  if (newS !== undefined)
298
299
  return this.parse(newS, px);
300
+ px.onParseIssue("bad-value", { role: "macro-var", name, value: s });
299
301
  return null;
300
302
  }
301
303
  case 39:
@@ -632,6 +634,8 @@ class AttrParser {
632
634
  this.attrs ??= [];
633
635
  this.attrs.push(new Attr(name, val));
634
636
  this.hasDynamic ||= !(val instanceof ConstVal);
637
+ } else {
638
+ this.px.onParseIssue("bad-value", { role: "attr", attr: name, value });
635
639
  }
636
640
  }
637
641
  pushWrapper(name, raw, val) {
@@ -647,6 +651,9 @@ class AttrParser {
647
651
  this.attrs ??= [];
648
652
  this.attrs.push(this.ifAttr);
649
653
  this.hasDynamic = true;
654
+ } else {
655
+ const info = { role: "if", attr: directiveName.slice(3), value };
656
+ this.px.onParseIssue("bad-value", info);
650
657
  }
651
658
  }
652
659
  parseThen(s) {
@@ -669,36 +676,44 @@ class AttrParser {
669
676
  this.events.add(eventName, handler, modifiers);
670
677
  }
671
678
  }
679
+ _parseDirectiveValue(directiveName, s, parserFn) {
680
+ const val = parserFn.call(vp, s, this.px);
681
+ if (val === null) {
682
+ const info = { role: "directive", directive: directiveName, value: s };
683
+ this.px.onParseIssue("bad-value", info);
684
+ }
685
+ return val;
686
+ }
672
687
  parseDirective(s, directiveName) {
673
688
  switch (directiveName) {
674
689
  case "dangerouslysetinnerhtml":
675
690
  this.attrs ??= [];
676
- this.attrs.push(new RawHtmlAttr(vp.parseText(s, this.px)));
691
+ this.attrs.push(new RawHtmlAttr(this._parseDirectiveValue(directiveName, s, vp.parseText)));
677
692
  this.hasDynamic = true;
678
693
  return;
679
694
  case "slot":
680
695
  this.pushWrapper("slot", s, vp.const(s));
681
696
  return;
682
697
  case "push-view":
683
- this.pushWrapper("push-view", s, vp.parseText(s, this.px));
698
+ this.pushWrapper("push-view", s, this._parseDirectiveValue(directiveName, s, vp.parseText));
684
699
  return;
685
700
  case "text":
686
- this.textChild = vp.parseText(s, this.px);
701
+ this.textChild = this._parseDirectiveValue(directiveName, s, vp.parseText);
687
702
  return;
688
703
  case "show":
689
- this.pushWrapper("show", s, vp.parseCondValue(s, this.px));
704
+ this.pushWrapper("show", s, this._parseDirectiveValue(directiveName, s, vp.parseCondValue));
690
705
  return;
691
706
  case "hide":
692
- this.pushWrapper("hide", s, vp.parseCondValue(s, this.px));
707
+ this.pushWrapper("hide", s, this._parseDirectiveValue(directiveName, s, vp.parseCondValue));
693
708
  return;
694
709
  case "each":
695
- this.eachAttr = this.pushWrapper("each", s, vp.parseEach(s, this.px));
710
+ this.eachAttr = this.pushWrapper("each", s, this._parseDirectiveValue(directiveName, s, vp.parseEach));
696
711
  return;
697
712
  case "enrich-with":
698
713
  if (this.eachAttr !== null) {
699
- this.eachAttr.enrichWithVal = vp.parseAlter(s, this.px);
714
+ this.eachAttr.enrichWithVal = this._parseDirectiveValue(directiveName, s, vp.parseAlter);
700
715
  } else {
701
- this.pushWrapper("scope", s, vp.parseAlter(s, this.px));
716
+ this.pushWrapper("scope", s, this._parseDirectiveValue(directiveName, s, vp.parseAlter));
702
717
  }
703
718
  return;
704
719
  case "when":
@@ -722,16 +737,18 @@ class AttrParser {
722
737
  this.parseThen(s);
723
738
  else if (directiveName.startsWith("else."))
724
739
  this.parseElse(s);
725
- else
726
- this.px.onParseIssue("unknown-directive", { name: directiveName, value: s });
740
+ else {
741
+ const info = { name: directiveName, value: s };
742
+ this.px.onParseIssue("unknown-directive", info);
743
+ }
727
744
  }
728
745
  _parseWhen(s) {
729
746
  if (this.eachAttr !== null)
730
- this.eachAttr.whenVal = vp.parseAlter(s, this.px);
747
+ this.eachAttr.whenVal = this._parseDirectiveValue("when", s, vp.parseAlter);
731
748
  }
732
749
  _parseLoopWith(s) {
733
750
  if (this.eachAttr !== null)
734
- this.eachAttr.loopWithVal = vp.parseAlter(s, this.px);
751
+ this.eachAttr.loopWithVal = this._parseDirectiveValue("loop-with", s, vp.parseAlter);
735
752
  }
736
753
  parse(attributes, parseAll = false) {
737
754
  for (const { name, value } of attributes) {
@@ -859,13 +876,22 @@ class EventHandler {
859
876
  static parse(s, px) {
860
877
  const [handlerName, ...rawArgs] = s.trim().split(/\s+/);
861
878
  const handlerVal = vp.parseHandlerName(handlerName, px);
862
- if (handlerVal === null)
879
+ if (handlerVal === null) {
880
+ const info = { role: "handler-name", value: handlerName };
881
+ px.onParseIssue("bad-value", info);
863
882
  return null;
883
+ }
864
884
  const args = new Array(rawArgs.length);
865
885
  vp.allowHandlerArg();
866
886
  for (let i = 0;i < rawArgs.length; i++) {
867
887
  const val = vp.parse(rawArgs[i], px);
868
- args[i] = val !== null ? val : vp.nullConstVal;
888
+ if (val !== null) {
889
+ args[i] = val;
890
+ } else {
891
+ const info = { role: "handler-arg", value: rawArgs[i] };
892
+ px.onParseIssue("bad-value", info);
893
+ args[i] = vp.nullConstVal;
894
+ }
869
895
  }
870
896
  return new EventHandler(handlerVal, args);
871
897
  }
@@ -1029,63 +1055,84 @@ class ANode extends BaseNode {
1029
1055
  else if (node instanceof px.Comment)
1030
1056
  return new CommentNode(node.textContent);
1031
1057
  const { childNodes, attributes: attrs, tagName: tag } = node;
1032
- const childs = new Array(childNodes.length);
1033
- for (let i = 0;i < childNodes.length; i++)
1034
- childs[i] = ANode.fromDOM(childNodes[i], px);
1035
- const isPseudoX = attrs[0]?.name === "@x";
1036
- if (tag === "X" || isPseudoX) {
1037
- if (attrs.length === 0)
1038
- return maybeFragment(childs);
1039
- const opIdx = isPseudoX ? 1 : 0;
1040
- const { name, value } = attrs[opIdx];
1041
- const as = attrs.getNamedItem("as")?.value ?? null;
1042
- let node2;
1043
- switch (name) {
1044
- case "slot":
1045
- node2 = new SlotNode(null, vp.const(value), maybeFragment(childs));
1046
- break;
1047
- case "text":
1048
- node2 = px.addNodeIf(RenderTextNode, vp.parseText(value, px));
1049
- break;
1050
- case "render":
1051
- node2 = px.addNodeIf(RenderNode, vp.parseRender(value, px), as);
1052
- break;
1053
- case "render-it":
1054
- node2 = px.addNodeIf(RenderItNode, vp.bindValIt, as);
1055
- break;
1056
- case "render-each":
1057
- node2 = RenderEachNode.parse(px, vp, value, as, attrs);
1058
- break;
1059
- case "show":
1060
- node2 = px.addNodeIf(ShowNode, vp.parseCondValue(value, px), maybeFragment(childs));
1061
- break;
1062
- case "hide":
1063
- node2 = px.addNodeIf(HideNode, vp.parseCondValue(value, px), maybeFragment(childs));
1064
- break;
1065
- default:
1066
- px.onParseIssue("unknown-x-op", { name, value });
1067
- return new CommentNode(`Error: InvalidSpecialTagOp ${name}=${value}`);
1068
- }
1069
- return processXExtras(node2, attrs, name, (isPseudoX ? 1 : 0) + 1, px);
1070
- } else if (tag.charCodeAt(1) === 58 && tag.charCodeAt(0) === 88) {
1071
- const macroName = tag.slice(2).toLowerCase();
1072
- if (macroName === "slot") {
1073
- const slotName = attrs.getNamedItem("name")?.value ?? "_";
1074
- return px.frame.macroSlots[slotName] ?? maybeFragment(childs);
1058
+ const childs = [];
1059
+ for (let i = 0;i < childNodes.length; i++) {
1060
+ const child = ANode.fromDOM(childNodes[i], px);
1061
+ if (child !== null)
1062
+ childs.push(child);
1063
+ }
1064
+ const prevTag = px.currentTag;
1065
+ px.currentTag = tag;
1066
+ try {
1067
+ const isPseudoX = attrs[0]?.name === "@x";
1068
+ if (tag === "X" || isPseudoX) {
1069
+ return parseXOp(attrs, childs, isPseudoX ? 1 : 0, px);
1070
+ } else if (tag.charCodeAt(1) === 58 && tag.charCodeAt(0) === 88) {
1071
+ const macroName = tag.slice(2).toLowerCase();
1072
+ if (macroName === "slot") {
1073
+ const slotName = attrs.getNamedItem("name")?.value ?? "_";
1074
+ return px.frame.macroSlots[slotName] ?? maybeFragment(childs);
1075
+ }
1076
+ const [nAttrs, wrappers] = Attributes.parse(attrs, px, true);
1077
+ px.onAttributes(nAttrs, wrappers, null, true, tag);
1078
+ return wrap(px.newMacroNode(macroName, nAttrs.toMacroVars(), childs), px, wrappers);
1079
+ } else if (VALID_NODE_RE.test(tag)) {
1080
+ const [nAttrs, wrappers, textChild] = Attributes.parse(attrs, px);
1081
+ px.onAttributes(nAttrs, wrappers, textChild, false, tag);
1082
+ if (textChild)
1083
+ childs.unshift(new RenderTextNode(null, textChild));
1084
+ const domChilds = tag !== "PRE" ? condenseChildsWhites(childs) : childs;
1085
+ return wrap(new DomNode(tag, nAttrs, domChilds), px, wrappers);
1075
1086
  }
1076
- const [nAttrs, wrappers] = Attributes.parse(attrs, px, true);
1077
- px.onAttributes(nAttrs, wrappers, null, true);
1078
- return wrap(px.newMacroNode(macroName, nAttrs.toMacroVars(), childs), px, wrappers);
1079
- } else if (VALID_NODE_RE.test(tag)) {
1080
- const [nAttrs, wrappers, textChild] = Attributes.parse(attrs, px);
1081
- px.onAttributes(nAttrs, wrappers, textChild);
1082
- if (textChild)
1083
- childs.unshift(new RenderTextNode(null, textChild));
1084
- const domChilds = tag !== "PRE" ? condenseChildsWhites(childs) : childs;
1085
- return wrap(new DomNode(tag, nAttrs, domChilds), px, wrappers);
1087
+ return new CommentNode(`Error: InvalidTagName ${tag}`);
1088
+ } finally {
1089
+ px.currentTag = prevTag;
1090
+ }
1091
+ }
1092
+ }
1093
+ function parseXOp(attrs, childs, opIdx, px) {
1094
+ if (attrs.length === 0)
1095
+ return maybeFragment(childs);
1096
+ const { name, value } = attrs[opIdx];
1097
+ const as = attrs.getNamedItem("as")?.value ?? null;
1098
+ let node;
1099
+ switch (name) {
1100
+ case "slot":
1101
+ node = new SlotNode(null, vp.const(value), maybeFragment(childs));
1102
+ break;
1103
+ case "text":
1104
+ node = px.addNodeIf(RenderTextNode, parseXOpVal(name, value, px, vp.parseText));
1105
+ break;
1106
+ case "render":
1107
+ node = px.addNodeIf(RenderNode, parseXOpVal(name, value, px, vp.parseRender), as);
1108
+ break;
1109
+ case "render-it":
1110
+ node = px.addNodeIf(RenderItNode, vp.bindValIt, as);
1111
+ break;
1112
+ case "render-each":
1113
+ node = RenderEachNode.parse(px, vp, value, as, attrs);
1114
+ break;
1115
+ case "show": {
1116
+ const val = parseXOpVal(name, value, px, vp.parseCondValue);
1117
+ node = px.addNodeIf(ShowNode, val, maybeFragment(childs));
1118
+ break;
1119
+ }
1120
+ case "hide": {
1121
+ const val = parseXOpVal(name, value, px, vp.parseCondValue);
1122
+ node = px.addNodeIf(HideNode, val, maybeFragment(childs));
1123
+ break;
1086
1124
  }
1087
- return new CommentNode(`Error: InvalidTagName ${tag}`);
1125
+ default:
1126
+ px.onParseIssue("unknown-x-op", { name, value });
1127
+ return new CommentNode(`Error: InvalidSpecialTagOp ${name}=${value}`);
1088
1128
  }
1129
+ return processXExtras(node, attrs, name, opIdx + 1, px);
1130
+ }
1131
+ function parseXOpVal(opName, value, px, parserFn) {
1132
+ const val = parserFn.call(vp, value, px);
1133
+ if (val === null)
1134
+ px.onParseIssue("bad-value", { role: "x-op", op: opName, value });
1135
+ return val;
1089
1136
  }
1090
1137
  function processXExtras(node, attrs, opName, startIdx, px) {
1091
1138
  const consumed = X_OP_CONSUMED[opName];
@@ -1213,7 +1260,7 @@ class RenderEachNode extends RenderViewId {
1213
1260
  return rx.renderEach(stack, this.iterInfo, this.nodeId, this.viewId);
1214
1261
  }
1215
1262
  static parse(px, vp2, s, as, attrs) {
1216
- const node = px.addNodeIf(RenderEachNode, vp2.parseEach(s, px), as);
1263
+ const node = px.addNodeIf(RenderEachNode, parseXOpVal("render-each", s, px, vp2.parseEach), as);
1217
1264
  if (node !== null) {
1218
1265
  const attrParser = getAttrParser(px);
1219
1266
  attrParser.eachAttr = attrParser.pushWrapper("each", s, node.val);
@@ -1369,6 +1416,7 @@ class ParseContext {
1369
1416
  this.Text = Text ?? globalThis.Text;
1370
1417
  this.Comment = Comment ?? globalThis.Comment;
1371
1418
  this.cacheConstNodes = true;
1419
+ this.currentTag = null;
1372
1420
  }
1373
1421
  isInsideMacro(name) {
1374
1422
  return this.frame.macroName === name || this.parent?.isInsideMacro(name);
@@ -1424,7 +1472,7 @@ class ParseContext {
1424
1472
  getNodeForId(id) {
1425
1473
  return this.nodes[id] ?? null;
1426
1474
  }
1427
- onAttributes(_attrs, _wrapperAttrs, _textChild, _isMacroCall) {}
1475
+ onAttributes(_attrs, _wrapperAttrs, _textChild, _isMacroCall, _tag) {}
1428
1476
  onParseIssue(kind, info) {
1429
1477
  console.warn(`tutuca parse issue [${kind}]`, info);
1430
1478
  }
@@ -1570,7 +1618,7 @@ class ParseCtxClassSetCollector extends ParseContext {
1570
1618
  v.classes = this.classes;
1571
1619
  return v;
1572
1620
  }
1573
- onAttributes(attrs, _wrapperAttrs, _textChild) {
1621
+ onAttributes(attrs, _wrapperAttrs, _textChild, _isMacroCall, _tag) {
1574
1622
  if (Array.isArray(attrs.items)) {
1575
1623
  for (const attr of attrs.items) {
1576
1624
  if (attr.name !== "class") {
@@ -1624,6 +1672,7 @@ var FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
1624
1672
  var COMPUTED_VAL_NOT_DEFINED = "COMPUTED_VAL_NOT_DEFINED";
1625
1673
  var COMPUTED_NOT_REFERENCED = "COMPUTED_NOT_REFERENCED";
1626
1674
  var DUPLICATE_ATTR_DEFINITION = "DUPLICATE_ATTR_DEFINITION";
1675
+ var IF_NO_BRANCH_SET = "IF_NO_BRANCH_SET";
1627
1676
  var UNKNOWN_REQUEST_NAME = "UNKNOWN_REQUEST_NAME";
1628
1677
  var UNKNOWN_COMPONENT_NAME = "UNKNOWN_COMPONENT_NAME";
1629
1678
  var UNKNOWN_MACRO_ARG = "UNKNOWN_MACRO_ARG";
@@ -1631,10 +1680,12 @@ var UNKNOWN_DIRECTIVE = "UNKNOWN_DIRECTIVE";
1631
1680
  var UNKNOWN_X_OP = "UNKNOWN_X_OP";
1632
1681
  var UNKNOWN_X_ATTR = "UNKNOWN_X_ATTR";
1633
1682
  var MAYBE_DROP_AT_PREFIX = "MAYBE_DROP_AT_PREFIX";
1683
+ var BAD_VALUE = "BAD_VALUE";
1634
1684
  var PARSE_ISSUE_KIND_TO_LINT_ID = {
1635
1685
  "unknown-directive": UNKNOWN_DIRECTIVE,
1636
1686
  "unknown-x-op": UNKNOWN_X_OP,
1637
- "unknown-x-attr": UNKNOWN_X_ATTR
1687
+ "unknown-x-attr": UNKNOWN_X_ATTR,
1688
+ "bad-value": BAD_VALUE
1638
1689
  };
1639
1690
  var X_KNOWN_OP_NAMES = new Set([
1640
1691
  "slot",
@@ -1704,7 +1755,8 @@ function checkMacroCallArgs(lx, view, Comp) {
1704
1755
  if (!(argName in defaults)) {
1705
1756
  lx.error(UNKNOWN_MACRO_ARG, {
1706
1757
  name: argName,
1707
- macroName: macroNode.name
1758
+ macroName: macroNode.name,
1759
+ tag: `x:${macroNode.name}`
1708
1760
  });
1709
1761
  }
1710
1762
  }
@@ -1759,7 +1811,13 @@ function checkEventModifiers(lx, view) {
1759
1811
  const modWrappers = MOD_WRAPPERS_BY_EVENT[name] ?? NO_WRAPPERS;
1760
1812
  for (const modifier of modifiers) {
1761
1813
  if (modWrappers[modifier] === undefined) {
1762
- lx.error(UNKNOWN_EVENT_MODIFIER, { name, modifier, handler, event });
1814
+ lx.error(UNKNOWN_EVENT_MODIFIER, {
1815
+ name,
1816
+ modifier,
1817
+ handler,
1818
+ event,
1819
+ originAttr: `@on.${name}+${modifiers.join("+")}`
1820
+ });
1763
1821
  }
1764
1822
  }
1765
1823
  }
@@ -1794,10 +1852,16 @@ function checkKnownHandlerNames(lx, view, Comp, referencedAlters, referencedComp
1794
1852
  const { fields } = Class.getMetaClass();
1795
1853
  for (const event of view.ctx.events) {
1796
1854
  for (const handler of event.handlers) {
1797
- const { args } = handler.handlerCall;
1855
+ const { args, handlerVal } = handler.handlerCall;
1856
+ const handlerName = handlerVal?.name;
1857
+ const eventName = handler.name;
1858
+ const errCtx = {
1859
+ eventName,
1860
+ handlerName,
1861
+ originAttr: `@on.${eventName}`
1862
+ };
1798
1863
  for (let i = 0;i < args.length; i++) {
1799
- const arg = args[i];
1800
- checkConsistentAttrVal(lx, arg, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
1864
+ checkConsistentAttrVal(lx, args[i], fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { ...errCtx, argIndex: i });
1801
1865
  }
1802
1866
  }
1803
1867
  }
@@ -1812,19 +1876,25 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
1812
1876
  for (const handler of event.handlers) {
1813
1877
  const { handlerVal } = handler.handlerCall;
1814
1878
  const hvName = handlerVal?.constructor.name;
1879
+ const eventName = handler.name;
1880
+ const originAttr = `@on.${eventName}`;
1815
1881
  if (hvName === "InputHandlerNameVal") {
1816
1882
  referencedInputs?.add(handlerVal.name);
1817
1883
  if (input[handlerVal.name] === undefined) {
1818
1884
  lx.error(INPUT_HANDLER_NOT_IMPLEMENTED, {
1819
1885
  name: handlerVal.name,
1820
1886
  handler,
1821
- event
1887
+ event,
1888
+ eventName,
1889
+ originAttr
1822
1890
  });
1823
1891
  if (proto[handlerVal.name] !== undefined) {
1824
1892
  lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, {
1825
1893
  name: handlerVal.name,
1826
1894
  handler,
1827
- event
1895
+ event,
1896
+ eventName,
1897
+ originAttr
1828
1898
  });
1829
1899
  }
1830
1900
  }
@@ -1834,13 +1904,17 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
1834
1904
  lx.error(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, {
1835
1905
  name: handlerVal.name,
1836
1906
  handler,
1837
- event
1907
+ event,
1908
+ eventName,
1909
+ originAttr
1838
1910
  });
1839
1911
  if (input[handlerVal.name] !== undefined) {
1840
1912
  lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, {
1841
1913
  name: handlerVal.name,
1842
1914
  handler,
1843
- event
1915
+ event,
1916
+ eventName,
1917
+ originAttr
1844
1918
  });
1845
1919
  }
1846
1920
  }
@@ -1850,47 +1924,77 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
1850
1924
  });
1851
1925
  }
1852
1926
  }
1853
- function checkConsistentAttrVal(lx, val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal = false) {
1927
+ function checkConsistentAttrVal(lx, val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal = false, errCtx = null) {
1854
1928
  const valName = val?.constructor.name;
1855
1929
  if (valName === "FieldVal" || valName === "RawFieldVal") {
1856
1930
  const { name } = val;
1857
1931
  if (fields[name] === undefined && proto[name] === undefined) {
1858
- lx.error(FIELD_VAL_NOT_DEFINED, { val, name });
1932
+ lx.error(FIELD_VAL_NOT_DEFINED, { ...errCtx, val, name });
1859
1933
  }
1860
1934
  } else if (valName === "ComputedVal") {
1861
1935
  const { name } = val;
1862
1936
  referencedComputed?.add(name);
1863
1937
  if (computed[name] === undefined) {
1864
- lx.error(COMPUTED_VAL_NOT_DEFINED, { val, name });
1938
+ lx.error(COMPUTED_VAL_NOT_DEFINED, { ...errCtx, val, name });
1865
1939
  }
1866
1940
  } else if (valName === "SeqAccessVal") {
1867
- checkConsistentAttrVal(lx, val.seqVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal);
1868
- checkConsistentAttrVal(lx, val.keyVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal);
1941
+ checkConsistentAttrVal(lx, val.seqVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal, errCtx);
1942
+ checkConsistentAttrVal(lx, val.keyVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal, errCtx);
1869
1943
  } else if (valName === "RequestVal") {
1870
1944
  if (scope.lookupRequest(val.name) === null) {
1871
- lx.error(UNKNOWN_REQUEST_NAME, { name: val.name });
1945
+ lx.error(UNKNOWN_REQUEST_NAME, { ...errCtx, name: val.name });
1872
1946
  }
1873
1947
  } else if (valName === "TypeVal") {
1874
1948
  if (scope.lookupComponent(val.name) === null) {
1875
- lx.error(UNKNOWN_COMPONENT_NAME, { name: val.name });
1949
+ lx.error(UNKNOWN_COMPONENT_NAME, { ...errCtx, name: val.name });
1876
1950
  }
1877
1951
  } else if (valName === "NameVal") {
1878
1952
  if (!skipNameVal && !isKnownHandlerName(val.name)) {
1879
- lx.error(UNKNOWN_HANDLER_ARG_NAME, { name: val.name });
1953
+ lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name });
1880
1954
  }
1881
1955
  } else if (valName === "StrTplVal") {
1882
1956
  for (const subVal of val.vals) {
1883
- checkConsistentAttrVal(lx, subVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal);
1957
+ checkConsistentAttrVal(lx, subVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, skipNameVal, errCtx);
1884
1958
  }
1885
1959
  } else if (valName === "AlterHandlerNameVal") {
1886
1960
  referencedAlters?.add(val.name);
1887
1961
  if (alter[val.name] === undefined) {
1888
- lx.error(ALT_HANDLER_NOT_DEFINED, { name: val.name });
1962
+ lx.error(ALT_HANDLER_NOT_DEFINED, { ...errCtx, name: val.name });
1889
1963
  }
1890
1964
  } else if (valName !== "ConstVal" && valName !== "BindVal" && valName !== "DynVal") {
1891
1965
  console.log(val);
1892
1966
  }
1893
1967
  }
1968
+ var NODE_KIND_TO_CTX = {
1969
+ RenderTextNode: { originAttr: "<x text>" },
1970
+ RenderNode: { originAttr: "<x render>" },
1971
+ RenderItNode: { originAttr: "<x render-it>" },
1972
+ RenderEachNode: { originAttr: "<x render-each>" },
1973
+ ShowNode: { originAttr: "<x show>" },
1974
+ HideNode: { originAttr: "<x hide>" },
1975
+ PushViewNameNode: { originAttr: "<x push-view>" }
1976
+ };
1977
+ function nodeCtxForNode(nodeKind) {
1978
+ return NODE_KIND_TO_CTX[nodeKind] ?? null;
1979
+ }
1980
+ function attrSourceLabel(attr) {
1981
+ const cn = attr.constructor.name;
1982
+ if (cn === "ConstAttr")
1983
+ return "literal";
1984
+ if (cn === "IfAttr")
1985
+ return `@if.${attr.name}`;
1986
+ if (cn === "RawHtmlAttr")
1987
+ return "@dangerouslysetinnerhtml";
1988
+ return `:${attr.name}`;
1989
+ }
1990
+ function attrOriginAttr(attr) {
1991
+ const cn = attr.constructor.name;
1992
+ if (cn === "IfAttr")
1993
+ return `@if.${attr.name}`;
1994
+ if (cn === "RawHtmlAttr")
1995
+ return "@dangerouslysetinnerhtml";
1996
+ return `:${attr.name}`;
1997
+ }
1894
1998
  function checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed) {
1895
1999
  const { computed, scope, views, alter, Class } = Comp;
1896
2000
  const { prototype: proto } = Class;
@@ -1898,25 +2002,39 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed) {
1898
2002
  for (const viewName in views) {
1899
2003
  lx.push({ viewName }, () => {
1900
2004
  const view = views[viewName];
1901
- for (const attr of view.ctx.attrs) {
1902
- const { attrs, wrapperAttrs, textChild, isMacroCall } = attr;
2005
+ for (const entry of view.ctx.attrs) {
2006
+ const { attrs, wrapperAttrs, textChild, isMacroCall, tag } = entry;
1903
2007
  if (attrs?.constructor.name === "DynAttrs") {
1904
- const seenNames = new Set;
1905
- for (const attr2 of attrs.items) {
1906
- const name = attr2?.name;
2008
+ const sourcesByName = new Map;
2009
+ for (const attr of attrs.items) {
2010
+ const name = attr?.name;
1907
2011
  if (name !== undefined && name !== "data-eid") {
1908
- if (seenNames.has(name)) {
1909
- lx.error(DUPLICATE_ATTR_DEFINITION, { name });
1910
- } else {
1911
- seenNames.add(name);
1912
- }
2012
+ const sources = sourcesByName.get(name);
2013
+ const label = attrSourceLabel(attr);
2014
+ if (sources)
2015
+ sources.push(label);
2016
+ else
2017
+ sourcesByName.set(name, [label]);
1913
2018
  }
1914
- if (attr2?.constructor.name === "IfAttr") {
1915
- for (const subVal of [attr2.condVal, attr2.thenVal, attr2.elseVal]) {
1916
- checkConsistentAttrVal(lx, subVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, isMacroCall);
2019
+ if (attr?.constructor.name === "IfAttr") {
2020
+ if (!attr.anyBranchIsSet) {
2021
+ lx.error(IF_NO_BRANCH_SET, { attr: attr.name, tag });
1917
2022
  }
1918
- } else if (attr2?.val !== undefined) {
1919
- checkConsistentAttrVal(lx, attr2.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, isMacroCall);
2023
+ const branches = [
2024
+ ["@if", attr.condVal],
2025
+ ["@then", attr.thenVal],
2026
+ ["@else", attr.elseVal]
2027
+ ];
2028
+ for (const [branch, subVal] of branches) {
2029
+ checkConsistentAttrVal(lx, subVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, isMacroCall, { tag, originAttr: `@if.${attr.name}`, branch });
2030
+ }
2031
+ } else if (attr?.val !== undefined) {
2032
+ checkConsistentAttrVal(lx, attr.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, isMacroCall, { tag, originAttr: attrOriginAttr(attr) });
2033
+ }
2034
+ }
2035
+ for (const [name, sources] of sourcesByName) {
2036
+ if (sources.length > 1) {
2037
+ lx.error(DUPLICATE_ATTR_DEFINITION, { name, sources, tag });
1920
2038
  }
1921
2039
  }
1922
2040
  }
@@ -1924,30 +2042,35 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedComputed) {
1924
2042
  for (const w of wrapperAttrs) {
1925
2043
  if (w.name === "each") {
1926
2044
  if (w.whenVal)
1927
- checkConsistentAttrVal(lx, w.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2045
+ checkConsistentAttrVal(lx, w.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { tag, originAttr: "@when" });
1928
2046
  if (w.enrichWithVal)
1929
- checkConsistentAttrVal(lx, w.enrichWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2047
+ checkConsistentAttrVal(lx, w.enrichWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { tag, originAttr: "@enrich-with" });
1930
2048
  if (w.loopWithVal)
1931
- checkConsistentAttrVal(lx, w.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
1932
- } else if (w.name !== "scope") {
1933
- checkConsistentAttrVal(lx, w.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2049
+ checkConsistentAttrVal(lx, w.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { tag, originAttr: "@loop-with" });
2050
+ } else {
2051
+ const originAttr = w.name === "scope" ? "@enrich-with" : `@${w.name}`;
2052
+ checkConsistentAttrVal(lx, w.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { tag, originAttr });
1934
2053
  }
1935
2054
  }
1936
2055
  }
1937
2056
  if (textChild) {
1938
- checkConsistentAttrVal(lx, textChild, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2057
+ checkConsistentAttrVal(lx, textChild, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { tag, originAttr: "@text" });
1939
2058
  }
1940
2059
  }
1941
2060
  for (const node of view.ctx.nodes) {
2061
+ const nodeKind = node.constructor.name;
2062
+ if (nodeKind === "ScopeNode")
2063
+ continue;
2064
+ const baseCtx = nodeCtxForNode(nodeKind);
1942
2065
  if (node.val) {
1943
- checkConsistentAttrVal(lx, node.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2066
+ checkConsistentAttrVal(lx, node.val, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, baseCtx);
1944
2067
  }
1945
- if (node.constructor.name === "RenderEachNode") {
2068
+ if (nodeKind === "RenderEachNode") {
1946
2069
  const iter = node.iterInfo;
1947
2070
  if (iter.whenVal)
1948
- checkConsistentAttrVal(lx, iter.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2071
+ checkConsistentAttrVal(lx, iter.whenVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { originAttr: "<x render-each when>" });
1949
2072
  if (iter.loopWithVal)
1950
- checkConsistentAttrVal(lx, iter.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed);
2073
+ checkConsistentAttrVal(lx, iter.loopWithVal, fields, proto, computed, scope, alter, referencedAlters, referencedComputed, false, { originAttr: "<x render-each loop-with>" });
1951
2074
  }
1952
2075
  }
1953
2076
  });
@@ -2009,59 +2132,123 @@ class LintParseContext extends ParseContext {
2009
2132
  this.attrs = [];
2010
2133
  this.parseIssues = [];
2011
2134
  }
2012
- onAttributes(attrs, wrapperAttrs, textChild, isMacroCall = false) {
2013
- this.attrs.push({ attrs, wrapperAttrs, textChild, isMacroCall });
2135
+ onAttributes(attrs, wrapperAttrs, textChild, isMacroCall = false, tag = null) {
2136
+ this.attrs.push({ attrs, wrapperAttrs, textChild, isMacroCall, tag });
2014
2137
  }
2015
2138
  onParseIssue(kind, info) {
2016
- this.parseIssues.push({ kind, info });
2139
+ const tag = this.currentTag;
2140
+ this.parseIssues.push({ kind, info: tag && info.tag === undefined ? { ...info, tag } : info });
2017
2141
  }
2018
2142
  }
2019
2143
 
2020
2144
  // tools/format/lint.js
2145
+ function badValueMessage(info) {
2146
+ const v = JSON.stringify(info.value);
2147
+ switch (info.role) {
2148
+ case "attr":
2149
+ return `Cannot parse value ${v} for attribute ':${info.attr}'`;
2150
+ case "directive":
2151
+ return `Cannot parse value ${v} for directive '@${info.directive}'`;
2152
+ case "if":
2153
+ return `Cannot parse condition ${v} for '@if.${info.attr}'`;
2154
+ case "x-op":
2155
+ return `Cannot parse value ${v} for <x ${info.op}>`;
2156
+ case "handler-name":
2157
+ return `Cannot parse handler name ${v}`;
2158
+ case "handler-arg":
2159
+ return `Cannot parse handler argument ${v}`;
2160
+ case "macro-var":
2161
+ return `Macro variable '^${info.name}' is not defined`;
2162
+ default:
2163
+ return `Cannot parse value ${v}`;
2164
+ }
2165
+ }
2166
+ function tagDisplay(tag) {
2167
+ return tag ? String(tag).toLowerCase() : null;
2168
+ }
2169
+ function fmtTagSuffix(info) {
2170
+ const t = tagDisplay(info?.tag);
2171
+ return t && t !== "x" ? ` on <${t}>` : "";
2172
+ }
2173
+ function fmtOriginSuffix(info) {
2174
+ if (!info)
2175
+ return "";
2176
+ const parts = [];
2177
+ if (info.originAttr) {
2178
+ const branch = info.branch ? `[${info.branch}]` : "";
2179
+ parts.push(`in ${info.originAttr}${branch}`);
2180
+ }
2181
+ if (info.handlerName) {
2182
+ parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
2183
+ }
2184
+ const t = tagDisplay(info.tag);
2185
+ if (t && t !== "x")
2186
+ parts.push(`on <${t}>`);
2187
+ return parts.length ? ` (${parts.join(", ")})` : "";
2188
+ }
2189
+ function fmtEventSuffix(info) {
2190
+ if (info?.originAttr)
2191
+ return ` in ${info.originAttr}`;
2192
+ if (info?.eventName)
2193
+ return ` in @on.${info.eventName}`;
2194
+ return "";
2195
+ }
2021
2196
  function lintIdToMessage(id, info) {
2022
2197
  switch (id) {
2023
2198
  case "RENDER_IT_OUTSIDE_OF_LOOP":
2024
2199
  return "render-it used outside of a loop";
2025
- case "UNKNOWN_EVENT_MODIFIER":
2026
- return `Unknown modifier '${info.modifier}' on '${info.name}' event`;
2200
+ case "UNKNOWN_EVENT_MODIFIER": {
2201
+ const mods = info.handler?.modifiers ?? [info.modifier];
2202
+ const written = `@on.${info.name}+${mods.join("+")}`;
2203
+ return `Unknown modifier '+${info.modifier}' in '${written}'`;
2204
+ }
2027
2205
  case "UNKNOWN_HANDLER_ARG_NAME":
2028
- return `Unknown handler argument '${info.name}'`;
2206
+ return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
2029
2207
  case "INPUT_HANDLER_NOT_IMPLEMENTED":
2030
- return `Input handler '${info.name}' is not implemented`;
2208
+ return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
2031
2209
  case "INPUT_HANDLER_NOT_REFERENCED":
2032
2210
  return `Input handler '${info.name}' is defined but not referenced`;
2033
2211
  case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
2034
- return `Method '.${info.name}' is not implemented`;
2212
+ return `Method '.${info.name}' is not implemented${fmtEventSuffix(info)}`;
2035
2213
  case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
2036
- return `'${info.name}' exists as input handler — use without '.' prefix`;
2214
+ return `'${info.name}' exists as input handler — use without '.' prefix${fmtEventSuffix(info)}`;
2037
2215
  case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
2038
- return `'${info.name}' exists as method — use with '.' prefix`;
2216
+ return `'${info.name}' exists as method — use with '.' prefix${fmtEventSuffix(info)}`;
2039
2217
  case "FIELD_VAL_NOT_DEFINED":
2040
- return `Field '.${info.name}' is not defined`;
2218
+ return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
2041
2219
  case "COMPUTED_VAL_NOT_DEFINED":
2042
- return `Computed property '$${info.name}' is not defined`;
2220
+ return `Computed property '$${info.name}' is not defined${fmtOriginSuffix(info)}`;
2043
2221
  case "COMPUTED_NOT_REFERENCED":
2044
2222
  return `Computed property '$${info.name}' is defined but not referenced`;
2045
- case "DUPLICATE_ATTR_DEFINITION":
2046
- return `Attribute '${info.name}' is defined more than once`;
2223
+ case "DUPLICATE_ATTR_DEFINITION": {
2224
+ const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
2225
+ const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
2226
+ return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
2227
+ }
2228
+ case "IF_NO_BRANCH_SET":
2229
+ return `'@if.${info.attr}' has no '@then' or '@else' branch — at least one must be set${fmtTagSuffix(info)}`;
2047
2230
  case "UNKNOWN_REQUEST_NAME":
2048
- return `Unknown request '!${info.name}'`;
2231
+ return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
2049
2232
  case "UNKNOWN_COMPONENT_NAME":
2050
- return `Unknown component '${info.name}'`;
2233
+ return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
2051
2234
  case "ALT_HANDLER_NOT_DEFINED":
2052
- return `Alter handler '${info.name}' is not defined`;
2235
+ return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
2053
2236
  case "ALT_HANDLER_NOT_REFERENCED":
2054
2237
  return `Alter handler '${info.name}' is defined but not referenced`;
2055
2238
  case "UNKNOWN_MACRO_ARG":
2056
2239
  return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
2057
2240
  case "UNKNOWN_DIRECTIVE":
2058
- return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'`;
2241
+ return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
2059
2242
  case "UNKNOWN_X_OP":
2060
- return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'`;
2243
+ return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
2061
2244
  case "UNKNOWN_X_ATTR":
2062
- return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>`;
2063
- case "MAYBE_DROP_AT_PREFIX":
2064
- return `Did you mean '${info.suggestion}'? Drop the '@' prefix on <x>.`;
2245
+ return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
2246
+ case "MAYBE_DROP_AT_PREFIX": {
2247
+ const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
2248
+ return `Did you mean '${info.suggestion}'? Drop the '@' from '${written}' on <x>.`;
2249
+ }
2250
+ case "BAD_VALUE":
2251
+ return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
2065
2252
  case "LINT_ERROR":
2066
2253
  return info.message;
2067
2254
  default:
@@ -7819,7 +8006,7 @@ initCollectionConversions();
7819
8006
  var { version } = pkg;
7820
8007
 
7821
8008
  // src/oo.js
7822
- var BAD_VALUE = Symbol("BadValue");
8009
+ var BAD_VALUE2 = Symbol("BadValue");
7823
8010
  var nullCoercer = (v) => v;
7824
8011
 
7825
8012
  class Field {
@@ -7866,8 +8053,8 @@ class Field {
7866
8053
  const setName = `set${uname}`;
7867
8054
  const that = this;
7868
8055
  proto[setName] = function(v) {
7869
- const v1 = that.coerceOr(v, BAD_VALUE);
7870
- if (v1 === BAD_VALUE) {
8056
+ const v1 = that.coerceOr(v, BAD_VALUE2);
8057
+ if (v1 === BAD_VALUE2) {
7871
8058
  console.warn("invalid value", v);
7872
8059
  return this;
7873
8060
  }
@@ -8741,12 +8928,13 @@ class LintClassCollectorCtx extends ParseCtxClassSetCollector {
8741
8928
  v.parseIssues = this.parseIssues;
8742
8929
  return v;
8743
8930
  }
8744
- onAttributes(attrs, wrapperAttrs, textChild, isMacroCall = false) {
8745
- super.onAttributes(attrs, wrapperAttrs, textChild, isMacroCall);
8746
- this.attrs.push({ attrs, wrapperAttrs, textChild, isMacroCall });
8931
+ onAttributes(attrs, wrapperAttrs, textChild, isMacroCall = false, tag = null) {
8932
+ super.onAttributes(attrs, wrapperAttrs, textChild, isMacroCall, tag);
8933
+ this.attrs.push({ attrs, wrapperAttrs, textChild, isMacroCall, tag });
8747
8934
  }
8748
8935
  onParseIssue(kind, info) {
8749
- this.parseIssues.push({ kind, info });
8936
+ const tag = this.currentTag;
8937
+ this.parseIssues.push({ kind, info: tag && info.tag === undefined ? { ...info, tag } : info });
8750
8938
  }
8751
8939
  }
8752
8940
  export {
@@ -8834,12 +9022,14 @@ export {
8834
9022
  INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
8835
9023
  INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
8836
9024
  Map2 as IMap,
9025
+ IF_NO_BRANCH_SET,
8837
9026
  FIELD_VAL_NOT_DEFINED,
8838
9027
  FIELD_CLASS,
8839
9028
  DUPLICATE_ATTR_DEFINITION,
8840
9029
  Collection,
8841
9030
  COMPUTED_VAL_NOT_DEFINED,
8842
9031
  COMPUTED_NOT_REFERENCED,
9032
+ BAD_VALUE,
8843
9033
  ALT_HANDLER_NOT_REFERENCED,
8844
9034
  ALT_HANDLER_NOT_DEFINED
8845
9035
  };