@markuplint/ml-core 2.0.0-rc.0 → 2.0.0-rc.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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { RuleInfo, RuleConfig, RuleConfigValue } from '@markuplint/ml-config';
2
+ export { getAttrSpecs } from '@markuplint/ml-spec';
2
3
  export * from './convert-ruleset';
3
4
  export * from './ml-core';
4
5
  export * from './ml-rule';
package/lib/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.enableDebug = exports.getIndent = exports.Ruleset = exports.MLParseError = void 0;
3
+ exports.enableDebug = exports.getIndent = exports.Ruleset = exports.MLParseError = exports.getAttrSpecs = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ var ml_spec_1 = require("@markuplint/ml-spec");
6
+ Object.defineProperty(exports, "getAttrSpecs", { enumerable: true, get: function () { return ml_spec_1.getAttrSpecs; } });
5
7
  (0, tslib_1.__exportStar)(require("./convert-ruleset"), exports);
6
8
  (0, tslib_1.__exportStar)(require("./ml-core"), exports);
7
9
  (0, tslib_1.__exportStar)(require("./ml-rule"), exports);
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
@@ -58,8 +58,8 @@ class MLCore {
58
58
  return violations;
59
59
  }
60
60
  for (const rule of (0, tslib_1.__classPrivateFieldGet)(this, _MLCore_rules, "f")) {
61
- const ruleInfo = rule.optimizeOption((0, tslib_1.__classPrivateFieldGet)(this, _MLCore_ruleset, "f").rules[rule.name] || false);
62
- if (ruleInfo.disabled) {
61
+ const ruleInfo = rule.getRuleInfo((0, tslib_1.__classPrivateFieldGet)(this, _MLCore_ruleset, "f"), rule.name);
62
+ if (ruleInfo.disabled && ruleInfo.nodeRules.length === 0 && ruleInfo.childNodeRules.length === 0) {
63
63
  continue;
64
64
  }
65
65
  (0, debug_1.log)('%s Rule: verify', rule.name);
@@ -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
  }
@@ -129,51 +129,58 @@ class MLDOMDocument {
129
129
  const ruleMapper = new rule_mapper_1.RuleMapper(this.nodeList);
130
130
  // add rules to node
131
131
  for (const node of this.nodeList) {
132
- docLog('Add rules to node <%s%s>', node.type === 'ElementCloseTag' ? '/' : '', 'nodeName' in node ? node.nodeName : `#${node.type}`);
132
+ if (node.type === 'ElementCloseTag') {
133
+ continue;
134
+ }
135
+ if (docLog.enabled) {
136
+ docLog('Add rules to node <%s>', 'nodeName' in node ? node.nodeName : `#${node.type}`);
137
+ }
133
138
  // global rules
134
139
  Object.keys(ruleset.rules).forEach(ruleName => {
135
140
  const rule = ruleset.rules[ruleName];
136
141
  ruleMapper.set(node, ruleName, {
137
142
  from: 'rules',
138
- index: -1,
143
+ specificity: [0, 0, 0],
139
144
  rule,
140
145
  });
141
146
  });
142
- if (node.type !== 'Element' &&
143
- node.type !== 'OmittedElement' &&
144
- node.type !== 'ElementCloseTag' &&
145
- node.type !== 'Text') {
147
+ if (node.type !== 'Element' && node.type !== 'OmittedElement' && node.type !== 'Text') {
146
148
  continue;
147
149
  }
148
- const selectorTarget = node.type === 'Element' || node.type === 'OmittedElement'
149
- ? node
150
- : node.type === 'ElementCloseTag'
151
- ? node.startTag
152
- : null;
150
+ const selectorTarget = node.type === 'Element' || node.type === 'OmittedElement' ? node : null;
153
151
  // node specs and special rules for node by selector
154
152
  ruleset.nodeRules.forEach((nodeRule, i) => {
155
153
  if (!nodeRule.rules) {
156
154
  return;
157
155
  }
156
+ if (!selectorTarget) {
157
+ return;
158
+ }
158
159
  const selector = nodeRule.selector || nodeRule.regexSelector || nodeRule.tagName;
159
- const matched =
160
+ const matches =
160
161
  /**
161
162
  * Forward v1.x compatibility
162
163
  */
163
164
  nodeRule.tagName && /^#text$/i.test(nodeRule.tagName) && node.type === 'Text'
164
- ? { __node: '#text' }
165
+ ? {
166
+ matched: true,
167
+ selector: '#text',
168
+ specificity: [0, 0, 0],
169
+ }
165
170
  : /**
166
171
  * v2.0.0 or later
167
172
  */
168
- selectorTarget && (0, match_selector_1.matchSelector)(selectorTarget, selector);
169
- if (!matched) {
173
+ (0, match_selector_1.matchSelector)(selectorTarget, selector);
174
+ if (!matches.matched) {
170
175
  return;
171
176
  }
172
- docLog('Matched nodeRule: <%s%s>(%s)', node.type === 'ElementCloseTag' ? '/' : '', '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
+ }
173
180
  const ruleList = Object.keys(nodeRule.rules);
174
181
  for (const ruleName of ruleList) {
175
182
  const rule = nodeRule.rules[ruleName];
176
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
183
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
177
184
  if (convertedRule === undefined) {
178
185
  continue;
179
186
  }
@@ -182,7 +189,7 @@ class MLDOMDocument {
182
189
  ruleLog('↑ nodeRule (%s): %O', ruleName, mergedRule);
183
190
  ruleMapper.set(node, ruleName, {
184
191
  from: 'nodeRules',
185
- index: i,
192
+ specificity: matches.specificity,
186
193
  rule: mergedRule,
187
194
  });
188
195
  }
@@ -206,15 +213,17 @@ class MLDOMDocument {
206
213
  if (!selector) {
207
214
  return;
208
215
  }
209
- const matched = (0, match_selector_1.matchSelector)(selectorTarget, selector);
210
- if (!matched) {
216
+ const matches = (0, match_selector_1.matchSelector)(selectorTarget, selector);
217
+ if (!matches.matched) {
211
218
  return;
212
219
  }
213
- docLog('Matched childNodeRule: <%s%s>(%s), inheritance: %o', node.type === 'ElementCloseTag' ? '/' : '', 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
+ }
214
223
  const targetDescendants = nodeRule.inheritance ? descendants : children;
215
224
  Object.keys(nodeRuleRules).forEach(ruleName => {
216
225
  const rule = nodeRuleRules[ruleName];
217
- const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matched);
226
+ const convertedRule = (0, ml_config_1.exchangeValueOnRule)(rule, matches.data || {});
218
227
  if (convertedRule === undefined) {
219
228
  return;
220
229
  }
@@ -224,7 +233,7 @@ class MLDOMDocument {
224
233
  targetDescendants.forEach(descendant => {
225
234
  ruleMapper.set(descendant, ruleName, {
226
235
  from: 'childNodeRules',
227
- index: i,
236
+ specificity: matches.specificity,
228
237
  rule: mergedRule,
229
238
  });
230
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 {};