@markuplint/rules 4.6.1 → 4.7.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.
@@ -1,4 +1,4 @@
1
1
  import type { ChildNode, Options, Result, Specs } from './types.js';
2
2
  import type { PermittedContentChoice } from '@markuplint/ml-spec';
3
3
  import type { ReadonlyDeep } from 'type-fest';
4
- export declare function choice(pattern: ReadonlyDeep<PermittedContentChoice>, elements: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
4
+ export declare function choice(pattern: ReadonlyDeep<PermittedContentChoice>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
@@ -4,9 +4,9 @@ import { Collection, modelLog } from './utils.js';
4
4
  const indexes = new WeakMap();
5
5
  export function choice(pattern,
6
6
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7
- elements, specs, options, depth) {
7
+ childNodes, specs, options, depth) {
8
8
  const choiceLog = cmLog.extend(`choice#${depth}`);
9
- const collection = new Collection(elements);
9
+ const collection = new Collection(childNodes);
10
10
  const unmatchedResults = [];
11
11
  let i = 0;
12
12
  for (const some of pattern.choice) {
@@ -5,10 +5,10 @@ import type { ReadonlyDeep } from 'type-fest';
5
5
  * Check content condition
6
6
  *
7
7
  * @param pattern
8
- * @param elements
8
+ * @param childNodes
9
9
  * @param specs
10
10
  * @param options
11
11
  * @param depth
12
12
  * @returns
13
13
  */
14
- export declare function complexBranch(pattern: ReadonlyDeep<PermittedContentPattern>, elements: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
14
+ export declare function complexBranch(pattern: ReadonlyDeep<PermittedContentPattern>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
@@ -6,7 +6,7 @@ import { isChoice, isTransparent } from './utils.js';
6
6
  * Check content condition
7
7
  *
8
8
  * @param pattern
9
- * @param elements
9
+ * @param childNodes
10
10
  * @param specs
11
11
  * @param options
12
12
  * @param depth
@@ -14,12 +14,12 @@ import { isChoice, isTransparent } from './utils.js';
14
14
  */
15
15
  export function complexBranch(pattern,
16
16
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
17
- elements, specs, options, depth) {
17
+ childNodes, specs, options, depth) {
18
18
  if (isChoice(pattern)) {
19
- return choice(pattern, elements, specs, options, depth);
19
+ return choice(pattern, childNodes, specs, options, depth);
20
20
  }
21
21
  if (isTransparent(pattern)) {
22
- return transparent(elements);
22
+ return transparent(childNodes);
23
23
  }
24
- return countPattern(pattern, elements, specs, options, depth);
24
+ return countPattern(pattern, childNodes, specs, options, depth);
25
25
  }
@@ -5,10 +5,10 @@ import type { ReadonlyDeep } from 'type-fest';
5
5
  * Check count
6
6
  *
7
7
  * @param pattern
8
- * @param elements
8
+ * @param childNodes
9
9
  * @param specs
10
10
  * @param options
11
11
  * @param depth
12
12
  * @returns
13
13
  */
14
- export declare function countPattern(pattern: ReadonlyDeep<PermittedContentOneOrMore> | ReadonlyDeep<PermittedContentOptional> | ReadonlyDeep<PermittedContentRequire> | ReadonlyDeep<PermittedContentZeroOrMore>, elements: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
14
+ export declare function countPattern(pattern: ReadonlyDeep<PermittedContentOneOrMore> | ReadonlyDeep<PermittedContentOptional> | ReadonlyDeep<PermittedContentRequire> | ReadonlyDeep<PermittedContentZeroOrMore>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
@@ -6,7 +6,7 @@ const cLog = cmLog.extend('countCompereResult');
6
6
  * Check count
7
7
  *
8
8
  * @param pattern
9
- * @param elements
9
+ * @param childNodes
10
10
  * @param specs
11
11
  * @param options
12
12
  * @param depth
@@ -14,9 +14,9 @@ const cLog = cmLog.extend('countCompereResult');
14
14
  */
15
15
  export function countPattern(pattern,
16
16
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
17
- elements, specs, options, depth) {
17
+ childNodes, specs, options, depth) {
18
18
  const ptLog = cmLog.extend(`countPattern#${depth}`);
19
- const collection = new Collection(elements);
19
+ const collection = new Collection(childNodes);
20
20
  const { model, min, max, repeat, missingType } = normalizeModel(pattern);
21
21
  ptLog('Model:\n RegEx: %s\n Schema: %o', modelLog(model, repeat), pattern);
22
22
  let prevResult = null;
@@ -7,12 +7,10 @@ export default createRule({
7
7
  defaultValue: [],
8
8
  defaultOptions: {
9
9
  ignoreHasMutableChildren: true,
10
+ evaluateConditionalChildNodes: false,
10
11
  },
11
12
  async verify({ document, report, t }) {
12
13
  await document.walkOn('Element', el => {
13
- if (el.rule.options.ignoreHasMutableChildren && el.hasMutableChildren()) {
14
- return;
15
- }
16
14
  const results = contentModel(el, el.rule.value, el.rule.options);
17
15
  for (const { type, scope, query, hint } of results) {
18
16
  let message = '';
@@ -28,6 +26,10 @@ export default createRule({
28
26
  break;
29
27
  }
30
28
  case 'MISSING_NODE_ONE_OR_MORE': {
29
+ if (scope.rule.options.ignoreHasMutableChildren &&
30
+ (!scope.is(scope.ELEMENT_NODE) || scope.hasMutableChildren())) {
31
+ break;
32
+ }
31
33
  message =
32
34
  message ||
33
35
  t('Require {0}', t('one or more elements')) + t('. ') + '(' + t('Need "{0*}"', query) + ')';
@@ -38,6 +40,10 @@ export default createRule({
38
40
  break;
39
41
  }
40
42
  case 'MISSING_NODE_REQUIRED': {
43
+ if (scope.rule.options.ignoreHasMutableChildren &&
44
+ (!scope.is(scope.ELEMENT_NODE) || scope.hasMutableChildren())) {
45
+ break;
46
+ }
41
47
  message =
42
48
  message ||
43
49
  t('Require {0}', t('an {0}', 'element')) + t('. ') + '(' + t('Need "{0*}"', query) + ')';
@@ -1,3 +1,3 @@
1
1
  import type { ChildNode, Result, Specs } from './types.js';
2
2
  export type SelectorResult = Result<'UNMATCHED_SELECTOR_BUT_MAY_EMPTY' | 'MISSING_NODE' | 'UNMATCHED_SELECTORS'>;
3
- export declare function matchesSelector(query: string, node: ChildNode | undefined, specs: Specs, depth: number): SelectorResult;
3
+ export declare function matchesSelector(query: string, childNode: ChildNode | undefined, specs: Specs, depth: number): SelectorResult;
@@ -3,10 +3,10 @@ import { cmLog } from './debug.js';
3
3
  import { cleanObject, matches } from './utils.js';
4
4
  export function matchesSelector(query,
5
5
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
6
- node, specs, depth) {
6
+ childNode, specs, depth) {
7
7
  const nodeLog = cmLog.extend(`node#${depth}`);
8
8
  const { selector, hasText, hasCustom } = optCondition(query, specs);
9
- if (node == null) {
9
+ if (childNode == null) {
10
10
  if (hasText) {
11
11
  nodeLog('<#text>.matches(%s) => ""', query);
12
12
  return {
@@ -27,23 +27,23 @@ node, specs, depth) {
27
27
  hint: {},
28
28
  };
29
29
  }
30
- if (node.is(node.TEXT_NODE)) {
30
+ if (childNode.is(childNode.TEXT_NODE)) {
31
31
  if (hasText) {
32
- nodeLog('<#text>.matches(%s) => "%s"', query, node.raw.trim());
32
+ nodeLog('<#text>.matches(%s) => "%s"', query, childNode.raw.trim());
33
33
  return {
34
34
  type: 'MATCHED',
35
- matched: [node],
35
+ matched: [childNode],
36
36
  unmatched: [],
37
37
  zeroMatch: true,
38
38
  query,
39
39
  hint: {},
40
40
  };
41
41
  }
42
- if (node.isWhitespace()) {
42
+ if (childNode.isWhitespace()) {
43
43
  nodeLog('<#text>.matches(%s) => WHITESPACE', query);
44
44
  return {
45
45
  type: 'MATCHED',
46
- matched: [node],
46
+ matched: [childNode],
47
47
  unmatched: [],
48
48
  zeroMatch: true,
49
49
  query,
@@ -54,41 +54,41 @@ node, specs, depth) {
54
54
  return {
55
55
  type: 'UNEXPECTED_EXTRA_NODE',
56
56
  matched: [],
57
- unmatched: [node],
57
+ unmatched: [childNode],
58
58
  zeroMatch: false,
59
59
  query,
60
60
  hint: {},
61
61
  };
62
62
  }
63
- if (node.is(node.MARKUPLINT_PREPROCESSOR_BLOCK)) {
64
- nodeLog('%s.matches(%s) => PBlock', node.raw, query);
63
+ if (childNode.is(childNode.MARKUPLINT_PREPROCESSOR_BLOCK)) {
64
+ nodeLog('%s.matches(%s) => PBlock', childNode.raw, query);
65
65
  return {
66
66
  type: 'MATCHED',
67
- matched: [node],
67
+ matched: [childNode],
68
68
  unmatched: [],
69
69
  zeroMatch: !!hasText,
70
70
  query,
71
71
  hint: {},
72
72
  };
73
73
  }
74
- if (node.is(node.ELEMENT_NODE)) {
75
- if (node.elementType !== 'html' && hasCustom) {
76
- nodeLog('%s.matches(%s) => CustomElement', node.raw, query);
74
+ if (childNode.is(childNode.ELEMENT_NODE)) {
75
+ if (childNode.elementType !== 'html' && hasCustom) {
76
+ nodeLog('%s.matches(%s) => CustomElement', childNode.raw, query);
77
77
  return {
78
78
  type: 'MATCHED',
79
- matched: [node],
79
+ matched: [childNode],
80
80
  unmatched: [],
81
81
  zeroMatch: !!hasText,
82
82
  query,
83
83
  hint: {},
84
84
  };
85
85
  }
86
- const result = matches(selector, node, specs);
87
- nodeLog('%s.matches(%s) => %s', node.raw, query, result.matched);
86
+ const result = matches(selector, childNode, specs);
87
+ nodeLog('%s.matches(%s) => %s', childNode.raw, query, result.matched);
88
88
  if (result.matched) {
89
89
  return {
90
90
  type: 'MATCHED',
91
- matched: [node],
91
+ matched: [childNode],
92
92
  unmatched: [],
93
93
  zeroMatch: !!hasText,
94
94
  query,
@@ -99,7 +99,7 @@ node, specs, depth) {
99
99
  return {
100
100
  type: 'UNMATCHED_SELECTOR_BUT_MAY_EMPTY',
101
101
  matched: [],
102
- unmatched: [node],
102
+ unmatched: [childNode],
103
103
  zeroMatch: true,
104
104
  query,
105
105
  hint: cleanObject({
@@ -110,7 +110,7 @@ node, specs, depth) {
110
110
  return {
111
111
  type: 'UNMATCHED_SELECTORS',
112
112
  matched: [],
113
- unmatched: [node],
113
+ unmatched: [childNode],
114
114
  zeroMatch: false,
115
115
  query,
116
116
  hint: cleanObject({
@@ -120,7 +120,7 @@ node, specs, depth) {
120
120
  }
121
121
  return {
122
122
  type: 'MATCHED',
123
- matched: [node],
123
+ matched: [childNode],
124
124
  unmatched: [],
125
125
  zeroMatch: !!hasText,
126
126
  query,
@@ -5,10 +5,10 @@ import type { ReadonlyDeep } from 'type-fest';
5
5
  * Check ordered array
6
6
  *
7
7
  * @param contents
8
- * @param nodes
8
+ * @param childNodes
9
9
  * @param specs
10
10
  * @param options
11
11
  * @param depth
12
12
  * @returns
13
13
  */
14
- export declare function order(contents: ReadonlyDeep<PermittedContentPattern[]>, nodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
14
+ export declare function order(contents: ReadonlyDeep<PermittedContentPattern[]>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): Result;
@@ -6,7 +6,7 @@ import { Collection, mergeHints, modelLog } from './utils.js';
6
6
  * Check ordered array
7
7
  *
8
8
  * @param contents
9
- * @param nodes
9
+ * @param childNodes
10
10
  * @param specs
11
11
  * @param options
12
12
  * @param depth
@@ -14,11 +14,11 @@ import { Collection, mergeHints, modelLog } from './utils.js';
14
14
  */
15
15
  export function order(contents,
16
16
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
17
- nodes, specs, options, depth) {
17
+ childNodes, specs, options, depth) {
18
18
  const orderLog = cmLog.extend(`order#${depth}`);
19
19
  const btLog = cmLog.extend(`backtrack#${depth}`);
20
20
  const patterns = deepCopy(contents);
21
- const collection = new Collection(nodes);
21
+ const collection = new Collection(childNodes);
22
22
  orderLog('Model:\n RegEx: %s\n Schema: %o', modelLog(patterns, ''), patterns);
23
23
  orderLog('Starts: %s', collection);
24
24
  let result = undefined;
@@ -2,4 +2,4 @@ import type { SelectorResult } from './matches-selector.js';
2
2
  import type { ChildNode, Options, Specs } from './types.js';
3
3
  import type { PermittedContentPattern, Model } from '@markuplint/ml-spec';
4
4
  import type { ReadonlyDeep } from 'type-fest';
5
- export declare function recursiveBranch(model: ReadonlyDeep<Model | PermittedContentPattern[]>, nodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): SelectorResult;
5
+ export declare function recursiveBranch(model: ReadonlyDeep<Model | PermittedContentPattern[]>, childNodes: readonly ChildNode[], specs: Specs, options: Options, depth: number): SelectorResult;
@@ -3,14 +3,14 @@ import { order } from './order.js';
3
3
  import { Collection, isModel } from './utils.js';
4
4
  export function recursiveBranch(model,
5
5
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
6
- nodes, specs, options, depth) {
6
+ childNodes, specs, options, depth) {
7
7
  if (!isModel(model)) {
8
- return order(model, nodes, specs, options, depth + 1);
8
+ return order(model, childNodes, specs, options, depth + 1);
9
9
  }
10
10
  if (typeof model === 'string') {
11
- return matchesSelector(model, nodes[0], specs, depth);
11
+ return matchesSelector(model, childNodes[0], specs, depth);
12
12
  }
13
- const collection = new Collection(nodes);
13
+ const collection = new Collection(childNodes);
14
14
  let lastUnmatched = null;
15
15
  for (const query of model) {
16
16
  const result = matchesSelector(query, collection.unmatched[0], specs, depth);
@@ -1,6 +1,8 @@
1
1
  import type { ChildNode, Options, Result, Specs } from './types.js';
2
2
  export declare const transparentMode: Map<ChildNode, true>;
3
- export declare function representTransparentNodes(nodes: ChildNode[], specs: Specs, options: Options): {
3
+ type TransparentNode = {
4
4
  nodes: ChildNode[];
5
5
  errors: Result[];
6
6
  };
7
+ export declare function representTransparentNodes(childNodes: readonly ChildNode[], specs: Specs, options: Options): TransparentNode[];
8
+ export {};
@@ -1,73 +1,94 @@
1
1
  import { getContentModel } from '@markuplint/ml-spec';
2
+ import { branchesToPatterns } from '@markuplint/shared';
2
3
  import { order } from './order.js';
3
- import { Collection, getChildNodesWithoutWhitespaces, isTransparent, matches } from './utils.js';
4
+ import { Collection, isTransparent, matches } from './utils.js';
4
5
  export const transparentMode = new Map();
5
6
  export function representTransparentNodes(
6
7
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7
- nodes, specs, options) {
8
- const result = [];
9
- const errors = [];
10
- const parent = nodes[0]?.parentElement;
11
- if (parent) {
12
- const { errors: parentErrors } = representTransparentNodes([parent], specs, options);
13
- errors.push(...parentErrors);
14
- }
15
- for (const node of nodes) {
16
- if (!node.is(node.ELEMENT_NODE)) {
17
- result.push(node);
8
+ childNodes, specs, options) {
9
+ const parentElement = childNodes[0]?.parentElement;
10
+ const parentResults = parentElement
11
+ ? representTransparentNodes([parentElement], specs, options)
12
+ : [{ nodes: [], errors: [] }];
13
+ const branches = [];
14
+ for (const childNode of childNodes) {
15
+ if (!childNode.is(childNode.ELEMENT_NODE)) {
16
+ branches.push(childNode);
18
17
  continue;
19
18
  }
20
- const models = getContentModel(node, specs.specs);
19
+ const models = getContentModel(childNode, specs.specs);
21
20
  if (models == null || typeof models === 'boolean') {
22
- result.push(node);
21
+ branches.push(childNode);
23
22
  continue;
24
23
  }
25
24
  const noTransparentModels = models.filter(m => !isTransparent(m));
26
25
  if (noTransparentModels.length === models.length) {
27
- result.push(node);
26
+ branches.push(childNode);
28
27
  continue;
29
28
  }
30
- const collection = new Collection(getChildNodesWithoutWhitespaces(node));
31
- let unmatched;
32
- if (noTransparentModels.length > 0) {
33
- const result = order(noTransparentModels, collection.unmatched, specs, options, Number.POSITIVE_INFINITY);
34
- unmatched = result.unmatched;
35
- }
36
- else {
37
- unmatched = collection.unmatched;
38
- }
39
- const transparent = models.find(m => isTransparent(m));
40
- if (!transparent || !isTransparent(transparent)) {
41
- throw new Error('Unreachable code');
42
- }
43
- for (const _child of unmatched) {
44
- const child = _child;
45
- if (transparentMode.has(child)) {
46
- continue;
29
+ const childNodesPatterns = options.evaluateConditionalChildNodes
30
+ ? childNode.conditionalChildNodes().map(childNodes => [...childNodes])
31
+ : [[...childNode.childNodes].filter(child => !(child.is(child.TEXT_NODE) && child.isWhitespace()))];
32
+ const representPattern = [];
33
+ for (const childNodes of childNodesPatterns) {
34
+ const collection = new Collection([...childNodes]);
35
+ let unmatched;
36
+ if (noTransparentModels.length > 0) {
37
+ const result = order(noTransparentModels, collection.unmatched, specs, options, Number.POSITIVE_INFINITY);
38
+ unmatched = result.unmatched;
47
39
  }
48
- transparentMode.set(child, true);
49
- if (child.is(child.ELEMENT_NODE)) {
50
- const transparentCondMatched = matches(transparent.transparent, child, specs);
51
- if (!transparentCondMatched.matched) {
52
- errors.push({
53
- type: 'TRANSPARENT_MODEL_DISALLOWS',
54
- matched: [],
55
- unmatched: [node],
56
- zeroMatch: false,
57
- query: transparent.transparent,
58
- hint: {
59
- not: transparentCondMatched.not,
60
- transparent: node,
61
- },
62
- });
40
+ else {
41
+ unmatched = collection.unmatched;
42
+ }
43
+ const transparent = models.find(m => isTransparent(m));
44
+ if (!transparent || !isTransparent(transparent)) {
45
+ throw new Error('Unreachable code');
46
+ }
47
+ for (const _child of unmatched) {
48
+ const child = _child;
49
+ if (transparentMode.has(child)) {
63
50
  continue;
64
51
  }
52
+ transparentMode.set(child, true);
53
+ if (child.is(child.ELEMENT_NODE)) {
54
+ const transparentCondMatched = matches(transparent.transparent, child, specs);
55
+ if (!transparentCondMatched.matched) {
56
+ representPattern.push({
57
+ type: 'TRANSPARENT_MODEL_DISALLOWS',
58
+ matched: [],
59
+ unmatched: [childNode],
60
+ zeroMatch: false,
61
+ query: transparent.transparent,
62
+ hint: {
63
+ not: transparentCondMatched.not,
64
+ transparent: childNode,
65
+ },
66
+ });
67
+ continue;
68
+ }
69
+ }
70
+ representPattern.push(child);
65
71
  }
66
- result.push(child);
67
72
  }
73
+ branches.push(representPattern);
68
74
  }
69
- return {
70
- nodes: result,
71
- errors,
72
- };
75
+ const patterns = branchesToPatterns(branches);
76
+ const result = parentResults.flatMap(parentResult => {
77
+ const patternResults = patterns.map(pattern => {
78
+ const nodes = pattern.filter((node) => 'nodeName' in node);
79
+ const errors = pattern.filter((node) => 'type' in node);
80
+ return {
81
+ nodes,
82
+ errors: [...parentResult.errors, ...errors],
83
+ };
84
+ });
85
+ if (patternResults.length === 0) {
86
+ return {
87
+ nodes: [],
88
+ errors: parentResult.errors,
89
+ };
90
+ }
91
+ return patternResults;
92
+ });
93
+ return result;
73
94
  }
@@ -1,6 +1,5 @@
1
1
  import { order } from './order.js';
2
2
  import { representTransparentNodes } from './represent-transparent-nodes.js';
3
- import { getChildNodesWithoutWhitespaces } from './utils.js';
4
3
  /**
5
4
  * Check start
6
5
  *
@@ -13,53 +12,60 @@ import { getChildNodesWithoutWhitespaces } from './utils.js';
13
12
  export function start(contents,
14
13
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
15
14
  el, specs, options) {
16
- if (contents === false) {
17
- if (el.childNodes.length > 0) {
15
+ const childNodesPatterns = options.evaluateConditionalChildNodes
16
+ ? el.conditionalChildNodes().map(childNodes => [...childNodes])
17
+ : [[...el.childNodes].filter(child => !(child.is(child.TEXT_NODE) && child.isWhitespace()))];
18
+ return childNodesPatterns.flatMap(childNodes => {
19
+ if (contents === false) {
20
+ if (childNodes.length > 0) {
21
+ return [
22
+ {
23
+ type: 'NOTHING',
24
+ scope: el,
25
+ query: ':not(*)',
26
+ hint: {},
27
+ },
28
+ ];
29
+ }
18
30
  return [
19
31
  {
20
- type: 'NOTHING',
32
+ type: 'MATCHED',
21
33
  scope: el,
22
- query: ':not(*)',
34
+ query: '*',
23
35
  hint: {},
24
36
  },
25
37
  ];
26
38
  }
27
- return [
28
- {
29
- type: 'MATCHED',
30
- scope: el,
31
- query: '*',
32
- hint: {},
33
- },
34
- ];
35
- }
36
- if (contents === true) {
37
- // Allows all elements
38
- return [
39
- {
40
- type: 'MATCHED',
41
- scope: el,
42
- query: '*',
43
- hint: {},
44
- },
45
- ];
46
- }
47
- const { nodes, errors } = representTransparentNodes(getChildNodesWithoutWhitespaces(el), specs, options);
48
- const result = order(contents, nodes, specs, options, 0);
49
- return [
50
- {
51
- type: result.type,
52
- scope: result.type === 'MISSING_NODE_REQUIRED' || result.type === 'MISSING_NODE_ONE_OR_MORE'
53
- ? el
54
- : result.unmatched[0] ?? el,
55
- query: result.query,
56
- hint: result.hint,
57
- },
58
- ...errors.map(error => ({
59
- type: error.type,
60
- scope: error.unmatched[0] ?? el,
61
- query: error.query,
62
- hint: error.hint,
63
- })),
64
- ];
39
+ if (contents === true) {
40
+ // Allows all elements
41
+ return [
42
+ {
43
+ type: 'MATCHED',
44
+ scope: el,
45
+ query: '*',
46
+ hint: {},
47
+ },
48
+ ];
49
+ }
50
+ const patterns = representTransparentNodes(childNodes, specs, options);
51
+ return patterns.flatMap(({ nodes, errors }) => {
52
+ const result = order(contents, nodes, specs, options, 0);
53
+ return [
54
+ {
55
+ type: result.type,
56
+ scope: result.type === 'MISSING_NODE_REQUIRED' || result.type === 'MISSING_NODE_ONE_OR_MORE'
57
+ ? el
58
+ : result.unmatched[0] ?? el,
59
+ query: result.query,
60
+ hint: result.hint,
61
+ },
62
+ ...errors.map(error => ({
63
+ type: error.type,
64
+ scope: error.unmatched[0] ?? el,
65
+ query: error.query,
66
+ hint: error.hint,
67
+ })),
68
+ ];
69
+ });
70
+ });
65
71
  }
@@ -1,2 +1,2 @@
1
1
  import type { ChildNode, Result } from './types.js';
2
- export declare function transparent(nodes: readonly ChildNode[]): Result;
2
+ export declare function transparent(childNodes: readonly ChildNode[]): Result;
@@ -2,14 +2,14 @@ import { cmLog } from './debug.js';
2
2
  const transparentLog = cmLog.extend('transparent');
3
3
  export function transparent(
4
4
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
5
- nodes) {
6
- if (nodes.length === 0 || nodes[0]?.parentElement?.parentElement) {
5
+ childNodes) {
6
+ if (childNodes.length === 0 || childNodes[0]?.parentElement?.parentElement) {
7
7
  transparentLog('Skipped');
8
8
  return {
9
- type: nodes.length === 0 ? 'MATCHED_ZERO' : 'MATCHED',
10
- matched: [...nodes],
9
+ type: childNodes.length === 0 ? 'MATCHED_ZERO' : 'MATCHED',
10
+ matched: [...childNodes],
11
11
  unmatched: [],
12
- zeroMatch: nodes.length === 0,
12
+ zeroMatch: childNodes.length === 0,
13
13
  query: 'transparent',
14
14
  hint: {},
15
15
  };
@@ -17,7 +17,7 @@ nodes) {
17
17
  transparentLog('Transparent model element is component root');
18
18
  return {
19
19
  type: 'MATCHED',
20
- matched: [...nodes],
20
+ matched: [...childNodes],
21
21
  unmatched: [],
22
22
  zeroMatch: false,
23
23
  query: 'transparent',
@@ -49,4 +49,8 @@ export type TagRule = {
49
49
  } & ReadonlyDeep<ContentModel>;
50
50
  export type Options = {
51
51
  readonly ignoreHasMutableChildren: boolean;
52
+ /**
53
+ * @experimental
54
+ */
55
+ readonly evaluateConditionalChildNodes: boolean;
52
56
  };
@@ -1,7 +1,6 @@
1
- import type { ChildNode, Element, Hints, MissingNodeReason, RepeatSign, Specs } from './types.js';
1
+ import type { ChildNode, Hints, MissingNodeReason, RepeatSign, Specs } from './types.js';
2
2
  import type { PermittedContentPattern, PermittedContentChoice, PermittedContentOneOrMore, PermittedContentOptional, PermittedContentRequire, PermittedContentTransparent, PermittedContentZeroOrMore, Model } from '@markuplint/ml-spec';
3
3
  import type { ReadonlyDeep } from 'type-fest';
4
- export declare function getChildNodesWithoutWhitespaces(el: Element): ChildNode[];
5
4
  export declare function isModel(model: ReadonlyDeep<Model | PermittedContentPattern[]>): model is ReadonlyDeep<Model>;
6
5
  export declare function matches(selector: string, node: ChildNode, specs: Specs): {
7
6
  matched: boolean;
@@ -30,7 +29,7 @@ export declare function mergeHints(a: Readonly<Hints>, b: Readonly<Hints>): Part
30
29
  }> | undefined;
31
30
  max?: number | undefined;
32
31
  not?: ChildNode | undefined;
33
- transparent?: Element | undefined;
32
+ transparent?: import("./types.js").Element | undefined;
34
33
  }>;
35
34
  export declare function cleanObject<T extends Object>(object: T): Partial<T>;
36
35
  export declare class Collection {
@@ -13,20 +13,6 @@ var _Collection_locked, _Collection_matched, _Collection_nodes, _Collection_orig
13
13
  import { createSelector } from '@markuplint/selector';
14
14
  import { bgGreen, green, bgRed, bgBlue, blue, bgMagenta, cyan } from './debug.js';
15
15
  import { transparentMode } from './represent-transparent-nodes.js';
16
- const getChildNodesWithoutWhitespacesCaches = new Map();
17
- export function getChildNodesWithoutWhitespaces(
18
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
19
- el) {
20
- let nodes = getChildNodesWithoutWhitespacesCaches.get(el);
21
- if (nodes) {
22
- return nodes;
23
- }
24
- nodes = [...el.childNodes].filter(node => {
25
- return !(node.is(node.TEXT_NODE) && node.isWhitespace());
26
- });
27
- getChildNodesWithoutWhitespacesCaches.set(el, nodes);
28
- return nodes;
29
- }
30
16
  export function isModel(model) {
31
17
  if (typeof model === 'string') {
32
18
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/rules",
3
- "version": "4.6.1",
3
+ "version": "4.7.0",
4
4
  "description": "Built-in rules of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -25,18 +25,18 @@
25
25
  "./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
26
26
  },
27
27
  "dependencies": {
28
- "@markuplint/html-spec": "4.6.1",
29
- "@markuplint/ml-core": "4.6.1",
30
- "@markuplint/ml-spec": "4.4.1",
31
- "@markuplint/selector": "4.5.1",
32
- "@markuplint/shared": "4.2.0",
33
- "@markuplint/types": "4.3.0",
28
+ "@markuplint/html-spec": "4.7.0",
29
+ "@markuplint/ml-core": "4.7.0",
30
+ "@markuplint/ml-spec": "4.5.0",
31
+ "@markuplint/selector": "4.6.0",
32
+ "@markuplint/shared": "4.3.0",
33
+ "@markuplint/types": "4.4.0",
34
34
  "@types/debug": "4.1.12",
35
35
  "@ungap/structured-clone": "1.2.0",
36
36
  "ansi-colors": "4.1.3",
37
37
  "chrono-node": "2.7.5",
38
38
  "debug": "4.3.4",
39
- "type-fest": "4.15.0"
39
+ "type-fest": "4.18.1"
40
40
  },
41
- "gitHead": "b029c86a6b3a9ea8189d2e5535e3023aaea753fd"
41
+ "gitHead": "b8d7bae9bdcdad63ff79abe21b88be12abde3633"
42
42
  }