@markuplint/ml-core 5.2.0 → 5.3.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.
@@ -3,7 +3,7 @@ var _a;
3
3
  import { getSpecByTagName, resolveNamespace } from '@markuplint/ml-spec';
4
4
  import { pretenderNamespaceToURI } from '@markuplint/ml-config';
5
5
  import { getChildNamespace } from '@markuplint/parser-utils';
6
- import { matchSelector } from '@markuplint/selector';
6
+ import { matchSelector, typeNameMatches } from '@markuplint/selector';
7
7
  import { getAccname } from '../helper/accname.js';
8
8
  import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
9
9
  import { MLAttr } from './attr.js';
@@ -12,7 +12,7 @@ import { MLElementCloseTag } from './element-close-tag.js';
12
12
  import { toNamedNodeMap } from './named-node-map.js';
13
13
  import { toHTMLCollection, toNodeList } from './node-list.js';
14
14
  import { MLParentNode } from './parent-node.js';
15
- import { UnexpectedCallError } from '@markuplint/shared';
15
+ import { UnexpectedCallError, asciiLowerCase, vuePropKey } from '@markuplint/shared';
16
16
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
17
17
  /**
18
18
  * Represents a DOM Element node wrapper in the markuplint DOM tree.
@@ -49,6 +49,140 @@ export class MLElement extends MLParentNode {
49
49
  get isForeignElement() {
50
50
  return this.#namespaceURI !== HTML_NAMESPACE;
51
51
  }
52
+ /**
53
+ * Whether two names would be one attribute on this element, for names that no attribute of the
54
+ * element stands behind (the names of a spec, of an option or of a rule value, where the
55
+ * attribute may be missing). It is {@link MLAttr.isNamedBy} without an attribute.
56
+ *
57
+ * The HTML syntax folds the ASCII case. A name that code sets through the DOM API is folded
58
+ * only on an element of the HTML namespace: on an SVG or MathML element it keeps its spelling,
59
+ * and a name with a capital letter is a prop of the framework. The props of a component are
60
+ * not folded, except that Vue camelizes them.
61
+ *
62
+ * With no attribute, whether the name is set through the DOM API is up to the parser alone: the
63
+ * attribute is not dynamic, so Svelte's name is read as the HTML parser reads it.
64
+ *
65
+ * @see https://dom.spec.whatwg.org/#dom-element-setattribute
66
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state
67
+ *
68
+ * @param a - A name
69
+ * @param b - The other name
70
+ * @returns `true` if both names are one attribute on this element
71
+ */
72
+ isSameAttrName(a, b) {
73
+ if (a === b) {
74
+ return true;
75
+ }
76
+ if (this.elementType === 'authored') {
77
+ return this.ownerMLDocument.componentNameVariants && vuePropKey(a) === vuePropKey(b);
78
+ }
79
+ if (this.ownerMLDocument.attrNameRoute === 'dom-api' && (this.isForeignElement || /[A-Z]/.test(a + b))) {
80
+ return false;
81
+ }
82
+ return asciiLowerCase(a) === asciiLowerCase(b);
83
+ }
84
+ /**
85
+ * Whether the `#custom` entry of a content model category (`:model(flow)` etc.) covers this element.
86
+ *
87
+ * Anything that is not a native HTML element counts, an authored component (`<Foo />`)
88
+ * as well as an autonomous custom element (`<x-foo>`). Keep it in step with `isTreatedAsCustom`
89
+ * of `permitted-contents` (`'pretended'` mode), which applies the same criterion to `#custom`;
90
+ * they are separate on purpose, as `isTreatedAsCustom` also serves the `'origin'` mode.
91
+ *
92
+ * Like {@link localName}, it follows the pretender, since a selector sees the element
93
+ * the component renders. A pretender target is created with `elementType: 'html'`, so it
94
+ * is a custom element when its own name has a hyphen. A target whose name is a component
95
+ * (`Tabs.Root`; the rule is the parser's, see {@link MLDocument.isComponentName}) is created as
96
+ * `'authored'` and counts like one written directly: the scanners of `pretenders.auto` end a chain at such a name when
97
+ * they cannot resolve it, and what that component renders is not known.
98
+ *
99
+ * Known limitation: the hyphen test is only the hyphen. A pretender target named after a reserved
100
+ * hyphenated name (`annotation-xml`, `font-face`, ...) counts as custom here, whereas the same
101
+ * element written directly does not. `isTreatedAsCustom` shares the limitation.
102
+ */
103
+ get isCustomElement() {
104
+ if (this.pretenderContext?.type === 'pretender') {
105
+ const { as } = this.pretenderContext;
106
+ return as.elementType === 'authored' || as.localName.includes('-');
107
+ }
108
+ return this.elementType !== 'html';
109
+ }
110
+ /**
111
+ * Whether the document tells the case of a tag name apart (see {@link MLDocument.tagNameCaseSensitive}).
112
+ * A type selector reads it, as {@link localName} alone cannot tell it: an element that HTML names
113
+ * case-insensitively (`<foobar>`) has a lowercase-folded pair of `localName` / `nodeName` in
114
+ * every document, but in JSX it is not `<FooBar>`.
115
+ *
116
+ * @implements `SelectorElement#tagNameCaseSensitive` of `@markuplint/selector`
117
+ */
118
+ get tagNameCaseSensitive() {
119
+ return this.ownerMLDocument.tagNameCaseSensitive;
120
+ }
121
+ /**
122
+ * What this element is for a type selector, in a document that resolves a component by any
123
+ * spelling of its name (Vue, see {@link MLDocument.componentNameVariants}); `undefined` in any other.
124
+ *
125
+ * - `component`: not an HTML element (`<FooBar>`, `<foo-bar>`), which {@link localName} names as written
126
+ * - `native`: an HTML element that the document takes for native (`<button>`, see {@link MLDocument.isNativeTag}:
127
+ * the parser's rule, as Vue's `isNativeTag`, else the specification), the guard of {@link pretending}.
128
+ * The element that a pretender stands for is judged by the specification alone
129
+ * - `unknown-html`: an HTML element that it does not (`<foobar>`; in Vue also `<marquee>`, which has a
130
+ * specification but resolves to a component when one is imported)
131
+ *
132
+ * Like {@link localName}, it follows the pretender, since a selector sees the element the component
133
+ * renders. An element of the SVG or MathML namespace has none: it keeps the case of its name.
134
+ *
135
+ * @implements `SelectorElement#tagNameKind` of `@markuplint/selector`
136
+ */
137
+ get tagNameKind() {
138
+ const document = this.ownerMLDocument;
139
+ if (!document.componentNameVariants) {
140
+ return;
141
+ }
142
+ const target = this.pretenderContext?.type === 'pretender' ? this.pretenderContext.as : this;
143
+ if (target.isForeignElement) {
144
+ return;
145
+ }
146
+ if (target.elementType !== 'html') {
147
+ return 'component';
148
+ }
149
+ // A tag as written is the language's to resolve; the name a pretender gives (`as`) is the element itself
150
+ const isNative = target === this
151
+ ? document.isNativeTag(target.localName, target.namespaceURI)
152
+ : getSpecByTagName(document.specs.specs, target.localName, target.namespaceURI) != null;
153
+ return isNative ? 'native' : 'unknown-html';
154
+ }
155
+ /**
156
+ * Whether a name that a user writes for an element (the `tag` of a rule option, as against the
157
+ * type name of a selector, which {@link matches} takes) stands for this element, written as it is,
158
+ * in a document that resolves a component by any spelling of its name (Vue, see
159
+ * {@link MLDocument.componentNameVariants}): `FooBar` stands for `<foo-bar>` and `<fooBar>`.
160
+ *
161
+ * It is `false` in any other document, so that a caller that compares a name as it is can ask
162
+ * it as well without changing what it does there. It ignores the pretender: the name is the one
163
+ * the element is written with. To ask so, it hides the pretender of this element for the length of the
164
+ * call (as {@link matchMLSelector} does for its second try), so nothing in it may be async.
165
+ *
166
+ * @param name - The name as the user wrote it
167
+ * @returns Whether the name stands for this element
168
+ */
169
+ matchesTagNameVariant(name) {
170
+ if (!this.ownerMLDocument.componentNameVariants) {
171
+ return false;
172
+ }
173
+ const pretenderContext = this.pretenderContext;
174
+ if (pretenderContext?.type !== 'pretender') {
175
+ return this.tagNameKind !== undefined && typeNameMatches(name, this);
176
+ }
177
+ // The element as written, not as the pretender makes it: as `matchMLSelector` does for its second try
178
+ this.pretenderContext = null;
179
+ try {
180
+ return this.tagNameKind !== undefined && typeNameMatches(name, this);
181
+ }
182
+ finally {
183
+ this.pretenderContext = pretenderContext;
184
+ }
185
+ }
52
186
  /**
53
187
  * Whether this element was implicitly created (e.g., an omitted `<body>` tag in HTML).
54
188
  */
