wikilint 2.4.5 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/default.json +1 -1
- package/config/enwiki.json +1 -1
- package/config/llwiki.json +1 -1
- package/config/minimum.json +3 -6
- package/config/zhwiki.json +1 -1
- package/dist/base.d.ts +23 -11
- package/dist/base.js +38 -0
- package/dist/bin/cli.js +35 -12
- package/dist/index.js +2 -0
- package/dist/lib/text.js +49 -8
- package/dist/parser/list.js +24 -12
- package/dist/src/arg.js +26 -8
- package/dist/src/attribute.js +34 -7
- package/dist/src/attributes.js +12 -2
- package/dist/src/converterFlags.js +17 -1
- package/dist/src/gallery.js +12 -0
- package/dist/src/html.d.ts +2 -0
- package/dist/src/html.js +27 -5
- package/dist/src/imageParameter.js +6 -1
- package/dist/src/link/base.js +26 -6
- package/dist/src/magicLink.js +28 -4
- package/dist/src/nested.js +14 -1
- package/dist/src/nowiki/comment.js +9 -3
- package/dist/src/nowiki/index.js +9 -3
- package/dist/src/nowiki/quote.js +14 -0
- package/dist/src/paramTag/index.js +9 -1
- package/dist/src/parameter.js +10 -8
- package/dist/src/table/td.js +18 -1
- package/dist/src/table/trBase.js +5 -7
- package/dist/src/tagPair/include.js +12 -3
- package/dist/src/transclude.js +8 -1
- package/dist/util/diff.js +3 -2
- package/dist/util/string.js +3 -2
- package/package.json +3 -2
package/config/default.json
CHANGED
|
@@ -767,7 +767,7 @@
|
|
|
767
767
|
"EXPECTED_UNCONNECTED_PAGE"
|
|
768
768
|
]
|
|
769
769
|
],
|
|
770
|
-
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
770
|
+
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
771
771
|
"interwiki": [],
|
|
772
772
|
"img": {
|
|
773
773
|
"thumbnail": "thumbnail",
|
package/config/enwiki.json
CHANGED
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
"EXPECTED_UNCONNECTED_PAGE"
|
|
370
370
|
]
|
|
371
371
|
],
|
|
372
|
-
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
372
|
+
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
373
373
|
"interwiki": [],
|
|
374
374
|
"img": {
|
|
375
375
|
"thumbnail": "thumbnail",
|
package/config/llwiki.json
CHANGED
|
@@ -518,7 +518,7 @@
|
|
|
518
518
|
"静态重定向"
|
|
519
519
|
]
|
|
520
520
|
],
|
|
521
|
-
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
521
|
+
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
522
522
|
"interwiki": [],
|
|
523
523
|
"img": {
|
|
524
524
|
"thumbnail": "thumbnail",
|
package/config/minimum.json
CHANGED
|
@@ -66,8 +66,7 @@
|
|
|
66
66
|
"wbr",
|
|
67
67
|
"hr",
|
|
68
68
|
"meta",
|
|
69
|
-
"link"
|
|
70
|
-
"img"
|
|
69
|
+
"link"
|
|
71
70
|
]
|
|
72
71
|
],
|
|
73
72
|
"namespaces": {},
|
|
@@ -82,6 +81,7 @@
|
|
|
82
81
|
"#speciale": "speciale",
|
|
83
82
|
"#tag": "tag",
|
|
84
83
|
"#formatdate": "formatdate",
|
|
84
|
+
"#dateformat": "formatdate",
|
|
85
85
|
"#invoke": "invoke",
|
|
86
86
|
"#while": "while",
|
|
87
87
|
"#dowhile": "dowhile",
|
|
@@ -118,13 +118,10 @@
|
|
|
118
118
|
],
|
|
119
119
|
[
|
|
120
120
|
"msg",
|
|
121
|
-
"原始",
|
|
122
121
|
"raw"
|
|
123
122
|
],
|
|
124
123
|
[
|
|
125
|
-
"替代",
|
|
126
124
|
"subst",
|
|
127
|
-
"安全替代",
|
|
128
125
|
"safesubst"
|
|
129
126
|
]
|
|
130
127
|
],
|
|
@@ -132,7 +129,7 @@
|
|
|
132
129
|
[],
|
|
133
130
|
[]
|
|
134
131
|
],
|
|
135
|
-
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
132
|
+
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
136
133
|
"interwiki": [],
|
|
137
134
|
"img": {},
|
|
138
135
|
"variants": []
|
package/config/zhwiki.json
CHANGED
|
@@ -707,7 +707,7 @@
|
|
|
707
707
|
"EXPECTED_UNCONNECTED_PAGE"
|
|
708
708
|
]
|
|
709
709
|
],
|
|
710
|
-
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
710
|
+
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
|
711
711
|
"interwiki": [],
|
|
712
712
|
"img": {
|
|
713
713
|
"thumbnail": "thumbnail",
|
package/dist/base.d.ts
CHANGED
|
@@ -10,18 +10,29 @@ export interface Config {
|
|
|
10
10
|
readonly variants: string[];
|
|
11
11
|
readonly excludes?: string[];
|
|
12
12
|
}
|
|
13
|
-
export
|
|
14
|
-
export
|
|
13
|
+
export declare const rules: readonly ["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
|
+
export declare namespace LintError {
|
|
15
|
+
type Severity = 'error' | 'warning';
|
|
16
|
+
type Rule = typeof rules[number];
|
|
17
|
+
interface Fix {
|
|
18
|
+
readonly range: [number, number];
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
15
22
|
export interface LintError {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
rule: LintError.Rule;
|
|
24
|
+
message: string;
|
|
25
|
+
severity: LintError.Severity;
|
|
26
|
+
startIndex: number;
|
|
27
|
+
endIndex: number;
|
|
28
|
+
startLine: number;
|
|
29
|
+
startCol: number;
|
|
30
|
+
endLine: number;
|
|
31
|
+
endCol: number;
|
|
32
|
+
fix?: LintError.Fix;
|
|
33
|
+
suggestions?: (LintError.Fix & {
|
|
34
|
+
desc: string;
|
|
35
|
+
})[];
|
|
25
36
|
}
|
|
26
37
|
/** 类似Node */
|
|
27
38
|
export interface AstNode {
|
|
@@ -36,6 +47,7 @@ interface AstElement extends AstNode {
|
|
|
36
47
|
export interface Parser {
|
|
37
48
|
config: string | Config;
|
|
38
49
|
i18n: string | Record<string, string> | undefined;
|
|
50
|
+
rules: readonly LintError.Rule[];
|
|
39
51
|
/** 获取解析设置 */
|
|
40
52
|
getConfig(): Config;
|
|
41
53
|
/**
|
package/dist/base.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rules = void 0;
|
|
4
|
+
exports.rules = [
|
|
5
|
+
'bold-header',
|
|
6
|
+
'format-leakage',
|
|
7
|
+
'fostered-content',
|
|
8
|
+
'h1',
|
|
9
|
+
'illegal-attr',
|
|
10
|
+
'insecure-style',
|
|
11
|
+
'invalid-gallery',
|
|
12
|
+
'invalid-imagemap',
|
|
13
|
+
'invalid-invoke',
|
|
14
|
+
'lonely-apos',
|
|
15
|
+
'lonely-bracket',
|
|
16
|
+
'lonely-http',
|
|
17
|
+
'nested-link',
|
|
18
|
+
'no-arg',
|
|
19
|
+
'no-duplicate',
|
|
20
|
+
'no-ignored',
|
|
21
|
+
'obsolete-attr',
|
|
22
|
+
'obsolete-tag',
|
|
23
|
+
'parsing-order',
|
|
24
|
+
'pipe-like',
|
|
25
|
+
'table-layout',
|
|
26
|
+
'tag-like',
|
|
27
|
+
'unbalanced-header',
|
|
28
|
+
'unclosed-comment',
|
|
29
|
+
'unclosed-quote',
|
|
30
|
+
'unclosed-table',
|
|
31
|
+
'unescaped',
|
|
32
|
+
'unknown-page',
|
|
33
|
+
'unmatched-tag',
|
|
34
|
+
'unterminated-url',
|
|
35
|
+
'url-encoding',
|
|
36
|
+
'var-anchor',
|
|
37
|
+
'void-ext',
|
|
38
|
+
];
|
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
|
|
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));
|
|
120
139
|
for (const { rule, message, severity, startLine, startCol } of problems) {
|
|
121
|
-
console.error(`
|
|
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/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* eslint n/exports-style: 0 */
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const base_1 = require("./base");
|
|
5
6
|
const debug_1 = require("./util/debug");
|
|
6
7
|
const constants_1 = require("./util/constants");
|
|
7
8
|
const string_1 = require("./util/string");
|
|
@@ -15,6 +16,7 @@ const rootRequire = (file, dir) => require(file.startsWith('/') ? file : `../${f
|
|
|
15
16
|
const Parser = {
|
|
16
17
|
config: 'default',
|
|
17
18
|
i18n: undefined,
|
|
19
|
+
rules: base_1.rules,
|
|
18
20
|
/** @implements */
|
|
19
21
|
getConfig() {
|
|
20
22
|
if (typeof this.config === 'string') {
|
package/dist/lib/text.js
CHANGED
|
@@ -80,8 +80,11 @@ class AstText extends node_1.AstNode {
|
|
|
80
80
|
*/
|
|
81
81
|
lint(start = this.getAbsoluteIndex()) {
|
|
82
82
|
const { data, parentNode, nextSibling, previousSibling } = this;
|
|
83
|
+
if (!parentNode) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
83
86
|
const { NowikiToken } = require('../src/nowiki');
|
|
84
|
-
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';
|
|
85
88
|
let errorRegex;
|
|
86
89
|
if (type === 'ext-inner' && (name === 'pre' || parentNode instanceof NowikiToken)) {
|
|
87
90
|
errorRegex = new RegExp(`<\\s*(?:\\/\\s*)${nowiki ? '' : '?'}(${name})\\b`, 'giu');
|
|
@@ -89,7 +92,7 @@ class AstText extends node_1.AstNode {
|
|
|
89
92
|
else if (type === 'free-ext-link'
|
|
90
93
|
|| type === 'ext-link-url'
|
|
91
94
|
|| type === 'image-parameter' && name === 'link'
|
|
92
|
-
||
|
|
95
|
+
|| isHtmlAttrVal) {
|
|
93
96
|
errorRegex = errorSyntaxUrl;
|
|
94
97
|
}
|
|
95
98
|
else {
|
|
@@ -121,14 +124,15 @@ class AstText extends node_1.AstNode {
|
|
|
121
124
|
else if (char === ']' && (index || length > 1)) {
|
|
122
125
|
errorRegex.lastIndex--;
|
|
123
126
|
}
|
|
124
|
-
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 === ']'))
|
|
125
129
|
|| char === '{' && (nextChar === char || previousChar === '-')
|
|
126
130
|
|| char === '}' && (previousChar === char || nextChar === '-')
|
|
127
131
|
|| char === '[' && (nextChar === char
|
|
128
132
|
|| type === 'ext-link-text'
|
|
129
|
-
|| !data.slice(index + 1).trim()
|
|
133
|
+
|| nextType === 'free-ext-link' && !data.slice(index + 1).trim())
|
|
130
134
|
|| char === ']' && (previousChar === char
|
|
131
|
-
|| !data.slice(0, index).
|
|
135
|
+
|| previousType === 'free-ext-link' && !data.slice(0, index).includes(']'))
|
|
132
136
|
? 'error'
|
|
133
137
|
: 'warning';
|
|
134
138
|
const leftBracket = char === '{' || char === '[', rightBracket = char === ']' || char === '}';
|
|
@@ -149,8 +153,7 @@ class AstText extends node_1.AstNode {
|
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
}
|
|
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
|
|
153
|
-
errors.push({
|
|
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 = {
|
|
154
157
|
rule: ruleMap[char],
|
|
155
158
|
message: index_1.default.msg('lonely "$1"', char === 'h' ? error : char),
|
|
156
159
|
severity,
|
|
@@ -160,7 +163,45 @@ class AstText extends node_1.AstNode {
|
|
|
160
163
|
endLine: startLine,
|
|
161
164
|
startCol,
|
|
162
165
|
endCol: startCol + length,
|
|
163
|
-
}
|
|
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);
|
|
164
205
|
}
|
|
165
206
|
return errors;
|
|
166
207
|
}
|
package/dist/parser/list.js
CHANGED
|
@@ -22,7 +22,8 @@ const parseList = (wikitext, config = index_1.default.getConfig(), accum = []) =
|
|
|
22
22
|
if (!dt) {
|
|
23
23
|
return text;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const { html: [normalTags] } = config, fullRegex = /:+|-\{|\0\d+x\x7F/gu;
|
|
26
|
+
let regex = fullRegex, ex = regex.exec(text), lt = 0, lc = 0;
|
|
26
27
|
/**
|
|
27
28
|
* 创建`DdToken`
|
|
28
29
|
* @param syntax `:`
|
|
@@ -35,15 +36,7 @@ const parseList = (wikitext, config = index_1.default.getConfig(), accum = []) =
|
|
|
35
36
|
};
|
|
36
37
|
while (ex && dt) {
|
|
37
38
|
const { 0: syntax, index } = ex;
|
|
38
|
-
if (syntax
|
|
39
|
-
if (syntax.length >= dt) {
|
|
40
|
-
return dd(syntax.slice(0, dt), index);
|
|
41
|
-
}
|
|
42
|
-
dt -= syntax.length;
|
|
43
|
-
regex.lastIndex = index + 4 + String(accum.length).length;
|
|
44
|
-
text = dd(syntax, index);
|
|
45
|
-
}
|
|
46
|
-
else if (syntax === '-{') {
|
|
39
|
+
if (syntax === '-{') {
|
|
47
40
|
if (!lc) {
|
|
48
41
|
const { lastIndex } = regex;
|
|
49
42
|
regex = /-\{|\}-/gu;
|
|
@@ -51,14 +44,33 @@ const parseList = (wikitext, config = index_1.default.getConfig(), accum = []) =
|
|
|
51
44
|
}
|
|
52
45
|
lc++;
|
|
53
46
|
}
|
|
54
|
-
else {
|
|
47
|
+
else if (syntax === '}-') {
|
|
55
48
|
lc--;
|
|
56
49
|
if (!lc) {
|
|
57
50
|
const { lastIndex } = regex;
|
|
58
|
-
regex =
|
|
51
|
+
regex = fullRegex;
|
|
59
52
|
regex.lastIndex = lastIndex;
|
|
60
53
|
}
|
|
61
54
|
}
|
|
55
|
+
else if (syntax.startsWith('\0')) {
|
|
56
|
+
const { name, closing, selfClosing } = accum[Number(syntax.slice(1, -2))];
|
|
57
|
+
if (!selfClosing || normalTags.includes(name)) {
|
|
58
|
+
if (!closing) {
|
|
59
|
+
lt++;
|
|
60
|
+
}
|
|
61
|
+
else if (lt) {
|
|
62
|
+
lt--;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (lt === 0) { // syntax === ':'
|
|
67
|
+
if (syntax.length >= dt) {
|
|
68
|
+
return dd(syntax.slice(0, dt), index);
|
|
69
|
+
}
|
|
70
|
+
dt -= syntax.length;
|
|
71
|
+
regex.lastIndex = index + 4 + String(accum.length).length;
|
|
72
|
+
text = dd(syntax, index);
|
|
73
|
+
}
|
|
62
74
|
ex = regex.exec(text);
|
|
63
75
|
}
|
|
64
76
|
return text;
|
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
|
@@ -20,6 +20,7 @@ const commonHtmlAttrs = new Set([
|
|
|
20
20
|
'aria-hidden',
|
|
21
21
|
'aria-label',
|
|
22
22
|
'aria-labelledby',
|
|
23
|
+
'aria-level',
|
|
23
24
|
'aria-owns',
|
|
24
25
|
'role',
|
|
25
26
|
'about',
|
|
@@ -145,7 +146,7 @@ const commonHtmlAttrs = new Set([
|
|
|
145
146
|
combobox: new Set(['placeholder', 'value', 'id', 'class', 'text', 'dropdown', 'style']),
|
|
146
147
|
}, insecureStyle = new RegExp('expression'
|
|
147
148
|
+ '|'
|
|
148
|
-
+ '(?:
|
|
149
|
+
+ '(?:accelerator|-o-link(?:-source)?|-o-replace)\\s*:'
|
|
149
150
|
+ '|'
|
|
150
151
|
+ '(?:url|image(?:-set)?)\\s*\\('
|
|
151
152
|
+ '|'
|
|
@@ -261,11 +262,24 @@ class AttributeToken extends index_2.Token {
|
|
|
261
262
|
const root = this.getRootNode();
|
|
262
263
|
rect = { start, ...root.posFromIndex(start) };
|
|
263
264
|
const e = (0, lint_1.generateForChild)(lastChild, rect, 'unclosed-quote', index_1.default.msg('unclosed $1', 'quotes'), 'warning');
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
e.startIndex--;
|
|
266
|
+
e.startCol--;
|
|
267
|
+
const fix = {
|
|
268
|
+
range: [e.endIndex, e.endIndex],
|
|
269
|
+
text: this.#quotes[0],
|
|
270
|
+
};
|
|
271
|
+
if (lastChild.childNodes.some(child => child.type === 'text' && /\s/u.test(child.text()))) {
|
|
272
|
+
e.suggestions = [
|
|
273
|
+
{
|
|
274
|
+
desc: 'close',
|
|
275
|
+
...fix,
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
e.fix = fix;
|
|
281
|
+
}
|
|
282
|
+
errors.push(e);
|
|
269
283
|
}
|
|
270
284
|
const attrs = extAttrs[tag];
|
|
271
285
|
if (attrs && !attrs.has(name)
|
|
@@ -286,7 +300,20 @@ class AttributeToken extends index_2.Token {
|
|
|
286
300
|
}
|
|
287
301
|
else if (name === 'tabindex' && typeof value === 'string' && value.trim() !== '0') {
|
|
288
302
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
289
|
-
|
|
303
|
+
const e = (0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'nonzero tabindex');
|
|
304
|
+
e.suggestions = [
|
|
305
|
+
{
|
|
306
|
+
desc: 'remove',
|
|
307
|
+
range: [start, e.endIndex],
|
|
308
|
+
text: '',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
desc: '0 tabindex',
|
|
312
|
+
range: [e.startIndex, e.endIndex],
|
|
313
|
+
text: '0',
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
errors.push(e);
|
|
290
317
|
}
|
|
291
318
|
return errors;
|
|
292
319
|
}
|
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;
|
|
@@ -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/gallery.js
CHANGED
|
@@ -67,6 +67,18 @@ class GalleryToken extends index_2.Token {
|
|
|
67
67
|
endLine: startLine,
|
|
68
68
|
startCol,
|
|
69
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
|
+
],
|
|
70
82
|
});
|
|
71
83
|
}
|
|
72
84
|
else if (child.type !== 'noinclude' && child.type !== 'text') {
|
package/dist/src/html.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare abstract class HtmlToken extends Token {
|
|
|
13
13
|
readonly childNodes: readonly [AttributesToken];
|
|
14
14
|
abstract get firstChild(): AttributesToken;
|
|
15
15
|
abstract get lastChild(): AttributesToken;
|
|
16
|
+
/** 是否自封闭 */
|
|
17
|
+
get selfClosing(): boolean;
|
|
16
18
|
/** 是否是闭合标签 */
|
|
17
19
|
get closing(): boolean;
|
|
18
20
|
/**
|
package/dist/src/html.js
CHANGED
|
@@ -41,6 +41,10 @@ class HtmlToken extends index_2.Token {
|
|
|
41
41
|
#closing;
|
|
42
42
|
#selfClosing;
|
|
43
43
|
#tag;
|
|
44
|
+
/** 是否自封闭 */
|
|
45
|
+
get selfClosing() {
|
|
46
|
+
return this.#selfClosing;
|
|
47
|
+
}
|
|
44
48
|
/** 是否是闭合标签 */
|
|
45
49
|
get closing() {
|
|
46
50
|
return this.#closing;
|
|
@@ -114,6 +118,24 @@ class HtmlToken extends index_2.Token {
|
|
|
114
118
|
if (ancestor && magicWords.has(ancestor.name)) {
|
|
115
119
|
error.severity = 'warning';
|
|
116
120
|
}
|
|
121
|
+
else {
|
|
122
|
+
error.suggestions = [
|
|
123
|
+
{
|
|
124
|
+
desc: 'remove',
|
|
125
|
+
range: [start, error.endIndex],
|
|
126
|
+
text: '',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (msg === 'tag that is both closing and self-closing') {
|
|
132
|
+
const { html: [, , voidTags] } = this.getAttribute('config');
|
|
133
|
+
if (voidTags.includes(this.name)) {
|
|
134
|
+
error.fix = {
|
|
135
|
+
range: [start + 1, start + 2],
|
|
136
|
+
text: '',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
117
139
|
}
|
|
118
140
|
errors.push(error);
|
|
119
141
|
}
|
|
@@ -131,7 +153,7 @@ class HtmlToken extends index_2.Token {
|
|
|
131
153
|
refError ??= (0, lint_1.generateForSelf)(this, { start }, 'h1', '');
|
|
132
154
|
errors.push({
|
|
133
155
|
...refError,
|
|
134
|
-
rule: '
|
|
156
|
+
rule: 'bold-header',
|
|
135
157
|
message: index_1.default.msg('bold in section header'),
|
|
136
158
|
severity: 'warning',
|
|
137
159
|
});
|
|
@@ -145,14 +167,14 @@ class HtmlToken extends index_2.Token {
|
|
|
145
167
|
* @throws `SyntaxError` 未匹配的标签
|
|
146
168
|
*/
|
|
147
169
|
findMatchingTag() {
|
|
148
|
-
const { html } = this.getAttribute('config'), { name: tagName, parentNode, closing } = this, string = (0, string_1.noWrap)(String(this));
|
|
149
|
-
if (closing && (this.#selfClosing ||
|
|
170
|
+
const { html: [normalTags, flexibleTags, voidTags] } = this.getAttribute('config'), { name: tagName, parentNode, closing } = this, string = (0, string_1.noWrap)(String(this));
|
|
171
|
+
if (closing && (this.#selfClosing || voidTags.includes(tagName))) {
|
|
150
172
|
throw new SyntaxError(`tag that is both closing and self-closing: ${string}`);
|
|
151
173
|
}
|
|
152
|
-
else if (
|
|
174
|
+
else if (voidTags.includes(tagName) || this.#selfClosing && flexibleTags.includes(tagName)) { // 自封闭标签
|
|
153
175
|
return this;
|
|
154
176
|
}
|
|
155
|
-
else if (this.#selfClosing &&
|
|
177
|
+
else if (this.#selfClosing && normalTags.includes(tagName)) {
|
|
156
178
|
throw new SyntaxError(`invalid self-closing tag: ${string}`);
|
|
157
179
|
}
|
|
158
180
|
else if (!parentNode) {
|
|
@@ -102,7 +102,12 @@ 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
113
|
errors.push((0, lint_1.generateForSelf)(this, { start }, 'url-encoding', 'unnecessary URL encoding in an internal link'));
|
package/dist/src/link/base.js
CHANGED
|
@@ -82,14 +82,34 @@ class LinkBaseToken extends index_2.Token {
|
|
|
82
82
|
rect ??= { start, ...this.getRootNode().posFromIndex(start) };
|
|
83
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/magicLink.js
CHANGED
|
@@ -30,17 +30,41 @@ class MagicLinkToken extends index_2.Token {
|
|
|
30
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,9 +20,15 @@ class CommentToken extends (0, hidden_1.hiddenToken)(base_1.NowikiBaseToken) {
|
|
|
20
20
|
}
|
|
21
21
|
/** @override */
|
|
22
22
|
lint(start = this.getAbsoluteIndex()) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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];
|
|
26
32
|
}
|
|
27
33
|
/** @private */
|
|
28
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
|
@@ -29,6 +29,13 @@ 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(`'`)) {
|
|
@@ -41,6 +48,13 @@ 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')) {
|
|
@@ -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
|
@@ -61,14 +61,16 @@ class ParameterToken extends index_2.Token {
|
|
|
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
63
|
const e = (0, lint_1.generateForChild)(firstChild, { start }, 'unescaped', 'unescaped query string in an anonymous parameter');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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/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
|
@@ -27,13 +27,11 @@ class TrBaseToken extends base_1.TableBaseToken {
|
|
|
27
27
|
catch { }
|
|
28
28
|
}
|
|
29
29
|
const error = (0, lint_1.generateForChild)(inter, { start }, 'fostered-content', 'content to be moved out from the table');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
startCol: 0,
|
|
36
|
-
});
|
|
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
|
}
|
|
@@ -22,9 +22,18 @@ class IncludeToken extends (0, hidden_1.hiddenToken)(index_2.TagPairToken) {
|
|
|
22
22
|
}
|
|
23
23
|
/** @override */
|
|
24
24
|
lint(start = this.getAbsoluteIndex()) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.suggestions = [
|
|
30
|
+
{
|
|
31
|
+
desc: 'close',
|
|
32
|
+
range: [e.endIndex, e.endIndex],
|
|
33
|
+
text: `</${this.name}>`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
return [e];
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
exports.IncludeToken = IncludeToken;
|
package/dist/src/transclude.js
CHANGED
|
@@ -166,7 +166,14 @@ 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) };
|
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/string.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noWrap = exports.decodeHtml = exports.text = exports.escapeRegExp = exports.removeComment = exports.tidy = exports.extUrlChar = exports.extUrlCharFirst = void 0;
|
|
4
|
-
|
|
5
|
-
exports.
|
|
4
|
+
const commonExtUrlChar = '[^[\\]<>"\0-\x1F\x7F\\p{Zs}\uFFFD]';
|
|
5
|
+
exports.extUrlCharFirst = `(?:\\[[\\da-f:.]+\\]|${commonExtUrlChar})`;
|
|
6
|
+
exports.extUrlChar = `(?:${commonExtUrlChar}|\0\\d+[c!~]\x7F)*`;
|
|
6
7
|
/**
|
|
7
8
|
* 生成正则替换函数
|
|
8
9
|
* @param regex 正则表达式
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikilint",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "A Node.js linter for MediaWiki markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
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
|
-
"prepublishOnly": "npm run build && rm dist/internal.js dist/
|
|
36
|
+
"prepublishOnly": "npm run build && rm dist/internal.js dist/[bmpu]*/*.d.ts",
|
|
37
37
|
"build": "bash build.sh",
|
|
38
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",
|
|
@@ -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",
|