tutuca 0.9.48 → 0.9.50
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/README.md +1 -3
- package/dist/tutuca-cli.js +214 -110
- package/dist/tutuca-dev.js +266 -159
- package/dist/tutuca-dev.min.js +2 -2
- package/package.json +3 -11
package/README.md
CHANGED
|
@@ -73,11 +73,9 @@ in the storybook shape `{ title, description?, items: [{ title, description?, va
|
|
|
73
73
|
|
|
74
74
|
```sh
|
|
75
75
|
npm install --save-dev tutuca
|
|
76
|
-
# prettier is optional, only needed for --pretty
|
|
77
|
-
npm install --save-dev prettier
|
|
78
76
|
```
|
|
79
77
|
|
|
80
|
-
The package exposes `tutuca` via `bin`, so `npx tutuca` (or a global `npm i -g tutuca`) just works. `jsdom`
|
|
78
|
+
The package exposes `tutuca` via `bin`, so `npx tutuca` (or a global `npm i -g tutuca`) just works. `jsdom` (needed by `render` and `lint`) and `prettier` (used by `--pretty`) ship as regular dependencies and are installed automatically.
|
|
81
79
|
|
|
82
80
|
### Commands
|
|
83
81
|
|
package/dist/tutuca-cli.js
CHANGED
|
@@ -3344,11 +3344,12 @@ class ComponentDocs {
|
|
|
3344
3344
|
}
|
|
3345
3345
|
|
|
3346
3346
|
class LintFinding {
|
|
3347
|
-
constructor({ id, level, info, context = {} }) {
|
|
3347
|
+
constructor({ id, level, info, context = {}, suggestion = null }) {
|
|
3348
3348
|
this.id = id;
|
|
3349
3349
|
this.level = level;
|
|
3350
3350
|
this.info = info;
|
|
3351
3351
|
this.context = context;
|
|
3352
|
+
this.suggestion = suggestion;
|
|
3352
3353
|
}
|
|
3353
3354
|
}
|
|
3354
3355
|
|
|
@@ -6633,10 +6634,7 @@ class LinterCtx {
|
|
|
6633
6634
|
a.quote = quote;
|
|
6634
6635
|
const dup = this.currentAttrs.find((x) => x.name === a.name);
|
|
6635
6636
|
if (dup) {
|
|
6636
|
-
this.report(HTML_DUPLICATE_ATTRIBUTE, LEVEL_WARN, a.nameStart, {
|
|
6637
|
-
name: a.name,
|
|
6638
|
-
firstAt: dup.nameStart
|
|
6639
|
-
});
|
|
6637
|
+
this.report(HTML_DUPLICATE_ATTRIBUTE, LEVEL_WARN, a.nameStart, { name: a.name, firstAt: dup.nameStart }, { kind: "remove", what: `the duplicate '${a.name}' attribute` });
|
|
6640
6638
|
} else {
|
|
6641
6639
|
this.currentAttrs.push(a);
|
|
6642
6640
|
}
|
|
@@ -6686,12 +6684,12 @@ class LinterCtx {
|
|
|
6686
6684
|
}
|
|
6687
6685
|
}
|
|
6688
6686
|
if (firstNonWs < 0) {
|
|
6689
|
-
this.report(HTML_SELF_CLOSING_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
6687
|
+
this.report(HTML_SELF_CLOSING_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "rewrite", from: `</${name}/>`, to: `</${name}>` });
|
|
6690
6688
|
} else {
|
|
6691
|
-
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
6689
|
+
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "remove", what: `attributes from </${name}>` });
|
|
6692
6690
|
}
|
|
6693
6691
|
} else {
|
|
6694
|
-
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
6692
|
+
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name }, { kind: "remove", what: `attributes from </${name}>` });
|
|
6695
6693
|
}
|
|
6696
6694
|
}
|
|
6697
6695
|
this.handleEndTag(name, start);
|
|
@@ -6724,14 +6722,15 @@ class LinterCtx {
|
|
|
6724
6722
|
return this.currentNamespace() !== NS.html;
|
|
6725
6723
|
}
|
|
6726
6724
|
onend() {}
|
|
6727
|
-
report(id, level, offset, info) {
|
|
6725
|
+
report(id, level, offset, info, suggestion = null) {
|
|
6728
6726
|
this.findingCount++;
|
|
6729
6727
|
const { line, column } = offsetToLineCol(this.lineStarts, offset);
|
|
6730
6728
|
this.onFinding({
|
|
6731
6729
|
id,
|
|
6732
6730
|
level,
|
|
6733
6731
|
info,
|
|
6734
|
-
location: { start: offset, end: offset + (info.tag?.length ?? 0), line, column }
|
|
6732
|
+
location: { start: offset, end: offset + (info.tag?.length ?? 0), line, column },
|
|
6733
|
+
suggestion
|
|
6735
6734
|
});
|
|
6736
6735
|
}
|
|
6737
6736
|
currentNode() {
|
|
@@ -6840,17 +6839,11 @@ class LinterCtx {
|
|
|
6840
6839
|
const ns = this.currentNamespace();
|
|
6841
6840
|
if (ns === NS.html) {
|
|
6842
6841
|
if (raw !== name) {
|
|
6843
|
-
this.report(HTML_TAG_NAME_HAS_UPPERCASE, LEVEL_ERROR, start, {
|
|
6844
|
-
raw,
|
|
6845
|
-
lowercased: name
|
|
6846
|
-
});
|
|
6842
|
+
this.report(HTML_TAG_NAME_HAS_UPPERCASE, LEVEL_ERROR, start, { raw, lowercased: name }, { kind: "rewrite", from: `<${raw}>`, to: `<${name}>` });
|
|
6847
6843
|
}
|
|
6848
6844
|
} else if (ns === NS.svg) {
|
|
6849
6845
|
if (raw !== raw.toLowerCase() && !this.svgCamelElements.has(raw)) {
|
|
6850
|
-
this.report(HTML_SVG_TAG_WILL_LOWERCASE, LEVEL_ERROR, start, {
|
|
6851
|
-
raw,
|
|
6852
|
-
lowercased: name
|
|
6853
|
-
});
|
|
6846
|
+
this.report(HTML_SVG_TAG_WILL_LOWERCASE, LEVEL_ERROR, start, { raw, lowercased: name }, { kind: "rewrite", from: `<${raw}>`, to: `<${name}>` });
|
|
6854
6847
|
}
|
|
6855
6848
|
}
|
|
6856
6849
|
const targetNs = ns !== NS.html ? ns : name === "svg" ? NS.svg : name === "math" ? NS.math : NS.html;
|
|
@@ -6858,20 +6851,14 @@ class LinterCtx {
|
|
|
6858
6851
|
for (const a of this.currentAttrs) {
|
|
6859
6852
|
const canonical = SVG_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
|
|
6860
6853
|
if (canonical && a.rawName !== canonical) {
|
|
6861
|
-
this.report(HTML_SVG_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
|
|
6862
|
-
raw: a.rawName,
|
|
6863
|
-
canonical
|
|
6864
|
-
});
|
|
6854
|
+
this.report(HTML_SVG_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, { raw: a.rawName, canonical }, { kind: "rewrite", from: a.rawName, to: canonical });
|
|
6865
6855
|
}
|
|
6866
6856
|
}
|
|
6867
6857
|
} else if (targetNs === NS.math) {
|
|
6868
6858
|
for (const a of this.currentAttrs) {
|
|
6869
6859
|
const canonical = MATHML_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
|
|
6870
6860
|
if (canonical && a.rawName !== canonical) {
|
|
6871
|
-
this.report(HTML_MATHML_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
|
|
6872
|
-
raw: a.rawName,
|
|
6873
|
-
canonical
|
|
6874
|
-
});
|
|
6861
|
+
this.report(HTML_MATHML_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, { raw: a.rawName, canonical }, { kind: "rewrite", from: a.rawName, to: canonical });
|
|
6875
6862
|
}
|
|
6876
6863
|
}
|
|
6877
6864
|
}
|
|
@@ -6986,10 +6973,7 @@ class LinterCtx {
|
|
|
6986
6973
|
}
|
|
6987
6974
|
if (name === "form") {
|
|
6988
6975
|
if (this.openElementsHas("form") && !this.openElementsHas("template")) {
|
|
6989
|
-
this.report(HTML_DUPLICATE_FORM, LEVEL_ERROR, start, {
|
|
6990
|
-
tag: raw,
|
|
6991
|
-
mode: this.insertionMode
|
|
6992
|
-
});
|
|
6976
|
+
this.report(HTML_DUPLICATE_FORM, LEVEL_ERROR, start, { tag: raw, mode: this.insertionMode }, { kind: "remove", what: "the inner <form>" });
|
|
6993
6977
|
return;
|
|
6994
6978
|
}
|
|
6995
6979
|
if (this.hasInButtonScope("p"))
|
|
@@ -7551,10 +7535,7 @@ class LinterCtx {
|
|
|
7551
7535
|
}
|
|
7552
7536
|
}
|
|
7553
7537
|
if (VOID_ELEMENTS.has(name)) {
|
|
7554
|
-
this.report(HTML_VOID_ELEMENT_HAS_CLOSE_TAG, LEVEL_WARN, start, {
|
|
7555
|
-
tag: name,
|
|
7556
|
-
mode: this.insertionMode
|
|
7557
|
-
});
|
|
7538
|
+
this.report(HTML_VOID_ELEMENT_HAS_CLOSE_TAG, LEVEL_WARN, start, { tag: name, mode: this.insertionMode }, { kind: "remove", what: `the </${name}>` });
|
|
7558
7539
|
return;
|
|
7559
7540
|
}
|
|
7560
7541
|
if (FORMATTING_ELEMENTS.has(name)) {
|
|
@@ -7691,7 +7672,89 @@ var init_htmllinter = __esm(() => {
|
|
|
7691
7672
|
]);
|
|
7692
7673
|
});
|
|
7693
7674
|
|
|
7675
|
+
// tools/core/util/closest-name.js
|
|
7676
|
+
function editDistance(a, b) {
|
|
7677
|
+
if (a === b)
|
|
7678
|
+
return 0;
|
|
7679
|
+
const la = a.length;
|
|
7680
|
+
const lb = b.length;
|
|
7681
|
+
if (la === 0)
|
|
7682
|
+
return lb;
|
|
7683
|
+
if (lb === 0)
|
|
7684
|
+
return la;
|
|
7685
|
+
const prev2 = new Array(lb + 1);
|
|
7686
|
+
const prev1 = new Array(lb + 1);
|
|
7687
|
+
const curr = new Array(lb + 1);
|
|
7688
|
+
for (let j = 0;j <= lb; j++)
|
|
7689
|
+
prev1[j] = j;
|
|
7690
|
+
for (let i = 1;i <= la; i++) {
|
|
7691
|
+
curr[0] = i;
|
|
7692
|
+
const ca = a.charCodeAt(i - 1);
|
|
7693
|
+
for (let j = 1;j <= lb; j++) {
|
|
7694
|
+
const cb = b.charCodeAt(j - 1);
|
|
7695
|
+
const cost = ca === cb ? 0 : 1;
|
|
7696
|
+
let v = Math.min(curr[j - 1] + 1, prev1[j] + 1, prev1[j - 1] + cost);
|
|
7697
|
+
if (i > 1 && j > 1 && ca === b.charCodeAt(j - 2) && a.charCodeAt(i - 2) === cb) {
|
|
7698
|
+
v = Math.min(v, prev2[j - 2] + 1);
|
|
7699
|
+
}
|
|
7700
|
+
curr[j] = v;
|
|
7701
|
+
}
|
|
7702
|
+
for (let j = 0;j <= lb; j++) {
|
|
7703
|
+
prev2[j] = prev1[j];
|
|
7704
|
+
prev1[j] = curr[j];
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
return prev1[lb];
|
|
7708
|
+
}
|
|
7709
|
+
function closestName(name, candidates, maxDistance = 2) {
|
|
7710
|
+
if (!name)
|
|
7711
|
+
return null;
|
|
7712
|
+
const lower = name.toLowerCase();
|
|
7713
|
+
let best = null;
|
|
7714
|
+
let bestDist = maxDistance + 1;
|
|
7715
|
+
for (const cand of candidates) {
|
|
7716
|
+
if (cand === name)
|
|
7717
|
+
return null;
|
|
7718
|
+
if (cand.toLowerCase() === lower)
|
|
7719
|
+
return cand;
|
|
7720
|
+
const d = editDistance(name, cand);
|
|
7721
|
+
if (d < bestDist) {
|
|
7722
|
+
best = cand;
|
|
7723
|
+
bestDist = d;
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
return bestDist <= maxDistance ? best : null;
|
|
7727
|
+
}
|
|
7728
|
+
|
|
7694
7729
|
// tools/core/lint-check.js
|
|
7730
|
+
function collectProtoMethodNames(proto) {
|
|
7731
|
+
const out = [];
|
|
7732
|
+
let cursor = proto;
|
|
7733
|
+
while (cursor && cursor !== Object.prototype) {
|
|
7734
|
+
for (const key of Object.getOwnPropertyNames(cursor)) {
|
|
7735
|
+
if (key === "constructor")
|
|
7736
|
+
continue;
|
|
7737
|
+
out.push(key);
|
|
7738
|
+
}
|
|
7739
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
7740
|
+
}
|
|
7741
|
+
return out;
|
|
7742
|
+
}
|
|
7743
|
+
function scopeKeysAlong(scope, mapKey) {
|
|
7744
|
+
const out = [];
|
|
7745
|
+
for (let cursor = scope;cursor; cursor = cursor.parent) {
|
|
7746
|
+
const map = cursor[mapKey];
|
|
7747
|
+
if (!map)
|
|
7748
|
+
continue;
|
|
7749
|
+
for (const key of Object.keys(map))
|
|
7750
|
+
out.push(key);
|
|
7751
|
+
}
|
|
7752
|
+
return out;
|
|
7753
|
+
}
|
|
7754
|
+
function replaceNameSuggestion(name, candidates) {
|
|
7755
|
+
const close = closestName(name, candidates);
|
|
7756
|
+
return close ? { kind: "replace-name", from: name, to: close } : null;
|
|
7757
|
+
}
|
|
7695
7758
|
function checkComponent(Comp, lx = new LintContext) {
|
|
7696
7759
|
return lx.push({ componentName: Comp.name }, () => {
|
|
7697
7760
|
const referencedAlters = new Set;
|
|
@@ -7717,7 +7780,7 @@ function checkView(lx, view, Comp, referencedAlters) {
|
|
|
7717
7780
|
function checkHtmlStructure(lx, view) {
|
|
7718
7781
|
if (typeof view.rawView !== "string" || !view.rawView)
|
|
7719
7782
|
return;
|
|
7720
|
-
lintHtml(view.rawView, (f) => lx.report(f.id, { ...f.info, location: f.location }, f.level), HTML_LINT_OPTS);
|
|
7783
|
+
lintHtml(view.rawView, (f) => lx.report(f.id, { ...f.info, location: f.location }, f.level, f.suggestion ?? null), HTML_LINT_OPTS);
|
|
7721
7784
|
}
|
|
7722
7785
|
function checkParseIssues(lx, view) {
|
|
7723
7786
|
const issues = view.ctx.parseIssues;
|
|
@@ -7727,12 +7790,19 @@ function checkParseIssues(lx, view) {
|
|
|
7727
7790
|
const id = PARSE_ISSUE_KIND_TO_LINT_ID[kind];
|
|
7728
7791
|
if (!id)
|
|
7729
7792
|
continue;
|
|
7730
|
-
|
|
7731
|
-
const
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7793
|
+
const atPrefixKnown = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
|
|
7794
|
+
const isAtPrefixedTypo = atPrefixKnown && info.name?.startsWith("@") && atPrefixKnown.has(info.name.slice(1));
|
|
7795
|
+
let suggestion = null;
|
|
7796
|
+
if (isAtPrefixedTypo) {
|
|
7797
|
+
suggestion = { kind: "drop-prefix", from: info.name, to: info.name.slice(1) };
|
|
7798
|
+
} else {
|
|
7799
|
+
const candidates = PARSE_ISSUE_KIND_TO_KNOWN_NAMES[kind];
|
|
7800
|
+
if (candidates)
|
|
7801
|
+
suggestion = replaceNameSuggestion(info.name, candidates);
|
|
7802
|
+
}
|
|
7803
|
+
lx.error(id, info, suggestion);
|
|
7804
|
+
if (isAtPrefixedTypo) {
|
|
7805
|
+
lx.hint(MAYBE_DROP_AT_PREFIX, { ...info, suggestion: info.name.slice(1) }, { kind: "drop-prefix", from: info.name, to: info.name.slice(1) });
|
|
7736
7806
|
}
|
|
7737
7807
|
}
|
|
7738
7808
|
}
|
|
@@ -7745,11 +7815,7 @@ function checkMacroCallArgs(lx, view, Comp) {
|
|
|
7745
7815
|
const { defaults } = macro;
|
|
7746
7816
|
for (const argName in macroNode.attrs) {
|
|
7747
7817
|
if (!(argName in defaults)) {
|
|
7748
|
-
lx.error(UNKNOWN_MACRO_ARG, {
|
|
7749
|
-
name: argName,
|
|
7750
|
-
macroName: macroNode.name,
|
|
7751
|
-
tag: `x:${macroNode.name}`
|
|
7752
|
-
});
|
|
7818
|
+
lx.error(UNKNOWN_MACRO_ARG, { name: argName, macroName: macroNode.name, tag: `x:${macroNode.name}` }, replaceNameSuggestion(argName, Object.keys(defaults)));
|
|
7753
7819
|
}
|
|
7754
7820
|
}
|
|
7755
7821
|
}
|
|
@@ -7771,8 +7837,9 @@ function walkForRenderIt(lx, node, loopDepth) {
|
|
|
7771
7837
|
return;
|
|
7772
7838
|
switch (node.constructor.name) {
|
|
7773
7839
|
case "RenderItNode":
|
|
7774
|
-
if (loopDepth === 0)
|
|
7775
|
-
lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node });
|
|
7840
|
+
if (loopDepth === 0) {
|
|
7841
|
+
lx.error(RENDER_IT_OUTSIDE_OF_LOOP, { node }, { kind: "wrap", from: "<x render-it>", to: "<x render-each>" });
|
|
7842
|
+
}
|
|
7776
7843
|
return;
|
|
7777
7844
|
case "EachNode":
|
|
7778
7845
|
walkForRenderIt(lx, node.node, loopDepth + 1);
|
|
@@ -7802,13 +7869,14 @@ function checkEventModifiers(lx, view) {
|
|
|
7802
7869
|
const modWrappers = MOD_WRAPPERS_BY_EVENT[name] ?? NO_WRAPPERS;
|
|
7803
7870
|
for (const modifier of modifiers) {
|
|
7804
7871
|
if (modWrappers[modifier] === undefined) {
|
|
7872
|
+
const close = closestName(modifier, Object.keys(modWrappers));
|
|
7805
7873
|
lx.error(UNKNOWN_EVENT_MODIFIER, {
|
|
7806
7874
|
name,
|
|
7807
7875
|
modifier,
|
|
7808
7876
|
handler,
|
|
7809
7877
|
event,
|
|
7810
7878
|
originAttr: `@on.${name}+${modifiers.join("+")}`
|
|
7811
|
-
});
|
|
7879
|
+
}, close ? { kind: "replace-name", from: `+${modifier}`, to: `+${close}` } : null);
|
|
7812
7880
|
}
|
|
7813
7881
|
}
|
|
7814
7882
|
}
|
|
@@ -7854,42 +7922,22 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
|
|
|
7854
7922
|
const originAttr = `@on.${eventName}`;
|
|
7855
7923
|
if (hvName === "InputHandlerNameVal") {
|
|
7856
7924
|
referencedInputs?.add(handlerVal.name);
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
eventName,
|
|
7863
|
-
originAttr
|
|
7864
|
-
});
|
|
7865
|
-
if (proto[handlerVal.name] !== undefined) {
|
|
7866
|
-
lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, {
|
|
7867
|
-
name: handlerVal.name,
|
|
7868
|
-
handler,
|
|
7869
|
-
event,
|
|
7870
|
-
eventName,
|
|
7871
|
-
originAttr
|
|
7872
|
-
});
|
|
7925
|
+
const { name } = handlerVal;
|
|
7926
|
+
if (input[name] === undefined) {
|
|
7927
|
+
const isMethodFix = proto[name] !== undefined;
|
|
7928
|
+
lx.error(INPUT_HANDLER_NOT_IMPLEMENTED, { name, handler, event, eventName, originAttr }, isMethodFix ? { kind: "add-prefix", from: name, to: `.${name}` } : replaceNameSuggestion(name, Object.keys(input)));
|
|
7929
|
+
if (isMethodFix) {
|
|
7930
|
+
lx.hint(INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, { name, handler, event, eventName, originAttr }, { kind: "add-prefix", from: name, to: `.${name}` });
|
|
7873
7931
|
}
|
|
7874
7932
|
}
|
|
7875
7933
|
} else if (hvName === "RawFieldVal") {
|
|
7876
7934
|
referencedInputs?.add(handlerVal.name);
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
eventName,
|
|
7883
|
-
originAttr
|
|
7884
|
-
});
|
|
7885
|
-
if (input[handlerVal.name] !== undefined) {
|
|
7886
|
-
lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, {
|
|
7887
|
-
name: handlerVal.name,
|
|
7888
|
-
handler,
|
|
7889
|
-
event,
|
|
7890
|
-
eventName,
|
|
7891
|
-
originAttr
|
|
7892
|
-
});
|
|
7935
|
+
const { name } = handlerVal;
|
|
7936
|
+
if (proto[name] === undefined) {
|
|
7937
|
+
const isInputFix = input[name] !== undefined;
|
|
7938
|
+
lx.error(INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, { name, handler, event, eventName, originAttr }, isInputFix ? { kind: "drop-prefix", from: `.${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto)));
|
|
7939
|
+
if (isInputFix) {
|
|
7940
|
+
lx.hint(INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, { name, handler, event, eventName, originAttr }, { kind: "drop-prefix", from: `.${name}`, to: name });
|
|
7893
7941
|
}
|
|
7894
7942
|
}
|
|
7895
7943
|
}
|
|
@@ -7903,22 +7951,23 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
|
|
|
7903
7951
|
if (valName === "FieldVal" || valName === "RawFieldVal") {
|
|
7904
7952
|
const { name } = val;
|
|
7905
7953
|
if (fields[name] === undefined && proto[name] === undefined) {
|
|
7906
|
-
|
|
7954
|
+
const candidates = [...Object.keys(fields), ...collectProtoMethodNames(proto)];
|
|
7955
|
+
lx.error(FIELD_VAL_NOT_DEFINED, { ...errCtx, val, name }, replaceNameSuggestion(name, candidates));
|
|
7907
7956
|
}
|
|
7908
7957
|
} else if (valName === "SeqAccessVal") {
|
|
7909
7958
|
checkConsistentAttrVal(lx, val.seqVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
|
|
7910
7959
|
checkConsistentAttrVal(lx, val.keyVal, fields, proto, scope, alter, referencedAlters, skipNameVal, errCtx);
|
|
7911
7960
|
} else if (valName === "RequestVal") {
|
|
7912
7961
|
if (scope.lookupRequest(val.name) === null) {
|
|
7913
|
-
lx.error(UNKNOWN_REQUEST_NAME, { ...errCtx, name: val.name });
|
|
7962
|
+
lx.error(UNKNOWN_REQUEST_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, scopeKeysAlong(scope, "reqsByName")));
|
|
7914
7963
|
}
|
|
7915
7964
|
} else if (valName === "TypeVal") {
|
|
7916
7965
|
if (scope.lookupComponent(val.name) === null) {
|
|
7917
|
-
lx.error(UNKNOWN_COMPONENT_NAME, { ...errCtx, name: val.name });
|
|
7966
|
+
lx.error(UNKNOWN_COMPONENT_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, scopeKeysAlong(scope, "byName")));
|
|
7918
7967
|
}
|
|
7919
7968
|
} else if (valName === "NameVal") {
|
|
7920
7969
|
if (!skipNameVal && !isKnownHandlerName(val.name)) {
|
|
7921
|
-
lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name });
|
|
7970
|
+
lx.error(UNKNOWN_HANDLER_ARG_NAME, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, KNOWN_HANDLER_NAMES));
|
|
7922
7971
|
}
|
|
7923
7972
|
} else if (valName === "StrTplVal") {
|
|
7924
7973
|
for (const subVal of val.vals) {
|
|
@@ -7927,7 +7976,7 @@ function checkConsistentAttrVal(lx, val, fields, proto, scope, alter, referenced
|
|
|
7927
7976
|
} else if (valName === "AlterHandlerNameVal") {
|
|
7928
7977
|
referencedAlters?.add(val.name);
|
|
7929
7978
|
if (alter[val.name] === undefined) {
|
|
7930
|
-
lx.error(ALT_HANDLER_NOT_DEFINED, { ...errCtx, name: val.name });
|
|
7979
|
+
lx.error(ALT_HANDLER_NOT_DEFINED, { ...errCtx, name: val.name }, replaceNameSuggestion(val.name, Object.keys(alter)));
|
|
7931
7980
|
}
|
|
7932
7981
|
} else if (valName !== "ConstVal" && valName !== "BindVal" && valName !== "DynVal") {
|
|
7933
7982
|
console.log(val);
|
|
@@ -8064,23 +8113,37 @@ class LintContext {
|
|
|
8064
8113
|
this.frame = prev;
|
|
8065
8114
|
}
|
|
8066
8115
|
}
|
|
8067
|
-
error(id, info) {
|
|
8068
|
-
this.report(id, info, LEVEL_ERROR2);
|
|
8116
|
+
error(id, info, suggestion = null) {
|
|
8117
|
+
this.report(id, info, LEVEL_ERROR2, suggestion);
|
|
8069
8118
|
}
|
|
8070
|
-
warn(id, info) {
|
|
8071
|
-
this.report(id, info, LEVEL_WARN2);
|
|
8119
|
+
warn(id, info, suggestion = null) {
|
|
8120
|
+
this.report(id, info, LEVEL_WARN2, suggestion);
|
|
8072
8121
|
}
|
|
8073
|
-
hint(id, info) {
|
|
8074
|
-
this.report(id, info, LEVEL_HINT);
|
|
8122
|
+
hint(id, info, suggestion = null) {
|
|
8123
|
+
this.report(id, info, LEVEL_HINT, suggestion);
|
|
8075
8124
|
}
|
|
8076
|
-
report(id, info = {}, level = LEVEL_ERROR2) {
|
|
8077
|
-
this.reports.push({ id, info, level, context: { ...this.frame } });
|
|
8125
|
+
report(id, info = {}, level = LEVEL_ERROR2, suggestion = null) {
|
|
8126
|
+
this.reports.push({ id, info, level, context: { ...this.frame }, suggestion });
|
|
8078
8127
|
}
|
|
8079
8128
|
}
|
|
8080
|
-
var 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", HTML_LINT_OPTS, NO_WRAPPERS, KNOWN_HANDLER_NAMES, NODE_KIND_TO_CTX, LintParseContext;
|
|
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;
|
|
8081
8130
|
var init_lint_check = __esm(() => {
|
|
8082
8131
|
init_anode();
|
|
8083
8132
|
init_htmllinter();
|
|
8133
|
+
KNOWN_DIRECTIVE_NAMES = new Set([
|
|
8134
|
+
"dangerouslysetinnerhtml",
|
|
8135
|
+
"slot",
|
|
8136
|
+
"push-view",
|
|
8137
|
+
"text",
|
|
8138
|
+
"show",
|
|
8139
|
+
"hide",
|
|
8140
|
+
"each",
|
|
8141
|
+
"enrich-with",
|
|
8142
|
+
"when",
|
|
8143
|
+
"loop-with",
|
|
8144
|
+
"then",
|
|
8145
|
+
"else"
|
|
8146
|
+
]);
|
|
8084
8147
|
PARSE_ISSUE_KIND_TO_LINT_ID = {
|
|
8085
8148
|
"unknown-directive": UNKNOWN_DIRECTIVE,
|
|
8086
8149
|
"unknown-x-op": UNKNOWN_X_OP,
|
|
@@ -8101,6 +8164,11 @@ var init_lint_check = __esm(() => {
|
|
|
8101
8164
|
"unknown-x-op": X_KNOWN_OP_NAMES,
|
|
8102
8165
|
"unknown-x-attr": X_KNOWN_ATTR_NAMES
|
|
8103
8166
|
};
|
|
8167
|
+
PARSE_ISSUE_KIND_TO_KNOWN_NAMES = {
|
|
8168
|
+
"unknown-directive": KNOWN_DIRECTIVE_NAMES,
|
|
8169
|
+
"unknown-x-op": X_KNOWN_OP_NAMES,
|
|
8170
|
+
"unknown-x-attr": X_KNOWN_ATTR_NAMES
|
|
8171
|
+
};
|
|
8104
8172
|
HTML_LINT_OPTS = {
|
|
8105
8173
|
fragmentContext: "template",
|
|
8106
8174
|
transparentTagPrefixes: ["x"]
|
|
@@ -14268,7 +14336,6 @@ GLOBAL FLAGS
|
|
|
14268
14336
|
-o, --output <file> Write to <file> instead of stdout.
|
|
14269
14337
|
--pretty Pretty-print HTML (md/html formats) via
|
|
14270
14338
|
prettier; JSON formatter uses indent 2.
|
|
14271
|
-
Requires \`prettier\` to be installed.
|
|
14272
14339
|
-h, --help Show this help.
|
|
14273
14340
|
--module <path> Alternative to first-positional module path.
|
|
14274
14341
|
|
|
@@ -14279,9 +14346,6 @@ EXIT CODES
|
|
|
14279
14346
|
3 render crash
|
|
14280
14347
|
4 test failures
|
|
14281
14348
|
|
|
14282
|
-
ENVIRONMENT
|
|
14283
|
-
\`prettier\` is an optional peer dep, only used by --pretty.
|
|
14284
|
-
|
|
14285
14349
|
EXAMPLES
|
|
14286
14350
|
# Inspect a module
|
|
14287
14351
|
tutuca ./src/components.js info
|
|
@@ -14456,7 +14520,7 @@ function fmtEventSuffix(info) {
|
|
|
14456
14520
|
function lintIdToMessage(id, info) {
|
|
14457
14521
|
switch (id) {
|
|
14458
14522
|
case "RENDER_IT_OUTSIDE_OF_LOOP":
|
|
14459
|
-
return "render-it used outside of a loop";
|
|
14523
|
+
return "<x render-it> used outside of a loop";
|
|
14460
14524
|
case "UNKNOWN_EVENT_MODIFIER": {
|
|
14461
14525
|
const mods = info.handler?.modifiers ?? [info.modifier];
|
|
14462
14526
|
const written = `@on.${info.name}+${mods.join("+")}`;
|
|
@@ -14467,13 +14531,13 @@ function lintIdToMessage(id, info) {
|
|
|
14467
14531
|
case "INPUT_HANDLER_NOT_IMPLEMENTED":
|
|
14468
14532
|
return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14469
14533
|
case "INPUT_HANDLER_NOT_REFERENCED":
|
|
14470
|
-
return `Input handler '${info.name}' is defined but
|
|
14534
|
+
return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
|
|
14471
14535
|
case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
|
|
14472
14536
|
return `Method '.${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14473
14537
|
case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
|
|
14474
|
-
return `'
|
|
14538
|
+
return `'.${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
|
|
14475
14539
|
case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
|
|
14476
|
-
return `'${info.name}'
|
|
14540
|
+
return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
|
|
14477
14541
|
case "FIELD_VAL_NOT_DEFINED":
|
|
14478
14542
|
return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14479
14543
|
case "DUPLICATE_ATTR_DEFINITION": {
|
|
@@ -14482,7 +14546,7 @@ function lintIdToMessage(id, info) {
|
|
|
14482
14546
|
return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
|
|
14483
14547
|
}
|
|
14484
14548
|
case "IF_NO_BRANCH_SET":
|
|
14485
|
-
return `'@if.${info.attr}' has no '@then' or '@else' branch —
|
|
14549
|
+
return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
|
|
14486
14550
|
case "UNKNOWN_REQUEST_NAME":
|
|
14487
14551
|
return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
|
|
14488
14552
|
case "UNKNOWN_COMPONENT_NAME":
|
|
@@ -14490,7 +14554,7 @@ function lintIdToMessage(id, info) {
|
|
|
14490
14554
|
case "ALT_HANDLER_NOT_DEFINED":
|
|
14491
14555
|
return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14492
14556
|
case "ALT_HANDLER_NOT_REFERENCED":
|
|
14493
|
-
return `Alter handler '${info.name}' is defined but
|
|
14557
|
+
return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
|
|
14494
14558
|
case "UNKNOWN_MACRO_ARG":
|
|
14495
14559
|
return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
|
|
14496
14560
|
case "UNKNOWN_DIRECTIVE":
|
|
@@ -14501,7 +14565,7 @@ function lintIdToMessage(id, info) {
|
|
|
14501
14565
|
return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
|
|
14502
14566
|
case "MAYBE_DROP_AT_PREFIX": {
|
|
14503
14567
|
const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
|
|
14504
|
-
return `
|
|
14568
|
+
return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
|
|
14505
14569
|
}
|
|
14506
14570
|
case "BAD_VALUE":
|
|
14507
14571
|
return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
|
|
@@ -14510,7 +14574,7 @@ function lintIdToMessage(id, info) {
|
|
|
14510
14574
|
case "HTML_SVG_TAG_WILL_LOWERCASE":
|
|
14511
14575
|
return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
|
|
14512
14576
|
case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
|
|
14513
|
-
return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode}
|
|
14577
|
+
return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
|
|
14514
14578
|
case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
|
|
14515
14579
|
return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
|
|
14516
14580
|
case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
|
|
@@ -14547,11 +14611,47 @@ function lintIdToMessage(id, info) {
|
|
|
14547
14611
|
return id;
|
|
14548
14612
|
}
|
|
14549
14613
|
}
|
|
14614
|
+
function suggestionToMessage(suggestion) {
|
|
14615
|
+
if (!suggestion)
|
|
14616
|
+
return null;
|
|
14617
|
+
switch (suggestion.kind) {
|
|
14618
|
+
case "replace-name":
|
|
14619
|
+
return `did you mean '${suggestion.to}'?`;
|
|
14620
|
+
case "drop-prefix":
|
|
14621
|
+
return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
|
|
14622
|
+
case "add-prefix":
|
|
14623
|
+
return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
|
|
14624
|
+
case "remove":
|
|
14625
|
+
return `remove ${suggestion.what}`;
|
|
14626
|
+
case "rewrite":
|
|
14627
|
+
return `use '${suggestion.to}' instead of '${suggestion.from}'`;
|
|
14628
|
+
case "wrap":
|
|
14629
|
+
return `wrap it in ${suggestion.to}`;
|
|
14630
|
+
default:
|
|
14631
|
+
return null;
|
|
14632
|
+
}
|
|
14633
|
+
}
|
|
14550
14634
|
function fmtLocationSuffix(info) {
|
|
14551
14635
|
const loc = info?.location;
|
|
14552
14636
|
if (!loc)
|
|
14553
14637
|
return "";
|
|
14554
|
-
return ` at
|
|
14638
|
+
return ` at line ${loc.line}, col ${loc.column}`;
|
|
14639
|
+
}
|
|
14640
|
+
function htmlActionPhrase(action, tag, parent) {
|
|
14641
|
+
switch (action) {
|
|
14642
|
+
case "ignored":
|
|
14643
|
+
return `the parser will drop this <${tag}>`;
|
|
14644
|
+
case "drop":
|
|
14645
|
+
return `the parser will drop this <${tag}>`;
|
|
14646
|
+
case "auto-close-implicit":
|
|
14647
|
+
return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
|
|
14648
|
+
case "foster-parent":
|
|
14649
|
+
return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
|
|
14650
|
+
case "foreign-breakout":
|
|
14651
|
+
return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
|
|
14652
|
+
default:
|
|
14653
|
+
return `parser action: ${action}`;
|
|
14654
|
+
}
|
|
14555
14655
|
}
|
|
14556
14656
|
|
|
14557
14657
|
// tools/format/cli.js
|
|
@@ -14636,7 +14736,9 @@ function fmtLintReport(rep) {
|
|
|
14636
14736
|
for (const f of c.findings) {
|
|
14637
14737
|
const tag = f.level.toUpperCase();
|
|
14638
14738
|
const view = f.context?.viewName ? ` [view=${f.context.viewName}]` : "";
|
|
14639
|
-
|
|
14739
|
+
const tail = suggestionToMessage(f.suggestion);
|
|
14740
|
+
const suffix = tail ? ` — ${tail}` : "";
|
|
14741
|
+
lines.push(` [${tag}]${view} ${lintIdToMessage(f.id, f.info)}${suffix}`);
|
|
14640
14742
|
}
|
|
14641
14743
|
}
|
|
14642
14744
|
lines.push("");
|
|
@@ -14817,7 +14919,9 @@ function fmtLintReport2(rep) {
|
|
|
14817
14919
|
}
|
|
14818
14920
|
for (const f of c.findings) {
|
|
14819
14921
|
const view = f.context?.viewName ? ` _(view: \`${f.context.viewName}\`)_` : "";
|
|
14820
|
-
|
|
14922
|
+
const tail = suggestionToMessage(f.suggestion);
|
|
14923
|
+
const suffix = tail ? ` — ${tail}` : "";
|
|
14924
|
+
lines.push(`- **${f.level.toUpperCase()}** ${lintIdToMessage(f.id, f.info)}${suffix}${view}`);
|
|
14821
14925
|
}
|
|
14822
14926
|
lines.push("");
|
|
14823
14927
|
}
|