@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.
@@ -1,20 +1,51 @@
1
1
  "use strict";
2
2
  var _Selector_ruleset, _Ruleset_selectorGroup, _StructuredSelector_edge, _StructuredSelector_selector, _SelectorTarget_isAdded, _SelectorTarget_combinatedFrom;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.createSelector = void 0;
4
+ exports.compareSpecificity = exports.createSelector = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const postcss_selector_parser_1 = (0, tslib_1.__importDefault)(require("postcss-selector-parser"));
7
+ const debug_1 = require("../../debug");
8
+ const log = debug_1.log.extend('selector');
9
+ const resLog = log.extend('result');
7
10
  function createSelector(selector) {
8
11
  return new Selector(selector);
9
12
  }
10
13
  exports.createSelector = createSelector;
14
+ function compareSpecificity(a, b) {
15
+ if (a[0] < b[0]) {
16
+ return -1;
17
+ }
18
+ else if (a[0] > b[0]) {
19
+ return 1;
20
+ }
21
+ else if (a[1] < b[1]) {
22
+ return -1;
23
+ }
24
+ else if (a[1] > b[1]) {
25
+ return 1;
26
+ }
27
+ else if (a[2] < b[2]) {
28
+ return -1;
29
+ }
30
+ else if (a[2] > b[2]) {
31
+ return 1;
32
+ }
33
+ return 0;
34
+ }
35
+ exports.compareSpecificity = compareSpecificity;
11
36
  class Selector {
12
37
  constructor(selector) {
13
38
  _Selector_ruleset.set(this, void 0);
14
39
  (0, tslib_1.__classPrivateFieldSet)(this, _Selector_ruleset, Ruleset.parse(selector), "f");
15
40
  }
16
41
  match(el, caller = el) {
17
- return (0, tslib_1.__classPrivateFieldGet)(this, _Selector_ruleset, "f").match(el, caller);
42
+ const results = (0, tslib_1.__classPrivateFieldGet)(this, _Selector_ruleset, "f").match(el, caller);
43
+ for (const result of results) {
44
+ if (result.matched) {
45
+ return result.specificity;
46
+ }
47
+ }
48
+ return false;
18
49
  }
19
50
  }
20
51
  _Selector_ruleset = new WeakMap();
@@ -31,7 +62,12 @@ class Ruleset {
31
62
  return new Ruleset(selectors);
32
63
  }
33
64
  match(el, caller) {
34
- return (0, tslib_1.__classPrivateFieldGet)(this, _Ruleset_selectorGroup, "f").some(selector => selector.match(el, caller));
65
+ log('%s', el.raw);
66
+ return (0, tslib_1.__classPrivateFieldGet)(this, _Ruleset_selectorGroup, "f").map(selector => {
67
+ const res = selector.match(el, caller);
68
+ resLog('%s => %o', selector.selector, res);
69
+ return res;
70
+ });
35
71
  }
36
72
  }
37
73
  _Ruleset_selectorGroup = new WeakMap();
@@ -65,6 +101,9 @@ class StructuredSelector {
65
101
  }
66
102
  });
67
103
  }
104
+ get selector() {
105
+ return (0, tslib_1.__classPrivateFieldGet)(this, _StructuredSelector_selector, "f").nodes.join('');
106
+ }
68
107
  match(el, caller) {
69
108
  return (0, tslib_1.__classPrivateFieldGet)(this, _StructuredSelector_edge, "f").match(el, caller);
70
109
  }
@@ -81,60 +120,124 @@ class SelectorTarget {
81
120
  _SelectorTarget_combinatedFrom.set(this, null);
82
121
  }
