@markuplint/rules 3.0.0-dev.51 → 3.0.0-dev.95

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,7 +1,7 @@
1
1
  import type { Translator } from '@markuplint/i18n';
2
2
  import type { Attribute as AttrSpec, AttributeType } from '@markuplint/ml-spec';
3
3
  type Invalid = {
4
- invalidType: 'non-existent' | 'invalid-value';
4
+ invalidType: 'non-existent' | 'invalid-value' | 'disallowed-attr';
5
5
  message: string;
6
6
  loc?: Loc;
7
7
  };
package/lib/attr-check.js CHANGED
@@ -22,6 +22,12 @@ function attrCheck(t, name, value, isCustomRule, spec) {
22
22
  // Ignore checking because ARIA attributes are check on another rule
23
23
  return false;
24
24
  }
25
+ // @see https://www.w3.org/TR/adapt/
26
+ // It is an experimental
27
+ if (/^adapt-.+$/.test(name)) {
28
+ // Ignore checking because "adapt-*" attribute is any type
29
+ return false;
30
+ }
25
31
  }
26
32
  // Existence
27
33
  if (!spec) {
@@ -41,6 +47,12 @@ function attrCheck(t, name, value, isCustomRule, spec) {
41
47
  t('Did you mean "{0*}"?', spec.name),
42
48
  };
43
49
  }
50
+ if (spec.noUse) {
51
+ return {
52
+ invalidType: 'disallowed-attr',
53
+ message: t('{0} is {1:c}', t('the "{0*}" {1}', name, 'attribute'), 'disallowed'),
54
+ };
55
+ }
44
56
  const types = Array.isArray(spec.type) ? spec.type : [spec.type];
