@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/ml-core.js +3 -0
  3. package/lib/ml-dom/helper/accname.d.ts +35 -0
  4. package/lib/ml-dom/helper/accname.js +69 -17
  5. package/lib/ml-dom/helper/debug.js +1 -1
  6. package/lib/ml-dom/helper/literal-values.d.ts +102 -0
  7. package/lib/ml-dom/helper/literal-values.js +193 -0
  8. package/lib/ml-dom/helper/pretended-attrs.d.ts +16 -0
  9. package/lib/ml-dom/helper/pretended-attrs.js +21 -0
  10. package/lib/ml-dom/helper/slot-assignment.d.ts +48 -0
  11. package/lib/ml-dom/helper/slot-assignment.js +292 -0
  12. package/lib/ml-dom/helper/tree-scope.d.ts +130 -0
  13. package/lib/ml-dom/helper/tree-scope.js +341 -0
  14. package/lib/ml-dom/helper/walkers.d.ts +15 -0
  15. package/lib/ml-dom/helper/walkers.js +34 -0
  16. package/lib/ml-dom/index.d.ts +4 -0
  17. package/lib/ml-dom/index.js +2 -0
  18. package/lib/ml-dom/node/attr.d.ts +77 -10
  19. package/lib/ml-dom/node/attr.js +209 -20
  20. package/lib/ml-dom/node/block.d.ts +15 -6
  21. package/lib/ml-dom/node/block.js +17 -8
  22. package/lib/ml-dom/node/document-fragment.d.ts +18 -6
  23. package/lib/ml-dom/node/document-fragment.js +19 -7
  24. package/lib/ml-dom/node/document.d.ts +36 -7
  25. package/lib/ml-dom/node/document.js +60 -17
  26. package/lib/ml-dom/node/dom-token-list.js +28 -3
  27. package/lib/ml-dom/node/element.d.ts +148 -24
  28. package/lib/ml-dom/node/element.js +258 -48
  29. package/lib/ml-dom/node/named-node-map.js +2 -1
  30. package/lib/ml-dom/node/node-list.js +2 -3
  31. package/lib/ml-dom/node/node.d.ts +55 -10
  32. package/lib/ml-dom/node/node.js +98 -13
  33. package/lib/ml-dom/node/parent-node.d.ts +39 -8
  34. package/lib/ml-dom/node/parent-node.js +79 -12
  35. package/lib/ml-dom/node/shadow-root.d.ts +162 -0
  36. package/lib/ml-dom/node/shadow-root.js +219 -0
  37. package/lib/ml-dom/node/text.d.ts +12 -3
  38. package/lib/ml-dom/node/text.js +14 -3
  39. package/lib/ml-rule/ml-rule.js +4 -1
  40. package/lib/test/index.js +3 -0
  41. package/lib/utils/index.d.ts +2 -0
  42. package/lib/utils/index.js +1 -0
  43. package/lib/utils/search-node-at-location.d.ts +39 -0
  44. package/lib/utils/search-node-at-location.js +53 -0
  45. package/package.json +12 -15
@@ -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 has a dynamic value (e.g., a template expression rather than a static string).
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
- isDynamicValue;
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.valueType = 'code';
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.isDynamicValue = true;
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.isDynamicValue = resolution.isDynamicValue;
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.valueType = resolution.valueType;
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.isDynamicValue = this._astToken.isDynamicValue;
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.isDynamicValue = this._astToken.isDynamicValue;
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 dynamic.
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.isDynamicValue === true);
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.#potentialValue;
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: transparency keeps the wrapper invisible to DOM traversal so
14
- * rules such as `permitted-contents` see the effective HTML children, while
15
- * `blockBehavior` lets `conditionalChildNodes()` enumerate every possible
16
- * rendering branch.
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 is transparent, meaning its children are treated
28
- * as belonging to the parent node for tree traversal purposes.
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
  /**
@@ -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: transparency keeps the wrapper invisible to DOM traversal so
10
- * rules such as `permitted-contents` see the effective HTML children, while
11
- * `blockBehavior` lets `conditionalChildNodes()` enumerate every possible
12
- * rendering branch.
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 is transparent, meaning its children are treated
24
- * as belonging to the parent node for tree traversal purposes.
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
- // Always transparent: blockBehavior may restrict child treatment in the future,
38
- // but currently all preprocessor blocks are transparent for tree traversal.
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
- * Used for JSX fragments and similar constructs where a set of nodes
8
- * is grouped without a wrapping element.
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
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
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): HTMLElement | null;
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
- * Used for JSX fragments and similar constructs where a set of nodes
6
- * is grouped without a wrapping element.
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
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
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
- throw new UnexpectedCallError('Not supported "getElementById" method');
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
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
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, import("@markuplint/ml-ast").MLASTNode> | null;
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.