wikilint 2.20.1 → 2.20.3
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/dist/bin/config.js +3 -1
- package/dist/lib/text.js +28 -7
- package/dist/src/arg.js +7 -7
- package/dist/src/attributes.js +1 -1
- package/dist/src/link/base.js +1 -1
- package/dist/src/link/file.js +21 -9
- package/dist/src/transclude.js +3 -3
- package/i18n/zh-hans.json +2 -2
- package/package.json +3 -5
package/dist/bin/config.js
CHANGED
|
@@ -116,7 +116,6 @@ exports.default = async (site, url, force, internal) => {
|
|
|
116
116
|
...ns.map(([id, canonical]) => [canonical.toLowerCase(), Number(id)]),
|
|
117
117
|
...namespacealiases.filter(({ id }) => filterGadget(id)).map(({ id, alias }) => [alias.toLowerCase(), id]),
|
|
118
118
|
]),
|
|
119
|
-
functionHook: [...functionhooks.map(s => s.toLowerCase()), 'msgnw'],
|
|
120
119
|
articlePath: articlepath,
|
|
121
120
|
};
|
|
122
121
|
config.doubleUnderscore[0] = [];
|
|
@@ -124,6 +123,9 @@ exports.default = async (site, url, force, internal) => {
|
|
|
124
123
|
Object.assign(config.parserFunction[0], (0, cm_1.getConfig)(magicwords, ({ name }) => name === 'msgnw'));
|
|
125
124
|
config.parserFunction[2] = getAliases(magicwords, new Set(['msg', 'raw']));
|
|
126
125
|
config.parserFunction[3] = getAliases(magicwords, new Set(['subst', 'safesubst']));
|
|
126
|
+
if (!mwConfig.functionHooks) {
|
|
127
|
+
Object.assign(config, { functionHook: [...functionhooks.map(s => s.toLowerCase()), 'msgnw'] });
|
|
128
|
+
}
|
|
127
129
|
if (!mwConfig.variableIDs) {
|
|
128
130
|
const { query: { variables } } = await (await fetch(`${url}/api.php?${new URLSearchParams({ ...params, siprop: 'variables' }).toString()}`)).json();
|
|
129
131
|
Object.assign(config, { variable: [...new Set([...variables, '='])] });
|
package/dist/lib/text.js
CHANGED
|
@@ -54,7 +54,7 @@ const errorSyntaxUrl = new RegExp(source, 'giu'), noLinkTypes = new Set(['attr-v
|
|
|
54
54
|
]);
|
|
55
55
|
let wordRegex;
|
|
56
56
|
try {
|
|
57
|
-
// eslint-disable-next-line prefer-regex-literals
|
|
57
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
58
58
|
wordRegex = new RegExp(String.raw `[\p{L}\p{N}_]`, 'u');
|
|
59
59
|
}
|
|
60
60
|
catch /* istanbul ignore next */ {
|
|
@@ -101,6 +101,7 @@ class AstText extends node_1.AstNode {
|
|
|
101
101
|
isHtmlAttrVal = true;
|
|
102
102
|
}
|
|
103
103
|
else if (tag === 'ref' && (grandName === 'name' || grandName === 'extends' || grandName === 'follow')
|
|
104
|
+
|| grandName === 'group' && (tag === 'ref' || tag === 'references')
|
|
104
105
|
|| tag === 'choose' && (grandName === 'before' || grandName === 'after')) {
|
|
105
106
|
return [];
|
|
106
107
|
}
|
|
@@ -116,7 +117,7 @@ class AstText extends node_1.AstNode {
|
|
|
116
117
|
return [];
|
|
117
118
|
}
|
|
118
119
|
errorRegex.lastIndex = 0;
|
|
119
|
-
const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), rootStr = root.toString(), { ext, html } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), tags = new Set([
|
|
120
|
+
const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), rootStr = root.toString(), { ext, html, variants } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), tags = new Set([
|
|
120
121
|
'onlyinclude',
|
|
121
122
|
'noinclude',
|
|
122
123
|
'includeonly',
|
|
@@ -135,7 +136,8 @@ class AstText extends node_1.AstNode {
|
|
|
135
136
|
error = error.slice(length);
|
|
136
137
|
}
|
|
137
138
|
error = error.toLowerCase();
|
|
138
|
-
const
|
|
139
|
+
const [char] = error, magicLink = char === 'r' || char === 'p' || char === 'i';
|
|
140
|
+
let { length } = error;
|
|
139
141
|
if (char === '<' && !tags.has(tag.toLowerCase())
|
|
140
142
|
|| char === '[' && type === 'ext-link-text' && (/&(?:rbrack|#93|#x5[Dd];);/u.test(data.slice(index + 1))
|
|
141
143
|
|| nextSibling?.is('ext') && nextName === 'nowiki'
|
|
@@ -146,12 +148,14 @@ class AstText extends node_1.AstNode {
|
|
|
146
148
|
else if (char === ']' && (index || length > 1)) {
|
|
147
149
|
errorRegex.lastIndex--;
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
let startIndex = start + index, endIndex = startIndex + length;
|
|
152
|
+
const nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1];
|
|
153
|
+
let severity = length > 1 && !(char === '<' && (/^<\s/u.test(error) || !/[\s/>]/u.test(nextChar ?? '') || disallowedTags.has(tag))
|
|
150
154
|
|| isHtmlAttrVal && (char === '[' || char === ']')
|
|
151
155
|
|| magicLink && type === 'parameter-value'
|
|
152
156
|
|| /^(?:rfc|pmid|isbn)$/iu.test(error))
|
|
153
|
-
|| char === '{' && (nextChar === char || previousChar === '-')
|
|
154
|
-
|| char === '}' && (previousChar === char || nextChar === '-')
|
|
157
|
+
|| char === '{' && (nextChar === char || previousChar === '-' && variants.length > 0)
|
|
158
|
+
|| char === '}' && (previousChar === char || nextChar === '-' && variants.length > 0)
|
|
155
159
|
|| char === '[' && (type === 'ext-link-text' || nextType === 'free-ext-link' && !data.slice(index + 1).trim())
|
|
156
160
|
|| char === ']' && previousType === 'free-ext-link'
|
|
157
161
|
&& !data.slice(0, index).includes(']')
|
|
@@ -178,9 +182,26 @@ class AstText extends node_1.AstNode {
|
|
|
178
182
|
if (magicLink) {
|
|
179
183
|
error = error.toUpperCase();
|
|
180
184
|
}
|
|
185
|
+
else if (error === '{' && previousChar === '-' && severity === 'error') {
|
|
186
|
+
severity = 'warning';
|
|
187
|
+
if (index > 0) {
|
|
188
|
+
error = '-{';
|
|
189
|
+
index--;
|
|
190
|
+
startIndex--;
|
|
191
|
+
length = 2;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (error === '}' && nextChar === '-' && severity === 'error') {
|
|
195
|
+
severity = 'warning';
|
|
196
|
+
if (index < data.length - 1) {
|
|
197
|
+
error = '}-';
|
|
198
|
+
endIndex++;
|
|
199
|
+
length = 2;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
181
202
|
const pos = this.posFromIndex(index), { line: startLine, character: startCol } = (0, lint_1.getEndPos)(top, left, pos.top + 1, pos.left), e = {
|
|
182
203
|
rule: ruleMap[char],
|
|
183
|
-
message: index_1.default.msg('lonely "$1"', magicLink || char === 'h' ? error : char),
|
|
204
|
+
message: index_1.default.msg('lonely "$1"', magicLink || char === 'h' || error === '-{' || error === '}-' ? error : char),
|
|
184
205
|
severity,
|
|
185
206
|
startIndex,
|
|
186
207
|
endIndex,
|
package/dist/src/arg.js
CHANGED
|
@@ -114,13 +114,6 @@ let ArgToken = (() => {
|
|
|
114
114
|
/** @private */
|
|
115
115
|
lint(start = this.getAbsoluteIndex(), re) {
|
|
116
116
|
const { childNodes: [argName, argDefault, ...rest] } = this;
|
|
117
|
-
if (!this.getAttribute('include')) {
|
|
118
|
-
const e = (0, lint_1.generateForSelf)(this, { start }, 'no-arg', 'unexpected template argument');
|
|
119
|
-
if (argDefault) {
|
|
120
|
-
e.suggestions = [{ range: [start, e.endIndex], text: argDefault.text(), desc: 'expand' }];
|
|
121
|
-
}
|
|
122
|
-
return [e];
|
|
123
|
-
}
|
|
124
117
|
argName.setAttribute('aIndex', start + 3);
|
|
125
118
|
const errors = argName.lint(start + 3, re);
|
|
126
119
|
if (argDefault) {
|
|
@@ -144,6 +137,13 @@ let ArgToken = (() => {
|
|
|
144
137
|
return e;
|
|
145
138
|
}));
|
|
146
139
|
}
|
|
140
|
+
if (!this.getAttribute('include')) {
|
|
141
|
+
const e = (0, lint_1.generateForSelf)(this, { start }, 'no-arg', 'unexpected template argument', 'warning');
|
|
142
|
+
if (argDefault) {
|
|
143
|
+
e.suggestions = [{ range: [start, e.endIndex], text: argDefault.text(), desc: 'expand' }];
|
|
144
|
+
}
|
|
145
|
+
errors.push(e);
|
|
146
|
+
}
|
|
147
147
|
return errors;
|
|
148
148
|
}
|
|
149
149
|
};
|
package/dist/src/attributes.js
CHANGED
|
@@ -23,7 +23,7 @@ const toAttributeType = (type) => type.slice(0, -1);
|
|
|
23
23
|
const toDirty = (type) => `${toAttributeType(type)}-dirty`;
|
|
24
24
|
let wordRegex;
|
|
25
25
|
try {
|
|
26
|
-
// eslint-disable-next-line prefer-regex-literals
|
|
26
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
27
27
|
wordRegex = new RegExp(String.raw `[\p{L}\p{N}]`, 'u');
|
|
28
28
|
}
|
|
29
29
|
catch /* istanbul ignore next */ {
|
package/dist/src/link/base.js
CHANGED
|
@@ -157,7 +157,7 @@ let LinkBaseToken = (() => {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
if (fragment !== undefined && !isLink(type)) {
|
|
160
|
-
const e = (0, lint_1.generateForChild)(target, rect, 'no-ignored', 'useless fragment'), j = target.childNodes.findIndex(c => c.type === 'text' && c.data.includes('#')), textNode = target.childNodes[j];
|
|
160
|
+
const e = (0, lint_1.generateForChild)(target, rect, 'no-ignored', 'useless fragment', 'warning'), j = target.childNodes.findIndex(c => c.type === 'text' && c.data.includes('#')), textNode = target.childNodes[j];
|
|
161
161
|
if (textNode) {
|
|
162
162
|
e.fix = {
|
|
163
163
|
range: [
|
package/dist/src/link/file.js
CHANGED
|
@@ -14,7 +14,7 @@ const frame = new Map([
|
|
|
14
14
|
['frameless', 'Frameless'],
|
|
15
15
|
['framed', 'Frame'],
|
|
16
16
|
['thumbnail', 'Thumb'],
|
|
17
|
-
]), horizAlign = new Set(['left', 'right', 'center', 'none']), vertAlign = new Set(['baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom']), extensions = new Set(['tiff', 'tif', 'png', 'gif', 'jpg', 'jpeg', 'webp', 'xcf', 'pdf', 'svg', 'djvu']);
|
|
17
|
+
]), argTypes = new Set(['arg']), transclusion = new Set(['template', 'magic-word']), horizAlign = new Set(['left', 'right', 'center', 'none']), vertAlign = new Set(['baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom']), extensions = new Set(['tiff', 'tif', 'png', 'gif', 'jpg', 'jpeg', 'webp', 'xcf', 'pdf', 'svg', 'djvu']);
|
|
18
18
|
/**
|
|
19
19
|
* a more sophisticated string-explode function
|
|
20
20
|
* @param str string to be exploded
|
|
@@ -39,6 +39,15 @@ const explode = (str) => {
|
|
|
39
39
|
exploded.push(str.slice(lastIndex));
|
|
40
40
|
return exploded;
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* filter out the image parameters that are not of the specified type
|
|
44
|
+
* @param args image parameter tokens
|
|
45
|
+
* @param types token types to be filtered
|
|
46
|
+
*/
|
|
47
|
+
const filterArgs = (args, types) => args.filter(({ childNodes }) => {
|
|
48
|
+
const visibleNodes = childNodes.filter(node => node.text().trim());
|
|
49
|
+
return visibleNodes.length !== 1 || !types.has(visibleNodes[0].type);
|
|
50
|
+
});
|
|
42
51
|
/**
|
|
43
52
|
* image
|
|
44
53
|
*
|
|
@@ -67,10 +76,7 @@ class FileToken extends base_1.LinkBaseToken {
|
|
|
67
76
|
}
|
|
68
77
|
/** @private */
|
|
69
78
|
lint(start = this.getAbsoluteIndex(), re) {
|
|
70
|
-
const errors = super.lint(start, re), args = this.getAllArgs().
|
|
71
|
-
const visibleNodes = childNodes.filter(node => node.text().trim());
|
|
72
|
-
return visibleNodes.length !== 1 || visibleNodes[0].type !== 'arg';
|
|
73
|
-
}), keys = [...new Set(args.map(({ name }) => name))], frameKeys = keys.filter(key => frame.has(key)), horizAlignKeys = keys.filter(key => horizAlign.has(key)), vertAlignKeys = keys.filter(key => vertAlign.has(key)), [fr] = frameKeys, unscaled = fr === 'framed' || fr === 'manualthumb', rect = new rect_1.BoundingRect(this, start);
|
|
79
|
+
const errors = super.lint(start, re), args = filterArgs(this.getAllArgs(), argTypes), keys = [...new Set(args.map(({ name }) => name))], frameKeys = keys.filter(key => frame.has(key)), horizAlignKeys = keys.filter(key => horizAlign.has(key)), vertAlignKeys = keys.filter(key => vertAlign.has(key)), [fr] = frameKeys, unscaled = fr === 'framed' || fr === 'manualthumb', rect = new rect_1.BoundingRect(this, start);
|
|
74
80
|
if (this.closest('ext-link-text')
|
|
75
81
|
&& this.getValue('link')?.trim() !== '') {
|
|
76
82
|
errors.push((0, lint_1.generateForSelf)(this, rect, 'nested-link', 'internal link in an external link'));
|
|
@@ -94,8 +100,8 @@ class FileToken extends base_1.LinkBaseToken {
|
|
|
94
100
|
* @param p1 替换$1
|
|
95
101
|
* @param severity 错误等级
|
|
96
102
|
*/
|
|
97
|
-
const generate = (msg, p1, severity) => (arg) => {
|
|
98
|
-
const e = (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', index_1.default.msg(`${msg} image $1 parameter`, p1),
|
|
103
|
+
const generate = (msg, p1, severity = true) => (arg) => {
|
|
104
|
+
const isError = typeof severity === 'function' ? severity(arg) : severity, e = (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', index_1.default.msg(`${msg} image $1 parameter`, p1), isError ? 'error' : 'warning');
|
|
99
105
|
e.suggestions = [{ desc: 'remove', range: [e.startIndex - 1, e.endIndex], text: '' }];
|
|
100
106
|
return e;
|
|
101
107
|
};
|
|
@@ -104,15 +110,21 @@ class FileToken extends base_1.LinkBaseToken {
|
|
|
104
110
|
if (key === 'invalid' || key === 'width' && unscaled) {
|
|
105
111
|
continue;
|
|
106
112
|
}
|
|
113
|
+
const isCaption = key === 'caption';
|
|
107
114
|
let relevantArgs = args.filter(({ name }) => name === key);
|
|
108
|
-
if (
|
|
115
|
+
if (isCaption) {
|
|
109
116
|
relevantArgs = [
|
|
110
117
|
...relevantArgs.slice(0, -1).filter(arg => arg.text()),
|
|
111
118
|
...relevantArgs.slice(-1),
|
|
112
119
|
];
|
|
113
120
|
}
|
|
114
121
|
if (relevantArgs.length > 1) {
|
|
115
|
-
|
|
122
|
+
let severity = !isCaption || !extension || extensions.has(extension);
|
|
123
|
+
if (isCaption && severity) {
|
|
124
|
+
const plainArgs = filterArgs(relevantArgs, transclusion);
|
|
125
|
+
severity = plainArgs.length > 1 && ((arg) => plainArgs.includes(arg));
|
|
126
|
+
}
|
|
127
|
+
errors.push(...relevantArgs.map(generate('duplicated', key, severity)));
|
|
116
128
|
}
|
|
117
129
|
}
|
|
118
130
|
if (frameKeys.length > 1) {
|
package/dist/src/transclude.js
CHANGED
|
@@ -174,8 +174,8 @@ let TranscludeToken = (() => {
|
|
|
174
174
|
}
|
|
175
175
|
const magicWord = lcModifier.slice(0, -1).toLowerCase(), isRaw = raw.includes(magicWord), isSubst = subst.includes(magicWord);
|
|
176
176
|
if (this.#raw && isRaw
|
|
177
|
-
|| !this.#raw && (isSubst || modifier
|
|
178
|
-
|| (debug_1.Shadow.running || this.length > 1) && (isRaw || isSubst || modifier
|
|
177
|
+
|| !this.#raw && (isSubst || !modifier)
|
|
178
|
+
|| (debug_1.Shadow.running || this.length > 1) && (isRaw || isSubst || !modifier)) {
|
|
179
179
|
this.setAttribute('modifier', modifier);
|
|
180
180
|
this.#raw = isRaw;
|
|
181
181
|
return Boolean(modifier);
|
|
@@ -274,7 +274,7 @@ let TranscludeToken = (() => {
|
|
|
274
274
|
const child = childNodes[invoke ? 1 : 0], i = child.childNodes
|
|
275
275
|
.findIndex(c => c.type === 'text' && (0, string_1.decodeHtml)(c.data).includes('#')), textNode = child.childNodes[i];
|
|
276
276
|
if (textNode) {
|
|
277
|
-
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'useless fragment');
|
|
277
|
+
const e = (0, lint_1.generateForChild)(child, rect, 'no-ignored', 'useless fragment', 'warning');
|
|
278
278
|
e.fix = {
|
|
279
279
|
range: [
|
|
280
280
|
e.startIndex + child.getRelativeIndex(i) + textNode.data.indexOf('#'),
|
package/i18n/zh-hans.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"additional \"|\" in a table cell": "表格单元格中多余的\"|\"",
|
|
5
5
|
"additional \"|\" in the link text": "链接文本中多余的\"|\"",
|
|
6
6
|
"attributes of a closing tag": "位于闭合标签的属性",
|
|
7
|
-
"bold": "粗体单引号",
|
|
7
|
+
"bold apostrophes": "粗体单引号",
|
|
8
8
|
"bold in section header": "段落标题中的粗体",
|
|
9
9
|
"conflicting image $1 parameter": "冲突的图片$1参数",
|
|
10
10
|
"containing invalid attribute": "包含无效属性",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"invalid parameter of <$1>": "<$1>的无效参数",
|
|
36
36
|
"invalid self-closing tag": "无效自封闭标签",
|
|
37
37
|
"invisible content inside triple braces": "三重括号内的不可见部分",
|
|
38
|
-
"italic": "斜体单引号",
|
|
38
|
+
"italic apostrophes": "斜体单引号",
|
|
39
39
|
"lonely \"$1\"": "孤立的\"$1\"",
|
|
40
40
|
"missing module function": "缺少模块函数",
|
|
41
41
|
"nonzero tabindex": "不为0的tabindex",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikilint",
|
|
3
|
-
"version": "2.20.
|
|
3
|
+
"version": "2.20.3",
|
|
4
4
|
"description": "A Node.js linter for MediaWiki markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@bhsd/common": "^0.
|
|
69
|
+
"@bhsd/common": "^0.10.0",
|
|
70
70
|
"vscode-languageserver-types": "^3.17.5"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"entities": "^6.0.0",
|
|
76
76
|
"mathjax": "^3.2.2",
|
|
77
77
|
"minimatch": "^10.0.1",
|
|
78
|
-
"stylelint": "^16.
|
|
78
|
+
"stylelint": "^16.19.1",
|
|
79
79
|
"vscode-css-languageservice": "^6.3.4",
|
|
80
80
|
"vscode-html-languageservice": "^5.3.3",
|
|
81
81
|
"vscode-json-languageservice": "^5.4.4"
|
|
@@ -92,7 +92,6 @@
|
|
|
92
92
|
"color-rgba": "^3.0.0",
|
|
93
93
|
"esbuild": "^0.25.2",
|
|
94
94
|
"eslint": "^8.57.1",
|
|
95
|
-
"eslint-plugin-es-x": "^8.4.1",
|
|
96
95
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
97
96
|
"eslint-plugin-jsdoc": "^50.6.3",
|
|
98
97
|
"eslint-plugin-json-es": "^1.6.0",
|
|
@@ -104,7 +103,6 @@
|
|
|
104
103
|
"http-server": "^14.1.1",
|
|
105
104
|
"mocha": "^11.1.0",
|
|
106
105
|
"nyc": "^17.1.0",
|
|
107
|
-
"stylelint-config-recommended": "^15.0.0",
|
|
108
106
|
"typescript": "^5.8.2",
|
|
109
107
|
"v8r": "^4.2.1",
|
|
110
108
|
"vscode-languageserver-textdocument": "^1.0.12"
|