45
57
  const invalidList = types.map(type => {
46
58
  if (!type) {
package/lib/debug.js CHANGED
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.log = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const debug_1 = tslib_1.__importDefault(require("debug"));
6
- exports.log = (0, debug_1.default)('markuplint-rules');
6
+ exports.log = (0, debug_1.default)('ml-rules');
package/lib/helpers.d.ts CHANGED
@@ -21,7 +21,7 @@ export declare function match(needle: string, pattern: string): boolean;
21
21
  */
22
22
  export declare const rePCENChar: string;
23
23
  export declare function isValidAttr(t: Translator, name: string, value: string, isDynamicValue: boolean, node: Element<any, any>, attrSpecs: Attribute[], log?: Log): false | {
24
- invalidType: "non-existent" | "invalid-value";
24
+ invalidType: "non-existent" | "invalid-value" | "disallowed-attr";
25
25
  message: string;
26
26
  loc?: {
27
27
  raw: string;
package/lib/index.js CHANGED
@@ -24,7 +24,9 @@ const no_hard_code_id_1 = tslib_1.__importDefault(require("./no-hard-code-id"));
24
24
  const no_refer_to_non_existent_id_1 = tslib_1.__importDefault(require("./no-refer-to-non-existent-id"));
25
25
  const no_use_event_handler_attr_1 = tslib_1.__importDefault(require("./no-use-event-handler-attr"));
26
26
  const permitted_contents_1 = tslib_1.__importDefault(require("./permitted-contents"));
27
+ const placeholder_label_option_1 = tslib_1.__importDefault(require("./placeholder-label-option"));
27
28
  const require_accessible_name_1 = tslib_1.__importDefault(require("./require-accessible-name"));
29
+ const require_datetime_1 = tslib_1.__importDefault(require("./require-datetime"));
28
30
  const required_attr_1 = tslib_1.__importDefault(require("./required-attr"));
29
31
  const required_element_1 = tslib_1.__importDefault(require("./required-element"));
30
32
  const required_h1_1 = tslib_1.__importDefault(require("./required-h1"));
@@ -54,7 +56,9 @@ exports.default = {
54
56
  'no-refer-to-non-existent-id': no_refer_to_non_existent_id_1.default,
55
57
  'no-use-event-handler-attr': no_use_event_handler_attr_1.default,
56
58
  'permitted-contents': permitted_contents_1.default,
59
+ 'placeholder-label-option': placeholder_label_option_1.default,
57
60
  'require-accessible-name': require_accessible_name_1.default,
61
+ 'require-datetime': require_datetime_1.default,
58
62
  'required-attr': required_attr_1.default,
59
63
  'required-element': required_element_1.default,
60
64
  'required-h1': required_h1_1.default,
@@ -77,6 +77,13 @@ exports.default = (0, ml_core_1.createRule)({
77
77
  }
78
78
  if (invalid) {
79
79
  switch (invalid.invalidType) {
80
+ case 'disallowed-attr': {
81
+ report({
82
+ scope: attr,
83
+ message: invalid.message,
84
+ });
85
+ break;
86
+ }
80
87
  case 'invalid-value': {
81
88
  if (attr.isDynamicValue) {
82
89
  break;
@@ -10,6 +10,14 @@ exports.default = (0, ml_core_1.createRule)({
10
10
  },
11
11
  async verify({ document, report, t }) {
12
12
  await document.walkOn('Element', el => {
13
+ /**
14
+ * Exception
15
+ *
16
+ * - The `textarea` element is possibly empty because a user inputs it.
17
+ */
18
+ if (el.localName === 'textarea') {
19
+ return;
20
+ }
13
21
  if (!(0, ml_spec_1.isPalpableElement)(el, el.ownerMLDocument.specs, {
14
22
  extendsSvg: false,
15
23
  extendsExposableElements: el.rule.options.extendsExposableElements,
@@ -1,5 +1,6 @@
1
1
  import type { ARIAVersion } from '@markuplint/ml-spec';
2
2
  declare const _default: import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, {
3
3
  ariaVersion: ARIAVersion;
4
+ fragmentRefersNameAttr: boolean;
4
5
  }>;
5
6
  export default _default;
@@ -1,15 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const ml_core_1 = require("@markuplint/ml-core");
4
+ const HYPERLINK_SELECTOR = 'a[href], area[href]';
4
5
  exports.default = (0, ml_core_1.createRule)({
5
6
  defaultOptions: {
6
7
  ariaVersion: '1.2',
8
+ fragmentRefersNameAttr: false,
7
9
  },
8
10
  async verify({ document, report, t }) {
9
11
  const idList = new Set();
12
+ const nameList = new Set();
10
13
  let hasDynamicId = false;
11
- await document.walkOn('Attr', attr => {
12
- if (attr.name.toLowerCase() !== 'id') {
14
+ let hasDynamicName = false;
15
+ document.querySelectorAll('[id]').forEach(el => {
16
+ const attr = el.getAttributeNode('id');
17
+ if (!attr) {
13
18
  return;
14
19
  }
15
20
  if (attr.isDynamicValue) {
@@ -22,6 +27,18 @@ exports.default = (0, ml_core_1.createRule)({
22
27
  if (hasDynamicId) {
23
28
  return;
24
29
  }
30
+ document.querySelectorAll('[name]').forEach(el => {
31
+ const attr = el.getAttributeNode('name');
32
+ if (!attr) {
33
+ return;
34
+ }
35
+ if (attr.isDynamicValue) {
36
+ hasDynamicName = true;
37
+ }
38
+ if (attr.valueType !== 'code') {
39
+ nameList.add(attr.value);
40
+ }
41
+ });
25
42
  await document.walkOn('Attr', attr => {
26
43
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
27
44
  const attrSpec = (0, ml_core_1.getAttrSpecs)(attr.ownerElement, document.specs);
@@ -100,5 +117,53 @@ exports.default = (0, ml_core_1.createRule)({
100
117
  }
101
118
  }
102
119
  });
120
+ /**
121
+ * @see https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment
122
+ */
123
+ await document.walkOn('Element', el => {
124
+ var _a, _b, _c, _d;
125
+ if (el.rule.options.fragmentRefersNameAttr && hasDynamicName) {
126
+ return;
127
+ }
128
+ if (!el.matches(HYPERLINK_SELECTOR)) {
129
+ return;
130
+ }
131
+ const href = el.getAttributeNode('href');
132
+ if (!href) {
133
+ return;
134
+ }
135
+ const rawFragment = (_a = href.value.match(/^#(.+)/)) === null || _a === void 0 ? void 0 : _a[1];
136
+ if (rawFragment == null) {
137
+ return;
138
+ }
139
+ const decodedFragment = decode(rawFragment);
140
+ // > 2. If fragment is the empty string, then return the special value top of the document.
141
+ // >
142
+ // > 9. If decodedFragment is an ASCII case-insensitive match for the string top, then return the top of the document.
143
+ if (decodedFragment === '' || /^top$/i.test(decodedFragment)) {
144
+ return;
145
+ }
146
+ if (!idList.has(decodedFragment) &&
147
+ (el.rule.options.fragmentRefersNameAttr ? !nameList.has(decodedFragment) : true)) {
148
+ report({
149
+ scope: href,
150
+ line: (_b = href.valueNode) === null || _b === void 0 ? void 0 : _b.startLine,
151
+ col: (_c = href.valueNode) === null || _c === void 0 ? void 0 : _c.startCol,
152
+ raw: (_d = href.valueNode) === null || _d === void 0 ? void 0 : _d.raw,
153
+ message: t('Missing {0}', t('"{0*}" ID', decodedFragment)),
154
+ });
155
+ }
156
+ });
103
157
  },
104
158
  });
159
+ function decode(fragment) {
160
+ try {
161
+ return decodeURI(fragment);
162
+ }
163
+ catch (e) {
164
+ if (e instanceof URIError) {
165
+ return fragment;
166
+ }
167
+ throw e;
168
+ }
169
+ }
@@ -1,14 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.choice = void 0;
4
+ const debug_1 = require("./debug");
4
5
  const order_1 = require("./order");
5
6
  const utils_1 = require("./utils");
7
+ const indexes = new WeakMap();
6
8
  function choice(pattern, elements, specs, options, depth) {
9
+ const choiceLog = debug_1.cmLog.extend(`choice#${depth}`);
7
10
  const collection = new utils_1.Collection(elements);
8
11
  const unmatchedResults = [];
12
+ let i = 0;
9
13
  for (const some of pattern.choice) {
14
+ choiceLog('Patterns[%s]: %s', i, (0, utils_1.modelLog)(some, ''));
10
15
  const result = (0, order_1.order)(some, collection.unmatched, specs, options, depth + 1);
11
16
  if (result.type === 'UNEXPECTED_EXTRA_NODE' || result.type === 'MATCHED' || result.type === 'MATCHED_ZERO') {
17
+ choiceLog('Results[%s]: %s', i, choiceLogString(pattern.choice, i));
12
18
  collection.addMatched(result.matched);
13
19
  return {
14
20
  type: result.type,
@@ -20,11 +26,27 @@ function choice(pattern, elements, specs, options, depth) {
20
26
  };
21
27
  }
22
28
  unmatchedResults.push(result);
29
+ indexes.set(result, i);
30
+ i++;
23
31
  }
24
- const barelyMatchedResult = unmatchedResults.sort((a, b) => b.matched.length - a.matched.length)[0];
32
+ const barelyMatchedResult = unmatchedResults.sort((a, b) => {
33
+ var _c, _d, _e, _f;
34
+ const computed1 = b.matched.length - a.matched.length;
35
+ if (computed1 !== 0) {
36
+ return computed1;
37
+ }
38
+ const _a = (_d = (_c = a.hint.missing) === null || _c === void 0 ? void 0 : _c.barelyMatchedElements) !== null && _d !== void 0 ? _d : 0;
39
+ const _b = (_f = (_e = b.hint.missing) === null || _e === void 0 ? void 0 : _e.barelyMatchedElements) !== null && _f !== void 0 ? _f : 0;
40
+ const computed2 = _b - _a;
41
+ return computed2;
42
+ })[0];
25
43
  if (!barelyMatchedResult) {
26
44
  throw new Error('Unreachable code');
27
45
  }
46
+ const index = indexes.get(barelyMatchedResult);
47
+ if (index != null) {
48
+ choiceLog('Results[%s]: %s', index, choiceLogString(pattern.choice, index, true));
49
+ }
28
50
  return {
29
51
  type: barelyMatchedResult.type,
30
52
  matched: collection.matched,
@@ -35,3 +57,14 @@ function choice(pattern, elements, specs, options, depth) {
35
57
  };
36
58
  }
37
59
  exports.choice = choice;
60
+ function choiceLogString(choice, index, barely = false) {
61
+ const colorFn = barely ? debug_1.bgBlue : debug_1.bgGreen;
62
+ return choice
63
+ .map((pattern, i) => {
64
+ if (i === index) {
65
+ return colorFn((0, utils_1.modelLog)(pattern, ''));
66
+ }
67
+ return (0, utils_1.modelLog)(pattern, '');
68
+ })
69
+ .join(', ');
70
+ }
@@ -15,94 +15,72 @@ const utils_1 = require("./utils");
15
15
  * @returns
16
16
  */
17
17
  function countPattern(pattern, elements, specs, options, depth) {
18
- var _a, _b, _c, _d, _e;
19
18
  const ptLog = debug_1.cmLog.extend(`countPattern#${depth}`);
20
19
  const collection = new utils_1.Collection(elements);
21
- let min;
22
- let max;
23
- let model;
24
- let type;
25
- let _type;
26
- if ((0, utils_1.isRequire)(pattern)) {
27
- min = (_a = pattern.min) !== null && _a !== void 0 ? _a : 1;
28
- max = Math.max((_b = pattern.max) !== null && _b !== void 0 ? _b : 1, min);
29
- model = pattern.require;
30
- type = 'MISSING_NODE_REQUIRED';
31
- _type = 'require';
32
- }
33
- else if ((0, utils_1.isOptional)(pattern)) {
34
- min = 0;
35
- max = Math.max((_c = pattern.max) !== null && _c !== void 0 ? _c : 1, 1);
36
- model = pattern.optional;
37
- _type = 'optional';
38
- }
39
- else if ((0, utils_1.isOneOrMore)(pattern)) {
40
- min = 1;
41
- max = Math.max((_d = pattern.max) !== null && _d !== void 0 ? _d : Infinity, 1);
42
- model = pattern.oneOrMore;
43
- type = 'MISSING_NODE_ONE_OR_MORE';
44
- _type = 'one or more';
45
- }
46
- else if ((0, utils_1.isZeroOrMore)(pattern)) {
47
- min = 0;
48
- max = Math.max((_e = pattern.max) !== null && _e !== void 0 ? _e : Infinity, 1);
49
- model = pattern.zeroOrMore;
50
- _type = 'zero or more';
51
- }
52
- else {
53
- throw new Error('Unreachable code');
54
- }
55
- ptLog('%s: %o', _type, model);
20
+ const { model, min, max, repeat, missingType } = (0, utils_1.normalizeModel)(pattern);
21
+ ptLog('Model:\n RegEx: %s\n Schema: %o', (0, utils_1.modelLog)(model, repeat), pattern);
56
22
  let prevResult = null;
23
+ let barelyResult = null;
24
+ let loopCount = 0;
57
25
  // eslint-disable-next-line no-constant-condition
58
26
  while (true) {
59
- ptLog('%s', collection);
27
+ loopCount++;
28
+ ptLog('Check#%s: %s', loopCount, collection);
60
29
  const result = (0, recursive_branch_1.recursiveBranch)(model, collection.unmatched, specs, options, depth);
61
30
  const added = collection.addMatched(result.matched);
31
+ const { matchedCount } = collection;
62
32
  if (result.type === 'UNMATCHED_SELECTOR_BUT_MAY_EMPTY') {
63
- ptLog('MATCHED_ZERO: %s', result.query);
64
- return {
33
+ ptLog('MATCHED_ZERO:\n model: %s\n max: %s\n collection: %s\n matched element: %s', (0, utils_1.modelLog)(model, repeat), max, collection, matchedCount);
34
+ return compereResult({
65
35
  type: 'MATCHED_ZERO',
66
36
  matched: collection.matched,
67
37
  unmatched: collection.unmatched,
68
38
  zeroMatch: true,
69
39
  query: result.query,
70
40
  hint: result.hint,
71
- };
41
+ }, barelyResult);
72
42
  }
73
43
  if (max < collection.matchedCount) {
74
44
  collection.max(max);
75
- ptLog('UNEXPECTED_EXTRA_NODE (max: %s): %s', max, result.query);
76
- return {
45
+ ptLog('UNEXPECTED_EXTRA_NODE:\n model: %s\n max: %s\n collection: %s\n matched element: %s', (0, utils_1.modelLog)(model, repeat), max, collection, matchedCount);
46
+ return compereResult({
77
47
  type: 'UNEXPECTED_EXTRA_NODE',
78
48
  matched: collection.matched,
79
49
  unmatched: collection.unmatched,
80
50
  zeroMatch: result.zeroMatch,
81
51
  query: result.query,
82
- hint: {
83
- ...result.hint,
84
- max,
85
- },
86
- };
52
+ hint: (0, utils_1.mergeHints)(result.hint, { max }),
53
+ }, barelyResult);
87
54
  }
88
55
  if (prevResult) {
89
56
  if (result.type === 'MISSING_NODE_ONE_OR_MORE' ||
90
57
  result.type === 'MISSING_NODE_REQUIRED' ||
91
58
  result.type === 'TRANSPARENT_MODEL_DISALLOWS') {
92
- ptLog('%s(continued): %s', result.type, collection);
93
- return {
59
+ ptLog('%s(continued): %s; Needs', result.type, collection, result.query);
60
+ return compereResult({
94
61
  type: result.type,
95
62
  matched: collection.matched,
96
63
  unmatched: collection.unmatched,
97
64
  zeroMatch: result.zeroMatch,
98
65
  query: result.query,
99
66
  hint: result.hint,
100
- };
67
+ }, barelyResult);
101
68
  }
102
69
  ptLog('%s(continued): %s', prevResult.type, collection);
103
- return prevResult;
70
+ return compereResult(prevResult, barelyResult);
104
71
  }
105
72
  if (added && collection.unmatched.length > 0) {
73
+ if (result.type !== 'MISSING_NODE' && result.type !== 'UNMATCHED_SELECTORS') {
74
+ barelyResult = {
75
+ type: result.type,
76
+ matched: collection.matched,
77
+ unmatched: collection.unmatched,
78
+ zeroMatch: result.zeroMatch,
79
+ query: result.query,
80
+ hint: result.hint,
81
+ };
82
+ }
83
+ ptLog('continue⤴️');
106
84
  continue;
107
85
  }
108
86
  if (collection.matchedCount + (result.zeroMatch ? 1 : 0) < min) {
@@ -110,16 +88,21 @@ function countPattern(pattern, elements, specs, options, depth) {
110
88
  result.type === 'MISSING_NODE_ONE_OR_MORE' ||
111
89
  result.type === 'TRANSPARENT_MODEL_DISALLOWS'
112
90
  ? result.type
113
- : type !== null && type !== void 0 ? type : 'MISSING_NODE_REQUIRED';
114
- ptLog('%s(in %s): %s', resultType, type, result.query);
115
- return {
91
+ : missingType !== null && missingType !== void 0 ? missingType : 'MISSING_NODE_REQUIRED';
92
+ ptLog('%s(in %s); Needs %s', resultType, missingType, result.query);
93
+ return compereResult({
116
94
  type: resultType,
117
95
  matched: collection.matched,
118
96
  unmatched: collection.unmatched,
119
97
  zeroMatch: result.zeroMatch,
120
98
  query: result.query,
121
- hint: result.hint,
122
- };
99
+ hint: (0, utils_1.mergeHints)(result.hint, {
100
+ missing: {
101
+ barelyMatchedElements: collection.matched.length,
102
+ need: result.query,
103
+ },
104
+ }),
105
+ }, barelyResult);
123
106
  }
124
107
  const resultType = collection.matched.length === 0 ? 'MATCHED_ZERO' : 'MATCHED';
125
108
  const zeroMatch = result.zeroMatch || min === 0 || resultType === 'MATCHED_ZERO';
@@ -129,27 +112,44 @@ function countPattern(pattern, elements, specs, options, depth) {
129
112
  unmatched: collection.unmatched,
130
113
  zeroMatch,
131
114
  query: result.query,
132
- hint: result.hint,
115
+ hint: (0, utils_1.mergeHints)(result.hint, {
116
+ missing: {
117
+ barelyMatchedElements: collection.matched.length,
118
+ need: result.query,
119
+ },
120
+ }),
133
121
  };
134
122
  if (!prevResult && collection.unmatched.length) {
135
- ptLog('continue checking');
136
123
  prevResult = matchedResult;
124
+ ptLog('continue⤴️ (add prev)');
137
125
  continue;
138
126
  }
139
- ptLog('%s: %s', resultType, collection);
127
+ ptLog('%s:\n model: %s\n max: %s\n collection: %s\n matched element: %s', resultType, (0, utils_1.modelLog)(model, repeat), max, collection, matchedCount);
140
128
  if (result.type === 'MISSING_NODE_REQUIRED' ||
141
129
  result.type === 'MISSING_NODE_ONE_OR_MORE' ||
142
130
  result.type === 'TRANSPARENT_MODEL_DISALLOWS') {
143
- return {
131
+ return compereResult({
144
132
  type: result.type,
145
133
  matched: collection.matched,
146
134
  unmatched: collection.unmatched,
147
135
  zeroMatch: result.zeroMatch,
148
136
  query: result.query,
149
137
  hint: result.hint,
150
- };
138
+ }, barelyResult);
151
139
  }
152
- return matchedResult;
140
+ return compereResult(matchedResult, barelyResult);
153
141
  }
154
142
  }
155
143
  exports.countPattern = countPattern;
144
+ const cLog = debug_1.cmLog.extend('countCompereResult');
145
+ function compereResult(a, b) {
146
+ cLog('current: %s %O\nbarely: %s %O', a.type, a.hint, b === null || b === void 0 ? void 0 : b.type, b === null || b === void 0 ? void 0 : b.hint);
147
+ if (b == null) {
148
+ return a;
149
+ }
150
+ if (a.type === 'MATCHED' || a.type === 'MATCHED_ZERO' || a.type === 'UNEXPECTED_EXTRA_NODE') {
151
+ return a;
152
+ }
153
+ const result = [a, b].sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = b.hint.missing) === null || _a === void 0 ? void 0 : _a.barelyMatchedElements) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = a.hint.missing) === null || _c === void 0 ? void 0 : _c.barelyMatchedElements) !== null && _d !== void 0 ? _d : 0); })[0];
154
+ return result;
155
+ }
@@ -103,9 +103,9 @@ function matchesSelector(query, node, specs, options, depth) {
103
103
  unmatched: [node],
104
104
  zeroMatch: true,
105
105
  query,
106
- hint: {
106
+ hint: (0, utils_1.cleanObject)({
107
107
  not: result.not,
108
- },
108
+ }),
109
109
  };
110
110
  }
111
111
  return {
@@ -114,9 +114,9 @@ function matchesSelector(query, node, specs, options, depth) {
114
114
  unmatched: [node],
115
115
  zeroMatch: false,
116
116
  query,
117
- hint: {
117
+ hint: (0, utils_1.cleanObject)({
118
118
  not: result.not,
119
- },
119
+ }),
120
120
  };
121
121
  }
122
122
  return {
@@ -16,11 +16,12 @@ const utils_1 = require("./utils");
16
16
  * @returns
17
17
  */
18
18
  function order(contents, nodes, specs, options, depth) {
19
- var _a, _b, _c, _d;
19
+ var _a, _b, _c, _d, _e;
20
20
  const orderLog = debug_1.cmLog.extend(`order#${depth}`);
21
21
  const btLog = debug_1.cmLog.extend(`backtrack#${depth}`);
22
22
  const patterns = (0, helpers_1.deepCopy)(contents);
23
23
  const collection = new utils_1.Collection(nodes);
24
+ orderLog('Model:\n RegEx: %s\n Schema: %o', (0, utils_1.modelLog)(patterns, ''), patterns);
24
25
  orderLog('Starts: %s', collection);
25
26
  let result = undefined;
26
27
  let backtrackMode = false;
@@ -29,28 +30,34 @@ function order(contents, nodes, specs, options, depth) {
29
30
  while (patterns.length) {
30
31
  result = (0, complex_branch_1.complexBranch)(patterns[0], collection.unmatched, specs, options, depth);
31
32
  collection.addMatched(result.matched);
32
- orderLog('stack: %s', collection);
33
33
  if (result.type !== 'UNEXPECTED_EXTRA_NODE' && result.type !== 'MATCHED' && result.type !== 'MATCHED_ZERO') {
34
34
  unmatchedResults.push(result);
35
35
  if (backtrackMode) {
36
36
  collection.back();
37
- btLog('🔙');
37
+ btLog('🔙◀BACK');
38
38
  backtrackMode = false;
39
39
  afterBacktrack = true;
40
40
  continue;
41
41
  }
42
- orderLog('conformed (%s): %s', result.type, collection.toString(true));
43
42
  const barelyMatchedResult = unmatchedResults.sort((a, b) => b.matched.length - a.matched.length)[0];
44
43
  if (!barelyMatchedResult) {
45
44
  throw new Error('Unreachable code');
46
45
  }
46
+ orderLog('Result (%s): %s%s', result.type, collection.toString(true), ((_a = barelyMatchedResult.hint.missing) === null || _a === void 0 ? void 0 : _a.barelyMatchedElements)
47
+ ? `; But ${barelyMatchedResult.hint.missing.barelyMatchedElements} elements hit out of pattern`
48
+ : '');
47
49
  return {
48
50
  type: barelyMatchedResult.type,
49
51
  matched: collection.matched,
50
52
  unmatched: collection.unmatched,
51
53
  zeroMatch: barelyMatchedResult.zeroMatch,
52
54
  query: barelyMatchedResult.query,
53
- hint: barelyMatchedResult.hint,
55
+ hint: (0, utils_1.mergeHints)(barelyMatchedResult.hint, {
56
+ missing: {
57
+ barelyMatchedElements: collection.matched.length,
58
+ need: barelyMatchedResult.query,
59
+ },
60
+ }),
54
61
  };
55
62
  }
56
63
  if (afterBacktrack) {
@@ -66,24 +73,25 @@ function order(contents, nodes, specs, options, depth) {
66
73
  patterns.shift();
67
74
  }
68
75
  if (collection.unmatched.length) {
69
- orderLog('Conformed (UNEXPECTED_EXTRA_NODE): %s', collection.toString(true));
76
+ orderLog('Result (UNEXPECTED_EXTRA_NODE): %s', collection.toString(true));
70
77
  return {
71
78
  type: 'UNEXPECTED_EXTRA_NODE',
72
79
  matched: collection.matched,
73
80
  unmatched: collection.unmatched,
74
81
  zeroMatch: false,
75
- query: (_a = result === null || result === void 0 ? void 0 : result.query) !== null && _a !== void 0 ? _a : 'N/A',
76
- hint: (_b = result === null || result === void 0 ? void 0 : result.hint) !== null && _b !== void 0 ? _b : {},
82
+ query: (_b = result === null || result === void 0 ? void 0 : result.query) !== null && _b !== void 0 ? _b : 'N/A',
83
+ hint: (_c = result === null || result === void 0 ? void 0 : result.hint) !== null && _c !== void 0 ? _c : {},
77
84
  };
78
85
  }
79
- orderLog('Conformed: %s', collection);
86
+ const resultType = collection.matched.length ? 'MATCHED' : 'MATCHED_ZERO';
87
+ orderLog('Result (%s): %s', resultType, collection.toString(true));
80
88
  return {
81
- type: collection.matched.length ? 'MATCHED' : 'MATCHED_ZERO',
89
+ type: resultType,
82
90
  matched: collection.matched,
83
91
  unmatched: collection.unmatched,
84
92
  zeroMatch: false,
85
- query: (_c = result === null || result === void 0 ? void 0 : result.query) !== null && _c !== void 0 ? _c : 'N/A',
86
- hint: (_d = result === null || result === void 0 ? void 0 : result.hint) !== null && _d !== void 0 ? _d : {},
93
+ query: (_d = result === null || result === void 0 ? void 0 : result.query) !== null && _d !== void 0 ? _d : 'N/A',
94
+ hint: (_e = result === null || result === void 0 ? void 0 : result.hint) !== null && _e !== void 0 ? _e : {},
87
95
  };
88
96
  }
89
97
  exports.order = order;
@@ -24,15 +24,21 @@ export type Result<T extends string = MatchedReason> = {
24
24
  unmatched: ChildNode[];
25
25
  zeroMatch: boolean;
26
26
  query: string;
27
- hint: {
28
- max?: number;
29
- not?: ChildNode;
30
- transparent?: Element;
27
+ hint: Hints;
28
+ };
29
+ export type Hints = {
30
+ max?: number;
31
+ not?: ChildNode;
32
+ transparent?: Element;
33
+ missing?: {
34
+ barelyMatchedElements?: number;
35
+ need?: string;
31
36
  };
32
37
  };
33
38
  export type MatchedReason = 'MATCHED' | 'MATCHED_ZERO';
34
39
  export type UnmatchedReason = 'NOTHING' | 'UNEXPECTED_EXTRA_NODE' | 'TRANSPARENT_MODEL_DISALLOWS' | MissingNodeReason;
35
40
  export type MissingNodeReason = 'MISSING_NODE_REQUIRED' | 'MISSING_NODE_ONE_OR_MORE';
41
+ export type RepeatSign = '' | '?' | '+' | '*' | `{${number},${number}}`;
36
42
  export type TransparentModel = {
37
43
  el: Element;
38
44
  additionalCondition: string;
@@ -1,4 +1,4 @@
1
- import type { ChildNode, Element, Specs } from './types';
1
+ import type { ChildNode, Element, Hints, MissingNodeReason, RepeatSign, Specs } from './types';
2
2
  import type { PermittedContentPattern, PermittedContentChoice, PermittedContentOneOrMore, PermittedContentOptional, PermittedContentRequire, PermittedContentTransparent, PermittedContentZeroOrMore, Model } from '@markuplint/ml-spec';
3
3
  export declare function getChildNodesWithoutWhitespaces(el: Element): ChildNode[];
4
4
  export declare function isModel(model: Model | PermittedContentPattern[]): model is Model;
@@ -15,6 +15,23 @@ export declare function isOneOrMore(content: PermittedContentPattern): content i
15
15
  export declare function isZeroOrMore(content: PermittedContentPattern): content is PermittedContentZeroOrMore;
16
16
  export declare function isChoice(content: PermittedContentPattern): content is PermittedContentChoice;
17
17
  export declare function isTransparent(content: PermittedContentPattern): content is PermittedContentTransparent;
18
+ export declare function normalizeModel(pattern: PermittedContentRequire | PermittedContentOptional | PermittedContentOneOrMore | PermittedContentZeroOrMore): {
19
+ model: PermittedContentPattern[] | Model;
20
+ min: number;
21
+ max: number;
22
+ repeat: RepeatSign;
23
+ missingType: MissingNodeReason | undefined;
24
+ };
25
+ export declare function mergeHints(a: Hints, b: Hints): Partial<{
26
+ missing: Partial<{
27
+ barelyMatchedElements?: number | undefined;
28
+ need?: string | undefined;
29
+ }> | undefined;
30
+ max?: number | undefined;
31
+ not?: import("packages/@markuplint/ml-core/lib").DocumentType<import("./types").TagRule[], import("./types").Options> | import("packages/@markuplint/ml-core/lib/ml-dom/node/character-data").MLCharacterData<import("./types").TagRule[], import("./types").Options, import("packages/@markuplint/ml-ast/lib").MLASTAbstractNode> | import("packages/@markuplint/ml-core/lib").Element<import("./types").TagRule[], import("./types").Options> | import("packages/@markuplint/ml-core/lib").Block<import("./types").TagRule[], import("./types").Options> | undefined;
32
+ transparent?: Element | undefined;
33
+ }>;
34
+ export declare function cleanObject<T extends Object>(object: T): Partial<T>;
18
35
  export declare class Collection {
19
36
  #private;
20
37
  constructor(origin: readonly ChildNode[]);
@@ -30,3 +47,4 @@ export declare class Collection {
30
47
  }
31
48
  export declare class UnsupportedError extends Error {
32
49
  }
50
+ export declare function modelLog(model: Model | PermittedContentPattern[], repeat: RepeatSign): string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var _Collection_locked, _Collection_matched, _Collection_nodes, _Collection_origin;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.UnsupportedError = exports.Collection = exports.isTransparent = exports.isChoice = exports.isZeroOrMore = exports.isOneOrMore = exports.isOptional = exports.isRequire = exports.matches = exports.isModel = exports.getChildNodesWithoutWhitespaces = void 0;
4
+ exports.modelLog = exports.UnsupportedError = exports.Collection = exports.cleanObject = exports.mergeHints = exports.normalizeModel = exports.isTransparent = exports.isChoice = exports.isZeroOrMore = exports.isOneOrMore = exports.isOptional = exports.isRequire = exports.matches = exports.isModel = exports.getChildNodesWithoutWhitespaces = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const selector_1 = require("@markuplint/selector");
7
7
  const debug_1 = require("./debug");
@@ -95,6 +95,81 @@ function isTransparent(content) {
95
95
  return 'transparent' in content;
96
96
  }
97
97
  exports.isTransparent = isTransparent;
98
+ function normalizeModel(pattern) {
99
+ var _a, _b, _c, _d, _e;
100
+ let model;
101
+ let min;
102
+ let max;
103
+ let repeat;
104
+ let missingType;
105
+ if (isRequire(pattern)) {
106
+ model = pattern.require;
107
+ min = (_a = pattern.min) !== null && _a !== void 0 ? _a : 1;
108
+ max = Math.max((_b = pattern.max) !== null && _b !== void 0 ? _b : 1, min);
109
+ missingType = 'MISSING_NODE_REQUIRED';
110
+ }
111
+ else if (isOptional(pattern)) {
112
+ model = pattern.optional;
113
+ min = 0;
114
+ max = Math.max((_c = pattern.max) !== null && _c !== void 0 ? _c : 1, 1);
115
+ }
116
+ else if (isOneOrMore(pattern)) {
117
+ model = pattern.oneOrMore;
118
+ min = 1;
119
+ max = Math.max((_d = pattern.max) !== null && _d !== void 0 ? _d : Infinity, 1);
120
+ missingType = 'MISSING_NODE_ONE_OR_MORE';
121
+ }
122
+ else if (isZeroOrMore(pattern)) {
123
+ model = pattern.zeroOrMore;
124
+ min = 0;
125
+ max = Math.max((_e = pattern.max) !== null && _e !== void 0 ? _e : Infinity, 1);
126
+ }
127
+ else {
128
+ throw new Error('Unreachable code');
129
+ }
130
+ if (min === 0 && max === 1) {
131
+ repeat = '?';
132
+ }
133
+ else if (min === 0 && !Number.isFinite(max)) {
134
+ repeat = '*';
135
+ }
136
+ else if (min === 1 && max === 1) {
137
+ repeat = '';
138
+ }
139
+ else if (min === 1 && !Number.isFinite(max)) {
140
+ repeat = '+';
141
+ }
142
+ else {
143
+ repeat = `{${min},${max}}`;
144
+ }
145
+ return {
146
+ model,
147
+ min,
148
+ max,
149
+ repeat,
150
+ missingType,
151
+ };
152
+ }
153
+ exports.normalizeModel = normalizeModel;
154
+ function mergeHints(a, b) {
155
+ const missing = [a.missing, b.missing].sort((a, b) => { var _a, _b; return ((_a = b === null || b === void 0 ? void 0 : b.barelyMatchedElements) !== null && _a !== void 0 ? _a : 0) - ((_b = a === null || a === void 0 ? void 0 : a.barelyMatchedElements) !== null && _b !== void 0 ? _b : 0); })[0];
156
+ return cleanObject({
157
+ ...a,
158
+ ...b,
159
+ missing: missing && cleanObject(missing),
160
+ });
161
+ }
162
+ exports.mergeHints = mergeHints;
163
+ function cleanObject(object) {
164
+ const newObject = {};
165
+ Object.entries(object).forEach(([key, value]) => {
166
+ if (value !== undefined) {
167
+ newObject[key] = value;
168
+ }
169
+ });
170
+ return newObject;
171
+ }
172
+ exports.cleanObject = cleanObject;
98
173
  class Collection {
99
174
  constructor(origin) {
100
175
  _Collection_locked.set(this, new Set());
@@ -182,3 +257,32 @@ _Collection_locked = new WeakMap(), _Collection_matched = new WeakMap(), _Collec
182
257
  class UnsupportedError extends Error {
183
258
  }
184
259
  exports.UnsupportedError = UnsupportedError;
260
+ function modelLog(model, repeat) {
261
+ if (!isModel(model)) {
262
+ return orderLog(model, repeat);
263
+ }
264
+ if (typeof model === 'string') {
265
+ return `<${model}>${repeat}`;
266
+ }
267
+ return `(<${model.join('>|<')}>)${repeat}`;
268
+ }
269
+ exports.modelLog = modelLog;
270
+ function orderLog(order, repeat) {
271
+ return order.length === 1
272
+ ? markRepeat(patternLog(order[0]), repeat)
273
+ : markRepeat(order.map(pattern => patternLog(pattern)).join(''), repeat);
274
+ }
275
+ function patternLog(pattern) {
276
+ if (isTransparent(pattern)) {
277
+ // 適当
278
+ return `:transparent(${modelLog(pattern.transparent, '')})`;
279
+ }
280
+ if (isChoice(pattern)) {
281
+ return `(${pattern.choice.map(candidate => orderLog(candidate, '')).join('|')})`;
282
+ }
283
+ const { model, repeat } = normalizeModel(pattern);
284
+ return modelLog(model, repeat);
285
+ }
286
+ function markRepeat(pattern, repeat) {
287
+ return repeat ? `(${pattern})${repeat}` : pattern;
288
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, null>;
2
+ export default _default;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ml_core_1 = require("@markuplint/ml-core");
4
+ exports.default = (0, ml_core_1.createRule)({
5
+ verify({ document, report, t }) {
6
+ document.querySelectorAll('select').forEach(select => {
7
+ if (hasPlaceholderLabelOption(select)) {
8
+ return;
9
+ }
10
+ if (!needPlaceholderLabelOption(select)) {
11
+ return;
12
+ }
13
+ report({
14
+ scope: select,
15
+ message: t('need {0}', t('the {0}', 'placeholder label option')),
16
+ });
17
+ });
18
+ },
19
+ });
20
+ /**
21
+ * > If a select element has a required attribute specified,
22
+ * > does not have a multiple attribute specified,
23
+ * > and has a display size of 1,
24
+ * > then the select element must have a placeholder label option.
25
+ *
26
+ * @param select
27
+ * @returns
28
+ */
29
+ function needPlaceholderLabelOption(select) {
30
+ const hasRequired = select.hasAttribute('required');
31
+ if (!hasRequired) {
32
+ return false;
33
+ }
34
+ const hasMultiple = select.hasAttribute('multiple');
35
+ if (hasMultiple) {
36
+ return false;
37
+ }
38
+ const size = select.getAttribute('size') || '1';
39
+ if (size !== '1') {
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ /**
45
+ * > If a select element has a required attribute specified,
46
+ * > does not have a multiple attribute specified,
47
+ * > and has a display size of 1;
48
+ * > and if the value of the first option element
49
+ * > in the select element's list of options (if any) is the empty string,
50
+ * > and that option element's parent node is the select element (and not an optgroup element),
51
+ * > then that option is the select element's **placeholder label option**.
52
+ *
53
+ * @param select
54
+ * @returns
55
+ */
56
+ function hasPlaceholderLabelOption(select) {
57
+ var _a;
58
+ // > has a required attribute specified
59
+ if (!select.hasAttribute('required')) {
60
+ return false;
61
+ }
62
+ // > does not have a multiple attribute specified
63
+ if (select.hasAttribute('multiple')) {
64
+ return false;
65
+ }
66
+ // > has a display size of 1
67
+ const size = select.getAttribute('size') || '1';
68
+ if (size !== '1') {
69
+ return false;
70
+ }
71
+ // > in the select element's list of options (if any) is the empty string
72
+ const firstOption = select.querySelector('option');
73
+ if (!firstOption) {
74
+ // if any
75
+ return true;
76
+ }
77
+ // > that option element's parent node is the select element (and not an optgroup element)
78
+ if (((_a = firstOption.parentElement) === null || _a === void 0 ? void 0 : _a.localName) === 'optgroup') {
79
+ return false;
80
+ }
81
+ const value = firstOption.getAttribute('value');
82
+ return value === '' || value === null;
83
+ }
@@ -0,0 +1,6 @@
1
+ import type { Lang } from './types';
2
+ type Options = {
3
+ langs?: Lang[];
4
+ };
5
+ declare const _default: import("@markuplint/ml-core").RuleSeed<boolean, Options>;
6
+ export default _default;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ml_core_1 = require("@markuplint/ml-core");
4
+ const types_1 = require("@markuplint/types");
5
+ const utils_1 = require("./utils");
6
+ exports.default = (0, ml_core_1.createRule)({
7
+ defaultOptions: {
8
+ langs: undefined,
9
+ },
10
+ verify({ document, report, t }) {
11
+ document.querySelectorAll('time:not([datetime])').forEach(time => {
12
+ if (time.hasMutableChildren()) {
13
+ return;
14
+ }
15
+ const content = time.textContent.trim();
16
+ const result = (0, types_1.check)(content, 'DateTime');
17
+ if (result.matched) {
18
+ return;
19
+ }
20
+ const candidate = (0, utils_1.getCandidateDatetimeString)(content, time.rule.options.langs);
21
+ if (candidate) {
22
+ report({
23
+ scope: time,
24
+ message: t('need {0*}', `datetime="${candidate}"`),
25
+ });
26
+ return;
27
+ }
28
+ report({
29
+ scope: time,
30
+ message: t('need {0}', t('the "{0*}" {1}', 'datetime', 'attribute')),
31
+ });
32
+ });
33
+ },
34
+ });
@@ -0,0 +1,10 @@
1
+ export type DateTimeKey = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'ms';
2
+ export type DateTimeData = Partial<Record<DateTimeKey, number>>;
3
+ export type DateTime = {
4
+ datetime: DateTimeData;
5
+ zone?: number;
6
+ };
7
+ /**
8
+ * @see https://github.com/wanasit/chrono#locales
9
+ */
10
+ export type Lang = 'en' | 'ja' | 'fr' | 'nl' | 'ru' | 'de' | 'pt' | 'zh';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import type { DateTime, Lang } from './types';
2
+ /**
3
+ * Datetime-ish text to a datetime data
4
+ *
5
+ * @param content
6
+ * @param langs
7
+ * @param base Reference date for a test
8
+ * @returns
9
+ */
10
+ export declare function parseADatetime(content: string, langs: Lang[], base?: Date): DateTime | null;
11
+ export declare function getCandidateDatetimeString(content: string, langs?: Lang[]): string | null;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCandidateDatetimeString = exports.parseADatetime = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const chrono = tslib_1.__importStar(require("chrono-node"));
6
+ const defaultLangs = ['en', 'ja', 'fr', 'nl', 'ru', 'de', 'pt', 'zh'];
7
+ /**
8
+ * Datetime-ish text to a datetime data
9
+ *
10
+ * @param content
11
+ * @param langs
12
+ * @param base Reference date for a test
13
+ * @returns
14
+ */
15
+ function parseADatetime(content, langs, base) {
16
+ const date = parseTryMultipleLangs(content, langs, base);
17
+ if (!date) {
18
+ return null;
19
+ }
20
+ const data = {};
21
+ if (date.isCertain('year')) {
22
+ data.year = date.get('year');
23
+ }
24
+ if (date.isCertain('month')) {
25
+ data.month = date.get('month');
26
+ }
27
+ if (date.isCertain('day')) {
28
+ data.day = date.get('day');
29
+ }
30
+ if (date.isCertain('hour')) {
31
+ data.hour = date.get('hour');
32
+ }
33
+ if (date.isCertain('hour')) {
34
+ data.minute = date.get('minute') || 0;
35
+ }
36
+ if (date.isCertain('second')) {
37
+ data.second = date.get('second');
38
+ }
39
+ if (date.isCertain('millisecond')) {
40
+ data.ms = date.get('millisecond');
41
+ }
42
+ const datetime = {
43
+ datetime: data,
44
+ };
45
+ if (date.isCertain('timezoneOffset')) {
46
+ datetime.zone = date.get('timezoneOffset');
47
+ }
48
+ return datetime;
49
+ }
50
+ exports.parseADatetime = parseADatetime;
51
+ function getCandidateDatetimeString(content, langs = defaultLangs) {
52
+ const date = parseADatetime(content, langs);
53
+ if (!date) {
54
+ return null;
55
+ }
56
+ let datetimeStr = toDatetimeString(date.datetime);
57
+ if (!datetimeStr) {
58
+ return null;
59
+ }
60
+ if (date.zone) {
61
+ const plusMinus = date.zone < 0 ? '-' : '+';
62
+ const hour = Math.floor(Math.abs(date.zone) / 60);
63
+ const minute = Math.abs(date.zone) % 60;
64
+ datetimeStr += `${plusMinus}${f(hour, 2)}${f(minute, 2)}`;
65
+ }
66
+ return datetimeStr;
67
+ }
68
+ exports.getCandidateDatetimeString = getCandidateDatetimeString;
69
+ function toDatetimeString(date) {
70
+ if (only(date, ['year', 'month'])) {
71
+ return `${f(date.year, 4)}-${f(date.month, 2)}`;
72
+ }
73
+ if (only(date, ['year', 'month', 'day'])) {
74
+ return `${f(date.year, 4)}-${f(date.month, 2)}-${f(date.day, 2)}`;
75
+ }
76
+ if (only(date, ['month', 'day'])) {
77
+ return `${f(date.month, 2)}-${f(date.day, 2)}`;
78
+ }
79
+ if (only(date, ['hour', 'minute'])) {
80
+ return `${f(date.hour, 2)}:${f(date.minute, 2)}`;
81
+ }
82
+ if (only(date, ['hour', 'minute', 'second'])) {
83
+ return `${f(date.hour, 2)}:${f(date.minute, 2)}:${f(date.second, 2)}`;
84
+ }
85
+ if (only(date, ['hour', 'minute', 'second', 'ms'])) {
86
+ return `${f(date.hour, 2)}:${f(date.minute, 2)}:${f(date.second, 2)}.${date.ms}`;
87
+ }
88
+ if (only(date, ['year', 'month', 'day', 'hour', 'minute'])) {
89
+ return `${f(date.year, 4)}-${f(date.month, 2)}-${f(date.day, 2)}T${f(date.hour, 2)}:${f(date.minute, 2)}`;
90
+ }
91
+ if (only(date, ['year', 'month', 'day', 'hour', 'minute', 'second'])) {
92
+ return `${f(date.year, 4)}-${f(date.month, 2)}-${f(date.day, 2)}T${f(date.hour, 2)}:${f(date.minute, 2)}:${f(date.second, 2)}`;
93
+ }
94
+ if (only(date, ['year', 'month', 'day', 'hour', 'minute', 'second', 'ms'])) {
95
+ return `${f(date.year, 4)}-${f(date.month, 2)}-${f(date.day, 2)}T${f(date.hour, 2)}:${f(date.minute, 2)}:${f(date.second, 2)}.${date.ms}`;
96
+ }
97
+ return null;
98
+ }
99
+ function parseTryMultipleLangs(content, langs, base) {
100
+ for (const lang of langs) {
101
+ // eslint-disable-next-line import/namespace
102
+ const results = chrono[lang].casual.parse(content, base);
103
+ // Is not multiple datetime contents
104
+ if (results.length < 1) {
105
+ continue;
106
+ }
107
+ const result = results[0];
108
+ // Is not a range or period
109
+ if (result.end) {
110
+ continue;
111
+ }
112
+ return result.start;
113
+ }
114
+ return null;
115
+ }
116
+ function only(date, keys) {
117
+ const list = Object.keys(date);
118
+ for (const exists of list) {
119
+ if (!keys.includes(exists)) {
120
+ return false;
121
+ }
122
+ }
123
+ return true;
124
+ }
125
+ /**
126
+ * Formatter
127
+ *
128
+ * @param n
129
+ * @param pad zero padding
130
+ * @returns
131
+ */
132
+ function f(n, pad) {
133
+ return n.toString(10).padStart(pad, '0');
134
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/rules",
3
- "version": "3.0.0-dev.51+3b7ed6eb",
3
+ "version": "3.0.0-dev.95+599674b1",
4
4
  "description": "Built-in rules of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -20,11 +20,12 @@
20
20
  "./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
21
21
  },
22
22
  "dependencies": {
23
- "@markuplint/html-spec": "3.0.0-dev.51+3b7ed6eb",
24
- "@markuplint/ml-core": "3.0.0-dev.51+3b7ed6eb",
25
- "@markuplint/ml-spec": "3.0.0-dev.51+3b7ed6eb",
26
- "@markuplint/types": "3.0.0-dev.51+3b7ed6eb",
23
+ "@markuplint/html-spec": "3.0.0-dev.95+599674b1",
24
+ "@markuplint/ml-core": "3.0.0-dev.95+599674b1",
25
+ "@markuplint/ml-spec": "3.0.0-dev.95+599674b1",
26
+ "@markuplint/types": "3.0.0-dev.95+599674b1",
27
27
  "@ungap/structured-clone": "^1.0.1",
28
+ "chrono-node": "^2.5.0",
28
29
  "debug": "^4.3.4",
29
30
  "html-entities": "^2.3.3",
30
31
  "tslib": "^2.4.1"
@@ -32,5 +33,5 @@
32
33
  "devDependencies": {
33
34
  "@types/debug": "^4.1.7"
34
35
  },
35
- "gitHead": "3b7ed6eb7b92282165a191b63d8b5d2810cded2a"
36
+ "gitHead": "599674b17e532b5700e63c741217aba826e30feb"
36
37
  }
package/schema.json CHANGED
@@ -75,9 +75,15 @@
75
75
  "permitted-contents": {
76
76
  "$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/permitted-contents/schema.json"
77
77
  },
78
+ "placeholder-label-option": {
79
+ "$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/placeholder-label-option/schema.json"
80
+ },
78
81
  "require-accessible-name": {
79
82
  "$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/require-accessible-name/schema.json"
80
83
  },
84
+ "require-datetime": {
85
+ "$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/require-datetime/schema.json"
86
+ },
81
87
  "required-attr": {
82
88
  "$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/required-attr/schema.json"
83
89
  },