tutuca 0.9.48 → 0.9.49

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.
@@ -6211,10 +6211,7 @@ class LinterCtx {
6211
6211
  a.quote = quote;
6212
6212
  const dup = this.currentAttrs.find((x) => x.name === a.name);
6213
6213
  if (dup) {
6214
- this.report(HTML_DUPLICATE_ATTRIBUTE, LEVEL_WARN, a.nameStart, {
6215
- name: a.name,
6216
- firstAt: dup.nameStart
6217
- });
6214
+ this.report(HTML_DUPLICATE_ATTRIBUTE, LEVEL_WARN, a.nameStart, { name: a.name, firstAt: dup.nameStart }, { kind: "remove", what: `the duplicate '${a.name}' attribute` });
6218
6215
  } else {
6219
6216
  this.currentAttrs.push(a);
6220
6217
  }
@@ -6264,12 +6261,12 @@ class LinterCtx {
6264
6261
  }
6265
6262
  }
6266
6263
  if (firstNonWs < 0) {
6267
- this.report(HTML_SELF_CLOSING_END_TAG, LEVEL_WARN, start, { tag: name });
6264
+ this.report(HTML_SELF_CLOSING_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "rewrite", from: `</${name}/>`, to: `</${name}>` });
6268
6265
  } else {
6269
- this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
6266
+ this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "remove", what: `attributes from </${name}>` });
6270
6267
  }
6271
6268
  } else {
6272
- this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
6269
+ this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "remove", what: `attributes from </${name}>` });
6273
6270
  }
6274
6271
  }
6275
6272
  this.handleEndTag(name, start);
@@ -6302,14 +6299,15 @@ class LinterCtx {
6302
6299
  return this.currentNamespace() !== NS.html;
6303
6300
  }
6304
6301
  onend() {}
