@markuplint/ml-core 5.5.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 +15 -0
- package/lib/ml-core.js +1 -0
- package/lib/ml-dom/helper/accname.d.ts +6 -3
- package/lib/ml-dom/helper/accname.js +14 -7
- 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 +42 -5
- package/lib/ml-dom/helper/tree-scope.js +35 -8
- package/lib/ml-dom/node/document.d.ts +11 -1
- package/lib/ml-dom/node/document.js +26 -9
- package/lib/ml-dom/node/element.d.ts +60 -6
- package/lib/ml-dom/node/element.js +90 -15
- package/lib/ml-dom/node/node.d.ts +12 -5
- package/lib/ml-dom/node/node.js +12 -5
- package/lib/ml-dom/node/text.d.ts +12 -3
- package/lib/ml-dom/node/text.js +14 -3
- package/lib/test/index.js +1 -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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { exchangeValueOnRule, mergeRule } from '@markuplint/ml-config';
|
|
2
|
-
import { schemaToSpec, getComputedRole, mayBeFocusable, getComputedAriaProps, isExposed, getSpecByTagName, ARIA_RECOMMENDED_VERSION, } from '@markuplint/ml-spec';
|
|
2
|
+
import { schemaToSpec, getComputedRole, mayBeFocusable, getComputedAriaProps, isExposed, isShadowRoot, getSpecByTagName, ARIA_RECOMMENDED_VERSION, } from '@markuplint/ml-spec';
|
|
3
3
|
import { ConfigParserError, isComponentName } from '@markuplint/parser-utils';
|
|
4
4
|
import { InvalidSelectorError } from '@markuplint/selector';
|
|
5
5
|
import { log as coreLog } from '../../debug.js';
|
|
6
|
+
import { searchNodeAtLocation } from '../../utils/search-node-at-location.js';
|
|
6
7
|
import { createNode } from '../helper/create-node.js';
|
|
7
8
|
import { nodeListToDebugMaps } from '../helper/debug.js';
|
|
8
9
|
import { sequentialWalker, syncWalk } from '../helper/walkers.js';
|
|
@@ -48,6 +49,11 @@ export class MLDocument extends MLParentNode {
|
|
|
48
49
|
* this.
|
|
49
50
|
*/
|
|
50
51
|
literalValues;
|
|
52
|
+
/**
|
|
53
|
+
* What the `slot` element is in the language of the document (`MLParser#slotElement`): the HTML
|
|
54
|
+
* element, or the placeholder of a framework's component, whose slot assignment is not computed.
|
|
55
|
+
*/
|
|
56
|
+
slotElement;
|
|
51
57
|
/**
|
|
52
58
|
* The rule currently being evaluated during a lint pass.
|
|
53
59
|
* Set by the linting engine before rule evaluation begins and used by nodes
|
|
@@ -159,6 +165,7 @@ export class MLDocument extends MLParentNode {
|
|
|
159
165
|
this.booleanish = options?.booleanish ?? false;
|
|
160
166
|
this.attrExpressionLanguage = options?.attrExpressionLanguage;
|
|
161
167
|
this.literalValues = options?.literalValues;
|
|
168
|
+
this.slotElement = options?.slotElement ?? 'html';
|
|
162
169
|
this.endTag = options?.endTag ?? 'omittable';
|
|
163
170
|
this.#filename = options?.filename;
|
|
164
171
|
this.tagNameCaseSensitive = options?.tagNameCaseSensitive ?? false;
|
|
@@ -179,8 +186,10 @@ export class MLDocument extends MLParentNode {
|
|
|
179
186
|
return createNode(astNode, this);
|
|
180
187
|
})
|
|
181
188
|
.filter((n) => !!n));
|
|
182
|
-
|
|
189
|
+
// A selector of the configuration is matched here, by a pretender and by a rule, so an
|
|
190
|
+
// invalid one is the fault of the configuration in both.
|
|
183
191
|
try {
|
|
192
|
+
this.#pretending(options?.pretenders);
|
|
184
193
|
this.#ruleMapping(ruleset);
|
|
185
194
|
}
|
|
186
195
|
catch (error) {
|
|
@@ -2133,7 +2142,16 @@ export class MLDocument extends MLParentNode {
|
|
|
2133
2142
|
const focusable = mayBeFocusable(node, node.ownerMLDocument.specs);
|
|
2134
2143
|
const nameRequired = role.role?.accessibleNameRequired ?? false;
|
|
2135
2144
|
const nameProhibited = role.role?.accessibleNameProhibited ?? false;
|
|
2136
|
-
|
|
2145
|
+
// A slot whose assignment is known is read by `getAccessibleName` above. The slot of a shadow root is in
|
|
2146
|
+
// a tree of its own, so every tree is searched, except the content of an ordinary `template` element,
|
|
2147
|
+
// which is not rendered where it is written.
|
|
2148
|
+
const hasSlot = node.deepQuerySelectorAll('slot').some(slot => {
|
|
2149
|
+
if (slot.getAssignedNodes() !== null) {
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
const root = slot.getRootNode();
|
|
2153
|
+
return isShadowRoot(root) || !root.is(root.DOCUMENT_FRAGMENT_NODE);
|
|
2154
|
+
});
|
|
2137
2155
|
aria.role = role.role?.name;
|
|
2138
2156
|
aria.nameRequired = nameRequired;
|
|
2139
2157
|
aria.nameProhibited = nameProhibited;
|
|
@@ -2338,18 +2356,17 @@ export class MLDocument extends MLParentNode {
|
|
|
2338
2356
|
/**
|
|
2339
2357
|
* Searches for a node at the given source location (line and column).
|
|
2340
2358
|
*
|
|
2359
|
+
* The node is the innermost one whose source range, from its start, included,
|
|
2360
|
+
* to its end, excluded, holds the position; a position in an end tag is its
|
|
2361
|
+
* element. The rule is `searchNodeAtLocation`'s.
|
|
2362
|
+
*
|
|
2341
2363
|
* @implements `@markuplint/ml-core` API: `MLDocument`
|
|
2342
2364
|
* @param line - The 1-based line number to search at
|
|
2343
2365
|
* @param col - The 1-based column number to search at
|
|
2344
2366
|
* @returns The node at the given location, or null if not found
|
|
2345
2367
|
*/
|
|
2346
2368
|
searchNodeByLocation(line, col) {
|
|
2347
|
-
|
|
2348
|
-
if (node.startLine <= line && line <= node.endLine && node.startCol <= col && col <= node.endCol) {
|
|
2349
|
-
return node;
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
return null;
|
|
2369
|
+
return searchNodeAtLocation(this.nodeList, line, col);
|
|
2353
2370
|
}
|
|
2354
2371
|
/**
|
|
2355
2372
|
* Sets the currently active rule for this document. Called by the linting engine
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeclarativeShadowRootAttachment } from '../helper/tree-scope.js';
|
|
2
|
+
import type { MLChildNode } from './child-node.js';
|
|
2
3
|
import type { MLDocument } from './document.js';
|
|
3
4
|
import type { MLNamedNodeMap } from './named-node-map.js';
|
|
4
5
|
import type { MLNode } from './node.js';
|
|
@@ -604,9 +605,10 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
604
605
|
*/
|
|
605
606
|
get ariaValueText(): string | null;
|
|
606
607
|
/**
|
|
607
|
-
*
|
|
608
|
+
* The slot of an open declarative shadow root that this element is assigned to, or `null` when no
|
|
609
|
+
* slot takes it, the root is closed (the DOM does not expose it), or the assignment is not known
|
|
610
|
+
* (see {@link MLElement.getFlatTreeSlot}).
|
|
608
611
|
*
|
|
609
|
-
* @unsupported
|
|
610
612
|
* @implements DOM API: `Element`
|
|
611
613
|
* @see https://dom.spec.whatwg.org/#dom-slotable-assignedslot
|
|
612
614
|
*/
|
|
@@ -1938,9 +1940,35 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
1938
1940
|
*/
|
|
1939
1941
|
animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions): Animation;
|
|
1940
1942
|
/**
|
|
1943
|
+
* The nodes this `slot` places, in tree order, or with `flatten` what it renders in the flat tree. It
|
|
1944
|
+
* is empty when the assignment is not known; use {@link MLElement.getAssignedNodes} to tell the two apart.
|
|
1945
|
+
*
|
|
1946
|
+
* @implements DOM API: `HTMLSlotElement`
|
|
1941
1947
|
* @see https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignednodes
|
|
1942
1948
|
*/
|
|
1943
|
-
assignedNodes():
|
|
1949
|
+
assignedNodes(options?: AssignedNodesOptions): MLChildNode<T, O>[];
|
|
1950
|
+
/**
|
|
1951
|
+
* The nodes this `slot` places, by the named slot assignment of the declarative shadow root it is in.
|
|
1952
|
+
* A slot that takes none has `[]`, and renders its own children. `null` when the assignment is not
|
|
1953
|
+
* known; what makes it so is in the module comment of `helper/slot-assignment.ts`.
|
|
1954
|
+
*
|
|
1955
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
1956
|
+
* @see https://dom.spec.whatwg.org/#find-slotables
|
|
1957
|
+
*/
|
|
1958
|
+
getAssignedNodes(): readonly MLChildNode<T, O>[] | null;
|
|
1959
|
+
/**
|
|
1960
|
+
* The slot that places this element in the flat tree: the slot of the shadow root of its parent that
|
|
1961
|
+
* takes it. `null` when its parent is a shadow host and no slot takes it, so that it is not rendered;
|
|
1962
|
+
* `undefined` when its parent is not a shadow host, or the assignment is not known
|
|
1963
|
+
* (see {@link MLElement.getAssignedNodes}).
|
|
1964
|
+
*
|
|
1965
|
+
* `getComposedParentElement` of `@markuplint/ml-spec` reads it as the parent that the accessibility
|
|
1966
|
+
* tree has for the element.
|
|
1967
|
+
*
|
|
1968
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
1969
|
+
* @see https://dom.spec.whatwg.org/#find-a-slot
|
|
1970
|
+
*/
|
|
1971
|
+
getFlatTreeSlot(): MLElement<T, O> | null | undefined;
|
|
1944
1972
|
/**
|
|
1945
1973
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
1946
1974
|
*
|
|
@@ -2229,6 +2257,21 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
2229
2257
|
* @returns True if any attribute is dynamic or lacks a name node (spread)
|
|
2230
2258
|
*/
|
|
2231
2259
|
hasMutableAttributes(): boolean;
|
|
2260
|
+
/**
|
|
2261
|
+
* Checks whether this element is a component whose rendered output is not known: the
|
|
2262
|
+
* parser sees it as a component (`elementType` is `'authored'`) and no pretender stands
|
|
2263
|
+
* for an element of HTML, which includes a pretender whose own target is a component
|
|
2264
|
+
* (the chain of `pretenders.auto` stopped at `Tabs.Root`).
|
|
2265
|
+
*
|
|
2266
|
+
* A web component is not one: it is the element that it is written as. The criterion of
|
|
2267
|
+
* `isTreatedAsCustom` in `@markuplint/rules` (`permitted-contents`) includes it, because
|
|
2268
|
+
* a content model accepts a custom element wherever it accepts flow content; change them
|
|
2269
|
+
* together.
|
|
2270
|
+
*
|
|
2271
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2272
|
+
* @returns True if what this element renders is not known
|
|
2273
|
+
*/
|
|
2274
|
+
isUnresolvedComponent(): boolean;
|
|
2232
2275
|
/**
|
|
2233
2276
|
* Checks whether this element has children that are potentially mutable,
|
|
2234
2277
|
* such as preprocessor-specific blocks, slot elements, or (optionally) elements
|
|
@@ -2243,15 +2286,26 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
2243
2286
|
* A component that no pretender resolved renders something that is not known, however its
|
|
2244
2287
|
* attributes are written (`size={size}`, `size={16}` and `size="16"` tell the same): a rule that
|
|
2245
2288
|
* asks about what the descendants are or have (the accessible name, the owned elements) cannot decide
|
|
2246
|
-
* on it, so it is mutable when `attr` is true. A component
|
|
2247
|
-
* that it renders, which the rules read themselves.
|
|
2289
|
+
* on it, so it is mutable when `attr` is true ({@link MLElement.isUnresolvedComponent}). A component
|
|
2290
|
+
* that a pretender resolved is the element that it renders, which the rules read themselves.
|
|
2291
|
+
*
|
|
2292
|
+
* A rule that asks which children an element has (the required children, the elements that
|
|
2293
|
+
* satisfy a query) does not pass `attr`: an attribute that cannot be known does not decide whether
|
|
2294
|
+
* a child exists. It looks for the unresolved components among the children itself.
|
|
2295
|
+
*
|
|
2296
|
+
* Every `slot` is mutable by default, as a `slot` that is not in a declarative shadow root renders what
|
|
2297
|
+
* is given to it, and a rule that reads the children as they are written (`require-datetime`) cannot
|
|
2298
|
+
* know it. A rule that reads what a slot of a shadow root renders by `MLElement#getAssignedNodes` says
|
|
2299
|
+
* which slots are with `isMutableSlot`.
|
|
2300
|
+
*
|
|
2248
2301
|
*
|
|
2249
2302
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2250
2303
|
* @param attr - When true, also considers children with mutable attributes, and components that no
|
|
2251
2304
|
* pretender resolved, as mutable
|
|
2305
|
+
* @param isMutableSlot - Whether a `slot` among the descendants may render what is not known
|
|
2252
2306
|
* @returns True if this element has potentially mutable children
|
|
2253
2307
|
*/
|
|
2254
|
-
hasMutableChildren(attr?: boolean): boolean;
|
|
2308
|
+
hasMutableChildren(attr?: boolean, isMutableSlot?: (slot: MLElement<T, O>) => boolean): boolean;
|
|
2255
2309
|
/**
|
|
2256
2310
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
2257
2311
|
*
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
var _a;
|
|
3
3
|
import { getSpecByTagName, resolveNamespace } from '@markuplint/ml-spec';
|
|
4
4
|
import { pretenderNamespaceToURI } from '@markuplint/ml-config';
|
|
5
|
-
import { getChildNamespace } from '@markuplint/parser-utils';
|
|
6
|
-
import { matchSelector, typeNameMatches } from '@markuplint/selector';
|
|
5
|
+
import { ConfigParserError, getChildNamespace } from '@markuplint/parser-utils';
|
|
6
|
+
import { InvalidSelectorError, matchSelector, typeNameMatches } from '@markuplint/selector';
|
|
7
7
|
import { getAccname, getAccnameVisits, getHostLanguageLabelSource, getReferencedTextAlternative, } from '../helper/accname.js';
|
|
8
8
|
import { literalToExpression } from '../helper/literal-values.js';
|
|
9
9
|
import { markPretendedAttr } from '../helper/pretended-attrs.js';
|
|
10
|
+
import { getAssignedNodes, getAssignedSlot, getFlatTreeSlot, getFlattenedSlottables, } from '../helper/slot-assignment.js';
|
|
10
11
|
import { getDeclarativeShadowRootAttachment, getDeclarativeShadowRootMode, getShadowRootTemplate, isHTMLTemplateElement, } from '../helper/tree-scope.js';
|
|
11
12
|
import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
|
|
12
13
|
import { MLAttr } from './attr.js';
|
|
@@ -17,7 +18,7 @@ import { toNamedNodeMap } from './named-node-map.js';
|
|
|
17
18
|
import { toHTMLCollection, toNodeList } from './node-list.js';
|
|
18
19
|
import { MLParentNode } from './parent-node.js';
|
|
19
20
|
import { MLShadowRoot } from './shadow-root.js';
|
|
20
|
-
import { UnexpectedCallError, asciiLowerCase, vuePropKey } from '@markuplint/shared';
|
|
21
|
+
import { UnexpectedCallError, asciiLowerCase, isFatalError, vuePropKey } from '@markuplint/shared';
|
|
21
22
|
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
22
23
|
/**
|
|
23
24
|
* Represents a DOM Element node wrapper in the markuplint DOM tree.
|
|
@@ -835,14 +836,15 @@ export class MLElement extends MLParentNode {
|
|
|
835
836
|
throw new UnexpectedCallError('Not supported "ariaValueText" property');
|
|
836
837
|
}
|
|
837
838
|
/**
|
|
838
|
-
*
|
|
839
|
+
* The slot of an open declarative shadow root that this element is assigned to, or `null` when no
|
|
840
|
+
* slot takes it, the root is closed (the DOM does not expose it), or the assignment is not known
|
|
841
|
+
* (see {@link MLElement.getFlatTreeSlot}).
|
|
839
842
|
*
|
|
840
|
-
* @unsupported
|
|
841
843
|
* @implements DOM API: `Element`
|
|
842
844
|
* @see https://dom.spec.whatwg.org/#dom-slotable-assignedslot
|
|
843
845
|
*/
|
|
844
846
|
get assignedSlot() {
|
|
845
|
-
|
|
847
|
+
return getAssignedSlot(this);
|
|
846
848
|
}
|
|
847
849
|
/**
|
|
848
850
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
@@ -2566,13 +2568,43 @@ export class MLElement extends MLParentNode {
|
|
|
2566
2568
|
throw new UnexpectedCallError('Not supported "animate" method');
|
|
2567
2569
|
}
|
|
2568
2570
|
/**
|
|
2571
|
+
* The nodes this `slot` places, in tree order, or with `flatten` what it renders in the flat tree. It
|
|
2572
|
+
* is empty when the assignment is not known; use {@link MLElement.getAssignedNodes} to tell the two apart.
|
|
2573
|
+
*
|
|
2574
|
+
* @implements DOM API: `HTMLSlotElement`
|
|
2569
2575
|
* @see https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignednodes
|
|
2570
2576
|
*/
|
|
2571
|
-
assignedNodes() {
|
|
2577
|
+
assignedNodes(options) {
|
|
2572
2578
|
if (this.localName !== 'slot') {
|
|
2573
2579
|
throw new TypeError('assignedNodes is not a function');
|
|
2574
2580
|
}
|
|
2575
|
-
return [];
|
|
2581
|
+
return [...(options?.flatten ? getFlattenedSlottables(this) : (this.getAssignedNodes() ?? []))];
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* The nodes this `slot` places, by the named slot assignment of the declarative shadow root it is in.
|
|
2585
|
+
* A slot that takes none has `[]`, and renders its own children. `null` when the assignment is not
|
|
2586
|
+
* known; what makes it so is in the module comment of `helper/slot-assignment.ts`.
|
|
2587
|
+
*
|
|
2588
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2589
|
+
* @see https://dom.spec.whatwg.org/#find-slotables
|
|
2590
|
+
*/
|
|
2591
|
+
getAssignedNodes() {
|
|
2592
|
+
return getAssignedNodes(this);
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* The slot that places this element in the flat tree: the slot of the shadow root of its parent that
|
|
2596
|
+
* takes it. `null` when its parent is a shadow host and no slot takes it, so that it is not rendered;
|
|
2597
|
+
* `undefined` when its parent is not a shadow host, or the assignment is not known
|
|
2598
|
+
* (see {@link MLElement.getAssignedNodes}).
|
|
2599
|
+
*
|
|
2600
|
+
* `getComposedParentElement` of `@markuplint/ml-spec` reads it as the parent that the accessibility
|
|
2601
|
+
* tree has for the element.
|
|
2602
|
+
*
|
|
2603
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2604
|
+
* @see https://dom.spec.whatwg.org/#find-a-slot
|
|
2605
|
+
*/
|
|
2606
|
+
getFlatTreeSlot() {
|
|
2607
|
+
return getFlatTreeSlot(this);
|
|
2576
2608
|
}
|
|
2577
2609
|
/**
|
|
2578
2610
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
@@ -3036,6 +3068,26 @@ export class MLElement extends MLParentNode {
|
|
|
3036
3068
|
}
|
|
3037
3069
|
return false;
|
|
3038
3070
|
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Checks whether this element is a component whose rendered output is not known: the
|
|
3073
|
+
* parser sees it as a component (`elementType` is `'authored'`) and no pretender stands
|
|
3074
|
+
* for an element of HTML, which includes a pretender whose own target is a component
|
|
3075
|
+
* (the chain of `pretenders.auto` stopped at `Tabs.Root`).
|
|
3076
|
+
*
|
|
3077
|
+
* A web component is not one: it is the element that it is written as. The criterion of
|
|
3078
|
+
* `isTreatedAsCustom` in `@markuplint/rules` (`permitted-contents`) includes it, because
|
|
3079
|
+
* a content model accepts a custom element wherever it accepts flow content; change them
|
|
3080
|
+
* together.
|
|
3081
|
+
*
|
|
3082
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
3083
|
+
* @returns True if what this element renders is not known
|
|
3084
|
+
*/
|
|
3085
|
+
isUnresolvedComponent() {
|
|
3086
|
+
if (this.pretenderContext?.type === 'pretender') {
|
|
3087
|
+
return this.pretenderContext.as.elementType === 'authored';
|
|
3088
|
+
}
|
|
3089
|
+
return this.elementType === 'authored';
|
|
3090
|
+
}
|
|
3039
3091
|
/**
|
|
3040
3092
|
* Checks whether this element has children that are potentially mutable,
|
|
3041
3093
|
* such as preprocessor-specific blocks, slot elements, or (optionally) elements
|
|
@@ -3050,15 +3102,26 @@ export class MLElement extends MLParentNode {
|
|
|
3050
3102
|
* A component that no pretender resolved renders something that is not known, however its
|
|
3051
3103
|
* attributes are written (`size={size}`, `size={16}` and `size="16"` tell the same): a rule that
|
|
3052
3104
|
* asks about what the descendants are or have (the accessible name, the owned elements) cannot decide
|
|
3053
|
-
* on it, so it is mutable when `attr` is true. A component
|
|
3054
|
-
* that it renders, which the rules read themselves.
|
|
3105
|
+
* on it, so it is mutable when `attr` is true ({@link MLElement.isUnresolvedComponent}). A component
|
|
3106
|
+
* that a pretender resolved is the element that it renders, which the rules read themselves.
|
|
3107
|
+
*
|
|
3108
|
+
* A rule that asks which children an element has (the required children, the elements that
|
|
3109
|
+
* satisfy a query) does not pass `attr`: an attribute that cannot be known does not decide whether
|
|
3110
|
+
* a child exists. It looks for the unresolved components among the children itself.
|
|
3111
|
+
*
|
|
3112
|
+
* Every `slot` is mutable by default, as a `slot` that is not in a declarative shadow root renders what
|
|
3113
|
+
* is given to it, and a rule that reads the children as they are written (`require-datetime`) cannot
|
|
3114
|
+
* know it. A rule that reads what a slot of a shadow root renders by `MLElement#getAssignedNodes` says
|
|
3115
|
+
* which slots are with `isMutableSlot`.
|
|
3116
|
+
*
|
|
3055
3117
|
*
|
|
3056
3118
|
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
3057
3119
|
* @param attr - When true, also considers children with mutable attributes, and components that no
|
|
3058
3120
|
* pretender resolved, as mutable
|
|
3121
|
+
* @param isMutableSlot - Whether a `slot` among the descendants may render what is not known
|
|
3059
3122
|
* @returns True if this element has potentially mutable children
|
|
3060
3123
|
*/
|
|
3061
|
-
hasMutableChildren(attr = false) {
|
|
3124
|
+
hasMutableChildren(attr = false, isMutableSlot = () => true) {
|
|
3062
3125
|
for (const child of this.getPureChildNodes()) {
|
|
3063
3126
|
if (child.is(child.MARKUPLINT_PREPROCESSOR_BLOCK)) {
|
|
3064
3127
|
if (child.blockBehavior) {
|
|
@@ -3070,13 +3133,13 @@ export class MLElement extends MLParentNode {
|
|
|
3070
3133
|
if (attr && child.hasMutableAttributes()) {
|
|
3071
3134
|
return true;
|
|
3072
3135
|
}
|
|
3073
|
-
if (attr && child.
|
|
3136
|
+
if (attr && child.isUnresolvedComponent()) {
|
|
3074
3137
|
return true;
|
|
3075
3138
|
}
|
|
3076
|
-
if (child.hasMutableChildren(attr)) {
|
|
3139
|
+
if (child.hasMutableChildren(attr, isMutableSlot)) {
|
|
3077
3140
|
return true;
|
|
3078
3141
|
}
|
|
3079
|
-
if (child.localName === 'slot') {
|
|
3142
|
+
if (child.localName === 'slot' && isMutableSlot(child)) {
|
|
3080
3143
|
return true;
|
|
3081
3144
|
}
|
|
3082
3145
|
}
|
|
@@ -3335,7 +3398,19 @@ export class MLElement extends MLParentNode {
|
|
|
3335
3398
|
if (this.elementType === 'html' && this.ownerMLDocument.isNativeTag(this.localName, this.namespaceURI)) {
|
|
3336
3399
|
return;
|
|
3337
3400
|
}
|
|
3338
|
-
const pretenderConfig = pretenders?.find(option =>
|
|
3401
|
+
const pretenderConfig = pretenders?.find(option => {
|
|
3402
|
+
try {
|
|
3403
|
+
return this.matches(option.selector);
|
|
3404
|
+
}
|
|
3405
|
+
catch (error) {
|
|
3406
|
+
// An entry that a scan or a file made names the file that declares its component, which is
|
|
3407
|
+
// where the user fixes it. `MLDocument` names the configuration for any other.
|
|
3408
|
+
if (!isFatalError(error) && error instanceof InvalidSelectorError && option.filePath) {
|
|
3409
|
+
throw new ConfigParserError(error.message, { filePath: option.filePath, raw: option.selector });
|
|
3410
|
+
}
|
|
3411
|
+
throw error;
|
|
3412
|
+
}
|
|
3413
|
+
});
|
|
3339
3414
|
const asAttrValue = this.getAttribute('as');
|
|
3340
3415
|
const pretenderElement = pretenderConfig?.as ??
|
|
3341
3416
|
(this.elementType === 'html' || !asAttrValue
|
|
@@ -256,8 +256,8 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
256
256
|
* A `template` element that is in no tree has none either.
|
|
257
257
|
*
|
|
258
258
|
* The selector engine and `closest()` read it, so that `section p` does not match a `p` in the
|
|
259
|
-
* content of a `template` in a `section`. {@link MLNode.parentElement}
|
|
260
|
-
* because the content model of a `template` element is validated against what is in it.
|
|
259
|
+
* content of a `template` in a `section`. {@link MLNode.parentElement} does not stop at that
|
|
260
|
+
* boundary because the content model of a `template` element is validated against what is in it.
|
|
261
261
|
*
|
|
262
262
|
* `getTreeParentElement` of `@markuplint/ml-spec`, which cannot use this getter, derives the same
|
|
263
263
|
* answer from `getRootNode()` for the algorithms of that package: a change of the boundary here
|
|
@@ -269,6 +269,10 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
269
269
|
/**
|
|
270
270
|
* The parent node that may be `Element`, `Document`, `DocumentFragment`, and `null`.
|
|
271
271
|
*
|
|
272
|
+
* A block of a template language is walked through (`MLBlock#isTransparent`): a node in a block
|
|
273
|
+
* has the node that contains the block as its parent. A node at the top of a fragment document
|
|
274
|
+
* has none.
|
|
275
|
+
*
|
|
272
276
|
* ## HTML:
|
|
273
277
|
*
|
|
274
278
|
* ```html
|
|
@@ -289,10 +293,10 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
289
293
|
*
|
|
290
294
|
* ```jsx
|
|
291
295
|
* <> // => null
|
|
292
|
-
* <div> // =>
|
|
296
|
+
* <div> // => <>
|
|
293
297
|
* {items.map(item => {
|
|
294
298
|
* return (
|
|
295
|
-
* <span /> // =>
|
|
299
|
+
* <span /> // => <div>
|
|
296
300
|
* )
|
|
297
301
|
* })}
|
|
298
302
|
* </div>
|
|
@@ -356,7 +360,7 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
356
360
|
*
|
|
357
361
|
* ```jsx
|
|
358
362
|
* <> // => #document
|
|
359
|
-
* <div> // =>
|
|
363
|
+
* <div> // => <>
|
|
360
364
|
* {items.map(item => {
|
|
361
365
|
* return (
|
|
362
366
|
* <span /> // => #ml-block
|
|
@@ -540,6 +544,9 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
|
|
|
540
544
|
*/
|
|
541
545
|
isEqualNode(otherNode: Node | null): boolean;
|
|
542
546
|
/**
|
|
547
|
+
* Whether the document of this node is a fragment of a document (a partial, a component) and
|
|
548
|
+
* not a whole one. The parser of the document decides it (`MLDocument#isFragment`).
|
|
549
|
+
*
|
|
543
550
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
544
551
|
*/
|
|
545
552
|
isInFragmentDocument(): boolean;
|
package/lib/ml-dom/node/node.js
CHANGED
|
@@ -333,8 +333,8 @@ export class MLNode extends MLToken {
|
|
|
333
333
|
* A `template` element that is in no tree has none either.
|
|
334
334
|
*
|
|
335
335
|
* The selector engine and `closest()` read it, so that `section p` does not match a `p` in the
|
|
336
|
-
* content of a `template` in a `section`. {@link MLNode.parentElement}
|
|
337
|
-
* because the content model of a `template` element is validated against what is in it.
|
|
336
|
+
* content of a `template` in a `section`. {@link MLNode.parentElement} does not stop at that
|
|
337
|
+
* boundary because the content model of a `template` element is validated against what is in it.
|
|
338
338
|
*
|
|
339
339
|
* `getTreeParentElement` of `@markuplint/ml-spec`, which cannot use this getter, derives the same
|
|
340
340
|
* answer from `getRootNode()` for the algorithms of that package: a change of the boundary here
|
|
@@ -355,6 +355,10 @@ export class MLNode extends MLToken {
|
|
|
355
355
|
/**
|
|
356
356
|
* The parent node that may be `Element`, `Document`, `DocumentFragment`, and `null`.
|
|
357
357
|
*
|
|
358
|
+
* A block of a template language is walked through (`MLBlock#isTransparent`): a node in a block
|
|
359
|
+
* has the node that contains the block as its parent. A node at the top of a fragment document
|
|
360
|
+
* has none.
|
|
361
|
+
*
|
|
358
362
|
* ## HTML:
|
|
359
363
|
*
|
|
360
364
|
* ```html
|
|
@@ -375,10 +379,10 @@ export class MLNode extends MLToken {
|
|
|
375
379
|
*
|
|
376
380
|
* ```jsx
|
|
377
381
|
* <> // => null
|
|
378
|
-
* <div> // =>
|
|
382
|
+
* <div> // => <>
|
|
379
383
|
* {items.map(item => {
|
|
380
384
|
* return (
|
|
381
|
-
* <span /> // =>
|
|
385
|
+
* <span /> // => <div>
|
|
382
386
|
* )
|
|
383
387
|
* })}
|
|
384
388
|
* </div>
|
|
@@ -511,7 +515,7 @@ export class MLNode extends MLToken {
|
|
|
511
515
|
*
|
|
512
516
|
* ```jsx
|
|
513
517
|
* <> // => #document
|
|
514
|
-
* <div> // =>
|
|
518
|
+
* <div> // => <>
|
|
515
519
|
* {items.map(item => {
|
|
516
520
|
* return (
|
|
517
521
|
* <span /> // => #ml-block
|
|
@@ -907,6 +911,9 @@ export class MLNode extends MLToken {
|
|
|
907
911
|
throw new UnexpectedCallError('Not supported "isEqualNode" method');
|
|
908
912
|
}
|
|
909
913
|
/**
|
|
914
|
+
* Whether the document of this node is a fragment of a document (a partial, a component) and
|
|
915
|
+
* not a whole one. The parser of the document decides it (`MLDocument#isFragment`).
|
|
916
|
+
*
|
|
910
917
|
* @implements `@markuplint/ml-core` API: `MLNode`
|
|
911
918
|
*/
|
|
912
919
|
isInFragmentDocument() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MLElement } from './element.js';
|
|
1
2
|
import type { TextNodeType } from './types.js';
|
|
2
3
|
import type { MLASTText } from '@markuplint/ml-ast';
|
|
3
4
|
import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
@@ -12,12 +13,20 @@ import { MLCharacterData } from './character-data.js';
|
|
|
12
13
|
*/
|
|
13
14
|
export declare class MLText<T extends RuleConfigValue, O extends PlainData = undefined> extends MLCharacterData<T, O, MLASTText> implements Text {
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* The slot of an open declarative shadow root that this text is assigned to, or `null` when no
|
|
17
|
+
* slot takes it, the root is closed (the DOM does not expose it), or the assignment is not known.
|
|
16
18
|
*
|
|
17
|
-
* @unsupported
|
|
18
19
|
* @implements DOM API: `Text`
|
|
20
|
+
* @see https://dom.spec.whatwg.org/#dom-slotable-assignedslot
|
|
21
|
+
*/
|
|
22
|
+
get assignedSlot(): HTMLSlotElement | null;
|
|
23
|
+
/**
|
|
24
|
+
* The slot that places this text in the flat tree. See {@link MLElement.getFlatTreeSlot}.
|
|
25
|
+
*
|
|
26
|
+
* @implements `@markuplint/ml-core` API: `MLText`
|
|
27
|
+
* @see https://dom.spec.whatwg.org/#find-a-slot
|
|
19
28
|
*/
|
|
20
|
-
|
|
29
|
+
getFlatTreeSlot(): MLElement<T, O> | null | undefined;
|
|
21
30
|
/**
|
|
22
31
|
* Returns `true` if this text node originated from an invalid (bogus) AST
|
|
23
32
|
* node, such as an orphaned end tag.
|
package/lib/ml-dom/node/text.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAssignedSlot, getFlatTreeSlot } from '../helper/slot-assignment.js';
|
|
1
2
|
import { MLCharacterData } from './character-data.js';
|
|
2
3
|
import { UnexpectedCallError } from '@markuplint/shared';
|
|
3
4
|
/**
|
|
@@ -16,13 +17,23 @@ const rawTextElements = new Set(['script', 'style']);
|
|
|
16
17
|
*/
|
|
17
18
|
export class MLText extends MLCharacterData {
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* The slot of an open declarative shadow root that this text is assigned to, or `null` when no
|
|
21
|
+
* slot takes it, the root is closed (the DOM does not expose it), or the assignment is not known.
|
|
20
22
|
*
|
|
21
|
-
* @unsupported
|
|
22
23
|
* @implements DOM API: `Text`
|
|
24
|
+
* @see https://dom.spec.whatwg.org/#dom-slotable-assignedslot
|
|
23
25
|
*/
|
|
24
26
|
get assignedSlot() {
|
|
25
|
-
|
|
27
|
+
return getAssignedSlot(this);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The slot that places this text in the flat tree. See {@link MLElement.getFlatTreeSlot}.
|
|
31
|
+
*
|
|
32
|
+
* @implements `@markuplint/ml-core` API: `MLText`
|
|
33
|
+
* @see https://dom.spec.whatwg.org/#find-a-slot
|
|
34
|
+
*/
|
|
35
|
+
getFlatTreeSlot() {
|
|
36
|
+
return getFlatTreeSlot(this);
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
39
|
* Returns `true` if this text node originated from an invalid (bogus) AST
|
package/lib/test/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export function createTestDocument(sourceCode, options) {
|
|
|
22
22
|
booleanish: usedParser.booleanish,
|
|
23
23
|
attrExpressionLanguage: usedParser.attrExpressionLanguage,
|
|
24
24
|
literalValues: usedParser.literalValues,
|
|
25
|
+
slotElement: usedParser.slotElement,
|
|
25
26
|
tagNameCaseSensitive: usedParser.tagNameCaseSensitive,
|
|
26
27
|
componentNameVariants: usedParser.componentNameVariants,
|
|
27
28
|
isComponentName: usedParser.isComponentName?.bind(usedParser),
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A source range: from its start, included, to its end, excluded (the end is
|
|
3
|
+
* where the next token starts). Without an end, it holds only its start.
|
|
4
|
+
*/
|
|
5
|
+
export interface LocatedToken {
|
|
6
|
+
readonly startLine: number;
|
|
7
|
+
readonly startCol: number;
|
|
8
|
+
readonly endLine?: number;
|
|
9
|
+
readonly endCol?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A node with its source range. The range of an element is its start tag; its
|
|
13
|
+
* end tag, which is not in a document's node list, is `closeTag`.
|
|
14
|
+
*/
|
|
15
|
+
export interface LocatedNode extends LocatedToken {
|
|
16
|
+
readonly closeTag?: LocatedToken | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Finds the node of `nodeList` at the source location `line`:`col` (both 1-based).
|
|
20
|
+
*
|
|
21
|
+
* The node is the one whose range holds the position, or whose end tag does: a
|
|
22
|
+
* document's node list has no attribute nodes and no end tags, so a position at
|
|
23
|
+
* an attribute, inside its value or in an end tag is the element that has it. Of
|
|
24
|
+
* several such nodes the last in the list is taken: the innermost. A block
|
|
25
|
+
* (`#ml-block`) holds the elements nested in it, and an omitted element
|
|
26
|
+
* (`<tbody>` with an empty source) starts where its first child does.
|
|
27
|
+
*
|
|
28
|
+
* The list is in document order, as the parser sorts its nodes by offset, so the
|
|
29
|
+
* search stops at the first node that starts after the position. It works on the
|
|
30
|
+
* structural `LocatedNode` so that it reads the node list of any version of the
|
|
31
|
+
* engine: the language server searches the document of the markuplint the user
|
|
32
|
+
* installed, and the suppressions search a node list cast to their own type.
|
|
33
|
+
*
|
|
34
|
+
* @param nodeList - The nodes of a document, in document order.
|
|
35
|
+
* @param line - The 1-based line number to search at.
|
|
36
|
+
* @param col - The 1-based column number to search at.
|
|
37
|
+
* @returns The node at the location, or `null` if there is none.
|
|
38
|
+
*/
|
|
39
|
+
export declare function searchNodeAtLocation<N extends LocatedNode>(nodeList: Iterable<N>, line: number, col: number): N | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the node of `nodeList` at the source location `line`:`col` (both 1-based).
|
|
3
|
+
*
|
|
4
|
+
* The node is the one whose range holds the position, or whose end tag does: a
|
|
5
|
+
* document's node list has no attribute nodes and no end tags, so a position at
|
|
6
|
+
* an attribute, inside its value or in an end tag is the element that has it. Of
|
|
7
|
+
* several such nodes the last in the list is taken: the innermost. A block
|
|
8
|
+
* (`#ml-block`) holds the elements nested in it, and an omitted element
|
|
9
|
+
* (`<tbody>` with an empty source) starts where its first child does.
|
|
10
|
+
*
|
|
11
|
+
* The list is in document order, as the parser sorts its nodes by offset, so the
|
|
12
|
+
* search stops at the first node that starts after the position. It works on the
|
|
13
|
+
* structural `LocatedNode` so that it reads the node list of any version of the
|
|
14
|
+
* engine: the language server searches the document of the markuplint the user
|
|
15
|
+
* installed, and the suppressions search a node list cast to their own type.
|
|
16
|
+
*
|
|
17
|
+
* @param nodeList - The nodes of a document, in document order.
|
|
18
|
+
* @param line - The 1-based line number to search at.
|
|
19
|
+
* @param col - The 1-based column number to search at.
|
|
20
|
+
* @returns The node at the location, or `null` if there is none.
|
|
21
|
+
*/
|
|
22
|
+
export function searchNodeAtLocation(nodeList, line, col) {
|
|
23
|
+
let found = null;
|
|
24
|
+
for (const node of nodeList) {
|
|
25
|
+
if (isBefore(line, col, node)) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (holdsLocation(node, line, col) || (node.closeTag != null && holdsLocation(node.closeTag, line, col))) {
|
|
29
|
+
found = node;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
/** Whether `line`:`col` comes before the start of `token`. */
|
|
35
|
+
function isBefore(line, col, token) {
|
|
36
|
+
return line < token.startLine || (line === token.startLine && col < token.startCol);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether `line`:`col` is the start of `token` or in its range. The start is
|
|
40
|
+
* compared first: the end of an `MLNode` is computed from its source on every read.
|
|
41
|
+
*/
|
|
42
|
+
function holdsLocation(token, line, col) {
|
|
43
|
+
if (isBefore(line, col, token)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (line === token.startLine && col === token.startCol) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (token.endLine === undefined || token.endCol === undefined) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return line < token.endLine || (line === token.endLine && col < token.endCol);
|
|
53
|
+
}
|