@markuplint/ml-core 2.0.0-dev.20211213.2 → 2.0.0-dev.20220107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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(): MLParseError | Document<RuleConfigValue, unknown>;
13
+ get document(): Document<RuleConfigValue, unknown> | MLParseError;
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;
package/lib/ml-core.js CHANGED
@@ -85,18 +85,20 @@ class MLCore {
85
85
  }
86
86
  (0, debug_1.log)('%s Rule: verify end', rule.name);
87
87
  }
88
- const { e, w, i } = violations.reduce((c, v) => {
89
- if (v.severity === 'error')
90
- c.e += 1;
91
- if (v.severity === 'warning')
92
- c.w += 1;
93
- if (v.severity === 'info')
94
- c.i += 1;
95
- return c;
96
- }, { e: 0, w: 0, i: 0 });
97
- resultLog('Error: %d', e);
98
- resultLog('Warning: %d', w);
99
- resultLog('Info: %d', i);
88
+ if (resultLog.enabled) {
89
+ const { e, w, i } = violations.reduce((c, v) => {
90
+ if (v.severity === 'error')
91
+ c.e += 1;
92
+ if (v.severity === 'warning')
93
+ c.w += 1;
94
+ if (v.severity === 'info')
95
+ c.i += 1;
96
+ return c;
97
+ }, { e: 0, w: 0, i: 0 });
98
+ resultLog('Error: %d', e);
99
+ resultLog('Warning: %d', w);
100
+ resultLog('Info: %d', i);
101
+ }
100
102
  (0, debug_1.log)('verify: end');
101
103
  return violations;
102
104
  }
