wikilint 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
  [![CodeQL](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/codeql.yml/badge.svg)](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/codeql.yml)
3
3
  [![CI](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/node.js.yml/badge.svg)](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/node.js.yml)
4
4
 
5
- # wikilint
5
+ # WikiLint
6
6
 
7
- This is a minimal version of [wikiparser-node](https://www.npmjs.com/package/wikiparser-node) customized for [eslint-plugin-wikitext](https://www.npmjs.com/package/eslint-plugin-wikitext).
7
+ This is a minimal version of [WikiParser-Node](https://www.npmjs.com/package/wikiparser-node) customized for [eslint-plugin-wikitext](https://www.npmjs.com/package/eslint-plugin-wikitext).
8
8
 
9
9
  You can also directly lint Wikitext articles in the command line using this package:
10
10
 
@@ -19,7 +19,7 @@ npx wikilint --config zhwiki --include --lang zh-hans *.wiki
19
19
  | `-c`, `--config` \<path or preset config\> | Choose parser's configuration | `default` |
20
20
  | `-h`, `--help` | Print available options | |
21
21
  | `-i`, `--include` | Parse for inclusion | no inclusion |
22
- | `-l`, `--lang` | Choose i18n language | English
22
+ | `-l`, `--lang` | Choose i18n language | English |
23
23
  | `-q`, `--quiet` | Report errors only | errors and warnings |
24
24
  | `-s`, `--strict` | Exit when there is an error or warning<br>Override `-q` or `--quiet` | Exit `1` only where there is an error |
25
25
  | `-v`, `--version` | Print package version | |
package/dist/base.d.ts CHANGED
@@ -11,7 +11,9 @@ export interface Config {
11
11
  readonly excludes?: string[];
12
12
  }
13
13
  export type Severity = 'error' | 'warning';
14
+ export type Rule = 'bold-header' | 'format-leakage' | 'fostered-content' | 'h1' | 'illegal-attr' | 'insecure-style' | 'invalid-gallery' | 'invalid-imagemap' | 'invalid-invoke' | 'lonely-apos' | 'lonely-bracket' | 'lonely-http' | 'nested-link' | 'no-arg' | 'no-duplicate' | 'no-ignored' | 'obsolete-attr' | 'obsolete-tag' | 'parsing-order' | 'pipe-like' | 'table-layout' | 'tag-like' | 'unbalanced-header' | 'unclosed-comment' | 'unclosed-quote' | 'unclosed-table' | 'unescaped' | 'unknown-page' | 'unmatched-tag' | 'unterminated-url' | 'url-encoding' | 'var-anchor' | 'void-ext';
14
15
  export interface LintError {
16
+ readonly rule: Rule;
15
17
  readonly message: string;
16
18
  readonly severity: Severity;
17
19
  readonly startIndex: number;
package/dist/bin/cli.js CHANGED
@@ -38,7 +38,7 @@ const plural = (n, word) => `${n} ${word}${n > 1 ? 's' : ''}`;
38
38
  * color the severity
39
39
  * @param severity problem severity
40
40
  */
41
- const coloredSeverity = (severity) => `\x1B[${severity === 'error' ? 31 : 33}m${severity}\x1B[0m`.padEnd(16);
41
+ const coloredSeverity = (severity) => `\x1B[${severity === 'error' ? 31 : 33}m${severity.padEnd(7)}\x1B[0m`;
42
42
  for (let i = 2; i < argv.length; i++) {
43
43
  option = argv[i];
44
44
  switch (option) {
@@ -116,9 +116,9 @@ for (const file of files) {
116
116
  }
117
117
  if (problems.length > 0) {
118
118
  console.error('\x1B[4m%s\x1B[0m', (0, path_1.resolve)(file));
119
- const { length: maxLineChars } = String(Math.max(...problems.map(({ startLine }) => startLine))), { length: maxColChars } = String(Math.max(...problems.map(({ startCol }) => startCol)));
120
- for (const { message, severity, startLine, startCol } of problems) {
121
- console.error(` ${String(startLine).padStart(maxLineChars)}:${String(startCol).padEnd(maxColChars)} ${coloredSeverity(severity)} ${message}`);
119
+ const { length: maxLineChars } = String(Math.max(...problems.map(({ startLine }) => startLine))), { length: maxColChars } = String(Math.max(...problems.map(({ startCol }) => startCol))), maxMessageChars = Math.max(...problems.map(({ message: { length } }) => length));
120
+ for (const { rule, message, severity, startLine, startCol } of problems) {
121
+ console.error(` \x1B[37m%s:%s\x1B[0m %s %s \x1B[37m%s\x1B[0m`, String(startLine).padStart(maxLineChars), String(startCol).padEnd(maxColChars), coloredSeverity(severity), message.padEnd(maxMessageChars), rule);
122
122
  }
123
123
  console.error();
124
124
  }
@@ -21,17 +21,13 @@ class AstElement extends node_1.AstNode {
21
21
  normalize() {
22
22
  const childNodes = [...this.childNodes];
23
23
  for (let i = childNodes.length - 1; i >= 0; i--) {
24
- const { type, data } = childNodes[i], prev = childNodes[i - 1];
24
+ const { type, data } = childNodes[i];
25
25
  if (type !== 'text' || this.getGaps(i - 1)) {
26
26
  //
27
27
  }
28
28
  else if (data === '') {
29
29
  childNodes.splice(i, 1);
30
30
  }
31
- else if (prev?.type === 'text') {
32
- prev.setAttribute('data', prev.data + data);
33
- childNodes.splice(i, 1);
34
- }
35
31
  }
36
32
  this.setAttribute('childNodes', childNodes);
37
33
  }
package/dist/lib/text.js CHANGED
@@ -17,6 +17,13 @@ errorSyntax = new RegExp(`${source}|https?[:/]\\/+`, 'giu'), errorSyntaxUrl = ne
17
17
  '{': /[{}]/u,
18
18
  ']': /[[\]](?=[^[\]]*$)/u,
19
19
  '}': /[{}](?=[^{}]*$)/u,
20
+ }, ruleMap = {
21
+ '<': 'tag-like',
22
+ '[': 'lonely-bracket',
23
+ '{': 'lonely-bracket',
24
+ ']': 'lonely-bracket',
25
+ '}': 'lonely-bracket',
26
+ h: 'lonely-http',
20
27
  }, disallowedTags = [
21
28
  'html',
22
29
  'head',
@@ -144,6 +151,7 @@ class AstText extends node_1.AstNode {
144
151
  }
145
152
  const lines = data.slice(0, index).split('\n'), startLine = lines.length + top - 1, line = lines[lines.length - 1], startCol = lines.length === 1 ? left + line.length : line.length;
146
153
  errors.push({
154
+ rule: ruleMap[char],
147
155
  message: index_1.default.msg('lonely "$1"', char === 'h' ? error : char),
148
156
  severity,
149
157
  startIndex,
package/dist/src/arg.js CHANGED
@@ -56,7 +56,7 @@ class ArgToken extends index_2.Token {
56
56
  /** @override */
57
57
  lint(start = this.getAbsoluteIndex()) {
58
58
  if (!this.getAttribute('include')) {
59
- return [(0, lint_1.generateForSelf)(this, { start }, 'unexpected template argument')];
59
+ return [(0, lint_1.generateForSelf)(this, { start }, 'no-arg', 'unexpected template argument')];
60
60
  }
61
61
  const { childNodes: [argName, argDefault, ...rest] } = this, errors = argName.lint(start + 3);
62
62
  if (argDefault) {
@@ -65,7 +65,7 @@ class ArgToken extends index_2.Token {
65
65
  if (rest.length > 0) {
66
66
  const rect = { start, ...this.getRootNode().posFromIndex(start) };
67
67
  errors.push(...rest.map(child => {
68
- const error = (0, lint_1.generateForChild)(child, rect, 'invisible content inside triple braces');
68
+ const error = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'invisible content inside triple braces');
69
69
  return {
70
70
  ...error,
71
71
  startIndex: error.startIndex - 1,
@@ -260,7 +260,7 @@ class AttributeToken extends index_2.Token {
260
260
  if (!balanced) {
261
261
  const root = this.getRootNode();
262
262
  rect = { start, ...root.posFromIndex(start) };
263
- const e = (0, lint_1.generateForChild)(lastChild, rect, index_1.default.msg('unclosed $1', 'quotes'), 'warning');
263
+ const e = (0, lint_1.generateForChild)(lastChild, rect, 'unclosed-quote', index_1.default.msg('unclosed $1', 'quotes'), 'warning');
264
264
  errors.push({
265
265
  ...e,
266
266
  startIndex: e.startIndex - 1,
@@ -274,19 +274,19 @@ class AttributeToken extends index_2.Token {
274
274
  && !/^(?:xmlns:[\w:.-]+|data-[^:]*)$/u.test(name)
275
275
  && (tag === 'meta' || tag === 'link' || !commonHtmlAttrs.has(name))) {
276
276
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
277
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'illegal attribute name'));
277
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, 'illegal-attr', 'illegal attribute name'));
278
278
  }
279
279
  else if (obsoleteAttrs[tag]?.has(name)) {
280
280
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
281
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete attribute', 'warning'));
281
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
282
282
  }
283
283
  else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
284
284
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
285
- errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure style'));
285
+ errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure-style', 'insecure style'));
286
286
  }
287
287
  else if (name === 'tabindex' && typeof value === 'string' && value.trim() !== '0') {
288
288
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
289
- errors.push((0, lint_1.generateForChild)(lastChild, rect, 'nonzero tabindex'));
289
+ errors.push((0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'nonzero tabindex'));
290
290
  }
291
291
  return errors;
292
292
  }
@@ -100,13 +100,13 @@ class AttributesToken extends index_2.Token {
100
100
  let rect;
101
101
  if (parentNode?.type === 'html' && parentNode.closing && this.text().trim()) {
102
102
  rect = { start, ...this.getRootNode().posFromIndex(start) };
103
- errors.push((0, lint_1.generateForSelf)(this, rect, 'attributes of a closing tag'));
103
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'no-ignored', 'attributes of a closing tag'));
104
104
  }
105
105
  for (let i = 0; i < length; i++) {
106
106
  const attr = childNodes[i];
107
107
  if (attr instanceof atom_1.AtomToken && attr.text().trim()) {
108
108
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
109
- errors.push((0, lint_1.generateForChild)(attr, rect, 'containing invalid attribute'));
109
+ errors.push((0, lint_1.generateForChild)(attr, rect, 'no-ignored', 'containing invalid attribute'));
110
110
  }
111
111
  else if (attr instanceof attribute_1.AttributeToken) {
112
112
  const { name } = attr;
@@ -122,7 +122,7 @@ class AttributesToken extends index_2.Token {
122
122
  if (duplicated.size > 0) {
123
123
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
124
124
  for (const key of duplicated) {
125
- errors.push(...attrs.get(key).map(attr => (0, lint_1.generateForChild)(attr, rect, index_1.default.msg('duplicated $1 attribute', key))));
125
+ errors.push(...attrs.get(key).map(attr => (0, lint_1.generateForChild)(attr, rect, 'no-duplicate', index_1.default.msg('duplicated $1 attribute', key))));
126
126
  }
127
127
  }
128
128
  return errors;
@@ -56,7 +56,7 @@ class ConverterFlagsToken extends index_2.Token {
56
56
  && !variantFlags.has(flag)
57
57
  && !unknownFlags.has(flag)
58
58
  && (variantFlags.size > 0 || !validFlags.has(flag))) {
59
- errors.push((0, lint_1.generateForChild)(child, rect, 'invalid conversion flag'));
59
+ errors.push((0, lint_1.generateForChild)(child, rect, 'no-ignored', 'invalid conversion flag'));
60
60
  }
61
61
  }
62
62
  return errors;
@@ -53,7 +53,7 @@ class ExtLinkToken extends index_2.Token {
53
53
  lint(start = this.getAbsoluteIndex()) {
54
54
  const errors = super.lint(start);
55
55
  if (this.length === 1 && this.closest('heading-title')) {
56
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'variable anchor in a section header'));
56
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'var-anchor', 'variable anchor in a section header'));
57
57
  }
58
58
  return errors;
59
59
  }
@@ -58,6 +58,7 @@ class GalleryToken extends index_2.Token {
58
58
  const child = this.childNodes[i], str = String(child), { length } = str, trimmed = str.trim(), startLine = top + i, startCol = i ? 0 : left;
59
59
  if (child.type === 'noinclude' && trimmed && !/^<!--.*-->$/u.test(trimmed)) {
60
60
  errors.push({
61
+ rule: 'no-ignored',
61
62
  message: index_1.default.msg('invalid content in <$1>', 'gallery'),
62
63
  severity: 'error',
63
64
  startIndex: start,
@@ -57,23 +57,23 @@ class HeadingToken extends index_2.Token {
57
57
  let rect;
58
58
  if (this.level === 1) {
59
59
  rect = { start, ...this.getRootNode().posFromIndex(start) };
60
- errors.push((0, lint_1.generateForChild)(firstChild, rect, '<h1>'));
60
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, 'h1', '<h1>'));
61
61
  }
62
62
  if (innerStr.startsWith('=') || innerStr.endsWith('=')) {
63
63
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
64
- errors.push((0, lint_1.generateForChild)(firstChild, rect, index_1.default.msg('unbalanced $1 in a section header', '"="')));
64
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, 'unbalanced-header', index_1.default.msg('unbalanced $1 in a section header', '"="')));
65
65
  }
66
66
  if (this.closest('html-attrs, table-attrs')) {
67
67
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
68
- errors.push((0, lint_1.generateForSelf)(this, rect, 'section header in a HTML tag'));
68
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'parsing-order', 'section header in a HTML tag'));
69
69
  }
70
70
  if (boldQuotes.length % 2) {
71
71
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
72
- errors.push((0, lint_1.generateForChild)(boldQuotes[boldQuotes.length - 1], { ...rect, start: start + level, left: rect.left + level }, index_1.default.msg('unbalanced $1 in a section header', 'bold apostrophes')));
72
+ errors.push((0, lint_1.generateForChild)(boldQuotes[boldQuotes.length - 1], { ...rect, start: start + level, left: rect.left + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'bold apostrophes')));
73
73
  }
74
74
  if (italicQuotes.length % 2) {
75
75
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
76
- errors.push((0, lint_1.generateForChild)(italicQuotes[italicQuotes.length - 1], { start: start + level }, index_1.default.msg('unbalanced $1 in a section header', 'italic apostrophes')));
76
+ errors.push((0, lint_1.generateForChild)(italicQuotes[italicQuotes.length - 1], { start: start + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'italic apostrophes')));
77
77
  }
78
78
  return errors;
79
79
  }
package/dist/src/html.js CHANGED
@@ -65,11 +65,8 @@ class HtmlToken extends index_2.Token {
65
65
  }
66
66
  /** @override */
67
67
  text() {
68
- const { closing, name } = this, tag = `${this.#tag}${closing ? '' : super.text()}`, { html } = this.getAttribute('config');
69
- if (html[2].includes(name)) {
70
- return closing && name !== 'br' ? '' : `<${tag}>`;
71
- }
72
- return `<${closing ? '/' : ''}${tag}${this.#selfClosing && html[1].includes(name) ? '/' : ''}>`;
68
+ const { closing, } = this, tag = `${this.#tag}${closing ? '' : super.text()}`;
69
+ return `<${closing ? '/' : ''}${tag}${this.#selfClosing ? '/' : ''}>`;
73
70
  }
74
71
  /** @private */
75
72
  getAttribute(key) {
@@ -82,13 +79,14 @@ class HtmlToken extends index_2.Token {
82
79
  const errors = super.lint(start);
83
80
  let refError;
84
81
  if (this.name === 'h1' && !this.closing) {
85
- refError = (0, lint_1.generateForSelf)(this, { start }, '<h1>');
82
+ refError = (0, lint_1.generateForSelf)(this, { start }, 'h1', '<h1>');
86
83
  errors.push(refError);
87
84
  }
88
85
  if (this.closest('table-attrs')) {
89
- refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
86
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
90
87
  errors.push({
91
88
  ...refError,
89
+ rule: 'parsing-order',
92
90
  message: index_1.default.msg('HTML tag in table attributes'),
93
91
  });
94
92
  }
@@ -98,8 +96,8 @@ class HtmlToken extends index_2.Token {
98
96
  catch (e) {
99
97
  if (e instanceof SyntaxError) {
100
98
  const { message } = e;
101
- refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
102
- const [msg] = message.split(':'), error = { ...refError, message: index_1.default.msg(msg) };
99
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
100
+ const [msg] = message.split(':'), error = { ...refError, rule: 'unmatched-tag', message: index_1.default.msg(msg) };
103
101
  if (msg === 'unclosed tag' && !this.closest('heading-title')) {
104
102
  if (formattingTags.has(this.name)) {
105
103
  const childNodes = this.parentNode?.childNodes, i = childNodes?.indexOf(this);
@@ -121,17 +119,19 @@ class HtmlToken extends index_2.Token {
121
119
  }
122
120
  }
123
121
  if (obsoleteTags.has(this.name)) {
124
- refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
122
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
125
123
  errors.push({
126
124
  ...refError,
125
+ rule: 'obsolete-tag',
127
126
  message: index_1.default.msg('obsolete HTML tag'),
128
127
  severity: 'warning',
129
128
  });
130
129
  }
131
130
  if ((this.name === 'b' || this.name === 'strong') && this.closest('heading-title')) {
132
- refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
131
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
133
132
  errors.push({
134
133
  ...refError,
134
+ rule: 'format-leakage',
135
135
  message: index_1.default.msg('bold in section header'),
136
136
  severity: 'warning',
137
137
  });
@@ -102,10 +102,10 @@ class ImageParameterToken extends index_2.Token {
102
102
  lint(start = this.getAbsoluteIndex()) {
103
103
  const errors = super.lint(start), { link, name } = this;
104
104
  if (name === 'invalid') {
105
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'invalid gallery image parameter'));
105
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'invalid-gallery', 'invalid gallery image parameter'));
106
106
  }
107
107
  else if (typeof link === 'object' && link.encoded) {
108
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'unnecessary URL encoding in an internal link'));
108
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'url-encoding', 'unnecessary URL encoding in an internal link'));
109
109
  }
110
110
  return errors;
111
111
  }
@@ -92,10 +92,10 @@ class ImagemapToken extends index_2.Token {
92
92
  errors.push(...this.childNodes.filter(child => {
93
93
  const str = String(child).trim();
94
94
  return child.type === 'noinclude' && str && !str.startsWith('#');
95
- }).map(child => (0, lint_1.generateForChild)(child, rect, 'invalid link in <imagemap>')));
95
+ }).map(child => (0, lint_1.generateForChild)(child, rect, 'invalid-imagemap', 'invalid link in <imagemap>')));
96
96
  }
97
97
  else {
98
- errors.push((0, lint_1.generateForSelf)(this, rect, '<imagemap> without an image'));
98
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'invalid-imagemap', '<imagemap> without an image'));
99
99
  }
100
100
  return errors;
101
101
  }
@@ -76,20 +76,20 @@ class LinkBaseToken extends index_2.Token {
76
76
  let rect;
77
77
  if (target.childNodes.some(({ type }) => type === 'template')) {
78
78
  rect = { start, ...this.getRootNode().posFromIndex(start) };
79
- errors.push((0, lint_1.generateForChild)(target, rect, 'template in an internal link target', 'warning'));
79
+ errors.push((0, lint_1.generateForChild)(target, rect, 'unknown-page', 'template in an internal link target', 'warning'));
80
80
  }
81
81
  if (encoded) {
82
82
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
83
- errors.push((0, lint_1.generateForChild)(target, rect, 'unnecessary URL encoding in an internal link'));
83
+ errors.push((0, lint_1.generateForChild)(target, rect, 'url-encoding', 'unnecessary URL encoding in an internal link'));
84
84
  }
85
85
  if ((linkType === 'link' || linkType === 'category')
86
86
  && linkText?.childNodes.some(({ type, data }) => type === 'text' && data.includes('|'))) {
87
87
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
88
- errors.push((0, lint_1.generateForChild)(linkText, rect, 'additional "|" in the link text', 'warning'));
88
+ errors.push((0, lint_1.generateForChild)(linkText, rect, 'pipe-like', 'additional "|" in the link text', 'warning'));
89
89
  }
90
90
  else if (linkType !== 'link' && fragment !== undefined) {
91
91
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
92
- errors.push((0, lint_1.generateForChild)(target, rect, 'useless fragment'));
92
+ errors.push((0, lint_1.generateForChild)(target, rect, 'no-ignored', 'useless fragment'));
93
93
  }
94
94
  return errors;
95
95
  }
@@ -58,7 +58,7 @@ class FileToken extends base_1.LinkBaseToken {
58
58
  return visibleNodes.length !== 1 || visibleNodes[0].type !== 'arg';
59
59
  }), keys = [...new Set(args.map(({ name }) => name))].filter(key => key !== 'invalid'), frameKeys = keys.filter(key => frame.has(key)), horizAlignKeys = keys.filter(key => horizAlign.has(key)), vertAlignKeys = keys.filter(key => vertAlign.has(key));
60
60
  if (this.closest('ext-link-text') && this.getValue('link')?.trim() !== '') {
61
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'internal link in an external link'));
61
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'nested-link', 'internal link in an external link'));
62
62
  }