@@ -700,28 +834,21 @@ export class MLElement extends MLParentNode {
700
834
  throw new UnexpectedCallError('Not supported "attributeStyleMap" property');
701
835
  }
702
836
  /**
837
+ * Every attribute written on the tag, the second of two spellings of one name too: unlike a DOM
838
+ * element, which keeps one of them, a rule checks each attribute that the author wrote, and
839
+ * `no-duplicate-attr` reports the second. Which of them the DOM keeps is what {@link getAttribute}
840
+ * returns.
841
+ *
703
842
  * @implements DOM API: `Element`
704
843
  * @see https://dom.spec.whatwg.org/#dom-element-attributes
705
844
  */
706
845
  get attributes() {
707
846
  const origin = this.pretenderContext?.type === 'pretender' ? this.pretenderContext.as.#attributes : this.#attributes;
708
- if (this.#normalizedAttrs.has(origin)) {
709
- return this.#normalizedAttrs.get(origin);
710
- }
711
- const names = new Set();
712
- const attrs = [];
713
- for (const attr of origin) {
714
- if (names.has(attr.name)) {
715
- /**
716
- * Skips a duplicated attribute
717
- *
718
- *@see https://html.spec.whatwg.org/#parse-error-duplicate-attribute
719
- */
720
- continue;
721
- }
722
- attrs.push(attr);
847
+ const cached = this.#normalizedAttrs.get(origin);
848
+ if (cached) {
849
+ return cached;
723
850
  }
724
- const map = toNamedNodeMap(attrs);
851
+ const map = toNamedNodeMap(origin);
725
852
  this.#normalizedAttrs.set(origin, map);
726
853
  return map;
727
854
  }
@@ -2475,16 +2602,31 @@ export class MLElement extends MLParentNode {
2475
2602
  throw new UnexpectedCallError('Not supported "getAnimations" method');
2476
2603
  }
2477
2604
  /**
2605
+ * Whether `attrName` is the name of an attribute of this element is what {@link MLAttr.isNamedBy}
2606
+ * says: `ROLE` is `role` on a `<div>` and, in JSX, is not on an `<svg>`. `hasAttribute`,
2607
+ * `getAttributeToken`, `getAttributePretended`, a pretender's `fromAttr` and `getNamedItem` ask the same.
2608
+ *
2478
2609
  * @implements DOM API: `Element`
2479
2610
  * @see https://dom.spec.whatwg.org/#ref-for-dom-element-getattribute%E2%91%A0
2480
2611
  */
2481
2612
  getAttribute(attrName) {
2613
+ let first = null;
2614
+ let lastSetByCode = null;
2482
2615
  for (const attr of this.attributes) {
2483
- if (attr.name.toLowerCase() === attrName.toLowerCase()) {
2484
- return attr.value;
2616
+ if (!attr.isNamedBy(attrName)) {
2617
+ continue;
2618
+ }
2619
+ first ??= attr;
2620
+ // A directive, and an attribute that may be written twice (`class` and `:class`), are not
2621
+ // two spellings of one name: the first is the one that is asked for
2622
+ if (attr.isSetThroughDOMAPI && !attr.isDirective && !attr.isDuplicatable) {
2623
+ lastSetByCode = attr;
2485
2624
  }
2486
2625
  }
2487
- return null;
2626
+ // Of two attributes of one name, the HTML parser keeps the first and drops the second, while code
2627
+ // that sets them one after another (`setAttribute`) lets the second overwrite the first.
2628
+ // @see https://html.spec.whatwg.org/#parse-error-duplicate-attribute
2629
+ return (lastSetByCode ?? first)?.value ?? null;
2488
2630
  }
2489
2631
  /**
2490
2632
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -2529,12 +2671,12 @@ export class MLElement extends MLParentNode {
2529
2671
  * the original component element, not on the virtual pretender element.
2530
2672
  *
2531
2673
  * @implements `@markuplint/ml-core` API: `MLElement`
2532
- * @param attrName - The attribute name to look up (case-insensitive)
2674
+ * @param attrName - The attribute name to look up (see {@link MLAttr.isNamedBy} for how its case is compared)
2533
2675
  * @returns The attribute value, or null if the attribute is not found
2534
2676
  */
2535
2677
  getAttributePretended(attrName) {
2536
2678
  for (const attr of this.#attributes) {
2537
- if (attr.name.toLowerCase() === attrName.toLowerCase()) {
2679
+ if (attr.isNamedBy(attrName)) {
2538
2680
  return attr.value;
2539
2681
  }
2540
2682
  }
@@ -2545,14 +2687,13 @@ export class MLElement extends MLParentNode {
2545
2687
  * Unlike `getAttribute`, this returns the full `MLAttr` token objects.
2546
2688
  *
2547
2689
  * @implements `@markuplint/ml-core` API: `MLElement`
2548
- * @param attrName - The attribute name to look up (case-insensitive)
2690
+ * @param attrName - The attribute name to look up (see {@link MLAttr.isNamedBy} for how its case is compared)
2549
2691
  * @returns An array of matching attribute tokens
2550
2692
  */
2551
2693
  getAttributeToken(attrName) {
2552
2694
  const attrs = [];
2553
- attrName = attrName.toLowerCase();
2554
2695
  for (const attr of this.getAttributeTokens()) {
2555
- if (attr.name === attrName) {
2696
+ if (attr.isNamedBy(attrName)) {
2556
2697
  attrs.push(attr);
2557
2698
  }
2558
2699
  }
@@ -2960,7 +3101,8 @@ export class MLElement extends MLParentNode {
2960
3101
  }
2961
3102
  /**
2962
3103
  * The namespace of an element named `name` rendered where this element is, for the
2963
- * element this element pretends to be.
3104
+ * element this element pretends to be. `null` for what is not an element, a
3105
+ * component.
2964
3106
  *
2965
3107
  * Where this element is has the namespace of this element, so the element gets the
2966
3108
  * namespace an element written there gets. For a component, it is taken from the
@@ -2990,13 +3132,16 @@ export class MLElement extends MLParentNode {
2990
3132
  // mask spec-driven rules — deprecation, ARIA role restrictions, browser support —
2991
3133
  // keyed on the original tag. See issue #3740.
2992
3134
  //
3135
+ // What is recognised is what the document says (`MLDocument#isNativeTag`): the specification, unless
3136
+ // the parser has a rule of its own. Vue's rule does not hold `<marquee>`, which Vue resolves to a
3137
+ // component when one is imported, so there it is pretender-eligible. See issue #4351.
3138
+ //
2993
3139
  // Names that the HTML parser cannot distinguish from typos (PascalCase JSX-like
2994
3140
  // usage in plain HTML such as `<SimpleButton>`) get `elementType === 'html'`
2995
3141
  // from the parser but have no spec entry; those remain pretender-eligible
2996
3142
  // (this is what `pretenders.scan` relies on). The legacy "no inline `as=` on
2997
3143
  // HTML elements" guard is preserved further down for that case.
2998
- if (this.elementType === 'html' &&
2999
- getSpecByTagName(this.ownerMLDocument.specs.specs, this.localName, this.namespaceURI) != null) {
3144
+ if (this.elementType === 'html' && this.ownerMLDocument.isNativeTag(this.localName, this.namespaceURI)) {
3000
3145
  return;
3001
3146
  }
3002
3147
  const pretenderConfig = pretenders?.find(option => this.matches(option.selector));
@@ -3028,16 +3173,19 @@ export class MLElement extends MLParentNode {
3028
3173
  aria = pretenderElement.aria;
3029
3174
  }
3030
3175
  const slots = typeof pretenderElement === 'string' ? undefined : pretenderElement.slots;
3176
+ // A component is given no name for the namespace, as `#resolveNamespace` does:
3177
+ // `getChildNamespace` compares names in lowercase, so `Svg` would enter SVG.
3178
+ const isComponent = this.ownerMLDocument.isComponentName(nodeName);
3031
3179
  const namespace = typeof pretenderElement !== 'string' && pretenderElement.namespace
3032
3180
  ? pretenderNamespaceToURI(pretenderElement.namespace)
3033
- : this.#getNamespaceInPlace(nodeName, renderedParent);
3181
+ : this.#getNamespaceInPlace(isComponent ? null : nodeName, renderedParent);
3034
3182
  const as = new _a({
3035
3183
  ...this._astToken,
3036
3184
  uuid: this.uuid + '_pretender',
3037
3185
  raw: `<${nodeName}>`,
3038
3186
  nodeName,
3039
3187
  namespace,
3040
- elementType: 'html',
3188
+ elementType: isComponent ? 'authored' : 'html',
3041
3189
  attributes,
3042
3190
  }, this.ownerMLDocument);
3043
3191
  // When slots is null, the component does not accept children (void-like).
@@ -3081,8 +3229,13 @@ export class MLElement extends MLParentNode {
3081
3229
  _value = value;
3082
3230
  }
3083
3231
  else if (value != null && 'fromAttr' in value) {
3084
- const fromAttr = value.fromAttr.toLowerCase();
3085
- const origin = this.#attributes.find(attr => attr.name.toLowerCase() === fromAttr);
3232
+ // The attribute of the usage site that the prop is. Vue gives a prop the attribute by the camelCase
3233
+ // of its name (`my-label` and `myLabel` are the prop `myLabel`, and `mylabel` is another),
3234
+ // and the prop declared as `'my-label'` is the same one, so both sides are read so. Elsewhere
3235
+ // the attribute is read as the DOM or the framework reads it (`isNamedBy`).
3236
+ const { fromAttr } = value;
3237
+ const isVue = this.ownerMLDocument.componentNameVariants;
3238
+ const origin = this.#attributes.find(attr => isVue ? vuePropKey(attr.name) === vuePropKey(fromAttr) : attr.isNamedBy(fromAttr));
3086
3239
  if (!origin && value.omitIfMissing) {
3087
3240
  return [];
3088
3241
  }
@@ -3157,6 +3310,14 @@ export class MLElement extends MLParentNode {
3157
3310
  if (spec.attrs) {
3158
3311
  attributes.push(...this.#createPretenderAttrs(spec.attrs, uuid));
3159
3312
  }
3313
+ const isComponent = this.ownerMLDocument.isComponentName(spec.element);
3314
+ let elementType = 'html';
3315
+ if (isComponent) {
3316
+ elementType = 'authored';
3317
+ }
3318
+ else if (spec.element.includes('-')) {
3319
+ elementType = 'web-component';
3320
+ }
3160
3321
  const virtual = new _a({
3161
3322
  ...this._astToken,
3162
3323
  uuid,
@@ -3164,8 +3325,8 @@ export class MLElement extends MLParentNode {
3164
3325
  nodeName: spec.element,
3165
3326
  namespace: spec.namespace
3166
3327
  ? pretenderNamespaceToURI(spec.namespace)
3167
- : renderedIn.#getChildNamespace(spec.element),
3168
- elementType: spec.element.includes('-') ? 'web-component' : 'html',
3328
+ : renderedIn.#getChildNamespace(isComponent ? null : spec.element),
3329
+ elementType,
3169
3330
  attributes,
3170
3331
  childNodes: [],
3171
3332
  pairNodeUuid: null,
@@ -1,7 +1,7 @@
1
1
  import { UnexpectedCallError } from '@markuplint/shared';
2
2
  export class MLNamedNodeMap extends Array {
3
3
  getNamedItem(qualifiedName) {
4
- return this.find(attr => attr.name === qualifiedName) ?? null;
4
+ return this.find(attr => attr.isNamedBy(qualifiedName)) ?? null;
5
5
  }
6
6
  /**
7
7
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -46,11 +46,13 @@ export class NodeStore {
46
46
  setNode(astNode,
47
47
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
48
48
  node) {
49
- if (!astNode.uuid) {
49
+ if (!astNode.uuid && nodeStoreError.enabled) {
50
50
  nodeStoreError('UUID is invalid: %s (%s: "%s")', astNode.uuid, astNode.nodeName, astNode.raw);
51
51
  nodeStoreError('Invalid node: %O', node);
52
52
  }
53
- nodeStoreLog('Mapped: %s (%s: "%s")', astNode.uuid, astNode.nodeName, astNode.raw.replaceAll('\n', '⏎').replaceAll('\t', '→'));
53
+ if (nodeStoreLog.enabled) {
54
+ nodeStoreLog('Mapped: %s (%s: "%s")', astNode.uuid, astNode.nodeName, astNode.raw.replaceAll('\n', '⏎').replaceAll('\t', '→'));
55
+ }
54
56
  this.#store.set(astNode.uuid, node);
55
57
  }
56
58
  }
@@ -20,38 +20,53 @@ export class RuleMapper {
20
20
  this.#nodeList = Object.freeze([document, ...document.nodeList]);
21
21
  }
22
22
  apply() {
23
- ruleMapperLog('ruleTree:');
23
+ if (ruleMapperLog.enabled) {
24
+ ruleMapperLog('ruleTree:');
25
+ }
26
+ const nodeEnabled = ruleMapperNodeLog.enabled;
27
+ const nodeRuleEnabled = ruleMapperNodeRuleLog.enabled;
24
28
  for (const node of this.#nodeList) {
25
29
  const rules = this.#ruleMap.get(node.uuid);
26
30
  if (!rules) {
27
31
  continue;
28
32
  }
29
- ruleMapperNodeLog('<%s>', node.nodeName);
33
+ if (nodeEnabled) {
34
+ ruleMapperNodeLog('<%s>', node.nodeName);
35
+ }
30
36
  for (const ruleName of Object.keys(rules)) {
31
37
  const rule = rules[ruleName];
32
38
  if (!rule) {
33
39
  continue;
34
40
  }
35
41
  node.rules[ruleName] = rule.rule;
36
- ruleMapperNodeRuleLog('[from: %s(%s)] %s: %o', rule.from, rule.specificity, ruleName, rule.rule);
42
+ if (nodeRuleEnabled) {
43
+ ruleMapperNodeRuleLog('[from: %s(%s)] %s: %o', rule.from, rule.specificity, ruleName, rule.rule);
44
+ }
37
45
  }
38
46
  }
39
47
  }
40
48
  set(
41
49
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
42
50
  node, ruleName, rule) {
51
+ const enabled = ruleMapperLog.enabled;
43
52
  const rules = this.#ruleMap.get(node.uuid) ?? {};
44
53
  const currentRule = rules[ruleName];
45
54
  if (currentRule) {
46
55
  const order = compareSpecificity(currentRule.specificity, rule.specificity);
47
56
  if (order === 1) {
48
- ruleMapperLog("Don't set %o ([%s] vs [%s])", rule, currentRule.specificity, rule.specificity);
57
+ if (enabled) {
58
+ ruleMapperLog("Don't set %o ([%s] vs [%s])", rule, currentRule.specificity, rule.specificity);
59
+ }
49
60
  return;
50
61
  }
51
- ruleMapperLog('Unset %o from %s', currentRule, node);
62
+ if (enabled) {
63
+ ruleMapperLog('Unset %o from %s', currentRule, node);
64
+ }
52
65
  }
53
66
  rules[ruleName] = rule;
54
67
  this.#ruleMap.set(node.uuid, rules);
55
- ruleMapperLog('Set to %s from %s (%o): %O', node.nodeName, rule.from, rule.specificity, rule.rule);
68
+ if (enabled) {
69
+ ruleMapperLog('Set to %s from %s (%o): %O', node.nodeName, rule.from, rule.specificity, rule.rule);
70
+ }
56
71
  }
57
72
  }
package/lib/test/index.js CHANGED
@@ -13,13 +13,22 @@ import { MLDocument } from '../ml-dom/node/document.js';
13
13
  * @returns A parsed MLDocument instance
14
14
  */
15
15
  export function createTestDocument(sourceCode, options) {
16
- const ast = options?.parser
17
- ? 'parser' in options.parser
18
- ? options.parser.parser.parse(sourceCode, options.config?.parserOptions)
19
- : options.parser.parse(sourceCode, options.config?.parserOptions)
20
- : parser.parse(sourceCode, options?.config?.parserOptions);
16
+ const usedParser = options?.parser ? ('parser' in options.parser ? options.parser.parser : options.parser) : parser;
17
+ const ast = usedParser.parse(sourceCode, options?.config?.parserOptions);
21
18
  const ruleset = convertRuleset(options?.config);
22
- const document = new MLDocument(ast, ruleset, [options?.specs ?? spec, {}], { ariaVersion: ARIA_RECOMMENDED_VERSION }, options?.pretenders ? { pretenders: options.pretenders } : undefined);
19
+ const document = new MLDocument(ast, ruleset, [options?.specs ?? spec, {}], { ariaVersion: ARIA_RECOMMENDED_VERSION }, {
20
+ // The settings of the language are what the parser says, as `MLCore` hands them to the document
21
+ endTag: usedParser.endTag,
22
+ booleanish: usedParser.booleanish,
23
+ tagNameCaseSensitive: usedParser.tagNameCaseSensitive,
24
+ componentNameVariants: usedParser.componentNameVariants,
25
+ isComponentName: usedParser.isComponentName?.bind(usedParser),
26
+ // Only a parser with a list of its own (Vue) has it, as against `isComponentName` that the base class has
27
+ isNativeTag: 'isNativeTag' in usedParser ? usedParser.isNativeTag?.bind(usedParser) : undefined,
28
+ attrNameCaseSensitive: usedParser.attrNameCaseSensitive,
29
+ attrNameRoute: usedParser.attrNameRoute,
30
+ ...(options?.pretenders ? { pretenders: options.pretenders } : {}),
31
+ });
23
32
  return document;
24
33
  }
25
34
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/ml-core",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "description": "The core module of markuplint",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,8 +15,8 @@
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
18
- "import": "./lib/index.js",
19
- "types": "./lib/index.d.ts"
18
+ "types": "./lib/index.d.ts",
19
+ "default": "./lib/index.js"
20
20
  }
21
21
  },
22
22
  "publishConfig": {
@@ -34,20 +34,20 @@
34
34
  "./lib/configs.js": "./lib/configs.browser.js"
35
35
  },
36
36
  "dependencies": {
37
- "@markuplint/config-presets": "5.2.0",
38
- "@markuplint/html-parser": "5.2.0",
39
- "@markuplint/html-spec": "5.2.0",
40
- "@markuplint/i18n": "5.1.1",
41
- "@markuplint/ml-ast": "5.1.1",
42
- "@markuplint/ml-config": "5.2.0",
43
- "@markuplint/ml-spec": "5.2.0",
44
- "@markuplint/parser-utils": "5.2.0",
45
- "@markuplint/selector": "5.2.0",
46
- "@markuplint/shared": "5.2.0",
37
+ "@markuplint/config-presets": "5.3.0",
38
+ "@markuplint/html-parser": "5.3.0",
39
+ "@markuplint/html-spec": "5.3.0",
40
+ "@markuplint/i18n": "5.3.0",
41
+ "@markuplint/ml-ast": "5.3.0",
42
+ "@markuplint/ml-config": "5.3.0",
43
+ "@markuplint/ml-spec": "5.3.0",
44
+ "@markuplint/parser-utils": "5.3.0",
45
+ "@markuplint/selector": "5.3.0",
46
+ "@markuplint/shared": "5.3.0",
47
47
  "@types/debug": "^4.1.13",
48
48
  "debug": "^4.4.3",
49
49
  "is-plain-object": "^5.1.0",
50
50
  "type-fest": "^5.10.0"
51
51
  },
52
- "gitHead": "d8b2feab77c9c862513615bea6b80d2817431878"
52
+ "gitHead": "f587cf0206c8426e57369a48d6d5fb22306eafde"
53
53
  }