83
122
  match(el, caller) {
84
- const matched = this._matchWithoutCombinateChecking(el, caller);
85
- if (!matched) {
86
- return false;
123
+ const unitCheck = this._matchWithoutCombinateChecking(el, caller);
124
+ if (!unitCheck.matched) {
125
+ return unitCheck;
87
126
  }
88
127
  if (!(0, tslib_1.__classPrivateFieldGet)(this, _SelectorTarget_combinatedFrom, "f")) {
89
- return true;
128
+ return unitCheck;
90
129
  }
91
130
  const { target, combinator } = (0, tslib_1.__classPrivateFieldGet)(this, _SelectorTarget_combinatedFrom, "f");
92
131
  switch (combinator.value) {
93
132
  // Descendant combinator
94
133
  case ' ': {
95
134
  let ancestor = el.parentNode;
135
+ let matched = false;
136
+ let specificity;
96
137
  while (ancestor) {
97
- const ancestorMatched = target._matchWithoutCombinateChecking(ancestor, caller);
98
- if (ancestorMatched) {
99
- return target.match(ancestor, caller);
138
+ const res = target.match(ancestor, caller);
139
+ if (!specificity) {
140
+ specificity = [
141
+ unitCheck.specificity[0] + res.specificity[0],
142
+ unitCheck.specificity[1] + res.specificity[1],
143
+ unitCheck.specificity[2] + res.specificity[2],
144
+ ];
145
+ }
146
+ if (res.matched) {
147
+ matched = true;
100
148
  }
101
149
  ancestor = ancestor.parentNode;
102
150
  }
103
- return false;
151
+ if (!specificity) {
152
+ const res = target.match(el, caller);
153
+ specificity = [
154
+ unitCheck.specificity[0] + res.specificity[0],
155
+ unitCheck.specificity[1] + res.specificity[1],
156
+ unitCheck.specificity[2] + res.specificity[2],
157
+ ];
158
+ }
159
+ return {
160
+ specificity,
161
+ matched,
162
+ };
104
163
  }
105
164
  // Child combinator
106
165
  case '>': {
107
- if (!el.parentNode) {
108
- return false;
166
+ let matched;
167
+ const specificity = unitCheck.specificity;
168
+ if (el.parentNode) {
169
+ const res = target.match(el.parentNode, caller);
170
+ specificity[0] += res.specificity[0];
171
+ specificity[1] += res.specificity[1];
172
+ specificity[2] += res.specificity[2];
173
+ matched = res.matched;
109
174
  }
110
- const parentMatched = target._matchWithoutCombinateChecking(el.parentNode, caller);
111
- if (parentMatched) {
112
- return target.match(el.parentNode, caller);
175
+ else {
176
+ const res = target.match(el, caller);
177
+ specificity[0] += res.specificity[0];
178
+ specificity[1] += res.specificity[1];
179
+ specificity[2] += res.specificity[2];
180
+ matched = false;
113
181
  }
114
- return false;
182
+ return {
183
+ specificity,
184
+ matched,
185
+ };
115
186
  }
116
187
  // Next-sibling combinator
117
188
  case '+': {
118
- if (!el.previousElementSibling) {
119
- return false;
189
+ let matched;
190
+ const specificity = unitCheck.specificity;
191
+ if (el.previousElementSibling) {
192
+ const res = target.match(el.previousElementSibling, caller);
193
+ specificity[0] += res.specificity[0];
194
+ specificity[1] += res.specificity[1];
195
+ specificity[2] += res.specificity[2];
196
+ matched = res.matched;
120
197
  }
121
- const prevMatched = target._matchWithoutCombinateChecking(el.previousElementSibling, caller);
122
- if (prevMatched) {
123
- return target.match(el.previousElementSibling, caller);
198
+ else {
199
+ const res = target.match(el, caller);
200
+ specificity[0] += res.specificity[0];
201
+ specificity[1] += res.specificity[1];
202
+ specificity[2] += res.specificity[2];
203
+ matched = false;
124
204
  }
125
- return false;
205
+ return {
206
+ specificity,
207
+ matched,
208
+ };
126
209
  }
127
- // Subsequent-sibling combinator
210
+ // // Subsequent-sibling combinator
128
211
  case '~': {
129
212
  let prev = el.previousElementSibling;
213
+ let matched = false;
214
+ let specificity;
130
215
  while (prev) {
131
- const prevMatched = target._matchWithoutCombinateChecking(prev, caller);
132
- if (prevMatched) {
133
- return target.match(prev, caller);
216
+ const res = target.match(prev, caller);
217
+ if (!specificity) {
218
+ specificity = [
219
+ unitCheck.specificity[0] + res.specificity[0],
220
+ unitCheck.specificity[1] + res.specificity[1],
221
+ unitCheck.specificity[2] + res.specificity[2],
222
+ ];
223
+ }
224
+ if (res.matched) {
225
+ matched = true;
134
226
  }
135
227
  prev = prev.previousElementSibling;
136
228
  }
137
- return false;
229
+ if (!specificity) {
230
+ const res = target.match(el, caller);
231
+ specificity = [
232
+ unitCheck.specificity[0] + res.specificity[0],
233
+ unitCheck.specificity[1] + res.specificity[1],
234
+ unitCheck.specificity[2] + res.specificity[2],
235
+ ];
236
+ }
237
+ return {
238
+ specificity,
239
+ matched,
240
+ };
138
241
  }
139
242
  // Column combinator
140
243
  case '||': {
@@ -146,27 +249,42 @@ class SelectorTarget {
146
249
  }
147
250
  }
148
251
  _matchWithoutCombinateChecking(el, caller) {
149
- if (!(0, tslib_1.__classPrivateFieldGet)(this, _SelectorTarget_isAdded, "f")) {
150
- return isScope(el, caller);
151
- }
152
- if (this.tag && this.tag.type === 'tag') {
153
- if (this.tag.value.toLowerCase() !== el.nodeName.toLowerCase()) {
154
- return false;
155
- }
252
+ const specificity = [0, 0, 0];
253
+ let matched = true;
254
+ if (!(0, tslib_1.__classPrivateFieldGet)(this, _SelectorTarget_isAdded, "f") && !isScope(el, caller)) {
255
+ matched = false;
156
256
  }
157
257
  if (!this.id.every(id => id.value === el.id)) {
158
- return false;
258
+ matched = false;
159
259
  }
260
+ specificity[0] += this.id.length;
160
261
  if (!this.class.every(className => el.classList.includes(className.value))) {
161
- return false;
262
+ matched = false;
162
263
  }
264
+ specificity[1] += this.class.length;
163
265
  if (!this.attr.every(attr => attrMatch(attr, el))) {
164
- return false;
266
+ matched = false;
165
267
  }
166
- if (!this.pseudo.every(pseudo => pseudoMatch(pseudo, el, caller))) {
167
- return false;
268
+ specificity[1] += this.attr.length;
269
+ for (const pseudo of this.pseudo) {
270
+ const pseudoRes = pseudoMatch(pseudo, el, caller);
271
+ specificity[0] += pseudoRes.specificity[0];
272
+ specificity[1] += pseudoRes.specificity[1];
273
+ specificity[2] += pseudoRes.specificity[2];
274
+ if (!pseudoRes.matched) {
275
+ matched = false;
276
+ }
168
277
  }
169
- return true;
278
+ if (this.tag && this.tag.type === 'tag') {
279
+ specificity[2] += 1;
280
+ if (this.tag.value.toLowerCase() !== el.nodeName.toLowerCase()) {
281
+ matched = false;
282
+ }
283
+ }
284
+ return {
285
+ specificity,
286
+ matched,
287
+ };
170
288
  }
171
289
  add(selector) {
172
290
  (0, tslib_1.__classPrivateFieldSet)(this, _SelectorTarget_isAdded, true, "f");
@@ -260,51 +378,87 @@ function pseudoMatch(pseudo, el, caller) {
260
378
  */
261
379
  case ':closest': {
262
380
  const ruleset = new Ruleset(pseudo.nodes);
381
+ const specificity = getSpecificity(ruleset.match(el, caller));
263
382
  let parent = el.parentNode;
264
383
  while (parent) {
265
- if (ruleset.match(parent, caller)) {
266
- return true;
384
+ if (ruleset.match(parent, caller).some(r => r.matched)) {
385
+ return {
386
+ specificity,
387
+ matched: true,
388
+ };
267
389
  }
268
390
  parent = parent.parentNode;
269
391
  }
270
- return false;
392
+ return {
393
+ specificity,
394
+ matched: false,
395
+ };
271
396
  }
272
397
  /**
273
398
  * Below, Selector Level 4
274
399
  */
275
400
  case ':not': {
276
401
  const ruleset = new Ruleset(pseudo.nodes);
277
- if (ruleset.match(el, caller)) {
278
- return false;
279
- }
280
- break;
402
+ const resList = ruleset.match(el, caller);
403
+ const specificity = getSpecificity(resList);
404
+ const matched = resList.every(r => !r.matched);
405
+ return {
406
+ specificity,
407
+ matched,
408
+ };
281
409
  }
282
410
  case ':is': {
283
411
  const ruleset = new Ruleset(pseudo.nodes);
284
- if (!ruleset.match(el, caller)) {
285
- return false;
286
- }
287
- break;
412
+ const resList = ruleset.match(el, caller);
413
+ const specificity = getSpecificity(resList);
414
+ const matched = resList.some(r => r.matched);
415
+ return {
416
+ specificity,
417
+ matched,
418
+ };
288
419
  }
289
420
  case ':has': {
290
421
  const ruleset = new Ruleset(pseudo.nodes);
422
+ const specificity = getSpecificity(ruleset.match(el, caller));
291
423
  const descendants = getDescendants(el);
292
- if (!descendants.some(desc => ruleset.match(desc, caller))) {
293
- return false;
294
- }
295
- break;
424
+ const matched = descendants.some(desc => ruleset.match(desc, caller).some(m => m.matched));
425
+ return {
426
+ specificity,
427
+ matched,
428
+ };
429
+ }
430
+ case ':where': {
431
+ const ruleset = new Ruleset(pseudo.nodes);
432
+ const resList = ruleset.match(el, caller);
433
+ const matched = resList.some(r => r.matched);
434
+ return {
435
+ specificity: [0, 0, 0],
436
+ matched,
437
+ };
296
438
  }
297
439
  case ':scope': {
298
440
  if (!isScope(el, caller)) {
299
- return false;
441
+ return {
442
+ specificity: [0, 1, 0],
443
+ matched: false,
444
+ };
300
445
  }
301
- break;
446
+ return {
447
+ specificity: [0, 1, 0],
448
+ matched: true,
449
+ };
302
450
  }
303
451
  case ':root': {
304
452
  if (!(!el.isInFragmentDocument && el.parentNode === null)) {
305
- return false;
453
+ return {
454
+ specificity: [0, 1, 0],
455
+ matched: false,
456
+ };
306
457
  }
307
- break;
458
+ return {
459
+ specificity: [0, 1, 0],
460
+ matched: true,
461
+ };
308
462
  }
309
463
  case ':enable':
310
464
  case ':disable':
@@ -337,7 +491,6 @@ function pseudoMatch(pseudo, el, caller) {
337
491
  case ':nth-col': {
338
492
  throw new Error(`Unsupported pseudo ${pseudo.toString()} selector yet. If you want it, please request it as the issue (https://github.com/markuplint/markuplint/issues/new).`);
339
493
  }
340
- case ':where':
341
494
  case ':dir':
342
495
  case ':lang':
343
496
  case ':any-link':
@@ -360,7 +513,6 @@ function pseudoMatch(pseudo, el, caller) {
360
513
  throw new Error(`Unsupported pseudo ${pseudo.toString()} selector.`);
361
514
  }
362
515
  }
363
- return true;
364
516
  }
365
517
  function isScope(el, caller) {
366
518
  return el.uuid === (caller === null || caller === void 0 ? void 0 : caller.uuid) || (!el.isInFragmentDocument && el.parentNode === null);
@@ -368,3 +520,21 @@ function isScope(el, caller) {
368
520
  function getDescendants(el, includeSelf = false) {
369
521
  return [...el.children.map(child => getDescendants(child, true)).flat(), ...(includeSelf ? [el] : [])];
370
522
  }
523
+ function getSpecificity(result) {
524
+ let specificity;
525
+ for (const res of result) {
526
+ if (specificity) {
527
+ const order = compareSpecificity(specificity, res.specificity);
528
+ if (order === -1) {
529
+ specificity = res.specificity;
530
+ }
531
+ }
532
+ else {
533
+ specificity = res.specificity;
534
+ }
535
+ }
536
+ if (!specificity) {
537
+ throw new Error('Result is empty');
538
+ }
539
+ return specificity;
540
+ }
@@ -1,9 +1,10 @@
1
+ import type { Specificity } from './helper/selector';
1
2
  import type { AnonymousNode } from './types';
2
3
  import type { AnyRule } from '@markuplint/ml-config';
3
4
  declare type RuleType = 'rules' | 'nodeRules' | 'childNodeRules';
4
5
  declare type MappingLayer = {
5
6
  from: RuleType;
6
- index: number;
7
+ specificity: Specificity;
7
8
  rule: AnyRule;
8
9
  };
9
10
  export declare class RuleMapper<N extends AnonymousNode<any, any> = AnonymousNode<any, any>> {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.RuleMapper = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const debug_1 = require("../debug");
7
+ const selector_1 = require("./helper/selector");
7
8
  const ruleMapperLog = debug_1.log.extend('rule-mapper');
8
9
  const ruleMapperNodeLog = ruleMapperLog.extend('node');
9
10
  const ruleMapperNodeRuleLog = ruleMapperNodeLog.extend('rule');
@@ -20,13 +21,12 @@ class RuleMapper {
20
21
  const rules = (0, tslib_1.__classPrivateFieldGet)(this, _RuleMapper_ruleMap, "f").get(node.uuid) || {};
21
22
  const currentRule = rules[ruleName];
22
23
  if (currentRule) {
23
- if (currentRule.index <= rule.index) {
24
- ruleMapperLog('Unset %o from %s', currentRule, node);
25
- }
26
- else {
27
- ruleMapperLog("Don't set %o (%d vs %d)", rule, currentRule.index, rule.index);
24
+ const order = (0, selector_1.compareSpecificity)(currentRule.specificity, rule.specificity);
25
+ if (order === 1) {
26
+ ruleMapperLog("Don't set %o ([%s] vs [%s])", rule, currentRule.specificity, rule.specificity);
28
27
  return;
29
28
  }
29
+ ruleMapperLog('Unset %o from %s', currentRule, node);
30
30
  }
31
31
  rules[ruleName] = rule;
32
32
  (0, tslib_1.__classPrivateFieldGet)(this, _RuleMapper_ruleMap, "f").set(node.uuid, rules);
@@ -42,13 +42,13 @@ class RuleMapper {
42
42
  if (!rules) {
43
43
  return;
44
44
  }
45
- const shash = node.type === 'ElementCloseTag' ? '/' : '';
45
+ const slash = node.type === 'ElementCloseTag' ? '/' : '';
46
46
  const nodeName = 'nodeName' in node ? node.nodeName : `#${node.type}`;
47
- ruleMapperNodeLog('<%s%s>', shash, nodeName);
47
+ ruleMapperNodeLog('<%s%s>', slash, nodeName);
48
48
  Object.keys(rules).forEach(ruleName => {
49
49
  const rule = rules[ruleName];
50
50
  node.rules[ruleName] = rule.rule;
51
- ruleMapperNodeRuleLog('[from: %s(%d)] %s: %o', rule.from, rule.index, ruleName, rule.rule);
51
+ ruleMapperNodeRuleLog('[from: %s(%s)] %s: %o', rule.from, rule.specificity, ruleName, rule.rule);
52
52
  });
53
53
  });
54
54
  }
@@ -145,7 +145,7 @@ class MLDOMAbstractElement extends node_1.default {
145
145
  return !!this.getAttributeToken(attrName).length;
146
146
  }
147
147
  matches(selector) {
148
- return (0, helper_1.createSelector)(selector).match(this);
148
+ return !!(0, helper_1.createSelector)(selector).match(this);
149
149
  }
150
150
  fixNodeName(name) {
151
151
  (0, tslib_1.__classPrivateFieldSet)(this, _MLDOMAbstractElement_fixedNodeName, name, "f");
@@ -28,19 +28,19 @@
28
28
  },
29
29
  "nodeRules": [
30
30
  {
31
- "selector": "html",
31
+ "selector": ":where(html)",
32
32
  "rules": {
33
33
  "required-attr": ["lang"]
34
34
  }
35
35
  },
36
36
  {
37
- "selector": "script[src]",
37
+ "selector": ":where(script[src])",
38
38
  "rules": {
39
39
  "required-attr": ["defer"]
40
40
  }
41
41
  },
42
42
  {
43
- "selector": "img",
43
+ "selector": ":where(img)",
44
44
  "rules": {
45
45
  "required-attr": [
46
46
  "width",
@@ -54,7 +54,7 @@
54
54
  }
55
55
  },
56
56
  {
57
- "selector": "iframe",
57
+ "selector": ":where(iframe)",
58
58
  "rules": {
59
59
  "required-attr": [
60
60
  "title",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  {
69
- "selector": "a[target=_blank], area[target=_blank]",
69
+ "selector": ":where(a[target=_blank], area[target=_blank])",
70
70
  "rules": {
71
71
  "required-attr": [
72
72
  {
@@ -77,19 +77,19 @@
77
77
  }
78
78
  },
79
79
  {
80
- "selector": "abbr",
80
+ "selector": ":where(abbr)",
81
81
  "rules": {
82
82
  "required-attr": ["title"]
83
83
  }
84
84
  },
85
85
  {
86
- "selector": "video[autoplay]",
86
+ "selector": ":where(video[autoplay])",
87
87
  "rules": {
88
88
  "required-attr": ["muted"]
89
89
  }
90
90
  },
91
91
  {
92
- "selector": "th, td",
92
+ "selector": ":where(th, td)",
93
93
  "rules": {
94
94
  "invalid-attr": {
95
95
  "option": {
@@ -103,7 +103,7 @@
103
103
  }
104
104
  },
105
105
  {
106
- "selector": ":not(button)",
106
+ "selector": ":where(:not(button))",
107
107
  "rules": {
108
108
  "invalid-attr": {
109
109
  "option": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/ml-core",
3
- "version": "2.0.0-dev.20211213.0",
3
+ "version": "2.0.0-dev.20220105.1",
4
4
  "description": "The core module of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -17,17 +17,17 @@
17
17
  "clean": "tsc --build --clean"
18
18
  },
19
19
  "dependencies": {
20
- "@markuplint/i18n": "2.0.0-dev.20211213.0",
21
- "@markuplint/ml-ast": "2.0.0-dev.20211115.1",
22
- "@markuplint/ml-config": "2.0.0-dev.2021116.1",
23
- "@markuplint/ml-spec": "2.0.0-dev.20211213.0",
20
+ "@markuplint/i18n": "2.0.0-dev.20220105.1",
21
+ "@markuplint/ml-ast": "2.0.0-dev.20211115.3",
22
+ "@markuplint/ml-config": "2.0.0-dev.2021116.3",
23
+ "@markuplint/ml-spec": "2.0.0-dev.20211227.1",
24
24
  "debug": "^4.3.2",
25
25
  "postcss-selector-parser": "^6.0.6",
26
26
  "tslib": "^2.3.1"
27
27
  },
28
28
  "devDependencies": {
29
- "@markuplint/html-parser": "2.0.0-dev.20211213.0",
29
+ "@markuplint/html-parser": "2.0.0-dev.20220105.1",
30
30
  "@types/debug": "^4.1.7"
31
31
  },
32
- "gitHead": "8f4682ca3e0937082af8c39b999443afa4a1aef3"
32
+ "gitHead": "73fab2765c080b0a42e71bdc1eed09bae0ed360e"
33
33
  }