63
63
  if (args.length === keys.length
64
64
  && frameKeys.length < 2
@@ -72,7 +72,7 @@ class FileToken extends base_1.LinkBaseToken {
72
72
  * @param msg 消息键
73
73
  * @param p1 替换$1
74
74
  */
75
- const generate = (msg, p1) => (arg) => (0, lint_1.generateForChild)(arg, rect, index_1.default.msg(`${msg} image $1 parameter`, p1));
75
+ const generate = (msg, p1) => (arg) => (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', index_1.default.msg(`${msg} image $1 parameter`, p1));
76
76
  for (const key of keys) {
77
77
  let relevantArgs = args.filter(({ name }) => name === key);
78
78
  if (key === 'caption') {
@@ -40,7 +40,7 @@ class GalleryImageToken extends file_1.FileToken {
40
40
  lint(start = this.getAbsoluteIndex()) {
41
41
  const errors = super.lint(start), { ns, interwiki } = this.getAttribute('title');
42
42
  if (interwiki || ns !== 6) {
43
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'invalid gallery image'));
43
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'invalid-gallery', 'invalid gallery image'));
44
44
  }
45
45
  return errors;
46
46
  }
@@ -13,7 +13,7 @@ class LinkToken extends base_1.LinkBaseToken {
13
13
  lint(start = this.getAbsoluteIndex()) {
14
14
  const errors = super.lint(start);
15
15
  if (this.closest('ext-link-text')) {
16
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'internal link in an external link'));
16
+ errors.push((0, lint_1.generateForSelf)(this, { start }, 'nested-link', 'internal link in an external link'));
17
17
  }
18
18
  return errors;
19
19
  }
@@ -27,7 +27,7 @@ class MagicLinkToken extends index_2.Token {
27
27
  continue;
28
28
  }
29
29
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
30
- const refError = (0, lint_1.generateForChild)(child, rect, '', 'warning');
30
+ const refError = (0, lint_1.generateForChild)(child, rect, 'unterminated-url', '', 'warning');
31
31
  regexGlobal.lastIndex = 0;
32
32
  for (let mt = regexGlobal.exec(data); mt; mt = regexGlobal.exec(data)) {
33
33
  const { index, 0: s } = mt, lines = data.slice(0, index).split('\n'), top = lines.length, left = lines[top - 1].length, startIndex = refError.startIndex + index, startLine = refError.startLine + top - 1, startCol = top === 1 ? refError.startCol + left : left;
@@ -50,7 +50,7 @@ class NestedToken extends index_2.Token {
50
50
  return str && !/^<!--.*-->$/su.test(str);
51
51
  }).map(child => {
52
52
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
53
- return (0, lint_1.generateForChild)(child, rect, index_1.default.msg('invalid content in <$1>', this.name));
53
+ return (0, lint_1.generateForChild)(child, rect, 'no-ignored', index_1.default.msg('invalid content in <$1>', this.name));
54
54
  }),
55
55
  ];
