html-validate 7.11.1 → 7.12.0
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/cjs/browser.d.ts +1 -1
- package/dist/cjs/browser.js +1 -0
- package/dist/cjs/browser.js.map +1 -1
- package/dist/cjs/cli.js +1 -0
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core.d.ts +27 -4
- package/dist/cjs/core.js +174 -4
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/elements.js +25 -8
- package/dist/cjs/elements.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/jest.d.ts +1 -1
- package/dist/cjs/meta-helper.d.ts +1 -1
- package/dist/cjs/rules-helper.d.ts +1 -1
- package/dist/cjs/test-utils.d.ts +1 -1
- package/dist/es/browser.d.ts +1 -1
- package/dist/es/browser.js +1 -1
- package/dist/es/cli.js +1 -0
- package/dist/es/cli.js.map +1 -1
- package/dist/es/core.d.ts +27 -4
- package/dist/es/core.js +174 -5
- package/dist/es/core.js.map +1 -1
- package/dist/es/elements.js +25 -8
- package/dist/es/elements.js.map +1 -1
- package/dist/es/index.d.ts +3 -3
- package/dist/es/index.js +1 -1
- package/dist/es/jest.d.ts +1 -1
- package/dist/es/meta-helper.d.ts +1 -1
- package/dist/es/rules-helper.d.ts +1 -1
- package/dist/es/test-utils.d.ts +1 -1
- package/dist/schema/elements.json +16 -0
- package/package.json +5 -5
package/dist/es/core.js
CHANGED
|
@@ -303,6 +303,10 @@ class Attribute {
|
|
|
303
303
|
if (this.value instanceof DynamicValue) {
|
|
304
304
|
return dynamicMatches;
|
|
305
305
|
}
|
|
306
|
+
/* test against an array of keywords */
|
|
307
|
+
if (Array.isArray(pattern)) {
|
|
308
|
+
return pattern.includes(this.value);
|
|
309
|
+
}
|
|
306
310
|
/* test value against pattern */
|
|
307
311
|
if (pattern instanceof RegExp) {
|
|
308
312
|
return this.value.match(pattern) !== null;
|
|
@@ -466,6 +470,7 @@ const MetaCopyableProperty = [
|
|
|
466
470
|
"interactive",
|
|
467
471
|
"transparent",
|
|
468
472
|
"form",
|
|
473
|
+
"formAssociated",
|
|
469
474
|
"labelable",
|
|
470
475
|
"attributes",
|
|
471
476
|
"permittedContent",
|
|
@@ -1656,6 +1661,10 @@ class UserError extends NestedError {
|
|
|
1656
1661
|
Error.captureStackTrace(this, UserError);
|
|
1657
1662
|
this.name = UserError.name;
|
|
1658
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* @public
|
|
1666
|
+
*/
|
|
1667
|
+
/* istanbul ignore next: default implementation */
|
|
1659
1668
|
prettyFormat() {
|
|
1660
1669
|
return undefined;
|
|
1661
1670
|
}
|
|
@@ -1878,6 +1887,10 @@ const patternProperties = {
|
|
|
1878
1887
|
title: "Mark element as a submittable form element",
|
|
1879
1888
|
type: "boolean"
|
|
1880
1889
|
},
|
|
1890
|
+
formAssociated: {
|
|
1891
|
+
title: "Mark element as a form-associated element",
|
|
1892
|
+
$ref: "#/definitions/FormAssociated"
|
|
1893
|
+
},
|
|
1881
1894
|
labelable: {
|
|
1882
1895
|
title: "Mark this element as labelable",
|
|
1883
1896
|
description: "This element may contain an associated label element.",
|
|
@@ -2023,6 +2036,16 @@ const definitions = {
|
|
|
2023
2036
|
}
|
|
2024
2037
|
}
|
|
2025
2038
|
},
|
|
2039
|
+
FormAssociated: {
|
|
2040
|
+
type: "object",
|
|
2041
|
+
additionalProperties: false,
|
|
2042
|
+
properties: {
|
|
2043
|
+
listed: {
|
|
2044
|
+
type: "boolean",
|
|
2045
|
+
title: "Listed elements have a name attribute and is listed in the form and fieldset elements property."
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2026
2049
|
Permitted: {
|
|
2027
2050
|
type: "array",
|
|
2028
2051
|
items: {
|
|
@@ -2281,6 +2304,9 @@ function migrateAttributes(src) {
|
|
|
2281
2304
|
function migrateElement(src) {
|
|
2282
2305
|
const result = {
|
|
2283
2306
|
...src,
|
|
2307
|
+
...{
|
|
2308
|
+
formAssociated: undefined,
|
|
2309
|
+
},
|
|
2284
2310
|
attributes: migrateAttributes(src),
|
|
2285
2311
|
textContent: src.textContent,
|
|
2286
2312
|
};
|
|
@@ -2291,6 +2317,14 @@ function migrateElement(src) {
|
|
|
2291
2317
|
if (!result.textContent) {
|
|
2292
2318
|
delete result.textContent;
|
|
2293
2319
|
}
|
|
2320
|
+
if (src.formAssociated) {
|
|
2321
|
+
result.formAssociated = {
|
|
2322
|
+
listed: Boolean(src.formAssociated.listed),
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
else {
|
|
2326
|
+
delete result.formAssociated;
|
|
2327
|
+
}
|
|
2294
2328
|
return result;
|
|
2295
2329
|
}
|
|
2296
2330
|
|
|
@@ -3453,6 +3487,16 @@ class Rule {
|
|
|
3453
3487
|
isKeywordIgnored(keyword, matcher = (list, it) => list.includes(it)) {
|
|
3454
3488
|
return isKeywordIgnored(this.options, keyword, matcher);
|
|
3455
3489
|
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Get [[MetaElement]] for the given tag. If no specific metadata is present
|
|
3492
|
+
* the global metadata is returned or null if no global is present.
|
|
3493
|
+
*
|
|
3494
|
+
* @public
|
|
3495
|
+
* @returns A shallow copy of metadata.
|
|
3496
|
+
*/
|
|
3497
|
+
getMetaFor(tagName) {
|
|
3498
|
+
return this.meta.getMetaFor(tagName);
|
|
3499
|
+
}
|
|
3456
3500
|
/**
|
|
3457
3501
|
* Find all tags which has enabled given property.
|
|
3458
3502
|
*/
|
|
@@ -4863,7 +4907,7 @@ class AttributeMisuse extends Rule {
|
|
|
4863
4907
|
if (!meta || !meta.allowed) {
|
|
4864
4908
|
return;
|
|
4865
4909
|
}
|
|
4866
|
-
const details = meta.allowed(node);
|
|
4910
|
+
const details = meta.allowed(node, attr);
|
|
4867
4911
|
if (details) {
|
|
4868
4912
|
this.report({
|
|
4869
4913
|
node,
|
|
@@ -5887,12 +5931,92 @@ class EmptyTitle extends Rule {
|
|
|
5887
5931
|
}
|
|
5888
5932
|
}
|
|
5889
5933
|
|
|
5934
|
+
const CACHE_KEY = Symbol("form-elements");
|
|
5935
|
+
function haveName(name) {
|
|
5936
|
+
return typeof name === "string" && name !== "";
|
|
5937
|
+
}
|
|
5938
|
+
function isRelevant$4(node) {
|
|
5939
|
+
if (node.is("input")) {
|
|
5940
|
+
/* ignore radiobuttons and checkboxes */
|
|
5941
|
+
const type = node.getAttribute("type");
|
|
5942
|
+
return !type || !type.valueMatches(["radio", "checkbox"], true);
|
|
5943
|
+
}
|
|
5944
|
+
return true;
|
|
5945
|
+
}
|
|
5946
|
+
class FormDupName extends Rule {
|
|
5947
|
+
documentation() {
|
|
5948
|
+
return {
|
|
5949
|
+
description: "Each form control must have a unique name.",
|
|
5950
|
+
url: "https://html-validate.org/rules/form-dup-name.html",
|
|
5951
|
+
};
|
|
5952
|
+
}
|
|
5953
|
+
setup() {
|
|
5954
|
+
const selector = this.getSelector();
|
|
5955
|
+
this.on("dom:ready", (event) => {
|
|
5956
|
+
var _a;
|
|
5957
|
+
const { document } = event;
|
|
5958
|
+
const controls = document.querySelectorAll(selector).filter(isRelevant$4);
|
|
5959
|
+
for (const control of controls) {
|
|
5960
|
+
const attr = control.getAttribute("name");
|
|
5961
|
+
const name = attr === null || attr === void 0 ? void 0 : attr.value;
|
|
5962
|
+
if (attr && haveName(name)) {
|
|
5963
|
+
const form = (_a = control.closest("form")) !== null && _a !== void 0 ? _a : document.root;
|
|
5964
|
+
this.validateName(control, form, attr, name);
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
validateName(control, form, attr, name) {
|
|
5970
|
+
const elements = this.getElements(form);
|
|
5971
|
+
if (elements.has(name)) {
|
|
5972
|
+
const context = {
|
|
5973
|
+
name,
|
|
5974
|
+
};
|
|
5975
|
+
this.report({
|
|
5976
|
+
node: control,
|
|
5977
|
+
location: attr.valueLocation,
|
|
5978
|
+
message: 'Duplicate form control name "{{ name }}"',
|
|
5979
|
+
context,
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
else {
|
|
5983
|
+
elements.add(name);
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
getSelector() {
|
|
5987
|
+
const tags = this.getTagsWithProperty("formAssociated").filter((it) => {
|
|
5988
|
+
return this.isListedElement(it);
|
|
5989
|
+
});
|
|
5990
|
+
return tags.join(", ");
|
|
5991
|
+
}
|
|
5992
|
+
isListedElement(tagName) {
|
|
5993
|
+
const meta = this.getMetaFor(tagName);
|
|
5994
|
+
/* istanbul ignore if: the earlier check for getTagsWithProperty ensures
|
|
5995
|
+
* these will actually be set so this is just an untestable fallback */
|
|
5996
|
+
if (!meta || !meta.formAssociated) {
|
|
5997
|
+
return false;
|
|
5998
|
+
}
|
|
5999
|
+
return meta.formAssociated.listed;
|
|
6000
|
+
}
|
|
6001
|
+
getElements(form) {
|
|
6002
|
+
const existing = form.cacheGet(CACHE_KEY);
|
|
6003
|
+
if (existing) {
|
|
6004
|
+
return existing;
|
|
6005
|
+
}
|
|
6006
|
+
else {
|
|
6007
|
+
const elements = new Set();
|
|
6008
|
+
form.cacheSet(CACHE_KEY, elements);
|
|
6009
|
+
return elements;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
|
|
5890
6014
|
const defaults$j = {
|
|
5891
6015
|
allowMultipleH1: false,
|
|
5892
6016
|
minInitialRank: "h1",
|
|
5893
6017
|
sectioningRoots: ["dialog", '[role="dialog"]'],
|
|
5894
6018
|
};
|
|
5895
|
-
function isRelevant$
|
|
6019
|
+
function isRelevant$3(event) {
|
|
5896
6020
|
const node = event.target;
|
|
5897
6021
|
return Boolean(node.meta && node.meta.heading);
|
|
5898
6022
|
}
|
|
@@ -5960,7 +6084,7 @@ class HeadingLevel extends Rule {
|
|
|
5960
6084
|
};
|
|
5961
6085
|
}
|
|
5962
6086
|
setup() {
|
|
5963
|
-
this.on("tag:start", isRelevant$
|
|
6087
|
+
this.on("tag:start", isRelevant$3, (event) => this.onTagStart(event));
|
|
5964
6088
|
this.on("tag:ready", (event) => this.onTagReady(event));
|
|
5965
6089
|
this.on("tag:close", (event) => this.onTagClose(event));
|
|
5966
6090
|
}
|
|
@@ -6494,6 +6618,42 @@ class MapDupName extends Rule {
|
|
|
6494
6618
|
}
|
|
6495
6619
|
}
|
|
6496
6620
|
|
|
6621
|
+
function isRelevant$2(event) {
|
|
6622
|
+
return event.target.is("map");
|
|
6623
|
+
}
|
|
6624
|
+
function hasStaticValue(attr) {
|
|
6625
|
+
return Boolean(attr && !(attr.value instanceof DynamicValue));
|
|
6626
|
+
}
|
|
6627
|
+
class MapIdName extends Rule {
|
|
6628
|
+
documentation() {
|
|
6629
|
+
return {
|
|
6630
|
+
description: "When the `id` attribute is present on a `<map>` element it must be equal to the `name` attribute.",
|
|
6631
|
+
url: "https://html-validate.org/rules/map-id-name.html",
|
|
6632
|
+
};
|
|
6633
|
+
}
|
|
6634
|
+
setup() {
|
|
6635
|
+
this.on("tag:ready", isRelevant$2, (event) => {
|
|
6636
|
+
var _a;
|
|
6637
|
+
const { target } = event;
|
|
6638
|
+
const id = target.getAttribute("id");
|
|
6639
|
+
const name = target.getAttribute("name");
|
|
6640
|
+
// /* ignore missing attributes or dynamic value */
|
|
6641
|
+
if (!hasStaticValue(id) || !hasStaticValue(name)) {
|
|
6642
|
+
return;
|
|
6643
|
+
}
|
|
6644
|
+
/* ignore when id and name is the same */
|
|
6645
|
+
if (id.value === name.value) {
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
this.report({
|
|
6649
|
+
node: event.target,
|
|
6650
|
+
message: `"id" and "name" attribute must be the same on <map> elements`,
|
|
6651
|
+
location: (_a = id.valueLocation) !== null && _a !== void 0 ? _a : name.valueLocation,
|
|
6652
|
+
});
|
|
6653
|
+
});
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6497
6657
|
class MissingDoctype extends Rule {
|
|
6498
6658
|
documentation() {
|
|
6499
6659
|
return {
|
|
@@ -8958,12 +9118,14 @@ const bundledRules = {
|
|
|
8958
9118
|
"element-required-content": ElementRequiredContent,
|
|
8959
9119
|
"empty-heading": EmptyHeading,
|
|
8960
9120
|
"empty-title": EmptyTitle,
|
|
9121
|
+
"form-dup-name": FormDupName,
|
|
8961
9122
|
"heading-level": HeadingLevel,
|
|
8962
9123
|
"id-pattern": IdPattern,
|
|
8963
9124
|
"input-attributes": InputAttributes,
|
|
8964
9125
|
"input-missing-label": InputMissingLabel,
|
|
8965
9126
|
"long-title": LongTitle,
|
|
8966
9127
|
"map-dup-name": MapDupName,
|
|
9128
|
+
"map-id-name": MapIdName,
|
|
8967
9129
|
"meta-refresh": MetaRefresh,
|
|
8968
9130
|
"missing-doctype": MissingDoctype,
|
|
8969
9131
|
"multiple-labeled-controls": MultipleLabeledControls,
|
|
@@ -9072,9 +9234,11 @@ const config$1 = {
|
|
|
9072
9234
|
"element-required-content": "error",
|
|
9073
9235
|
"empty-heading": "error",
|
|
9074
9236
|
"empty-title": "error",
|
|
9237
|
+
"form-dup-name": "error",
|
|
9075
9238
|
"input-attributes": "error",
|
|
9076
9239
|
"long-title": "error",
|
|
9077
9240
|
"map-dup-name": "error",
|
|
9241
|
+
"map-id-name": "error",
|
|
9078
9242
|
"meta-refresh": "error",
|
|
9079
9243
|
"multiple-labeled-controls": "error",
|
|
9080
9244
|
"no-autoplay": ["error", { include: ["audio", "video"] }],
|
|
@@ -9135,6 +9299,7 @@ const config = {
|
|
|
9135
9299
|
"element-required-attributes": "error",
|
|
9136
9300
|
"element-required-content": "error",
|
|
9137
9301
|
"map-dup-name": "error",
|
|
9302
|
+
"map-id-name": "error",
|
|
9138
9303
|
"multiple-labeled-controls": "error",
|
|
9139
9304
|
"no-deprecated-attr": "error",
|
|
9140
9305
|
"no-dup-attr": "error",
|
|
@@ -9964,6 +10129,8 @@ class Parser {
|
|
|
9964
10129
|
}
|
|
9965
10130
|
/* resolve any dynamic meta element properties */
|
|
9966
10131
|
this.dom.resolveMeta(this.metaTable);
|
|
10132
|
+
/* enable cache on root element, all children already have cached enabled */
|
|
10133
|
+
this.dom.root.cacheEnable();
|
|
9967
10134
|
/* trigger any rules waiting for DOM ready */
|
|
9968
10135
|
this.trigger("dom:ready", {
|
|
9969
10136
|
document: this.dom,
|
|
@@ -10795,6 +10962,8 @@ class Engine {
|
|
|
10795
10962
|
var _a, _b;
|
|
10796
10963
|
/* wait for a tag to open and find the current block by using its parent */
|
|
10797
10964
|
if (directiveBlock === null) {
|
|
10965
|
+
/* istanbul ignore next: there will always be a parent (root element if
|
|
10966
|
+
* nothing else) but typescript doesn't know that */
|
|
10798
10967
|
directiveBlock = (_b = (_a = data.target.parent) === null || _a === void 0 ? void 0 : _a.unique) !== null && _b !== void 0 ? _b : null;
|
|
10799
10968
|
}
|
|
10800
10969
|
/* disable rules directly on the node so it will be recorded for later,
|
|
@@ -11223,7 +11392,7 @@ class HtmlValidate {
|
|
|
11223
11392
|
/** @public */
|
|
11224
11393
|
const name = "html-validate";
|
|
11225
11394
|
/** @public */
|
|
11226
|
-
const version = "7.
|
|
11395
|
+
const version = "7.12.0";
|
|
11227
11396
|
/** @public */
|
|
11228
11397
|
const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
|
|
11229
11398
|
|
|
@@ -11666,5 +11835,5 @@ function getFormatter(name) {
|
|
|
11666
11835
|
return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
|
|
11667
11836
|
}
|
|
11668
11837
|
|
|
11669
|
-
export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, definePlugin as j, getFormatter as k, legacyRequire as l, ensureError as m, configDataFromFile as n, compatibilityCheck as o, presets as p, codeframe as q, ruleExists as r, sliceLocation as s, isTextNode as t, isElementNode as u, version as v, generateIdSelector as w, name as x, bugs as y };
|
|
11838
|
+
export { Attribute as A, Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, Reporter as h, TemplateExtractor as i, definePlugin as j, getFormatter as k, legacyRequire as l, ensureError as m, configDataFromFile as n, compatibilityCheck as o, presets as p, codeframe as q, ruleExists as r, sliceLocation as s, isTextNode as t, isElementNode as u, version as v, generateIdSelector as w, name as x, bugs as y };
|
|
11670
11839
|
//# sourceMappingURL=core.js.map
|