@markuplint/rules 4.0.0-alpha.1 → 4.0.0-alpha.10

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.
Files changed (56) hide show
  1. package/.eslintrc +5 -0
  2. package/LICENSE +1 -1
  3. package/lib/attr-check.js +2 -2
  4. package/lib/attr-duplication/index.js +3 -0
  5. package/lib/attr-value-quotes/index.js +3 -1
  6. package/lib/case-sensitive-attr-name/index.js +3 -0
  7. package/lib/case-sensitive-tag-name/index.js +3 -0
  8. package/lib/character-reference/index.js +8 -5
  9. package/lib/class-naming/index.js +5 -2
  10. package/lib/create-message.d.ts +1 -1
  11. package/lib/create-message.js +21 -10
  12. package/lib/deprecated-attr/index.js +3 -0
  13. package/lib/deprecated-element/index.js +4 -1
  14. package/lib/disallowed-element/index.js +7 -4
  15. package/lib/doctype/index.js +3 -0
  16. package/lib/end-tag/index.js +3 -0
  17. package/lib/helpers.js +10 -6
  18. package/lib/id-duplication/index.js +3 -0
  19. package/lib/index.d.ts +6 -2
  20. package/lib/ineffective-attr/index.js +3 -0
  21. package/lib/invalid-attr/index.js +3 -0
  22. package/lib/label-has-control/index.js +3 -0
  23. package/lib/landmark-roles/index.js +13 -12
  24. package/lib/no-boolean-attr-value/index.js +3 -0
  25. package/lib/no-default-value/index.js +3 -0
  26. package/lib/no-empty-palpable-content/index.js +4 -1
  27. package/lib/no-hard-code-id/index.js +3 -0
  28. package/lib/no-refer-to-non-existent-id/index.d.ts +1 -1
  29. package/lib/no-refer-to-non-existent-id/index.js +11 -8
  30. package/lib/no-use-event-handler-attr/index.js +3 -0
  31. package/lib/permitted-contents/choice.js +14 -4
  32. package/lib/permitted-contents/content-model.js +6 -3
  33. package/lib/permitted-contents/count-pattern.js +1 -1
  34. package/lib/permitted-contents/debug.browser.d.ts +2 -2
  35. package/lib/permitted-contents/debug.d.ts +2 -2
  36. package/lib/permitted-contents/index.js +3 -0
  37. package/lib/permitted-contents/matches-selector.js +2 -2
  38. package/lib/permitted-contents/order.js +3 -3
  39. package/lib/permitted-contents/represent-transparent-nodes.js +1 -1
  40. package/lib/permitted-contents/transparent.js +2 -2
  41. package/lib/permitted-contents/utils.js +27 -18
  42. package/lib/placeholder-label-option/index.js +7 -4
  43. package/lib/require-accessible-name/index.js +3 -3
  44. package/lib/require-datetime/index.js +8 -5
  45. package/lib/require-datetime/utils.js +1 -1
  46. package/lib/required-attr/index.js +3 -0
  47. package/lib/required-element/index.js +4 -1
  48. package/lib/required-h1/index.js +3 -0
  49. package/lib/use-list/index.d.ts +4 -0
  50. package/lib/use-list/index.js +47 -27
  51. package/lib/wai-aria/checkings/implicit-props.js +7 -7
  52. package/lib/wai-aria/checkings/presentational-children.js +1 -1
  53. package/lib/wai-aria/checkings/required-owned-elements.js +3 -3
  54. package/lib/wai-aria/checkings/value.js +3 -3
  55. package/lib/wai-aria/index.js +3 -0
  56. package/package.json +11 -12