@@ -132,13 +132,15 @@ class MLDOMDocument {
132
132
  if (node.type === 'ElementCloseTag') {
133
133
  continue;
134
134
  }
135
- docLog('Add rules to node <%s>', 'nodeName' in node ? node.nodeName : `#${node.type}`);
135
+ if (docLog.enabled) {
136
+ docLog('Add rules to node <%s>', 'nodeName' in node ? node.nodeName : `#${node.type}`);
137
+ }
136
138
  // global rules
137
139
  Object.keys(ruleset.rules).forEach(ruleName => {
138
140
  const rule = ruleset.rules[ruleName];
139
141
  ruleMapper.set(node, ruleName, {
140
142
  from: 'rules',
141
- index: -1,
143
+ specificity: [0, 0, 0],
142
144
  rule,
143
145
  });
144
146
  });
@@ -151,25 +153,34 @@ class MLDOMDocument {
151
153
  if (!nodeRule.rules) {
152
154
  return;
153
155
  }
156
+ if (!selectorTarget) {
157
+ return;
158
+ }
154
159
  const selector = nodeRule.selector || nodeRule.regexSelector || nodeRule.tagName;
155
- const matched =
160
+ const matches =
156
161
  /**
157
162
  * Forward v1.x compatibility
158
163
  */
159
164
  nodeRule.tagName && /^#text$/i.test(nodeRule.tagName) && node.type === 'Text'
160
- ? { __node: '#text' }
165
+ ? {
166
+ matched: true,
167
+ selector: '#text',
168
+ specificity: [0, 0, 0],
169
+ }
161
170
  : /**
162
171
  * v2.0.0 or later
163
172
  */
164
- selectorTarget && (0, match_selector_1.matchSelector)(selectorTarget, selector);
165
- if (!matched) {
173
+ (0, match_selector_1.matchSelector)(selectorTarget, selector);
174
+ if (!matches.matched) {
166
175
  return;
167
176
  }
168
- docLog('Matched nodeRule: <%s>(%s)', 'nodeName' in node ? node.nodeName : node.type, matched.__node || 'No Selector');
177
+ if (docLog.enabled) {
178
+ docLog('Matched nodeRule: <%s>(%s)', 'nodeName' in node ? node.nodeName : node.type, matches.selector || '*');
179
+ }
169
180
  const ruleList = Object.keys(nodeRule.rules);
170
181
  for (const ruleName of ruleList) {
171
182
  const rule = nodeRule.rules[ruleName];
172
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
183
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
173
184
  if (convertedRule === undefined) {
174
185
  continue;
175
186
  }
@@ -178,7 +189,7 @@ class MLDOMDocument {
178
189
  ruleLog('↑ nodeRule (%s): %O', ruleName, mergedRule);
179
190
  ruleMapper.set(node, ruleName, {
180
191
  from: 'nodeRules',
181
- index: i,
192
+ specificity: matches.specificity,
182
193
  rule: mergedRule,
183
194
  });
184
195
  }
@@ -202,15 +213,17 @@ class MLDOMDocument {
202
213
  if (!selector) {
203
214
  return;
204
215
  }
205
- const matched = (0, match_selector_1.matchSelector)(selectorTarget, selector);
206
- if (!matched) {
216
+ const matches = (0, match_selector_1.matchSelector)(selectorTarget, selector);
217
+ if (!matches.matched) {
207
218
  return;
208
219
  }
209
- docLog('Matched childNodeRule: <%s>(%s), inheritance: %o', selectorTarget.nodeName, matched.__node || 'No Selector', !!nodeRule.inheritance);
220
+ if (docLog.enabled) {
221
+ docLog('Matched childNodeRule: <%s>(%s), inheritance: %o', selectorTarget.nodeName, matches.selector || '*', !!nodeRule.inheritance);
222
+ }
210
223
  const targetDescendants = nodeRule.inheritance ? descendants : children;
211
224
  Object.keys(nodeRuleRules).forEach(ruleName => {
212
225
  const rule = nodeRuleRules[ruleName];
213
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
226
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
214
227
  if (convertedRule === undefined) {
215
228
  return;
216
229
  }
@@ -220,7 +233,7 @@ class MLDOMDocument {
220
233
  targetDescendants.forEach(descendant => {
221
234
  ruleMapper.set(descendant, ruleName, {
222
235
  from: 'childNodeRules',
223
- index: i,
236
+ specificity: matches.specificity,
224
237
  rule: mergedRule,
225
238
  });
226
239
  });
@@ -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,113 +47,86 @@ 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) {
48
59
  // Descendant combinator
49
60
  case ' ': {
50
- let ancestor = el.parentNode;
61
+ let ancestor = el.getParentElement();
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
- ancestor = ancestor.parentNode;
67
+ ancestor = ancestor.getParentElement();
62
68
  }
63
- return null;
69
+ return {
70
+ matched: false,
71
+ };
64
72
  }
65
73
  // Child combinator
66
74
  case '>': {
67
- if (!el.parentNode) {
68
- return null;
75
+ const parentNode = el.getParentElement();
76
+ if (!parentNode) {
77
+ return { matched: false };
69
78
  }
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;
79
+ const matches = target.match(parentNode);
80
+ if (matches.matched) {
81
+ return mergeMatches(matches, unitCheck, ' > ');
78
82
  }
79
- return null;
83
+ return { matched: false };
80
84
  }
81
85
  // Next-sibling combinator
82
86
  case '+': {
83
87
  if (!el.previousElementSibling) {
84
- return null;
88
+ return { matched: false };
85
89
  }
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;
90
+ const matches = target.match(el.previousElementSibling);
91
+ if (matches.matched) {
92
+ return mergeMatches(matches, unitCheck, ' + ');
94
93
  }
95
- return null;
94
+ return { matched: false };
96
95
  }
97
96
  // Subsequent-sibling combinator
98
97
  case '~': {
99
98
  let prev = el.previousElementSibling;
100
99
  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;
100
+ const matches = target.match(prev);
101
+ if (matches.matched) {
102
+ return mergeMatches(matches, unitCheck, ' ~ ');
109
103
  }
110
104
  prev = prev.previousElementSibling;
111
105
  }
112
- return null;
106
+ return { matched: false };
113
107
  }
114
108
  // Prev-sibling combinator
115
109
  case ':has(+)': {
116
110
  if (!el.nextElementSibling) {
117
- return null;
111
+ return { matched: false };
118
112
  }
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;
113
+ const matches = target.match(el.nextElementSibling);
114
+ if (matches.matched) {
115
+ return mergeMatches(matches, unitCheck, ':has(+ ', true);
127
116
  }
128
- return null;
117
+ return { matched: false };
129
118
  }
130
119
  // Subsequent-sibling (in front) combinator
131
120
  case ':has(~)': {
132
121
  let next = el.nextElementSibling;
133
122
  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;
123
+ const matches = target.match(next);
124
+ if (matches.matched) {
125
+ return mergeMatches(matches, unitCheck, ':has(~ ', true);
142
126
  }
143
127
  next = next.nextElementSibling;
144
128
  }
145
- return null;
129
+ return { matched: false };
146
130
  }
147
131
  default: {
148
132
  throw new Error(`Unsupported ${this._combinatedFrom.combinator} combinator in selector`);
@@ -157,18 +141,25 @@ class SelectorTarget {
157
141
  }
158
142
  }
159
143
  function uncombinatedRegexSelect(el, selector) {
160
- let matchedMap = {};
144
+ let matched = true;
145
+ let data = {};
146
+ let tagSelector = '';
147
+ const specificity = [0, 0, 0];
148
+ const specifiedAttr = new Map();
161
149
  if (selector.nodeName) {
162
150
  const matchedNodeName = (0, ml_config_1.regexSelectorMatches)(selector.nodeName, el.nodeName);
163
- if (!matchedNodeName) {
164
- return null;
151
+ if (matchedNodeName) {
152
+ delete matchedNodeName.$0;
165
153
  }
166
- delete matchedNodeName.$0;
167
- matchedMap = {
168
- ...matchedMap,
154
+ else {
155
+ matched = false;
156
+ }
157
+ data = {
158
+ ...data,
169
159
  ...matchedNodeName,
170
- __node: el.nodeName,
171
160
  };
161
+ tagSelector = el.nodeName;
162
+ specificity[2] = 1;
172
163
  }
173
164
  if (selector.attrName) {
174
165
  const selectorAttrName = selector.attrName;
@@ -177,16 +168,16 @@ function uncombinatedRegexSelect(el, selector) {
177
168
  const matchedAttrName = (0, ml_config_1.regexSelectorMatches)(selectorAttrName, attrName);
178
169
  if (matchedAttrName) {
179
170
  delete matchedAttrName.$0;
180
- matchedMap = {
181
- ...matchedMap,
171
+ data = {
172
+ ...data,
182
173
  ...matchedAttrName,
183
- __node: matchedMap.__node ? `${matchedMap.__node}[${attrName}]` : `[${attrName}]`,
184
174
  };
175
+ specifiedAttr.set(attrName, '');
185
176
  }
186
177
  return matchedAttrName;
187
178
  });
188
179
  if (!matchedAttrNameList.some(_ => !!_)) {
189
- return null;
180
+ matched = false;
190
181
  }
191
182
  }
192
183
  if (selector.attrValue) {
@@ -197,19 +188,46 @@ function uncombinatedRegexSelect(el, selector) {
197
188
  const matchedAttrValue = (0, ml_config_1.regexSelectorMatches)(selectorAttrValue, attrValue);
198
189
  if (matchedAttrValue) {
199
190
  delete matchedAttrValue.$0;
200
- matchedMap = {
201
- ...matchedMap,
191
+ data = {
192
+ ...data,
202
193
  ...matchedAttrValue,
203
- __node: matchedMap.__node
204
- ? `${matchedMap.__node}[${attrName}="${attrValue}"]`
205
- : `[${attrName}="${attrValue}"]`,
206
194
  };
195
+ specifiedAttr.set(attrName, attrValue);
207
196
  }
208
197
  return matchedAttrValue;
209
198
  });
210
199
  if (!matchedAttrValueList.some(_ => !!_)) {
211
- return null;
200
+ matched = false;
212
201
  }
213
202
  }
214
- return matchedMap;
203
+ const attrSelector = Array.from(specifiedAttr.entries())
204
+ .map(([name, value]) => {
205
+ return `[${name}${value ? `="${value}"` : ''}]`;
206
+ })
207
+ .join('');
208
+ specificity[1] += specifiedAttr.size;
209
+ if (matched) {
210
+ return {
211
+ matched,
212
+ selector: `${tagSelector}${attrSelector}`,
213
+ specificity,
214
+ data,
215
+ };
216
+ }
217
+ return { matched };
218
+ }
219
+ function mergeMatches(a, b, sep, close = false) {
220
+ return {
221
+ matched: true,
222
+ selector: `${a.selector}${sep}${b.selector}${close ? ')' : ''}`,
223
+ specificity: [
224
+ a.specificity[0] + b.specificity[0],
225
+ a.specificity[1] + b.specificity[1],
226
+ a.specificity[2] + b.specificity[2],
227
+ ],
228
+ data: {
229
+ ...a.data,
230
+ ...b.data,
231
+ },
232
+ };
215
233
  }
@@ -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 {};