wikilint 2.48.0 → 2.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,6 +97,6 @@ repos:
97
97
  # You can add any CLI options as needed
98
98
  entry: wikilint
99
99
  language: node
100
- additional_dependencies: ['wikilint@2.48.0']
100
+ additional_dependencies: ['wikilint@2.48.2']
101
101
  files: \.(mediawiki|wiki|wikitext)$
102
102
  ```
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "type": "object",
4
4
  "properties": {
5
5
  "ext": {
package/dist/base.d.mts CHANGED
@@ -2,7 +2,7 @@ import type { Range, Position, ColorInformation, ColorPresentation, CompletionIt
2
2
  export interface Config {
3
3
  ext: string[];
4
4
  readonly html: [string[], string[], string[]];
5
- readonly namespaces: Record<string, string>;
5
+ readonly namespaces: Record<number, string>;
6
6
  readonly nsid: Record<string, number>;
7
7
  readonly parserFunction: [Record<string, string>, Record<string, string>, string[], string[]];
8
8
  readonly doubleUnderscore: [string[], string[], Record<string, string>, Record<string, string>];
package/dist/base.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { Range, Position, ColorInformation, ColorPresentation, CompletionIt
2
2
  export interface Config {
3
3
  ext: string[];
4
4
  readonly html: [string[], string[], string[]];
5
- readonly namespaces: Record<string, string>;
5
+ readonly namespaces: Record<number, string>;
6
6
  readonly nsid: Record<string, number>;
7
7
  readonly parserFunction: [Record<string, string>, Record<string, string>, string[], string[]];
8
8
  readonly doubleUnderscore: [string[], string[], Record<string, string>, Record<string, string>];
package/dist/bin/cli.js CHANGED
@@ -404,9 +404,9 @@ const { mtimeMs } = fs_1.default.statSync(config), obj = cache?.[include ? 'incl
404
404
  exiting ||= Boolean(nLocalErr || strict && nLocalWarn);
405
405
  }
406
406
  if (nErr || nWarn) {
407
- console.error(styleText(['red', 'bold'], '%s'), `\n✖ ${plural(nErr + nWarn, 'problem')} (${plural(nErr, 'error')}, ${plural(nWarn, 'warning')})`);
407
+ console.error(styleText(['red', 'bold'], '\n✖ %s (%s, %s)'), plural(nErr + nWarn, 'problem'), plural(nErr, 'error'), plural(nWarn, 'warning'));
408
408
  if (nFixableErr || nFixableWarn) {
409
- console.error(styleText(['red', 'bold'], '%s'), ` ${plural(nFixableErr, 'error')} and ${plural(nFixableWarn, 'warning')} potentially fixable with the \`--fix\` option.`);
409
+ console.error(styleText(['red', 'bold'], ' %s and %s potentially fixable with the `--fix` option.'), plural(nFixableErr, 'error'), plural(nFixableWarn, 'warning'));
410
410
  }
411
411
  console.error();
412
412
  }
@@ -41,7 +41,7 @@ const filterGadget = (id) => {
41
41
  const n = Number(id);
42
42
  return n < 2300 || n > 2303; // Gadget, Gadget talk, Gadget definition, Gadget definition talk
43
43
  };
44
- const pkg = 'wikilint', version = '2.48.0';
44
+ const pkg = 'wikilint', version = '2.48.2';
45
45
  /**
46
46
  * Get the parser configuration for a Wikimedia Foundation project.
47
47
  * @param site site nickname
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ const path_1 = __importDefault(require("path"));
16
16
  const common_1 = require("@bhsd/common");
17
17
  const diff_1 = require("./util/diff");
18
18
  /* NOT FOR BROWSER ONLY */
19
- const re = new RegExp(String.raw `^https?:\/\/([^./]+)\.(${common_1.wmf})\.org`, 'iu');
19
+ const re = new RegExp(String.raw `^https?://([^./]+)\.(${common_1.wmf})\.org`, 'iu');
20
20
  /**
21
21
  * require一个JSON文件
22
22
  * @param file 文件名
@@ -1,8 +1,8 @@
1
1
  import type { Position } from 'vscode-languageserver-types';
2
2
  import type { TextDocument, Range } from 'vscode-languageserver-textdocument';
3
- import type { JSONDocument, LanguageService as JSONLanguageService } from 'vscode-json-languageservice';
4
- import type { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
5
- import type { IHTMLDataProvider } from 'vscode-html-languageservice';
3
+ import type * as JsonLanguageService from 'vscode-json-languageservice';
4
+ import type * as CssLanguageService from 'vscode-css-languageservice';
5
+ import type * as HtmlLanguageService from 'vscode-html-languageservice';
6
6
  import type { PublicApi } from 'stylelint';
7
7
  import type { Token } from '../internal';
8
8
  export interface TexvcLocation {
@@ -32,9 +32,9 @@ declare interface Texvcjs {
32
32
  }): TexvcReport;
33
33
  }
34
34
  export declare const loadTexvcjs: () => Texvcjs | null;
35
- export declare const loadJsonLSP: () => JSONLanguageService | null;
36
- export declare const loadCssLSP: () => CSSLanguageService | null;
37
- export declare const loadHtmlData: () => IHTMLDataProvider | null;
35
+ export declare const loadJsonLSP: () => JsonLanguageService.LanguageService | null;
36
+ export declare const loadCssLSP: () => CssLanguageService.LanguageService | null;
37
+ export declare const loadHtmlData: () => HtmlLanguageService.IHTMLDataProvider | null;
38
38
  export declare const loadStylelint: () => PublicApi | null;
39
39
  /** embedded document */
