@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
package/lib/ml-dom/node/attr.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveNamespace, compileDirectivePatterns, resolveDirective } from '@markuplint/ml-spec';
|
|
2
|
-
import { searchIDLAttribute, suggestIDLAttribute } from '@markuplint/parser-utils';
|
|
2
|
+
import { parseLiteralExpression, searchIDLAttribute, suggestIDLAttribute } from '@markuplint/parser-utils';
|
|
3
3
|
import { adjustForeignAttrName } from '../helper/foreign-attr-names.js';
|
|
4
|
+
import { getBooleanAttrNames, literalToString, renderLiteral } from '../helper/literal-values.js';
|
|
5
|
+
import { isPretendedAttr } from '../helper/pretended-attrs.js';
|
|
4
6
|
import { MLToken } from '../token/token.js';
|
|
5
7
|
import { MLDomTokenList } from './dom-token-list.js';
|
|
6
8
|
import { MLNode } from './node.js';
|
|
@@ -36,9 +38,13 @@ export class MLAttr extends MLNode {
|
|
|
36
38
|
*/
|
|
37
39
|
isDuplicatable;
|
|
38
40
|
/**
|
|
39
|
-
* 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.
|
|
40
46
|
*/
|
|
41
|
-
|
|
47
|
+
isExpressionValue;
|
|
42
48
|
#localName;
|
|
43
49
|
/**
|
|
44
50
|
* The token representing the attribute name, or null for spread attributes.
|
|
@@ -52,6 +58,10 @@ export class MLAttr extends MLNode {
|
|
|
52
58
|
ownerElement;
|
|
53
59
|
#potentialName;
|
|
54
60
|
#potentialValue;
|
|
61
|
+
#literal = null;
|
|
62
|
+
#isPretended = false;
|
|
63
|
+
#valueType = 'string';
|
|
64
|
+
#rendering;
|
|
55
65
|
/**
|
|
56
66
|
* The whitespace token after the equal sign, or null if absent.
|
|
57
67
|
*/
|
|
@@ -72,13 +82,6 @@ export class MLAttr extends MLNode {
|
|
|
72
82
|
* The token representing the attribute value, or null if the attribute has no value.
|
|
73
83
|
*/
|
|
74
84
|
valueNode = null;
|
|
75
|
-
/**
|
|
76
|
-
* Returns the "string" if HTML syntax. Otherwise, returns a type in its syntax.
|
|
77
|
-
*
|
|
78
|
-
* @default "string"
|
|
79
|
-
* @implements `@markuplint/ml-core` API: `MLAttr`
|
|
80
|
-
*/
|
|
81
|
-
valueType = 'string';
|
|
82
85
|
/**
|
|
83
86
|
* Creates a new MLAttr instance from an AST attribute token.
|
|
84
87
|
*
|
|
@@ -92,12 +95,12 @@ export class MLAttr extends MLNode {
|
|
|
92
95
|
this.ownerElement = ownElement;
|
|
93
96
|
if (this._astToken.type === 'spread') {
|
|
94
97
|
this.#namespaceURI = null;
|
|
95
|
-
this
|
|
98
|
+
this.#valueType = 'code';
|
|
96
99
|
this.#localName = '#spread';
|
|
97
100
|
this.#potentialName = '#spread';
|
|
98
101
|
this.#potentialValue = this._astToken.raw;
|
|
99
102
|
this.isDirective = true;
|
|
100
|
-
this.
|
|
103
|
+
this.isExpressionValue = true;
|
|
101
104
|
this.isDuplicatable = true;
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
@@ -111,6 +114,9 @@ export class MLAttr extends MLNode {
|
|
|
111
114
|
this.endQuote = new MLToken(this._astToken.endQuote);
|
|
112
115
|
this.candidate = this._astToken.candidate;
|
|
113
116
|
this.#potentialValue = this._astToken.potentialValue ?? this.valueNode?.raw ?? '';
|
|
117
|
+
this.#isPretended = isPretendedAttr(this._astToken);
|
|
118
|
+
// The language that the value is written in, when the value is code of a language that can be read as a literal
|
|
119
|
+
let language;
|
|
114
120
|
if (this._astToken.potentialName == null) {
|
|
115
121
|
// Try declarative directive pattern resolution from spec
|
|
116
122
|
const patterns = ownElement.ownerMLDocument.specs.directivePatterns ?? [];
|
|
@@ -119,18 +125,20 @@ export class MLAttr extends MLNode {
|
|
|
119
125
|
: null;
|
|
120
126
|
if (resolution) {
|
|
121
127
|
this.#potentialName = resolution.potentialName ?? this.nameNode?.raw ?? '';
|
|
122
|
-
this.
|
|
128
|
+
this.isExpressionValue = resolution.isDynamicValue;
|
|
123
129
|
this.isDirective = resolution.isDirective;
|
|
124
130
|
this.isDuplicatable = resolution.isDuplicatable ?? this._astToken.isDuplicatable;
|
|
125
131
|
if (resolution.valueType) {
|
|
126
|
-
this
|
|
132
|
+
this.#valueType = resolution.valueType;
|
|
127
133
|
}
|
|
134
|
+
language = resolution.expressionLanguage;
|
|
128
135
|
}
|
|
129
136
|
else {
|
|
130
137
|
this.#potentialName = this.nameNode?.raw ?? '';
|
|
131
|
-
this.
|
|
138
|
+
this.isExpressionValue = this._astToken.isDynamicValue;
|
|
132
139
|
this.isDirective = this._astToken.isDirective;
|
|
133
140
|
this.isDuplicatable = this._astToken.isDuplicatable;
|
|
141
|
+
language = ownElement.ownerMLDocument.attrExpressionLanguage;
|
|
134
142
|
}
|
|
135
143
|
// IDL attribute resolution (after directivePatterns).
|
|
136
144
|
// Performed in the core, not in each parser: any spec opting in via
|
|
@@ -153,12 +161,14 @@ export class MLAttr extends MLNode {
|
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
else {
|
|
156
|
-
// Parser-set potentialName takes precedence
|
|
164
|
+
// Parser-set potentialName takes precedence. Its value is not the value of the attribute
|
|
165
|
+
// (a condition of `class:list`, a variable of a shorthand), so it is not read as a literal.
|
|
157
166
|
this.#potentialName = this._astToken.potentialName;
|
|
158
|
-
this.
|
|
167
|
+
this.isExpressionValue = this._astToken.isDynamicValue;
|
|
159
168
|
this.isDirective = this._astToken.isDirective;
|
|
160
169
|
this.isDuplicatable = this._astToken.isDuplicatable;
|
|
161
170
|
}
|
|
171
|
+
this.#literal = this.#readLiteral(language);
|
|
162
172
|
// The HTML parser lower-cases the name before it adjusts a foreign attribute, so `XLINK:HREF`
|
|
163
173
|
// is `xlink:href` there; a name that code sets keeps the case of its prefix.
|
|
164
174
|
const prefixed = this.#potentialName.includes(':') && !this.isSetThroughDOMAPI;
|
|
@@ -172,6 +182,76 @@ export class MLAttr extends MLNode {
|
|
|
172
182
|
const prefix = nameWithPrefix.split(':')[0];
|
|
173
183
|
this.#namespaceURI = hasPrefix && ns.namespace === prefix ? ns.namespaceURI : null;
|
|
174
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Whether the framework does not output this attribute, which is known from its value being a literal
|
|
187
|
+
* that the framework drops (`disabled={false}`, `title={null}`). It is still in the list of the
|
|
188
|
+
* attributes of the element (`MLElement#attributes`), as a name that is misspelled is so whatever its
|
|
189
|
+
* value is, and a lookup by name (`getAttribute`, `hasAttribute`) does not find it, as the DOM does not.
|
|
190
|
+
* An absent attribute is {@link MLAttr.isDynamicValue} as well, so that a rule that reads the value skips it.
|
|
191
|
+
*
|
|
192
|
+
* Which rules skip it: a rule that judges the value or the presence of an attribute (the value of
|
|
193
|
+
* `aria-*`, a required attribute, a conflict between two attributes) reads what the framework outputs,
|
|
194
|
+
* and an attribute that is not output is not there to judge. A rule that judges the name (a misspelling,
|
|
195
|
+
* a deprecated or obsolete attribute, the order) judges what the author wrote, so it still reports an
|
|
196
|
+
* absent attribute.
|
|
197
|
+
*/
|
|
198
|
+
get isAbsent() {
|
|
199
|
+
return this.#render().absent ? true : undefined;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Whether the value of this attribute is not known: the code is not a literal (`{cond}`), the
|
|
203
|
+
* framework renders a literal in a way that is not known (`disabled={""}`), or the attribute is
|
|
204
|
+
* not output ({@link MLAttr.isAbsent}), where there is no value to read.
|
|
205
|
+
*
|
|
206
|
+
* It is not whether the value is written as code (that is {@link MLAttr.isExpressionValue}): a
|
|
207
|
+
* literal that is written as code (`aria-busy={true}`) is rendered by the framework to a value
|
|
208
|
+
* (`"true"`), which is known.
|
|
209
|
+
*
|
|
210
|
+
* A rule that reads it to skip a value that it cannot know reads this; a rule that asks how the
|
|
211
|
+
* value is written (its quotes, its escapes) reads {@link MLAttr.isExpressionValue} instead.
|
|
212
|
+
*
|
|
213
|
+
* On a component (`elementType: "authored"`) an attribute is a prop, which is rendered where it is
|
|
214
|
+
* forwarded to, not here: a literal in it is not dynamic, and its `value` is the string form of the
|
|
215
|
+
* literal ({@link MLAttr.literal}). What the component renders is not known, and that is told by
|
|
216
|
+
* `MLElement#hasMutableChildren`, not by the attributes of the component.
|
|
217
|
+
*/
|
|
218
|
+
get isDynamicValue() {
|
|
219
|
+
return this.#render().dynamic ? true : undefined;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Whether this attribute is made by a pretender (`pretenders` of a configuration): it is not in the
|
|
223
|
+
* source, so the position of its tokens is that of the element, and a fix of a violation on it
|
|
224
|
+
* would rewrite the tag.
|
|
225
|
+
*
|
|
226
|
+
* An attribute that a pretender takes over from the source (`inheritAttrs`) is not.
|
|
227
|
+
*
|
|
228
|
+
* A fix of a violation that is reported on a pretended attribute is dropped (`MLRule`), and a rule
|
|
229
|
+
* that fixes by the range of the attributes of an element (`attr-order`) leaves them out.
|
|
230
|
+
*/
|
|
231
|
+
get isPretended() {
|
|
232
|
+
return this.#isPretended;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The literal that the value of this attribute is written as, when it is nothing but a literal in a
|
|
236
|
+
* language that can be read as one: `{true}`, `{"x"}`, `:x="0"`, or no value at all in the
|
|
237
|
+
* shorthand of JSX (`<div aria-hidden>` is `true`). `null` otherwise.
|
|
238
|
+
*
|
|
239
|
+
* What a literal is rendered as depends on the framework, the element and the attribute name
|
|
240
|
+
* ({@link MLAttr.value}, {@link MLAttr.isAbsent}); this is what is written.
|
|
241
|
+
*/
|
|
242
|
+
get literal() {
|
|
243
|
+
return this.#literal;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Returns the "string" if HTML syntax. Otherwise, returns a type in its syntax. An attribute whose value
|
|
247
|
+
* is a literal that is resolved is "string", whichever language it is written in.
|
|
248
|
+
*
|
|
249
|
+
* @default "string"
|
|
250
|
+
* @implements `@markuplint/ml-core` API: `MLAttr`
|
|
251
|
+
*/
|
|
252
|
+
get valueType() {
|
|
253
|
+
return this.#render().resolved ? 'string' : this.#valueType;
|
|
254
|
+
}
|
|
175
255
|
/**
|
|
176
256
|
* Returns the local name portion of the attribute (without namespace prefix).
|
|
177
257
|
*
|
|
@@ -284,11 +364,12 @@ export class MLAttr extends MLNode {
|
|
|
284
364
|
* Whether the name of this attribute reaches the DOM through the DOM API (`setAttribute`) and
|
|
285
365
|
* not through the HTML parser of the browser: what the parser says of its language
|
|
286
366
|
* (`MLDocument#attrNameRoute`), and for a parser that says it of a dynamic attribute only
|
|
287
|
-
* (Svelte), whether this one is
|
|
367
|
+
* (Svelte), whether this one is written as code: how its name is set is a matter of how it is
|
|
368
|
+
* written, not of the value that results.
|
|
288
369
|
*/
|
|
289
370
|
get isSetThroughDOMAPI() {
|
|
290
371
|
const route = this.ownerElement.ownerMLDocument.attrNameRoute;
|
|
291
|
-
return route === 'dom-api' || (route === 'dom-api-when-dynamic' && this.
|
|
372
|
+
return route === 'dom-api' || (route === 'dom-api-when-dynamic' && this.isExpressionValue === true);
|
|
292
373
|
}
|
|
293
374
|
/**
|
|
294
375
|
* Whether a selector matches the name of this attribute without regard to ASCII case: the DOM
|
|
@@ -300,6 +381,16 @@ export class MLAttr extends MLNode {
|
|
|
300
381
|
const el = this.ownerElement;
|
|
301
382
|
return el.elementType !== 'authored' && !el.isForeignElement;
|
|
302
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Whether the framework does not output this attribute, so that no selector matches it
|
|
386
|
+
* ({@link MLAttr.isAbsent}). It is a boolean because `SelectorAttr#absent` of `@markuplint/selector`
|
|
387
|
+
* is the one that a selector reads.
|
|
388
|
+
*
|
|
389
|
+
* @implements `SelectorAttr#absent` of `@markuplint/selector`
|
|
390
|
+
*/
|
|
391
|
+
get absent() {
|
|
392
|
+
return this.isAbsent === true;
|
|
393
|
+
}
|
|
303
394
|
/**
|
|
304
395
|
* The name that a selector is matched with.
|
|
305
396
|
*
|
|
@@ -392,7 +483,105 @@ export class MLAttr extends MLNode {
|
|
|
392
483
|
* @see https://dom.spec.whatwg.org/#dom-attr-value
|
|
393
484
|
*/
|
|
394
485
|
get value() {
|
|
395
|
-
return this.#
|
|
486
|
+
return this.#render().value;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Reads the literal that the value is written as, which is decided by the language of the
|
|
490
|
+
* code: what the parser says for the attribute it reads, what the directive pattern says for the
|
|
491
|
+
* one that a spec resolves, and always JavaScript for the one that a pretender makes.
|
|
492
|
+
*
|
|
493
|
+
* An attribute that has no value (`:disabled`, which Vue reads as `:disabled="disabled"`) is not the
|
|
494
|
+
* empty string, and a value that is not an attribute value (the condition of a directive, a
|
|
495
|
+
* `{name}` shorthand, which have a `potentialName`) is not read.
|
|
496
|
+
*
|
|
497
|
+
* @param language - The language that the parser or the pattern says
|
|
498
|
+
*/
|
|
499
|
+
#readLiteral(language) {
|
|
500
|
+
if (this.isDirective || !this.valueNode) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
if (this.isExpressionValue) {
|
|
504
|
+
const isJavaScript = this.#isPretended || language === 'javascript';
|
|
505
|
+
// A value that is not written has no equal sign. A pretender's expression has none either.
|
|
506
|
+
const isWritten = this.#isPretended || this.equal?.raw !== '';
|
|
507
|
+
// `potentialValue` is the expression itself when the value of the token has more than it
|
|
508
|
+
// (Svelte's `disabled="{false}"` is the expression `false` in quotes)
|
|
509
|
+
return isJavaScript && isWritten ? parseLiteralExpression(this.#potentialValue) : null;
|
|
510
|
+
}
|
|
511
|
+
// The shorthand of JSX: an attribute without a value is `true`. An attribute that a pretender made has no
|
|
512
|
+
// tokens of a value either, and is not written in JSX.
|
|
513
|
+
const document = this.ownerElement.ownerMLDocument;
|
|
514
|
+
if (document.booleanish &&
|
|
515
|
+
!this.#isPretended &&
|
|
516
|
+
this._astToken.type === 'attr' &&
|
|
517
|
+
this._astToken.potentialName == null &&
|
|
518
|
+
this.equal?.raw === '' &&
|
|
519
|
+
this.valueNode.raw === '') {
|
|
520
|
+
return { type: 'boolean', value: true, key: 'true' };
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* What the attribute is: the value, whether it is known, and whether it is output at all.
|
|
526
|
+
* A literal is rendered as the framework renders it (see `renderLiteral`), which depends on the
|
|
527
|
+
* owner element, so it is done on first use and not by the constructor: `MLElement` makes its
|
|
528
|
+
* attributes before it knows its own type.
|
|
529
|
+
*/
|
|
530
|
+
#render() {
|
|
531
|
+
if (this.#rendering) {
|
|
532
|
+
return this.#rendering;
|
|
533
|
+
}
|
|
534
|
+
const literal = this.#literal;
|
|
535
|
+
let rendering;
|
|
536
|
+
if (!literal) {
|
|
537
|
+
rendering = {
|
|
538
|
+
value: this.#potentialValue,
|
|
539
|
+
dynamic: this.isExpressionValue === true,
|
|
540
|
+
absent: false,
|
|
541
|
+
resolved: false,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
else if (this.ownerElement.elementType === 'authored') {
|
|
545
|
+
// A component receives the literal as a prop. It is rendered by the element that the
|
|
546
|
+
// prop is forwarded to, which is not known here.
|
|
547
|
+
rendering = { value: literalToString(literal), dynamic: false, absent: false, resolved: true };
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const rendered = renderLiteral(literal, this.#renderingContext());
|
|
551
|
+
switch (rendered.kind) {
|
|
552
|
+
case 'value': {
|
|
553
|
+
rendering = { value: rendered.value, dynamic: false, absent: false, resolved: true };
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case 'absent': {
|
|
557
|
+
rendering = { value: '', dynamic: true, absent: true, resolved: true };
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
default: {
|
|
561
|
+
rendering = { value: '', dynamic: true, absent: false, resolved: false };
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Before the owner element is complete, what depends on it is not final
|
|
566
|
+
if (this.ownerElement.elementType !== undefined) {
|
|
567
|
+
this.#rendering = rendering;
|
|
568
|
+
}
|
|
569
|
+
return rendering;
|
|
570
|
+
}
|
|
571
|
+
#renderingContext() {
|
|
572
|
+
const el = this.ownerElement;
|
|
573
|
+
const document = el.ownerMLDocument;
|
|
574
|
+
const specs = document.specs;
|
|
575
|
+
const name = asciiLowerCase(this.#potentialName);
|
|
576
|
+
return {
|
|
577
|
+
name,
|
|
578
|
+
isBoolean: getBooleanAttrNames(specs).has(name),
|
|
579
|
+
// A target of a pretender is made as an element of HTML even when its name is a custom element's
|
|
580
|
+
isCustomElement: el.elementType === 'web-component' ||
|
|
581
|
+
(el.elementType === 'html' && !el.isForeignElement && el.localName.includes('-')),
|
|
582
|
+
specLiteralValues: specs.literalValues,
|
|
583
|
+
parserLiteralValues: document.literalValues,
|
|
584
|
+
};
|
|
396
585
|
}
|
|
397
586
|
/**
|
|
398
587
|
* Returns a normalized string representation of the attribute,
|
|
@@ -10,10 +10,15 @@ import { MLNode } from './node.js';
|
|
|
10
10
|
* loops (`each`), and other preprocessor directives that are not part of standard HTML.
|
|
11
11
|
*
|
|
12
12
|
* Serves as the bridge between template syntax and HTML content model
|
|
13
|
-
* validation
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* validation. `isTransparent` is always true, so `parentNode` walks through
|
|
14
|
+
* every preprocessor block and an element inside a block keeps the element
|
|
15
|
+
* that contains the block as its parent.
|
|
16
|
+
*
|
|
17
|
+
* `childNodes` does not follow that. Only fragments and blocks whose
|
|
18
|
+
* `blockBehavior.type` is `each` or `end` are flattened into the parent.
|
|
19
|
+
* A conditional block stays in `childNodes` so `conditionalChildNodes()` can
|
|
20
|
+
* tell its branches apart. Flattening those branches would make elements
|
|
21
|
+
* that never render together into siblings.
|
|
17
22
|
*
|
|
18
23
|
* @template T - The rule configuration value type
|
|
19
24
|
* @template O - The rule options type
|
|
@@ -24,8 +29,12 @@ export declare class MLBlock<T extends RuleConfigValue, O extends PlainData = un
|
|
|
24
29
|
*/
|
|
25
30
|
readonly blockBehavior: MLASTBlockBehavior | null;
|
|
26
31
|
/**
|
|
27
|
-
* Whether this block
|
|
28
|
-
*
|
|
32
|
+
* Whether `parentNode` walks through this block. Always true, so an
|
|
33
|
+
* element inside the block keeps the element that contains the block as
|
|
34
|
+
* its parent. This flag does not decide `childNodes` flattening: only
|
|
35
|
+
* fragments and blocks whose `blockBehavior.type` is `each` or `end` are
|
|
36
|
+
* flattened, and a conditional block stays so its branches can be told
|
|
37
|
+
* apart.
|
|
29
38
|
*/
|
|
30
39
|
readonly isTransparent: boolean;
|
|
31
40
|
/**
|
package/lib/ml-dom/node/block.js
CHANGED
|
@@ -6,10 +6,15 @@ import { MLNode } from './node.js';
|
|
|
6
6
|
* loops (`each`), and other preprocessor directives that are not part of standard HTML.
|
|
7
7
|
*
|
|
8
8
|
* Serves as the bridge between template syntax and HTML content model
|
|
9
|
-
* validation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* validation. `isTransparent` is always true, so `parentNode` walks through
|
|
10
|
+
* every preprocessor block and an element inside a block keeps the element
|
|
11
|
+
* that contains the block as its parent.
|
|
12
|
+
*
|
|
13
|
+
* `childNodes` does not follow that. Only fragments and blocks whose
|
|
14
|
+
* `blockBehavior.type` is `each` or `end` are flattened into the parent.
|
|
15
|
+
* A conditional block stays in `childNodes` so `conditionalChildNodes()` can
|
|
16
|
+
* tell its branches apart. Flattening those branches would make elements
|
|
17
|
+
* that never render together into siblings.
|
|
13
18
|
*
|
|
14
19
|
* @template T - The rule configuration value type
|
|
15
20
|
* @template O - The rule options type
|
|
@@ -20,8 +25,12 @@ export class MLBlock extends MLNode {
|
|
|
20
25
|
*/
|
|
21
26
|
blockBehavior;
|
|
22
27
|
/**
|
|
23
|
-
* Whether this block
|
|
24
|
-
*
|
|
28
|
+
* Whether `parentNode` walks through this block. Always true, so an
|
|
29
|
+
* element inside the block keeps the element that contains the block as
|
|
30
|
+
* its parent. This flag does not decide `childNodes` flattening: only
|
|
31
|
+
* fragments and blocks whose `blockBehavior.type` is `each` or `end` are
|
|
32
|
+
* flattened, and a conditional block stays so its branches can be told
|
|
33
|
+
* apart.
|
|
25
34
|
*/
|
|
26
35
|
isTransparent;
|
|
27
36
|
/**
|
|
@@ -34,8 +43,8 @@ export class MLBlock extends MLNode {
|
|
|
34
43
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
35
44
|
document) {
|
|
36
45
|
super(astNode, document, astNode.isFragment);
|
|
37
|
-
//
|
|
38
|
-
//
|
|
46
|
+
// `parentNode` walks through every block. `childNodes` flattens only
|
|
47
|
+
// `each` and `end`; see the class comment.
|
|
39
48
|
this.isTransparent = true;
|
|
40
49
|
this.blockBehavior = astNode.blockBehavior;
|
|
41
50
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import type { MLElement } from './element.js';
|
|
1
2
|
import type { DocumentFragmentNodeType } from './types.js';
|
|
2
3
|
import type { MLASTNode } from '@markuplint/ml-ast';
|
|
3
4
|
import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
4
5
|
import { MLParentNode } from './parent-node.js';
|
|
5
6
|
/**
|
|
6
7
|
* Represents a DOM DocumentFragment node wrapper in the markuplint DOM tree.
|
|
7
|
-
*
|
|
8
|
-
* is
|
|
8
|
+
*
|
|
9
|
+
* It is the `content` of an HTML `template` element, a tree of its own. markuplint does not
|
|
10
|
+
* keep it apart in the syntax tree: the children of the `template` element are the children of
|
|
11
|
+
* the fragment, as the same nodes, so the fragment is built from a copy of the AST node of the
|
|
12
|
+
* `template` element that has a uuid of its own (the node store keys nodes by uuid).
|
|
13
|
+
*
|
|
14
|
+
* It has no parent: a document fragment is a root, and `getRootNode()` of it is itself.
|
|
9
15
|
*
|
|
10
16
|
* @template T - The rule configuration value type
|
|
11
17
|
* @template O - The rule options type
|
|
@@ -27,11 +33,17 @@ export declare class MLDocumentFragment<T extends RuleConfigValue, O extends Pla
|
|
|
27
33
|
*/
|
|
28
34
|
get textContent(): string;
|
|
29
35
|
/**
|
|
30
|
-
*
|
|
36
|
+
* A document fragment has no parent.
|
|
37
|
+
*
|
|
38
|
+
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
39
|
+
*/
|
|
40
|
+
get syntacticalParentNode(): null;
|
|
41
|
+
/**
|
|
42
|
+
* Finds the first element in this tree whose id is `elementId`: not in the content of a `template`
|
|
43
|
+
* element under it, which is a tree of its own.
|
|
31
44
|
*
|
|
32
|
-
* @deprecated
|
|
33
|
-
* @unsupported
|
|
34
45
|
* @implements DOM API: `DocumentFragment`
|
|
46
|
+
* @see https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
|
|
35
47
|
*/
|
|
36
|
-
getElementById(elementId: string):
|
|
48
|
+
getElementById(elementId: string): MLElement<T, O> | null;
|
|
37
49
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { MLParentNode } from './parent-node.js';
|
|
2
|
-
import { UnexpectedCallError } from '@markuplint/shared';
|
|
3
2
|
/**
|
|
4
3
|
* Represents a DOM DocumentFragment node wrapper in the markuplint DOM tree.
|
|
5
|
-
*
|
|
6
|
-
* is
|
|
4
|
+
*
|
|
5
|
+
* It is the `content` of an HTML `template` element, a tree of its own. markuplint does not
|
|
6
|
+
* keep it apart in the syntax tree: the children of the `template` element are the children of
|
|
7
|
+
* the fragment, as the same nodes, so the fragment is built from a copy of the AST node of the
|
|
8
|
+
* `template` element that has a uuid of its own (the node store keys nodes by uuid).
|
|
9
|
+
*
|
|
10
|
+
* It has no parent: a document fragment is a root, and `getRootNode()` of it is itself.
|
|
7
11
|
*
|
|
8
12
|
* @template T - The rule configuration value type
|
|
9
13
|
* @template O - The rule options type
|
|
@@ -31,13 +35,21 @@ export class MLDocumentFragment extends MLParentNode {
|
|
|
31
35
|
return [...this.childNodes].map(child => child.textContent ?? '').join('');
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
38
|
+
* A document fragment has no parent.
|
|
39
|
+
*
|
|
40
|
+
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
41
|
+
*/
|
|
42
|
+
get syntacticalParentNode() {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Finds the first element in this tree whose id is `elementId`: not in the content of a `template`
|
|
47
|
+
* element under it, which is a tree of its own.
|
|
35
48
|
*
|
|
36
|
-
* @deprecated
|
|
37
|
-
* @unsupported
|
|
38
49
|
* @implements DOM API: `DocumentFragment`
|
|
50
|
+
* @see https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
|
|
39
51
|
*/
|
|
40
52
|
getElementById(elementId) {
|
|
41
|
-
|
|
53
|
+
return this._getElementById(elementId);
|
|
42
54
|
}
|
|
43
55
|
}
|
|
@@ -10,7 +10,7 @@ import type { Ruleset } from '../../ruleset/index.js';
|
|
|
10
10
|
import type { MLSchema } from '../../types.js';
|
|
11
11
|
import type { Walker } from '../helper/walkers.js';
|
|
12
12
|
import type { MLToken } from '../token/token.js';
|
|
13
|
-
import type { AttrNameRoute, EndTagType, MLASTDocument, MLASTNodeTreeItem, MLASTParseError } from '@markuplint/ml-ast';
|
|
13
|
+
import type { AttrNameRoute, EndTagType, ExpressionLanguage, LiteralValueDeclaration, MLASTDocument, MLASTNodeTreeItem, MLASTParseError } from '@markuplint/ml-ast';
|
|
14
14
|
import type { PlainData, Pretender, RuleCommonSettings, RuleConfigValue } from '@markuplint/ml-config';
|
|
15
15
|
import type { ARIAVersion, MLMLSpec } from '@markuplint/ml-spec';
|
|
16
16
|
import { MLParentNode } from './parent-node.js';
|
|
@@ -38,6 +38,23 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
38
38
|
* @default false
|
|
39
39
|
*/
|
|
40
40
|
readonly booleanish: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The language of the code that an attribute value is written in when it is written as code
|
|
43
|
+
* (`MLParser#attrExpressionLanguage`). Absent when the parser does not say; then no attribute
|
|
44
|
+
* value of the document is read as a literal (a pretender's attribute is, whatever it says).
|
|
45
|
+
*/
|
|
46
|
+
readonly attrExpressionLanguage?: ExpressionLanguage;
|
|
47
|
+
/**
|
|
48
|
+
* How the framework of the parser renders a literal in an attribute value, where it differs from
|
|
49
|
+
* the default (`MLParser#literalValues`). What the spec says (`MLMLSpec#literalValues`) wins over
|
|
50
|
+
* this.
|
|
51
|
+
*/
|
|
52
|
+
readonly literalValues?: LiteralValueDeclaration;
|
|
53
|
+
/**
|
|
54
|
+
* What the `slot` element is in the language of the document (`MLParser#slotElement`): the HTML
|
|
55
|
+
* element, or the placeholder of a framework's component, whose slot assignment is not computed.
|
|
56
|
+
*/
|
|
57
|
+
readonly slotElement: 'html' | 'framework';
|
|
41
58
|
/**
|
|
42
59
|
* The rule currently being evaluated during a lint pass.
|
|
43
60
|
* Set by the linting engine before rule evaluation begins and used by nodes
|
|
@@ -137,6 +154,9 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
137
154
|
readonly filename?: string;
|
|
138
155
|
readonly endTag?: 'xml' | 'omittable' | 'never';
|
|
139
156
|
readonly booleanish?: boolean;
|
|
157
|
+
readonly attrExpressionLanguage?: ExpressionLanguage;
|
|
158
|
+
readonly literalValues?: LiteralValueDeclaration;
|
|
159
|
+
readonly slotElement?: 'html' | 'framework';
|
|
140
160
|
readonly tagNameCaseSensitive?: boolean;
|
|
141
161
|
readonly componentNameVariants?: boolean;
|
|
142
162
|
readonly isComponentName?: (name: string) => boolean;
|
|
@@ -1641,14 +1661,17 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
1641
1661
|
*/
|
|
1642
1662
|
getAnimations(): Animation[];
|
|
1643
1663
|
/**
|
|
1664
|
+
* The first element of the tree of the document whose id is `elementId`, with the character
|
|
1665
|
+
* references in the markup resolved: not in the content of an HTML `template` element, which is a
|
|
1666
|
+
* tree of its own.
|
|
1667
|
+
*
|
|
1644
1668
|
* @implements DOM API: `Document`
|
|
1669
|
+
* @see https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
|
|
1645
1670
|
*/
|
|
1646
1671
|
getElementById(elementId: string): MLElement<T, O> | null;
|
|
1647
1672
|
/**
|
|
1648
|
-
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
1649
|
-
*
|
|
1650
|
-
* @unsupported
|
|
1651
1673
|
* @implements DOM API: `Document`
|
|
1674
|
+
* @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
|
|
1652
1675
|
*/
|
|
1653
1676
|
getElementsByClassName(classNames: string): HTMLCollectionOf<MLElement<T, O>>;
|
|
1654
1677
|
/**
|
|
@@ -1659,10 +1682,12 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
1659
1682
|
*/
|
|
1660
1683
|
getElementsByName(elementName: string): NodeListOf<HTMLElement>;
|
|
1661
1684
|
/**
|
|
1662
|
-
*
|
|
1685
|
+
* The elements of the tree of the document that a type selector of that name matches, so that a
|
|
1686
|
+
* name is compared as a selector compares it in the language of the document (the case, and a
|
|
1687
|
+
* component that a pretender stands for), not as the DOM compares a tag name.
|
|
1663
1688
|
*
|
|
1664
|
-
* @unsupported
|
|
1665
1689
|
* @implements DOM API: `Document`
|
|
1690
|
+
* @see https://dom.spec.whatwg.org/#concept-getelementsbytagname
|
|
1666
1691
|
*/
|
|
1667
1692
|
getElementsByTagName(qualifiedName: string): HTMLCollectionOf<MLElement<T, O>>;
|
|
1668
1693
|
/**
|
|
@@ -1765,12 +1790,16 @@ export declare class MLDocument<T extends RuleConfigValue, O extends PlainData =
|
|
|
1765
1790
|
/**
|
|
1766
1791
|
* Searches for a node at the given source location (line and column).
|
|
1767
1792
|
*
|
|
1793
|
+
* The node is the innermost one whose source range, from its start, included,
|
|
1794
|
+
* to its end, excluded, holds the position; a position in an end tag is its
|
|
1795
|
+
* element. The rule is `searchNodeAtLocation`'s.
|
|
1796
|
+
*
|
|
1768
1797
|
* @implements `@markuplint/ml-core` API: `MLDocument`
|
|
1769
1798
|
* @param line - The 1-based line number to search at
|
|
1770
1799
|
* @param col - The 1-based column number to search at
|
|
1771
1800
|
* @returns The node at the given location, or null if not found
|
|
1772
1801
|
*/
|
|
1773
|
-
searchNodeByLocation(line: number, col: number): MLNode<T, O
|
|
1802
|
+
searchNodeByLocation(line: number, col: number): MLNode<T, O> | null;
|
|
1774
1803
|
/**
|
|
1775
1804
|
* Sets the currently active rule for this document. Called by the linting engine
|
|
1776
1805
|
* before each rule evaluation pass.
|