@markuplint/rules 2.4.3 → 3.0.0-alpha.1
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 +2 -6
- package/lib/attr-check.js +1 -1
- package/lib/attr-duplication/index.js +10 -7
- package/lib/attr-value-quotes/index.js +22 -26
- package/lib/case-sensitive-attr-name/index.d.ts +1 -5
- package/lib/case-sensitive-attr-name/index.js +52 -82
- package/lib/case-sensitive-tag-name/index.js +39 -31
- package/lib/character-reference/index.d.ts +2 -1
- package/lib/character-reference/index.js +6 -8
- package/lib/class-naming/index.js +12 -12
- package/lib/create-message.d.ts +1 -1
- package/lib/create-message.js +5 -5
- package/lib/deprecated-attr/index.js +17 -18
- package/lib/deprecated-element/index.js +5 -7
- package/lib/disallowed-element/index.js +8 -9
- package/lib/doctype/index.d.ts +1 -1
- package/lib/doctype/index.js +8 -8
- package/lib/end-tag/index.js +5 -2
- package/lib/helpers.d.ts +15 -28
- package/lib/helpers.js +42 -156
- package/lib/id-duplication/index.js +4 -6
- package/lib/index.d.ts +12 -21
- package/lib/index.js +0 -8
- package/lib/ineffective-attr/index.js +20 -28
- package/lib/invalid-attr/index.js +83 -83
- package/lib/landmark-roles/index.d.ts +2 -2
- package/lib/landmark-roles/index.js +10 -10
- package/lib/no-boolean-attr-value/index.js +34 -35
- package/lib/no-default-value/index.js +21 -25
- package/lib/no-hard-code-id/index.js +10 -14
- package/lib/no-refer-to-non-existent-id/index.d.ts +4 -1
- package/lib/no-refer-to-non-existent-id/index.js +77 -83
- package/lib/no-use-event-handler-attr/index.js +21 -23
- package/lib/permitted-contents/choice.d.ts +3 -0
- package/lib/permitted-contents/choice.js +37 -0
- package/lib/permitted-contents/complex-branch.d.ts +13 -0
- package/lib/permitted-contents/complex-branch.js +27 -0
- package/lib/permitted-contents/content-model.d.ts +2 -0
- package/lib/permitted-contents/content-model.js +49 -0
- package/lib/permitted-contents/count-pattern.d.ts +13 -0
- package/lib/permitted-contents/count-pattern.js +155 -0
- package/lib/permitted-contents/debug.d.ts +2 -0
- package/lib/permitted-contents/debug.js +5 -0
- package/lib/permitted-contents/index.d.ts +2 -5
- package/lib/permitted-contents/index.js +78 -249
- package/lib/permitted-contents/matches-selector.d.ts +3 -0
- package/lib/permitted-contents/matches-selector.js +185 -0
- package/lib/permitted-contents/order.d.ts +13 -0
- package/lib/permitted-contents/order.js +89 -0
- package/lib/permitted-contents/recursive-branch.d.ts +4 -0
- package/lib/permitted-contents/recursive-branch.js +43 -0
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +6 -0
- package/lib/permitted-contents/represent-transparent-nodes.js +79 -0
- package/lib/permitted-contents/start.d.ts +12 -0
- package/lib/permitted-contents/start.js +71 -0
- package/lib/permitted-contents/transparent.d.ts +2 -0
- package/lib/permitted-contents/transparent.js +34 -0
- package/lib/permitted-contents/types.d.ts +45 -0
- package/lib/permitted-contents/types.js +2 -0
- package/lib/permitted-contents/utils.d.ts +32 -0
- package/lib/permitted-contents/utils.js +184 -0
- package/lib/require-accessible-name/index.d.ts +5 -1
- package/lib/require-accessible-name/index.js +11 -4
- package/lib/required-attr/index.js +16 -25
- package/lib/required-element/index.d.ts +1 -1
- package/lib/required-element/index.js +6 -6
- package/lib/required-h1/index.d.ts +2 -2
- package/lib/required-h1/index.js +3 -3
- package/lib/use-list/index.js +2 -2
- package/lib/wai-aria/checkings/abstract-role.d.ts +3 -0
- package/lib/wai-aria/checkings/abstract-role.js +22 -0
- package/lib/wai-aria/checkings/default-value.d.ts +6 -0
- package/lib/wai-aria/checkings/default-value.js +17 -0
- package/lib/wai-aria/checkings/deprecated-props.d.ts +7 -0
- package/lib/wai-aria/checkings/deprecated-props.js +18 -0
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +7 -0
- package/lib/wai-aria/checkings/disallowed-prop.js +22 -0
- package/lib/wai-aria/checkings/implicit-props.d.ts +7 -0
- package/lib/wai-aria/checkings/implicit-props.js +50 -0
- package/lib/wai-aria/checkings/implicit-role.d.ts +3 -0
- package/lib/wai-aria/checkings/implicit-role.js +21 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +11 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.js +47 -0
- package/lib/wai-aria/checkings/no-global-prop.d.ts +6 -0
- package/lib/wai-aria/checkings/no-global-prop.js +14 -0
- package/lib/wai-aria/checkings/non-existant-role.d.ts +3 -0
- package/lib/wai-aria/checkings/non-existant-role.js +28 -0
- package/lib/wai-aria/checkings/permitted-roles.d.ts +3 -0
- package/lib/wai-aria/checkings/permitted-roles.js +28 -0
- package/lib/wai-aria/checkings/presentational-children.d.ts +29 -0
- package/lib/wai-aria/checkings/presentational-children.js +60 -0
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +11 -0
- package/lib/wai-aria/checkings/required-owned-elements.js +63 -0
- package/lib/wai-aria/checkings/required-prop.d.ts +9 -0
- package/lib/wai-aria/checkings/required-prop.js +27 -0
- package/lib/wai-aria/checkings/value.d.ts +7 -0
- package/lib/wai-aria/checkings/value.js +21 -0
- package/lib/wai-aria/index.d.ts +1 -8
- package/lib/wai-aria/index.js +66 -207
- package/lib/wai-aria/types.d.ts +13 -0
- package/lib/wai-aria/types.js +2 -0
- package/package.json +8 -7
- package/schema.json +0 -12
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/attr-equal-space-after/index.d.ts +0 -3
- package/lib/attr-equal-space-after/index.js +0 -90
- package/lib/attr-equal-space-before/index.d.ts +0 -3
- package/lib/attr-equal-space-before/index.js +0 -90
- package/lib/attr-spacing/index.d.ts +0 -6
- package/lib/attr-spacing/index.js +0 -100
- package/lib/indentation/index.d.ts +0 -7
- package/lib/indentation/index.js +0 -208
- package/lib/permitted-contents/array.combination.d.ts +0 -1
- package/lib/permitted-contents/array.combination.js +0 -33
- package/lib/permitted-contents/permitted-content.spec-to-regexp.d.ts +0 -11
- package/lib/permitted-contents/permitted-content.spec-to-regexp.js +0 -225
- package/lib/permitted-contents/unfold-content-models-to-tags.d.ts +0 -2
- package/lib/permitted-contents/unfold-content-models-to-tags.js +0 -8
|
@@ -1,282 +1,111 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
3
|
const ml_core_1 = require("@markuplint/ml-core");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const unfold_content_models_to_tags_1 = tslib_1.__importDefault(require("./unfold-content-models-to-tags"));
|
|
8
|
-
const expMapOnNodeId = new Map();
|
|
4
|
+
const content_model_1 = require("./content-model");
|
|
5
|
+
const represent_transparent_nodes_1 = require("./represent-transparent-nodes");
|
|
9
6
|
exports.default = (0, ml_core_1.createRule)({
|
|
10
7
|
defaultValue: [],
|
|
11
8
|
defaultOptions: {
|
|
12
9
|
ignoreHasMutableChildren: true,
|
|
13
10
|
},
|
|
14
11
|
async verify({ document, report, t }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!node.rule.value) {
|
|
12
|
+
await document.walkOn('Element', el => {
|
|
13
|
+
var _a, _b, _c;
|
|
14
|
+
if (!el.rule.value) {
|
|
19
15
|
return;
|
|
20
16
|
}
|
|
21
|
-
if (
|
|
17
|
+
if (el.rule.option.ignoreHasMutableChildren && el.hasMutableChildren()) {
|
|
22
18
|
return;
|
|
23
19
|
}
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
let matched = false;
|
|
37
|
-
if (spec.conditional) {
|
|
38
|
-
for (const conditional of spec.conditional) {
|
|
39
|
-
matched =
|
|
40
|
-
('hasAttr' in conditional.condition && node.hasAttribute(conditional.condition.hasAttr)) ||
|
|
41
|
-
('parent' in conditional.condition &&
|
|
42
|
-
!!node.parentNode &&
|
|
43
|
-
node.parentNode.type === 'Element' &&
|
|
44
|
-
node.parentNode.matches(conditional.condition.parent));
|
|
45
|
-
// console.log({ ...conditional, matched });
|
|
46
|
-
if (matched) {
|
|
47
|
-
try {
|
|
48
|
-
const parentExp = getRegExpFromParentNode(document.specs, node, expGen);
|
|
49
|
-
const exp = expGen.specToRegExp(conditional.contents, parentExp, node.ns);
|
|
50
|
-
const conditionalResult = match(exp, childNodes, node.ns, false);
|
|
51
|
-
if (!conditionalResult) {
|
|
52
|
-
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0*}" {1}', node.nodeName, 'element')), 'invalid');
|
|
53
|
-
report({
|
|
54
|
-
scope: node,
|
|
55
|
-
message: specType !== 'Any Language'
|
|
56
|
-
? t('{0} according to {1}', message, `the ${specType} specification`)
|
|
57
|
-
: message,
|
|
58
|
-
line: node.startLine,
|
|
59
|
-
col: node.startCol,
|
|
60
|
-
raw: node.raw,
|
|
61
|
-
});
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (e) {
|
|
66
|
-
if (e instanceof Error) {
|
|
67
|
-
// eslint-disable-next-line no-console
|
|
68
|
-
console.warn(node.raw, 'conditional', conditional, e.message);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
throw e;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
20
|
+
const results = (0, content_model_1.contentModel)(el, el.rule.value, el.rule.option);
|
|
21
|
+
for (const { type, scope, query, hint } of results) {
|
|
22
|
+
let message = '';
|
|
23
|
+
if (hint) {
|
|
24
|
+
if (hint.max != null) {
|
|
25
|
+
message =
|
|
26
|
+
t('there is more content than it needs') +
|
|
27
|
+
t('. ') +
|
|
28
|
+
t('the max number of elements required is {0}', `${hint.max}`);
|
|
75
29
|
}
|
|
76
30
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!specResult) {
|
|
82
|
-
const message = t('{0} is {1:c}', t('{0} of {1}', t('the {0}', 'content'), t('the "{0*}" {1}', node.nodeName, 'element')), 'invalid');
|
|
83
|
-
report({
|
|
84
|
-
scope: node,
|
|
85
|
-
message: specType !== 'Any Language'
|
|
86
|
-
? t('{0} according to {1}', message, `the ${specType} specification`)
|
|
87
|
-
: message,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'MATCHED':
|
|
33
|
+
case 'MATCHED_ZERO': {
|
|
34
|
+
break;
|
|
90
35
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
36
|
+
case 'MISSING_NODE_ONE_OR_MORE': {
|
|
37
|
+
message =
|
|
38
|
+
message ||
|
|
39
|
+
t('Require {0}', t('one or more elements')) + t('. ') + '(' + t('Need "{0*}"', query) + ')';
|
|
40
|
+
report({
|
|
41
|
+
scope,
|
|
42
|
+
message,
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
99
45
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
const parentExp = getRegExpFromParentNode(document.specs, node, expGen);
|
|
107
|
-
try {
|
|
108
|
-
const exp = expGen.specToRegExp(rule.contents, parentExp, node.ns);
|
|
109
|
-
// Evaluate the custom element if the optional schema.
|
|
110
|
-
const r = match(exp, childNodes, node.ns, node.isCustomElement);
|
|
111
|
-
if (!r) {
|
|
46
|
+
case 'MISSING_NODE_REQUIRED': {
|
|
47
|
+
message =
|
|
48
|
+
message ||
|
|
49
|
+
t('Require {0}', t('an {0}', 'element')) + t('. ') + '(' + t('Need "{0*}"', query) + ')';
|
|
112
50
|
report({
|
|
113
|
-
scope
|
|
114
|
-
message
|
|
115
|
-
line: node.startLine,
|
|
116
|
-
col: node.startCol,
|
|
117
|
-
raw: node.raw,
|
|
51
|
+
scope,
|
|
52
|
+
message,
|
|
118
53
|
});
|
|
119
|
-
|
|
54
|
+
break;
|
|
120
55
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
56
|
+
case 'UNEXPECTED_EXTRA_NODE': {
|
|
57
|
+
const not = (_a = hint.not) !== null && _a !== void 0 ? _a : scope;
|
|
58
|
+
message =
|
|
59
|
+
message ||
|
|
60
|
+
(represent_transparent_nodes_1.transparentMode.has(scope)
|
|
61
|
+
? t('{0} is not allowed in {1} through the transparent model in this context', name(not, t), name(el, t))
|
|
62
|
+
: t('{0} is not allowed in {1} in this context', name(not, t), name(el, t)));
|
|
63
|
+
report({
|
|
64
|
+
scope: not,
|
|
65
|
+
message,
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'TRANSPARENT_MODEL_DISALLOWS': {
|
|
70
|
+
const not = (_b = hint.not) !== null && _b !== void 0 ? _b : scope;
|
|
71
|
+
const tp = (_c = hint.transparent) !== null && _c !== void 0 ? _c : el;
|
|
72
|
+
report({
|
|
73
|
+
scope: not,
|
|
74
|
+
message: t('{0} is {1} but {2}', name(tp, t), t('a {0}', 'transparent model'), t('also disallows {0} in this context', name(not, t))),
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'VOID_ELEMENT': {
|
|
79
|
+
report({
|
|
80
|
+
scope: el,
|
|
81
|
+
message: t('{0} is {1}', t('the {0}', 'element'), t('a {0}', 'void element')),
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
126
84
|
}
|
|
127
|
-
|
|
128
|
-
throw
|
|
85
|
+
default: {
|
|
86
|
+
throw new Error('Unreachable code');
|
|
129
87
|
}
|
|
130
88
|
}
|
|
131
89
|
}
|
|
90
|
+
represent_transparent_nodes_1.transparentMode.clear();
|
|
132
91
|
});
|
|
133
|
-
expMapOnNodeId.clear();
|
|
134
92
|
},
|
|
135
93
|
});
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// FIXME: https://github.com/markuplint/markuplint/issues/388
|
|
140
|
-
if (!evalCustomElement && node.type === 'Element' && node.isCustomElement) {
|
|
141
|
-
return '';
|
|
142
|
-
}
|
|
143
|
-
if (node.type !== 'Element') {
|
|
144
|
-
return '<#text>';
|
|
145
|
-
}
|
|
146
|
-
ownNS = ownNS || 'html';
|
|
147
|
-
const name = node.nameWithNS.replace(new RegExp(`^${ownNS}:`), '');
|
|
148
|
-
return `<${name}>`;
|
|
149
|
-
})
|
|
150
|
-
.join('');
|
|
151
|
-
}
|
|
152
|
-
function getRegExpFromNode(specs, node, expGen) {
|
|
153
|
-
var _a;
|
|
154
|
-
// console.log({ n: node.nodeName });
|
|
155
|
-
if (expMapOnNodeId.has(node.uuid)) {
|
|
156
|
-
return expMapOnNodeId.get(node.uuid);
|
|
94
|
+
function name(scope, t) {
|
|
95
|
+
if (scope.is(scope.ELEMENT_NODE)) {
|
|
96
|
+
return t('the "{0}" {1}', scope.localName, 'element');
|
|
157
97
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const contentRule = spec ? spec.contents : true;
|
|
161
|
-
const exp = expGen.specToRegExp(contentRule, parentExp, node.ns || null);
|
|
162
|
-
expMapOnNodeId.set(node.uuid, exp);
|
|
163
|
-
return exp;
|
|
164
|
-
}
|
|
165
|
-
function getRegExpFromParentNode(specs, node, expGen) {
|
|
166
|
-
// console.log({ p: node.nodeName });
|
|
167
|
-
const parentExp = node.parentNode ? getRegExpFromNode(specs, node.parentNode, expGen) : null;
|
|
168
|
-
return parentExp;
|
|
169
|
-
}
|
|
170
|
-
const matchingCacheMap = new Map();
|
|
171
|
-
function match(exp, childNodes, ownNS, evalCustomElement) {
|
|
172
|
-
const target = normalization(childNodes, ownNS, evalCustomElement);
|
|
173
|
-
const cacheKey = target + exp.source + childNodes.map(n => (n.type == 'Element' ? n.toNormalizeString() : n.originRaw)).join('');
|
|
174
|
-
const res = matchingCacheMap.get(cacheKey);
|
|
175
|
-
if (res != null) {
|
|
176
|
-
return res;
|
|
98
|
+
if (scope.is(scope.TEXT_NODE)) {
|
|
99
|
+
return t('the {0}', 'text node');
|
|
177
100
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return matched;
|
|
181
|
-
}
|
|
182
|
-
function _match(target, exp, childNodes) {
|
|
183
|
-
const result = exp.exec(target);
|
|
184
|
-
if (!result) {
|
|
185
|
-
return false;
|
|
101
|
+
if (scope.is(scope.CDATA_SECTION_NODE)) {
|
|
102
|
+
return t('the {0}', 'comment');
|
|
186
103
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!capGroups) {
|
|
190
|
-
return true;
|
|
104
|
+
if (scope.is(scope.DOCUMENT_TYPE_NODE)) {
|
|
105
|
+
return t('the {0}', 'doctype');
|
|
191
106
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const matched = capGroups[groupName];
|
|
195
|
-
if (!matched) {
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
const [type, , ..._selector] = groupName.split(/(?<=[a-z0-9])_/gi);
|
|
199
|
-
// console.log({ type, _selector });
|
|
200
|
-
switch (type) {
|
|
201
|
-
case 'ACM': {
|
|
202
|
-
const [model, tag] = _selector;
|
|
203
|
-
const selectors = (0, unfold_content_models_to_tags_1.default)(`#${model}`).filter(selector => {
|
|
204
|
-
const [, tagName] = /^([^[\]]+)(?:\[[^\]]+\])?$/.exec(selector) || [];
|
|
205
|
-
return tagName.toLowerCase() === tag.toLowerCase();
|
|
206
|
-
});
|
|
207
|
-
for (const node of childNodes) {
|
|
208
|
-
if (node.type === 'Text') {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (node.nodeName.toLowerCase() !== tag.toLowerCase()) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
for (const selector of selectors) {
|
|
215
|
-
const matched = node.matches(selector);
|
|
216
|
-
// console.log({ raw: node.raw, selector, matched });
|
|
217
|
-
if (matched) {
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
case 'NAD': {
|
|
226
|
-
let targetsMaybeIncludesNotAllowedDescendants = Array.from(new Set(matched.split(/><|<|>/g).filter(_ => _)));
|
|
227
|
-
const contents = new Set();
|
|
228
|
-
const transparentGroupName = groupNames.find(name => /^TRANSPARENT_[0-9]+$/.test(name));
|
|
229
|
-
const inTransparent = _selector.includes('__InTRANSPARENT')
|
|
230
|
-
? transparentGroupName && capGroups[transparentGroupName]
|
|
231
|
-
? capGroups[transparentGroupName].split(/><|<|>/g).filter(_ => _)
|
|
232
|
-
: null
|
|
233
|
-
: null;
|
|
234
|
-
_selector.forEach(content => {
|
|
235
|
-
if (content[0] === '_') {
|
|
236
|
-
(0, unfold_content_models_to_tags_1.default)(content.replace('_', '#')).forEach(tag => contents.add(tag));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
contents.add(content);
|
|
240
|
-
});
|
|
241
|
-
const selectors = Array.from(contents);
|
|
242
|
-
targetsMaybeIncludesNotAllowedDescendants = targetsMaybeIncludesNotAllowedDescendants.filter(content => inTransparent ? inTransparent.includes(content) : true);
|
|
243
|
-
// console.log({
|
|
244
|
-
// groupName,
|
|
245
|
-
// matched,
|
|
246
|
-
// _selector,
|
|
247
|
-
// type,
|
|
248
|
-
// selectors,
|
|
249
|
-
// inTransparent,
|
|
250
|
-
// targetsMaybeIncludesNotAllowedDescendants,
|
|
251
|
-
// });
|
|
252
|
-
for (const node of childNodes) {
|
|
253
|
-
for (const target of targetsMaybeIncludesNotAllowedDescendants) {
|
|
254
|
-
if (node.type === 'Text') {
|
|
255
|
-
if (selectors.includes('#text')) {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
if (node.nodeName.toLowerCase() !== target.toLowerCase()) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
for (const selector of selectors) {
|
|
264
|
-
// console.log({ selector, target });
|
|
265
|
-
// Self
|
|
266
|
-
if (node.matches(selector)) {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
// Descendants
|
|
270
|
-
const nodeList = node.querySelectorAll(selector);
|
|
271
|
-
if (nodeList.length) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
107
|
+
if (scope.is(scope.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
108
|
+
return t('the {0}', 'code block');
|
|
280
109
|
}
|
|
281
|
-
return
|
|
110
|
+
return t('the {0}', 'node');
|
|
282
111
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChildNode, Options, Result, Specs } from './types';
|
|
2
|
+
export declare type SelectorResult = Result<'UNMATCHED_SELECTOR_BUT_MAY_EMPTY' | 'MISSING_NODE' | 'UNMATCHED_SELECTORS'>;
|
|
3
|
+
export declare function matchesSelector(query: string, node: ChildNode | undefined, specs: Specs, options: Options, depth: number): SelectorResult;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesSelector = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
const debug_1 = require("./debug");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
function matchesSelector(query, node, specs, options, depth) {
|
|
8
|
+
const nodeLog = debug_1.cmLog.extend(`node#${depth}`);
|
|
9
|
+
const { selector, hasText, hasCustom } = optCondition(query, specs);
|
|
10
|
+
if (node == null) {
|
|
11
|
+
if (hasText) {
|
|
12
|
+
nodeLog('<#text>.matches(%s) => ""', query);
|
|
13
|
+
return {
|
|
14
|
+
type: 'MATCHED_ZERO',
|
|
15
|
+
matched: node ? [node] : [],
|
|
16
|
+
unmatched: [],
|
|
17
|
+
zeroMatch: true,
|
|
18
|
+
query,
|
|
19
|
+
hint: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
type: 'MISSING_NODE',
|
|
24
|
+
matched: [],
|
|
25
|
+
unmatched: [],
|
|
26
|
+
zeroMatch: false,
|
|
27
|
+
query,
|
|
28
|
+
hint: {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (node.is(node.TEXT_NODE)) {
|
|
32
|
+
if (hasText) {
|
|
33
|
+
nodeLog('<#text>.matches(%s) => "%s"', query, node.raw.trim());
|
|
34
|
+
return {
|
|
35
|
+
type: 'MATCHED',
|
|
36
|
+
matched: [node],
|
|
37
|
+
unmatched: [],
|
|
38
|
+
zeroMatch: true,
|
|
39
|
+
query,
|
|
40
|
+
hint: {},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (node.isWhitespace()) {
|
|
44
|
+
nodeLog('<#text>.matches(%s) => WHITESPACE', query);
|
|
45
|
+
return {
|
|
46
|
+
type: 'MATCHED',
|
|
47
|
+
matched: [node],
|
|
48
|
+
unmatched: [],
|
|
49
|
+
zeroMatch: true,
|
|
50
|
+
query,
|
|
51
|
+
hint: {},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Disallows a text node
|
|
55
|
+
return {
|
|
56
|
+
type: 'UNEXPECTED_EXTRA_NODE',
|
|
57
|
+
matched: [],
|
|
58
|
+
unmatched: [node],
|
|
59
|
+
zeroMatch: false,
|
|
60
|
+
query,
|
|
61
|
+
hint: {},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (node.is(node.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
65
|
+
nodeLog('%s.matches(%s) => PBlock', node.raw, query);
|
|
66
|
+
return {
|
|
67
|
+
type: 'MATCHED',
|
|
68
|
+
matched: [node],
|
|
69
|
+
unmatched: [],
|
|
70
|
+
zeroMatch: !!hasText,
|
|
71
|
+
query,
|
|
72
|
+
hint: {},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (node.is(node.ELEMENT_NODE)) {
|
|
76
|
+
if (node.elementType !== 'html' && hasCustom) {
|
|
77
|
+
nodeLog('%s.matches(%s) => CustomElement', node.raw, query);
|
|
78
|
+
return {
|
|
79
|
+
type: 'MATCHED',
|
|
80
|
+
matched: [node],
|
|
81
|
+
unmatched: [],
|
|
82
|
+
zeroMatch: !!hasText,
|
|
83
|
+
query,
|
|
84
|
+
hint: {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const result = (0, utils_1.matches)(selector, node, specs);
|
|
88
|
+
nodeLog('%s.matches(%s) => %s', node.raw, query, result.matched);
|
|
89
|
+
if (result.matched) {
|
|
90
|
+
return {
|
|
91
|
+
type: 'MATCHED',
|
|
92
|
+
matched: [node],
|
|
93
|
+
unmatched: [],
|
|
94
|
+
zeroMatch: !!hasText,
|
|
95
|
+
query,
|
|
96
|
+
hint: {},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (hasText) {
|
|
100
|
+
return {
|
|
101
|
+
type: 'UNMATCHED_SELECTOR_BUT_MAY_EMPTY',
|
|
102
|
+
matched: [],
|
|
103
|
+
unmatched: [node],
|
|
104
|
+
zeroMatch: true,
|
|
105
|
+
query,
|
|
106
|
+
hint: {
|
|
107
|
+
not: result.not,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
type: 'UNMATCHED_SELECTORS',
|
|
113
|
+
matched: [],
|
|
114
|
+
unmatched: [node],
|
|
115
|
+
zeroMatch: false,
|
|
116
|
+
query,
|
|
117
|
+
hint: {
|
|
118
|
+
not: result.not,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
type: 'MATCHED',
|
|
124
|
+
matched: [node],
|
|
125
|
+
unmatched: [],
|
|
126
|
+
zeroMatch: !!hasText,
|
|
127
|
+
query,
|
|
128
|
+
hint: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
exports.matchesSelector = matchesSelector;
|
|
132
|
+
const conditionWithoutSpecs = {
|
|
133
|
+
'#custom': {
|
|
134
|
+
selector: '#custom',
|
|
135
|
+
hasCustom: true,
|
|
136
|
+
hasText: false,
|
|
137
|
+
},
|
|
138
|
+
'#text': {
|
|
139
|
+
selector: '#text',
|
|
140
|
+
hasCustom: false,
|
|
141
|
+
hasText: true,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
const optConditionSpecsBaseCaches = new Map();
|
|
145
|
+
function optCondition(query, specs) {
|
|
146
|
+
var _a;
|
|
147
|
+
const condWithoutSpecs = conditionWithoutSpecs[query];
|
|
148
|
+
if (condWithoutSpecs) {
|
|
149
|
+
return condWithoutSpecs;
|
|
150
|
+
}
|
|
151
|
+
const queryCaches = (_a = optConditionSpecsBaseCaches.get(specs)) !== null && _a !== void 0 ? _a : new Map();
|
|
152
|
+
const cached = queryCaches.get(query);
|
|
153
|
+
if (cached) {
|
|
154
|
+
return cached;
|
|
155
|
+
}
|
|
156
|
+
let hasCustom = false;
|
|
157
|
+
let hasText = false;
|
|
158
|
+
const selector = query.replace(/^(?::model\(([^)]+)\)|#([a-z-]+))(.*)$/, (_, $model, _model, $last) => {
|
|
159
|
+
const _selectors = (0, ml_spec_1.contentModelCategoryToTagNames)(`#${$model !== null && $model !== void 0 ? $model : _model}`, specs.def);
|
|
160
|
+
if (_selectors.length === 0) {
|
|
161
|
+
throw new Error(`${$model !== null && $model !== void 0 ? $model : _model} is empty`);
|
|
162
|
+
}
|
|
163
|
+
const selectors = [];
|
|
164
|
+
for (const selector of _selectors) {
|
|
165
|
+
if (selector === '#custom') {
|
|
166
|
+
hasCustom = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (selector === '#text') {
|
|
170
|
+
hasText = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
selectors.push(selector);
|
|
174
|
+
}
|
|
175
|
+
return `:is(${selectors.join(',')})${$last !== null && $last !== void 0 ? $last : ''}`;
|
|
176
|
+
});
|
|
177
|
+
const result = {
|
|
178
|
+
selector,
|
|
179
|
+
hasCustom,
|
|
180
|
+
hasText,
|
|
181
|
+
};
|
|
182
|
+
queryCaches.set(query, result);
|
|
183
|
+
optConditionSpecsBaseCaches.set(specs, queryCaches);
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChildNode, Options, Result, Specs } from './types';
|
|
2
|
+
import type { PermittedContentPattern } from '@markuplint/ml-spec';
|
|
3
|
+
/**
|
|
4
|
+
* Check ordered array
|
|
5
|
+
*
|
|
6
|
+
* @param contents
|
|
7
|
+
* @param nodes
|
|
8
|
+
* @param specs
|
|
9
|
+
* @param options
|
|
10
|
+
* @param depth
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export declare function order(contents: ReadonlyArray<PermittedContentPattern>, nodes: ReadonlyArray<ChildNode>, specs: Specs, options: Options, depth: number): Result;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.order = void 0;
|
|
4
|
+
const helpers_1 = require("../helpers");
|
|
5
|
+
const complex_branch_1 = require("./complex-branch");
|
|
6
|
+
const debug_1 = require("./debug");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
/**
|
|
9
|
+
* Check ordered array
|
|
10
|
+
*
|
|
11
|
+
* @param contents
|
|
12
|
+
* @param nodes
|
|
13
|
+
* @param specs
|
|
14
|
+
* @param options
|
|
15
|
+
* @param depth
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
function order(contents, nodes, specs, options, depth) {
|
|
19
|
+
var _a, _b, _c, _d;
|
|
20
|
+
const orderLog = debug_1.cmLog.extend(`order#${depth}`);
|
|
21
|
+
const btLog = debug_1.cmLog.extend(`backtrack#${depth}`);
|
|
22
|
+
const patterns = (0, helpers_1.deepCopy)(contents);
|
|
23
|
+
const collection = new utils_1.Collection(nodes);
|
|
24
|
+
orderLog('Starts: %s', collection);
|
|
25
|
+
let result = undefined;
|
|
26
|
+
let backtrackMode = false;
|
|
27
|
+
let afterBacktrack = false;
|
|
28
|
+
const unmatchedResults = [];
|
|
29
|
+
while (patterns.length) {
|
|
30
|
+
result = (0, complex_branch_1.complexBranch)(patterns[0], collection.unmatched, specs, options, depth);
|
|
31
|
+
collection.addMatched(result.matched);
|
|
32
|
+
orderLog('stack: %s', collection);
|
|
33
|
+
if (result.type !== 'UNEXPECTED_EXTRA_NODE' && result.type !== 'MATCHED' && result.type !== 'MATCHED_ZERO') {
|
|
34
|
+
unmatchedResults.push(result);
|
|
35
|
+
if (backtrackMode) {
|
|
36
|
+
collection.back();
|
|
37
|
+
btLog('🔙');
|
|
38
|
+
backtrackMode = false;
|
|
39
|
+
afterBacktrack = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
orderLog('conformed (%s): %s', result.type, collection.toString(true));
|
|
43
|
+
const barelyMatchedResult = unmatchedResults.sort((a, b) => b.matched.length - a.matched.length)[0];
|
|
44
|
+
if (!barelyMatchedResult) {
|
|
45
|
+
throw new Error('Unreachable code');
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: barelyMatchedResult.type,
|
|
49
|
+
matched: collection.matched,
|
|
50
|
+
unmatched: collection.unmatched,
|
|
51
|
+
zeroMatch: barelyMatchedResult.zeroMatch,
|
|
52
|
+
query: barelyMatchedResult.query,
|
|
53
|
+
hint: barelyMatchedResult.hint,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (afterBacktrack) {
|
|
57
|
+
collection.lock();
|
|
58
|
+
afterBacktrack = false;
|
|
59
|
+
}
|
|
60
|
+
if (result.zeroMatch) {
|
|
61
|
+
backtrackMode = true;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
backtrackMode = false;
|
|
65
|
+
}
|
|
66
|
+
patterns.shift();
|
|
67
|
+
}
|
|
68
|
+
if (collection.unmatched.length) {
|
|
69
|
+
orderLog('Conformed (UNEXPECTED_EXTRA_NODE): %s', collection.toString(true));
|
|
70
|
+
return {
|
|
71
|
+
type: 'UNEXPECTED_EXTRA_NODE',
|
|
72
|
+
matched: collection.matched,
|
|
73
|
+
unmatched: collection.unmatched,
|
|
74
|
+
zeroMatch: false,
|
|
75
|
+
query: (_a = result === null || result === void 0 ? void 0 : result.query) !== null && _a !== void 0 ? _a : 'N/A',
|
|
76
|
+
hint: (_b = result === null || result === void 0 ? void 0 : result.hint) !== null && _b !== void 0 ? _b : {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
orderLog('Conformed: %s', collection);
|
|
80
|
+
return {
|
|
81
|
+
type: collection.matched.length ? 'MATCHED' : 'MATCHED_ZERO',
|
|
82
|
+
matched: collection.matched,
|
|
83
|
+
unmatched: collection.unmatched,
|
|
84
|
+
zeroMatch: false,
|
|
85
|
+
query: (_c = result === null || result === void 0 ? void 0 : result.query) !== null && _c !== void 0 ? _c : 'N/A',
|
|
86
|
+
hint: (_d = result === null || result === void 0 ? void 0 : result.hint) !== null && _d !== void 0 ? _d : {},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
exports.order = order;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SelectorResult } from './matches-selector';
|
|
2
|
+
import type { ChildNode, Options, Specs } from './types';
|
|
3
|
+
import type { PermittedContentPattern, Model } from '@markuplint/ml-spec';
|
|
4
|
+
export declare function recursiveBranch(model: Model | PermittedContentPattern[], nodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): SelectorResult;
|