@markuplint/ml-core 2.0.0-dev.20211213.1 → 2.0.0-dev.20220106.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,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 {};