@markuplint/ml-core 2.0.0-dev.20211213.0 → 2.0.0-dev.20220105.1

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/lib/ml-core.d.ts CHANGED
@@ -10,7 +10,7 @@ export declare type MLCoreParams = {
10
10
  export declare class MLCore {
11
11
  #private;
12
12
  constructor({ parser, sourceCode, ruleset, rules, locale, schemas, parserOptions, filename, debug }: MLCoreParams);
13
- get document(): Document<RuleConfigValue, unknown> | MLParseError;
13
+ get document(): MLParseError | Document<RuleConfigValue, unknown>;
14
14
  verify(fix?: boolean): Promise<Violation[]>;
15
15
  setCode(sourceCode: string): void;
16
16
  update({ parser, ruleset, rules, locale, schemas, parserOptions }: Partial<MLFabric>): void;
@@ -138,7 +138,7 @@ class MLDOMDocument {
138
138
  const rule = ruleset.rules[ruleName];
139
139
  ruleMapper.set(node, ruleName, {
140
140
  from: 'rules',
141
- index: -1,
141
+ specificity: [0, 0, 0],
142
142
  rule,
143
143
  });
144
144
  });
@@ -151,25 +151,32 @@ class MLDOMDocument {
151
151
  if (!nodeRule.rules) {
152
152
  return;
153
153
  }
154
+ if (!selectorTarget) {
155
+ return;
156
+ }
154
157
  const selector = nodeRule.selector || nodeRule.regexSelector || nodeRule.tagName;
155
- const matched =
158
+ const matches =
156
159
  /**
157
160
  * Forward v1.x compatibility
158
161
  */
159
162
  nodeRule.tagName && /^#text$/i.test(nodeRule.tagName) && node.type === 'Text'
160
- ? { __node: '#text' }
163
+ ? {
164
+ matched: true,
165
+ selector: '#text',
166
+ specificity: [0, 0, 0],
167
+ }
161
168
  : /**
162
169
  * v2.0.0 or later
163
170
  */
164
- selectorTarget && (0, match_selector_1.matchSelector)(selectorTarget, selector);
165
- if (!matched) {
171
+ (0, match_selector_1.matchSelector)(selectorTarget, selector);
172
+ if (!matches.matched) {
166
173
  return;
167
174
  }
168
- docLog('Matched nodeRule: <%s>(%s)', 'nodeName' in node ? node.nodeName : node.type, matched.__node || 'No Selector');
175
+ docLog('Matched nodeRule: <%s>(%s)', 'nodeName' in node ? node.nodeName : node.type, matches.selector || '*');
169
176
  const ruleList = Object.keys(nodeRule.rules);
170
177
  for (const ruleName of ruleList) {
171
178
  const rule = nodeRule.rules[ruleName];
172
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
179
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
173
180
  if (convertedRule === undefined) {
174
181
  continue;
175
182
  }
@@ -178,7 +185,7 @@ class MLDOMDocument {
178
185
  ruleLog('↑ nodeRule (%s): %O', ruleName, mergedRule);
179
186
  ruleMapper.set(node, ruleName, {
180
187
  from: 'nodeRules',
181
- index: i,
188
+ specificity: matches.specificity,
182
189
  rule: mergedRule,
183
190
  });
184
191
  }
@@ -202,15 +209,15 @@ class MLDOMDocument {
202
209
  if (!selector) {
203
210
  return;
204
211
  }
205
- const matched = (0, match_selector_1.matchSelector)(selectorTarget, selector);
206
- if (!matched) {
212
+ const matches = (0, match_selector_1.matchSelector)(selectorTarget, selector);
213
+ if (!matches.matched) {
207
214
  return;
208
215
  }
209
- docLog('Matched childNodeRule: <%s>(%s), inheritance: %o', selectorTarget.nodeName, matched.__node || 'No Selector', !!nodeRule.inheritance);
216
+ docLog('Matched childNodeRule: <%s>(%s), inheritance: %o', selectorTarget.nodeName, matches.selector || '*', !!nodeRule.inheritance);
210
217
  const targetDescendants = nodeRule.inheritance ? descendants : children;
211
218
  Object.keys(nodeRuleRules).forEach(ruleName => {
212
219
  const rule = nodeRuleRules[ruleName];
213
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
220
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
214
221
  if (convertedRule === undefined) {
215
222
  return;
216
223
  }
@@ -220,7 +227,7 @@ class MLDOMDocument {
220
227
  targetDescendants.forEach(descendant => {
221
228
  ruleMapper.set(descendant, ruleName, {
222
229
  from: 'childNodeRules',
223
- index: i,
230
+ specificity: matches.specificity,
224
231
  rule: mergedRule,
225
232
  });
226
233
  });
@@ -1,5 +1,16 @@
1
1
  import type MLDOMAbstractElement from '../tokens/abstract-element';
2
+ import type { Specificity } from './selector';
2
3
  import type { RegexSelector } from '@markuplint/ml-config';
4
+ export declare type SelectorMatches = SelectorMatched | SelectorUnmatched;
5
+ declare type SelectorMatched = {
6
+ matched: true;
7
+ selector: string;
8
+ specificity: Specificity;
9
+ data?: Record<string, string>;
10
+ };
11
+ declare type SelectorUnmatched = {
12
+ matched: false;
13
+ };
3
14
  declare type TargetElement = MLDOMAbstractElement<any, any>;
4
- export declare function matchSelector(el: TargetElement, selector: string | RegexSelector | undefined): Record<string, string> | null;
15
+ export declare function matchSelector(el: TargetElement, selector: string | RegexSelector | undefined): SelectorMatches;
5
16
  export {};
@@ -5,12 +5,23 @@ const ml_config_1 = require("@markuplint/ml-config");
5
5
  const selector_1 = require("./selector");
6
6
  function matchSelector(el, selector) {
7
7
  if (!selector) {
8
- return null;
8
+ return {
9
+ matched: false,
10
+ };
9
11
  }
10
12
  if (typeof selector === 'string') {
11
13
  const sel = (0, selector_1.createSelector)(selector);
12
- const matched = sel.match(el);
13
- return matched ? { __node: selector } : null;
14
+ const specificity = sel.match(el);
15
+ if (specificity) {
16
+ return {
17
+ matched: true,
18
+ selector,
19
+ specificity,
20
+ };
21
+ }
22
+ return {
23
+ matched: false,
24
+ };
14
25
  }
15
26
  return regexSelect(el, selector);
16
27
  }
@@ -36,12 +47,12 @@ class SelectorTarget {
36
47
  };
37
48
  }
38
49
  match(el) {
39
- const matched = this._matchWithoutCombinateChecking(el);
40
- if (!matched) {
41
- return null;
50
+ const unitCheck = this._matchWithoutCombinateChecking(el);
51
+ if (!unitCheck.matched) {
52
+ return unitCheck;
42
53
  }
43
54
  if (!this._combinatedFrom) {
44
- return matched;
55
+ return unitCheck;
45
56
  }
46
57
  const { target, combinator } = this._combinatedFrom;
47
58
  switch (combinator) {
@@ -49,100 +60,72 @@ class SelectorTarget {
49
60
  case ' ': {
50
61
  let ancestor = el.parentNode;
51
62
  while (ancestor) {
52
- const ancestorMatched = target.match(ancestor);
53
- if (ancestorMatched) {
54
- const res = {
55
- ...ancestorMatched,
56
- ...matched,
57
- __node: `${ancestorMatched.__node} ${matched.__node}`,
58
- };
59
- return res;
63
+ const matches = target.match(ancestor);
64
+ if (matches.matched) {
65
+ return mergeMatches(matches, unitCheck, ' ');
60
66
  }
61
67
  ancestor = ancestor.parentNode;
62
68
  }
63
- return null;
69
+ return {
70
+ matched: false,
71
+ };
64
72
  }
65
73
  // Child combinator
66
74
  case '>': {
67
75
  if (!el.parentNode) {
68
- return null;
76
+ return { matched: false };
69
77
  }
70
- const parentMatched = target.match(el.parentNode);
71
- if (parentMatched) {
72
- const res = {
73
- ...parentMatched,
74
- ...matched,
75
- __node: `${parentMatched.__node} > ${matched.__node}`,
76
- };
77
- return res;
78
+ const matches = target.match(el.parentNode);
79
+ if (matches.matched) {
80
+ return mergeMatches(matches, unitCheck, ' > ');
78
81
  }
79
- return null;
82
+ return { matched: false };
80
83
  }
81
84
  // Next-sibling combinator
82
85
  case '+': {
83
86
  if (!el.previousElementSibling) {
84
- return null;
87
+ return { matched: false };
85
88
  }
86
- const prevMatched = target.match(el.previousElementSibling);
87
- if (prevMatched) {
88
- const res = {
89
- ...prevMatched,
90
- ...matched,
91
- __node: `${prevMatched.__node} + ${matched.__node}`,
92
- };
93
- return res;
89
+ const matches = target.match(el.previousElementSibling);
90
+ if (matches.matched) {
91
+ return mergeMatches(matches, unitCheck, ' + ');
94
92
  }
95
- return null;
93
+ return { matched: false };
96
94
  }
97
95
  // Subsequent-sibling combinator
98
96
  case '~': {
99
97
  let prev = el.previousElementSibling;
100
98
  while (prev) {
101
- const prevMatched = target.match(prev);
102
- if (prevMatched) {
103
- const res = {
104
- ...prevMatched,
105
- ...matched,
106
- __node: `${prevMatched.__node} ~ ${matched.__node}`,
107
- };
108
- return res;
99
+ const matches = target.match(prev);
100
+ if (matches.matched) {
101
+ return mergeMatches(matches, unitCheck, ' ~ ');
109
102
  }
110
103
  prev = prev.previousElementSibling;
111
104
  }
112
- return null;
105
+ return { matched: false };
113
106
  }
114
107
  // Prev-sibling combinator
115
108
  case ':has(+)': {
116
109
  if (!el.nextElementSibling) {
117
- return null;
110
+ return { matched: false };
118
111
  }
119
- const nextMatched = target.match(el.nextElementSibling);
120
- if (nextMatched) {
121
- const res = {
122
- ...nextMatched,
123
- ...matched,
124
- __node: `${nextMatched.__node}:has(+ ${matched.__node})`,
125
- };
126
- return res;
112
+ const matches = target.match(el.nextElementSibling);
113
+ if (matches.matched) {
114
+ return mergeMatches(matches, unitCheck, ':has(+ ', true);
127
115
  }
128
- return null;
116
+ return { matched: false };
129
117
  }
130
118
  // Subsequent-sibling (in front) combinator
131
119
  case ':has(~)': {
132
120
  let next = el.nextElementSibling;
133
121
  while (next) {
134
- const nextMatched = target.match(next);
135
- if (nextMatched) {
136
- const res = {
137
- ...nextMatched,
138
- ...matched,
139
- __node: `${nextMatched.__node}:has(~ ${matched.__node})`,
140
- };
141
- return res;
122
+ const matches = target.match(next);
123
+ if (matches.matched) {
124
+ return mergeMatches(matches, unitCheck, ':has(~ ', true);
142
125
  }
143
126
  next = next.nextElementSibling;
144
127
  }
145
- return null;
128
+ return { matched: false };
146
129
  }
147
130
  default: {
148
131
  throw new Error(`Unsupported ${this._combinatedFrom.combinator} combinator in selector`);
@@ -157,18 +140,25 @@ class SelectorTarget {
157
140
  }
158
141
  }
159
142
  function uncombinatedRegexSelect(el, selector) {
160
- let matchedMap = {};
143
+ let matched = true;
144
+ let data = {};
145
+ let tagSelector = '';
146
+ const specificity = [0, 0, 0];
147
+ const specifiedAttr = new Map();
161
148
  if (selector.nodeName) {
162
149
  const matchedNodeName = (0, ml_config_1.regexSelectorMatches)(selector.nodeName, el.nodeName);
163
- if (!matchedNodeName) {
164
- return null;
150
+ if (matchedNodeName) {
151
+ delete matchedNodeName.$0;
165
152
  }
166
- delete matchedNodeName.$0;
167
- matchedMap = {
168
- ...matchedMap,
153
+ else {
154
+ matched = false;
155
+ }
156
+ data = {
157
+ ...data,
169
158
  ...matchedNodeName,
170
- __node: el.nodeName,
171
159
  };
160
+ tagSelector = el.nodeName;
161
+ specificity[2] = 1;
172
162
  }
173
163
  if (selector.attrName) {
174
164
  const selectorAttrName = selector.attrName;
@@ -177,16 +167,16 @@ function uncombinatedRegexSelect(el, selector) {
177
167
  const matchedAttrName = (0, ml_config_1.regexSelectorMatches)(selectorAttrName, attrName);
178
168
  if (matchedAttrName) {
179
169
  delete matchedAttrName.$0;
180
- matchedMap = {
181
- ...matchedMap,
170
+ data = {
171
+ ...data,
182
172
  ...matchedAttrName,
183
- __node: matchedMap.__node ? `${matchedMap.__node}[${attrName}]` : `[${attrName}]`,
184
173
  };
174
+ specifiedAttr.set(attrName, '');
185
175
  }
186
176
  return matchedAttrName;
187
177
  });
188
178
  if (!matchedAttrNameList.some(_ => !!_)) {
189
- return null;
179
+ matched = false;
190
180
  }
191
181
  }
192
182
  if (selector.attrValue) {
@@ -197,19 +187,46 @@ function uncombinatedRegexSelect(el, selector) {
197
187
  const matchedAttrValue = (0, ml_config_1.regexSelectorMatches)(selectorAttrValue, attrValue);
198
188
  if (matchedAttrValue) {
199
189
  delete matchedAttrValue.$0;
200
- matchedMap = {
201
- ...matchedMap,
190
+ data = {
191
+ ...data,
202
192
  ...matchedAttrValue,
203
- __node: matchedMap.__node
204
- ? `${matchedMap.__node}[${attrName}="${attrValue}"]`
205
- : `[${attrName}="${attrValue}"]`,
206
193
  };
194
+ specifiedAttr.set(attrName, attrValue);
207
195
  }
208
196
  return matchedAttrValue;
209
197
  });
210
198
  if (!matchedAttrValueList.some(_ => !!_)) {
211
- return null;
199
+ matched = false;
212
200
  }
213
201
  }
214
- return matchedMap;
202
+ const attrSelector = Array.from(specifiedAttr.entries())
203
+ .map(([name, value]) => {
204
+ return `[${name}${value ? `="${value}"` : ''}]`;
205
+ })
206
+ .join('');
207
+ specificity[1] += specifiedAttr.size;
208
+ if (matched) {
209
+ return {
210
+ matched,
211
+ selector: `${tagSelector}${attrSelector}`,
212
+ specificity,
213
+ data,
214
+ };
215
+ }
216
+ return { matched };
217
+ }
218
+ function mergeMatches(a, b, sep, close = false) {
219
+ return {
220
+ matched: true,
221
+ selector: `${a.selector}${sep}${b.selector}${close ? ')' : ''}`,
222
+ specificity: [
223
+ a.specificity[0] + b.specificity[0],
224
+ a.specificity[1] + b.specificity[1],
225
+ a.specificity[2] + b.specificity[2],
226
+ ],
227
+ data: {
228
+ ...a.data,
229
+ ...b.data,
230
+ },
231
+ };
215
232
  }
@@ -1,9 +1,11 @@
1
1
  import type MLDOMAbstractElement from '../tokens/abstract-element';
2
+ export declare type Specificity = [number, number, number];
2
3
  export declare function createSelector(selector: string): Selector;
4
+ export declare function compareSpecificity(a: Specificity, b: Specificity): 0 | 1 | -1;
3
5
  declare type TargetElement = MLDOMAbstractElement<any, any>;
4
6
  declare class Selector {
5
7
  #private;
6
8
  constructor(selector: string);
7
- match(el: TargetElement, caller?: TargetElement | null): boolean;
9
+ match(el: TargetElement, caller?: TargetElement | null): Specificity | false;
8
10
  }
9
11
  export {};