@@ -12,23 +12,33 @@ elements, specs, options, depth) {
12
12
  for (const some of pattern.choice) {
13
13
  choiceLog('Patterns[%s]: %s', i, modelLog(some, ''));
14
14
  const result = order(some, collection.unmatched, specs, options, depth + 1);
15
- if (result.type === 'UNEXPECTED_EXTRA_NODE' || result.type === 'MATCHED' || result.type === 'MATCHED_ZERO') {
15
+ if (result.type === 'MATCHED' ||
16
+ result.type === 'MATCHED_ZERO' ||
17
+ (result.type === 'UNEXPECTED_EXTRA_NODE' && result.matched.length > 0)) {
16
18
  choiceLog('Results[%s]: %s', i, choiceLogString(pattern.choice, i));
17
- collection.addMatched(result.matched);
18
19
  return {
19
20
  type: result.type,
20
- matched: collection.matched,
21
- unmatched: collection.unmatched,
21
+ matched: result.matched,
22
+ unmatched: result.unmatched,
22
23
  zeroMatch: result.zeroMatch,
23
24
  query: result.query,
24
25
  hint: result.hint,
25
26
  };
26
27
  }
27
28
  unmatchedResults.push(result);
29
+ collection.addMatched(result.matched);
28
30
  indexes.set(result, i);
29
31
  i++;
30
32
  }
31
33
  const barelyMatchedResult = unmatchedResults.sort((a, b) => {
34
+ if (a.type !== b.type) {
35
+ if (a.type === 'UNEXPECTED_EXTRA_NODE') {
36
+ return -1;
37
+ }
38
+ if (b.type === 'UNEXPECTED_EXTRA_NODE') {
39
+ return 1;
40
+ }
41
+ }
32
42
  const computed1 = b.matched.length - a.matched.length;
33
43
  if (computed1 !== 0) {
34
44
  return computed1;
@@ -3,7 +3,7 @@ import { start } from './start.js';
3
3
  export function contentModel(
4
4
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
5
5
  el, rules, options) {
6
- const model = createModel(el, rules);
6
+ const { model, specs } = createModel(el, rules);
7
7
  if (model == null) {
8
8
  return [
9
9
  {
@@ -14,7 +14,7 @@ el, rules, options) {
14
14
  },
15
15
  ];
16
16
  }
17
- const result = start(model, el, el.ownerMLDocument.specs, options);
17
+ const result = start(model, el, specs, options);
18
18
  return result;
19
19
  }
20
20
  function createModel(
@@ -22,7 +22,10 @@ function createModel(
22
22
  el, rules) {
23
23
  const specs = cachedSpecs(el.ownerMLDocument.specs, rules);
24
24
  const model = getContentModel(el, specs.specs);
25
- return model;
25
+ return {
26
+ model,
27
+ specs,
28
+ };
26
29
  }
27
30
  const caches = new Map();
28
31
  function cachedSpecs(specs, rules) {
@@ -1,6 +1,7 @@
1
1
  import { cmLog } from './debug.js';
2
2
  import { recursiveBranch } from './recursive-branch.js';
3
3
  import { Collection, mergeHints, modelLog, normalizeModel } from './utils.js';
4
+ const cLog = cmLog.extend('countCompereResult');
4
5
  /**
5
6
  * Check count
6
7
  *
@@ -139,7 +140,6 @@ elements, specs, options, depth) {
139
140
  return compereResult(matchedResult, barelyResult);
140
141
  }
141
142
  }
142
- const cLog = cmLog.extend('countCompereResult');
143
143
  function compereResult(
144
144
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
145
145
  a,
@@ -1,5 +1,5 @@
1
- /// <reference types="debug" />
2
- export declare const cmLog: import("debug").Debugger;
1
+ import type { Log } from '../debug.js';
2
+ export declare const cmLog: Log;
3
3
  export declare const bgGreen: {
4
4
  (): void;
5
5
  bold(): void;
@@ -1,6 +1,6 @@
1
- /// <reference types="debug" />
1
+ import type { Log } from '../debug.js';
2
2
  import color from 'ansi-colors';
3
- export declare const cmLog: import("debug").Debugger;
3
+ export declare const cmLog: Log;
4
4
  export declare const bgGreen: color.StyleFunction;
5
5
  export declare const green: color.StyleFunction;
6
6
  export declare const bgRed: color.StyleFunction;
@@ -2,6 +2,9 @@ import { createRule } from '@markuplint/ml-core';
2
2
  import { contentModel } from './content-model.js';
3
3
  import { transparentMode } from './represent-transparent-nodes.js';
4
4
  export default createRule({
5
+ meta: {
6
+ category: 'validation',
7
+ },
5
8
  defaultValue: [],
6
9
  defaultOptions: {
7
10
  ignoreHasMutableChildren: true,
@@ -152,7 +152,7 @@ function optCondition(query, specs) {
152
152
  }
153
153
  let hasCustom = false;
154
154
  let hasText = false;
155
- const selector = query.replace(/^(?::model\(([^)]+)\)|#([a-z-]+))(.*)$/, (_, $model, _model, $last) => {
155
+ const selector = query.replace(/^:model\(([^)]+)\)|^#([a-z-]+)/, (_, $model, _model) => {
156
156
  const _selectors = contentModelCategoryToTagNames(`#${$model ?? _model}`, specs.def);
157
157
  if (_selectors.length === 0) {
158
158
  throw new Error(`${$model ?? _model} is empty`);
@@ -169,7 +169,7 @@ function optCondition(query, specs) {
169
169
  }
170
170
  selectors.push(selector);
171
171
  }
172
- return `:is(${selectors.join(',')})${$last ?? ''}`;
172
+ return `:is(${selectors.join(',')})`;
173
173
  });
174
174
  const result = {
175
175
  selector,
@@ -41,9 +41,9 @@ nodes, specs, options, depth) {
41
41
  if (!barelyMatchedResult) {
42
42
  throw new Error('Unreachable code');
43
43
  }
44
- orderLog('Result (%s): %s%s', result.type, collection.toString(true), barelyMatchedResult.hint.missing?.barelyMatchedElements != null
45
- ? `; But ${barelyMatchedResult.hint.missing.barelyMatchedElements} elements hit out of pattern`
46
- : '');
44
+ orderLog('Result (%s): %s%s', result.type, collection.toString(true), barelyMatchedResult.hint.missing?.barelyMatchedElements == null
45
+ ? ''
46
+ : `; But ${barelyMatchedResult.hint.missing.barelyMatchedElements} elements hit out of pattern`);
47
47
  return {
48
48
  type: barelyMatchedResult.type,
49
49
  matched: collection.matched,
@@ -30,7 +30,7 @@ nodes, specs, options) {
30
30
  const collection = new Collection(getChildNodesWithoutWhitespaces(node));
31
31
  let unmatched;
32
32
  if (noTransparentModels.length > 0) {
33
- const result = order(noTransparentModels, collection.unmatched, specs, options, Infinity);
33
+ const result = order(noTransparentModels, collection.unmatched, specs, options, Number.POSITIVE_INFINITY);
34
34
  unmatched = result.unmatched;
35
35
  }
36
36
  else {
@@ -7,7 +7,7 @@ nodes) {
7
7
  transparentLog('Skipped');
8
8
  return {
9
9
  type: nodes.length === 0 ? 'MATCHED_ZERO' : 'MATCHED',
10
- matched: nodes.slice(),
10
+ matched: [...nodes],
11
11
  unmatched: [],
12
12
  zeroMatch: nodes.length === 0,
13
13
  query: 'transparent',
@@ -17,7 +17,7 @@ nodes) {
17
17
  transparentLog('Transparent model element is component root');
18
18
  return {
19
19
  type: 'MATCHED',
20
- matched: nodes.slice(),
20
+ matched: [...nodes],
21
21
  unmatched: [],
22
22
  zeroMatch: false,
23
23
  query: 'transparent',
@@ -1,5 +1,15 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
1
12
  var _Collection_locked, _Collection_matched, _Collection_nodes, _Collection_origin;
2
- import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
13
  import { createSelector } from '@markuplint/selector';
4
14
  import { bgGreen, green, bgRed, bgBlue, blue, bgMagenta, cyan } from './debug.js';
5
15
  import { transparentMode } from './represent-transparent-nodes.js';
@@ -11,7 +21,7 @@ el) {
11
21
  if (nodes) {
12
22
  return nodes;
13
23
  }
14
- nodes = Array.from(el.childNodes).filter(node => {
24
+ nodes = [...el.childNodes].filter(node => {
15
25
  return !(node.is(node.TEXT_NODE) && node.isWhitespace());
16
26
  });
17
27
  getChildNodesWithoutWhitespacesCaches.set(el, nodes);
@@ -44,10 +54,8 @@ node, specs) {
44
54
  };
45
55
  }
46
56
  const not = selectorResult
47
- .map(r => (r.matched ? [] : r.not ?? []))
48
- .flat()
49
- .map(descendants)
50
- .flat()
57
+ .flatMap(r => (r.matched ? [] : r.not ?? []))
58
+ .flatMap(descendants)
51
59
  .shift();
52
60
  return {
53
61
  matched: false,
@@ -57,7 +65,7 @@ node, specs) {
57
65
  function descendants(
58
66
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
59
67
  selectorResult) {
60
- let nodes = selectorResult.nodes.slice();
68
+ let nodes = [...selectorResult.nodes];
61
69
  while (selectorResult.has.length > 0) {
62
70
  for (const dep of selectorResult.has) {
63
71
  if (dep.has.length === 0) {
@@ -109,13 +117,13 @@ export function normalizeModel(pattern) {
109
117
  else if (isOneOrMore(pattern)) {
110
118
  model = pattern.oneOrMore;
111
119
  min = 1;
112
- max = Math.max(pattern.max ?? Infinity, 1);
120
+ max = Math.max(pattern.max ?? Number.POSITIVE_INFINITY, 1);
113
121
  missingType = 'MISSING_NODE_ONE_OR_MORE';
114
122
  }
115
123
  else if (isZeroOrMore(pattern)) {
116
124
  model = pattern.zeroOrMore;
117
125
  min = 0;
118
- max = Math.max(pattern.max ?? Infinity, 1);
126
+ max = Math.max(pattern.max ?? Number.POSITIVE_INFINITY, 1);
119
127
  }
120
128
  else {
121
129
  throw new Error('Unreachable code');
@@ -157,11 +165,11 @@ b) {
157
165
  }
158
166
  export function cleanObject(object) {
159
167
  const newObject = {};
160
- Object.entries(object).forEach(([key, value]) => {
168
+ for (const [key, value] of Object.entries(object)) {
161
169
  if (value !== undefined) {
162
170
  newObject[key] = value;
163
171
  }
164
- });
172
+ }
165
173
  return newObject;
166
174
  }
167
175
  export class Collection {
@@ -172,20 +180,20 @@ export class Collection {
172
180
  _Collection_matched.set(this, new Set());
173
181
  _Collection_nodes.set(this, void 0);
174
182
  _Collection_origin.set(this, void 0);
175
- __classPrivateFieldSet(this, _Collection_origin, origin.slice(), "f");
183
+ __classPrivateFieldSet(this, _Collection_origin, [...origin], "f");
176
184
  __classPrivateFieldSet(this, _Collection_nodes, new Set(__classPrivateFieldGet(this, _Collection_origin, "f")), "f");
177
185
  }
178
186
  get matched() {
179
- return Array.from(__classPrivateFieldGet(this, _Collection_matched, "f"));
187
+ return [...__classPrivateFieldGet(this, _Collection_matched, "f")];
180
188
  }
181
189
  get matchedCount() {
182
190
  return __classPrivateFieldGet(this, _Collection_matched, "f").size;
183
191
  }
184
192
  get nodes() {
185
- return __classPrivateFieldGet(this, _Collection_origin, "f").slice();
193
+ return [...__classPrivateFieldGet(this, _Collection_origin, "f")];
186
194
  }
187
195
  get unmatched() {
188
- return Array.from(__classPrivateFieldGet(this, _Collection_nodes, "f")).filter(n => !__classPrivateFieldGet(this, _Collection_matched, "f").has(n));
196
+ return [...__classPrivateFieldGet(this, _Collection_nodes, "f")].filter(n => !__classPrivateFieldGet(this, _Collection_matched, "f").has(n));
189
197
  }
190
198
  addMatched(
191
199
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -206,13 +214,14 @@ export class Collection {
206
214
  __classPrivateFieldSet(this, _Collection_locked, new Set(__classPrivateFieldGet(this, _Collection_matched, "f")), "f");
207
215
  }
208
216
  max(max) {
209
- const sliced = Array.from(__classPrivateFieldGet(this, _Collection_matched, "f")).slice(max);
210
- sliced.forEach(n => __classPrivateFieldGet(this, _Collection_matched, "f").delete(n));
217
+ const sliced = [...__classPrivateFieldGet(this, _Collection_matched, "f")].slice(max);
218
+ for (const n of sliced)
219
+ __classPrivateFieldGet(this, _Collection_matched, "f").delete(n);
211
220
  }
212
221
  toString(highlightExtraNodes = false) {
213
222
  const out = [];
214
223
  for (const n of __classPrivateFieldGet(this, _Collection_origin, "f")) {
215
- const raw = n.is(n.TEXT_NODE) ? `:text(${n.raw.replace(/\n/g, '\\n')})` : n.raw;
224
+ const raw = n.is(n.TEXT_NODE) ? `:text(${n.raw.replaceAll('\n', '\\n')})` : n.raw;
216
225
  if (__classPrivateFieldGet(this, _Collection_locked, "f").has(n)) {
217
226
  if (transparentMode.has(n)) {
218
227
  out.push(bgBlue.bold(raw));
@@ -1,18 +1,21 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  export default createRule({
3
+ meta: {
4
+ category: 'validation',
5
+ },
3
6
  verify({ document, report, t }) {
4
- document.querySelectorAll('select').forEach(select => {
7
+ for (const select of document.querySelectorAll('select')) {
5
8
  if (hasPlaceholderLabelOption(select)) {
6
- return;
9
+ continue;
7
10
  }
8
11
  if (!needPlaceholderLabelOption(select)) {
9
- return;
12
+ continue;
10
13
  }
11
14
  report({
12
15
  scope: select,
13
16
  message: t('need {0}', t('the {0}', 'placeholder label option')),
14
17
  });
15
- });
18
+ }
16
19
  },
17
20
  });
18
21
  /**
@@ -2,14 +2,14 @@ import { createRule, getRoleSpec, getComputedRole } from '@markuplint/ml-core';
2
2
  import { ARIA_RECOMMENDED_VERSION, isExposed } from '@markuplint/ml-spec';
3
3
  import { accnameMayBeMutable } from '../helpers.js';
4
4
  export default createRule({
5
+ meta: {
6
+ category: 'a11y',
7
+ },
5
8
  defaultOptions: {
6
9
  ariaVersion: ARIA_RECOMMENDED_VERSION,
7
10
  },
8
11
  async verify({ document, report, t }) {
9
12
  await document.walkOn('Element', el => {
10
- if (el.pretenderContext?.type === 'pretender') {
11
- return;
12
- }
13
13
  if (accnameMayBeMutable(el, document)) {
14
14
  return;
15
15
  }
@@ -2,18 +2,21 @@ import { createRule } from '@markuplint/ml-core';
2
2
  import { check } from '@markuplint/types';
3
3
  import { getCandidateDatetimeString } from './utils.js';
4
4
  export default createRule({
5
+ meta: {
6
+ category: 'validation',
7
+ },
5
8
  defaultOptions: {
6
9
  langs: undefined,
7
10
  },
8
11
  verify({ document, report, t }) {
9
- document.querySelectorAll('time:not([datetime])').forEach(time => {
12
+ for (const time of document.querySelectorAll('time:not([datetime])')) {
10
13
  if (time.hasMutableChildren()) {
11
- return;
14
+ continue;
12
15
  }
13
16
  const content = time.textContent.trim();
14
17
  const result = check(content, 'DateTime');
15
18
  if (result.matched) {
16
- return;
19
+ continue;
17
20
  }
18
21
  const candidate = getCandidateDatetimeString(content, time.rule.options.langs);
19
22
  if (candidate) {
@@ -21,12 +24,12 @@ export default createRule({
21
24
  scope: time,
22
25
  message: t('need {0*}', `datetime="${candidate}"`),
23
26
  });
24
- return;
27
+ continue;
25
28
  }
26
29
  report({
27
30
  scope: time,
28
31
  message: t('need {0}', t('the "{0*}" {1}', 'datetime', 'attribute')),
29
32
  });
30
- });
33
+ }
31
34
  },
32
35
  });
@@ -94,7 +94,7 @@ function parseTryMultipleLangs(content, langs, base) {
94
94
  for (const lang of langs) {
95
95
  const results = chrono[lang].casual.parse(content, base);
96
96
  // Is not multiple datetime contents
97
- if (results.length < 1) {
97
+ if (results.length === 0) {
98
98
  continue;
99
99
  }
100
100
  const result = results[0];
@@ -1,6 +1,9 @@
1
1
  import { createRule, getAttrSpecs } from '@markuplint/ml-core';
2
2
  import { attrMatches, match } from '../helpers.js';
3
3
  export default createRule({
4
+ meta: {
5
+ category: 'validation',
6
+ },
4
7
  defaultValue: [],
5
8
  async verify({ document, report, t }) {
6
9
  await document.walkOn('Element', el => {
@@ -1,5 +1,8 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  export default createRule({
3
+ meta: {
4
+ category: 'validation',
5
+ },
3
6
  defaultValue: [],
4
7
  defaultOptions: {
5
8
  ignoreHasMutableContents: true,
@@ -28,7 +31,7 @@ export default createRule({
28
31
  return;
29
32
  }
30
33
  for (const query of el.rule.value) {
31
- const exists = Array.from(el.children).find(child => child.matches(query));
34
+ const exists = [...el.children].find(child => child.matches(query));
32
35
  if (!exists) {
33
36
  const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
34
37
  report({
@@ -1,5 +1,8 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  export default createRule({
3
+ meta: {
4
+ category: 'a11y',
5
+ },
3
6
  defaultOptions: {
4
7
  'expected-once': true,
5
8
  'in-document-fragment': false,
@@ -1,6 +1,10 @@
1
1
  type Bullets = readonly string[];
2
2
  type Options = {
3
3
  spaceNeededBullets?: string[];
4
+ noPrev?: boolean;
5
+ prevElement?: boolean;
6
+ prevComment?: boolean;
7
+ prevCodeBlock?: boolean;
4
8
  };
5
9
  declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<Bullets, Options>>;
6
10
  export default _default;
@@ -1,36 +1,39 @@
1
1
  import { createRule } from '@markuplint/ml-core';
2
2
  import { decodeEntities } from '@markuplint/shared';
3
3
  export default createRule({
4
+ meta: {
5
+ category: 'a11y',
6
+ },
4
7
  defaultValue: [
5
8
  /**
6
9
  * @see https://en.wikipedia.org/wiki/Bullet_(typography)#In_Unicode
7
10
  */
8
- '\u2022',
9
- '\u2023',
10
- '\u2043',
11
- '\u204C',
12
- '\u204D',
13
- '\u2219',
14
- '\u25CB',
15
- '\u25CF',
16
- '\u25D8',
17
- '\u25E6',
18
- '\u2619',
19
- '\u2765',
20
- '\u2767',
21
- '\u29BE',
22
- '\u29BF',
11
+ '\u2022', // • BULLET (HTML &#8226; · &bull;, &bullet;)
12
+ '\u2023', // ‣ TRIANGULAR BULLET (HTML &#8227;)
13
+ '\u2043', // ⁃ HYPHEN BULLET (HTML &#8259; · &hybull;)
14
+ '\u204C', // ⁌ BLACK LEFTWARDS BULLET (HTML &#8268;)
15
+ '\u204D', // ⁍ BLACK RIGHTWARDS BULLET (HTML &#8269;)
16
+ '\u2219', // ∙ BULLET OPERATOR (HTML &#8729;) for use in mathematical notation primarily as a dot product instead of interupt.
17
+ '\u25CB', // ○ WHITE CIRCLE (HTML &#9675; · &cir;)
18
+ '\u25CF', // ● BLACK CIRCLE (HTML &#9679;)
19
+ '\u25D8', // ◘ INVERSE BULLET (HTML &#9688;)
20
+ '\u25E6', // ◦ WHITE BULLET (HTML &#9702;)
21
+ '\u2619', // ☙ REVERSED ROTATED FLORAL HEART BULLET (HTML &#9753;); see Fleuron (typography)
22
+ '\u2765', // ❥ ROTATED HEAVY BLACK HEART BULLET (HTML &#10085;)
23
+ '\u2767', // ❧ ROTATED FLORAL HEART BULLET (HTML &#10087;); see Fleuron (typography)
24
+ '\u29BE', // ⦾ CIRCLED WHITE BULLET (HTML &#10686; · &olcir;)
25
+ '\u29BF', // ⦿ CIRCLED BULLET (HTML &#10687; · &ofcir;)
23
26
  /**
24
27
  * In Japanese
25
28
  * @see https://ja.wikipedia.org/wiki/中黒#符号位置
26
29
  */
27
- '\u00B7',
28
- '\u0387',
29
- '\u2022',
30
- '\u2219',
31
- '\u22C5',
32
- '\u30FB',
33
- '\uFF65',
30
+ '\u00B7', // MIDDLE DOT
31
+ '\u0387', // GREEK ANO TELIA
32
+ '\u2022', // BULLET
33
+ '\u2219', // BULLET OPERATOR
34
+ '\u22C5', // DOT OPERATOR
35
+ '\u30FB', // KATAKANA MIDDLE DOT
36
+ '\uFF65', // HALFWIDTH KATAKANA MIDDLE DOT
34
37
  /**
35
38
  * In Other Languages
36
39
  */
@@ -38,8 +41,8 @@ export default createRule({
38
41
  /**
39
42
  * From Markdown
40
43
  */
41
- '-',
42
- '*',
44
+ '-', // dashes
45
+ '*', // asterisks
43
46
  '+', // plus signs
44
47
  ],
45
48
  defaultOptions: {
@@ -47,10 +50,14 @@ export default createRule({
47
50
  /**
48
51
  * From Markdown
49
52
  */
50
- '-',
51
- '*',
53
+ '-', // dashes
54
+ '*', // asterisks
52
55
  '+', // plus signs
53
56
  ],
57
+ noPrev: true,
58
+ prevElement: true,
59
+ prevComment: true,
60
+ prevCodeBlock: false,
54
61
  },
55
62
  defaultSeverity: 'warning',
56
63
  async verify({ document, report, t }) {
@@ -64,6 +71,19 @@ export default createRule({
64
71
  // character only
65
72
  return;
66
73
  }
74
+ if (textNode.rule.options.noPrev === false && !textNode.prevNode) {
75
+ return;
76
+ }
77
+ if (textNode.rule.options.prevElement === false && textNode.prevNode?.is(textNode.ELEMENT_NODE)) {
78
+ return;
79
+ }
80
+ if (textNode.rule.options.prevComment === false && textNode.prevNode?.is(textNode.COMMENT_NODE)) {
81
+ return;
82
+ }
83
+ if (textNode.rule.options.prevCodeBlock === false &&
84
+ textNode.prevNode?.is(textNode.MARKUPLINT_PREPROCESSOR_BLOCK)) {
85
+ return;
86
+ }
67
87
  const bullets = textNode.rule.value;
68
88
  const spaceNeededBullets = textNode.rule.options.spaceNeededBullets ?? [];
69
89
  if (isMayListItem(text, bullets, spaceNeededBullets)) {
@@ -76,7 +96,7 @@ export default createRule({
76
96
  },
77
97
  });
78
98
  function isMayListItem(text, bullets, spaceNeededBullets) {
79
- const textArray = Array.from(text);
99
+ const textArray = [...text];
80
100
  const firstLetter = textArray[0] ?? '';
81
101
  const isBullet = bullets.includes(firstLetter);
82
102
  const needSpace = spaceNeededBullets.includes(firstLetter);
@@ -34,13 +34,13 @@ export const checkingImplicitProps = ({ attr, propSpecs, attrSpecs }) => t => {
34
34
  message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the current "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
35
35
  };
36
36
  }
37
- else if (value === 'true') {
38
- if (!equivalentHtmlAttr.isNotStrictEquivalent && htmlAttrSpec?.type === 'Boolean') {
39
- return {
40
- scope: attr,
41
- message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
42
- };
43
- }
37
+ else if (value === 'true' &&
38
+ !equivalentHtmlAttr.isNotStrictEquivalent &&
39
+ htmlAttrSpec?.type === 'Boolean') {
40
+ return {
41
+ scope: attr,
42
+ message: t('{0} contradicts {1}', t('the "{0*}" {1}', attr.name, `ARIA ${propSpec.type}`), t('the implicit "{0}" {1}', equivalentHtmlAttr.htmlAttrName, 'attribute')),
43
+ };
44
44
  }
45
45
  }
46
46
  };
@@ -33,7 +33,7 @@ export const checkingPresentationalChildren = ({ el }) => t => {
33
33
  if (!ancestor.role) {
34
34
  return;
35
35
  }
36
- const hasAriaAttr = Array.from(el.attributes).some(attr => /^aria-|^role$/i.test(attr.name));
36
+ const hasAriaAttr = [...el.attributes].some(attr => /^aria-|^role$/i.test(attr.name));
37
37
  if (!hasAriaAttr) {
38
38
  return;
39
39
  }
@@ -25,7 +25,7 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
25
25
  return;
26
26
  }
27
27
  // TODO: Needs to resolve `aria-own`
28
- const children = Array.from(el.childNodes).map(child => {
28
+ const children = [...el.childNodes].map(child => {
29
29
  if (child.is(child.ELEMENT_NODE)) {
30
30
  if (child.matches('[aria-busy="true" i]')) {
31
31
  return [child, 'BUSY'];
@@ -75,7 +75,7 @@ export const checkingRequiredOwnedElements = ({ el, role }) => t => {
75
75
  if (mayBeBeforeCreated(el)) {
76
76
  return {
77
77
  scope: el,
78
- message: t('{0}. Or, {1}', t('require {0}', role.requiredOwnedElements.length === 1 && role.requiredOwnedElements[0]
78
+ message: t('{0}. Or, {1}', t('{0} requires {1}', t('the {0}', 'child element'), role.requiredOwnedElements.length === 1 && role.requiredOwnedElements[0]
79
79
  ? t('the "{0*}" {1}', role.requiredOwnedElements[0], 'role')
80
80
  : t('the {0}', 'roles') + `: ${t(role.requiredOwnedElements)}`), t('require {0}', 'aria-busy="true"')),
81
81
  };
@@ -93,7 +93,7 @@ el) {
93
93
  if (el.isEmpty()) {
94
94
  return true;
95
95
  }
96
- return Array.from(el.children).every(child => {
96
+ return [...el.children].every(child => {
97
97
  return ['script', 'template'].includes(child.localName);
98
98
  });
99
99
  }
@@ -49,7 +49,7 @@ export function checkAriaValue(type, value, tokenEnum, booleanish) {
49
49
  return tokenEnum.includes(value);
50
50
  }
51
51
  case 'token list': {
52
- const list = value.split(/\s+/g).map(s => s.trim());
52
+ const list = value.split(/\s+/).map(s => s.trim());
53
53
  return list.every(token => tokenEnum.includes(token));
54
54
  }
55
55
  case 'string':
@@ -76,10 +76,10 @@ export function checkAriaValue(type, value, tokenEnum, booleanish) {
76
76
  return ['true', 'false', 'undefined'].includes(value);
77
77
  }
78
78
  case 'integer': {
79
- return parseInt(value).toString() === value;
79
+ return Number.parseInt(value).toString() === value;
80
80
  }
81
81
  case 'number': {
82
- return parseFloat(value).toString() === value;
82
+ return Number.parseFloat(value).toString() === value;
83
83
  }
84
84
  }
85
85
  // For skipping checking