wikilint 2.4.4 → 2.5.0
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 +3 -3
- package/dist/base.d.ts +21 -9
- package/dist/bin/cli.js +36 -13
- package/dist/lib/text.js +57 -8
- package/dist/src/arg.js +26 -8
- package/dist/src/attribute.js +36 -10
- package/dist/src/attributes.js +13 -3
- package/dist/src/converterFlags.js +17 -1
- package/dist/src/extLink.js +1 -1
- package/dist/src/gallery.js +13 -0
- package/dist/src/heading.js +5 -5
- package/dist/src/html.js +27 -6
- package/dist/src/imageParameter.js +7 -2
- package/dist/src/imagemap.js +2 -2
- package/dist/src/link/base.js +28 -8
- package/dist/src/link/file.js +2 -2
- package/dist/src/link/galleryImage.js +1 -1
- package/dist/src/link/index.js +1 -1
- package/dist/src/magicLink.js +29 -5
- package/dist/src/nested.js +14 -1
- package/dist/src/nowiki/comment.js +9 -1
- package/dist/src/nowiki/index.js +9 -3
- package/dist/src/nowiki/quote.js +18 -3
- package/dist/src/paramTag/index.js +9 -1
- package/dist/src/parameter.js +11 -9
- package/dist/src/table/index.js +1 -1
- package/dist/src/table/td.js +18 -1
- package/dist/src/table/trBase.js +6 -8
- package/dist/src/tagPair/ext.js +2 -2
- package/dist/src/tagPair/include.js +9 -1
- package/dist/src/transclude.js +11 -4
- package/dist/util/diff.js +3 -2
- package/dist/util/lint.js +2 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
[](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/codeql.yml)
|
|
3
3
|
[](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/node.js.yml)
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# WikiLint
|
|
6
6
|
|
|
7
|
-
This is a minimal version of [
|
|
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
|
@@ -10,16 +10,28 @@ export interface Config {
|
|
|
10
10
|
readonly variants: string[];
|
|
11
11
|
readonly excludes?: string[];
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export declare namespace LintError {
|
|
14
|
+
type Severity = 'error' | 'warning';
|
|
15
|
+
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';
|
|
16
|
+
interface Fix {
|
|
17
|
+
readonly range: [number, number];
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
14
21
|
export interface LintError {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
rule: LintError.Rule;
|
|
23
|
+
message: string;
|
|
24
|
+
severity: LintError.Severity;
|
|
25
|
+
startIndex: number;
|
|
26
|
+
endIndex: number;
|
|
27
|
+
startLine: number;
|
|
28
|
+
startCol: number;
|
|
29
|
+
endLine: number;
|
|
30
|
+
endCol: number;
|
|
31
|
+
fix?: LintError.Fix;
|
|
32
|
+
suggestions?: (LintError.Fix & {
|
|
33
|
+
desc: string;
|
|
34
|
+
})[];
|
|
23
35
|
}
|
|
24
36
|
/** 类似Node */
|
|
25
37
|
export interface AstNode {
|
package/dist/bin/cli.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
+
const chalk = require("chalk");
|
|
6
7
|
const Parser = require("../index");
|
|
7
8
|
const man = `
|
|
8
9
|
Available options:
|
|
9
10
|
-c, --config <path or preset config> Choose parser's configuration
|
|
11
|
+
--fix Automatically fix problems
|
|
10
12
|
-h, --help Print available options
|
|
11
13
|
-i, --include Parse for inclusion
|
|
12
14
|
-l, --lang Choose i18n language
|
|
@@ -14,8 +16,8 @@ Available options:
|
|
|
14
16
|
-s, --strict Exit when there is an error or warning
|
|
15
17
|
Override -q or --quiet
|
|
16
18
|
-v, --version Print package version
|
|
17
|
-
`, preset = new Set(
|
|
18
|
-
let include = false, quiet = false, strict = false, exit = false, nErr = 0, nWarn = 0, option, config, lang;
|
|
19
|
+
`, preset = new Set((0, fs_1.readdirSync)('./config').filter(file => file.endsWith('.json')).map(file => file.slice(0, -5))), { argv } = process, files = [];
|
|
20
|
+
let include = false, quiet = false, strict = false, exit = false, fixing = false, nErr = 0, nWarn = 0, nFixableErr = 0, nFixableWarn = 0, option, config, lang;
|
|
19
21
|
/**
|
|
20
22
|
* throw if `-c` or `--config` option is incorrect
|
|
21
23
|
* @throws `Error` unrecognized config input
|
|
@@ -33,12 +35,12 @@ const throwOnConfig = () => {
|
|
|
33
35
|
* @param n number of items
|
|
34
36
|
* @param word item name
|
|
35
37
|
*/
|
|
36
|
-
const plural = (n, word) => `${n} ${word}${n
|
|
38
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
37
39
|
/**
|
|
38
40
|
* color the severity
|
|
39
41
|
* @param severity problem severity
|
|
40
42
|
*/
|
|
41
|
-
const coloredSeverity = (severity) =>
|
|
43
|
+
const coloredSeverity = (severity) => chalk[severity === 'error' ? 'red' : 'yellow'](severity.padEnd(7));
|
|
42
44
|
for (let i = 2; i < argv.length; i++) {
|
|
43
45
|
option = argv[i];
|
|
44
46
|
switch (option) {
|
|
@@ -47,6 +49,9 @@ for (let i = 2; i < argv.length; i++) {
|
|
|
47
49
|
config = argv[++i];
|
|
48
50
|
throwOnConfig();
|
|
49
51
|
break;
|
|
52
|
+
case '--fix':
|
|
53
|
+
fixing = true;
|
|
54
|
+
break;
|
|
50
55
|
case '-h':
|
|
51
56
|
case '--help':
|
|
52
57
|
console.log(man);
|
|
@@ -105,28 +110,46 @@ if (quiet && strict) {
|
|
|
105
110
|
console.error('-s or --strict will override -q or --quiet\n');
|
|
106
111
|
}
|
|
107
112
|
for (const file of files) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
let wikitext = (0, fs_1.readFileSync)(file, 'utf8'), problems = Parser.parse(wikitext, include).lint();
|
|
114
|
+
if (fixing && problems.some(({ fix }) => fix)) {
|
|
115
|
+
// 倒序修复,跳过嵌套的修复
|
|
116
|
+
const fixable = problems.map(({ fix }) => fix).filter(Boolean)
|
|
117
|
+
.sort(({ range: [aFrom, aTo] }, { range: [bFrom, bTo] }) => aTo === bTo ? bFrom - aFrom : bTo - aTo);
|
|
118
|
+
let start = Infinity;
|
|
119
|
+
for (const { range: [from, to], text } of fixable) {
|
|
120
|
+
if (to <= start) {
|
|
121
|
+
wikitext = `${wikitext.slice(0, from)}${text}${wikitext.slice(to)}`;
|
|
122
|
+
start = from;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
void fs_1.promises.writeFile(file, wikitext);
|
|
126
|
+
problems = Parser.parse(wikitext, include).lint();
|
|
127
|
+
}
|
|
128
|
+
const errors = problems.filter(({ severity }) => severity === 'error'), fixable = problems.filter(({ fix }) => fix), nLocalErr = errors.length, nLocalWarn = problems.length - nLocalErr, nLocalFixableErr = fixable.filter(({ severity }) => severity === 'error').length, nLocalFixableWarn = fixable.length - nLocalFixableErr;
|
|
111
129
|
if (quiet) {
|
|
112
130
|
problems = errors;
|
|
113
131
|
}
|
|
114
132
|
else {
|
|
115
133
|
nWarn += nLocalWarn;
|
|
134
|
+
nFixableWarn += nLocalFixableWarn;
|
|
116
135
|
}
|
|
117
136
|
if (problems.length > 0) {
|
|
118
|
-
console.error('
|
|
119
|
-
const
|
|
120
|
-
for (const { message, severity, startLine, startCol } of problems) {
|
|
121
|
-
console.error(` ${String(startLine).padStart(maxLineChars)
|
|
137
|
+
console.error(`\n${chalk.underline('%s')}`, (0, path_1.resolve)(file));
|
|
138
|
+
const maxLineChars = String(Math.max(...problems.map(({ startLine }) => startLine))).length, maxColChars = String(Math.max(...problems.map(({ startCol }) => startCol))).length, maxMessageChars = Math.max(...problems.map(({ message: { length } }) => length));
|
|
139
|
+
for (const { rule, message, severity, startLine, startCol } of problems) {
|
|
140
|
+
console.error(` ${chalk.dim('%s:%s')} %s %s ${chalk.dim('%s')}`, String(startLine).padStart(maxLineChars), String(startCol).padEnd(maxColChars), coloredSeverity(severity), message.padEnd(maxMessageChars), rule);
|
|
122
141
|
}
|
|
123
|
-
console.error();
|
|
124
142
|
}
|
|
125
143
|
nErr += nLocalErr;
|
|
144
|
+
nFixableErr += nLocalFixableErr;
|
|
126
145
|
exit ||= Boolean(nLocalErr || strict && nLocalWarn);
|
|
127
146
|
}
|
|
128
147
|
if (nErr || nWarn) {
|
|
129
|
-
console.error('
|
|
148
|
+
console.error(chalk.red.bold('%s'), `\n✖ ${plural(nErr + nWarn, 'problem')} (${plural(nErr, 'error')}, ${plural(nWarn, 'warning')})`);
|
|
149
|
+
if (nFixableErr || nFixableWarn) {
|
|
150
|
+
console.error(chalk.red.bold('%s'), ` ${plural(nFixableErr, 'error')} and ${plural(nFixableWarn, 'warning')} potentially fixable with the \`--fix\` option.`);
|
|
151
|
+
}
|
|
152
|
+
console.error();
|
|
130
153
|
}
|
|
131
154
|
if (exit) {
|
|
132
155
|
process.exitCode = 1;
|
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',
|
|
@@ -73,8 +80,11 @@ class AstText extends node_1.AstNode {
|
|
|
73
80
|
*/
|
|
74
81
|
lint(start = this.getAbsoluteIndex()) {
|
|
75
82
|
const { data, parentNode, nextSibling, previousSibling } = this;
|
|
83
|
+
if (!parentNode) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
76
86
|
const { NowikiToken } = require('../src/nowiki');
|
|
77
|
-
const { type, name } = parentNode, nowiki = name === 'nowiki' || name === 'pre';
|
|
87
|
+
const { type, name } = parentNode, nowiki = name === 'nowiki' || name === 'pre', isHtmlAttrVal = type === 'attr-value' && parentNode.parentNode.type !== 'ext-attr';
|
|
78
88
|
let errorRegex;
|
|
79
89
|
if (type === 'ext-inner' && (name === 'pre' || parentNode instanceof NowikiToken)) {
|
|
80
90
|
errorRegex = new RegExp(`<\\s*(?:\\/\\s*)${nowiki ? '' : '?'}(${name})\\b`, 'giu');
|
|
@@ -82,7 +92,7 @@ class AstText extends node_1.AstNode {
|
|
|
82
92
|
else if (type === 'free-ext-link'
|
|
83
93
|
|| type === 'ext-link-url'
|
|
84
94
|
|| type === 'image-parameter' && name === 'link'
|
|
85
|
-
||
|
|
95
|
+
|| isHtmlAttrVal) {
|
|
86
96
|
errorRegex = errorSyntaxUrl;
|
|
87
97
|
}
|
|
88
98
|
else {
|
|
@@ -114,14 +124,15 @@ class AstText extends node_1.AstNode {
|
|
|
114
124
|
else if (char === ']' && (index || length > 1)) {
|
|
115
125
|
errorRegex.lastIndex--;
|
|
116
126
|
}
|
|
117
|
-
const startIndex = start + index, endIndex = startIndex + length, rootStr = String(root), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && (char
|
|
127
|
+
const startIndex = start + index, endIndex = startIndex + length, rootStr = String(root), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && !(char === '<' && (nowiki || !/[\s/>]/u.test(nextChar ?? ''))
|
|
128
|
+
|| isHtmlAttrVal && (char === '[' || char === ']'))
|
|
118
129
|
|| char === '{' && (nextChar === char || previousChar === '-')
|
|
119
130
|
|| char === '}' && (previousChar === char || nextChar === '-')
|
|
120
131
|
|| char === '[' && (nextChar === char
|
|
121
132
|
|| type === 'ext-link-text'
|
|
122
|
-
|| !data.slice(index + 1).trim()
|
|
133
|
+
|| nextType === 'free-ext-link' && !data.slice(index + 1).trim())
|
|
123
134
|
|| char === ']' && (previousChar === char
|
|
124
|
-
|| !data.slice(0, index).
|
|
135
|
+
|| previousType === 'free-ext-link' && !data.slice(0, index).includes(']'))
|
|
125
136
|
? 'error'
|
|
126
137
|
: 'warning';
|
|
127
138
|
const leftBracket = char === '{' || char === '[', rightBracket = char === ']' || char === '}';
|
|
@@ -142,8 +153,8 @@ class AstText extends node_1.AstNode {
|
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
}
|
|
145
|
-
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
|
-
|
|
156
|
+
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, e = {
|
|
157
|
+
rule: ruleMap[char],
|
|
147
158
|
message: index_1.default.msg('lonely "$1"', char === 'h' ? error : char),
|
|
148
159
|
severity,
|
|
149
160
|
startIndex,
|
|
@@ -152,7 +163,45 @@ class AstText extends node_1.AstNode {
|
|
|
152
163
|
endLine: startLine,
|
|
153
164
|
startCol,
|
|
154
165
|
endCol: startCol + length,
|
|
155
|
-
}
|
|
166
|
+
};
|
|
167
|
+
if (char === '<') {
|
|
168
|
+
e.suggestions = [
|
|
169
|
+
{
|
|
170
|
+
desc: 'escape',
|
|
171
|
+
range: [startIndex, startIndex + 1],
|
|
172
|
+
text: '<',
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
else if (char === 'h'
|
|
177
|
+
&& !(type === 'ext-link-text' || type === 'link-text')
|
|
178
|
+
&& /[\p{L}\d_]/u.test(previousChar || '')) {
|
|
179
|
+
e.suggestions = [
|
|
180
|
+
{
|
|
181
|
+
desc: 'whitespace',
|
|
182
|
+
range: [startIndex, startIndex],
|
|
183
|
+
text: ' ',
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
else if (char === '[' && type === 'ext-link-text') {
|
|
188
|
+
const i = parentNode.getAbsoluteIndex() + String(parentNode).length;
|
|
189
|
+
e.suggestions = [
|
|
190
|
+
{
|
|
191
|
+
desc: 'escape',
|
|
192
|
+
range: [i, i + 1],
|
|
193
|
+
text: ']',
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
else if (char === ']' && previousType === 'free-ext-link' && severity === 'error') {
|
|
198
|
+
const i = start - String(previousSibling).length;
|
|
199
|
+
e.fix = {
|
|
200
|
+
range: [i, i],
|
|
201
|
+
text: '[',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
errors.push(e);
|
|
156
205
|
}
|
|
157
206
|
return errors;
|
|
158
207
|
}
|
package/dist/src/arg.js
CHANGED
|
@@ -55,22 +55,40 @@ class ArgToken extends index_2.Token {
|
|
|
55
55
|
}
|
|
56
56
|
/** @override */
|
|
57
57
|
lint(start = this.getAbsoluteIndex()) {
|
|
58
|
+
const { childNodes: [argName, argDefault, ...rest] } = this;
|
|
58
59
|
if (!this.getAttribute('include')) {
|
|
59
|
-
|
|
60
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'no-arg', 'unexpected template argument');
|
|
61
|
+
if (argDefault) {
|
|
62
|
+
e.fix = {
|
|
63
|
+
range: [start, e.endIndex],
|
|
64
|
+
text: argDefault.text(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return [e];
|
|
60
68
|
}
|
|
61
|
-
const
|
|
69
|
+
const errors = argName.lint(start + 3);
|
|
62
70
|
if (argDefault) {
|
|
63
71
|
errors.push(...argDefault.lint(start + 4 + String(argName).length));
|
|
64
72
|
}
|
|
65
73
|
if (rest.length > 0) {
|
|
66
74
|
const rect = { start, ...this.getRootNode().posFromIndex(start) };
|
|
67
75
|
errors.push(...rest.map(child => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'invisible content inside triple braces');
|
|
77
|
+
e.startIndex--;
|
|
78
|
+
e.startCol--;
|
|
79
|
+
e.suggestions = [
|
|
80
|
+
{
|
|
81
|
+
desc: 'remove',
|
|
82
|
+
range: [e.startIndex, e.endIndex],
|
|
83
|
+
text: '',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
desc: 'escape',
|
|
87
|
+
range: [e.startIndex, e.startIndex + 1],
|
|
88
|
+
text: '{{!}}',
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
return e;
|
|
74
92
|
}));
|
|
75
93
|
}
|
|
76
94
|
return errors;
|
package/dist/src/attribute.js
CHANGED
|
@@ -260,12 +260,25 @@ 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');
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
const e = (0, lint_1.generateForChild)(lastChild, rect, 'unclosed-quote', index_1.default.msg('unclosed $1', 'quotes'), 'warning');
|
|
264
|
+
e.startIndex--;
|
|
265
|
+
e.startCol--;
|
|
266
|
+
const fix = {
|
|
267
|
+
range: [e.endIndex, e.endIndex],
|
|
268
|
+
text: this.#quotes[0],
|
|
269
|
+
};
|
|
270
|
+
if (lastChild.childNodes.some(child => child.type === 'text' && /\s/u.test(child.text()))) {
|
|
271
|
+
e.suggestions = [
|
|
272
|
+
{
|
|
273
|
+
desc: 'quote',
|
|
274
|
+
...fix,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
e.fix = fix;
|
|
280
|
+
}
|
|
281
|
+
errors.push(e);
|
|
269
282
|
}
|
|
270
283
|
const attrs = extAttrs[tag];
|
|
271
284
|
if (attrs && !attrs.has(name)
|
|
@@ -274,19 +287,32 @@ class AttributeToken extends index_2.Token {
|
|
|
274
287
|
&& !/^(?:xmlns:[\w:.-]+|data-[^:]*)$/u.test(name)
|
|
275
288
|
&& (tag === 'meta' || tag === 'link' || !commonHtmlAttrs.has(name))) {
|
|
276
289
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
277
|
-
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'illegal attribute name'));
|
|
290
|
+
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'illegal-attr', 'illegal attribute name'));
|
|
278
291
|
}
|
|
279
292
|
else if (obsoleteAttrs[tag]?.has(name)) {
|
|
280
293
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
281
|
-
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete attribute', 'warning'));
|
|
294
|
+
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
|
|
282
295
|
}
|
|
283
296
|
else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
|
|
284
297
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
285
|
-
errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure style'));
|
|
298
|
+
errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure-style', 'insecure style'));
|
|
286
299
|
}
|
|
287
300
|
else if (name === 'tabindex' && typeof value === 'string' && value.trim() !== '0') {
|
|
288
301
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
289
|
-
|
|
302
|
+
const e = (0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'nonzero tabindex');
|
|
303
|
+
e.suggestions = [
|
|
304
|
+
{
|
|
305
|
+
desc: 'remove',
|
|
306
|
+
range: [start, e.endIndex],
|
|
307
|
+
text: '',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
desc: '0 tabindex',
|
|
311
|
+
range: [e.startIndex, e.endIndex],
|
|
312
|
+
text: '0',
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
errors.push(e);
|
|
290
316
|
}
|
|
291
317
|
return errors;
|
|
292
318
|
}
|
package/dist/src/attributes.js
CHANGED
|
@@ -100,13 +100,23 @@ 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
|
-
|
|
103
|
+
const e = (0, lint_1.generateForSelf)(this, rect, 'no-ignored', 'attributes of a closing tag');
|
|
104
|
+
e.fix = { range: [start, e.endIndex], text: '' };
|
|
105
|
+
errors.push(e);
|
|
104
106
|
}
|
|
105
107
|
for (let i = 0; i < length; i++) {
|
|
106
108
|
const attr = childNodes[i];
|
|
107
109
|
if (attr instanceof atom_1.AtomToken && attr.text().trim()) {
|
|
108
110
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
109
|
-
|
|
111
|
+
const e = (0, lint_1.generateForChild)(attr, rect, 'no-ignored', 'containing invalid attribute');
|
|
112
|
+
e.suggestions = [
|
|
113
|
+
{
|
|
114
|
+
desc: 'remove',
|
|
115
|
+
range: [e.startIndex, e.endIndex],
|
|
116
|
+
text: ' ',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
errors.push(e);
|
|
110
120
|
}
|
|
111
121
|
else if (attr instanceof attribute_1.AttributeToken) {
|
|
112
122
|
const { name } = attr;
|
|
@@ -122,7 +132,7 @@ class AttributesToken extends index_2.Token {
|
|
|
122
132
|
if (duplicated.size > 0) {
|
|
123
133
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
124
134
|
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))));
|
|
135
|
+
errors.push(...attrs.get(key).map(attr => (0, lint_1.generateForChild)(attr, rect, 'no-duplicate', index_1.default.msg('duplicated $1 attribute', key))));
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
return errors;
|
|
@@ -56,7 +56,23 @@ 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
|
-
|
|
59
|
+
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'invalid conversion flag');
|
|
60
|
+
if (variantFlags.size === 0 && definedFlags.has(flag.toUpperCase())) {
|
|
61
|
+
e.fix = {
|
|
62
|
+
range: [e.startIndex, e.endIndex],
|
|
63
|
+
text: flag.toUpperCase(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
e.suggestions = [
|
|
68
|
+
{
|
|
69
|
+
desc: 'remove',
|
|
70
|
+
range: [e.startIndex, e.endIndex],
|
|
71
|
+
text: '',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
errors.push(e);
|
|
60
76
|
}
|
|
61
77
|
}
|
|
62
78
|
return errors;
|
package/dist/src/extLink.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/gallery.js
CHANGED
|
@@ -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,
|
|
@@ -66,6 +67,18 @@ class GalleryToken extends index_2.Token {
|
|
|
66
67
|
endLine: startLine,
|
|
67
68
|
startCol,
|
|
68
69
|
endCol: startCol + length,
|
|
70
|
+
suggestions: [
|
|
71
|
+
{
|
|
72
|
+
desc: 'remove',
|
|
73
|
+
range: [start, start + length],
|
|
74
|
+
text: '',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
desc: 'comment',
|
|
78
|
+
range: [start, start + length],
|
|
79
|
+
text: `<!--${str}-->`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
69
82
|
});
|
|
70
83
|
}
|
|
71
84
|
else if (child.type !== 'noinclude' && child.type !== 'text') {
|
package/dist/src/heading.js
CHANGED
|
@@ -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
|
@@ -79,13 +79,14 @@ class HtmlToken extends index_2.Token {
|
|
|
79
79
|
const errors = super.lint(start);
|
|
80
80
|
let refError;
|
|
81
81
|
if (this.name === 'h1' && !this.closing) {
|
|
82
|
-
refError = (0, lint_1.generateForSelf)(this, { start }, '<h1>');
|
|
82
|
+
refError = (0, lint_1.generateForSelf)(this, { start }, 'h1', '<h1>');
|
|
83
83
|
errors.push(refError);
|
|
84
84
|
}
|
|
85
85
|
if (this.closest('table-attrs')) {
|
|
86
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
|
|
86
|
+
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
|
|
87
87
|
errors.push({
|
|
88
88
|
...refError,
|
|
89
|
+
rule: 'parsing-order',
|
|
89
90
|
message: index_1.default.msg('HTML tag in table attributes'),
|
|
90
91
|
});
|
|
91
92
|
}
|
|
@@ -95,8 +96,8 @@ class HtmlToken extends index_2.Token {
|
|
|
95
96
|
catch (e) {
|
|
96
97
|
if (e instanceof SyntaxError) {
|
|
97
98
|
const { message } = e;
|
|
98
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
|
|
99
|
-
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) };
|
|
100
101
|
if (msg === 'unclosed tag' && !this.closest('heading-title')) {
|
|
101
102
|
if (formattingTags.has(this.name)) {
|
|
102
103
|
const childNodes = this.parentNode?.childNodes, i = childNodes?.indexOf(this);
|
|
@@ -113,22 +114,42 @@ class HtmlToken extends index_2.Token {
|
|
|
113
114
|
if (ancestor && magicWords.has(ancestor.name)) {
|
|
114
115
|
error.severity = 'warning';
|
|
115
116
|
}
|
|
117
|
+
else {
|
|
118
|
+
error.suggestions = [
|
|
119
|
+
{
|
|
120
|
+
desc: 'remove',
|
|
121
|
+
range: [start, error.endIndex],
|
|
122
|
+
text: '',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (msg === 'tag that is both closing and self-closing') {
|
|
128
|
+
const { html: [, , voidTags] } = this.getAttribute('config');
|
|
129
|
+
if (voidTags.includes(this.name)) {
|
|
130
|
+
error.fix = {
|
|
131
|
+
range: [start + 1, start + 2],
|
|
132
|
+
text: '',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
116
135
|
}
|
|
117
136
|
errors.push(error);
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
139
|
if (obsoleteTags.has(this.name)) {
|
|
121
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
|
|
140
|
+
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
|
|
122
141
|
errors.push({
|
|
123
142
|
...refError,
|
|
143
|
+
rule: 'obsolete-tag',
|
|
124
144
|
message: index_1.default.msg('obsolete HTML tag'),
|
|
125
145
|
severity: 'warning',
|
|
126
146
|
});
|
|
127
147
|
}
|
|
128
148
|
if ((this.name === 'b' || this.name === 'strong') && this.closest('heading-title')) {
|
|
129
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, '');
|
|
149
|
+
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
|
|
130
150
|
errors.push({
|
|
131
151
|
...refError,
|
|
152
|
+
rule: 'bold-header',
|
|
132
153
|
message: index_1.default.msg('bold in section header'),
|
|
133
154
|
severity: 'warning',
|
|
134
155
|
});
|
|
@@ -102,10 +102,15 @@ 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
|
-
|
|
105
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'invalid-gallery', 'invalid gallery image parameter');
|
|
106
|
+
e.fix = {
|
|
107
|
+
range: [start, start + e.endIndex],
|
|
108
|
+
text: '',
|
|
109
|
+
};
|
|
110
|
+
errors.push(e);
|
|
106
111
|
}
|
|
107
112
|
else if (typeof link === 'object' && link.encoded) {
|
|
108
|
-
errors.push((0, lint_1.generateForSelf)(this, { start }, 'unnecessary URL encoding in an internal link'));
|
|
113
|
+
errors.push((0, lint_1.generateForSelf)(this, { start }, 'url-encoding', 'unnecessary URL encoding in an internal link'));
|
|
109
114
|
}
|
|
110
115
|
return errors;
|
|
111
116
|
}
|
package/dist/src/imagemap.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/link/base.js
CHANGED
|
@@ -76,20 +76,40 @@ 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
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (linkType === 'link' || linkType === 'category') {
|
|
86
|
+
const textNode = linkText?.childNodes.find((c) => c.type === 'text' && c.data.includes('|'));
|
|
87
|
+
if (textNode) {
|
|
88
|
+
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
89
|
+
const e = (0, lint_1.generateForChild)(linkText, rect, 'pipe-like', 'additional "|" in the link text', 'warning');
|
|
90
|
+
e.suggestions = [
|
|
91
|
+
{
|
|
92
|
+
desc: 'escape',
|
|
93
|
+
range: [
|
|
94
|
+
e.startIndex + textNode.getRelativeIndex(),
|
|
95
|
+
e.startIndex + textNode.getRelativeIndex() + textNode.data.length,
|
|
96
|
+
],
|
|
97
|
+
text: textNode.data.replace(/\|/gu, '|'),
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
errors.push(e);
|
|
101
|
+
}
|
|
89
102
|
}
|
|
90
|
-
|
|
103
|
+
if (linkType !== 'link' && fragment !== undefined) {
|
|
91
104
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
92
|
-
|
|
105
|
+
const e = (0, lint_1.generateForChild)(target, rect, 'no-ignored', 'useless fragment'), textNode = target.childNodes.find((c) => c.type === 'text' && c.data.includes('#'));
|
|
106
|
+
if (textNode) {
|
|
107
|
+
e.fix = {
|
|
108
|
+
range: [e.startIndex + textNode.getRelativeIndex() + textNode.data.indexOf('#'), e.endIndex],
|
|
109
|
+
text: '',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
errors.push(e);
|
|
93
113
|
}
|
|
94
114
|
return errors;
|
|
95
115
|
}
|
package/dist/src/link/file.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/link/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/magicLink.js
CHANGED
|
@@ -27,20 +27,44 @@ 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
|
-
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;
|
|
34
|
-
|
|
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, pipe = s.startsWith('|');
|
|
34
|
+
const e = {
|
|
35
35
|
...refError,
|
|
36
|
-
message: index_1.default.msg('$1 in URL',
|
|
36
|
+
message: index_1.default.msg('$1 in URL', pipe ? '"|"' : 'full-width punctuation'),
|
|
37
37
|
startIndex,
|
|
38
38
|
endIndex: startIndex + s.length,
|
|
39
39
|
startLine,
|
|
40
40
|
endLine: startLine,
|
|
41
41
|
startCol,
|
|
42
42
|
endCol: startCol + s.length,
|
|
43
|
-
}
|
|
43
|
+
};
|
|
44
|
+
if (!pipe) {
|
|
45
|
+
e.suggestions = [
|
|
46
|
+
{
|
|
47
|
+
desc: 'whitespace',
|
|
48
|
+
range: [startIndex, startIndex],
|
|
49
|
+
text: ' ',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
desc: 'escape',
|
|
53
|
+
range: [startIndex, e.endIndex],
|
|
54
|
+
text: encodeURI(s),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
else if (s.length === 1) {
|
|
59
|
+
e.suggestions = [
|
|
60
|
+
{
|
|
61
|
+
desc: 'whitespace',
|
|
62
|
+
range: [startIndex, startIndex + 1],
|
|
63
|
+
text: ' ',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
errors.push(e);
|
|
44
68
|
}
|
|
45
69
|
}
|
|
46
70
|
return errors;
|
package/dist/src/nested.js
CHANGED
|
@@ -50,7 +50,20 @@ 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
|
-
|
|
53
|
+
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', index_1.default.msg('invalid content in <$1>', this.name));
|
|
54
|
+
e.suggestions = [
|
|
55
|
+
{
|
|
56
|
+
desc: 'remove',
|
|
57
|
+
range: [e.startIndex, e.endIndex],
|
|
58
|
+
text: '',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
desc: 'comment',
|
|
62
|
+
range: [e.startIndex, e.startIndex],
|
|
63
|
+
text: `<!--${String(child)}-->`,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
return e;
|
|
54
67
|
}),
|
|
55
68
|
];
|
|
56
69
|
}
|
|
@@ -20,7 +20,15 @@ class CommentToken extends (0, hidden_1.hiddenToken)(base_1.NowikiBaseToken) {
|
|
|
20
20
|
}
|
|
21
21
|
/** @override */
|
|
22
22
|
lint(start = this.getAbsoluteIndex()) {
|
|
23
|
-
|
|
23
|
+
if (this.closed) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'unclosed-comment', index_1.default.msg('unclosed $1', 'HTML comment'));
|
|
27
|
+
e.fix = {
|
|
28
|
+
range: [e.endIndex, e.endIndex],
|
|
29
|
+
text: '-->',
|
|
30
|
+
};
|
|
31
|
+
return [e];
|
|
24
32
|
}
|
|
25
33
|
/** @private */
|
|
26
34
|
toString() {
|
package/dist/src/nowiki/index.js
CHANGED
|
@@ -10,9 +10,15 @@ class NowikiToken extends base_1.NowikiBaseToken {
|
|
|
10
10
|
/** @override */
|
|
11
11
|
lint(start = this.getAbsoluteIndex()) {
|
|
12
12
|
const { name, firstChild: { data } } = this;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if ((name === 'templatestyles' || name === 'section') && data) {
|
|
14
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'void-ext', index_1.default.msg('nothing should be in <$1>', name));
|
|
15
|
+
e.fix = {
|
|
16
|
+
range: [start - 1, e.endIndex + name.length + 3],
|
|
17
|
+
text: '/>',
|
|
18
|
+
};
|
|
19
|
+
return [e];
|
|
20
|
+
}
|
|
21
|
+
return super.lint(start);
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
exports.NowikiToken = NowikiToken;
|
package/dist/src/nowiki/quote.js
CHANGED
|
@@ -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,
|
|
@@ -29,10 +29,17 @@ class QuoteToken extends base_1.NowikiBaseToken {
|
|
|
29
29
|
startCol: endCol - length,
|
|
30
30
|
endLine,
|
|
31
31
|
endCol,
|
|
32
|
+
suggestions: [
|
|
33
|
+
{
|
|
34
|
+
desc: 'escape',
|
|
35
|
+
range: [startIndex, endIndex],
|
|
36
|
+
text: '''.repeat(length),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
32
39
|
});
|
|
33
40
|
}
|
|
34
41
|
if (nextSibling?.type === 'text' && nextSibling.data.startsWith(`'`)) {
|
|
35
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, message);
|
|
42
|
+
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'lonely-apos', message);
|
|
36
43
|
const { endIndex: startIndex, endLine: startLine, endCol: startCol } = refError, [{ length }] = /^'+/u.exec(nextSibling.data), endIndex = startIndex + length;
|
|
37
44
|
errors.push({
|
|
38
45
|
...refError,
|
|
@@ -41,12 +48,20 @@ class QuoteToken extends base_1.NowikiBaseToken {
|
|
|
41
48
|
startLine,
|
|
42
49
|
startCol,
|
|
43
50
|
endCol: startCol + length,
|
|
51
|
+
suggestions: [
|
|
52
|
+
{
|
|
53
|
+
desc: 'escape',
|
|
54
|
+
range: [startIndex, endIndex],
|
|
55
|
+
text: '''.repeat(length),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
44
58
|
});
|
|
45
59
|
}
|
|
46
60
|
if (bold && this.closest('heading-title')) {
|
|
47
|
-
refError ??= (0, lint_1.generateForSelf)(this, { start }, message);
|
|
61
|
+
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'lonely-apos', message);
|
|
48
62
|
errors.push({
|
|
49
63
|
...refError,
|
|
64
|
+
rule: 'bold-header',
|
|
50
65
|
message: index_1.default.msg('bold in section header'),
|
|
51
66
|
severity: 'warning',
|
|
52
67
|
});
|
|
@@ -39,7 +39,15 @@ 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
|
-
|
|
42
|
+
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', index_1.default.msg('invalid parameter of <$1>', this.name));
|
|
43
|
+
e.suggestions = [
|
|
44
|
+
{
|
|
45
|
+
desc: 'remove',
|
|
46
|
+
range: [e.startIndex, e.endIndex],
|
|
47
|
+
text: '',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
return e;
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
53
|
}
|
package/dist/src/parameter.js
CHANGED
|
@@ -60,15 +60,17 @@ 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');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const e = (0, lint_1.generateForChild)(firstChild, { start }, 'unescaped', 'unescaped query string in an anonymous parameter');
|
|
64
|
+
e.startIndex = e.endIndex;
|
|
65
|
+
e.startLine = e.endLine;
|
|
66
|
+
e.startCol = e.endCol;
|
|
67
|
+
e.endIndex++;
|
|
68
|
+
e.endCol++;
|
|
69
|
+
e.fix = {
|
|
70
|
+
range: [e.startIndex, e.endIndex],
|
|
71
|
+
text: '{{=}}',
|
|
72
|
+
};
|
|
73
|
+
errors.push(e);
|
|
72
74
|
}
|
|
73
75
|
return errors;
|
|
74
76
|
}
|
package/dist/src/table/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/table/td.js
CHANGED
|
@@ -81,7 +81,24 @@ class TdToken extends base_1.TableBaseToken {
|
|
|
81
81
|
if (child.type === 'text') {
|
|
82
82
|
const { data } = child;
|
|
83
83
|
if (data.includes('|')) {
|
|
84
|
-
|
|
84
|
+
const isError = data.includes('||'), e = (0, lint_1.generateForChild)(child, { start }, 'pipe-like', 'additional "|" in a table cell', isError ? 'error' : 'warning');
|
|
85
|
+
if (isError) {
|
|
86
|
+
const syntax = { caption: '|+', td: '|', th: '!' }[this.subtype];
|
|
87
|
+
e.fix = {
|
|
88
|
+
range: [e.startIndex, e.endIndex],
|
|
89
|
+
text: data.replace(/\|\|/gu, `\n${syntax}`),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
e.suggestions = [
|
|
94
|
+
{
|
|
95
|
+
desc: 'escape',
|
|
96
|
+
range: [e.startIndex, e.endIndex],
|
|
97
|
+
text: data.replace(/\|/gu, '|'),
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
errors.push(e);
|
|
85
102
|
}
|
|
86
103
|
}
|
|
87
104
|
}
|
package/dist/src/table/trBase.js
CHANGED
|
@@ -26,14 +26,12 @@ 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');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
startCol: 0,
|
|
36
|
-
});
|
|
29
|
+
const error = (0, lint_1.generateForChild)(inter, { start }, 'fostered-content', 'content to be moved out from the table');
|
|
30
|
+
error.severity = first.type === 'template' ? 'warning' : 'error';
|
|
31
|
+
error.startIndex++;
|
|
32
|
+
error.startLine++;
|
|
33
|
+
error.startCol = 0;
|
|
34
|
+
errors.push(error);
|
|
37
35
|
return errors;
|
|
38
36
|
}
|
|
39
37
|
}
|
package/dist/src/tagPair/ext.js
CHANGED
|
@@ -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,15 @@ class IncludeToken extends (0, hidden_1.hiddenToken)(index_2.TagPairToken) {
|
|
|
22
22
|
}
|
|
23
23
|
/** @override */
|
|
24
24
|
lint(start = this.getAbsoluteIndex()) {
|
|
25
|
-
|
|
25
|
+
if (this.closed) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'unclosed-comment', index_1.default.msg('unclosed $1', `<${this.name}>`));
|
|
29
|
+
e.fix = {
|
|
30
|
+
range: [e.endIndex, e.endIndex],
|
|
31
|
+
text: `</${this.name}>`,
|
|
32
|
+
};
|
|
33
|
+
return [e];
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
exports.IncludeToken = IncludeToken;
|
package/dist/src/transclude.js
CHANGED
|
@@ -166,21 +166,28 @@ 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
|
-
|
|
169
|
+
const child = childNodes[type === 'template' ? 0 : 1], e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'useless fragment'), textNode = child.childNodes.find((c) => c.type === 'text' && c.data.includes('#'));
|
|
170
|
+
if (textNode) {
|
|
171
|
+
e.fix = {
|
|
172
|
+
range: [e.startIndex + textNode.getRelativeIndex() + textNode.data.indexOf('#'), e.endIndex],
|
|
173
|
+
text: '',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
errors.push(e);
|
|
170
177
|
}
|
|
171
178
|
if (!title.valid) {
|
|
172
179
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
173
|
-
errors.push((0, lint_1.generateForChild)(childNodes[1], rect, 'illegal module name'));
|
|
180
|
+
errors.push((0, lint_1.generateForChild)(childNodes[1], rect, 'invalid-invoke', 'illegal module name'));
|
|
174
181
|
}
|
|
175
182
|
if (type === 'magic-word' && length === 2) {
|
|
176
183
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
177
|
-
errors.push((0, lint_1.generateForSelf)(this, rect, 'missing module function'));
|
|
184
|
+
errors.push((0, lint_1.generateForSelf)(this, rect, 'invalid-invoke', 'missing module function'));
|
|
178
185
|
return errors;
|
|
179
186
|
}
|
|
180
187
|
const duplicatedArgs = this.getDuplicatedArgs();
|
|
181
188
|
if (duplicatedArgs.length > 0) {
|
|
182
189
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
183
|
-
errors.push(...duplicatedArgs.flatMap(([, args]) => args).map(arg => (0, lint_1.generateForChild)(arg, rect, 'duplicated parameter')));
|
|
190
|
+
errors.push(...duplicatedArgs.flatMap(([, args]) => args).map(arg => (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', 'duplicated parameter')));
|
|
184
191
|
}
|
|
185
192
|
return errors;
|
|
186
193
|
}
|
package/dist/util/diff.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.info = exports.error = exports.diff = exports.cmd = void 0;
|
|
4
4
|
const fs = require("fs/promises");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
|
+
const chalk = require("chalk");
|
|
6
7
|
process.on('unhandledRejection', e => {
|
|
7
8
|
console.error(e);
|
|
8
9
|
});
|
|
@@ -72,11 +73,11 @@ const diff = async (oldStr, newStr, uid = -1) => {
|
|
|
72
73
|
exports.diff = diff;
|
|
73
74
|
/** @implements */
|
|
74
75
|
const error = (msg, ...args) => {
|
|
75
|
-
console.error(
|
|
76
|
+
console.error(chalk.red(msg), ...args);
|
|
76
77
|
};
|
|
77
78
|
exports.error = error;
|
|
78
79
|
/** @implements */
|
|
79
80
|
const info = (msg, ...args) => {
|
|
80
|
-
console.info(
|
|
81
|
+
console.info(chalk.green(msg), ...args);
|
|
81
82
|
};
|
|
82
83
|
exports.info = info;
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikilint",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "A Node.js linter for MediaWiki markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
|
52
52
|
"@typescript-eslint/parser": "^6.19.1",
|
|
53
53
|
"ajv-cli": "^5.0.0",
|
|
54
|
+
"chalk": "^4.1.2",
|
|
54
55
|
"eslint": "^8.56.0",
|
|
55
56
|
"eslint-plugin-es-x": "^7.5.0",
|
|
56
57
|
"eslint-plugin-eslint-comments": "^3.2.0",
|