wikilint 2.23.0 → 2.25.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.
Files changed (77) hide show
  1. package/README.md +3 -3
  2. package/bin/config.js +2 -2
  3. package/config/default.json +6 -0
  4. package/config/enwiki.json +2 -2
  5. package/config/llwiki.json +382 -261
  6. package/config/moegirl.json +325 -325
  7. package/coverage/badge.svg +1 -1
  8. package/dist/base.d.mts +20 -2
  9. package/dist/base.d.ts +20 -2
  10. package/dist/bin/config.js +17 -5
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +32 -12
  13. package/dist/lib/element.d.ts +0 -7
  14. package/dist/lib/element.js +7 -13
  15. package/dist/lib/lintConfig.js +0 -3
  16. package/dist/lib/lsp.d.ts +19 -1
  17. package/dist/lib/lsp.js +130 -15
  18. package/dist/lib/text.d.ts +17 -0
  19. package/dist/lib/text.js +73 -11
  20. package/dist/lib/title.js +3 -2
  21. package/dist/mixin/attributesParent.js +1 -1
  22. package/dist/mixin/elementLike.d.ts +7 -0
  23. package/dist/mixin/elementLike.js +7 -0
  24. package/dist/mixin/noEscape.d.ts +4 -0
  25. package/dist/mixin/noEscape.js +20 -0
  26. package/dist/parser/commentAndExt.js +7 -8
  27. package/dist/parser/externalLinks.js +1 -1
  28. package/dist/parser/hrAndDoubleUnderscore.js +8 -5
  29. package/dist/src/arg.js +7 -6
  30. package/dist/src/attribute.d.ts +1 -0
  31. package/dist/src/attribute.js +18 -13
  32. package/dist/src/attributes.js +6 -6
  33. package/dist/src/converter.js +2 -1
  34. package/dist/src/converterFlags.js +3 -3
  35. package/dist/src/extLink.js +1 -1
  36. package/dist/src/gallery.js +9 -8
  37. package/dist/src/heading.js +158 -98
  38. package/dist/src/html.js +20 -18
  39. package/dist/src/imageParameter.js +4 -4
  40. package/dist/src/imagemap.js +4 -4
  41. package/dist/src/index.d.ts +4 -0
  42. package/dist/src/index.js +2 -2
  43. package/dist/src/link/base.js +9 -22
  44. package/dist/src/link/file.js +18 -8
  45. package/dist/src/link/galleryImage.js +2 -2
  46. package/dist/src/link/index.d.ts +2 -0
  47. package/dist/src/link/index.js +10 -1
  48. package/dist/src/link/redirectTarget.js +2 -2
  49. package/dist/src/magicLink.js +5 -5
  50. package/dist/src/nested.js +3 -3
  51. package/dist/src/nowiki/base.js +61 -10
  52. package/dist/src/nowiki/comment.js +2 -2
  53. package/dist/src/nowiki/index.js +3 -3
  54. package/dist/src/nowiki/quote.js +11 -11
  55. package/dist/src/onlyinclude.js +2 -1
  56. package/dist/src/paramTag/index.js +2 -2
  57. package/dist/src/parameter.js +2 -2
  58. package/dist/src/redirect.js +2 -1
  59. package/dist/src/table/base.d.ts +2 -0
  60. package/dist/src/table/base.js +30 -2
  61. package/dist/src/table/index.js +4 -5
  62. package/dist/src/table/td.d.ts +1 -0
  63. package/dist/src/table/td.js +14 -13
  64. package/dist/src/table/trBase.js +1 -1
  65. package/dist/src/tagPair/ext.js +2 -2
  66. package/dist/src/tagPair/include.js +4 -4
  67. package/dist/src/tagPair/index.js +2 -1
  68. package/dist/src/transclude.js +13 -18
  69. package/dist/util/constants.js +2 -1
  70. package/dist/util/lint.js +91 -2
  71. package/dist/util/sharable.js +1 -1
  72. package/dist/util/sharable.mjs +1 -1
  73. package/dist/util/string.js +11 -10
  74. package/i18n/en.json +77 -0
  75. package/i18n/zh-hans.json +71 -56
  76. package/i18n/zh-hant.json +71 -56
  77. package/package.json +8 -9
package/dist/lib/text.js CHANGED
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AstText = void 0;
7
7
  const string_1 = require("../util/string");
8
8
  const lint_1 = require("../util/lint");
9
+ const debug_1 = require("../util/debug");
9
10
  const index_1 = __importDefault(require("../index"));
10
11
  const node_1 = require("./node");