56
56
  }
@@ -20,7 +20,9 @@ class CommentToken extends (0, hidden_1.hiddenToken)(base_1.NowikiBaseToken) {
20
20
  }
21
21
  /** @override */
22
22
  lint(start = this.getAbsoluteIndex()) {
23
- return this.closed ? [] : [(0, lint_1.generateForSelf)(this, { start }, index_1.default.msg('unclosed $1', 'HTML comment'))];
23
+ return this.closed
24
+ ? []
25
+ : [(0, lint_1.generateForSelf)(this, { start }, 'unclosed-comment', index_1.default.msg('unclosed $1', 'HTML comment'))];
24
26
  }
25
27
  /** @private */
26
28
  toString() {
@@ -11,7 +11,7 @@ class NowikiToken extends base_1.NowikiBaseToken {
11
11
  lint(start = this.getAbsoluteIndex()) {
12
12
  const { name, firstChild: { data } } = this;
13
13
  return (name === 'templatestyles' || name === 'section') && data
14
- ? [(0, lint_1.generateForSelf)(this, { start }, index_1.default.msg('nothing should be in <$1>', name))]
14
+ ? [(0, lint_1.generateForSelf)(this, { start }, 'void-ext', index_1.default.msg('nothing should be in <$1>', name))]
15
15
  : super.lint(start);
16
16
  }
17
17
  }
@@ -20,7 +20,7 @@ class QuoteToken extends base_1.NowikiBaseToken {
20
20
  const { previousSibling, nextSibling, bold } = this, message = index_1.default.msg('lonely "$1"', `'`), errors = [];
21
21
  let refError;
22
22
  if (previousSibling?.type === 'text' && previousSibling.data.endsWith(`'`)) {
23
- refError = (0, lint_1.generateForSelf)(this, { start }, message);
23
+ refError = (0, lint_1.generateForSelf)(this, { start }, 'lonely-apos', message);
24
24
  const { startIndex: endIndex, startLine: endLine, startCol: endCol } = refError, [, { length }] = /(?:^|[^'])('+)$/u.exec(previousSibling.data), startIndex = start - length;
25
25
  errors.push({
26
26
  ...refError,
@@ -32,7 +32,7 @@ class QuoteToken extends base_1.NowikiBaseToken {
32
32
  });
33
33
  }
34
34
  if (nextSibling?.type === 'text' && nextSibling.data.startsWith(`'`)) {
35
- refError ??= (0, lint_1.generateForSelf)(this, { start }, message);
35
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'lonely-apos', message);
36
36
  const { endIndex: startIndex, endLine: startLine, endCol: startCol } = refError, [{ length }] = /^'+/u.exec(nextSibling.data), endIndex = startIndex + length;
37
37
  errors.push({
38
38
  ...refError,
@@ -44,9 +44,10 @@ class QuoteToken extends base_1.NowikiBaseToken {
44
44
  });
45
45
  }
46
46
  if (bold && this.closest('heading-title')) {
47
- refError ??= (0, lint_1.generateForSelf)(this, { start }, message);
47
+ refError ??= (0, lint_1.generateForSelf)(this, { start }, 'lonely-apos', message);
48
48
  errors.push({
49
49
  ...refError,
50
+ rule: 'bold-header',
50
51
  message: index_1.default.msg('bold in section header'),
51
52
  severity: 'warning',
52
53
  });
@@ -39,7 +39,7 @@ class ParamTagToken extends index_2.Token {
39
39
  return str && !(i >= 0 ? /^[a-z]+(?:\[\])?\s*(?:=|$)/iu : /^[a-z]+(?:\[\])?\s*=/iu).test(str);
40
40
  }).map(child => {
41
41
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
42
- return (0, lint_1.generateForChild)(child, rect, index_1.default.msg('invalid parameter of <$1>', this.name));
42
+ return (0, lint_1.generateForChild)(child, rect, 'no-ignored', index_1.default.msg('invalid parameter of <$1>', this.name));
43
43
  });
44
44
  }
45
45
  }
@@ -60,7 +60,7 @@ class ParameterToken extends index_2.Token {
60
60
  /https?:\/\/(?:\[[\da-f:.]+\]|[^[\]<>"\t\n\p{Zs}])(?:[^[\]<>"\0\t\n\p{Zs}]|\0\d+c\x7F)*$/iu;
61
61
  const errors = super.lint(start), { firstChild } = this, link = new RegExp(`https?://${string_1.extUrlCharFirst}${string_1.extUrlChar}$`, 'iu').exec(firstChild.text())?.[0];
62
62
  if (link && new URL(link).search) {
63
- const e = (0, lint_1.generateForChild)(firstChild, { start }, 'unescaped query string in an anonymous parameter');
63
+ const e = (0, lint_1.generateForChild)(firstChild, { start }, 'unescaped', 'unescaped query string in an anonymous parameter');
64
64
  errors.push({
65
65
  ...e,
66
66
  startIndex: e.endIndex,
@@ -28,7 +28,7 @@ class TableToken extends trBase_1.TrBaseToken {
28
28
  lint(start = this.getAbsoluteIndex()) {
29
29
  const errors = super.lint(start);
30
30
  if (!this.closed) {
31
- errors.push((0, lint_1.generateForChild)(this.firstChild, { start }, index_1.default.msg('unclosed $1', 'table')));
31
+ errors.push((0, lint_1.generateForChild)(this.firstChild, { start }, 'unclosed-table', index_1.default.msg('unclosed $1', 'table')));
32
32
  }
33
33
  return errors;
34
34
  }
@@ -81,7 +81,7 @@ class TdToken extends base_1.TableBaseToken {
81
81
  if (child.type === 'text') {
82
82
  const { data } = child;
83
83
  if (data.includes('|')) {
84
- errors.push((0, lint_1.generateForChild)(child, { start }, 'additional "|" in a table cell', data.includes('||') ? 'error' : 'warning'));
84
+ errors.push((0, lint_1.generateForChild)(child, { start }, 'pipe-like', 'additional "|" in a table cell', data.includes('||') ? 'error' : 'warning'));
85
85
  }
86
86
  }
87
87
  }
@@ -26,7 +26,7 @@ class TrBaseToken extends base_1.TableBaseToken {
26
26
  }
27
27
  catch { }
28
28
  }
29
- const error = (0, lint_1.generateForChild)(inter, { start }, 'content to be moved out from the table');
29
+ const error = (0, lint_1.generateForChild)(inter, { start }, 'fostered-content', 'content to be moved out from the table');
30
30
  errors.push({
31
31
  ...error,
32
32
  severity: first.type === 'template' ? 'warning' : 'error',
@@ -124,11 +124,11 @@ class ExtToken extends index_3.TagPairToken {
124
124
  let rect;
125
125
  if (this.name !== 'nowiki' && this.closest('html-attrs, table-attrs')) {
126
126
  rect = { start, ...this.getRootNode().posFromIndex(start) };
127
- errors.push((0, lint_1.generateForSelf)(this, rect, 'extension tag in HTML tag attributes'));
127
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'parsing-order', 'extension tag in HTML tag attributes'));
128
128
  }
129
129
  if (this.name === 'ref' && this.closest('heading-title')) {
130
130
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
131
- errors.push((0, lint_1.generateForSelf)(this, rect, 'variable anchor in a section header'));
131
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'var-anchor', 'variable anchor in a section header'));
132
132
  }
133
133
  return errors;
134
134
  }
@@ -22,7 +22,9 @@ class IncludeToken extends (0, hidden_1.hiddenToken)(index_2.TagPairToken) {
22
22
  }
23
23
  /** @override */
24
24
  lint(start = this.getAbsoluteIndex()) {
25
- return this.closed ? [] : [(0, lint_1.generateForSelf)(this, { start }, index_1.default.msg('unclosed $1', `<${this.name}>`))];
25
+ return this.closed
26
+ ? []
27
+ : [(0, lint_1.generateForSelf)(this, { start }, 'unclosed-comment', index_1.default.msg('unclosed $1', `<${this.name}>`))];
26
28
  }
27
29
  }
28
30
  exports.IncludeToken = IncludeToken;
@@ -166,21 +166,21 @@ class TranscludeToken extends index_2.Token {
166
166
  const title = this.#getTitle();
167
167
  if (title.fragment !== undefined) {
168
168
  rect = { start, ...this.getRootNode().posFromIndex(start) };
169
- errors.push((0, lint_1.generateForChild)(childNodes[type === 'template' ? 0 : 1], rect, 'useless fragment'));
169
+ errors.push((0, lint_1.generateForChild)(childNodes[type === 'template' ? 0 : 1], rect, 'no-ignored', 'useless fragment'));
170
170
  }
171
171
  if (!title.valid) {
172
172
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
173
- errors.push((0, lint_1.generateForChild)(childNodes[1], rect, 'illegal module name'));
173
+ errors.push((0, lint_1.generateForChild)(childNodes[1], rect, 'invalid-invoke', 'illegal module name'));
174
174
  }
175
175
  if (type === 'magic-word' && length === 2) {
176
176
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
177
- errors.push((0, lint_1.generateForSelf)(this, rect, 'missing module function'));
177
+ errors.push((0, lint_1.generateForSelf)(this, rect, 'invalid-invoke', 'missing module function'));
178
178
  return errors;
179
179
  }
180
180
  const duplicatedArgs = this.getDuplicatedArgs();
181
181
  if (duplicatedArgs.length > 0) {
182
182
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
183
- errors.push(...duplicatedArgs.flatMap(([, args]) => args).map(arg => (0, lint_1.generateForChild)(arg, rect, 'duplicated parameter')));
183
+ errors.push(...duplicatedArgs.flatMap(([, args]) => args).map(arg => (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', 'duplicated parameter')));
184
184
  }
185
185
  return errors;
186
186
  }
package/dist/util/lint.js CHANGED
@@ -6,9 +6,10 @@ const index_1 = require("../index");
6
6
  * 生成lint函数
7
7
  * @param func lint函数
8
8
  */
9
- const factory = (func) => (token, boundingRect, msg, severity = 'error') => {
9
+ const factory = (func) => (token, boundingRect, rule, msg, severity = 'error') => {
10
10
  const { start } = boundingRect, { top, left } = 'top' in boundingRect ? boundingRect : token.getRootNode().posFromIndex(start), { offsetHeight, offsetWidth } = token, { startIndex, startLine, startCol } = func(token, start, top, left);
11
11
  return {
12
+ rule,
12
13
  message: index_1.default.msg(msg),
13
14
  severity,
14
15
  startIndex,
package/i18n/zh-hans.json CHANGED
@@ -21,6 +21,7 @@
21
21
  "HTML tag in table attributes": "表格属性中的HTML标签",
22
22
  "illegal attribute name": "非法的属性名",
23
23
  "illegal module name": "非法的模块名称",
24
+ "inconsistent table layout": "不一致的表格布局",
24
25
  "insecure style": "不安全的样式",
25
26
  "internal link in an external link": "外链中的内链",
26
27
  "invalid content in <$1>": "<$1>内的无效内容",
package/i18n/zh-hant.json CHANGED
@@ -21,6 +21,7 @@
21
21
  "HTML tag in table attributes": "表格屬性中的HTML標籤",
22
22
  "illegal attribute name": "非法的屬性名",
23
23
  "illegal module name": "非法的模組名稱",
24
+ "inconsistent table layout": "不一致的表格佈局",
24
25
  "insecure style": "不安全的樣式",
25
26
  "internal link in an external link": "外部連結中的內部連結",
26
27
  "invalid content in <$1>": "<$1>內的無效內容",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -35,7 +35,7 @@
35
35
  "declaration": "grep -rl --include='*.d.ts' '@private' dist/ | xargs bash sed.sh -i -E '/^\\s+\\/\\*\\* @private/,+1d'; node ./dist/bin/declaration.js",
36
36
  "prepublishOnly": "npm run build && rm dist/internal.js dist/base.js dist/[bmpu]*/*.d.ts",
37
37
  "build": "bash build.sh",
38
- "diff": "git diff --ignore-all-space --color-moved",
38
+ "diff": "bash diff.sh",
39
39
  "diff:stat": "f() { git diff --stat --ignore-all-space --color=always $1 $2 -- . ':!extensions/' ':!bin/' | grep '\\.ts'; }; f",
40
40
  "lint:ts": "tsc --noEmit && eslint --cache .",
41
41
  "lint:json": "ajv -s config/.schema.json -d 'config/*.json' --strict=true --strict-required=false",