@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
@@ -0,0 +1,162 @@
1
+ import type { MLDocument } from './document.js';
2
+ import type { MLElement } from './element.js';
3
+ import type { MLASTNode } from '@markuplint/ml-ast';
4
+ import type { PlainData, RuleConfigValue } from '@markuplint/ml-config';
5
+ import { MLDocumentFragment } from './document-fragment.js';
6
+ /**
7
+ * A declarative shadow root: the content of a `template` element with a `shadowrootmode` attribute
8
+ * that the HTML parser attaches to the parent of the `template` element as a shadow root. As the
9
+ * parser sets the template contents of such a `template` element to the shadow root, a
10
+ * shadow root is the `content` of its `template` element, and it is a tree of its own.
11
+ *
12
+ * @template T - The rule configuration value type
13
+ * @template O - The rule options type
14
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
15
+ */
16
+ export declare class MLShadowRoot<T extends RuleConfigValue, O extends PlainData = undefined> extends MLDocumentFragment<T, O> implements ShadowRoot {
17
+ #private;
18
+ /**
19
+ * @param astNode - A copy of the AST node of the `template` element that has a uuid of its own
20
+ * @param document - The owning document
21
+ * @param template - The `template` element whose content it is
22
+ */
23
+ constructor(astNode: MLASTNode, document: MLDocument<T, O>, template: MLElement<T, O>);
24
+ /**
25
+ * The element that the shadow root is attached to.
26
+ *
27
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-host
28
+ */
29
+ get host(): MLElement<T, O>;
30
+ /**
31
+ * The `shadowrootmode` attribute of the `template` element.
32
+ *
33
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-mode
34
+ */
35
+ get mode(): 'open' | 'closed';
36
+ /**
37
+ * Whether the `template` element has the `shadowrootdelegatesfocus` attribute.
38
+ *
39
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-delegatesfocus
40
+ */
41
+ get delegatesFocus(): boolean;
42
+ /**
43
+ * Whether the `template` element has the `shadowrootclonable` attribute.
44
+ *
45
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-clonable
46
+ */
47
+ get clonable(): boolean;
48
+ /**
49
+ * Whether the `template` element has the `shadowrootserializable` attribute.
50
+ *
51
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-serializable
52
+ */
53
+ get serializable(): boolean;
54
+ /**
55
+ * `manual` when the `shadowrootslotassignment` attribute of the `template` element says so,
56
+ * `named` otherwise.
57
+ *
58
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment
59
+ */
60
+ get slotAssignment(): 'named' | 'manual';
61
+ /**
62
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
63
+ *
64
+ * @unsupported
65
+ * @implements DOM API: `ShadowRoot`
66
+ */
67
+ get innerHTML(): string;
68
+ set innerHTML(html: string);
69
+ /**
70
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
71
+ *
72
+ * @unsupported
73
+ * @implements DOM API: `ShadowRoot`
74
+ */
75
+ get onslotchange(): ((this: ShadowRoot, ev: Event) => any) | null;
76
+ set onslotchange(handler: ((this: ShadowRoot, ev: Event) => any) | null);
77
+ /**
78
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
79
+ *
80
+ * @unsupported
81
+ * @implements DOM API: `ShadowRoot`
82
+ */
83
+ getHTML(options?: GetHTMLOptions): string;
84
+ /**
85
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
86
+ *
87
+ * @unsupported
88
+ * @implements DOM API: `ShadowRoot`
89
+ */
90
+ setHTMLUnsafe(html: string): void;
91
+ /**
92
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
93
+ *
94
+ * @unsupported
95
+ * @implements DOM API: `DocumentOrShadowRoot`
96
+ */
97
+ get activeElement(): Element | null;
98
+ /**
99
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
100
+ *
101
+ * @unsupported
102
+ * @implements DOM API: `DocumentOrShadowRoot`
103
+ */
104
+ get adoptedStyleSheets(): CSSStyleSheet[];
105
+ set adoptedStyleSheets(sheets: CSSStyleSheet[]);
106
+ /**
107
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
108
+ *
109
+ * @unsupported
110
+ * @implements DOM API: `DocumentOrShadowRoot`
111
+ */
112
+ get customElementRegistry(): CustomElementRegistry | null;
113
+ /**
114
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
115
+ *
116
+ * @unsupported
117
+ * @implements DOM API: `DocumentOrShadowRoot`
118
+ */
119
+ get fullscreenElement(): Element | null;
120
+ /**
121
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
122
+ *
123
+ * @unsupported
124
+ * @implements DOM API: `DocumentOrShadowRoot`
125
+ */
126
+ get pictureInPictureElement(): Element | null;
127
+ /**
128
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
129
+ *
130
+ * @unsupported
131
+ * @implements DOM API: `DocumentOrShadowRoot`
132
+ */
133
+ get pointerLockElement(): Element | null;
134
+ /**
135
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
136
+ *
137
+ * @unsupported
138
+ * @implements DOM API: `DocumentOrShadowRoot`
139
+ */
140
+ get styleSheets(): StyleSheetList;
141
+ /**
142
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
143
+ *
144
+ * @unsupported
145
+ * @implements DOM API: `DocumentOrShadowRoot`
146
+ */
147
+ elementFromPoint(x: number, y: number): Element | null;
148
+ /**
149
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
150
+ *
151
+ * @unsupported
152
+ * @implements DOM API: `DocumentOrShadowRoot`
153
+ */
154
+ elementsFromPoint(x: number, y: number): Element[];
155
+ /**
156
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
157
+ *
158
+ * @unsupported
159
+ * @implements DOM API: `DocumentOrShadowRoot`
160
+ */
161
+ getAnimations(): Animation[];
162
+ }
@@ -0,0 +1,219 @@
1
+ import { getShadowHost } from '../helper/tree-scope.js';
2
+ import { MLDocumentFragment } from './document-fragment.js';
3
+ import { UnexpectedCallError } from '@markuplint/shared';
4
+ /**
5
+ * A declarative shadow root: the content of a `template` element with a `shadowrootmode` attribute
6
+ * that the HTML parser attaches to the parent of the `template` element as a shadow root. As the
7
+ * parser sets the template contents of such a `template` element to the shadow root, a
8
+ * shadow root is the `content` of its `template` element, and it is a tree of its own.
9
+ *
10
+ * @template T - The rule configuration value type
11
+ * @template O - The rule options type
12
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
13
+ */
14
+ export class MLShadowRoot extends MLDocumentFragment {
15
+ #template;
16
+ /**
17
+ * @param astNode - A copy of the AST node of the `template` element that has a uuid of its own
18
+ * @param document - The owning document
19
+ * @param template - The `template` element whose content it is
20
+ */
21
+ constructor(astNode,
22
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
23
+ document,
24
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
25
+ template) {
26
+ super(astNode, document);
27
+ this.#template = template;
28
+ }
29
+ /**
30
+ * The element that the shadow root is attached to.
31
+ *
32
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-host
33
+ */
34
+ get host() {
35
+ const host = getShadowHost(this.#template);
36
+ if (!host) {
37
+ throw new TypeError('A shadow root is attached to an element');
38
+ }
39
+ return host;
40
+ }
41
+ /**
42
+ * The `shadowrootmode` attribute of the `template` element.
43
+ *
44
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-mode
45
+ */
46
+ get mode() {
47
+ return this.#template.declarativeShadowRootMode ?? 'open';
48
+ }
49
+ /**
50
+ * Whether the `template` element has the `shadowrootdelegatesfocus` attribute.
51
+ *
52
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-delegatesfocus
53
+ */
54
+ get delegatesFocus() {
55
+ return this.#template.hasAttribute('shadowrootdelegatesfocus');
56
+ }
57
+ /**
58
+ * Whether the `template` element has the `shadowrootclonable` attribute.
59
+ *
60
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-clonable
61
+ */
62
+ get clonable() {
63
+ return this.#template.hasAttribute('shadowrootclonable');
64
+ }
65
+ /**
66
+ * Whether the `template` element has the `shadowrootserializable` attribute.
67
+ *
68
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-serializable
69
+ */
70
+ get serializable() {
71
+ return this.#template.hasAttribute('shadowrootserializable');
72
+ }
73
+ /**
74
+ * `manual` when the `shadowrootslotassignment` attribute of the `template` element says so,
75
+ * `named` otherwise.
76
+ *
77
+ * @see https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment
78
+ */
79
+ get slotAssignment() {
80
+ return this.#template.getAttribute('shadowrootslotassignment')?.toLowerCase() === 'manual' ? 'manual' : 'named';
81
+ }
82
+ /**
83
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
84
+ *
85
+ * @unsupported
86
+ * @implements DOM API: `ShadowRoot`
87
+ */
88
+ get innerHTML() {
89
+ throw new UnexpectedCallError('Not supported "innerHTML" property');
90
+ }
91
+ set innerHTML(html) {
92
+ throw new UnexpectedCallError('Not supported "innerHTML" property');
93
+ }
94
+ /**
95
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
96
+ *
97
+ * @unsupported
98
+ * @implements DOM API: `ShadowRoot`
99
+ */
100
+ get onslotchange() {
101
+ throw new UnexpectedCallError('Not supported "onslotchange" property');
102
+ }
103
+ set onslotchange(handler) {
104
+ throw new UnexpectedCallError('Not supported "onslotchange" property');
105
+ }
106
+ /**
107
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
108
+ *
109
+ * @unsupported
110
+ * @implements DOM API: `ShadowRoot`
111
+ */
112
+ getHTML(
113
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
114
+ options) {
115
+ throw new UnexpectedCallError('Not supported "getHTML" method');
116
+ }
117
+ /**
118
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
119
+ *
120
+ * @unsupported
121
+ * @implements DOM API: `ShadowRoot`
122
+ */
123
+ setHTMLUnsafe(html) {
124
+ throw new UnexpectedCallError('Not supported "setHTMLUnsafe" method');
125
+ }
126
+ /**
127
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
128
+ *
129
+ * @unsupported
130
+ * @implements DOM API: `DocumentOrShadowRoot`
131
+ */
132
+ get activeElement() {
133
+ throw new UnexpectedCallError('Not supported "activeElement" property');
134
+ }
135
+ /**
136
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
137
+ *
138
+ * @unsupported
139
+ * @implements DOM API: `DocumentOrShadowRoot`
140
+ */
141
+ get adoptedStyleSheets() {
142
+ throw new UnexpectedCallError('Not supported "adoptedStyleSheets" property');
143
+ }
144
+ set adoptedStyleSheets(sheets) {
145
+ throw new UnexpectedCallError('Not supported "adoptedStyleSheets" property');
146
+ }
147
+ /**
148
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
149
+ *
150
+ * @unsupported
151
+ * @implements DOM API: `DocumentOrShadowRoot`
152
+ */
153
+ get customElementRegistry() {
154
+ throw new UnexpectedCallError('Not supported "customElementRegistry" property');
155
+ }
156
+ /**
157
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
158
+ *
159
+ * @unsupported
160
+ * @implements DOM API: `DocumentOrShadowRoot`
161
+ */
162
+ get fullscreenElement() {
163
+ throw new UnexpectedCallError('Not supported "fullscreenElement" property');
164
+ }
165
+ /**
166
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
167
+ *
168
+ * @unsupported
169
+ * @implements DOM API: `DocumentOrShadowRoot`
170
+ */
171
+ get pictureInPictureElement() {
172
+ throw new UnexpectedCallError('Not supported "pictureInPictureElement" property');
173
+ }
174
+ /**
175
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
176
+ *
177
+ * @unsupported
178
+ * @implements DOM API: `DocumentOrShadowRoot`
179
+ */
180
+ get pointerLockElement() {
181
+ throw new UnexpectedCallError('Not supported "pointerLockElement" property');
182
+ }
183
+ /**
184
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
185
+ *
186
+ * @unsupported
187
+ * @implements DOM API: `DocumentOrShadowRoot`
188
+ */
189
+ get styleSheets() {
190
+ throw new UnexpectedCallError('Not supported "styleSheets" property');
191
+ }
192
+ /**
193
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
194
+ *
195
+ * @unsupported
196
+ * @implements DOM API: `DocumentOrShadowRoot`
197
+ */
198
+ elementFromPoint(x, y) {
199
+ throw new UnexpectedCallError('Not supported "elementFromPoint" method');
200
+ }
201
+ /**
202
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
203
+ *
204
+ * @unsupported
205
+ * @implements DOM API: `DocumentOrShadowRoot`
206
+ */
207
+ elementsFromPoint(x, y) {
208
+ throw new UnexpectedCallError('Not supported "elementsFromPoint" method');
209
+ }
210
+ /**
211
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
212
+ *
213
+ * @unsupported
214
+ * @implements DOM API: `DocumentOrShadowRoot`
215
+ */
216
+ getAnimations() {
217
+ throw new UnexpectedCallError('Not supported "getAnimations" method');
218
+ }
219
+ }
@@ -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
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
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
- get assignedSlot(): HTMLSlotElement;
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.
@@ -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
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
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
- throw new UnexpectedCallError('Not supported "assignedSlot" property');
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
@@ -172,7 +172,10 @@ export class MLRule {
172
172
  const aliasName = this.baseRuleId ? this.name : undefined;
173
173
  const violations = context.reports.map(report => {
174
174
  let fixData;
175
- if (fix && report.fix) {
175
+ // An attribute that a pretender made is not in the source: the position of its tokens is that of
176
+ // the element, so a fix that edits the range of the attribute would rewrite the tag
177
+ const isPretendedScope = 'scope' in report && 'isPretended' in report.scope && report.scope.isPretended;
178
+ if (fix && report.fix && !isPretendedScope) {
176
179
  const edits = report.fix(sharedFixer);
177
180
  const editArray = Array.isArray(edits) ? edits : [edits];
178
181
  if (editArray.length > 0) {
package/lib/test/index.js CHANGED
@@ -20,6 +20,9 @@ export function createTestDocument(sourceCode, options) {
20
20
  // The settings of the language are what the parser says, as `MLCore` hands them to the document
21
21
  endTag: usedParser.endTag,
22
22
  booleanish: usedParser.booleanish,
23
+ attrExpressionLanguage: usedParser.attrExpressionLanguage,
24
+ literalValues: usedParser.literalValues,
25
+ slotElement: usedParser.slotElement,
23
26
  tagNameCaseSensitive: usedParser.tagNameCaseSensitive,
24
27
  componentNameVariants: usedParser.componentNameVariants,
25
28
  isComponentName: usedParser.isComponentName?.bind(usedParser),
@@ -1 +1,3 @@
1
1
  export { getLocationFromChars } from './get-location-from-chars.js';
2
+ export type { LocatedNode, LocatedToken } from './search-node-at-location.js';
3
+ export { searchNodeAtLocation } from './search-node-at-location.js';
@@ -1 +1,2 @@
1
1
  export { getLocationFromChars } from './get-location-from-chars.js';
2
+ export { searchNodeAtLocation } from './search-node-at-location.js';
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/ml-core",
3
- "version": "5.4.0",
3
+ "version": "5.6.0",
4
4
  "description": "The core module of markuplint",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,9 +22,6 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
- "typedoc": {
26
- "entryPoint": "./src/index.ts"
27
- },
28
25
  "scripts": {
29
26
  "build": "tsc --project tsconfig.build.json",
30
27
  "dev": "tsc --watch --project tsconfig.build.json",
@@ -34,20 +31,20 @@
34
31
  "./lib/configs.js": "./lib/configs.browser.js"
35
32
  },
36
33
  "dependencies": {
37
- "@markuplint/config-presets": "5.4.0",
38
- "@markuplint/html-parser": "5.4.0",
39
- "@markuplint/html-spec": "5.4.0",
40
- "@markuplint/i18n": "5.3.0",
41
- "@markuplint/ml-ast": "5.3.0",
42
- "@markuplint/ml-config": "5.4.0",
43
- "@markuplint/ml-spec": "5.4.0",
44
- "@markuplint/parser-utils": "5.4.0",
45
- "@markuplint/selector": "5.4.0",
46
- "@markuplint/shared": "5.4.0",
34
+ "@markuplint/config-presets": "5.6.0",
35
+ "@markuplint/html-parser": "5.6.0",
36
+ "@markuplint/html-spec": "5.6.0",
37
+ "@markuplint/i18n": "5.6.0",
38
+ "@markuplint/ml-ast": "5.6.0",
39
+ "@markuplint/ml-config": "5.6.0",
40
+ "@markuplint/ml-spec": "5.6.0",
41
+ "@markuplint/parser-utils": "5.6.0",
42
+ "@markuplint/selector": "5.6.0",
43
+ "@markuplint/shared": "5.6.0",
47
44
  "@types/debug": "^4.1.13",
48
45
  "debug": "^4.4.3",
49
46
  "is-plain-object": "^5.1.0",
50
47
  "type-fest": "^5.10.0"
51
48
  },
52
- "gitHead": "647f6fb5d5743f060678599c97e9c84b58970747"
49
+ "gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
53
50
  }