@markuplint/ml-core 5.4.0 → 5.6.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/CHANGELOG.md +29 -0
- package/lib/ml-core.js +3 -0
- package/lib/ml-dom/helper/accname.d.ts +35 -0
- package/lib/ml-dom/helper/accname.js +69 -17
- package/lib/ml-dom/helper/debug.js +1 -1
- package/lib/ml-dom/helper/literal-values.d.ts +102 -0
- package/lib/ml-dom/helper/literal-values.js +193 -0
- package/lib/ml-dom/helper/pretended-attrs.d.ts +16 -0
- package/lib/ml-dom/helper/pretended-attrs.js +21 -0
- package/lib/ml-dom/helper/slot-assignment.d.ts +48 -0
- package/lib/ml-dom/helper/slot-assignment.js +292 -0
- package/lib/ml-dom/helper/tree-scope.d.ts +130 -0
- package/lib/ml-dom/helper/tree-scope.js +341 -0
- package/lib/ml-dom/helper/walkers.d.ts +15 -0
- package/lib/ml-dom/helper/walkers.js +34 -0
- package/lib/ml-dom/index.d.ts +4 -0
- package/lib/ml-dom/index.js +2 -0
- package/lib/ml-dom/node/attr.d.ts +77 -10
- package/lib/ml-dom/node/attr.js +209 -20
- package/lib/ml-dom/node/block.d.ts +15 -6
- package/lib/ml-dom/node/block.js +17 -8
- package/lib/ml-dom/node/document-fragment.d.ts +18 -6
- package/lib/ml-dom/node/document-fragment.js +19 -7
- package/lib/ml-dom/node/document.d.ts +36 -7
- package/lib/ml-dom/node/document.js +60 -17
- package/lib/ml-dom/node/dom-token-list.js +28 -3
- package/lib/ml-dom/node/element.d.ts +148 -24
- package/lib/ml-dom/node/element.js +258 -48
- package/lib/ml-dom/node/named-node-map.js +2 -1
- package/lib/ml-dom/node/node-list.js +2 -3
- package/lib/ml-dom/node/node.d.ts +55 -10
- package/lib/ml-dom/node/node.js +98 -13
- package/lib/ml-dom/node/parent-node.d.ts +39 -8
- package/lib/ml-dom/node/parent-node.js +79 -12
- package/lib/ml-dom/node/shadow-root.d.ts +162 -0
- package/lib/ml-dom/node/shadow-root.js +219 -0
- package/lib/ml-dom/node/text.d.ts +12 -3
- package/lib/ml-dom/node/text.js +14 -3
- package/lib/ml-rule/ml-rule.js +4 -1
- package/lib/test/index.js +3 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/search-node-at-location.d.ts +39 -0
- package/lib/utils/search-node-at-location.js +53 -0
- package/package.json +12 -15
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { isPotentialCustomElementName } from '@markuplint/parser-utils';
|
|
2
|
+
import { decodeEntities } from '@markuplint/shared';
|
|
3
|
+
/**
|
|
4
|
+
* @module
|
|
5
|
+
*
|
|
6
|
+
* The tree of a node is what its `getRootNode()` returns: the document, the content of an
|
|
7
|
+
* HTML `template` element, or a declarative shadow root. An id is unique in a tree, and an id
|
|
8
|
+
* reference is resolved in a tree, as the specification says of "the element's tree".
|
|
9
|
+
*
|
|
10
|
+
* markuplint keeps the content of a `template` element as the children of the element (the
|
|
11
|
+
* syntax tree has no other place for it), so the boundary is made here and not by the shape of
|
|
12
|
+
* the tree: the queries of `MLParentNode`, `getRootNode()`, `treeParentElement`, the selector
|
|
13
|
+
* engine and `contains()` stop at it. `parentNode` / `parentElement` do not: the content
|
|
14
|
+
* model of a `template` element is validated against the elements that the syntax tree puts
|
|
15
|
+
* in it, so they go on to the `template` element. The algorithms of `@markuplint/ml-spec` walk up
|
|
16
|
+
* with `getTreeParentElement` / `getComposedParentElement`, which read `getRootNode()`, so the
|
|
17
|
+
* boundary made here is the boundary they stop at.
|
|
18
|
+
*
|
|
19
|
+
* @see https://dom.spec.whatwg.org/#concept-tree
|
|
20
|
+
* @see https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute
|
|
21
|
+
* @see https://html.spec.whatwg.org/multipage/scripting.html#the-template-element
|
|
22
|
+
*/
|
|
23
|
+
export const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
24
|
+
/**
|
|
25
|
+
* The names that the DOM accepts as a shadow host, besides a valid custom element name.
|
|
26
|
+
*
|
|
27
|
+
* @see https://dom.spec.whatwg.org/#valid-shadow-host-name
|
|
28
|
+
*/
|
|
29
|
+
const SHADOW_HOST_NAMES = new Set([
|
|
30
|
+
'article',
|
|
31
|
+
'aside',
|
|
32
|
+
'blockquote',
|
|
33
|
+
'body',
|
|
34
|
+
'div',
|
|
35
|
+
'footer',
|
|
36
|
+
'h1',
|
|
37
|
+
'h2',
|
|
38
|
+
'h3',
|
|
39
|
+
'h4',
|
|
40
|
+
'h5',
|
|
41
|
+
'h6',
|
|
42
|
+
'header',
|
|
43
|
+
'main',
|
|
44
|
+
'nav',
|
|
45
|
+
'p',
|
|
46
|
+
'section',
|
|
47
|
+
'span',
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Whether `node` is skipped when the parent of a `template` element is looked for: what the
|
|
51
|
+
* template engine or the framework renders away (a block of a template language, a fragment of
|
|
52
|
+
* JSX, a `template` with a directive such as `v-if`) leaves its children to the next element
|
|
53
|
+
* above.
|
|
54
|
+
*
|
|
55
|
+
* `isRenderedAway` of `@markuplint/rules` (`src/helpers.ts`) decides the same of an element for the
|
|
56
|
+
* rules that walk up the ancestors: a kind added here belongs to it too.
|
|
57
|
+
*
|
|
58
|
+
* @param node - A node of the syntax tree
|
|
59
|
+
* @returns `true` if the node is not an element of the output
|
|
60
|
+
*/
|
|
61
|
+
export function isTransparent(
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
63
|
+
node) {
|
|
64
|
+
if (node.is(node.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (node.isFragment) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return node.is(node.ELEMENT_NODE) && hasDirectiveTemplate(node);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether the element is a `template` that has a directive of a template engine (Vue's `v-if`,
|
|
74
|
+
* `v-for`, `v-slot`). The engine renders its children in place of it and drops it, so it is not
|
|
75
|
+
* an HTML `template` element.
|
|
76
|
+
*
|
|
77
|
+
* @param el - An element
|
|
78
|
+
* @returns `true` if it is such a `template`
|
|
79
|
+
*/
|
|
80
|
+
function hasDirectiveTemplate(
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
82
|
+
el) {
|
|
83
|
+
if (el.localName !== 'template') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
for (const attr of el.attributes) {
|
|
87
|
+
// A spread attribute (`{...props}`) sets attributes of the element, which stays
|
|
88
|
+
if (attr.isDirective && attr.localName !== '#spread') {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether the element is an HTML `template` element, which holds its children in `content`, a tree
|
|
96
|
+
* of its own.
|
|
97
|
+
*
|
|
98
|
+
* It is not when the element is a fragment, has a directive of a template engine, or the
|
|
99
|
+
* framework puts the children in the element itself (`templateContent: 'in-place'` of the spec).
|
|
100
|
+
*
|
|
101
|
+
* @template T - The rule configuration value type
|
|
102
|
+
* @template O - The rule options type
|
|
103
|
+
* @param el - An element
|
|
104
|
+
* @returns `true` if the children of the element belong to a tree of their own
|
|
105
|
+
*/
|
|
106
|
+
export function isHTMLTemplateElement(
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
108
|
+
el) {
|
|
109
|
+
if (el.isFragment || el.localName !== 'template' || el.namespaceURI !== HTML_NAMESPACE) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (el.ownerMLDocument.specs.templateContent === 'in-place') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return !hasDirectiveTemplate(el);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The state of the `shadowrootmode` attribute of an HTML `template` element.
|
|
119
|
+
*
|
|
120
|
+
* @param template - A `template` element
|
|
121
|
+
* @returns `open` or `closed`, `none` for a missing or invalid value (the missing value default and
|
|
122
|
+
* the invalid value default are both the None state), and `dynamic` for a value that is not known
|
|
123
|
+
* until the template is rendered
|
|
124
|
+
* @see https://html.spec.whatwg.org/multipage/scripting.html#attr-template-shadowrootmode
|
|
125
|
+
*/
|
|
126
|
+
function getShadowRootModeState(
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
128
|
+
template) {
|
|
129
|
+
const attr = template.getAttributeNode('shadowrootmode');
|
|
130
|
+
if (!attr || attr.isAbsent) {
|
|
131
|
+
return 'none';
|
|
132
|
+
}
|
|
133
|
+
if (attr.isDynamicValue) {
|
|
134
|
+
return 'dynamic';
|
|
135
|
+
}
|
|
136
|
+
const value = attr.value.toLowerCase();
|
|
137
|
+
return value === 'open' || value === 'closed' ? value : 'none';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The mode of the declarative shadow root that a `template` element asks for.
|
|
141
|
+
*
|
|
142
|
+
* @template T - The rule configuration value type
|
|
143
|
+
* @template O - The rule options type
|
|
144
|
+
* @param template - An element
|
|
145
|
+
* @returns `open` or `closed`, or `null` when it is not an HTML `template` element, the
|
|
146
|
+
* attribute is missing or invalid, or its value is dynamic
|
|
147
|
+
*/
|
|
148
|
+
export function getDeclarativeShadowRootMode(
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
150
|
+
template) {
|
|
151
|
+
if (!isHTMLTemplateElement(template)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const state = getShadowRootModeState(template);
|
|
155
|
+
return state === 'open' || state === 'closed' ? state : null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The element that the HTML parser makes the shadow host of a `template` element: its parent in
|
|
159
|
+
* the output, where what the engine renders away (see {@link isTransparent}) is skipped. It is the
|
|
160
|
+
* parent in the output of any node, so it is the host a light child is a child of.
|
|
161
|
+
*
|
|
162
|
+
* @template T - The rule configuration value type
|
|
163
|
+
* @template O - The rule options type
|
|
164
|
+
* @param template - A `template` element, or any node whose parent in the output is wanted
|
|
165
|
+
* @returns The element, or `null` when the file has none above the node
|
|
166
|
+
*/
|
|
167
|
+
export function getShadowHost(
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
169
|
+
template) {
|
|
170
|
+
let node = template.syntacticalParentNode;
|
|
171
|
+
while (node) {
|
|
172
|
+
if (node.is(node.DOCUMENT_NODE) || node.is(node.DOCUMENT_FRAGMENT_NODE)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (!isTransparent(node) && node.is(node.ELEMENT_NODE)) {
|
|
176
|
+
return node;
|
|
177
|
+
}
|
|
178
|
+
node = node.syntacticalParentNode;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Whether the host can have a shadow root, judged by what the markup says.
|
|
184
|
+
*
|
|
185
|
+
* @param host - The parent element of a `template` element
|
|
186
|
+
* @returns `true` or `false`, and `null` for an element that is not what it is written as (a
|
|
187
|
+
* component whose output is not known)
|
|
188
|
+
* @see https://dom.spec.whatwg.org/#concept-attach-a-shadow-root
|
|
189
|
+
*/
|
|
190
|
+
function canBeShadowHost(
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
192
|
+
host) {
|
|
193
|
+
if (host.elementType === 'authored' && host.pretenderContext?.type !== 'pretender') {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
if (host.namespaceURI !== HTML_NAMESPACE) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const name = host.localName;
|
|
200
|
+
return SHADOW_HOST_NAMES.has(name) || isPotentialCustomElementName(name);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Whether the children written in `host` are its children in the output, so that a `template`
|
|
204
|
+
* among them is parsed in it. A pretended component renders them in the element it pretends to
|
|
205
|
+
* be only when it neither drops them (`slots: null`) nor puts them in a slot wrapper; a custom
|
|
206
|
+
* element keeps them in its light DOM. This is the same placement as the rendered parent
|
|
207
|
+
* `MLElement` gives a child for its namespace.
|
|
208
|
+
*
|
|
209
|
+
* @param host - The parent element of a `template` element
|
|
210
|
+
* @returns `false` when the component places the children elsewhere or not at all
|
|
211
|
+
*/
|
|
212
|
+
function rendersChildrenInItself(
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
214
|
+
host) {
|
|
215
|
+
const context = host.pretenderContext;
|
|
216
|
+
if (context?.type !== 'pretender' || host.elementType === 'web-component') {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (context.as.childNodes.length === 0) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return !context.slotContent || context.slotContent.wrapper === context.as;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The `template` elements that sit in the output under the host, in the order of the markup,
|
|
226
|
+
* seen through what the engine renders away.
|
|
227
|
+
*
|
|
228
|
+
* @param host - A shadow host candidate
|
|
229
|
+
* @returns The `template` elements, which are HTML ones
|
|
230
|
+
*/
|
|
231
|
+
function getTemplatesOfHost(
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
233
|
+
host) {
|
|
234
|
+
const templates = [];
|
|
235
|
+
for (const child of host.getPureChildNodes()) {
|
|
236
|
+
if (isTransparent(child)) {
|
|
237
|
+
templates.push(...getTemplatesOfHost(child));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (child.is(child.ELEMENT_NODE) && isHTMLTemplateElement(child)) {
|
|
241
|
+
templates.push(child);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return templates;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Whether a declarative shadow root is attached to the parent of a `template` element, by what
|
|
248
|
+
* the HTML parser does with the start tag of `template` and what the DOM does to attach a shadow
|
|
249
|
+
* root: the mode is `open` or `closed` (ASCII case-insensitive), the parent is an HTML element with
|
|
250
|
+
* a valid shadow host name (a valid custom element name, or one of the names the DOM lists), and no
|
|
251
|
+
* earlier `template` of the same parent has attached one.
|
|
252
|
+
*
|
|
253
|
+
* The file is taken as parsed as a document that was served, so declarative shadow roots are
|
|
254
|
+
* allowed. The parser would not attach one to the top level of a fragment parsed through `innerHTML`,
|
|
255
|
+
* but whether a file is that is not something its markup says.
|
|
256
|
+
*
|
|
257
|
+
* Known limitation: a custom element definition may disable the shadow root (`disable shadow`),
|
|
258
|
+
* which only the script that defines the element tells. It is taken as not disabled.
|
|
259
|
+
*
|
|
260
|
+
* @template T - The rule configuration value type
|
|
261
|
+
* @template O - The rule options type
|
|
262
|
+
* @param template - An element
|
|
263
|
+
* @returns The state. An element that is not an HTML `template` element is `not-attached`
|
|
264
|
+
* @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
|
|
265
|
+
* @see https://dom.spec.whatwg.org/#concept-attach-a-shadow-root
|
|
266
|
+
*/
|
|
267
|
+
export function getDeclarativeShadowRootAttachment(
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
269
|
+
template) {
|
|
270
|
+
if (!isHTMLTemplateElement(template)) {
|
|
271
|
+
return 'not-attached';
|
|
272
|
+
}
|
|
273
|
+
const mode = getShadowRootModeState(template);
|
|
274
|
+
if (mode === 'none') {
|
|
275
|
+
return 'not-attached';
|
|
276
|
+
}
|
|
277
|
+
const host = getShadowHost(template);
|
|
278
|
+
if (!host || !rendersChildrenInItself(host)) {
|
|
279
|
+
return 'unknown';
|
|
280
|
+
}
|
|
281
|
+
const hostCheck = canBeShadowHost(host);
|
|
282
|
+
if (hostCheck === false) {
|
|
283
|
+
return 'not-attached';
|
|
284
|
+
}
|
|
285
|
+
if (hostCheck === null || mode === 'dynamic') {
|
|
286
|
+
return 'unknown';
|
|
287
|
+
}
|
|
288
|
+
// The first `template` that asks for a shadow root and can have one takes the host.
|
|
289
|
+
let undecided = false;
|
|
290
|
+
for (const earlier of getTemplatesOfHost(host)) {
|
|
291
|
+
if (earlier === template) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
const earlierMode = getShadowRootModeState(earlier);
|
|
295
|
+
if (earlierMode === 'none') {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (earlierMode === 'dynamic') {
|
|
299
|
+
undecided = true;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
return 'not-attached';
|
|
303
|
+
}
|
|
304
|
+
return undecided ? 'unknown' : 'attached';
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The `template` element of a host whose declarative shadow root is attached, if there is one.
|
|
308
|
+
*
|
|
309
|
+
* @template T - The rule configuration value type
|
|
310
|
+
* @template O - The rule options type
|
|
311
|
+
* @param host - An element
|
|
312
|
+
* @returns The `template` element whose content is the shadow root of the host, or `null`
|
|
313
|
+
*/
|
|
314
|
+
export function getShadowRootTemplate(
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
316
|
+
host) {
|
|
317
|
+
return (getTemplatesOfHost(host).find(template => getDeclarativeShadowRootAttachment(template) === 'attached') ?? null);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The string that the DOM has for an attribute: the value with the character references in the markup
|
|
321
|
+
* resolved, so that `ab` is `ab`. A value that is written as code is a string already, with no
|
|
322
|
+
* character reference in it.
|
|
323
|
+
*
|
|
324
|
+
* @template T - The rule configuration value type
|
|
325
|
+
* @template O - The rule options type
|
|
326
|
+
* @param attr - An attribute, or `null` for one that is missing
|
|
327
|
+
* @param value - A part of the value of the attribute as it is written (one token of a list), which the
|
|
328
|
+
* attribute decides on whether it has a character reference; the whole value by default
|
|
329
|
+
* @returns The string, or `null` when the attribute is missing, a directive, or its value is not known
|
|
330
|
+
* until it is rendered (a dynamic value)
|
|
331
|
+
* @see https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute
|
|
332
|
+
*/
|
|
333
|
+
export function getDOMValue(
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
335
|
+
attr, value) {
|
|
336
|
+
if (!attr || attr.isDynamicValue || attr.isDirective) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const written = value ?? attr.value;
|
|
340
|
+
return attr.isExpressionValue ? written : decodeEntities(written);
|
|
341
|
+
}
|
|
@@ -3,4 +3,19 @@ import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
|
3
3
|
export type Walker<T extends RuleConfigValue, O extends PlainData = undefined, N = MLNode<T, O>> = (node: N) => void | Promise<void> | Promise<void>[];
|
|
4
4
|
export type SyncWalker<T extends RuleConfigValue, O extends PlainData = undefined, N = MLNode<T, O>> = (node: N) => void;
|
|
5
5
|
export declare function syncWalk<T extends RuleConfigValue, O extends PlainData = undefined>(nodeList: ReadonlyArray<MLNode<T, O>>, walker: SyncWalker<T, O>): void;
|
|
6
|
+
/**
|
|
7
|
+
* Walks the nodes in tree order (a node before its descendants), within one tree.
|
|
8
|
+
*
|
|
9
|
+
* Within one tree means it does not go into the children of an HTML `template` element, which
|
|
10
|
+
* are a tree of their own (see `tree-scope.ts`), and it does not report a `template` element that is
|
|
11
|
+
* in no tree (a declarative shadow root is attached to its parent, or whether it is cannot be told).
|
|
12
|
+
* With `across`, it goes into every tree and reports every node, in the order of the markup.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The rule configuration value type
|
|
15
|
+
* @template O - The rule options type
|
|
16
|
+
* @param nodeList - The nodes to start with: the children of the root of the tree
|
|
17
|
+
* @param walker - Called with each node
|
|
18
|
+
* @param across - Whether to go into the trees that other trees hold
|
|
19
|
+
*/
|
|
20
|
+
export declare function walkTree<T extends RuleConfigValue, O extends PlainData = undefined>(nodeList: ReadonlyArray<MLNode<T, O>>, walker: SyncWalker<T, O>, across?: boolean): void;
|
|
6
21
|
export declare function sequentialWalker<T extends RuleConfigValue, O extends PlainData = undefined, N = MLNode<T, O>>(list: ReadonlyArray<N>, walker: Walker<T, O, N>): Promise<void>;
|
|
@@ -8,6 +8,40 @@ nodeList, walker) {
|
|
|
8
8
|
walker(node);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Walks the nodes in tree order (a node before its descendants), within one tree.
|
|
13
|
+
*
|
|
14
|
+
* Within one tree means it does not go into the children of an HTML `template` element, which
|
|
15
|
+
* are a tree of their own (see `tree-scope.ts`), and it does not report a `template` element that is
|
|
16
|
+
* in no tree (a declarative shadow root is attached to its parent, or whether it is cannot be told).
|
|
17
|
+
* With `across`, it goes into every tree and reports every node, in the order of the markup.
|
|
18
|
+
*
|
|
19
|
+
* @template T - The rule configuration value type
|
|
20
|
+
* @template O - The rule options type
|
|
21
|
+
* @param nodeList - The nodes to start with: the children of the root of the tree
|
|
22
|
+
* @param walker - Called with each node
|
|
23
|
+
* @param across - Whether to go into the trees that other trees hold
|
|
24
|
+
*/
|
|
25
|
+
export function walkTree(
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
27
|
+
nodeList, walker, across = false) {
|
|
28
|
+
for (const node of nodeList) {
|
|
29
|
+
if (node.is(node.ELEMENT_NODE)) {
|
|
30
|
+
const holdsOtherTree = node.isHTMLTemplateElement;
|
|
31
|
+
if (across || !node.isInNoTree) {
|
|
32
|
+
walker(node);
|
|
33
|
+
}
|
|
34
|
+
if (across || !holdsOtherTree) {
|
|
35
|
+
walkTree([...node.childNodes], walker, across);
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
walker(node);
|
|
40
|
+
if (node.is(node.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
41
|
+
walkTree([...node.childNodes], walker, across);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
11
45
|
export function sequentialWalker(list, walker) {
|
|
12
46
|
if (list.length === 0) {
|
|
13
47
|
return Promise.resolve();
|
package/lib/ml-dom/index.d.ts
CHANGED
|
@@ -6,4 +6,8 @@ export type { MLDocumentType as DocumentType } from './node/document-type.js';
|
|
|
6
6
|
export type { MLElement as Element } from './node/element.js';
|
|
7
7
|
export type { MLText as Text } from './node/text.js';
|
|
8
8
|
export type { MLChildNode as ChildNode } from './node/child-node.js';
|
|
9
|
+
export type { MLShadowRoot as ShadowRoot } from './node/shadow-root.js';
|
|
10
|
+
export type { DeclarativeShadowRootAttachment } from './helper/tree-scope.js';
|
|
11
|
+
export { getDOMValue } from './helper/tree-scope.js';
|
|
12
|
+
export { getRenderedChildNodes } from './helper/accname.js';
|
|
9
13
|
export { MLDocument as Document } from './node/document.js';
|
package/lib/ml-dom/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import type { MLElement } from './element.js';
|
|
|
2
2
|
import type { AttributeNodeType } from './types.js';
|
|
3
3
|
import type { MLASTAttr } from '@markuplint/ml-ast';
|
|
4
4
|
import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
5
|
+
import type { LiteralExpression } from '@markuplint/parser-utils';
|
|
5
6
|
import { MLToken } from '../token/token.js';
|
|
6
7
|
import { MLDomTokenList } from './dom-token-list.js';
|
|
7
8
|
import { MLNode } from './node.js';
|
|
@@ -37,9 +38,13 @@ export declare class MLAttr<T extends RuleConfigValue, O extends PlainData = und
|
|
|
37
38
|
*/
|
|
38
39
|
readonly isDuplicatable: boolean;
|
|
39
40
|
/**
|
|
40
|
-
* Whether this attribute
|
|
41
|
+
* Whether the value of this attribute is written as code (`{x}` of JSX, `:x="…"` of Vue, a value of
|
|
42
|
+
* `=` in Pug, a masked block of a template language), whatever the code is: a literal (`{true}`) or not
|
|
43
|
+
* (`{cond}`). It is a matter of syntax, which a rule that checks how the value is written reads
|
|
44
|
+
* (`attr-value-quotes`, `no-unescaped-char`), while {@link MLAttr.isDynamicValue} is a matter of the
|
|
45
|
+
* value that results.
|
|
41
46
|
*/
|
|
42
|
-
readonly
|
|
47
|
+
readonly isExpressionValue?: true;
|
|
43
48
|
/**
|
|
44
49
|
* The token representing the attribute name, or null for spread attributes.
|
|
45
50
|
*/
|
|
@@ -69,13 +74,6 @@ export declare class MLAttr<T extends RuleConfigValue, O extends PlainData = und
|
|
|
69
74
|
* The token representing the attribute value, or null if the attribute has no value.
|
|
70
75
|
*/
|
|
71
76
|
readonly valueNode: MLToken | null;
|
|
72
|
-
/**
|
|
73
|
-
* Returns the "string" if HTML syntax. Otherwise, returns a type in its syntax.
|
|
74
|
-
*
|
|
75
|
-
* @default "string"
|
|
76
|
-
* @implements `@markuplint/ml-core` API: `MLAttr`
|
|
77
|
-
*/
|
|
78
|
-
readonly valueType: 'string' | 'number' | 'boolean' | 'code';
|
|
79
77
|
/**
|
|
80
78
|
* Creates a new MLAttr instance from an AST attribute token.
|
|
81
79
|
*
|
|
@@ -83,6 +81,66 @@ export declare class MLAttr<T extends RuleConfigValue, O extends PlainData = und
|
|
|
83
81
|
* @param ownElement - The element that owns this attribute
|
|
84
82
|
*/
|
|
85
83
|
constructor(astToken: MLASTAttr, ownElement: MLElement<T, O>);
|
|
84
|
+
/**
|
|
85
|
+
* Whether the framework does not output this attribute, which is known from its value being a literal
|
|
86
|
+
* that the framework drops (`disabled={false}`, `title={null}`). It is still in the list of the
|
|
87
|
+
* attributes of the element (`MLElement#attributes`), as a name that is misspelled is so whatever its
|
|
88
|
+
* value is, and a lookup by name (`getAttribute`, `hasAttribute`) does not find it, as the DOM does not.
|
|
89
|
+
* An absent attribute is {@link MLAttr.isDynamicValue} as well, so that a rule that reads the value skips it.
|
|
90
|
+
*
|
|
91
|
+
* Which rules skip it: a rule that judges the value or the presence of an attribute (the value of
|
|
92
|
+
* `aria-*`, a required attribute, a conflict between two attributes) reads what the framework outputs,
|
|
93
|
+
* and an attribute that is not output is not there to judge. A rule that judges the name (a misspelling,
|
|
94
|
+
* a deprecated or obsolete attribute, the order) judges what the author wrote, so it still reports an
|
|
95
|
+
* absent attribute.
|
|
96
|
+
*/
|
|
97
|
+
get isAbsent(): true | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the value of this attribute is not known: the code is not a literal (`{cond}`), the
|
|
100
|
+
* framework renders a literal in a way that is not known (`disabled={""}`), or the attribute is
|
|
101
|
+
* not output ({@link MLAttr.isAbsent}), where there is no value to read.
|
|
102
|
+
*
|
|
103
|
+
* It is not whether the value is written as code (that is {@link MLAttr.isExpressionValue}): a
|
|
104
|
+
* literal that is written as code (`aria-busy={true}`) is rendered by the framework to a value
|
|
105
|
+
* (`"true"`), which is known.
|
|
106
|
+
*
|
|
107
|
+
* A rule that reads it to skip a value that it cannot know reads this; a rule that asks how the
|
|
108
|
+
* value is written (its quotes, its escapes) reads {@link MLAttr.isExpressionValue} instead.
|
|
109
|
+
*
|
|
110
|
+
* On a component (`elementType: "authored"`) an attribute is a prop, which is rendered where it is
|
|
111
|
+
* forwarded to, not here: a literal in it is not dynamic, and its `value` is the string form of the
|
|
112
|
+
* literal ({@link MLAttr.literal}). What the component renders is not known, and that is told by
|
|
113
|
+
* `MLElement#hasMutableChildren`, not by the attributes of the component.
|
|
114
|
+
*/
|
|
115
|
+
get isDynamicValue(): true | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Whether this attribute is made by a pretender (`pretenders` of a configuration): it is not in the
|
|
118
|
+
* source, so the position of its tokens is that of the element, and a fix of a violation on it
|
|
119
|
+
* would rewrite the tag.
|
|
120
|
+
*
|
|
121
|
+
* An attribute that a pretender takes over from the source (`inheritAttrs`) is not.
|
|
122
|
+
*
|
|
123
|
+
* A fix of a violation that is reported on a pretended attribute is dropped (`MLRule`), and a rule
|
|
124
|
+
* that fixes by the range of the attributes of an element (`attr-order`) leaves them out.
|
|
125
|
+
*/
|
|
126
|
+
get isPretended(): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* The literal that the value of this attribute is written as, when it is nothing but a literal in a
|
|
129
|
+
* language that can be read as one: `{true}`, `{"x"}`, `:x="0"`, or no value at all in the
|
|
130
|
+
* shorthand of JSX (`<div aria-hidden>` is `true`). `null` otherwise.
|
|
131
|
+
*
|
|
132
|
+
* What a literal is rendered as depends on the framework, the element and the attribute name
|
|
133
|
+
* ({@link MLAttr.value}, {@link MLAttr.isAbsent}); this is what is written.
|
|
134
|
+
*/
|
|
135
|
+
get literal(): LiteralExpression | null;
|
|
136
|
+
/**
|
|
137
|
+
* Returns the "string" if HTML syntax. Otherwise, returns a type in its syntax. An attribute whose value
|
|
138
|
+
* is a literal that is resolved is "string", whichever language it is written in.
|
|
139
|
+
*
|
|
140
|
+
* @default "string"
|
|
141
|
+
* @implements `@markuplint/ml-core` API: `MLAttr`
|
|
142
|
+
*/
|
|
143
|
+
get valueType(): 'string' | 'number' | 'boolean' | 'code';
|
|
86
144
|
/**
|
|
87
145
|
* Returns the local name portion of the attribute (without namespace prefix).
|
|
88
146
|
*
|
|
@@ -152,7 +210,8 @@ export declare class MLAttr<T extends RuleConfigValue, O extends PlainData = und
|
|
|
152
210
|
* Whether the name of this attribute reaches the DOM through the DOM API (`setAttribute`) and
|
|
153
211
|
* not through the HTML parser of the browser: what the parser says of its language
|
|
154
212
|
* (`MLDocument#attrNameRoute`), and for a parser that says it of a dynamic attribute only
|
|
155
|
-
* (Svelte), whether this one is
|
|
213
|
+
* (Svelte), whether this one is written as code: how its name is set is a matter of how it is
|
|
214
|
+
* written, not of the value that results.
|
|
156
215
|
*/
|
|
157
216
|
get isSetThroughDOMAPI(): boolean;
|
|
158
217
|
/**
|
|
@@ -162,6 +221,14 @@ export declare class MLAttr<T extends RuleConfigValue, O extends PlainData = und
|
|
|
162
221
|
* @implements `SelectorAttr#nameCaseInsensitive` of `@markuplint/selector`
|
|
163
222
|
*/
|
|
164
223
|
get nameCaseInsensitive(): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Whether the framework does not output this attribute, so that no selector matches it
|
|
226
|
+
* ({@link MLAttr.isAbsent}). It is a boolean because `SelectorAttr#absent` of `@markuplint/selector`
|
|
227
|
+
* is the one that a selector reads.
|
|
228
|
+
*
|
|
229
|
+
* @implements `SelectorAttr#absent` of `@markuplint/selector`
|
|
230
|
+
*/
|
|
231
|
+
get absent(): boolean;
|
|
165
232
|
/**
|
|
166
233
|
* The name that a selector is matched with.
|
|
167
234
|
*
|