tutuca 0.9.38 → 0.9.40
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 +244 -58
- package/dist/tutuca-dev.js +251 -56
- package/dist/tutuca-dev.min.js +1 -1
- package/package.json +1 -1
package/dist/tutuca-dev.js
CHANGED
|
@@ -2468,8 +2468,11 @@ var DEFAULT_SCOPE_BOUNDARIES = new Set([
|
|
|
2468
2468
|
"th",
|
|
2469
2469
|
"marquee",
|
|
2470
2470
|
"object",
|
|
2471
|
+
"select",
|
|
2471
2472
|
"template"
|
|
2472
2473
|
]);
|
|
2474
|
+
var MATHML_TEXT_INTEGRATION_POINT_NAMES = new Set(["mi", "mo", "mn", "ms", "mtext"]);
|
|
2475
|
+
var SVG_HTML_INTEGRATION_POINT_NAMES = new Set(["foreignobject", "desc", "title"]);
|
|
2473
2476
|
var SCOPE_LIST_ITEM = new Set([...DEFAULT_SCOPE_BOUNDARIES, "ol", "ul"]);
|
|
2474
2477
|
var SCOPE_BUTTON = new Set([...DEFAULT_SCOPE_BOUNDARIES, "button"]);
|
|
2475
2478
|
var SCOPE_DEFAULT = DEFAULT_SCOPE_BOUNDARIES;
|
|
@@ -2574,6 +2577,12 @@ var STANDARD_SVG_CAMEL_ATTRS = new Set([
|
|
|
2574
2577
|
"zoomAndPan"
|
|
2575
2578
|
]);
|
|
2576
2579
|
var MATHML_CAMEL_ATTRS = new Set(["definitionURL"]);
|
|
2580
|
+
var SVG_ATTR_LOWERCASE_TO_CAMEL = new Map;
|
|
2581
|
+
for (const camel of STANDARD_SVG_CAMEL_ATTRS)
|
|
2582
|
+
SVG_ATTR_LOWERCASE_TO_CAMEL.set(camel.toLowerCase(), camel);
|
|
2583
|
+
var MATHML_ATTR_LOWERCASE_TO_CAMEL = new Map;
|
|
2584
|
+
for (const camel of MATHML_CAMEL_ATTRS)
|
|
2585
|
+
MATHML_ATTR_LOWERCASE_TO_CAMEL.set(camel.toLowerCase(), camel);
|
|
2577
2586
|
var FOREIGN_BREAKOUT_TAGS = new Set([
|
|
2578
2587
|
"b",
|
|
2579
2588
|
"big",
|
|
@@ -2668,7 +2677,6 @@ var SELECT_BREAKOUT_TAGS = new Set(["input", "keygen", "textarea", "select"]);
|
|
|
2668
2677
|
var MODES = Object.freeze({
|
|
2669
2678
|
inBody: "inBody",
|
|
2670
2679
|
inTable: "inTable",
|
|
2671
|
-
inTableText: "inTableText",
|
|
2672
2680
|
inCaption: "inCaption",
|
|
2673
2681
|
inColumnGroup: "inColumnGroup",
|
|
2674
2682
|
inTableBody: "inTableBody",
|
|
@@ -2676,8 +2684,7 @@ var MODES = Object.freeze({
|
|
|
2676
2684
|
inCell: "inCell",
|
|
2677
2685
|
inSelect: "inSelect",
|
|
2678
2686
|
inSelectInTable: "inSelectInTable",
|
|
2679
|
-
inTemplate: "inTemplate"
|
|
2680
|
-
text: "text"
|
|
2687
|
+
inTemplate: "inTemplate"
|
|
2681
2688
|
});
|
|
2682
2689
|
var NS = Object.freeze({
|
|
2683
2690
|
html: "html",
|
|
@@ -2705,6 +2712,8 @@ var FRAGMENT_CONTEXT_MODES = Object.freeze({
|
|
|
2705
2712
|
// tools/core/htmllinter.js
|
|
2706
2713
|
var HTML_TAG_NAME_HAS_UPPERCASE = "HTML_TAG_NAME_HAS_UPPERCASE";
|
|
2707
2714
|
var HTML_SVG_TAG_WILL_LOWERCASE = "HTML_SVG_TAG_WILL_LOWERCASE";
|
|
2715
|
+
var HTML_SVG_ATTR_WILL_LOWERCASE = "HTML_SVG_ATTR_WILL_LOWERCASE";
|
|
2716
|
+
var HTML_MATHML_ATTR_WILL_LOWERCASE = "HTML_MATHML_ATTR_WILL_LOWERCASE";
|
|
2708
2717
|
var HTML_TAG_NOT_ALLOWED_IN_PARENT = "HTML_TAG_NOT_ALLOWED_IN_PARENT";
|
|
2709
2718
|
var HTML_TEXT_NOT_ALLOWED_IN_PARENT = "HTML_TEXT_NOT_ALLOWED_IN_PARENT";
|
|
2710
2719
|
var HTML_VOID_ELEMENT_HAS_CLOSE_TAG = "HTML_VOID_ELEMENT_HAS_CLOSE_TAG";
|
|
@@ -2712,6 +2721,13 @@ var HTML_DUPLICATE_FORM = "HTML_DUPLICATE_FORM";
|
|
|
2712
2721
|
var HTML_NESTED_INTERACTIVE = "HTML_NESTED_INTERACTIVE";
|
|
2713
2722
|
var HTML_MISNESTED_FORMATTING = "HTML_MISNESTED_FORMATTING";
|
|
2714
2723
|
var HTML_UNEXPECTED_END_TAG = "HTML_UNEXPECTED_END_TAG";
|
|
2724
|
+
var HTML_DUPLICATE_ATTRIBUTE = "HTML_DUPLICATE_ATTRIBUTE";
|
|
2725
|
+
var HTML_ATTRIBUTES_ON_END_TAG = "HTML_ATTRIBUTES_ON_END_TAG";
|
|
2726
|
+
var HTML_SELF_CLOSING_END_TAG = "HTML_SELF_CLOSING_END_TAG";
|
|
2727
|
+
var HTML_MISSING_ATTRIBUTE_VALUE = "HTML_MISSING_ATTRIBUTE_VALUE";
|
|
2728
|
+
var HTML_CDATA_IN_HTML_NAMESPACE = "HTML_CDATA_IN_HTML_NAMESPACE";
|
|
2729
|
+
var HTML_BOGUS_COMMENT = "HTML_BOGUS_COMMENT";
|
|
2730
|
+
var HTML_UNCLOSED_BEFORE_END = "HTML_UNCLOSED_BEFORE_END";
|
|
2715
2731
|
var LEVEL_ERROR = "error";
|
|
2716
2732
|
var LEVEL_WARN = "warn";
|
|
2717
2733
|
var TABLE_SCOPE_TAGS = new Set([
|
|
@@ -2726,6 +2742,18 @@ var TABLE_SCOPE_TAGS = new Set([
|
|
|
2726
2742
|
"table"
|
|
2727
2743
|
]);
|
|
2728
2744
|
var TABLE_BODY_CELL_TAGS = new Set(["td", "th"]);
|
|
2745
|
+
var IMPLIED_END_TAGS = new Set([
|
|
2746
|
+
"dd",
|
|
2747
|
+
"dt",
|
|
2748
|
+
"li",
|
|
2749
|
+
"optgroup",
|
|
2750
|
+
"option",
|
|
2751
|
+
"p",
|
|
2752
|
+
"rb",
|
|
2753
|
+
"rp",
|
|
2754
|
+
"rt",
|
|
2755
|
+
"rtc"
|
|
2756
|
+
]);
|
|
2729
2757
|
function lintHtml(html, onFinding, opts = {}) {
|
|
2730
2758
|
const TokenizerClass = opts.TokenizerClass ?? HtmlTokenizer;
|
|
2731
2759
|
const ctx = new LinterCtx(html, onFinding, opts);
|
|
@@ -2766,30 +2794,75 @@ class LinterCtx {
|
|
|
2766
2794
|
this.openElements.push({ name: "select", ns: NS.html, start: -1 });
|
|
2767
2795
|
}
|
|
2768
2796
|
this.insertionMode = ctxInfo.mode;
|
|
2769
|
-
this.originalInsertionMode = MODES.inBody;
|
|
2770
2797
|
this.templateInsertionModes = ctxName === "template" ? [MODES.inTemplate] : [];
|
|
2771
2798
|
this.activeFormatting = [];
|
|
2772
|
-
this.formPointer = null;
|
|
2773
2799
|
this.framesetOk = true;
|
|
2774
2800
|
this.svgCamelElements = opts.svgCamelElements ?? STANDARD_SVG_CAMEL_ELEMENTS;
|
|
2775
|
-
this.transparentTagPrefixes = opts.transparentTagPrefixes ?? [];
|
|
2801
|
+
this.transparentTagPrefixes = (opts.transparentTagPrefixes ?? []).map((p) => p.toLowerCase());
|
|
2776
2802
|
this.currentTagName = "";
|
|
2777
2803
|
this.currentTagRawName = "";
|
|
2778
2804
|
this.currentTagStart = 0;
|
|
2805
|
+
this.currentAttrs = [];
|
|
2806
|
+
this.currentAttr = null;
|
|
2779
2807
|
this.tokenizer = null;
|
|
2780
|
-
this.textRestoreMode = null;
|
|
2781
2808
|
}
|
|
2782
2809
|
onopentagname(start, end) {
|
|
2783
2810
|
const raw = this.html.slice(start, end);
|
|
2784
2811
|
this.currentTagRawName = raw;
|
|
2785
2812
|
this.currentTagName = raw.toLowerCase();
|
|
2786
2813
|
this.currentTagStart = start;
|
|
2814
|
+
this.currentAttrs = [];
|
|
2815
|
+
this.currentAttr = null;
|
|
2816
|
+
}
|
|
2817
|
+
onattribname(start, end) {
|
|
2818
|
+
const rawName = this.html.slice(start, end);
|
|
2819
|
+
this.currentAttr = {
|
|
2820
|
+
name: rawName.toLowerCase(),
|
|
2821
|
+
rawName,
|
|
2822
|
+
nameStart: start,
|
|
2823
|
+
value: null,
|
|
2824
|
+
valueStart: -1,
|
|
2825
|
+
valueEnd: -1,
|
|
2826
|
+
quote: QuoteType.NoValue
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
onattribdata(start, end) {
|
|
2830
|
+
if (!this.currentAttr)
|
|
2831
|
+
return;
|
|
2832
|
+
this.currentAttr.valueStart = start;
|
|
2833
|
+
this.currentAttr.valueEnd = end;
|
|
2834
|
+
this.currentAttr.value = this.html.slice(start, end);
|
|
2835
|
+
}
|
|
2836
|
+
onattribend(quote, _end) {
|
|
2837
|
+
const a = this.currentAttr;
|
|
2838
|
+
if (!a)
|
|
2839
|
+
return;
|
|
2840
|
+
a.quote = quote;
|
|
2841
|
+
const dup = this.currentAttrs.find((x) => x.name === a.name);
|
|
2842
|
+
if (dup) {
|
|
2843
|
+
this.report(HTML_DUPLICATE_ATTRIBUTE, LEVEL_WARN, a.nameStart, {
|
|
2844
|
+
name: a.name,
|
|
2845
|
+
firstAt: dup.nameStart
|
|
2846
|
+
});
|
|
2847
|
+
} else {
|
|
2848
|
+
this.currentAttrs.push(a);
|
|
2849
|
+
}
|
|
2850
|
+
if (a.quote === QuoteType.Unquoted && a.value === "") {
|
|
2851
|
+
this.report(HTML_MISSING_ATTRIBUTE_VALUE, LEVEL_WARN, a.nameStart, {
|
|
2852
|
+
name: a.name
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
this.currentAttr = null;
|
|
2787
2856
|
}
|
|
2788
|
-
onattribname(_start, _end) {}
|
|
2789
|
-
onattribdata(_start, _end) {}
|
|
2790
2857
|
onattribentity(_cp) {}
|
|
2791
|
-
onattribend(_quote, _end) {}
|
|
2792
2858
|
ontextentity(_cp, _end) {}
|
|
2859
|
+
getAttr(name) {
|
|
2860
|
+
const a = this.currentAttrs.find((x) => x.name === name);
|
|
2861
|
+
return a ? a.value : null;
|
|
2862
|
+
}
|
|
2863
|
+
hasAttr(name) {
|
|
2864
|
+
return this.currentAttrs.some((x) => x.name === name);
|
|
2865
|
+
}
|
|
2793
2866
|
onopentagend(endIndex) {
|
|
2794
2867
|
this.handleStartTag(false, endIndex);
|
|
2795
2868
|
}
|
|
@@ -2799,20 +2872,56 @@ class LinterCtx {
|
|
|
2799
2872
|
onclosetag(start, end) {
|
|
2800
2873
|
const raw = this.html.slice(start, end);
|
|
2801
2874
|
const name = raw.toLowerCase();
|
|
2875
|
+
let i = end;
|
|
2876
|
+
let lastNonWs = -1;
|
|
2877
|
+
while (i < this.html.length) {
|
|
2878
|
+
const c = this.html.charCodeAt(i);
|
|
2879
|
+
if (c === 62)
|
|
2880
|
+
break;
|
|
2881
|
+
if (c !== 32 && c !== 9 && c !== 10 && c !== 13 && c !== 12)
|
|
2882
|
+
lastNonWs = i;
|
|
2883
|
+
i++;
|
|
2884
|
+
}
|
|
2885
|
+
if (lastNonWs >= 0) {
|
|
2886
|
+
if (this.html.charCodeAt(lastNonWs) === 47 && lastNonWs === i - 1) {
|
|
2887
|
+
let firstNonWs = -1;
|
|
2888
|
+
for (let j = end;j < lastNonWs; j++) {
|
|
2889
|
+
const c2 = this.html.charCodeAt(j);
|
|
2890
|
+
if (c2 !== 32 && c2 !== 9 && c2 !== 10 && c2 !== 13 && c2 !== 12) {
|
|
2891
|
+
firstNonWs = j;
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
if (firstNonWs < 0) {
|
|
2896
|
+
this.report(HTML_SELF_CLOSING_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
2897
|
+
} else {
|
|
2898
|
+
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
2899
|
+
}
|
|
2900
|
+
} else {
|
|
2901
|
+
this.report(HTML_ATTRIBUTES_ON_END_TAG, LEVEL_WARN, start, { tag: name });
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2802
2904
|
this.handleEndTag(name, start);
|
|
2803
2905
|
}
|
|
2804
2906
|
ontext(start, end) {
|
|
2805
2907
|
if (start >= end)
|
|
2806
2908
|
return;
|
|
2909
|
+
const top = this.currentNode();
|
|
2910
|
+
if (top && top.ns !== NS.html && !this.isIntegrationPoint(top))
|
|
2911
|
+
return;
|
|
2807
2912
|
this.handleText(start, end);
|
|
2808
2913
|
}
|
|
2809
|
-
oncomment(
|
|
2914
|
+
oncomment(start, _end, endOffset) {
|
|
2915
|
+
if (endOffset === 0) {
|
|
2916
|
+
this.report(HTML_BOGUS_COMMENT, LEVEL_WARN, start, {
|
|
2917
|
+
mode: this.insertionMode
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2810
2921
|
oncdata(start, _end, _endOffset) {
|
|
2811
2922
|
if (this.currentNamespace() === NS.html) {
|
|
2812
|
-
this.report(
|
|
2813
|
-
|
|
2814
|
-
mode: this.insertionMode,
|
|
2815
|
-
action: "ignored"
|
|
2923
|
+
this.report(HTML_CDATA_IN_HTML_NAMESPACE, LEVEL_WARN, start, {
|
|
2924
|
+
mode: this.insertionMode
|
|
2816
2925
|
});
|
|
2817
2926
|
}
|
|
2818
2927
|
}
|
|
@@ -2850,14 +2959,37 @@ class LinterCtx {
|
|
|
2850
2959
|
const f = this.openElements[i];
|
|
2851
2960
|
if (f.name === target && f.ns === NS.html)
|
|
2852
2961
|
return true;
|
|
2853
|
-
if (f.ns === NS.html
|
|
2854
|
-
|
|
2855
|
-
|
|
2962
|
+
if (f.ns === NS.html) {
|
|
2963
|
+
if (scopeSet.has(f.name))
|
|
2964
|
+
return false;
|
|
2965
|
+
} else if (this.isScopeBoundary(f)) {
|
|
2856
2966
|
return false;
|
|
2857
2967
|
}
|
|
2858
2968
|
}
|
|
2859
2969
|
return false;
|
|
2860
2970
|
}
|
|
2971
|
+
isScopeBoundary(frame) {
|
|
2972
|
+
if (frame.ns === NS.math) {
|
|
2973
|
+
return MATHML_TEXT_INTEGRATION_POINT_NAMES.has(frame.name) || frame.name === "annotation-xml";
|
|
2974
|
+
}
|
|
2975
|
+
if (frame.ns === NS.svg) {
|
|
2976
|
+
return SVG_HTML_INTEGRATION_POINT_NAMES.has(frame.name);
|
|
2977
|
+
}
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
isIntegrationPoint(frame) {
|
|
2981
|
+
if (frame.ns === NS.math) {
|
|
2982
|
+
if (MATHML_TEXT_INTEGRATION_POINT_NAMES.has(frame.name))
|
|
2983
|
+
return true;
|
|
2984
|
+
if (frame.name === "annotation-xml" && frame.htmlIntegration)
|
|
2985
|
+
return true;
|
|
2986
|
+
return false;
|
|
2987
|
+
}
|
|
2988
|
+
if (frame.ns === NS.svg) {
|
|
2989
|
+
return SVG_HTML_INTEGRATION_POINT_NAMES.has(frame.name);
|
|
2990
|
+
}
|
|
2991
|
+
return false;
|
|
2992
|
+
}
|
|
2861
2993
|
hasInDefaultScope(target) {
|
|
2862
2994
|
return this.hasInScope(target, SCOPE_DEFAULT);
|
|
2863
2995
|
}
|
|
@@ -2928,13 +3060,43 @@ class LinterCtx {
|
|
|
2928
3060
|
});
|
|
2929
3061
|
}
|
|
2930
3062
|
}
|
|
3063
|
+
const targetNs = ns !== NS.html ? ns : name === "svg" ? NS.svg : name === "math" ? NS.math : NS.html;
|
|
3064
|
+
if (targetNs === NS.svg) {
|
|
3065
|
+
for (const a of this.currentAttrs) {
|
|
3066
|
+
const canonical = SVG_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
|
|
3067
|
+
if (canonical && a.rawName !== canonical) {
|
|
3068
|
+
this.report(HTML_SVG_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
|
|
3069
|
+
raw: a.rawName,
|
|
3070
|
+
canonical
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
} else if (targetNs === NS.math) {
|
|
3075
|
+
for (const a of this.currentAttrs) {
|
|
3076
|
+
const canonical = MATHML_ATTR_LOWERCASE_TO_CAMEL.get(a.name);
|
|
3077
|
+
if (canonical && a.rawName !== canonical) {
|
|
3078
|
+
this.report(HTML_MATHML_ATTR_WILL_LOWERCASE, LEVEL_ERROR, a.nameStart, {
|
|
3079
|
+
raw: a.rawName,
|
|
3080
|
+
canonical
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
2931
3085
|
if (this.isTransparentTag(name))
|
|
2932
3086
|
return;
|
|
2933
|
-
|
|
3087
|
+
const top = this.currentNode();
|
|
3088
|
+
const inForeign = ns !== NS.html && !(top && this.isIntegrationPoint(top));
|
|
3089
|
+
if (inForeign && !this.shouldBreakoutFromForeign(name)) {
|
|
2934
3090
|
this.startTagInForeign(name, raw, selfClosing, start);
|
|
2935
3091
|
return;
|
|
2936
3092
|
}
|
|
2937
|
-
if (
|
|
3093
|
+
if (inForeign && this.shouldBreakoutFromForeign(name)) {
|
|
3094
|
+
this.report(HTML_TAG_NOT_ALLOWED_IN_PARENT, LEVEL_WARN, start, {
|
|
3095
|
+
tag: raw,
|
|
3096
|
+
parent: this.currentNode()?.name ?? "(root)",
|
|
3097
|
+
mode: this.insertionMode,
|
|
3098
|
+
action: "foreign-breakout"
|
|
3099
|
+
});
|
|
2938
3100
|
while (this.openElements.length && this.currentNode()?.ns !== NS.html) {
|
|
2939
3101
|
this.openElements.pop();
|
|
2940
3102
|
}
|
|
@@ -2944,20 +3106,28 @@ class LinterCtx {
|
|
|
2944
3106
|
shouldBreakoutFromForeign(name) {
|
|
2945
3107
|
if (FOREIGN_BREAKOUT_TAGS.has(name))
|
|
2946
3108
|
return true;
|
|
2947
|
-
if (name === "font")
|
|
2948
|
-
return
|
|
3109
|
+
if (name === "font") {
|
|
3110
|
+
return this.hasAttr("color") || this.hasAttr("face") || this.hasAttr("size");
|
|
3111
|
+
}
|
|
2949
3112
|
return false;
|
|
2950
3113
|
}
|
|
2951
3114
|
startTagInForeign(name, raw, selfClosing, start) {
|
|
2952
3115
|
const ns = name === "svg" ? NS.svg : name === "math" ? NS.math : this.currentNamespace();
|
|
2953
3116
|
const top = this.currentNode();
|
|
2954
|
-
if (top &&
|
|
3117
|
+
if (top && this.isIntegrationPoint(top) && name !== "mglyph" && name !== "malignmark") {
|
|
2955
3118
|
this.dispatchStartTag(name, raw, selfClosing, start, start + raw.length);
|
|
2956
3119
|
return;
|
|
2957
3120
|
}
|
|
2958
3121
|
if (selfClosing)
|
|
2959
3122
|
return;
|
|
2960
|
-
|
|
3123
|
+
const frame = { name, ns, start };
|
|
3124
|
+
if (ns === NS.math && name === "annotation-xml") {
|
|
3125
|
+
const enc = (this.getAttr("encoding") ?? "").toLowerCase();
|
|
3126
|
+
if (enc === "text/html" || enc === "application/xhtml+xml") {
|
|
3127
|
+
frame.htmlIntegration = true;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
this.openElements.push(frame);
|
|
2961
3131
|
}
|
|
2962
3132
|
dispatchStartTag(name, raw, selfClosing, start, endIndex) {
|
|
2963
3133
|
switch (this.insertionMode) {
|
|
@@ -2967,10 +3137,6 @@ class LinterCtx {
|
|
|
2967
3137
|
return this.startInBody(name, raw, selfClosing, start, endIndex);
|
|
2968
3138
|
case MODES.inTable:
|
|
2969
3139
|
return this.startInTable(name, raw, selfClosing, start, endIndex);
|
|
2970
|
-
case MODES.inTableText:
|
|
2971
|
-
this.flushTableText();
|
|
2972
|
-
this.insertionMode = this.originalInsertionMode;
|
|
2973
|
-
return this.dispatchStartTag(name, raw, selfClosing, start, endIndex);
|
|
2974
3140
|
case MODES.inCaption:
|
|
2975
3141
|
return this.startInCaption(name, raw, selfClosing, start, endIndex);
|
|
2976
3142
|
case MODES.inColumnGroup:
|
|
@@ -2985,8 +3151,6 @@ class LinterCtx {
|
|
|
2985
3151
|
return this.startInSelect(name, raw, selfClosing, start, endIndex);
|
|
2986
3152
|
case MODES.inSelectInTable:
|
|
2987
3153
|
return this.startInSelectInTable(name, raw, selfClosing, start, endIndex);
|
|
2988
|
-
case MODES.text:
|
|
2989
|
-
return;
|
|
2990
3154
|
}
|
|
2991
3155
|
}
|
|
2992
3156
|
startInBody(name, raw, selfClosing, start, _endIndex) {
|
|
@@ -3028,7 +3192,7 @@ class LinterCtx {
|
|
|
3028
3192
|
return;
|
|
3029
3193
|
}
|
|
3030
3194
|
if (name === "form") {
|
|
3031
|
-
if (this.
|
|
3195
|
+
if (this.openElementsHas("form") && !this.openElementsHas("template")) {
|
|
3032
3196
|
this.report(HTML_DUPLICATE_FORM, LEVEL_ERROR, start, {
|
|
3033
3197
|
tag: raw,
|
|
3034
3198
|
mode: this.insertionMode
|
|
@@ -3038,7 +3202,6 @@ class LinterCtx {
|
|
|
3038
3202
|
if (this.hasInButtonScope("p"))
|
|
3039
3203
|
this.implicitlyClose("p", start, raw);
|
|
3040
3204
|
this.push(raw, NS.html, start);
|
|
3041
|
-
this.formPointer = this.currentNode();
|
|
3042
3205
|
return;
|
|
3043
3206
|
}
|
|
3044
3207
|
if (name === "li") {
|
|
@@ -3111,7 +3274,7 @@ class LinterCtx {
|
|
|
3111
3274
|
return;
|
|
3112
3275
|
}
|
|
3113
3276
|
if (name === "a") {
|
|
3114
|
-
if (this.
|
|
3277
|
+
if (this.activeFormattingHas("a")) {
|
|
3115
3278
|
this.report(HTML_NESTED_INTERACTIVE, LEVEL_WARN, start, {
|
|
3116
3279
|
tag: raw,
|
|
3117
3280
|
mode: this.insertionMode
|
|
@@ -3128,7 +3291,7 @@ class LinterCtx {
|
|
|
3128
3291
|
return;
|
|
3129
3292
|
}
|
|
3130
3293
|
if (name === "nobr") {
|
|
3131
|
-
if (this.
|
|
3294
|
+
if (this.activeFormattingHas("nobr")) {
|
|
3132
3295
|
this.report(HTML_NESTED_INTERACTIVE, LEVEL_WARN, start, {
|
|
3133
3296
|
tag: raw,
|
|
3134
3297
|
mode: this.insertionMode
|
|
@@ -3212,7 +3375,6 @@ class LinterCtx {
|
|
|
3212
3375
|
if (e === null)
|
|
3213
3376
|
break;
|
|
3214
3377
|
if (e.name === name) {
|
|
3215
|
-
this.activeFormatting.splice(i, 1);
|
|
3216
3378
|
const idx = this.openElements.indexOf(e);
|
|
3217
3379
|
if (idx >= 0)
|
|
3218
3380
|
this.openElements.splice(idx, 1);
|
|
@@ -3305,6 +3467,9 @@ class LinterCtx {
|
|
|
3305
3467
|
return this.startInBody(name, raw, selfClosing, start, endIndex);
|
|
3306
3468
|
}
|
|
3307
3469
|
if (name === "input") {
|
|
3470
|
+
const type = (this.getAttr("type") ?? "").toLowerCase();
|
|
3471
|
+
if (type === "hidden")
|
|
3472
|
+
return;
|
|
3308
3473
|
this.report(HTML_TAG_NOT_ALLOWED_IN_PARENT, LEVEL_WARN, start, {
|
|
3309
3474
|
tag: raw,
|
|
3310
3475
|
parent: "table",
|
|
@@ -3340,18 +3505,6 @@ class LinterCtx {
|
|
|
3340
3505
|
this.openElements.pop();
|
|
3341
3506
|
}
|
|
3342
3507
|
}
|
|
3343
|
-
flushTableText() {
|
|
3344
|
-
if (!this.pendingTableText)
|
|
3345
|
-
return;
|
|
3346
|
-
const { hasNonWhitespace, start, snippet } = this.pendingTableText;
|
|
3347
|
-
if (hasNonWhitespace) {
|
|
3348
|
-
this.report(HTML_TEXT_NOT_ALLOWED_IN_PARENT, LEVEL_ERROR, start, {
|
|
3349
|
-
mode: this.originalInsertionMode,
|
|
3350
|
-
snippet
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3353
|
-
this.pendingTableText = null;
|
|
3354
|
-
}
|
|
3355
3508
|
startInCaption(name, raw, selfClosing, start, endIndex) {
|
|
3356
3509
|
if (name === "caption" || name === "col" || name === "colgroup" || name === "tbody" || name === "td" || name === "tfoot" || name === "th" || name === "thead" || name === "tr") {
|
|
3357
3510
|
if (this.hasInTableScope("caption")) {
|
|
@@ -3584,14 +3737,24 @@ class LinterCtx {
|
|
|
3584
3737
|
return;
|
|
3585
3738
|
const ns = this.currentNamespace();
|
|
3586
3739
|
if (ns !== NS.html) {
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3740
|
+
let stackIdx = this.openElements.length - 1;
|
|
3741
|
+
let first = true;
|
|
3742
|
+
while (stackIdx > 0) {
|
|
3743
|
+
const f = this.openElements[stackIdx];
|
|
3744
|
+
if (!first && f.ns === NS.html)
|
|
3590
3745
|
break;
|
|
3591
|
-
if (f.name.toLowerCase() === name) {
|
|
3592
|
-
this.openElements.length =
|
|
3746
|
+
if (f.ns !== NS.html && f.name.toLowerCase() === name) {
|
|
3747
|
+
this.openElements.length = stackIdx;
|
|
3593
3748
|
return;
|
|
3594
3749
|
}
|
|
3750
|
+
if (first) {
|
|
3751
|
+
this.report(HTML_UNEXPECTED_END_TAG, LEVEL_WARN, start, {
|
|
3752
|
+
tag: name,
|
|
3753
|
+
mode: this.insertionMode
|
|
3754
|
+
});
|
|
3755
|
+
first = false;
|
|
3756
|
+
}
|
|
3757
|
+
stackIdx--;
|
|
3595
3758
|
}
|
|
3596
3759
|
}
|
|
3597
3760
|
if (VOID_ELEMENTS.has(name)) {
|
|
@@ -3622,19 +3785,33 @@ class LinterCtx {
|
|
|
3622
3785
|
});
|
|
3623
3786
|
return;
|
|
3624
3787
|
}
|
|
3788
|
+
const endIsTableStructural = TABLE_SCOPE_TAGS.has(name);
|
|
3625
3789
|
for (let i = this.openElements.length - 1;i >= 0; i--) {
|
|
3626
3790
|
const f = this.openElements[i];
|
|
3627
3791
|
if (f.ns === NS.html && f.name === name) {
|
|
3792
|
+
for (let j = this.openElements.length - 1;j > i; j--) {
|
|
3793
|
+
const popped = this.openElements[j];
|
|
3794
|
+
if (popped.ns === NS.html && popped.name !== name && !IMPLIED_END_TAGS.has(popped.name) && !(endIsTableStructural && TABLE_SCOPE_TAGS.has(popped.name))) {
|
|
3795
|
+
this.report(HTML_UNCLOSED_BEFORE_END, LEVEL_WARN, start, {
|
|
3796
|
+
tag: name,
|
|
3797
|
+
unclosed: popped.name,
|
|
3798
|
+
mode: this.insertionMode
|
|
3799
|
+
});
|
|
3800
|
+
break;
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3628
3803
|
this.openElements.length = i;
|
|
3629
|
-
if (name === "form")
|
|
3630
|
-
this.formPointer = null;
|
|
3631
3804
|
if (TABLE_SCOPE_TAGS.has(name) || name === "select" || name === "template") {
|
|
3632
3805
|
this.resetInsertionModeAppropriately();
|
|
3633
3806
|
}
|
|
3634
3807
|
return;
|
|
3635
3808
|
}
|
|
3636
|
-
if (f.ns === NS.html && SPECIAL_ELEMENTS.has(f.name)) {
|
|
3637
|
-
|
|
3809
|
+
if (f.ns === NS.html && SPECIAL_ELEMENTS.has(f.name) && !IMPLIED_END_TAGS.has(f.name) && !(endIsTableStructural && TABLE_SCOPE_TAGS.has(f.name))) {
|
|
3810
|
+
this.report(HTML_UNEXPECTED_END_TAG, LEVEL_WARN, start, {
|
|
3811
|
+
tag: name,
|
|
3812
|
+
mode: this.insertionMode
|
|
3813
|
+
});
|
|
3814
|
+
return;
|
|
3638
3815
|
}
|
|
3639
3816
|
}
|
|
3640
3817
|
}
|
|
@@ -4294,6 +4471,24 @@ function lintIdToMessage(id, info) {
|
|
|
4294
4471
|
return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
|
|
4295
4472
|
case "HTML_UNEXPECTED_END_TAG":
|
|
4296
4473
|
return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
|
|
4474
|
+
case "HTML_UNCLOSED_BEFORE_END":
|
|
4475
|
+
return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
|
|
4476
|
+
case "HTML_DUPLICATE_ATTRIBUTE":
|
|
4477
|
+
return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
|
|
4478
|
+
case "HTML_ATTRIBUTES_ON_END_TAG":
|
|
4479
|
+
return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
|
|
4480
|
+
case "HTML_SELF_CLOSING_END_TAG":
|
|
4481
|
+
return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
|
|
4482
|
+
case "HTML_MISSING_ATTRIBUTE_VALUE":
|
|
4483
|
+
return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
|
|
4484
|
+
case "HTML_CDATA_IN_HTML_NAMESPACE":
|
|
4485
|
+
return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
|
|
4486
|
+
case "HTML_BOGUS_COMMENT":
|
|
4487
|
+
return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
|
|
4488
|
+
case "HTML_SVG_ATTR_WILL_LOWERCASE":
|
|
4489
|
+
return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
4490
|
+
case "HTML_MATHML_ATTR_WILL_LOWERCASE":
|
|
4491
|
+
return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
4297
4492
|
case "LINT_ERROR":
|
|
4298
4493
|
return info.message;
|
|
4299
4494
|
default:
|