6305
- report(id, level, offset, info) {
6302
+ report(id, level, offset, info, suggestion = null) {
6306
6303
  this.findingCount++;
6307
6304
  const { line, column } = offsetToLineCol(this.lineStarts, offset);
6308
6305
  this.onFinding({
6309
6306
  id,
6310
6307
  level,
6311
6308
  info,
6312
- location: { start: offset, end: offset + (info.tag?.length ?? 0), line, column }
6309
+ location: { start: offset, end: offset + (info.tag?.length ?? 0), line, column },
6310
+ suggestion
6313
6311
  });
6314
6312
  }
6315
6313
  currentNode() {
@@ -6418,17 +6416,11 @@ class LinterCtx {
6418
6416
  const ns = this.currentNamespace();
6419
6417
  if (ns === NS.html) {
6420
6418
  if (raw !== name) {
6421
- this.report(HTML_TAG_NAME_HAS_UPPERCASE, LEVEL_ERROR, start, {
6422
- raw,
6423
- lowercased: name
6424
- });
6419
+ this.report(HTML_TAG_NAME_HAS_UPPERCASE, LEVEL_ERROR, start, { raw, lowercased: name }, { kind: "rewrite", from: `<${raw}>`, to: `<${name}>` });
6425
6420
  }
6426
6421
  } else if (ns === NS.svg) {
6427
6422
  if (raw !== raw.toLowerCase() && !this.svgCamelElements.has(raw)) {
6428
- this.report(HTML_SVG_TAG_WILL_LOWERCASE, LEVEL_ERROR, start, {
6429
- raw,
6430
- lowercased: name
6431
- });
6423
+ this.report(HTML_SVG_TAG_WILL_LOWERCASE, LEVEL_ERROR, start, { raw, lowercased: name }, { kind: "rewrite", from: `<${raw}>`, to: `<${name}>` });
6432
6424
  }
6433
6425
  }
6434
6426
  const targetNs = ns !== NS.html ? ns : name === "svg" ? NS.svg : name === "math" ? NS.math : NS.html;
@@ -6436,20 +6428,14 @@ class LinterCtx {
6436
6428
  for (const a of this.currentAttrs) {
6437
6429
  const canonical = SVG_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
6438
6430
  if (canonical && a.rawName !== canonical) {
6439
- this.report(HTML_SVG_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
6440
- raw: a.rawName,
6441
- canonical
6442
- });
6431
+ this.report(HTML_SVG_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, { raw: a.rawName, canonical }, { kind: "rewrite", from: a.rawName, to: canonical });
6443
6432
  }
6444
6433
  }
6445
6434
  } else if (targetNs === NS.math) {
6446
6435
  for (const a of this.currentAttrs) {
6447
6436
  const canonical = MATHML_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
6448
6437
  if (canonical && a.rawName !== canonical) {
6449
- this.report(HTML_MATHML_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
6450
- raw: a.rawName,
6451
- canonical
6452
- });
6438
+ this.report(HTML_MATHML_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, { raw: a.rawName, canonical }, { kind: "rewrite", from: a.rawName, to: canonical });
6453
6439
  }
6454
6440
  }
6455
6441
  }
@@ -6564,10 +6550,7 @@ class LinterCtx {
6564
6550
  }
6565
6551
  if (name === "form") {
6566
6552
  if (this.openElementsHas("form") && !this.openElementsHas("template")) {
6567
- this.report(HTML_DUPLICATE_FORM, LEVEL_ERROR, start, {
6568
- tag: raw,
6569
- mode: this.insertionMode
6570
- });
6553
+ this.report(HTML_DUPLICATE_FORM, LEVEL_ERROR, start, { tag: raw, mode: this.insertionMode }, { kind: "remove", what: "the inner <form>" });
6571
6554
  return;
6572
6555
  }
6573
6556
  if (this.hasInButtonScope("p"))
@@ -7129,10 +7112,7 @@ class LinterCtx {
7129
7112
  }
7130
7113
  }
7131
7114
  if (VOID_ELEMENTS.has(name)) {
7132
- this.report(HTML_VOID_ELEMENT_HAS_CLOSE_TAG, LEVEL_WARN, start, {
7133
- tag: name,
7134
- mode: this.insertionMode
7135
- });
7115
+ this.report(HTML_VOID_ELEMENT_HAS_CLOSE_TAG, LEVEL_WARN, start, { tag: name, mode: this.insertionMode }, { kind: "remove", what: `the </${name}>` });
7136
7116
  return;
7137
7117
  }
7138
7118
  if (FORMATTING_ELEMENTS.has(name)) {
@@ -7240,7 +7220,75 @@ function offsetToLineCol(lineStarts, offset) {
7240
7220
  return { line: lo + 1, column: offset - lineStarts[lo] + 1 };
7241
7221
  }
7242
7222
 
7223
+ // tools/core/util/closest-name.js
7224
+ function editDistance(a, b) {
7225
+ if (a === b)
7226
+ return 0;
7227
+ const la = a.length;
7228
+ const lb = b.length;
7229
+ if (la === 0)
7230
+ return lb;
7231
+ if (lb === 0)
7232
+ return la;
7233
+ const prev2 = new Array(lb + 1);
7234
+ const prev1 = new Array(lb + 1);
7235
+ const curr = new Array(lb + 1);
7236
+ for (let j = 0;j <= lb; j++)
7237
+ prev1[j] = j;
7238
+ for (let i = 1;i <= la; i++) {
7239
+ curr[0] = i;
7240
+ const ca = a.charCodeAt(i - 1);
7241
+ for (let j = 1;j <= lb; j++) {
7242
+ const cb = b.charCodeAt(j - 1);
7243
+ const cost = ca === cb ? 0 : 1;
7244
+ let v = Math.min(curr[j - 1] + 1, prev1[j] + 1, prev1[j - 1] + cost);
7245
+ if (i > 1 && j > 1 && ca === b.charCodeAt(j - 2) && a.charCodeAt(i - 2) === cb) {
7246
+ v = Math.min(v, prev2[j - 2] + 1);
7247
+ }
7248
+ curr[j] = v;
7249
+ }
7250
+ for (let j = 0;j <= lb; j++) {
7251
+ prev2[j] = prev1[j];
7252
+ prev1[j] = curr[j];
7253
+ }
7254
+ }
7255
+ return prev1[lb];
7256
+ }
7257
+ function closestName(name, candidates, maxDistance = 2) {
7258
+ if (!name)
7259
+ return null;
7260
+ const lower = name.toLowerCase();
7261
+ let best = null;
7262
+ let bestDist = maxDistance + 1;
7263
+ for (const cand of candidates) {
7264
+ if (cand === name)
7265
+ return null;
7266
+ if (cand.toLowerCase() === lower)
7267
+ return cand;
7268
+ const d = editDistance(name, cand);
7269
+ if (d < bestDist) {
7270
+ best = cand;
7271
+ bestDist = d;
7272
+ }
7273
+ }
7274
+ return bestDist <= maxDistance ? best : null;
7275
+ }
7276
+
7243
7277
  // tools/core/lint-check.js
7278
+ var KNOWN_DIRECTIVE_NAMES = new Set([
7279
+ "dangerouslysetinnerhtml",
7280
+ "slot",
7281
+ "push-view",
7282
+ "text",
7283
+ "show",
7284
+ "hide",
7285
+ "each",
7286
+ "enrich-with",
7287
+ "when",
7288
+ "loop-with",
7289
+ "then",
7290
+ "else"
7291
+ ]);
7244
7292
  var ALT_HANDLER_NOT_DEFINED = "ALT_HANDLER_NOT_DEFINED";
7245
7293
  var ALT_HANDLER_NOT_REFERENCED = "ALT_HANDLER_NOT_REFERENCED";
7246
7294
  var RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
@@ -7285,6 +7333,39 @@ var AT_PREFIX_HINT_KNOWN_BY_KIND = {
7285
7333
  var LEVEL_WARN2 = "warn";
7286
7334
  var LEVEL_ERROR2 = "error";
7287
7335
  var LEVEL_HINT = "hint";
7336
+ var PARSE_ISSUE_KIND_TO_KNOWN_NAMES = {
7337
+ "unknown-directive": KNOWN_DIRECTIVE_NAMES,
7338
+ "unknown-x-op": X_KNOWN_OP_NAMES,
7339
+ "unknown-x-attr": X_KNOWN_ATTR_NAMES
7340
+ };
7341
+ function collectProtoMethodNames(proto) {
7342
+ const out = [];
7343
+ let cursor = proto;
7344
+ while (cursor && cursor !== Object.prototype) {
7345
+ for (const key of Object.getOwnPropertyNames(cursor)) {
7346
+ if (key === "constructor")
7347
+ continue;
7348
+ out.push(key);
7349
+ }
7350
+ cursor = Object.getPrototypeOf(cursor);
7351
+ }
7352
+ return out;
7353
+ }
7354
+ function scopeKeysAlong(scope, mapKey) {
7355
+ const out = [];
7356
+ for (let cursor = scope;cursor; cursor = cursor.parent) {
7357
+ const map = cursor[mapKey];
7358
+ if (!map)
7359
+ continue;
7360
+ for (const key of Object.keys(map))
7361
+ out.push(key);
7362
+ }
7363
+ return out;
7364
+ }
7365
+ function replaceNameSuggestion(name, candidates) {
7366
+ const close = closestName(name, candidates);
7367
+ return close ? { kind: "replace-name", from: name, to: close } : null;
7368
+ }
7288
7369
  function checkComponent(Comp, lx = new LintContext) {
7289
7370
  return lx.push({ componentName: Comp.name }, () => {
7290
7371
  const referencedAlters = new Set;
@@ -7314,7 +7395,7 @@ var HTML_LINT_OPTS = {
7314
7395
  function checkHtmlStructure(lx, view) {
7315
7396
  if (typeof view.rawView !== "string" || !view.rawView)
7316
7397
  return;
7317
- lintHtml(view.rawView, (f) => lx.report(f.id, { ...f.info, location: f.location }, f.level), HTML_LINT_OPTS);
7398
+ lintHtml(view.rawView, (f) => lx.report(f.id, { ...f.info, location: f.location }, f.level, f.suggestion ?? null), HTML_LINT_OPTS);
7318
7399
  }
7319
7400
  function checkParseIssues(lx, view) {
7320
7401
  const issues = view.ctx.parseIssues;
@@ -7324,12 +7405,19 @@ function checkParseIssues(lx, view) {
7324
7405
  const id = PARSE_ISSUE_KIND_TO_LINT_ID[kind];
7325
7406
  if (!id)
7326
7407
  continue;
7327
- lx.error(id, info);
7328
- const known = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
7329
- if (known && info.name?.startsWith("@")) {
7330
- const suggestion = info.name.slice(1);
7331
- if (known.has(suggestion))
7332
- lx.hint(MAYBE_DROP_AT_PREFIX, { ...info, suggestion });
7408
+ const atPrefixKnown = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
7409
+ const isAtPrefixedTypo = atPrefixKnown && info.name?.startsWith("@") && atPrefixKnown.has(info.name.slice(1));
7410
+ let suggestion = null;
7411
+ if (isAtPrefixedTypo) {
7412
+ suggestion = { kind: "drop-prefix", from: info.name, to: info.name.slice(1) };
7413
+ } else {
7414
+ const candidates = PARSE_ISSUE_KIND_TO_KNOWN_NAMES[kind];
7415
+ if (candidates)
7416
+ suggestion = replaceNameSuggestion(info.name, candidates);
7417
+ }
7418
+ lx.error(id, info, suggestion);
7419
+ if (isAtPrefixedTypo) {
7420
+ lx.hint(MAYBE_DROP_AT_PREFIX, { ...info, suggestion: info.name.slice(1) }, { kind: "drop-prefix", from: info.name, to: info.name.slice(1) });
7333
7421
  }
7334
7422
  }
7335
7423
  }
@@ -7342,11 +7430,7 @@ function checkMacroCallArgs(lx, view, Comp) {
7342
7430
  const { defaults } = macro;
7343
7431
  for (const argName in macroNode.attrs) {
7344
7432
  if (!(argName in defaults)) {
7345
- lx.error(UNKNOWN_MACRO_ARG, {
7346
- name: argName,
7347
- macroName: macroNode.name,
7348
- tag: `x:${macroNode.name}`
7349
- });
7433
+ lx.error(UNKNOWN_MACRO_ARG, { name: argName, macroName: macroNode.name, tag: `x:${macroNode.name}` }, replaceNameSuggestion(argName, Object.keys(defaults)));
7350
7434
  }
7351
7435
  }
7352
7436
  }
@@ -7368,8 +7452,9 @@ function walkForRenderIt(lx, node, loopDepth) {
7368
7452
  return;
7369
7453
  switch (node.constructor.name) {
7370
7454
  case "RenderItNode":
7371
- if (loopDepth === 0)
7372
- lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node });
7455
+ if (loopDepth === 0) {
7456
+ lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node }, { kind: "wrap", from: "<x render-it>", to: "<x render-each>" });
7457
+ }
7373
7458
  return;
7374
7459
  case "EachNode":
7375
7460
  walkForRenderIt(lx, node.node, loopDepth + 1);
@@ -7400,13 +7485,14 @@ function checkEventModifiers(lx, view) {
7400
7485
  const modWrappers = MOD_WRAPPERS_BY_EVENT[name] ?? NO_WRAPPERS;
7401
7486
  for (const modifier of modifiers) {
7402
7487
  if (modWrappers[modifier] === undefined) {
7488
+ const close = closestName(modifier, Object.keys(modWrappers));
7403
7489
  lx.error(UNKNOWN_EVENT_MODIFIER, {
7404
7490
  name,
7405
7491
  modifier,
7406
7492
  handler,
7407
7493
  event,
7408
7494
  originAttr: `@on.${name}+${modifiers.join("+")}`
7409
- });
7495
+ }, close ? { kind: "replace-name", from: `+${modifier}`, to: `+${close}` } : null);
7410
7496
  }
7411
7497
  }
7412
7498
  }
@@ -7472,42 +7558,22 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
7472
7558
  const originAttr = `@on.${eventName}`;
7473
7559
  if (hvName === "InputHandlerNameVal") {
7474
7560
  referencedInputs?.add(handlerVal.name);
7475
- if (input[handlerVal.name] === undefined) {
7476
- lx.error(INPUT_HANDLER_NOT_IMPLEMENTED, {
7477
- name: handlerVal.name,
7478
- handler,
7479
- event,
7480
- eventName,
7481
- originAttr
7482
- });
7483
- if (proto[handlerVal.name] !== undefined) {
7484
- lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, {
7485
- name: handlerVal.name,
7486
- handler,
7487
- event,
7488
- eventName,
7489
- originAttr
7490
- });
7561
+ const { name } = handlerVal;
7562
+ if (input[name] === undefined) {
7563
+ const isMethodFix = proto[name] !== undefined;
7564
+ lx.error(INPUT_HANDLER_NOT_IMPLEMENTED, { name, handler, event, eventName, originAttr }, isMethodFix ? { kind: "add-prefix", from: name, to: `.${name}` } : replaceNameSuggestion(name, Object.keys(input)));
7565
+ if (isMethodFix) {
7566
+ lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, { name, handler, event, eventName, originAttr }, { kind: "add-prefix", from: name, to: `.${name}` });
7491
7567
  }
7492
7568
  }
7493
7569
  } else if (hvName === "RawFieldVal") {
7494
7570
  referencedInputs?.add(handlerVal.name);
7495
- if (proto[handlerVal.name] === undefined) {
7496
- lx.error(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, {
7497
- name: handlerVal.name,
7498
- handler,
7499
- event,
7500
- eventName,
7501
- originAttr
7502
- });
7503
- if (input[handlerVal.name] !== undefined) {
7504
- lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, {
7505
- name: handlerVal.name,
7506
- handler,
7507
- event,
7508
- eventName,
7509
- originAttr
7510
- });
7571
+ const { name } = handlerVal;
7572
+ if (proto[name] === undefined) {
7573
+ const isInputFix = input[name] !== undefined;
7574
+ lx.error(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, { name, handler, event, eventName, originAttr }, isInputFix ? { kind: "drop-prefix", from: `.${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto)));
7575
+ if (isInputFix) {
7576
+ lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, { name, handler, event, eventName, originAttr }, { kind: "drop-prefix", from: `.${name}`, to: name });
7511
7577
  }
7512
7578
  }
7513
7579
  }
@@ -7521,22 +7587,23 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
7521
7587
  if (valName === "FieldVal" || valName === "RawFieldVal") {
7522
7588
  const { name } = val;
7523
7589
  if (fields[name] === undefined && proto[name] === undefined) {
7524
- lx.error(FIELD_VAL_NOT_DEFINED, { ...errCtx, val, name });
7590
+ const candidates = [...Object.keys(fields), ...collectProtoMethodNames(proto)];
7591
+ lx.error(FIELD_VAL_NOT_DEFINED, { ...errCtx, val, name }, replaceNameSuggestion(name, candidates));
7525
7592
  }
7526
7593
  } else if (valName === "SeqAccessVal") {
7527
7594
  checkConsistentAttrVal(lx, val.seqVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
7528
7595
  checkConsistentAttrVal(lx, val.keyVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
7529
7596
  } else if (valName === "RequestVal") {
7530
7597
  if (scope.lookupRequest(val.name) === null) {
7531
- lx.error(UNKNOWN_REQUEST_NAME, { ...errCtx, name: val.name });
7598
+ lx.error(UNKNOWN_REQUEST_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, scopeKeysAlong(scope, "reqsByName")));
7532
7599
  }
7533
7600
  } else if (valName === "TypeVal") {
7534
7601
  if (scope.lookupComponent(val.name) === null) {
7535
- lx.error(UNKNOWN_COMPONENT_NAME, { ...errCtx, name: val.name });
7602
+ lx.error(UNKNOWN_COMPONENT_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, scopeKeysAlong(scope, "byName")));
7536
7603
  }
7537
7604
  } else if (valName === "NameVal") {
7538
7605
  if (!skipNameVal && !isKnownHandlerName(val.name)) {
7539
- lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name });
7606
+ lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, KNOWN_HANDLER_NAMES));
7540
7607
  }
7541
7608
  } else if (valName === "StrTplVal") {
7542
7609
  for (const subVal of val.vals) {
@@ -7545,7 +7612,7 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
7545
7612
  } else if (valName === "AlterHandlerNameVal") {
7546
7613
  referencedAlters?.add(val.name);
7547
7614
  if (alter[val.name] === undefined) {
7548
- lx.error(ALT_HANDLER_NOT_DEFINED, { ...errCtx, name: val.name });
7615
+ lx.error(ALT_HANDLER_NOT_DEFINED, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, Object.keys(alter)));
7549
7616
  }
7550
7617
  } else if (valName !== "ConstVal" && valName !== "BindVal" && valName !== "DynVal") {
7551
7618
  console.log(val);
@@ -7691,17 +7758,17 @@ class LintContext {
7691
7758
  this.frame = prev;
7692
7759
  }
7693
7760
  }
7694
- error(id, info) {
7695
- this.report(id, info, LEVEL_ERROR2);
7761
+ error(id, info, suggestion = null) {
7762
+ this.report(id, info, LEVEL_ERROR2, suggestion);
7696
7763
  }
7697
- warn(id, info) {
7698
- this.report(id, info, LEVEL_WARN2);
7764
+ warn(id, info, suggestion = null) {
7765
+ this.report(id, info, LEVEL_WARN2, suggestion);
7699
7766
  }
7700
- hint(id, info) {
7701
- this.report(id, info, LEVEL_HINT);
7767
+ hint(id, info, suggestion = null) {
7768
+ this.report(id, info, LEVEL_HINT, suggestion);
7702
7769
  }
7703
- report(id, info = {}, level = LEVEL_ERROR2) {
7704
- this.reports.push({ id, info, level, context: { ...this.frame } });
7770
+ report(id, info = {}, level = LEVEL_ERROR2, suggestion = null) {
7771
+ this.reports.push({ id, info, level, context: { ...this.frame }, suggestion });
7705
7772
  }
7706
7773
  }
7707
7774
 
@@ -7720,61 +7787,6 @@ class LintParseContext extends ParseContext {
7720
7787
  }
7721
7788
  }
7722
7789
 
7723
- // tools/core/test-console.js
7724
- var PASS = "color: #0a0; font-weight: bold";
7725
- var FAIL = "color: #c00; font-weight: bold";
7726
- var SKIP = "color: #888";
7727
- var DIM = "color: #888";
7728
- var RESET = "color: inherit; font-weight: normal";
7729
- function reportTestNode(node) {
7730
- if (node.children) {
7731
- const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
7732
- console.group(label);
7733
- for (const child of node.children)
7734
- reportTestNode(child);
7735
- console.groupEnd();
7736
- return;
7737
- }
7738
- const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
7739
- if (node.status === "pass") {
7740
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
7741
- } else if (node.status === "skip") {
7742
- console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
7743
- } else {
7744
- console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
7745
- console.error(node.error?.message ?? "(no error message)");
7746
- if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
7747
- console.log("expected:", node.error.expected);
7748
- console.log("actual: ", node.error.actual);
7749
- }
7750
- if (node.error?.stack)
7751
- console.log(node.error.stack);
7752
- console.groupEnd();
7753
- }
7754
- }
7755
- function reportTestReportToConsole(report) {
7756
- for (const m of report.modules) {
7757
- const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
7758
- console.group(label);
7759
- if (m.suites.length === 0) {
7760
- console.log("(no tests)");
7761
- } else {
7762
- for (const s of m.suites)
7763
- reportTestNode(s);
7764
- }
7765
- const c = m.counts;
7766
- const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
7767
- if (c.fail > 0)
7768
- console.error(`%c${summary}`, FAIL);
7769
- else if (c.total === 0)
7770
- console.log(`%c${summary}`, DIM);
7771
- else
7772
- console.log(`%c${summary}`, PASS);
7773
- console.groupEnd();
7774
- }
7775
- return report;
7776
- }
7777
-
7778
7790
  // tools/core/results.js
7779
7791
  class ModuleInfo {
7780
7792
  constructor({ path = null, present = new Set, counts = {}, warnings = [] }) {
@@ -7812,11 +7824,12 @@ class ComponentDocs {
7812
7824
  }
7813
7825
 
7814
7826
  class LintFinding {
7815
- constructor({ id, level, info, context = {} }) {
7827
+ constructor({ id, level, info, context = {}, suggestion = null }) {
7816
7828
  this.id = id;
7817
7829
  this.level = level;
7818
7830
  this.info = info;
7819
7831
  this.context = context;
7832
+ this.suggestion = suggestion;
7820
7833
  }
7821
7834
  }
7822
7835
 
@@ -8146,6 +8159,61 @@ async function runTests({
8146
8159
  });
8147
8160
  }
8148
8161
 
8162
+ // tools/core/test-console.js
8163
+ var PASS = "color: #0a0; font-weight: bold";
8164
+ var FAIL = "color: #c00; font-weight: bold";
8165
+ var SKIP = "color: #888";
8166
+ var DIM = "color: #888";
8167
+ var RESET = "color: inherit; font-weight: normal";
8168
+ function reportTestNode(node) {
8169
+ if (node.children) {
8170
+ const label = node.componentName ? `${node.title} [${node.componentName}]` : node.title;
8171
+ console.group(label);
8172
+ for (const child of node.children)
8173
+ reportTestNode(child);
8174
+ console.groupEnd();
8175
+ return;
8176
+ }
8177
+ const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
8178
+ if (node.status === "pass") {
8179
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
8180
+ } else if (node.status === "skip") {
8181
+ console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
8182
+ } else {
8183
+ console.group(`%c✗%c ${node.title}%c${dur}`, FAIL, RESET, DIM);
8184
+ console.error(node.error?.message ?? "(no error message)");
8185
+ if (node.error && (("expected" in node.error) || ("actual" in node.error))) {
8186
+ console.log("expected:", node.error.expected);
8187
+ console.log("actual: ", node.error.actual);
8188
+ }
8189
+ if (node.error?.stack)
8190
+ console.log(node.error.stack);
8191
+ console.groupEnd();
8192
+ }
8193
+ }
8194
+ function reportTestReportToConsole(report) {
8195
+ for (const m of report.modules) {
8196
+ const label = `tutuca tests${m.path ? ` — ${m.path}` : ""}`;
8197
+ console.group(label);
8198
+ if (m.suites.length === 0) {
8199
+ console.log("(no tests)");
8200
+ } else {
8201
+ for (const s of m.suites)
8202
+ reportTestNode(s);
8203
+ }
8204
+ const c = m.counts;
8205
+ const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
8206
+ if (c.fail > 0)
8207
+ console.error(`%c${summary}`, FAIL);
8208
+ else if (c.total === 0)
8209
+ console.log(`%c${summary}`, DIM);
8210
+ else
8211
+ console.log(`%c${summary}`, PASS);
8212
+ console.groupEnd();
8213
+ }
8214
+ return report;
8215
+ }
8216
+
8149
8217
  // tools/format/lint.js
8150
8218
  function badValueMessage(info) {
8151
8219
  const v = JSON.stringify(info.value);
@@ -8201,7 +8269,7 @@ function fmtEventSuffix(info) {
8201
8269
  function lintIdToMessage(id, info) {
8202
8270
  switch (id) {
8203
8271
  case "RENDER_IT_OUTSIDE_OF_LOOP":
8204
- return "render-it used outside of a loop";
8272
+ return "<x render-it> used outside of a loop";
8205
8273
  case "UNKNOWN_EVENT_MODIFIER": {
8206
8274
  const mods = info.handler?.modifiers ?? [info.modifier];
8207
8275
  const written = `@on.${info.name}+${mods.join("+")}`;
@@ -8212,13 +8280,13 @@ function lintIdToMessage(id, info) {
8212
8280
  case "INPUT_HANDLER_NOT_IMPLEMENTED":
8213
8281
  return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
8214
8282
  case "INPUT_HANDLER_NOT_REFERENCED":
8215
- return `Input handler '${info.name}' is defined but not referenced`;
8283
+ return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
8216
8284
  case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
8217
8285
  return `Method '.${info.name}' is not implemented${fmtEventSuffix(info)}`;
8218
8286
  case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
8219
- return `'${info.name}' exists as input handler use without '.' prefix${fmtEventSuffix(info)}`;
8287
+ return `'.${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
8220
8288
  case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
8221
- return `'${info.name}' exists as method use with '.' prefix${fmtEventSuffix(info)}`;
8289
+ return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
8222
8290
  case "FIELD_VAL_NOT_DEFINED":
8223
8291
  return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
8224
8292
  case "DUPLICATE_ATTR_DEFINITION": {
@@ -8227,7 +8295,7 @@ function lintIdToMessage(id, info) {
8227
8295
  return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
8228
8296
  }
8229
8297
  case "IF_NO_BRANCH_SET":
8230
- return `'@if.${info.attr}' has no '@then' or '@else' branch — at least one must be set${fmtTagSuffix(info)}`;
8298
+ return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
8231
8299
  case "UNKNOWN_REQUEST_NAME":
8232
8300
  return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
8233
8301
  case "UNKNOWN_COMPONENT_NAME":
@@ -8235,7 +8303,7 @@ function lintIdToMessage(id, info) {
8235
8303
  case "ALT_HANDLER_NOT_DEFINED":
8236
8304
  return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
8237
8305
  case "ALT_HANDLER_NOT_REFERENCED":
8238
- return `Alter handler '${info.name}' is defined but not referenced`;
8306
+ return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
8239
8307
  case "UNKNOWN_MACRO_ARG":
8240
8308
  return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
8241
8309
  case "UNKNOWN_DIRECTIVE":
@@ -8246,7 +8314,7 @@ function lintIdToMessage(id, info) {
8246
8314
  return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
8247
8315
  case "MAYBE_DROP_AT_PREFIX": {
8248
8316
  const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
8249
- return `Did you mean '${info.suggestion}'? Drop the '@' from '${written}' on <x>.`;
8317
+ return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
8250
8318
  }
8251
8319
  case "BAD_VALUE":
8252
8320
  return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
@@ -8255,7 +8323,7 @@ function lintIdToMessage(id, info) {
8255
8323
  case "HTML_SVG_TAG_WILL_LOWERCASE":
8256
8324
  return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
8257
8325
  case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
8258
- return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} (action: ${info.action})${fmtLocationSuffix(info)}`;
8326
+ return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
8259
8327
  case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
8260
8328
  return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
8261
8329
  case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
@@ -8292,11 +8360,47 @@ function lintIdToMessage(id, info) {
8292
8360
  return id;
8293
8361
  }
8294
8362
  }
8363
+ function suggestionToMessage(suggestion) {
8364
+ if (!suggestion)
8365
+ return null;
8366
+ switch (suggestion.kind) {
8367
+ case "replace-name":
8368
+ return `did you mean '${suggestion.to}'?`;
8369
+ case "drop-prefix":
8370
+ return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
8371
+ case "add-prefix":
8372
+ return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
8373
+ case "remove":
8374
+ return `remove ${suggestion.what}`;
8375
+ case "rewrite":
8376
+ return `use '${suggestion.to}' instead of '${suggestion.from}'`;
8377
+ case "wrap":
8378
+ return `wrap it in ${suggestion.to}`;
8379
+ default:
8380
+ return null;
8381
+ }
8382
+ }
8295
8383
  function fmtLocationSuffix(info) {
8296
8384
  const loc = info?.location;
8297
8385
  if (!loc)
8298
8386
  return "";
8299
- return ` at L${loc.line}:C${loc.column}`;
8387
+ return ` at line ${loc.line}, col ${loc.column}`;
8388
+ }
8389
+ function htmlActionPhrase(action, tag, parent) {
8390
+ switch (action) {
8391
+ case "ignored":
8392
+ return `the parser will drop this <${tag}>`;
8393
+ case "drop":
8394
+ return `the parser will drop this <${tag}>`;
8395
+ case "auto-close-implicit":
8396
+ return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
8397
+ case "foster-parent":
8398
+ return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
8399
+ case "foreign-breakout":
8400
+ return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
8401
+ default:
8402
+ return `parser action: ${action}`;
8403
+ }
8300
8404
  }
8301
8405
 
8302
8406
  // src/components.js
@@ -14862,7 +14966,9 @@ function check(app) {
14862
14966
  console.group(Comp.name);
14863
14967
  for (const r of reports) {
14864
14968
  counts[r.level]++;
14865
- const line = `[${r.level}] ${lintIdToMessage(r.id, r.info)}`;
14969
+ const tail = suggestionToMessage(r.suggestion);
14970
+ const suffix = tail ? ` — ${tail}` : "";
14971
+ const line = `[${r.level}] ${lintIdToMessage(r.id, r.info)}${suffix}`;
14866
14972
  if (r.level === "error")
14867
14973
  console.error(line);
14868
14974
  else if (r.level === "warn")
@@ -14907,6 +15013,7 @@ export {
14907
15013
  update$1 as update,
14908
15014
  tutuca,
14909
15015
  test3 as test,
15016
+ suggestionToMessage,
14910
15017
  setIn$1 as setIn,
14911
15018
  set2 as set,
14912
15019
  runTests,