tutuca 0.9.47 → 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.
- package/dist/tutuca-cli.js +218 -109
- package/dist/tutuca-dev.js +270 -162
- package/dist/tutuca-dev.min.js +3 -3
- package/dist/tutuca-extra.js +4 -3
- package/dist/tutuca-extra.min.js +2 -2
- package/dist/tutuca.js +4 -3
- package/dist/tutuca.min.js +2 -2
- package/package.json +1 -1
- package/skill/tutuca/core.md +15 -0
package/dist/tutuca-dev.js
CHANGED
|
@@ -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
|
-
|
|
7328
|
-
const
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
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
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
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
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
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
|
-
|
|
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
|
|
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 `'
|
|
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}'
|
|
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 —
|
|
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
|
|
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 `
|
|
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}
|
|
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
|
|
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
|
|
@@ -8346,7 +8450,8 @@ class ComponentStack {
|
|
|
8346
8450
|
enter() {
|
|
8347
8451
|
return new ComponentStack(this.comps, this);
|
|
8348
8452
|
}
|
|
8349
|
-
registerComponents(comps,
|
|
8453
|
+
registerComponents(comps, opts) {
|
|
8454
|
+
const { aliases: aliases2 = {} } = opts ?? {};
|
|
8350
8455
|
for (let i = 0;i < comps.length; i++) {
|
|
8351
8456
|
const comp = comps[i];
|
|
8352
8457
|
comp.scope = this.enter();
|
|
@@ -9438,9 +9543,9 @@ class App {
|
|
|
9438
9543
|
sendAtRoot(name, args, opts) {
|
|
9439
9544
|
this.transactor.pushSend(new Path([]), name, args, opts);
|
|
9440
9545
|
}
|
|
9441
|
-
registerComponents(comps,
|
|
9546
|
+
registerComponents(comps, opts) {
|
|
9442
9547
|
const scope = this.compStack.enter();
|
|
9443
|
-
scope.registerComponents(comps,
|
|
9548
|
+
scope.registerComponents(comps, opts);
|
|
9444
9549
|
return scope;
|
|
9445
9550
|
}
|
|
9446
9551
|
_transactNextBatch(maxRunTimeMs = 10) {
|
|
@@ -14861,7 +14966,9 @@ function check(app) {
|
|
|
14861
14966
|
console.group(Comp.name);
|
|
14862
14967
|
for (const r of reports) {
|
|
14863
14968
|
counts[r.level]++;
|
|
14864
|
-
const
|
|
14969
|
+
const tail = suggestionToMessage(r.suggestion);
|
|
14970
|
+
const suffix = tail ? ` — ${tail}` : "";
|
|
14971
|
+
const line = `[${r.level}] ${lintIdToMessage(r.id, r.info)}${suffix}`;
|
|
14865
14972
|
if (r.level === "error")
|
|
14866
14973
|
console.error(line);
|
|
14867
14974
|
else if (r.level === "warn")
|
|
@@ -14906,6 +15013,7 @@ export {
|
|
|
14906
15013
|
update$1 as update,
|
|
14907
15014
|
tutuca,
|
|
14908
15015
|
test3 as test,
|
|
15016
|
+
suggestionToMessage,
|
|
14909
15017
|
setIn$1 as setIn,
|
|
14910
15018
|
set2 as set,
|
|
14911
15019
|
runTests,
|