11
- const sp = String.raw `[${string_1.zs}\t]*`, source = String.raw `<\s*(?:/\s*)?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*?\])|((?:^|\])[^[]*?)\]+|(?:rfc|pmid)(?=[-::]?${sp}\d)|isbn(?=[-::]?${sp}(?:\d(?:${sp}|-)){6})`;
12
+ const sp = String.raw `[${string_1.zs}\t]*`, anySp = String.raw `[^\S\n]*`, source = String.raw `<${anySp}(?:/${anySp})?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*?\])|((?:^|\])[^[]*?)\]+|(?:rfc|pmid)(?=[-::]?${sp}\d)|isbn(?=[-::]?${sp}(?:\d(?:${sp}|-)){6})`;
12
13
  const errorSyntax = new RegExp(String.raw `${source}|https?[:/]/+`, 'giu');
13
14
  const errorSyntaxUrl = new RegExp(source, 'giu'), noLinkTypes = new Set(['attr-value', 'ext-link-text', 'link-text']), regexes = {
14
15
  '[': /[[\]]/u,
@@ -103,7 +104,9 @@ class AstText extends node_1.AstNode {
103
104
  return [];
104
105
  }
105
106
  errorRegex.lastIndex = 0;
106
- 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([
107
+ 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), lintConfig = index_1.default.lintConfig['tag-like'], specified = typeof lintConfig === 'number'
108
+ ? new Set()
109
+ : new Set(Object.keys(lintConfig[1]).filter(tag => tag !== 'invalid' && tag !== 'disallowed')), tags = new Set([
107
110
  'onlyinclude',
108
111
  'noinclude',
109
112
  'includeonly',
@@ -111,6 +114,7 @@ class AstText extends node_1.AstNode {
111
114
  ...html[0],
112
115
  ...html[1],
113
116
  ...html[2],
117
+ ...specified,
114
118
  ...disallowedTags,
115
119
  ]);
116
120
  for (let mt = errorRegex.exec(data); mt; mt = errorRegex.exec(data)) {
@@ -147,10 +151,13 @@ class AstText extends node_1.AstNode {
147
151
  else if (char === '<') {
148
152
  rule = 'tag-like';
149
153
  let key;
150
- if (/^<\s/u.test(error) || !/[\s/>]/u.test(nextChar ?? '')) {
154
+ if (/^<\/?\s/u.test(error) || !/[\s/>]/u.test(nextChar ?? '')) {
151
155
  key = 'invalid';
152
156
  }
153
- else if (disallowedTags.has(tag)) {
157
+ else if (specified.has(tag)) {
158
+ key = tag;
159
+ }
160
+ else if (disallowedTags.has(tag) && !ext.includes(tag)) {
154
161
  key = 'disallowed';
155
162
  }
156
163
  severity = index_1.default.lintConfig.getSeverity(rule, key);
@@ -210,7 +217,7 @@ class AstText extends node_1.AstNode {
210
217
  // LintError
211
218
  const pos = this.posFromIndex(index), { line: startLine, character: startCol } = (0, lint_1.getEndPos)(top, left, pos.top + 1, pos.left), e = {
212
219
  rule,
213
- message: index_1.default.msg('lonely "$1"', magicLink || char === 'h' || lConverter || rConverter ? error : char),
220
+ message: index_1.default.msg('lonely', magicLink || char === 'h' || lConverter || rConverter ? error : char),
214
221
  severity,
215
222
  startIndex,
216
223
  endIndex,
@@ -221,26 +228,26 @@ class AstText extends node_1.AstNode {
221
228
  };
222
229
  // Suggestions
223
230
  if (char === '<') {
224
- e.suggestions = [{ desc: 'escape', range: [startIndex, startIndex + 1], text: '&lt;' }];
231
+ e.suggestions = [(0, lint_1.fixByEscape)(startIndex, '&lt;')];
225
232
  }
226
233
  else if (char === 'h' && type !== 'link-text' && wordRegex.test(previousChar || '')) {
227
- e.suggestions = [{ desc: 'whitespace', range: [startIndex, startIndex], text: ' ' }];
234
+ e.suggestions = [(0, lint_1.fixBySpace)(startIndex)];
228
235
  }
229
236
  else if (lbrack && type === 'ext-link-text') {
230
237
  const i = parentNode.getAbsoluteIndex() + parentNode.toString().length;
231
- e.suggestions = [{ desc: 'escape', range: [i, i + 1], text: '&#93;' }];
238
+ e.suggestions = [(0, lint_1.fixByEscape)(i, '&#93;')];
232
239
  }
233
240
  else if (rbrack && brokenExtLink) {
234
241
  const i = start - previousSibling.toString().length;
235
- e.suggestions = [{ desc: 'left bracket', range: [i, i], text: '[' }];
242
+ e.suggestions = [(0, lint_1.fixByInsert)(i, 'left-bracket', '[')];
236
243
  }
237
244
  else if (magicLink) {
238
245
  e.suggestions = [
239
246
  ...mt[0] === error
240
247
  ? []
241
- : [{ desc: 'uppercase', range: [startIndex, endIndex], text: error }],
248
+ : [(0, lint_1.fixByUpper)(e, error)],
242
249
  ...nextChar === ':' || nextChar === ':'
243
- ? [{ desc: 'whitespace', range: [endIndex, endIndex + 1], text: ' ' }]
250
+ ? [(0, lint_1.fixBySpace)(endIndex, 1)]
244
251
  : [],
245
252
  ];
246
253
  }
@@ -264,5 +271,60 @@ class AstText extends node_1.AstNode {
264
271
  replaceData(text) {
265
272
  this.#setData(text);
266
273
  }
274
+ /**
275
+ * Split the text node into two parts
276
+ *
277
+ * 将文本子节点分裂为两部分
278
+ * @param offset position to be splitted at / 分裂位置
279
+ * @throws `RangeError` 错误的断开位置
280
+ * @throws `Error` 没有父节点
281
+ */
282
+ splitText(offset) {
283
+ LSP: { // eslint-disable-line no-unused-labels
284
+ const { parentNode, data } = this;
285
+ /* istanbul ignore if */
286
+ if (!parentNode) {
287
+ throw new Error('The text node to be split has no parent node!');
288
+ }
289
+ const newText = new AstText(data.slice(offset));
290
+ (0, debug_1.setChildNodes)(parentNode, parentNode.childNodes.indexOf(this) + 1, 0, [newText]);
291
+ this.setAttribute('data', data.slice(0, offset));
292
+ return newText;
293
+ }
294
+ }
295
+ /**
296
+ * Escape `=` and `|`
297
+ *
298
+ * 转义 `=` 和 `|`
299
+ * @since v1.1.4
300
+ * @throws `Error` 没有父节点
301
+ */
302
+ escape() {
303
+ LSP: { // eslint-disable-line no-unused-labels
304
+ const { parentNode } = this;
305
+ /* istanbul ignore if */
306
+ if (!parentNode) {
307
+ throw new Error('The text node to be escaped has no parent node!');
308
+ }
309
+ const { TranscludeToken } = require('../src/transclude');
310
+ const config = parentNode.getAttribute('config'), index = parentNode.childNodes.indexOf(this) + 1;
311
+ /**
312
+ * Get the last index of `=` or `|`
313
+ * @param j start position from the end
314
+ */
315
+ const lastIndexOf = (j) => Math.max(this.data.lastIndexOf('=', j), this.data.lastIndexOf('|', j));
316
+ let i = lastIndexOf();
317
+ const callback = /** @ignore */ () =>
318
+ // @ts-expect-error abstract class
319
+ new TranscludeToken(this.data[i] === '=' ? '=' : '!', [], config);
320
+ for (; i >= 0; i = lastIndexOf(i - 1)) {
321
+ if (i < this.data.length - 1) {
322
+ this.splitText(i + 1);
323
+ }
324
+ parentNode.insertAt(debug_1.Shadow.run(callback), index);
325
+ this.#setData(this.data.slice(0, i));
326
+ }
327
+ }
328
+ }
267
329
  }
268
330
  exports.AstText = AstText;
package/dist/lib/title.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Title = void 0;
4
+ const common_1 = require("@bhsd/common");
4
5
  const string_1 = require("../util/string");
5
6
  /**
6
7
  * title object of a MediaWiki page
@@ -67,7 +68,7 @@ class Title {
67
68
  if (decode && title.includes('%')) {
68
69
  try {
69
70
  const encoded = /%(?!21|3[ce]|5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
70
- title = (0, string_1.rawurldecode)(title);
71
+ title = (0, common_1.rawurldecode)(title);
71
72
  this.encoded = encoded;
72
73
  }
73
74
  catch { }
@@ -97,7 +98,7 @@ class Title {
97
98
  let fragment = title.slice(i).trim().slice(1);
98
99
  if (fragment.includes('%')) {
99
100
  try {
100
- fragment = (0, string_1.rawurldecode)(fragment);
101
+ fragment = (0, common_1.rawurldecode)(fragment);
101
102
  }
102
103
  catch { }
103
104
  }
@@ -14,7 +14,7 @@ const attributesParent = (i = 0) => (constructor) => {
14
14
  return this.childNodes[i];
15
15
  }
16
16
  hasAttr(key) {
17
- return this.#getAttributesChild().hasAttr(key);
17
+ LSP: return this.#getAttributesChild().hasAttr(key); // eslint-disable-line no-unused-labels
18
18
  }
19
19
  getAttr(key) {
20
20
  return this.#getAttributesChild().getAttr(key);
@@ -17,5 +17,12 @@ export interface ElementLike {
17
17
  * @param selector selector / 选择器
18
18
  */
19
19
  querySelectorAll<T = Token>(selector: string): T[];
20
+ /**
21
+ * Escape `=` and `|`
22
+ *
23
+ * 转义 `=` 和 `|`
24
+ * @since v1.18.3
25
+ */
26
+ escape(): void;
20
27
  }
21
28
  /** @ignore */
@@ -45,6 +45,13 @@ const elementLike = (constructor) => {
45
45
  querySelectorAll(selector) {
46
46
  return this.getElementsBy(this.#getCondition(selector));
47
47
  }
48
+ escape() {
49
+ LSP: { // eslint-disable-line no-unused-labels
50
+ for (const child of this.childNodes) {
51
+ child.escape();
52
+ }
53
+ }
54
+ }
48
55
  }
49
56
  /* eslint-enable jsdoc/require-jsdoc */
50
57
  (0, debug_1.mixin)(ElementLike, constructor);
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 不需要转义的类
3
+ * @ignore
4
+ */
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noEscape = void 0;
4
+ const debug_1 = require("../util/debug");
5
+ /**
6
+ * 不需要转义的类
7
+ * @ignore
8
+ */
9
+ const noEscape = (constructor) => {
10
+ /* eslint-disable jsdoc/require-jsdoc */
11
+ class NoEscapeToken extends constructor {
12
+ escape() {
13
+ //
14
+ }
15
+ }
16
+ /* eslint-enable jsdoc/require-jsdoc */
17
+ (0, debug_1.mixin)(NoEscapeToken, constructor);
18
+ return NoEscapeToken;
19
+ };
20
+ exports.noEscape = noEscape;
@@ -11,9 +11,8 @@ const ext_1 = require("../src/tagPair/ext");
11
11
  const comment_1 = require("../src/nowiki/comment");
12
12
  const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', { length } = onlyincludeLeft, getRegex = [false, true].map(includeOnly => {
13
13
  const noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly';
14
- return (0, common_1.getObjRegex)(ext => new RegExp(String.raw `<!--[\s\S]*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${ext.filter(tag => tag !== 'img').join('|')
15
- // eslint-disable-next-line unicorn/prefer-string-raw
16
- })(\s[^>]*?)?(?:/>|>([\s\S]*?)</(${'\\1'}\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>([\s\S]*?)(?:</(${includeRegex}\s*)>|$))${ext.includes('img') ? String.raw `|<img(\s[^>]*?)?(/?)>` : ''}`, 'giu'));
14
+ return (0, common_1.getObjRegex)(ext => new RegExp(String.raw `<!--[\s\S]*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${ext.join('|') // eslint-disable-next-line unicorn/prefer-string-raw
15
+ })(\s[^>]*?)?(?:/>|>([\s\S]*?)</(${'\\1'}\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>([\s\S]*?)(?:</(${includeRegex}\s*)>|$))`, 'giu'));
17
16
  });
18
17
  /**
19
18
  * 更新`<onlyinclude>`和`</onlyinclude>`的位置
@@ -74,23 +73,23 @@ const parseCommentAndExt = (wikitext, config, accum, includeOnly) => {
74
73
  });
75
74
  wikitext = (0, string_1.restore)(wikitext, stack);
76
75
  }
77
- return wikitext.replace(getRegex[includeOnly ? 1 : 0](newExt), (substr, name, attr, inner, closing, include, includeAttr, includeInner, includeClosing, imgAttr, imgClosing) => {
76
+ return wikitext.replace(getRegex[includeOnly ? 1 : 0](newExt), (substr, name, attr, inner, closing, include, includeAttr, includeInner, includeClosing) => {
78
77
  const l = accum.length;
79
78
  let ch = 'n';
80
- if (name || newExt.includes('img') && imgClosing !== undefined) {
79
+ if (name) {
81
80
  ch = 'e';
82
81
  // @ts-expect-error abstract class
83
- new ext_1.ExtToken(name ?? 'img', name ? attr : imgAttr, inner, name ? closing : imgClosing && undefined, newConfig, include, accum);
82
+ new ext_1.ExtToken(name, attr, inner, closing, newConfig, include, accum);
84
83
  }
85
84
  else if (substr.startsWith('<!--')) {
86
85
  ch = 'c';
87
86
  const closed = substr.endsWith('-->');
88
87
  // @ts-expect-error abstract class
89
- new comment_1.CommentToken(substr.slice(4, closed ? -3 : undefined), closed, config, accum);
88
+ new comment_1.CommentToken((0, string_1.restore)(substr, accum, 1).slice(4, closed ? -3 : undefined), closed, config, accum);
90
89
  }
91
90
  else if (include) {
92
91
  // @ts-expect-error abstract class
93
- new include_1.IncludeToken(include, includeAttr, includeInner, includeClosing, config, accum);
92
+ new include_1.IncludeToken(include, includeAttr && (0, string_1.restore)(includeAttr, accum, 1), includeInner && (0, string_1.restore)(includeInner, accum, 1), includeClosing, config, accum);
94
93
  }
95
94
  else {
96
95
  // @ts-expect-error abstract class
@@ -12,7 +12,7 @@ const magicLink_1 = require("../src/magicLink");
12
12
  * @param inFile 是否在图链中
13
13
  */
14
14
  const parseExternalLinks = (wikitext, config, accum, inFile) => {
15
- config.regexExternalLinks ??= new RegExp(String.raw `\[(\0\d+f\x7F|(?:(?:${config.protocol}|//)${string_1.extUrlCharFirst}|\0\d+m\x7F)${string_1.extUrlChar}(?=[[\]<>"\t${string_1.zs}]|\0\d))([${string_1.zs}]*(?![${string_1.zs}]))([^\]\x01-\x08\x0A-\x1F\uFFFD]*)\]`, 'giu');
15
+ config.regexExternalLinks ??= new RegExp(String.raw `\[((?:\0\d+[cn]\x7F)*(?:\0\d+f\x7F|(?:(?:${config.protocol}|//)${string_1.extUrlCharFirst}|\0\d+m\x7F)${string_1.extUrlChar}(?=[[\]<>"\t${string_1.zs}]|\0\d)))([${string_1.zs}]*(?![${string_1.zs}]))([^\]\x01-\x08\x0A-\x1F\uFFFD]*)\]`, 'giu');
16
16
  return wikitext.replace(config.regexExternalLinks, (_, url, space, text) => {
17
17
  const { length } = accum, mt = /&[lg]t;/u.exec(url);
18
18
  if (mt) {
@@ -30,12 +30,15 @@ const parseHrAndDoubleUnderscore = ({ firstChild: { data }, type, name }, config
30
30
  return `\0${accum.length - 1}${caseInsensitive && (aliases?.[lc] ?? /* istanbul ignore next */ lc) === 'toc' ? 'u' : 'n'}\x7F`;
31
31
  }
32
32
  return m;
33
- }).replace(/^((?:\0\d+[cn]\x7F)*)(={1,6})(.+)\2((?:\s|\0\d+[cn]\x7F)*)$/gmu, (_, lead, equals, heading, trail) => {
34
- const text = `${lead}\0${accum.length}h\x7F`;
35
- // @ts-expect-error abstract class
36
- new heading_1.HeadingToken(equals.length, [heading, trail], config, accum);
37
- return text;
38
33
  });
34
+ if (!config.excludes.includes('heading')) {
35
+ data = data.replace(/^((?:\0\d+[cn]\x7F)*)(={1,6})(.+)\2((?:\s|\0\d+[cn]\x7F)*)$/gmu, (_, lead, equals, heading, trail) => {
36
+ const text = `${lead}\0${accum.length}h\x7F`;
37
+ // @ts-expect-error abstract class
38
+ new heading_1.HeadingToken(equals.length, [heading, trail], config, accum);
39
+ return text;
40
+ });
41
+ }
39
42
  return type === 'root' || type === 'ext-inner' && name === 'poem' ? data : data.slice(1);
40
43
  };
41
44
  exports.parseHrAndDoubleUnderscore = parseHrAndDoubleUnderscore;
package/dist/src/arg.js CHANGED
@@ -43,6 +43,7 @@ const lint_1 = require("../util/lint");
43
43
  const rect_1 = require("../lib/rect");
44
44
  const padded_1 = require("../mixin/padded");
45
45
  const gapped_1 = require("../mixin/gapped");
46
+ const noEscape_1 = require("../mixin/noEscape");
46
47
  const index_1 = __importDefault(require("../index"));
47
48
  const index_2 = require("./index");
48
49
  const atom_1 = require("./atom");
@@ -54,7 +55,7 @@ const hidden_1 = require("./hidden");
54
55
  * @classdesc `{childNodes: [AtomToken, ?Token, ...HiddenToken[]]}`
55
56
  */
56
57
  let ArgToken = (() => {
57
- let _classDecorators = [(0, padded_1.padded)('{{{'), (0, gapped_1.gapped)()];
58
+ let _classDecorators = [noEscape_1.noEscape, (0, padded_1.padded)('{{{'), (0, gapped_1.gapped)()];
58
59
  let _classDescriptor;
59
60
  let _classExtraInitializers = [];
60
61
  let _classThis;
@@ -132,20 +133,20 @@ let ArgToken = (() => {
132
133
  if (s[0] && rest.length > 0) {
133
134
  const rect = new rect_1.BoundingRect(this, start);
134
135
  errors.push(...rest.map(child => {
135
- const e = (0, lint_1.generateForChild)(child, rect, rules[0], 'invisible content inside triple braces', s[0]);
136
+ const e = (0, lint_1.generateForChild)(child, rect, rules[0], 'invisible-triple-braces', s[0]);
136
137
  e.startIndex--;
137
138
  e.startCol--;
138
139
  e.suggestions = [
139
- { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' },
140
- { desc: 'escape', range: [e.startIndex, e.startIndex + 1], text: '{{!}}' },
140
+ (0, lint_1.fixByRemove)(e),
141
+ (0, lint_1.fixByEscape)(e.startIndex, '{{!}}'),
141
142
  ];
142
143
  return e;
143
144
  }));
144
145
  }
145
146
  if (s[1] && !this.getAttribute('include')) {
146
- const e = (0, lint_1.generateForSelf)(this, { start }, rules[1], 'unexpected template argument', s[1]);
147
+ const e = (0, lint_1.generateForSelf)(this, { start }, rules[1], 'unexpected-argument', s[1]);
147
148
  if (argDefault) {
148
- e.suggestions = [{ desc: 'expand', range: [start, e.endIndex], text: argDefault.text() }];
149
+ e.suggestions = [(0, lint_1.fixBy)(e, 'expand', argDefault.text())];
149
150
  }
150
151
  errors.push(e);
151
152
  }
@@ -40,5 +40,6 @@ export declare abstract class AttributeToken extends Token {
40
40
  * 获取属性值
41
41
  */
42
42
  getValue(): string | true;
43
+ escape(): void;
43
44
  }
44
45
  export {};
@@ -63,8 +63,7 @@ class AttributeToken extends index_2.Token {
63
63
  valueToken.setAttribute('stage', constants_1.MAX_STAGE - 1);
64
64
  }
65
65
  else if (tag === 'gallery' && key === 'caption'
66
- || tag === 'choose' && (key === 'before' || key === 'after')
67
- || tag === 'img' && key === 'src' && value?.includes('{{')) {
66
+ || tag === 'choose' && (key === 'before' || key === 'after')) {
68
67
  const newConfig = {
69
68
  ...config,
70
69
  excludes: [...config.excludes, 'heading', 'html', 'table', 'hr', 'list'],
@@ -127,23 +126,23 @@ class AttributeToken extends index_2.Token {
127
126
  && !parentNode?.hasAttr('itemscope')) {
128
127
  const s = index_1.default.lintConfig.getSeverity(rule, 'unknown');
129
128
  if (s) {
130
- const e = (0, lint_1.generateForChild)(firstChild, rect, rule, 'illegal attribute name', s);
131
- e.suggestions = [{ desc: 'remove', range: [start, start + length], text: '' }];
129
+ const e = (0, lint_1.generateForChild)(firstChild, rect, rule, 'illegal-attribute-name', s);
130
+ e.suggestions = [(0, lint_1.fixByRemove)(start, length)];
132
131
  return e;
133
132
  }
134
133
  }
135
134
  else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
136
135
  rule = 'insecure-style';
137
136
  const s = index_1.default.lintConfig.getSeverity(rule);
138
- return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'insecure style', s);
137
+ return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'insecure-style', s);
139
138
  }
140
139
  else if (name === 'tabindex' && typeof value === 'string' && value !== '0') {
141
140
  const s = index_1.default.lintConfig.getSeverity(rule, 'tabindex');
142
141
  if (s) {
143
- const e = (0, lint_1.generateForChild)(lastChild, rect, rule, 'nonzero tabindex', s);
142
+ const e = (0, lint_1.generateForChild)(lastChild, rect, rule, 'nonzero-tabindex', s);
144
143
  e.suggestions = [
145
- { desc: 'remove', range: [start, start + length], text: '' },
146
- { desc: '0 tabindex', range: [e.startIndex, e.endIndex], text: '0' },
144
+ (0, lint_1.fixByRemove)(start, length),
145
+ (0, lint_1.fixBy)(e, '0 tabindex', '0'),
147
146
  ];
148
147
  return e;
149
148
  }
@@ -152,7 +151,7 @@ class AttributeToken extends index_2.Token {
152
151
  const data = (0, lint_1.provideValues)(tag, name), v = String(value).toLowerCase();
153
152
  if (data.length > 0 && data.every(n => n !== v)) {
154
153
  const s = index_1.default.lintConfig.getSeverity(rule, 'value');
155
- return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal attribute value', s);
154
+ return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal-attribute-value', s);
156
155
  }
157
156
  }
158
157
  else if (typeof value === 'string' && ((/^xmlns:[\w:.-]+$/u.test(name) || urlAttrs.has(name)) && evil.test(value)
@@ -161,7 +160,7 @@ class AttributeToken extends index_2.Token {
161
160
  && !new RegExp(String.raw `^(?:${this.getAttribute('config').protocol}|//)\S+$`, 'iu')
162
161
  .test(value))) {
163
162
  const s = index_1.default.lintConfig.getSeverity(rule, 'value');
164
- return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal attribute value', s);
163
+ return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal-attribute-value', s);
165
164
  }
166
165
  return false;
167
166
  }
@@ -169,10 +168,10 @@ class AttributeToken extends index_2.Token {
169
168
  lint(start = this.getAbsoluteIndex(), re) {
170
169
  const errors = super.lint(start, re), { balanced, firstChild, lastChild, name, tag } = this, rect = new rect_1.BoundingRect(this, start), rules = ['unclosed-quote', 'obsolete-attr'], s = rules.map(rule => index_1.default.lintConfig.getSeverity(rule, name));
171
170
  if (s[0] && !balanced) {
172
- const e = (0, lint_1.generateForChild)(lastChild, rect, rules[0], index_1.default.msg('unclosed $1', 'quotes'), s[0]);
171
+ const e = (0, lint_1.generateForChild)(lastChild, rect, rules[0], index_1.default.msg('unclosed', 'quotes'), s[0]);
173
172
  e.startIndex--;
174
173
  e.startCol--;
175
- e.suggestions = [{ desc: 'close', range: [e.endIndex, e.endIndex], text: this.#quotes[0] }];
174
+ e.suggestions = [(0, lint_1.fixByClose)(e.endIndex, this.#quotes[0])];
176
175
  errors.push(e);
177
176
  }
178
177
  const e = this.#lint(start, rect);
@@ -180,7 +179,7 @@ class AttributeToken extends index_2.Token {
180
179
  errors.push(e);
181
180
  }
182
181
  if (s[1] && sharable_1.obsoleteAttrs[tag]?.has(name)) {
183
- errors.push((0, lint_1.generateForChild)(firstChild, rect, rules[1], 'obsolete attribute', s[1]));
182
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, rules[1], 'obsolete-attribute', s[1]));
184
183
  }
185
184
  return errors;
186
185
  }
@@ -192,5 +191,11 @@ class AttributeToken extends index_2.Token {
192
191
  getValue() {
193
192
  return this.#equal ? this.lastChild.text().trim() : this.type === 'ext-attr' || '';
194
193
  }
194
+ escape() {
195
+ LSP: { // eslint-disable-line no-unused-labels
196
+ this.#equal = '{{=}}';
197
+ this.lastChild.escape();
198
+ }
199
+ }
195
200
  }
196
201
  exports.AttributeToken = AttributeToken;
@@ -133,10 +133,10 @@ class AttributesToken extends index_2.Token {
133
133
  const errors = super.lint(start, re), { parentNode, childNodes } = this, attrs = new Map(), duplicated = new Set(), rect = new rect_1.BoundingRect(this, start), rules = ['no-ignored', 'no-duplicate'], s = ['closingTag', 'invalidAttributes', 'nonWordAttributes']
134
134
  .map(k => index_1.default.lintConfig.getSeverity(rules[0], k));
135
135
  if (s[0] && this.#lint()) {
136
- const e = (0, lint_1.generateForSelf)(this, rect, rules[0], 'attributes of a closing tag', s[0]), index = parentNode.getAbsoluteIndex();
136
+ const e = (0, lint_1.generateForSelf)(this, rect, rules[0], 'attributes-of-closing-tag', s[0]), index = parentNode.getAbsoluteIndex();
137
137
  e.suggestions = [
138
- { desc: 'remove', range: [start, e.endIndex], text: '' },
139
- { desc: 'open', range: [index + 1, index + 2], text: '' },
138
+ (0, lint_1.fixByRemove)(e),
139
+ (0, lint_1.fixByOpen)(index),
140
140
  ];
141
141
  errors.push(e);
142
142
  }
@@ -154,8 +154,8 @@ class AttributesToken extends index_2.Token {
154
154
  else {
155
155
  const str = attr.text().trim(), severity = s[wordRegex.test(str) ? 1 : 2];
156
156
  if (str && severity) {
157
- const e = (0, lint_1.generateForChild)(attr, rect, rules[0], 'containing invalid attribute', severity);
158
- e.suggestions = [{ desc: 'remove', range: [e.startIndex, e.endIndex], text: ' ' }];
157
+ const e = (0, lint_1.generateForChild)(attr, rect, rules[0], 'invalid-attribute', severity);
158
+ e.suggestions = [(0, lint_1.fixByRemove)(e, 0, ' ')];
159
159
  errors.push(e);
160
160
  }
161
161
  }
@@ -168,7 +168,7 @@ class AttributesToken extends index_2.Token {
168
168
  return [attr, value === true ? '' : value];
169
169
  });
170
170
  errors.push(...pairs.map(([attr, value], i) => {
171
- const e = (0, lint_1.generateForChild)(attr, rect, rules[1], index_1.default.msg('duplicated $1 attribute', key), severity), remove = { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' };
171
+ const e = (0, lint_1.generateForChild)(attr, rect, rules[1], index_1.default.msg('duplicate-attribute', key), severity), remove = (0, lint_1.fixByRemove)(e);
172
172
  if (!value || pairs.slice(0, i).some(([, v]) => v === value)) {
173
173
  e.fix = remove;
174
174
  }
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.ConverterToken = void 0;
38
38
  const string_1 = require("../util/string");
39
39
  const padded_1 = require("../mixin/padded");
40
+ const noEscape_1 = require("../mixin/noEscape");
40
41
  const index_1 = require("./index");
41
42
  const converterFlags_1 = require("./converterFlags");
42
43
  const converterRule_1 = require("./converterRule");
@@ -47,7 +48,7 @@ const converterRule_1 = require("./converterRule");
47
48
  * @classdesc `{childNodes: [ConverterFlagsToken, ...ConverterRuleToken[]]}`
48
49
  */
49
50
  let ConverterToken = (() => {
50
- let _classDecorators = [(0, padded_1.padded)('-{')];
51
+ let _classDecorators = [noEscape_1.noEscape, (0, padded_1.padded)('-{')];
51
52
  let _classDescriptor;
52
53
  let _classExtraInitializers = [];
53
54
  let _classThis;
@@ -123,12 +123,12 @@ let ConverterFlagsToken = (() => {
123
123
  for (let i = 0; i < this.length; i++) {
124
124
  const child = this.childNodes[i], flag = child.text().trim();
125
125
  if (this.isInvalidFlag(flag, variantFlags, unknownFlags, validFlags)) {
126
- const e = (0, lint_1.generateForChild)(child, rect, rule, 'invalid conversion flag', s);
126
+ const e = (0, lint_1.generateForChild)(child, rect, rule, 'invalid-conversion-flag', s);
127
127
  if (variantFlags.size === 0 && definedFlags.has(flag.toUpperCase())) {
128
- e.fix = { desc: 'uppercase', range: [e.startIndex, e.endIndex], text: flag.toUpperCase() };
128
+ e.fix = (0, lint_1.fixByUpper)(e, flag);
129
129
  }
130
130
  else {
131
- e.suggestions = [{ desc: 'remove', range: [e.startIndex - (i && 1), e.endIndex], text: '' }];
131
+ e.suggestions = [(0, lint_1.fixByRemove)(e, i && -1)];
132
132
  }
133
133
  errors.push(e);
134
134
  }
@@ -108,7 +108,7 @@ let ExtLinkToken = (() => {
108
108
  lint(start = this.getAbsoluteIndex(), re) {
109
109
  const errors = super.lint(start, re), rule = 'var-anchor', s = index_1.default.lintConfig.getSeverity(rule, 'extLink');
110
110
  if (s && this.length === 1 && this.closest('heading-title')) {
111
- errors.push((0, lint_1.generateForSelf)(this, { start }, rule, 'variable anchor in a section header', s));
111
+ errors.push((0, lint_1.generateForSelf)(this, { start }, rule, 'variable-anchor', s));
112
112
  }
113
113
  return errors;
114
114
  }
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.GalleryToken = void 0;
41
+ const lint_1 = require("../util/lint");
41
42
  const multiLine_1 = require("../mixin/multiLine");
42
43
  const index_1 = __importDefault(require("../index"));
43
44
  const index_2 = require("./index");
@@ -110,10 +111,9 @@ let GalleryToken = (() => {
110
111
  [, , severity] = s;
111
112
  }
112
113
  if (severity) {
113
- const endIndex = start + length;
114
- errors.push({
114
+ const endIndex = start + length, e = {
115
115
  rule,
116
- message: index_1.default.msg('invalid content in <$1>', 'gallery'),
116
+ message: index_1.default.msg('invalid-content', 'gallery'),
117
117
  severity,
118
118
  startIndex: start,
119
119
  endIndex,
@@ -121,11 +121,12 @@ let GalleryToken = (() => {
121
121
  endLine: startLine,
122
122
  startCol,
123
123
  endCol: startCol + length,
124
- suggestions: [
125
- { desc: 'remove', range: [start, endIndex], text: '' },
126
- { desc: 'comment', range: [start, endIndex], text: `<!--${str}-->` },
127
- ],
128
- });
124
+ };
125
+ e.suggestions = [
126
+ (0, lint_1.fixByRemove)(e),
127
+ (0, lint_1.fixByComment)(e, str),
128
+ ];
129
+ errors.push(e);
129
130
  }
130
131
  }
131
132
  else if (type !== 'noinclude' && type !== 'text') {