tutuca 0.9.50 → 0.9.52

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.
@@ -4317,7 +4317,15 @@ var init_value = __esm(() => {
4317
4317
  vals[i] = val;
4318
4318
  allConsts &&= val instanceof ConstVal;
4319
4319
  }
4320
- return allConsts ? new ConstVal(vals.map((v) => v.val).join("")) : new StrTplVal(vals);
4320
+ if (allConsts)
4321
+ return new ConstVal(vals.map((v) => v.val).join(""));
4322
+ let lo = 0;
4323
+ let hi = vals.length;
4324
+ while (lo < hi && vals[lo] instanceof ConstVal && vals[lo].val === "")
4325
+ lo++;
4326
+ while (hi > lo && vals[hi - 1] instanceof ConstVal && vals[hi - 1].val === "")
4327
+ hi--;
4328
+ return new StrTplVal(lo === 0 && hi === vals.length ? vals : vals.slice(lo, hi));
4321
4329
  }
4322
4330
  };
4323
4331
  NameVal = class NameVal extends VarVal {
@@ -5472,8 +5480,10 @@ class ComponentStack {
5472
5480
  function defaultOnStackEnter() {
5473
5481
  return null;
5474
5482
  }
5483
+ var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter on views dynamic fields methods statics", KNOWN_SPEC_KEYS;
5475
5484
  var init_components = __esm(() => {
5476
5485
  init_attribute();
5486
+ KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
5477
5487
  });
5478
5488
 
5479
5489
  // tools/core/html-tokenizer.js
@@ -7755,8 +7765,25 @@ function replaceNameSuggestion(name, candidates) {
7755
7765
  const close = closestName(name, candidates);
7756
7766
  return close ? { kind: "replace-name", from: name, to: close } : null;
7757
7767
  }
7758
- function checkComponent(Comp, lx = new LintContext) {
7768
+ function classifyBadValue(value) {
7769
+ if (typeof value !== "string")
7770
+ return null;
7771
+ const s = value.trim();
7772
+ if (s === "")
7773
+ return null;
7774
+ if (/\s\?\s.+\s:\s/.test(s))
7775
+ return "ternary";
7776
+ if (/===|!==|==|!=|<=|>=|\s<\s|\s>\s/.test(s))
7777
+ return "comparison";
7778
+ if (/&&|\|\|/.test(s))
7779
+ return "logical";
7780
+ if (/^\.[A-Za-z_]\w*\s+\S/.test(s))
7781
+ return "call-with-args";
7782
+ return null;
7783
+ }
7784
+ function checkComponent(Comp, lx = new LintContext, { wellKnownExtras = EMPTY_SET } = {}) {
7759
7785
  return lx.push({ componentName: Comp.name }, () => {
7786
+ checkUnknownSpecKeys(lx, Comp, wellKnownExtras);
7760
7787
  const referencedAlters = new Set;
7761
7788
  const referencedInputs = new Set;
7762
7789
  checkEventHandlersHaveImpls(lx, Comp, referencedInputs);
@@ -7790,6 +7817,13 @@ function checkParseIssues(lx, view) {
7790
7817
  const id = PARSE_ISSUE_KIND_TO_LINT_ID[kind];
7791
7818
  if (!id)
7792
7819
  continue;
7820
+ if (kind === "bad-value") {
7821
+ const detected = classifyBadValue(info.value);
7822
+ if (detected) {
7823
+ lx.error(UNSUPPORTED_EXPR_SYNTAX, { ...info, detected }, { kind: "rephrase", from: info.value, text: UNSUPPORTED_EXPR_GUIDANCE[detected] });
7824
+ continue;
7825
+ }
7826
+ }
7793
7827
  const atPrefixKnown = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
7794
7828
  const isAtPrefixedTypo = atPrefixKnown && info.name?.startsWith("@") && atPrefixKnown.has(info.name.slice(1));
7795
7829
  let suggestion = null;
@@ -7970,7 +8004,12 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
7970
8004
  lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, KNOWN_HANDLER_NAMES));
7971
8005
  }
7972
8006
  } else if (valName === "StrTplVal") {
7973
- for (const subVal of val.vals) {
8007
+ const vs = val.vals;
8008
+ if (vs.length === 1) {
8009
+ const simpler = String(vs[0]);
8010
+ lx.warn(REDUNDANT_TEMPLATE_STRING, { ...errCtx, simpler }, { kind: "rewrite", from: `{${simpler}}`, to: simpler });
8011
+ }
8012
+ for (const subVal of vs) {
7974
8013
  checkConsistentAttrVal(lx, subVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
7975
8014
  }
7976
8015
  } else if (valName === "AlterHandlerNameVal") {
@@ -8084,6 +8123,18 @@ function checkConsistentAttrs(lx, Comp, referencedAlters) {
8084
8123
  });
8085
8124
  }
8086
8125
  }
8126
+ function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
8127
+ const extra = Comp.extra;
8128
+ if (!extra)
8129
+ return;
8130
+ let candidates = null;
8131
+ for (const key of Object.keys(extra)) {
8132
+ if (wellKnownExtras.has(key))
8133
+ continue;
8134
+ candidates ??= [...KNOWN_COMPONENT_SPEC_KEYS, ...wellKnownExtras];
8135
+ lx.warn(UNKNOWN_COMPONENT_SPEC_KEY, { key }, replaceNameSuggestion(key, candidates));
8136
+ }
8137
+ }
8087
8138
  function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
8088
8139
  for (const name in Comp.alter) {
8089
8140
  if (!referencedAlters.has(name)) {
@@ -8126,10 +8177,12 @@ class LintContext {
8126
8177
  this.reports.push({ id, info, level, context: { ...this.frame }, suggestion });
8127
8178
  }
8128
8179
  }
8129
- var KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED = "ALT_HANDLER_NOT_DEFINED", ALT_HANDLER_NOT_REFERENCED = "ALT_HANDLER_NOT_REFERENCED", RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP", UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER", UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME", INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED", INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED", INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED", INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD", INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER", FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED", DUPLICATE_ATTR_DEFINITION = "DUPLICATE_ATTR_DEFINITION", IF_NO_BRANCH_SET = "IF_NO_BRANCH_SET", UNKNOWN_REQUEST_NAME = "UNKNOWN_REQUEST_NAME", UNKNOWN_COMPONENT_NAME = "UNKNOWN_COMPONENT_NAME", UNKNOWN_MACRO_ARG = "UNKNOWN_MACRO_ARG", UNKNOWN_DIRECTIVE = "UNKNOWN_DIRECTIVE", UNKNOWN_X_OP = "UNKNOWN_X_OP", UNKNOWN_X_ATTR = "UNKNOWN_X_ATTR", MAYBE_DROP_AT_PREFIX = "MAYBE_DROP_AT_PREFIX", BAD_VALUE = "BAD_VALUE", PARSE_ISSUE_KIND_TO_LINT_ID, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, AT_PREFIX_HINT_KNOWN_BY_KIND, LEVEL_WARN2 = "warn", LEVEL_ERROR2 = "error", LEVEL_HINT = "hint", PARSE_ISSUE_KIND_TO_KNOWN_NAMES, HTML_LINT_OPTS, NO_WRAPPERS, KNOWN_HANDLER_NAMES, NODE_KIND_TO_CTX, LintParseContext;
8180
+ var KNOWN_COMPONENT_SPEC_KEYS, EMPTY_SET, KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED = "ALT_HANDLER_NOT_DEFINED", ALT_HANDLER_NOT_REFERENCED = "ALT_HANDLER_NOT_REFERENCED", RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP", UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER", UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME", INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED", INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED", INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED", INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD", INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER", FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED", DUPLICATE_ATTR_DEFINITION = "DUPLICATE_ATTR_DEFINITION", IF_NO_BRANCH_SET = "IF_NO_BRANCH_SET", UNKNOWN_REQUEST_NAME = "UNKNOWN_REQUEST_NAME", UNKNOWN_COMPONENT_NAME = "UNKNOWN_COMPONENT_NAME", UNKNOWN_MACRO_ARG = "UNKNOWN_MACRO_ARG", UNKNOWN_DIRECTIVE = "UNKNOWN_DIRECTIVE", UNKNOWN_X_OP = "UNKNOWN_X_OP", UNKNOWN_X_ATTR = "UNKNOWN_X_ATTR", MAYBE_DROP_AT_PREFIX = "MAYBE_DROP_AT_PREFIX", BAD_VALUE = "BAD_VALUE", UNSUPPORTED_EXPR_SYNTAX = "UNSUPPORTED_EXPR_SYNTAX", REDUNDANT_TEMPLATE_STRING = "REDUNDANT_TEMPLATE_STRING", UNKNOWN_COMPONENT_SPEC_KEY = "UNKNOWN_COMPONENT_SPEC_KEY", PARSE_ISSUE_KIND_TO_LINT_ID, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, AT_PREFIX_HINT_KNOWN_BY_KIND, LEVEL_WARN2 = "warn", LEVEL_ERROR2 = "error", LEVEL_HINT = "hint", PARSE_ISSUE_KIND_TO_KNOWN_NAMES, UNSUPPORTED_EXPR_GUIDANCE, HTML_LINT_OPTS, NO_WRAPPERS, KNOWN_HANDLER_NAMES, NODE_KIND_TO_CTX, LintParseContext;
8130
8181
  var init_lint_check = __esm(() => {
8131
8182
  init_anode();
8132
8183
  init_htmllinter();
8184
+ KNOWN_COMPONENT_SPEC_KEYS = new Set("name view style commonStyle globalStyle input receive bubble response alter on views dynamic fields methods statics".split(" "));
8185
+ EMPTY_SET = new Set;
8133
8186
  KNOWN_DIRECTIVE_NAMES = new Set([
8134
8187
  "dangerouslysetinnerhtml",
8135
8188
  "slot",
@@ -8169,6 +8222,12 @@ var init_lint_check = __esm(() => {
8169
8222
  "unknown-x-op": X_KNOWN_OP_NAMES,
8170
8223
  "unknown-x-attr": X_KNOWN_ATTR_NAMES
8171
8224
  };
8225
+ UNSUPPORTED_EXPR_GUIDANCE = {
8226
+ ternary: "Ternary expressions aren't supported in dynamic attributes. Define a method or computed field on the component that returns the value, then reference it as '.methodName'.",
8227
+ comparison: "Comparisons aren't supported in dynamic attributes. Define a method like '.isFooSelected' that returns the boolean, then reference it as '.isFooSelected'.",
8228
+ logical: "Logical operators aren't supported in dynamic attributes. Combine the conditions in a method on the component and reference it as '.methodName'.",
8229
+ "call-with-args": "Method calls with arguments aren't supported here. Reference a no-arg method ('.methodName') and read what you need from component state, or split into per-case methods."
8230
+ };
8172
8231
  HTML_LINT_OPTS = {
8173
8232
  fragmentContext: "template",
8174
8233
  transparentTagPrefixes: ["x"]
@@ -8220,7 +8279,7 @@ var init_lint_check = __esm(() => {
8220
8279
  });
8221
8280
 
8222
8281
  // tools/core/lint.js
8223
- function lintComponents(normalized, { name = null, LintParseContextClass }) {
8282
+ function lintComponents(normalized, { name = null, LintParseContextClass, wellKnownExtras = new Set }) {
8224
8283
  const comps = normalized.components;
8225
8284
  const picked = name === null ? comps : comps.filter((c) => c.name === name);
8226
8285
  const stack = new ComponentStack;
@@ -8232,7 +8291,7 @@ function lintComponents(normalized, { name = null, LintParseContextClass }) {
8232
8291
  const results = [];
8233
8292
  for (const comp of picked) {
8234
8293
  comp.compile(LintParseContextClass);
8235
- const lx = checkComponent(comp);
8294
+ const lx = checkComponent(comp, undefined, { wellKnownExtras });
8236
8295
  results.push(new LintComponentResult({
8237
8296
  componentName: comp.name,
8238
8297
  findings: lx.reports.map((r) => new LintFinding(r))
@@ -11148,7 +11207,7 @@ var keyMapper = (v, k) => k, entryMapper = (v, k) => [k, v], not = (predicate) =
11148
11207
  const iter = SetCollection(value);
11149
11208
  assertNotInfinite(iter.size);
11150
11209
  iter.forEach((v) => set3.add(v));
11151
- }), SetImpl, makeSet = (map, ownerID) => new SetImpl(map, ownerID), EMPTY_SET, emptySet = () => EMPTY_SET || (EMPTY_SET = makeSet(emptyMap())), OrderedSet = (value) => value === undefined || value === null ? emptyOrderedSet() : isOrderedSet(value) ? value : emptyOrderedSet().withMutations((set3) => {
11210
+ }), SetImpl, makeSet = (map, ownerID) => new SetImpl(map, ownerID), EMPTY_SET2, emptySet = () => EMPTY_SET2 || (EMPTY_SET2 = makeSet(emptyMap())), OrderedSet = (value) => value === undefined || value === null ? emptyOrderedSet() : isOrderedSet(value) ? value : emptyOrderedSet().withMutations((set3) => {
11152
11211
  const iter = SetCollection(value);
11153
11212
  assertNotInfinite(iter.size);
11154
11213
  iter.forEach((v) => set3.add(v));
@@ -14466,6 +14525,28 @@ function makeFormatter(name, table) {
14466
14525
  }
14467
14526
 
14468
14527
  // tools/format/lint.js
14528
+ var UNSUPPORTED_EXPR_LABEL = {
14529
+ ternary: "ternary expression",
14530
+ comparison: "comparison",
14531
+ logical: "logical expression",
14532
+ "call-with-args": "method call with arguments"
14533
+ };
14534
+ function unsupportedExprMessage(info) {
14535
+ const v = JSON.stringify(info.value);
14536
+ const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
14537
+ switch (info.role) {
14538
+ case "attr":
14539
+ return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
14540
+ case "directive":
14541
+ return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
14542
+ case "if":
14543
+ return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
14544
+ case "x-op":
14545
+ return `Unsupported ${label} ${v} in <x ${info.op}>`;
14546
+ default:
14547
+ return `Unsupported ${label} ${v}`;
14548
+ }
14549
+ }
14469
14550
  function badValueMessage(info) {
14470
14551
  const v = JSON.stringify(info.value);
14471
14552
  switch (info.role) {
@@ -14569,6 +14650,12 @@ function lintIdToMessage(id, info) {
14569
14650
  }
14570
14651
  case "BAD_VALUE":
14571
14652
  return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
14653
+ case "UNSUPPORTED_EXPR_SYNTAX":
14654
+ return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
14655
+ case "REDUNDANT_TEMPLATE_STRING":
14656
+ return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
14657
+ case "UNKNOWN_COMPONENT_SPEC_KEY":
14658
+ return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
14572
14659
  case "HTML_TAG_NAME_HAS_UPPERCASE":
14573
14660
  return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
14574
14661
  case "HTML_SVG_TAG_WILL_LOWERCASE":
@@ -14627,6 +14714,8 @@ function suggestionToMessage(suggestion) {
14627
14714
  return `use '${suggestion.to}' instead of '${suggestion.from}'`;
14628
14715
  case "wrap":
14629
14716
  return `wrap it in ${suggestion.to}`;
14717
+ case "rephrase":
14718
+ return suggestion.text ?? null;
14630
14719
  default:
14631
14720
  return null;
14632
14721
  }
@@ -3792,7 +3792,15 @@ class StrTplVal extends VarVal {
3792
3792
  vals[i] = val;
3793
3793
  allConsts &&= val instanceof ConstVal;
3794
3794
  }
3795
- return allConsts ? new ConstVal(vals.map((v) => v.val).join("")) : new StrTplVal(vals);
3795
+ if (allConsts)
3796
+ return new ConstVal(vals.map((v) => v.val).join(""));
3797
+ let lo = 0;
3798
+ let hi = vals.length;
3799
+ while (lo < hi && vals[lo] instanceof ConstVal && vals[lo].val === "")
3800
+ lo++;
3801
+ while (hi > lo && vals[hi - 1] instanceof ConstVal && vals[hi - 1].val === "")
3802
+ hi--;
3803
+ return new StrTplVal(lo === 0 && hi === vals.length ? vals : vals.slice(lo, hi));
3796
3804
  }
3797
3805
  }
3798
3806
 
@@ -7275,6 +7283,8 @@ function closestName(name, candidates, maxDistance = 2) {
7275
7283
  }
7276
7284
 
7277
7285
  // tools/core/lint-check.js
7286
+ var KNOWN_COMPONENT_SPEC_KEYS = new Set("name view style commonStyle globalStyle input receive bubble response alter on views dynamic fields methods statics".split(" "));
7287
+ var EMPTY_SET = new Set;
7278
7288
  var KNOWN_DIRECTIVE_NAMES = new Set([
7279
7289
  "dangerouslysetinnerhtml",
7280
7290
  "slot",
@@ -7310,6 +7320,9 @@ var UNKNOWN_X_OP = "UNKNOWN_X_OP";
7310
7320
  var UNKNOWN_X_ATTR = "UNKNOWN_X_ATTR";
7311
7321
  var MAYBE_DROP_AT_PREFIX = "MAYBE_DROP_AT_PREFIX";
7312
7322
  var BAD_VALUE = "BAD_VALUE";
7323
+ var UNSUPPORTED_EXPR_SYNTAX = "UNSUPPORTED_EXPR_SYNTAX";
7324
+ var REDUNDANT_TEMPLATE_STRING = "REDUNDANT_TEMPLATE_STRING";
7325
+ var UNKNOWN_COMPONENT_SPEC_KEY = "UNKNOWN_COMPONENT_SPEC_KEY";
7313
7326
  var PARSE_ISSUE_KIND_TO_LINT_ID = {
7314
7327
  "unknown-directive": UNKNOWN_DIRECTIVE,
7315
7328
  "unknown-x-op": UNKNOWN_X_OP,
@@ -7366,8 +7379,31 @@ function replaceNameSuggestion(name, candidates) {
7366
7379
  const close = closestName(name, candidates);
7367
7380
  return close ? { kind: "replace-name", from: name, to: close } : null;
7368
7381
  }
7369
- function checkComponent(Comp, lx = new LintContext) {
7382
+ function classifyBadValue(value) {
7383
+ if (typeof value !== "string")
7384
+ return null;
7385
+ const s = value.trim();
7386
+ if (s === "")
7387
+ return null;
7388
+ if (/\s\?\s.+\s:\s/.test(s))
7389
+ return "ternary";
7390
+ if (/===|!==|==|!=|<=|>=|\s<\s|\s>\s/.test(s))
7391
+ return "comparison";
7392
+ if (/&&|\|\|/.test(s))
7393
+ return "logical";
7394
+ if (/^\.[A-Za-z_]\w*\s+\S/.test(s))
7395
+ return "call-with-args";
7396
+ return null;
7397
+ }
7398
+ var UNSUPPORTED_EXPR_GUIDANCE = {
7399
+ ternary: "Ternary expressions aren't supported in dynamic attributes. Define a method or computed field on the component that returns the value, then reference it as '.methodName'.",
7400
+ comparison: "Comparisons aren't supported in dynamic attributes. Define a method like '.isFooSelected' that returns the boolean, then reference it as '.isFooSelected'.",
7401
+ logical: "Logical operators aren't supported in dynamic attributes. Combine the conditions in a method on the component and reference it as '.methodName'.",
7402
+ "call-with-args": "Method calls with arguments aren't supported here. Reference a no-arg method ('.methodName') and read what you need from component state, or split into per-case methods."
7403
+ };
7404
+ function checkComponent(Comp, lx = new LintContext, { wellKnownExtras = EMPTY_SET } = {}) {
7370
7405
  return lx.push({ componentName: Comp.name }, () => {
7406
+ checkUnknownSpecKeys(lx, Comp, wellKnownExtras);
7371
7407
  const referencedAlters = new Set;
7372
7408
  const referencedInputs = new Set;
7373
7409
  checkEventHandlersHaveImpls(lx, Comp, referencedInputs);
@@ -7405,6 +7441,13 @@ function checkParseIssues(lx, view) {
7405
7441
  const id = PARSE_ISSUE_KIND_TO_LINT_ID[kind];
7406
7442
  if (!id)
7407
7443
  continue;
7444
+ if (kind === "bad-value") {
7445
+ const detected = classifyBadValue(info.value);
7446
+ if (detected) {
7447
+ lx.error(UNSUPPORTED_EXPR_SYNTAX, { ...info, detected }, { kind: "rephrase", from: info.value, text: UNSUPPORTED_EXPR_GUIDANCE[detected] });
7448
+ continue;
7449
+ }
7450
+ }
7408
7451
  const atPrefixKnown = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
7409
7452
  const isAtPrefixedTypo = atPrefixKnown && info.name?.startsWith("@") && atPrefixKnown.has(info.name.slice(1));
7410
7453
  let suggestion = null;
@@ -7606,7 +7649,12 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
7606
7649
  lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, KNOWN_HANDLER_NAMES));
7607
7650
  }
7608
7651
  } else if (valName === "StrTplVal") {
7609
- for (const subVal of val.vals) {
7652
+ const vs = val.vals;
7653
+ if (vs.length === 1) {
7654
+ const simpler = String(vs[0]);
7655
+ lx.warn(REDUNDANT_TEMPLATE_STRING, { ...errCtx, simpler }, { kind: "rewrite", from: `{${simpler}}`, to: simpler });
7656
+ }
7657
+ for (const subVal of vs) {
7610
7658
  checkConsistentAttrVal(lx, subVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
7611
7659
  }
7612
7660
  } else if (valName === "AlterHandlerNameVal") {
@@ -7729,6 +7777,18 @@ function checkConsistentAttrs(lx, Comp, referencedAlters) {
7729
7777
  });
7730
7778
  }
7731
7779
  }
7780
+ function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
7781
+ const extra = Comp.extra;
7782
+ if (!extra)
7783
+ return;
7784
+ let candidates = null;
7785
+ for (const key of Object.keys(extra)) {
7786
+ if (wellKnownExtras.has(key))
7787
+ continue;
7788
+ candidates ??= [...KNOWN_COMPONENT_SPEC_KEYS, ...wellKnownExtras];
7789
+ lx.warn(UNKNOWN_COMPONENT_SPEC_KEY, { key }, replaceNameSuggestion(key, candidates));
7790
+ }
7791
+ }
7732
7792
  function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
7733
7793
  for (const name in Comp.alter) {
7734
7794
  if (!referencedAlters.has(name)) {
@@ -8215,6 +8275,28 @@ function reportTestReportToConsole(report) {
8215
8275
  }
8216
8276
 
8217
8277
  // tools/format/lint.js
8278
+ var UNSUPPORTED_EXPR_LABEL = {
8279
+ ternary: "ternary expression",
8280
+ comparison: "comparison",
8281
+ logical: "logical expression",
8282
+ "call-with-args": "method call with arguments"
8283
+ };
8284
+ function unsupportedExprMessage(info) {
8285
+ const v = JSON.stringify(info.value);
8286
+ const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
8287
+ switch (info.role) {
8288
+ case "attr":
8289
+ return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
8290
+ case "directive":
8291
+ return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
8292
+ case "if":
8293
+ return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
8294
+ case "x-op":
8295
+ return `Unsupported ${label} ${v} in <x ${info.op}>`;
8296
+ default:
8297
+ return `Unsupported ${label} ${v}`;
8298
+ }
8299
+ }
8218
8300
  function badValueMessage(info) {
8219
8301
  const v = JSON.stringify(info.value);
8220
8302
  switch (info.role) {
@@ -8318,6 +8400,12 @@ function lintIdToMessage(id, info) {
8318
8400
  }
8319
8401
  case "BAD_VALUE":
8320
8402
  return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
8403
+ case "UNSUPPORTED_EXPR_SYNTAX":
8404
+ return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
8405
+ case "REDUNDANT_TEMPLATE_STRING":
8406
+ return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
8407
+ case "UNKNOWN_COMPONENT_SPEC_KEY":
8408
+ return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
8321
8409
  case "HTML_TAG_NAME_HAS_UPPERCASE":
8322
8410
  return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
8323
8411
  case "HTML_SVG_TAG_WILL_LOWERCASE":
@@ -8376,6 +8464,8 @@ function suggestionToMessage(suggestion) {
8376
8464
  return `use '${suggestion.to}' instead of '${suggestion.from}'`;
8377
8465
  case "wrap":
8378
8466
  return `wrap it in ${suggestion.to}`;
8467
+ case "rephrase":
8468
+ return suggestion.text ?? null;
8379
8469
  default:
8380
8470
  return null;
8381
8471
  }
@@ -8522,6 +8612,8 @@ class DynamicAlias extends Dynamic {
8522
8612
  }
8523
8613
  }
8524
8614
  var isString = (v) => typeof v === "string";
8615
+ var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter on views dynamic fields methods statics";
8616
+ var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
8525
8617
  var _compId = 0;
8526
8618
 
8527
8619
  class Component {
@@ -8546,6 +8638,10 @@ class Component {
8546
8638
  this._rawDynamic = o.dynamic ?? {};
8547
8639
  this.dynamic = {};
8548
8640
  this.scope = null;
8641
+ this.extra = {};
8642
+ for (const key of Object.keys(o))
8643
+ if (!KNOWN_SPEC_KEYS.has(key))
8644
+ this.extra[key] = o[key];
8549
8645
  }
8550
8646
  compile(ParseContext2) {
8551
8647
  for (const key in this._rawDynamic) {
@@ -13435,8 +13531,8 @@ class SetImpl extends SetCollectionImpl {
13435
13531
  }
13436
13532
  Set2.isSet = isSet;
13437
13533
  var makeSet = (map, ownerID) => new SetImpl(map, ownerID);
13438
- var EMPTY_SET;
13439
- var emptySet = () => EMPTY_SET || (EMPTY_SET = makeSet(emptyMap()));
13534
+ var EMPTY_SET2;
13535
+ var emptySet = () => EMPTY_SET2 || (EMPTY_SET2 = makeSet(emptyMap()));
13440
13536
  function filterByIters(set3, iters, shouldRemove) {
13441
13537
  if (iters.length === 0) {
13442
13538
  return set3;
@@ -14283,16 +14379,16 @@ function extendProtoTruthy(proto, name, uname) {
14283
14379
  return !this.get(name);
14284
14380
  };
14285
14381
  }
14286
- var EMPTY_SET2 = Set2();
14382
+ var EMPTY_SET3 = Set2();
14287
14383
  var isetCoercer = (v) => Array.isArray(v) ? Set2(v) : v instanceof Set ? Set2(v) : null;
14288
14384
 
14289
14385
  class FieldSet extends Field {
14290
- constructor(name, defaultValue = EMPTY_SET2) {
14386
+ constructor(name, defaultValue = EMPTY_SET3) {
14291
14387
  super("set", name, CHECK_TYPE_SET, isetCoercer, defaultValue);
14292
14388
  }
14293
14389
  extendProtoForType(proto, uname) {
14294
14390
  const { name } = this;
14295
- extendProtoSized(proto, name, uname, EMPTY_SET2);
14391
+ extendProtoSized(proto, name, uname, EMPTY_SET3);
14296
14392
  proto[`addIn${uname}`] = function(v) {
14297
14393
  return this.set(name, this.get(name).add(v));
14298
14394
  };
@@ -15062,6 +15158,7 @@ export {
15062
15158
  compileClassesToStyle,
15063
15159
  checkComponent,
15064
15160
  check,
15161
+ UNSUPPORTED_EXPR_SYNTAX,
15065
15162
  UNKNOWN_X_OP,
15066
15163
  UNKNOWN_X_ATTR,
15067
15164
  UNKNOWN_REQUEST_NAME,
@@ -15069,6 +15166,7 @@ export {
15069
15166
  UNKNOWN_HANDLER_ARG_NAME,
15070
15167
  UNKNOWN_EVENT_MODIFIER,
15071
15168
  UNKNOWN_DIRECTIVE,
15169
+ UNKNOWN_COMPONENT_SPEC_KEY,
15072
15170
  UNKNOWN_COMPONENT_NAME,
15073
15171
  TestResult,
15074
15172
  TestReport,
@@ -15085,6 +15183,7 @@ export {
15085
15183
  Record,
15086
15184
  Range,
15087
15185
  RENDER_IT_OUTSIDE_OF_LOOP,
15186
+ REDUNDANT_TEMPLATE_STRING,
15088
15187
  ParseContext,
15089
15188
  PairSorting,
15090
15189
  OrderedSet,