40
40
  declare abstract class EmbeddedDocument implements TextDocument {
@@ -65,7 +65,7 @@ declare abstract class EmbeddedDocument implements TextDocument {
65
65
  }
66
66
  /** embedded JSON document */
67
67
  export declare class EmbeddedJSONDocument extends EmbeddedDocument {
68
- jsonDoc: JSONDocument;
68
+ jsonDoc: JsonLanguageService.JSONDocument;
69
69
  abstract getLineRange(): Range;
70
70
  abstract getEOLCharacters(): string;
71
71
  /**
@@ -76,7 +76,7 @@ export declare class EmbeddedJSONDocument extends EmbeddedDocument {
76
76
  }
77
77
  /** embedded CSS document */
78
78
  export declare class EmbeddedCSSDocument extends EmbeddedDocument {
79
- styleSheet: Stylesheet;
79
+ styleSheet: CssLanguageService.Stylesheet;
80
80
  abstract getLineRange(): Range;
81
81
  abstract getEOLCharacters(): string;
82
82
  /**
@@ -27,8 +27,7 @@ let jsonLSP;
27
27
  const loadJsonLSP = () => {
28
28
  if (jsonLSP === undefined) {
29
29
  try {
30
- jsonLSP = require('vscode-json-languageservice')
31
- .getLanguageService({
30
+ jsonLSP = require('vscode-json-languageservice').getLanguageService({
32
31
  /* c8 ignore start */
33
32
  /** @implements */
34
33
  async schemaRequestService(uri) {
@@ -67,8 +66,7 @@ let cssLSP;
67
66
  const loadCssLSP = () => {
68
67
  if (cssLSP === undefined) {
69
68
  try {
70
- cssLSP = require('vscode-css-languageservice')
71
- .getCSSLanguageService();
69
+ cssLSP = require('vscode-css-languageservice').getCSSLanguageService();
72
70
  }
73
71
  catch /* c8 ignore start */ {
74
72
  cssLSP = null;
package/dist/lib/lsp.js CHANGED
@@ -583,7 +583,7 @@ class LanguageService {
583
583
  this.config ??= index_1.default.getConfig();
584
584
  const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = this.config, tags = new Set([ext, html].flat(2));
585
585
  const re = new RegExp('(?:' // eslint-disable-line prefer-template
586
- + String.raw `<(\/?\w*)` // tag ($1)
586
+ + String.raw `<(/?\w*)` // tag ($1)
587
587
  + '|'
588
588
  + String.raw `(\{{2,4}|\[\[)\s*([^|{}<>[\]\s][^|{}<>[\]#]*)?` // braces and brackets ($2, $3)
589
589
  + '|'
@@ -625,7 +625,7 @@ class LanguageService {
625
625
  * @param position position / 位置
626
626
  */
627
627
  async provideCompletionItems(text, position) {
628
- const { re, allTags, functions, switches, jaSwitches, protocols, params, tags, ext, } = this.#prepareCompletionConfig(), { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], mt = re.exec(curLine?.slice(0, character) ?? '');
628
+ const { re, allTags, functions, switches, jaSwitches, protocols, params, tags, ext, } = this.#prepareCompletionConfig(), { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], mt = re.exec(curLine?.slice(Math.max(0, character - 1e4), character) ?? '');
629
629
  if (mt?.[1] !== undefined) { // tag
630
630
  const closing = mt[1].startsWith('/');
631
631
  return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trimStart().startsWith('>') ? '>' : '');
package/dist/lib/node.js CHANGED
@@ -90,7 +90,7 @@ let AstNode = (() => {
90
90
  /** @private */
91
91
  getChildNodes() {
92
92
  const { childNodes } = this;
93
- if (Object.isFrozen(childNodes)) { // NOT FOR TEMPLATE
93
+ if (Object.isFrozen(childNodes)) { // eslint-disable-line unicorn/prefer-ternary
94
94
  return [...childNodes];
95
95
  }
96
96
  return childNodes;
package/dist/lib/text.js CHANGED
@@ -43,16 +43,6 @@ const errorSyntaxUrl = /* #__PURE__ */ new RegExp(source, 'giu'), noLinkTypes =
43
43
  'select',
44
44
  'textarea',
45
45
  ]);
46
- const wordRegex = /* #__PURE__ */ (() => {
47
- try {
48
- // eslint-disable-next-line prefer-regex-literals
49
- return new RegExp(String.raw `[\p{L}\p{N}_]`, 'u');
50
- }
51
- catch /* c8 ignore start */ {
52
- return /\w/u;
53
- }
54
- /* c8 ignore stop */
55
- })();
56
46
  /**
57
47
  * text node
58
48
  *
@@ -70,7 +60,7 @@ class AstText extends node_1.AstNode {
70
60
  }
71
61
  /** @private */
72
62
  toString(skip) {
73
- if (skip && !this.parentNode?.getAttribute('built')) { // NOT FOR TEMPLATE
63
+ if (skip && !this.parentNode?.getAttribute('built')) { // eslint-disable-line unicorn/prefer-ternary
74
64
  return (0, string_1.removeComment)(this.data);
75
65
  }
76
66
  return this.data;
@@ -274,7 +264,7 @@ class AstText extends node_1.AstNode {
274
264
  if (char === '<') {
275
265
  e.suggestions = [(0, lint_1.fixByEscape)(startIndex, '&lt;')];
276
266
  }
277
- else if (char === 'h' && type !== 'link-text' && wordRegex.test(previousChar ?? '')) {
267
+ else if (char === 'h' && type !== 'link-text' && /[\p{L}\p{N}_]/u.test(previousChar ?? '')) {
278
268
  e.suggestions = [(0, lint_1.fixBySpace)(startIndex)];
279
269
  }
280
270
  else if (lbrackInExtLinkText) {
@@ -3,24 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCommentAndExt = void 0;
4
4
  const common_1 = require("@bhsd/common");
5
5
  const string_1 = require("../util/string");
6
+ const replace_1 = require("../util/replace");
6
7
  const onlyinclude_1 = require("../src/onlyinclude");
7
8
  const noinclude_1 = require("../src/nowiki/noinclude");
8
9
  const include_1 = require("../src/tagPair/include");
9
10
  const ext_1 = require("../src/tagPair/ext");
10
11
  const comment_1 = require("../src/nowiki/comment");
11
- const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', { length } = onlyincludeLeft, getExtRegex = [false, true].map(includeOnly => {
12
+ const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', getExtRegex = [false, true].map(includeOnly => {
12
13
  const noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly';
13
- return (0, common_1.getRegex)(exts => new RegExp(String.raw `<!--.*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${exts // eslint-disable-next-line unicorn/prefer-string-raw
14
- })(\s[^>]*?)?(?:/>|>(.*?)</(${'\\1'}\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>(.*?)(?:</(${includeRegex}\s*)>|$))`, 'gisu'));
14
+ return (0, common_1.getRegex)(exts => new RegExp(String.raw `<!--.*?(?:-->|$)|<${noincludeRegex}(?=\s|\/?>)|</${noincludeRegex}\s*>|<(${exts})(?=\s|\/?>)|<(${includeRegex})(?=\s|\/?>)`, 'gisu'));
15
15
  });
16
- /**
17
- * 更新`<onlyinclude>`和`</onlyinclude>`的位置
18
- * @param wikitext
19
- */
20
- const update = (wikitext) => {
21
- const i = wikitext.indexOf(onlyincludeLeft);
22
- return { i, j: wikitext.indexOf(onlyincludeRight, i + length) };
23
- };
24
16
  /**
25
17
  * 解析HTML注释和扩展标签
26
18
  * @param wikitext
@@ -30,32 +22,20 @@ const update = (wikitext) => {
30
22
  */
31
23
  const parseCommentAndExt = (wikitext, config, accum, includeOnly) => {
32
24
  if (includeOnly) {
33
- let { i, j } = update(wikitext);
34
- if (i !== -1 && j !== -1) { // `<onlyinclude>`拥有最高优先级
35
- let str = '';
36
- /**
37
- * 忽略未被`<onlyinclude>`和`</onlyinclude>`包裹的内容
38
- * @param text 未被包裹的内容
39
- */
40
- const noinclude = (text) => {
41
- // @ts-expect-error abstract class
42
- new noinclude_1.NoincludeToken(text, config, accum);
43
- str += `\0${accum.length - 1}n\x7F`;
44
- };
45
- while (i !== -1 && j !== -1) {
25
+ const i = wikitext.indexOf(onlyincludeLeft);
26
+ if (i !== -1 && wikitext.includes(onlyincludeRight, i)) { // `<onlyinclude>`拥有最高优先级
27
+ return (0, replace_1.safeStringReplace)(wikitext, onlyincludeLeft, onlyincludeRight, false, inner => {
46
28
  const token = `\0${accum.length}g\x7F`;
47
- new onlyinclude_1.OnlyincludeToken(wikitext.slice(i + length, j), config, accum);
48
- if (i > 0) {
49
- noinclude(wikitext.slice(0, i));
29
+ new onlyinclude_1.OnlyincludeToken(inner, config, accum);
30
+ return token;
31
+ }, s => {
32
+ if (!s) {
33
+ return '';
50
34
  }
51
- str += token;
52
- wikitext = wikitext.slice(j + length + 1);
53
- ({ i, j } = update(wikitext));
54
- }
55
- if (wikitext) {
56
- noinclude(wikitext);
57
- }
58
- return str;
35
+ // @ts-expect-error abstract class
36
+ new noinclude_1.NoincludeToken(s, config, accum);
37
+ return `\0${accum.length - 1}n\x7F`;
38
+ });
59
39
  }
60
40
  }
61
41
  const { ext } = config;
@@ -65,45 +45,103 @@ const parseCommentAndExt = (wikitext, config, accum, includeOnly) => {
65
45
  const stack = [];
66
46
  newExt = ext.filter(e => e !== 'translate' && e !== 'tvar');
67
47
  newConfig = { ...config, ext: newExt };
68
- wikitext = wikitext.replace(/<nowiki>.*?<\/nowiki>/gisu, m => {
69
- stack.push(m);
48
+ // 隐藏`<nowiki>`标签
49
+ const text = (0, replace_1.safeStringReplace)(wikitext, '<nowiki>', '</nowiki>', true, (inner, open, close) => {
50
+ stack.push(open + inner + close);
70
51
  return `\0${stack.length - 1}\x7F`;
71
- }).replace(/<translate( nowrap)?>(.*?)<\/translate>/gsu, (_, p1, p2) => {
52
+ });
53
+ // 解析`<translate>`标签
54
+ wikitext = (0, replace_1.safePatternReplace)(text, /<translate( nowrap)?>/gu, '</translate>', (inner, [, p1]) => {
72
55
  const l = accum.length;
73
56
  // @ts-expect-error abstract class
74
- new TranslateToken(p1, p2 && (0, string_1.restore)(p2, stack), newConfig, accum);
57
+ new TranslateToken(p1, inner && (0, string_1.restore)(inner, stack), newConfig, accum);
75
58
  return `\0${l}g\x7F`;
76
59
  });
77
60
  wikitext = (0, string_1.restore)(wikitext, stack);
78
61
  }
79
- const re = getExtRegex[includeOnly ? 1 : 0](newExt.join('|'));
62
+ const re = getExtRegex[includeOnly ? 1 : 0](newExt.join('|')), noMoreTags = new Set();
80
63
  re.lastIndex = 0;
81
- return re.test(wikitext)
82
- ? wikitext.replace(re, (substr, name, attr, inner, closing, include, includeAttr, includeInner, includeClosing) => {
83
- const l = accum.length;
84
- let ch = 'n';
85
- if (name) {
86
- ch = 'e';
87
- // @ts-expect-error abstract class
88
- new ext_1.ExtToken(name, attr, inner, closing, newConfig, include, accum);
64
+ let lastIndex = 0, text = '', noMoreGt = false;
65
+ for (let mt = re.exec(wikitext); mt; mt = re.exec(wikitext)) {
66
+ const [substr, name, include] = mt, tag = name || include, { index } = mt, l = accum.length;
67
+ text += wikitext.slice(lastIndex, index);
68
+ ({ lastIndex } = re);
69
+ let ch = 'n', matched = true;
70
+ if (substr.startsWith('<!--')) {
71
+ ch = 'c';
72
+ const closed = substr.endsWith('-->');
73
+ // @ts-expect-error abstract class
74
+ new comment_1.CommentToken((0, string_1.restore)(substr, accum, 1)
75
+ .slice(4, closed ? -3 : undefined), closed, config, accum);
76
+ }
77
+ else if (noMoreGt) {
78
+ matched = false;
79
+ }
80
+ else if (tag) {
81
+ const i = wikitext.indexOf('>', lastIndex);
82
+ let attr, inner, closing;
83
+ if (i === -1) {
84
+ matched = false;
85
+ noMoreGt = true;
89
86
  }
90
- else if (substr.startsWith('<!--')) {
91
- ch = 'c';
92
- const closed = substr.endsWith('-->');
93
- // @ts-expect-error abstract class
94
- new comment_1.CommentToken((0, string_1.restore)(substr, accum, 1)
95
- .slice(4, closed ? -3 : undefined), closed, config, accum);
87
+ else if (wikitext[i - 1] === '/') {
88
+ attr = wikitext.slice(lastIndex, i - 1);
89
+ lastIndex = i + 1;
90
+ re.lastIndex = lastIndex;
96
91
  }
97
- else if (include) {
98
- // @ts-expect-error abstract class
99
- new include_1.IncludeToken(include, includeAttr && (0, string_1.restore)(includeAttr, accum, 1), includeInner && (0, string_1.restore)(includeInner, accum, 1), includeClosing, config, accum);
92
+ else if (noMoreTags.has(tag.toLowerCase())) {
93
+ matched = false;
100
94
  }
101
95
  else {
96
+ attr = wikitext.slice(lastIndex, i);
97
+ const after = wikitext.slice(i + 1), m = new RegExp(String.raw `</(${tag}\s*)>`, 'iu').exec(after);
98
+ if (m) {
99
+ inner = after.slice(0, m.index);
100
+ [, closing] = m;
101
+ lastIndex = i + 1 + m.index + m[0].length;
102
+ re.lastIndex = lastIndex;
103
+ }
104
+ else if (include) {
105
+ inner = after;
106
+ lastIndex = wikitext.length;
107
+ re.lastIndex = lastIndex;
108
+ }
109
+ else {
110
+ matched = false;
111
+ noMoreTags.add(tag.toLowerCase());
112
+ }
113
+ }
114
+ if (matched) {
115
+ if (name) {
116
+ ch = 'e';
117
+ // @ts-expect-error abstract class
118
+ new ext_1.ExtToken(name, attr, inner, closing, newConfig, include, accum);
119
+ }
120
+ else {
121
+ // @ts-expect-error abstract class
122
+ new include_1.IncludeToken(include, attr && (0, string_1.restore)(attr, accum, 1), inner && (0, string_1.restore)(inner, accum, 1), closing, config, accum);
123
+ }
124
+ }
125
+ }
126
+ else if (substr.startsWith('</')) {
127
+ // @ts-expect-error abstract class
128
+ new noinclude_1.NoincludeToken(wikitext.slice(index, lastIndex), config, accum, true);
129
+ }
130
+ else {
131
+ const i = wikitext.indexOf('>', lastIndex);
132
+ if (i === -1) {
133
+ matched = false;
134
+ noMoreGt = true;
135
+ }
136
+ else {
137
+ lastIndex = i + 1;
138
+ re.lastIndex = lastIndex;
102
139
  // @ts-expect-error abstract class
103
- new noinclude_1.NoincludeToken(substr, config, accum, true);
140
+ new noinclude_1.NoincludeToken(wikitext.slice(index, lastIndex), config, accum, true);
104
141
  }
105
- return `\0${l}${ch}\x7F`;
106
- })
107
- : wikitext;
142
+ }
143
+ text += matched ? `\0${l}${ch}\x7F` : substr;
144
+ }
145
+ return text + wikitext.slice(lastIndex);
108
146
  };
109
147
  exports.parseCommentAndExt = parseCommentAndExt;
@@ -11,8 +11,8 @@ const converter_1 = require("../src/converter");
11
11
  const parseConverter = (text, config, accum) => {
12
12
  config.regexConverter ??= new RegExp(String.raw `;(?=(?:[^;]*?=>)?\s*(?:${config.variants.join('|')})\s*:|(?:\s|\0\d+[cn]\x7F)*$)`, 'u');
13
13
  const regex1 = /-\{/gu, regex2 = /-\{|\}-/gu, stack = [];
14
- let regex = regex1, mt = regex.exec(text);
15
- while (mt) {
14
+ let regex = regex1;
15
+ for (let mt = regex.exec(text); mt; mt = regex.exec(text)) {
16
16
  const { 0: syntax, index } = mt;
17
17
  if (syntax === '}-') {
18
18
  const top = stack.pop(), { length } = accum, str = text.slice(top.index + 2, index), i = str.indexOf('|'), [flags, raw] = i === -1 ? [[], str] : [str.slice(0, i).split(';'), str.slice(i + 1)], temp = raw.replace(/(&[#a-z\d]+);/giu, '$1\x01'), rules = temp.split(config.regexConverter)
@@ -30,7 +30,6 @@ const parseConverter = (text, config, accum) => {
30
30
  regex = regex2;
31
31
  regex.lastIndex = index + 2;
32
32
  }
33
- mt = regex.exec(text);
34
33
  }
35
34
  return text;
36
35
  };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseExternalLinks = void 0;
4
4
  const string_1 = require("../util/string");
5
+ const replace_1 = require("../util/replace");
5
6
  const extLink_1 = require("../src/extLink");
6
7
  const magicLink_1 = require("../src/magicLink");
7
8
  /**
@@ -12,13 +13,17 @@ const magicLink_1 = require("../src/magicLink");
12
13
  * @param inFile 是否在图链中
13
14
  */
14
15
  const parseExternalLinks = (wikitext, config, accum, inFile) => {
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
- return wikitext.replace(config.regexExternalLinks, (_, url, space, text) => {
17
- const { length } = accum, mt = /&[lg]t;/u.exec(url);
18
- if (mt) {
16
+ 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}]))`, 'giu');
17
+ const re = config.regexExternalLinks;
18
+ return (0, replace_1.safePatternReplace)(wikitext, re, ']', (text, [, url, space]) => {
19
+ if (/[\x01-\x08\x0A-\x1F\uFFFD]/u.test(text)) {
20
+ return false;
21
+ }
22
+ const { length } = accum, i = url.search(/&[lg]t;/u);
23
+ if (i !== -1) {
19
24
  space = '';
20
- text = url.slice(mt.index) + space + text;
21
- url = url.slice(0, mt.index);
25
+ text = url.slice(i) + space + text;
26
+ url = url.slice(0, i);
22
27
  }
23
28
  if (inFile) {
24
29
  // @ts-expect-error abstract class
@@ -11,23 +11,15 @@ const space = String.raw `[${string_1.zs}\t]|&nbsp;|&#0*160;|&#x0*a0;`, sp = `(?
11
11
  * @param accum
12
12
  */
13
13
  const parseMagicLinks = (wikitext, config, accum) => {
14
- if (!config.regexMagicLinks) {
15
- try {
16
- config.regexMagicLinks = new RegExp(String.raw `(^|[^\p{L}\p{N}_])(?:(?:${config.protocol})(${string_1.extUrlCharFirst}${string_1.extUrlChar})|${magicLinkPattern})`, 'giu');
17
- }
18
- catch /* c8 ignore start */ {
19
- config.regexMagicLinks = new RegExp(String.raw `(^|\W)(?:(?:${config.protocol})(${string_1.extUrlCharFirst}${string_1.extUrlChar})|${magicLinkPattern})`, 'giu');
20
- }
21
- /* c8 ignore stop */
22
- }
14
+ config.regexMagicLinks ??= new RegExp(String.raw `(^|[^\p{L}\p{N}_])(?:(?:${config.protocol})(${string_1.extUrlCharFirst}${string_1.extUrlChar})|${magicLinkPattern})`, 'giu');
23
15
  return wikitext.replace(config.regexMagicLinks, (m, lead, p1) => {
24
16
  let url = lead ? m.slice(lead.length) : m;
25
17
  if (p1) {
26
18
  let trail = '';
27
- const m2 = /&(?:[lg]t|nbsp|#x0*(?:3[ce]|a0)|#0*(?:6[02]|160));/iu.exec(url);
28
- if (m2) {
29
- trail = url.slice(m2.index);
30
- url = url.slice(0, m2.index);
19
+ const index = url.search(/&(?:[lg]t|nbsp|#x0*(?:3[ce]|a0)|#0*(?:6[02]|160));/iu);
20
+ if (index !== -1) {
21
+ trail = url.slice(index);
22
+ url = url.slice(0, index);
31
23
  }
32
24
  const sep = url.includes('(') ? /[^,;\\.:!?][,;\\.:!?]+$/u : /[^,;\\.:!?)][,;\\.:!?)]+$/u, sepChars = sep.exec(url);
33
25
  if (sepChars) {
@@ -99,7 +99,7 @@ const parseTable = ({ firstChild: { data }, type, name }, config, accum) => {
99
99
  else {
100
100
  top = pop(top, stack);
101
101
  const regex = cell === '!' ? /!!|(?:\||\0\d+!\x7F){2}|\0\d+\+\x7F/gu : /(?:\||\0\d+!\x7F){2}|\0\d+\+\x7F/gu;
102
- let mt = regex.exec(attr), lastIndex = 0, lastSyntax = `\n${spaces}${cell}`;
102
+ let lastIndex = 0, lastSyntax = `\n${spaces}${cell}`, mt;
103
103
  /**
104
104
  * 创建表格单元格
105
105
  * @param tr 当前表格行
@@ -110,11 +110,10 @@ const parseTable = ({ firstChild: { data }, type, name }, config, accum) => {
110
110
  tr.insertAt(td);
111
111
  return td;
112
112
  };
113
- while (mt) {
113
+ for (mt = regex.exec(attr); mt; mt = regex.exec(attr)) {
114
114
  newTd(top);
115
115
  ({ lastIndex } = regex);
116
116
  [lastSyntax] = mt;
117
- mt = regex.exec(attr);
118
117
  }
119
118
  stack.push(top, newTd(top));
120
119
  }
@@ -14,7 +14,7 @@ const index_2 = require("./index");
14
14
  const atom_1 = require("./atom");
15
15
  /* NOT FOR BROWSER ONLY */
16
16
  const document_1 = require("../lib/document");
17
- const insecureStyle = /expression|(?:accelerator|-o-link(?:-source)?|-o-replace)\s*:|(?:url|src|image(?:-set)?)\s*\(|attr\s*\([^)]+[\s,]url/u, evil = /(?:^|\s|\*\/)(?:java|vb)script(?:\W|$)/iu, complexTypes = new Set(['ext', 'arg', 'magic-word', 'template']), itemAttrs = new Set(['itemtype', 'itemid', 'itemref']), urlAttrs = new Set([
17
+ const insecureStyle = /expression|(?:accelerator|-o-link(?:-source)?|-o-replace)\s*:|(?:url|src|image(?:-set)?)\s*\(|attr\s*\((?:(?!attr\s*\()[^)])+[\s,]url/u, evil = /(?:^|\s|\*\/)(?:java|vb)script(?:\W|$)/iu, complexTypes = new Set(['ext', 'arg', 'magic-word', 'template']), itemAttrs = new Set(['itemtype', 'itemid', 'itemref']), urlAttrs = new Set([
18
18
  'about',
19
19
  'property',
20
20
  'resource',
@@ -178,19 +178,19 @@ class AttributeToken extends index_2.Token {
178
178
  }
179
179
  }
180
180
  }
181
- else if ((/^xmlns:[\w:.-]+$/u.test(name) || urlAttrs.has(name)) && evil.test(value)
182
- || simple
183
- && (name === 'href' || type === 'ext-attr' && tag === 'img' && name === 'src')
184
- && !new RegExp(String.raw `^(?:${this.getAttribute('config').protocol}|//)\S+$`, 'iu')
185
- .test(value)) {
186
- LINT: {
187
- const s = lintConfig.getSeverity(rule, 'value');
188
- return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal-attribute-value', s);
181
+ else {
182
+ let valueIssue = (/^xmlns:[\w:.-]+$/u.test(name) || urlAttrs.has(name)) && evil.test(value)
183
+ || simple
184
+ && (name === 'href' || type === 'ext-attr' && tag === 'img' && name === 'src')
185
+ && !new RegExp(String.raw `^(?:${this.getAttribute('config').protocol}|//)\S+$`, 'iu')
186
+ .test(value);
187
+ if (simple && !valueIssue && type !== 'ext-attr') {
188
+ const data = (0, lint_1.provideValues)(tag, name), v = value.toLowerCase();
189
+ if (data.length > 0 && data.every(n => n !== v)) {
190
+ valueIssue = true;
191
+ }
189
192
  }
190
- }
191
- else if (simple && type !== 'ext-attr') {
192
- const data = (0, lint_1.provideValues)(tag, name), v = value.toLowerCase();
193
- if (data.length > 0 && data.every(n => n !== v)) {
193
+ if (valueIssue) {
194
194
  LINT: {
195
195
  const s = lintConfig.getSeverity(rule, 'value');
196
196
  return s && (0, lint_1.generateForChild)(lastChild, rect, rule, 'illegal-attribute-value', s);
@@ -22,16 +22,6 @@ exports.toAttributeType = toAttributeType;
22
22
  * @param type 属性类型
23
23
  */
24
24
  const toDirty = (type) => `${(0, exports.toAttributeType)(type)}-dirty`;
25
- const wordRegex = /* #__PURE__ */ (() => {
26
- try {
27
- // eslint-disable-next-line prefer-regex-literals
28
- return new RegExp(String.raw `[\p{L}\p{N}]`, 'u');
29
- }
30
- catch /* c8 ignore start */ {
31
- return /[^\W_]/u;
32
- }
33
- /* c8 ignore stop */
34
- })();
35
25
  const required = new Map([
36
26
  ['indicator', ['name']],
37
27
  ['langconvert', ['from', 'to']],
@@ -63,14 +53,14 @@ class AttributesToken extends index_2.Token {
63
53
  this.setAttribute('name', name);
64
54
  if (attr) {
65
55
  const regex = /([^\s/](?:(?!\0\d+~\x7F)[^\s/=])*)(?:((?:\s(?:\s|\0\d+[cn]\x7F)*)?(?:=|\0\d+~\x7F)(?:\s|\0\d+[cn]\x7F)*)(?:(["'])(.*?)(\3|$)|(\S*)))?/gsu;
66
- let out = '', mt = regex.exec(attr), lastIndex = 0;
56
+ let out = '', lastIndex = 0;
67
57
  const insertDirty = /** 插入无效属性 */ () => {
68
58
  if (out) {
69
59
  super.insertAt(new atom_1.AtomToken(out, toDirty(type), config, accum));
70
60
  out = '';
71
61
  }
72
62
  };
73
- while (mt) {
63
+ for (let mt = regex.exec(attr); mt; mt = regex.exec(attr)) {
74
64
  const { index, 0: full, 1: key, 2: equal, 3: quoteStart, 4: quoted, 5: quoteEnd, 6: unquoted } = mt;
75
65
  out += attr.slice(lastIndex, index);
76
66
  if (/^(?:[\w:]|\0\d+t\x7F)(?:[\w:.-]|\0\d+t\x7F)*$/u
@@ -85,7 +75,6 @@ class AttributesToken extends index_2.Token {
85
75
  out += full;
86
76
  }
87
77
  ({ lastIndex } = regex);
88
- mt = regex.exec(attr);
89
78
  }
90
79
  out += attr.slice(lastIndex);
91
80
  insertDirty();
@@ -185,7 +174,7 @@ class AttributesToken extends index_2.Token {
185
174
  }
186
175
  }
187
176
  else {
188
- const str = attr.text().trim(), severity = s[wordRegex.test(str) ? 1 : 2];
177
+ const str = attr.text().trim(), severity = s[/[\p{L}\p{N}]/u.test(str) ? 1 : 2];
189
178
  if (str && severity) {
190
179
  const e = (0, lint_1.generateForChild)(attr, rect, rules[0], 'invalid-attribute', severity);
191
180
  if (computeEditInfo) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CommentedToken = void 0;
4
+ const replace_1 = require("../util/replace");
4
5
  const index_1 = require("./index");
5
6
  const comment_1 = require("./nowiki/comment");
6
7
  /**
@@ -15,25 +16,19 @@ class CommentedToken extends index_1.Token {
15
16
  constructor(wikitext, config, accum = []) {
16
17
  super(undefined, config, accum, {});
17
18
  if (wikitext) {
18
- let i = wikitext.indexOf('<!--'), j = i !== -1 && wikitext.indexOf('-->', i + 4), lastIndex = 0;
19
- while (j !== false && j !== -1) {
20
- if (i > lastIndex) {
21
- this.insertAt(wikitext.slice(lastIndex, i));
22
- }
19
+ (0, replace_1.safeStringReplace)(wikitext, '<!--', '-->', false, inner => {
23
20
  // @ts-expect-error abstract class
24
- this.insertAt(new comment_1.CommentToken(wikitext.slice(i + 4, j), true, config, accum));
25
- lastIndex = j + 3;
26
- i = wikitext.indexOf('<!--', lastIndex);
27
- j = i !== -1 && wikitext.indexOf('-->', i + 4);
28
- }
29
- if (lastIndex < wikitext.length) {
30
- this.insertAt(wikitext.slice(lastIndex));
31
- }
21
+ this.insertAt(new comment_1.CommentToken(inner, true, config, accum));
22
+ return '';
23
+ }, s => {
24
+ this.insertAt(s);
25
+ return '';
26
+ });
32
27
  }
33
28
  }
34
29
  /** @private */
35
30
  lint(start = this.getAbsoluteIndex()) {
36
- LINT: return super.lint(start, new RegExp(String.raw `<\s*(?:\/\s*)?(${this.name})\b`, 'giu'));
31
+ LINT: return super.lint(start, new RegExp(String.raw `<\s*(?:/\s*)?(${this.name})\b`, 'giu'));
37
32
  }
38
33
  }
39
34
  exports.CommentedToken = CommentedToken;
@@ -86,7 +86,7 @@ let ConverterRuleToken = (() => {
86
86
  */
87
87
  constructor(rule, hasColon = true, config = index_1.default.getConfig(), accum = []) {
88
88
  super(undefined, config, accum, {});
89
- const i = rule.indexOf(':'), j = rule.slice(0, i).indexOf('=>'), v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i);
89
+ const i = rule.indexOf(':'), j = rule.slice(0, i).indexOf('=>'), v = rule.slice(j === -1 ? 0 : j + 2, i);
90
90
  if (hasColon && config.variants.includes(v.trim())) {
91
91
  super.insertAt(new atom_1.AtomToken(v, 'converter-rule-variant', config, accum));
92
92
  super.insertAt(getRuleFromTo(rule.slice(i + 1), 'to', config, accum));
package/dist/src/index.js CHANGED
@@ -113,6 +113,7 @@ class Token extends element_1.AstElement {
113
113
  return this;
114
114
  }
115
115
  /* c8 ignore start */
116
+ // eslint-disable-next-line unicorn/prefer-combined-guards
116
117
  if (this.#stage >= constants_1.MAX_STAGE) {
117
118
  return this;
118
119
  }
@@ -423,8 +424,7 @@ class Token extends element_1.AstElement {
423
424
  }
424
425
  if (!ignoreDisables) {
425
426
  const regex = new RegExp(String.raw `<!--\s*${configurationComment}-(disable(?:(?:-next)?-line)?|enable)(\s[\sa-z,-]*)?-->`, 'gu'), ignores = [];
426
- let mt = regex.exec(wikitext);
427
- while (mt) {
427
+ for (let mt = regex.exec(wikitext); mt; mt = regex.exec(wikitext)) {
428
428
  const { 1: type, index } = mt, detail = mt[2]?.trim();
429
429
  let line;
430
430
  if (type === 'disable-line' || type === 'disable-next-line') {
@@ -436,7 +436,6 @@ class Token extends element_1.AstElement {
436
436
  to: type === 'enable' ? regex.lastIndex : undefined,
437
437
  rules: detail ? new Set(detail.split(',').map(rule => rule.trim())) : undefined,
438
438
  });
439
- mt = regex.exec(wikitext);
440
439
  }
441
440
  errors = errors.filter(({ rule, startLine, startIndex }) => {
442
441
  const nearest = { pos: 0 };
@@ -124,7 +124,7 @@ let LinkBaseToken = (() => {
124
124
  /** @private */
125
125
  toString(skip) {
126
126
  const str = super.toString(skip, this.#delimiter);
127
- if (this.#bracket) { // NOT FOR TEMPLATE
127
+ if (this.#bracket) { // eslint-disable-line unicorn/prefer-ternary
128
128
  return `[[${str}]]`;
129
129
  }
130
130
  return str;
@@ -137,7 +137,7 @@ let LinkBaseToken = (() => {
137
137
  'trim']()
138
138
  : `${target.trim()}|${(0, string_1.text)(childNodes.slice(1)
139
139
  .filter(({ type: t, name }) => t !== 'image-parameter' || name !== 'invalid'), '|')}`;
140
- if (this.#bracket) { // NOT FOR TEMPLATE
140
+ if (this.#bracket) { // eslint-disable-line unicorn/prefer-ternary
141
141
  return `[[${str}]]`;
142
142
  }
143
143
  return str;
@@ -25,8 +25,8 @@ const explode = (str) => {
25
25
  return [];
26
26
  }
27
27
  const regex = /-\{|\}-|\|/gu, exploded = [];
28
- let mt = regex.exec(str), depth = 0, lastIndex = 0;
29
- while (mt) {
28
+ let depth = 0, lastIndex = 0;
29
+ for (let mt = regex.exec(str); mt; mt = regex.exec(str)) {
30
30
  const { 0: match, index } = mt;
31
31
  if (match !== '|') {
32
32
  depth += match === '-{' ? 1 : -1;
@@ -35,7 +35,6 @@ const explode = (str) => {
35
35
  exploded.push(str.slice(lastIndex, index));
36
36
  ({ lastIndex } = regex);
37
37
  }
38
- mt = regex.exec(str);
39
38
  }
40
39
  exploded.push(str.slice(lastIndex));
41
40
  return exploded;
@@ -28,7 +28,7 @@ class GalleryToken extends index_2.MultiLineToken {
28
28
  continue;
29
29
  }
30
30
  const [, file, alt] = matches;
31
- if (this.#checkFile(file)) {
31
+ if (this.#checkFile(file)) { // eslint-disable-line unicorn/prefer-ternary
32
32
  // @ts-expect-error abstract class
33
33
  super.insertAt(new galleryImage_1.GalleryImageToken('gallery', file, alt, config, accum));
34
34
  }
@@ -40,13 +40,51 @@ class NestedToken extends index_2.Token {
40
40
  wikitext &&= (0, braces_1.parseBraces)(wikitext, config, accum);
41
41
  accum.splice(length, 1);
42
42
  }
43
- else {
44
- wikitext &&= wikitext.replace(regex, (_, name, attr, inner, closing) => {
45
- const str = `\0${accum.length + 1}e\x7F`;
46
- // @ts-expect-error abstract class
47
- new ext_1.ExtToken(name, attr, inner, closing, config, false, accum);
48
- return str;
49
- });
43
+ else if (wikitext) {
44
+ const noMoreTags = new Set();
45
+ regex.lastIndex = 0;
46
+ let lastIndex = 0, text = '';
47
+ for (let mt = regex.exec(wikitext); mt; mt = regex.exec(wikitext)) {
48
+ const i = wikitext.indexOf('>', regex.lastIndex);
49
+ if (i === -1) {
50
+ break;
51
+ }
52
+ const [substr, name] = mt, { index } = mt;
53
+ text += wikitext.slice(lastIndex, index);
54
+ ({ lastIndex } = regex);
55
+ let matched = true, attr, inner, closing;
56
+ if (wikitext[i - 1] === '/') {
57
+ attr = wikitext.slice(lastIndex, i - 1);
58
+ lastIndex = i + 1;
59
+ regex.lastIndex = lastIndex;
60
+ }
61
+ else if (noMoreTags.has(name.toLowerCase())) {
62
+ matched = false;
63
+ }
64
+ else {
65
+ const after = wikitext.slice(i + 1), m = new RegExp(String.raw `</(${name}\s*)>`, 'iu').exec(after);
66
+ if (m) {
67
+ inner = after.slice(0, m.index);
68
+ [, closing] = m;
69
+ lastIndex = i + 1 + m.index + m[0].length;
70
+ regex.lastIndex = lastIndex;
71
+ }
72
+ else {
73
+ matched = false;
74
+ noMoreTags.add(name.toLowerCase());
75
+ }
76
+ }
77
+ if (matched) {
78
+ const str = `\0${accum.length + 1}e\x7F`;
79
+ // @ts-expect-error abstract class
80
+ new ext_1.ExtToken(name, attr, inner, closing, config, false, accum);
81
+ text += str;
82
+ }
83
+ else {
84
+ text += substr;
85
+ }
86
+ }
87
+ wikitext = text + wikitext.slice(lastIndex);
50
88
  }
51
89
  wikitext &&= wikitext.replace(/(^|\0\d+.\x7F)([^\0]+)(?=$|\0\d+.\x7F)/gu, (_, lead, substr) => {
52
90
  // @ts-expect-error abstract class
@@ -8,7 +8,7 @@ const string_1 = require("../util/string");
8
8
  const lint_1 = require("../util/lint");
9
9
  const index_1 = __importDefault(require("../index"));
10
10
  const index_2 = require("./index");
11
- const linkRegex = /* #__PURE__ */ (() => new RegExp(`https?://${string_1.extUrlCharFirst}${string_1.extUrlChar}$`, 'iu'))();
11
+ const linkRegex = /* #__PURE__ */ (() => new RegExp(`^https?://${string_1.extUrlCharFirst}${string_1.extUrlChar}$`, 'u'))();
12
12
  /**
13
13
  * template or magic word parameter
14
14
  *
@@ -74,9 +74,9 @@ class ParameterToken extends index_2.Token {
74
74
  LINT: {
75
75
  const errors = super.lint(start, re), rule = 'unescaped', { lintConfig } = index_1.default, s = lintConfig.getSeverity(rule);
76
76
  if (s) {
77
- const { firstChild } = this, link = linkRegex.exec(firstChild.text())?.[0];
77
+ const { firstChild } = this, key = firstChild.text().toLowerCase(), i = Math.max(key.lastIndexOf('http://'), key.lastIndexOf('https://')), link = i !== -1 && key.slice(i);
78
78
  try {
79
- if (link && new URL(link).search) {
79
+ if (link && linkRegex.test(link) && new URL(link).search) {
80
80
  const e = (0, lint_1.generateForChild)(firstChild, { start }, rule, 'unescaped-query', s);
81
81
  e.startIndex = e.endIndex;
82
82
  e.startLine = e.endLine;
package/dist/src/pre.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PreToken = void 0;
4
4
  const constants_1 = require("../util/constants");
5
+ const replace_1 = require("../util/replace");
5
6
  const index_1 = require("./index");
6
7
  const noinclude_1 = require("./nowiki/noinclude");
7
8
  /**
@@ -14,29 +15,13 @@ class PreToken extends index_1.Token {
14
15
  }
15
16
  /** @class */
16
17
  constructor(wikitext, config, accum = []) {
17
- if (wikitext) {
18
- const opening = /<nowiki>/giu, closing = /<\/nowiki>/giu, { length } = opening.source;
19
- let i = opening.exec(wikitext);
20
- if (i) {
21
- closing.lastIndex = i.index + length;
22
- }
23
- let j = closing.exec(wikitext), lastIndex = 0, str = '';
24
- while (i && j) {
25
- // @ts-expect-error abstract class
26
- new noinclude_1.NoincludeToken(i[0], config, accum, true);
27
- // @ts-expect-error abstract class
28
- new noinclude_1.NoincludeToken(j[0], config, accum, true);
29
- str += `${wikitext.slice(lastIndex, i.index)}\0${accum.length - 1}n\x7F${wikitext.slice(i.index + length, j.index)}\0${accum.length}n\x7F`;
30
- lastIndex = j.index + length + 1;
31
- opening.lastIndex = lastIndex;
32
- i = opening.exec(wikitext);
33
- if (i) {
34
- closing.lastIndex = i.index + length;
35
- }
36
- j = closing.exec(wikitext);
37
- }
38
- wikitext = str + wikitext.slice(lastIndex);
39
- }
18
+ wikitext &&= (0, replace_1.safeStringReplace)(wikitext, '<nowiki>', '</nowiki>', true, (inner, opening, closing) => {
19
+ // @ts-expect-error abstract class
20
+ new noinclude_1.NoincludeToken(opening, config, accum, true);
21
+ // @ts-expect-error abstract class
22
+ new noinclude_1.NoincludeToken(closing, config, accum, true);
23
+ return `\0${accum.length - 1}n\x7F${inner}\0${accum.length}n\x7F`;
24
+ });
40
25
  super(wikitext, config, accum, {});
41
26
  this.setAttribute('stage', constants_1.MAX_STAGE - 1);
42
27
  }
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CharinsertLineToken = void 0;
7
7
  const lint_1 = require("../../util/lint");
8
+ const replace_1 = require("../../util/replace");
8
9
  const rect_1 = require("../../lib/rect");
9
10
  const index_1 = __importDefault(require("../../index"));
10
11
  const index_2 = require("./index");
@@ -21,9 +22,9 @@ class CharinsertLineToken extends index_2.SingleLineToken {
21
22
  }
22
23
  /** @class */
23
24
  constructor(wikitext, config, accum) {
24
- super(wikitext?.replace(/<(nowiki)>(.*?)<\/(nowiki)>/giu, (_, opening, content, closing) => {
25
+ super(wikitext && (0, replace_1.safeStringReplace)(wikitext, '<nowiki>', '</nowiki>', true, (content, opening, closing) => {
25
26
  // @ts-expect-error abstract class
26
- new ext_1.ExtToken(opening, undefined, content, closing, config, false, accum);
27
+ new ext_1.ExtToken(opening.slice(1, -1), undefined, content, closing.slice(2, -1), config, false, accum);
27
28
  return `\0${accum.length - 2}e\x7F`;
28
29
  }), config, accum);
29
30
  }
@@ -37,22 +38,21 @@ class CharinsertLineToken extends index_2.SingleLineToken {
37
38
  const errors = [], str = this.childNodes.map(child => child.type === 'text'
38
39
  ? child.data
39
40
  : `<nowiki>${child.innerText.replace(/[^+]/gu, '_')}</nowiki>`).join(''), re = /(?:^|\s)\S+/gu, rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-content', 'charinsert');
40
- let mt = re.exec(str);
41
- while (mt) {
41
+ for (let mt = re.exec(str); mt; mt = re.exec(str)) {
42
42
  const [item] = mt;
43
43
  let i = item.indexOf('+');
44
44
  if (i !== -1 && item.slice(0, i).trim()) {
45
45
  i = item.indexOf('+', i + 1);
46
46
  }
47
- if (i !== -1) {
48
- const e = (0, lint_1.generateForSelf)(this, rect, rule, msg, s), startOffset = mt.index + i, endOffset = mt.index + item.length;
49
- e.endIndex = start + endOffset;
50
- e.endCol = e.startCol + endOffset;
51
- e.startIndex += startOffset;
52
- e.startCol += startOffset;
53
- errors.push(e);
47
+ if (i === -1) {
48
+ continue;
54
49
  }
55
- mt = re.exec(str);
50
+ const e = (0, lint_1.generateForSelf)(this, rect, rule, msg, s), startOffset = mt.index + i, endOffset = mt.index + item.length;
51
+ e.endIndex = start + endOffset;
52
+ e.endCol = e.startCol + endOffset;
53
+ e.startIndex += startOffset;
54
+ e.startCol += startOffset;
55
+ errors.push(e);
56
56
  }
57
57
  return errors;
58
58
  }
@@ -152,14 +152,14 @@ let ExtToken = (() => {
152
152
  // NestedToken 依赖 ExtToken
153
153
  const { NestedToken } = require('../nested');
154
154
  // @ts-expect-error abstract class
155
- innerToken = new NestedToken(inner, /<(option|choicetemplate)(\s[^>]*?)?(?:\/>|>(.*?)<\/(\1)>)/gsu, ['option', 'choicetemplate'], newConfig, accum);
155
+ innerToken = new NestedToken(inner, /<(option|choicetemplate)(?=\s|\/?>)/gu, ['option', 'choicetemplate'], newConfig, accum);
156
156
  break;
157
157
  }
158
158
  case 'combobox': {
159
159
  // NestedToken 依赖 ExtToken
160
160
  const { NestedToken } = require('../nested');
161
161
  // @ts-expect-error abstract class
162
- innerToken = new NestedToken(inner, /<(combooption)(\s[^>]*?)?(?:\/>|>(.*?)<\/(combooption\s*)>)/gisu, ['combooption'], newConfig, accum);
162
+ innerToken = new NestedToken(inner, /<(combooption)(?=\s|\/?>)/gu, ['combooption'], newConfig, accum);
163
163
  break;
164
164
  }
165
165
  case 'gallery': {
@@ -22,19 +22,49 @@ class TranslateToken extends index_2.TagPairToken {
22
22
  */
23
23
  constructor(attr, inner, config, accum = []) {
24
24
  const attrToken = new syntax_1.SyntaxToken(attr, 'translate-attr', config, accum);
25
- inner = inner?.replace(/<tvar(\|[^>]+)>(.*?)<\/>/gsu, (_, p1, p2) => {
26
- // @ts-expect-error abstract class
27
- new tvar_1.TvarToken('tvar', p1, false, config, accum);
28
- // @ts-expect-error abstract class
29
- new tvar_1.TvarToken('', '', true, config, accum);
30
- return `\0${accum.length - 2}n\x7F${p2}\0${accum.length}n\x7F`;
31
- }).replace(/<(tvar)(\s+name\s*=(?:\s*(?:(["']).*?\3|[^"'\s>]+))?\s*)>(.*?)<\/(tvar)(\s*)>/gisu, (_, p1, p2, __, p3, p4, p5) => {
32
- // @ts-expect-error abstract class
33
- new tvar_1.TvarToken(p1, p2, false, config, accum);
34
- // @ts-expect-error abstract class
35
- new tvar_1.TvarToken(p4, p5, true, config, accum);
36
- return `\0${accum.length - 2}n\x7F${p3}\0${accum.length}n\x7F`;
37
- });
25
+ if (inner) {
26
+ let result = '', lastIndex = 0, i, j, k, index;
27
+ const update = /** @ignore */ () => {
28
+ i = inner.indexOf('<tvar|', lastIndex);
29
+ index = i + 6;
30
+ j = inner.indexOf('>', index);
31
+ k = inner.indexOf('</>', j + 1);
32
+ return [i, j === index ? -1 : j, k];
33
+ };
34
+ for ([i, j, k] = update(); i !== -1 && j !== -1 && k !== -1; [i, j, k] = update()) {
35
+ result += inner.slice(lastIndex, i);
36
+ lastIndex = k + 3;
37
+ // @ts-expect-error abstract class
38
+ new tvar_1.TvarToken('tvar', inner.slice(index - 1, j), false, config, accum);
39
+ // @ts-expect-error abstract class
40
+ new tvar_1.TvarToken('', '', true, config, accum);
41
+ result += `\0${accum.length - 2}n\x7F${inner.slice(j + 1, k)}\0${accum.length}n\x7F`;
42
+ }
43
+ inner = result + inner.slice(lastIndex);
44
+ result = '';
45
+ lastIndex = 0;
46
+ const start = /<(tvar)(\s+name\s*=)/giu, end = /<\/(tvar)(\s*)>/giu;
47
+ for (let mt = start.exec(inner); mt; mt = start.exec(inner)) {
48
+ i = inner.indexOf('>', start.lastIndex);
49
+ const name = inner.slice(start.lastIndex, i);
50
+ if (i === -1 || !/^(?:\s*(?:(["']).*?\1|[^"'\s>]+))?\s*$/su.test(name)) {
51
+ break;
52
+ }
53
+ end.lastIndex = i + 1;
54
+ const mt2 = end.exec(inner);
55
+ if (!mt2) {
56
+ break;
57
+ }
58
+ // @ts-expect-error abstract class
59
+ new tvar_1.TvarToken(mt[1], mt[2] + name, false, config, accum);
60
+ // @ts-expect-error abstract class
61
+ new tvar_1.TvarToken(mt2[1], mt2[2], true, config, accum);
62
+ result += `${inner.slice(lastIndex, mt.index)}\0${accum.length - 2}n\x7F${inner.slice(i + 1, mt2.index)}\0${accum.length}n\x7F`;
63
+ ({ lastIndex } = end);
64
+ start.lastIndex = lastIndex;
65
+ }
66
+ inner = result + inner.slice(lastIndex);
67
+ }
38
68
  const innerToken = new index_1.Token(inner, config, accum);
39
69
  innerToken.type = 'translate-inner';
40
70
  super('translate', attrToken, innerToken, 'translate', config, accum);
@@ -333,7 +333,7 @@ let TranscludeToken = (() => {
333
333
  this.#anonCount +=
334
334
  1;
335
335
  let i;
336
- // eslint-disable-next-line prefer-const
336
+ // eslint-disable-next-line prefer-const, unicorn/no-immediate-mutation
337
337
  i = this.#anonCount - 1;
338
338
  const token = addedToken, newName = String(i + 1);
339
339
  token.setAttribute('name', newName);
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ /**
8
+ * 验证JSON数据并返回错误信息
9
+ * @param schemaPath JSON Schema文件路径
10
+ * @param dataVar 错误信息前缀
11
+ */
12
+ exports.default = (schemaPath, dataVar = '#') => {
13
+ const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf8'));
14
+ const Ajv = require(schema.$schema?.includes('draft-07') ? 'ajv' : 'ajv/dist/2020'), ajvErrors = require('ajv-errors');
15
+ const ajv = new Ajv({ allErrors: true });
16
+ ajvErrors(ajv, { singleError: true });
17
+ ajv.addKeyword('markdownDescription');
18
+ const v = ajv.compile(schema);
19
+ return data => {
20
+ if (v(data)) {
21
+ return '';
22
+ }
23
+ const errors = v.errors, { instancePath } = errors[0], related = errors.filter(error => error.instancePath === instancePath);
24
+ return ajv.errorsText([related.find(({ keyword }) => keyword === 'errorMessage') || related[0]], { dataVar });
25
+ };
26
+ };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safePatternReplace = exports.safeStringReplace = void 0;
4
+ /**
5
+ * 使用`String#indexOf`避免正则表达式的回溯
6
+ * @param wikitext 源文本
7
+ * @param start 起始字符串
8
+ * @param end 结束字符串
9
+ * @param insensitive 是否忽略大小写
10
+ * @param matched 匹配到的内容处理函数
11
+ * @param unmatched 未匹配到的内容处理函数
12
+ */
13
+ const safeStringReplace = (wikitext, start, end, insensitive, matched, unmatched = (str) => str) => {
14
+ const text = insensitive ? wikitext.toLowerCase() : wikitext, lStart = start.length, lEnd = end.length;
15
+ let result = '', lastIndex = 0, i, j, index;
16
+ const update = /** @ignore */ () => {
17
+ i = text.indexOf(start, lastIndex);
18
+ index = i + lStart;
19
+ return [i, text.indexOf(end, index), index];
20
+ };
21
+ for ([i, j, index] = update(); i !== -1 && j !== -1; [i, j, index] = update()) {
22
+ result += unmatched(wikitext.slice(lastIndex, i));
23
+ lastIndex = j + lEnd;
24
+ result += matched(wikitext.slice(index, j), insensitive ? wikitext.slice(i, index) : start, insensitive ? wikitext.slice(j, lastIndex) : end);
25
+ }
26
+ return result + unmatched(wikitext.slice(lastIndex));
27
+ };
28
+ exports.safeStringReplace = safeStringReplace;
29
+ /**
30
+ * 使用`RegExp#exec`避免正则表达式的回溯
31
+ * @param wikitext 源文本
32
+ * @param start 起始正则表达式
33
+ * @param end 结束字符串
34
+ * @param matched 匹配到的内容处理函数
35
+ */
36
+ const safePatternReplace = (wikitext, start, end, matched) => {
37
+ let result = '', lastIndex = 0;
38
+ start.lastIndex = 0;
39
+ for (let mt = start.exec(wikitext); mt; mt = start.exec(wikitext)) {
40
+ const i = wikitext.indexOf(end, start.lastIndex);
41
+ if (i === -1) {
42
+ break;
43
+ }
44
+ const { index } = mt;
45
+ result += wikitext.slice(lastIndex, index);
46
+ lastIndex = i + end.length;
47
+ const r = matched(wikitext.slice(start.lastIndex, i), mt);
48
+ if (r === false) {
49
+ ({ lastIndex } = start);
50
+ result += wikitext.slice(index, lastIndex);
51
+ }
52
+ else {
53
+ result += r;
54
+ start.lastIndex = lastIndex;
55
+ }
56
+ }
57
+ return result + wikitext.slice(lastIndex);
58
+ };
59
+ exports.safePatternReplace = safePatternReplace;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toLowerCase = exports.decodeNumber = exports.decodeHtml = exports.decodeHtmlBasic = exports.text = exports.removeCommentLine = exports.removeComment = exports.tidy = exports.trimLc = exports.extUrlChar = exports.extUrlCharFirst = exports.zs = void 0;
4
4
  exports.restore = restore;
5
+ /* NOT FOR BROWSER ONLY END */
5
6
  exports.zs = String.raw ` \xA0\u1680\u2000-\u200A\u202F\u205F\u3000`;
6
7
  const commonExtUrlChar = String.raw `[^[\]<>"\0-\x1F\x7F${exports.zs}\uFFFD]`;
7
8
  exports.extUrlCharFirst = String.raw `(?:\[[\da-f:.]+\]|${commonExtUrlChar})`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.48.0",
3
+ "version": "2.48.2",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -39,7 +39,7 @@
39
39
  "url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
40
40
  },
41
41
  "scripts": {
42
- "ls": "npm i --package-lock-only && npm ls --package-lock-only --all --include=peer --omit=dev",
42
+ "ls": "npm i --package-lock-only && npm ls --package-lock-only -a --include=peer --omit=dev",
43
43
  "prepublishOnly": "npm run build:core",
44
44
  "build:core": "bash build.sh",
45
45
  "build:define": "bash sed.sh -i \"s/pkg = \\$PKG/pkg = '$(npm pkg get name)'/; s/version = \\$VERSION/version = '$(npm pkg get version)'/\" dist/bin/config.js",
@@ -47,15 +47,15 @@
47
47
  "diff": "bash diff.sh",
48
48
  "diff:stat": "f() { git diff --stat -w --color=always $1 $2 -- . | grep '\\.ts'; }; f",
49
49
  "lint:ts": "[ ! -f dist/bin/config.js ] && npm run build:core; tsc --noEmit && eslint --cache .",
50
- "lint:json": "v8r -s config/.schema.json config/*.json && v8r -s data/.schema.json data/*.json && testUtil dist/test/json.js",
51
- "lint:md": "markdownlint-cli2 '*.md'",
52
- "lint": "npm run lint:ts && npm run lint:json && npm run lint:md",
50
+ "lint:md": "myMarkdownlint",
51
+ "lint": "npm run lint:ts && npm run lint:md",
53
52
  "prof": "node dist/test/prof.js",
54
- "coverage": "tsc --declaration false --target es2024 && npm run build:define && c8 npm run test:ci && node dist/script/coverage.js && open coverage/index.html",
53
+ "coverage": "tsc -d false -t es2024 && npm run build:define && c8 npm run test:ci && node dist/script/coverage.js && open coverage/index.html",
55
54
  "test:unit": "testUtil dist/test/test.js",
55
+ "test:json": "testUtil dist/test/json.js",
56
56
  "test:parser": "LC_ALL=en.UTF-8 testUtil dist/test/parserTests.js",
57
57
  "test:perf": "testUtil --test-reporter=spec dist/test/perf.js",
58
- "test": "npm run test:unit && npm run test:parser",
58
+ "test": "npm run test:unit && npm run test:parser && npm run test:json",
59
59
  "test:parseronly": "LSP=0 npm run test:parser",
60
60
  "test:ci": "LSP=0 npm test",
61
61
  "test:end": "pkill -x http-server",
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "peerDependencies": {
77
77
  "mathoid-texvcjs": ">=0.4.0",
78
- "stylelint": ">=16.0.0",
78
+ "stylelint-config-recommended": ">=14.0.0",
79
79
  "vscode-css-languageservice": ">=3.0.0",
80
80
  "vscode-html-languageservice": ">=3.1.0",
81
81
  "vscode-json-languageservice": ">=3.2.0"
@@ -84,7 +84,7 @@
84
84
  "mathoid-texvcjs": {
85
85
  "optional": true
86
86
  },
87
- "stylelint": {
87
+ "stylelint-config-recommended": {
88
88
  "optional": true
89
89
  },
90
90
  "vscode-css-languageservice": {
@@ -98,30 +98,31 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
- "@bhsd/cm-util": "^2.4.1",
101
+ "@bhsd/cm-util": "^2.4.2",
102
102
  "@bhsd/common": "^4.1.0",
103
- "@bhsd/stylelint-util": "^1.1.0",
103
+ "@bhsd/stylelint-util": "^2.0.0",
104
104
  "culori": "^4.0.2",
105
105
  "entities": "^8.1.0",
106
- "vscode-languageserver-types": "^3.18.3"
106
+ "vscode-languageserver-types": "^3.18.5"
107
107
  },
108
108
  "devDependencies": {
109
- "@bhsd/code-standard": "^4.3.0",
109
+ "@bhsd/code-standard": "^5.1.0",
110
110
  "@bhsd/nodejs": "^1.2.1",
111
- "@bhsd/test-util": "^3.2.1",
111
+ "@bhsd/test-util": "^3.3.0",
112
+ "ajv": "^8.20.0",
113
+ "ajv-errors": "^3.0.0",
112
114
  "c8": "^12.0.0",
113
115
  "diff2html-cli": "^5.2.15",
114
116
  "esbuild": "^0.28.2",
115
- "eslint": "^10.11.0",
116
- "markdownlint-cli2": "^0.23.3",
117
+ "eslint": "^10.14.0",
117
118
  "mathoid-texvcjs": "^0.6.0",
118
119
  "stylelint": "^17.16.0",
120
+ "stylelint-config-recommended": "^18.0.0",
119
121
  "typescript": "^6.0.3",
120
- "v8r": "^6.1.0",
121
122
  "vscode-css-languageservice": "^6.3.10",
122
123
  "vscode-html-languageservice": "^5.6.2",
123
124
  "vscode-json-languageservice": "^5.7.2",
124
- "vscode-languageserver-textdocument": "^1.0.15"
125
+ "vscode-languageserver-textdocument": "^1.0.16"
125
126
  },
126
127
  "engines": {
127
128
  "node": "^20.19.0 || ^22.13.0 || >=24.11.0"