wikilint 2.3.5 → 2.3.8

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.
@@ -19,7 +19,7 @@ class AstElement extends node_1.AstNode {
19
19
  }
20
20
  /** 合并相邻的文本子节点 */
21
21
  normalize() {
22
- const { childNodes } = this;
22
+ const childNodes = [...this.childNodes];
23
23
  for (let i = childNodes.length - 1; i >= 0; i--) {
24
24
  const { type, data } = childNodes[i], prev = childNodes[i - 1];
25
25
  if (type !== 'text' || this.getGaps(i - 1)) {
@@ -33,6 +33,7 @@ class AstElement extends node_1.AstNode {
33
33
  childNodes.splice(i, 1);
34
34
  }
35
35
  }
36
+ this.setAttribute('childNodes', childNodes);
36
37
  }
37
38
  /**
38
39
  * 移除子节点
@@ -81,7 +82,9 @@ class AstElement extends node_1.AstNode {
81
82
  * @param elements 新的子节点
82
83
  */
83
84
  replaceChildren(...elements) {
84
- this.childNodes.length = 0;
85
+ for (let i = this.length - 1; i >= 0; i--) {
86
+ this.removeAt(i);
87
+ }
85
88
  this.append(...elements);
86
89
  }
87
90
  /**
@@ -90,7 +93,8 @@ class AstElement extends node_1.AstNode {
90
93
  * @param i 子节点位置
91
94
  */
92
95
  setText(str, i = 0) {
93
- const oldText = this.childNodes.at(i);
96
+ i += i < 0 ? this.length : 0;
97
+ const oldText = this.childNodes[i];
94
98
  const { data } = oldText;
95
99
  oldText.replaceData(str);
96
100
  return data;
package/dist/lib/node.js CHANGED
@@ -11,7 +11,7 @@ class AstNode {
11
11
  }
12
12
  /** 末位子节点 */
13
13
  get lastChild() {
14
- return this.childNodes.at(-1);
14
+ return this.childNodes[this.childNodes.length - 1];
15
15
  }
16
16
  /** 父节点 */
17
17
  get parentNode() {
@@ -68,21 +68,15 @@ class AstNode {
68
68
  posFromIndex(index) {
69
69
  const str = String(this);
70
70
  if (index >= -str.length && index <= str.length) {
71
- const lines = str.slice(0, index).split('\n');
72
- return {
73
- top: lines.length - 1,
74
- left: lines.at(-1).length,
75
- };
71
+ const lines = str.slice(0, index).split('\n'), top = lines.length - 1;
72
+ return { top, left: lines[top].length };
76
73
  }
77
74
  return undefined;
78
75
  }
79
76
  /** 获取行数和最后一行的列数 */
80
77
  #getDimension() {
81
- const lines = String(this).split('\n');
82
- return {
83
- height: lines.length,
84
- width: lines.at(-1).length,
85
- };
78
+ const lines = String(this).split('\n'), height = lines.length;
79
+ return { height, width: lines[height - 1].length };
86
80
  }
87
81
  /** @private */
88
82
  getGaps(i) {
package/dist/lib/text.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AstText = void 0;
4
4
  const Parser = require("../index");
5
5
  const node_1 = require("./node");
6
- const errorSyntax = /<\s*\/?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*\])|(?<=^|\])([^[]*?)\]+|\]{2,}|https?[:/]\/+/giu, errorSyntaxUrl = /<\s*\/?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*\])|(?<=^|\])([^[]*?)\]+|\]{2,}/giu, disallowedTags = [
6
+ const errorSyntax = /<\s*\/?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*\])|((?:^|\])[^[]*?)\]+|https?[:/]\/+/giu, errorSyntaxUrl = /<\s*\/?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*\])|((?:^|\])[^[]*?)\]+/giu, disallowedTags = [
7
7
  'html',
8
8
  'head',
9
9
  'style',
@@ -37,7 +37,11 @@ class AstText extends node_1.AstNode {
37
37
  /** @param text 包含文本 */
38
38
  constructor(text) {
39
39
  super();
40
- this.data = text;
40
+ Object.defineProperties(this, {
41
+ data: {
42
+ value: text,
43
+ },
44
+ });
41
45
  }
42
46
  /** @private */
43
47
  toString() {
@@ -68,17 +72,19 @@ class AstText extends node_1.AstNode {
68
72
  else {
69
73
  errorRegex = errorSyntax;
70
74
  }
71
- const errors = [...data.matchAll(errorRegex)], { ext, html } = this.getRootNode().getAttribute('config');
72
- if (errors.length > 0) {
75
+ const errors = [], { ext, html } = this.getRootNode().getAttribute('config');
76
+ if (data.search(errorRegex) !== -1) {
77
+ errorRegex.lastIndex = 0;
73
78
  const root = this.getRootNode(), { top, left } = root.posFromIndex(start), tags = new Set(['onlyinclude', 'noinclude', 'includeonly', ext, html, disallowedTags].flat(2));
74
- return errors
75
- .map(({ 0: error, 1: tag, 2: prefix, index }) => {
76
- if (prefix) {
79
+ for (let mt = errorRegex.exec(data); mt; mt = errorRegex.exec(data)) {
80
+ const [, tag, prefix] = mt;
81
+ let { 0: error, index } = mt;
82
+ if (prefix && prefix !== ']') {
77
83
  const { length } = prefix;
78
84
  index += length;
79
85
  error = error.slice(length);
80
86
  }
81
- const startIndex = start + index, lines = data.slice(0, index).split('\n'), startLine = lines.length + top - 1, line = lines.at(-1), startCol = lines.length === 1 ? left + line.length : line.length, { 0: char, length } = error, endIndex = startIndex + length, rootStr = String(root), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && (char !== '<' || /[\s/>]/u.test(nextChar ?? ''))
87
+ const startIndex = start + index, lines = data.slice(0, index).split('\n'), startLine = lines.length + top - 1, line = lines[lines.length - 1], startCol = lines.length === 1 ? left + line.length : line.length, { 0: char, length } = error, endIndex = startIndex + length, rootStr = String(root), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && (char !== '<' || /[\s/>]/u.test(nextChar ?? ''))
82
88
  || char === '{' && (nextChar === char || previousChar === '-')
83
89
  || char === '}' && (previousChar === char || nextChar === '-')
84
90
  || char === '[' && (nextChar === char
@@ -88,17 +94,20 @@ class AstText extends node_1.AstNode {
88
94
  || !data.slice(0, index).trim() && previousType === 'free-ext-link')
89
95
  ? 'error'
90
96
  : 'warning';
91
- return (char !== '<' || tags.has(tag.toLowerCase())) && {
92
- message: Parser.msg('lonely "$1"', char === 'h' ? error : char),
93
- severity,
94
- startIndex,
95
- endIndex,
96
- startLine,
97
- endLine: startLine,
98
- startCol,
99
- endCol: startCol + length,
100
- };
101
- }).filter(Boolean);
97
+ if (char !== '<' || tags.has(tag.toLowerCase())) {
98
+ errors.push({
99
+ message: Parser.msg('lonely "$1"', char === 'h' ? error : char),
100
+ severity,
101
+ startIndex,
102
+ endIndex,
103
+ startLine,
104
+ endLine: startLine,
105
+ startCol,
106
+ endCol: startCol + length,
107
+ });
108
+ }
109
+ }
110
+ return errors;
102
111
  }
103
112
  return [];
104
113
  }
package/dist/lib/title.js CHANGED
@@ -27,7 +27,7 @@ class Title {
27
27
  }
28
28
  catch { }
29
29
  }
30
- title = title.replaceAll('_', ' ').trim();
30
+ title = title.replace(/_/gu, ' ').trim();
31
31
  let ns = defaultNs;
32
32
  if (title.startsWith(':')) {
33
33
  ns = 0;
@@ -10,7 +10,6 @@ const hidden = (constructor) => {
10
10
  class AnyHiddenToken extends constructor {
11
11
  static hidden = true;
12
12
  /** 没有可见部分 */
13
- // eslint-disable-next-line class-methods-use-this
14
13
  text() {
15
14
  return '';
16
15
  }
@@ -17,7 +17,7 @@ const parseBraces = (wikitext, config = Parser.getConfig(), accum = []) => {
17
17
  const source = `${config.excludes?.includes('heading') ? '' : '^(\0\\d+c\x7F)*={1,6}|'}\\[\\[|\\{{2,}|-\\{(?!\\{)`, { parserFunction: [, , , subst] } = config, stack = [], closes = { '=': '\n', '{': '\\}{2,}|\\|', '-': '\\}-', '[': '\\]\\]' }, marks = new Map([['!', '!'], ['!!', '+'], ['(!', '{'], ['!)', '}'], ['!-', '-'], ['=', '~']]);
18
18
  let regex = new RegExp(source, 'gmu'), mt = regex.exec(wikitext), moreBraces = wikitext.includes('}}'), lastIndex;
19
19
  while (mt
20
- || lastIndex !== undefined && lastIndex <= wikitext.length && stack.at(-1)?.[0]?.startsWith('=')) {
20
+ || lastIndex !== undefined && lastIndex <= wikitext.length && stack[stack.length - 1]?.[0]?.startsWith('=')) {
21
21
  if (mt?.[1]) {
22
22
  const [, { length }] = mt;
23
23
  mt[0] = mt[0].slice(length);
@@ -29,14 +29,14 @@ const parseBraces = (wikitext, config = Parser.getConfig(), accum = []) => {
29
29
  * @param text wikitext全文
30
30
  */
31
31
  const push = (text) => {
32
- parts.at(-1).push(text.slice(topPos, curIndex));
32
+ parts[parts.length - 1].push(text.slice(topPos, curIndex));
33
33
  };
34
34
  if (syntax === ']]' || syntax === '}-') { // 情形1:闭合内链或转换
35
35
  lastIndex = curIndex + 2;
36
36
  }
37
37
  else if (syntax === '\n') { // 情形2:闭合标题或文末
38
38
  lastIndex = curIndex + 1;
39
- const { pos, findEqual } = stack.at(-1) ?? {};
39
+ const { pos, findEqual } = stack[stack.length - 1] ?? {};
40
40
  if (pos === undefined || findEqual || (0, string_1.removeComment)(wikitext.slice(pos, index)) !== '') {
41
41
  const rmt = /^(={1,6})(.+)\1((?:\s|\0\d+c\x7F)*)$/u
42
42
  .exec(wikitext.slice(index, curIndex));
@@ -112,10 +112,10 @@ const parseBraces = (wikitext, config = Parser.getConfig(), accum = []) => {
112
112
  stack.push(...'0' in top ? [top] : [], mt);
113
113
  }
114
114
  moreBraces &&= wikitext.slice(lastIndex).includes('}}');
115
- let curTop = stack.at(-1);
115
+ let curTop = stack[stack.length - 1];
116
116
  if (!moreBraces && curTop?.[0]?.startsWith('{')) {
117
117
  stack.pop();
118
- curTop = stack.at(-1);
118
+ curTop = stack[stack.length - 1];
119
119
  }
120
120
  regex = new RegExp(source + (curTop ? `|${closes[curTop[0][0]]}${curTop.findEqual ? '|=' : ''}` : ''), 'gmu');
121
121
  regex.lastIndex = lastIndex;
@@ -49,9 +49,17 @@ const parseCommentAndExt = (wikitext, config = Parser.getConfig(), accum = [], i
49
49
  return str;
50
50
  }
51
51
  }
52
- const ext = config.ext.join('|'), noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly', regex = new RegExp('<!--.*?(?:-->|$)|' // comment
53
- + `<${noincludeRegex}(?:\\s[^>]*?)?>|</${noincludeRegex}\\s*>|` // <noinclude>
54
- + `<(${ext})(\\s[^>]*?)?(?:/>|>(.*?)</(\\1\\s*)>)|` // 扩展标签
52
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
53
+ /<foo(?:\s[^>]*)?>|<\/foo\s*>/giu;
54
+ /<(bar)(\s[^>]*?)?(?:\/>|>(.*?)<\/(\1\s*)>)/gisu;
55
+ /<(baz)(\s[^>]*?)?(?:\/>|>(.*?)(?:<\/(baz\s*)>|$))/gisu;
56
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
57
+ const ext = config.ext.join('|'), noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly', regex = new RegExp('<!--.*?(?:-->|$)' // comment
58
+ + '|'
59
+ + `<${noincludeRegex}(?:\\s[^>]*)?>|</${noincludeRegex}\\s*>` // <noinclude>
60
+ + '|'
61
+ + `<(${ext})(\\s[^>]*?)?(?:/>|>(.*?)</(\\1\\s*)>)` // 扩展标签
62
+ + '|'
55
63
  + `<(${includeRegex})(\\s[^>]*?)?(?:/>|>(.*?)(?:</(${includeRegex}\\s*)>|$))`, // <includeonly>
56
64
  'gisu');
57
65
  return wikitext.replace(regex, (substr, name, attr, inner, closing, include, includeAttr, includeInner, includeClosing) => {
@@ -15,10 +15,10 @@ const parseConverter = (text, config = Parser.getConfig(), accum = []) => {
15
15
  while (mt) {
16
16
  const { 0: syntax, index } = mt;
17
17
  if (syntax === '}-') {
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)],
19
- // eslint-disable-next-line regexp/prefer-lookaround
20
- temp = raw.replace(/(&[#a-z\d]+);/giu, '$1\x01'), variants = `(?:${config.variants.join('|')})`, rules = temp.split(new RegExp(`;(?=\\s*(?:${variants}|[^;]*?=>\\s*${variants})\\s*:)`, 'u'))
21
- .map(rule => rule.replaceAll('\x01', ';'));
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
19
+ /;(?=(?:[^;]*?=>)?\s*foo\s*:|(?:\s|\0\d+c\x7F)*$)/u;
20
+ 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'), variants = `(?:${config.variants.join('|')})`, rules = temp.split(new RegExp(`;(?=(?:[^;]*?=>)?\\s*${variants}\\s*:|(?:\\s|\0\\d+c\x7F)*$)`, 'u'))
21
+ .map(rule => rule.replace(/\x01/gu, ';'));
22
22
  new converter_1.ConverterToken(flags, rules, config, accum);
23
23
  text = `${text.slice(0, top.index)}\0${length}v\x7F${text.slice(index + 2)}`;
24
24
  if (stack.length === 0) {
@@ -11,7 +11,10 @@ const extLink_1 = require("../src/extLink");
11
11
  * @param accum
12
12
  */
13
13
  const parseExternalLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
14
- const regex = new RegExp(`\\[((?:(?:${config.protocol}|//)${string_1.extUrlCharFirst}|\0\\d+m\x7F)${string_1.extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0A-\x1F\uFFFD]*)\\]`, 'giu');
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
15
+ /\[((?:\[[\da-f:.]+\]|[^[\]\t\n\p{Zs}])[^[\]\t\n\p{Zs}]*(?=[[\]\t\p{Zs}]|\0\d))(\p{Zs}*(?=\P{Zs}))([^\]\n]*)\]/giu;
16
+ const regex = new RegExp(`\\[((?:(?:${config.protocol}|//)${string_1.extUrlCharFirst}|\0\\d+m\x7F)${string_1.extUrlChar}(?=[[\\]<>"\\t\\p{Zs}]|\0\\d))`
17
+ + '(\\p{Zs}*(?=\\P{Zs}))([^\\]\x01-\x08\x0A-\x1F\uFFFD]*)\\]', 'giu');
15
18
  return wikitext.replace(regex, (_, url, space, text) => {
16
19
  const { length } = accum, mt = /&[lg]t;/u.exec(url);
17
20
  if (mt) {
@@ -11,9 +11,11 @@ const magicLink_1 = require("../src/magicLink");
11
11
  * @param accum
12
12
  */
13
13
  const parseMagicLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
14
- const regex = new RegExp(`(?<![\\p{L}\\d_])(?:${config.protocol})(${string_1.extUrlCharFirst}${string_1.extUrlChar})`, 'giu');
15
- return wikitext.replace(regex, (m, p1) => {
16
- let trail = '', url = m;
14
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
15
+ /(^|[^\p{L}\d_])((?:\[[\da-f:.]+\]|[^[\]<>"\t\n\p{Zs}])(?:[^[\]<>"\0\t\n\p{Zs}]|\0\d+c\x7F)*)/giu;
16
+ const regex = new RegExp(`(^|[^\\p{L}\\d_])(?:${config.protocol})(${string_1.extUrlCharFirst}${string_1.extUrlChar})`, 'giu');
17
+ return wikitext.replace(regex, (m, lead, p1) => {
18
+ let trail = '', url = lead ? m.slice(1) : m;
17
19
  const m2 = /&(?:lt|gt|nbsp|#x0*(?:3[ce]|a0)|#0*(?:6[02]|160));/iu.exec(url);
18
20
  if (m2) {
19
21
  trail = url.slice(m2.index);
@@ -32,7 +34,7 @@ const parseMagicLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
32
34
  return m;
33
35
  }
34
36
  new magicLink_1.MagicLinkToken(url, false, config, accum);
35
- return `\0${accum.length - 1}w\x7F${trail}`;
37
+ return `${lead}\0${accum.length - 1}w\x7F${trail}`;
36
38
  });
37
39
  };
38
40
  exports.parseMagicLinks = parseMagicLinks;
@@ -35,7 +35,7 @@ const parseQuotes = (wikitext, config = Parser.getConfig(), accum = []) => {
35
35
  firstSpace = i;
36
36
  }
37
37
  }
38
- else if (arr[i - 1].at(-2) === ' ') {
38
+ else if (arr[i - 1].slice(-2, -1) === ' ') {
39
39
  firstSingle = i;
40
40
  }
41
41
  else {
@@ -80,7 +80,7 @@ const parseTable = ({ firstChild: { data }, type, name }, config = Parser.getCon
80
80
  top = stack.pop();
81
81
  }
82
82
  top.close(`\n${spaces}${closing}`, true);
83
- push(attr, stack.at(-1));
83
+ push(attr, stack[stack.length - 1]);
84
84
  }
85
85
  else if (row) {
86
86
  top = pop();
@@ -112,12 +112,19 @@ const commonHtmlAttrs = new Set([
112
112
  ]),
113
113
  tabs: new Set(['plain', 'class', 'container', 'id', 'title', 'style']),
114
114
  combobox: new Set(['placeholder', 'value', 'id', 'class', 'text', 'dropdown', 'style']),
115
- }, insecureStyle = new RegExp(`${'expression'}|${'(?:filter|accelerator|-o-link(?:-source)?|-o-replace)\\s*:'}|${'(?:url|image(?:-set)?)\\s*\\('}|${'attr\\s*\\([^)]+[\\s,]url'}`, 'u');
115
+ }, insecureStyle = new RegExp('expression'
116
+ + '|'
117
+ + '(?:filter|accelerator|-o-link(?:-source)?|-o-replace)\\s*:'
118
+ + '|'
119
+ + '(?:url|image(?:-set)?)\\s*\\('
120
+ + '|'
121
+ + 'attr\\s*\\([^)]+[\\s,]url', 'u');
116
122
  /**
117
123
  * 扩展和HTML标签属性
118
124
  * @classdesc `{childNodes: [AtomToken, Token|AtomToken]}`
119
125
  */
120
126
  class AttributeToken extends index_1.Token {
127
+ tag;
121
128
  #equal;
122
129
  #quotes;
123
130
  /** 引号是否匹配 */
@@ -172,7 +179,7 @@ class AttributeToken extends index_1.Token {
172
179
  /** @private */
173
180
  afterBuild() {
174
181
  if (this.#equal.includes('\0')) {
175
- this.#equal = this.buildFromStr(this.#equal, 'string');
182
+ this.#equal = this.buildFromStr(this.#equal, constants_1.BuildMethod.String);
176
183
  }
177
184
  if (this.parentNode) {
178
185
  this.setAttribute('tag', this.parentNode.name);
@@ -233,7 +240,7 @@ class AttributeToken extends index_1.Token {
233
240
  if (this.#quotes[1]) {
234
241
  return value;
235
242
  }
236
- return this.#quotes[0] ? value.trimEnd() : value.trim();
243
+ return value[this.#quotes[0] ? 'trimEnd' : 'trim']();
237
244
  }
238
245
  return this.type === 'ext-attr' || '';
239
246
  }
@@ -32,13 +32,12 @@ class AttributesToken extends index_1.Token {
32
32
  this.type = type;
33
33
  this.setAttribute('name', name);
34
34
  if (attr) {
35
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
36
+ /([^\s/](?:(?!\0\d+~\x7F)[^\s/=])*)(?:(\s*(?:=|\0\d+~\x7F)\s*)(?:(["'])(.*?)(\3|$)|(\S*)))?/gsu;
35
37
  const regex = new RegExp('([^\\s/](?:(?!\0\\d+~\x7F)[^\\s/=])*)' // 属性名
36
- + '(?:'
37
- + '((?:\\s|\0\\d+c\x7F)*' // `=`前的空白字符
38
- + '(?:=|\0\\d+~\x7F)' // `=`
39
- + '(?:\\s|\0\\d+c\x7F)*)' // `=`后的空白字符
40
- + `(?:(["'])(.*?)(\\3|$)|(\\S*))` // 属性值
41
- + ')?', 'gsu');
38
+ + `(?:${'((?:\\s|\0\\d+c\x7F)*(?:=|\0\\d+~\x7F)(?:\\s|\0\\d+c\x7F)*)' // `=`和前后的空白字符
39
+ + `(?:(["'])(.*?)(\\3|$)|(\\S*))` // 属性值
40
+ })?`, 'gsu');
42
41
  let out = '', mt = regex.exec(attr), lastIndex = 0;
43
42
  const insertDirty = /** 插入无效属性 */ () => {
44
43
  if (out) {
@@ -84,7 +83,7 @@ class AttributesToken extends index_1.Token {
84
83
  */
85
84
  getAttrToken(key) {
86
85
  const tokens = this.getAttrTokens(key);
87
- return tokens.at(-1);
86
+ return tokens[tokens.length - 1];
88
87
  }
89
88
  /**
90
89
  * 获取指定属性
@@ -30,7 +30,7 @@ export declare class HtmlToken extends Token {
30
30
  * 搜索匹配的标签
31
31
  * @throws `SyntaxError` 同时闭合和自封闭的标签
32
32
  * @throws `SyntaxError` 无效自封闭标签
33
- * @throws `SyntaxError` 未闭合的标签
33
+ * @throws `SyntaxError` 未匹配的标签
34
34
  */
35
35
  findMatchingTag(): this | undefined;
36
36
  }
package/dist/src/html.js CHANGED
@@ -92,7 +92,7 @@ class HtmlToken extends index_1.Token {
92
92
  * 搜索匹配的标签
93
93
  * @throws `SyntaxError` 同时闭合和自封闭的标签
94
94
  * @throws `SyntaxError` 无效自封闭标签
95
- * @throws `SyntaxError` 未闭合的标签
95
+ * @throws `SyntaxError` 未匹配的标签
96
96
  */
97
97
  findMatchingTag() {
98
98
  const { html } = this.getAttribute('config'), { name: tagName, parentNode, closing } = this, string = (0, string_1.noWrap)(String(this));
@@ -16,6 +16,8 @@ function validate(key, val, config = Parser.getConfig(), halfParsed = false) {
16
16
  if (!value) {
17
17
  return val;
18
18
  }
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
20
+ /^(?:\/\/(?:\[[\da-f:.]+\]|[^[\]<>"\t\n\p{Zs}])|\0\d+m\x7F)(?:[^[\]<>"\0\t\n\p{Zs}]|\0\d+c\x7F)*$/iu;
19
21
  const regex = new RegExp(`^(?:(?:${config.protocol}|//)${string_1.extUrlCharFirst}|\0\\d+m\x7F)${string_1.extUrlChar}$`, 'iu');
20
22
  if (regex.test(value)) {
21
23
  return val;
@@ -59,7 +59,7 @@ class ImagemapToken extends index_1.Token {
59
59
  }
60
60
  else if (protocols.has(substr.slice(1, substr.indexOf(':') + 1))
61
61
  || protocols.has(substr.slice(1, substr.indexOf('//') + 2))) {
62
- const mtEx = /^\[([^\]\s]+)(?:(\s+)(\S[^\]]*)?)?\][\w\s]*$/u
62
+ const mtEx = /^\[([^\]\s]+)(?:(\s+(?=\S))([^\]]*))?\][\w\s]*$/u
63
63
  .exec(substr);
64
64
  if (mtEx) {
65
65
  super.insertAt(new imagemapLink_1.ImagemapLinkToken(line.slice(0, i), mtEx.slice(1), substr.slice(substr.indexOf(']') + 1), config, accum));
@@ -1,3 +1,4 @@
1
+ import { BuildMethod } from '../util/constants';
1
2
  import * as Parser from '../index';
2
3
  import { AstElement } from '../lib/element';
3
4
  import { AstText } from '../lib/text';
package/dist/src/index.js CHANGED
@@ -127,15 +127,15 @@ class Token extends element_1.AstElement {
127
127
  if (i % 2 === 0) {
128
128
  return new text_1.AstText(s);
129
129
  }
130
- else if (Number.isNaN(Number(s.at(-1)))) {
130
+ else if (Number.isNaN(Number(s.slice(-1)))) {
131
131
  return this.#accum[Number(s.slice(0, -1))];
132
132
  }
133
133
  throw new Error(`解析错误!未正确标记的 Token:${s}`);
134
134
  });
135
- if (type === 'string') {
135
+ if (type === constants_1.BuildMethod.String) {
136
136
  return nodes.map(String).join('');
137
137
  }
138
- else if (type === 'text') {
138
+ else if (type === constants_1.BuildMethod.Text) {
139
139
  return (0, string_1.text)(nodes);
140
140
  }
141
141
  return nodes;
@@ -279,10 +279,7 @@ class Token extends element_1.AstElement {
279
279
  return this.#include;
280
280
  }
281
281
  const root = this.getRootNode();
282
- if (root !== this) {
283
- return root.getAttribute('include');
284
- }
285
- return false;
282
+ return (root !== this && root.getAttribute('include'));
286
283
  }
287
284
  default:
288
285
  return super.getAttribute(key);
@@ -34,7 +34,7 @@ class LinkBaseToken extends index_1.Token {
34
34
  afterBuild() {
35
35
  this.#title = this.getTitle();
36
36
  if (this.#delimiter.includes('\0')) {
37
- this.#delimiter = this.buildFromStr(this.#delimiter, 'string');
37
+ this.#delimiter = this.buildFromStr(this.#delimiter, constants_1.BuildMethod.String);
38
38
  }
39
39
  }
40
40
  /** @private */
@@ -28,9 +28,10 @@ class MagicLinkToken extends index_1.Token {
28
28
  }
29
29
  rect ??= { start, ...this.getRootNode().posFromIndex(start) };
30
30
  const refError = (0, lint_1.generateForChild)(child, rect, '', 'warning');
31
- errors.push(...[...data.matchAll(regexGlobal)].map(({ index, 0: s }) => {
32
- const lines = data.slice(0, index).split('\n'), { length: top } = lines, { length: left } = lines.at(-1), startIndex = refError.startIndex + index, startLine = refError.startLine + top - 1, startCol = top === 1 ? refError.startCol + left : left;
33
- return {
31
+ regexGlobal.lastIndex = 0;
32
+ for (let mt = regexGlobal.exec(data); mt; mt = regexGlobal.exec(data)) {
33
+ const { index, 0: s } = mt, lines = data.slice(0, index).split('\n'), { length: top } = lines, { length: left } = lines[lines.length - 1], startIndex = refError.startIndex + index, startLine = refError.startLine + top - 1, startCol = top === 1 ? refError.startCol + left : left;
34
+ errors.push({
34
35
  ...refError,
35
36
  message: Parser.msg('$1 in URL', s.startsWith('|') ? '"|"' : Parser.msg('full-width punctuation')),
36
37
  startIndex,
@@ -39,8 +40,8 @@ class MagicLinkToken extends index_1.Token {
39
40
  endLine: startLine,
40
41
  startCol,
41
42
  endCol: startCol + s.length,
42
- };
43
- }));
43
+ });
44
+ }
44
45
  }
45
46
  return errors;
46
47
  }
@@ -28,9 +28,9 @@ class NestedToken extends index_1.Token {
28
28
  new comment_1.CommentToken(comment.slice(4, closed ? -3 : undefined), closed, config, accum);
29
29
  }
30
30
  return str;
31
- })?.replace(/(?<=^|\0\d+[ce]\x7F)[^\0]+(?=$|\0\d+[ce]\x7F)/gu, substr => {
31
+ })?.replace(/(^|\0\d+[ce]\x7F)([^\0]+)(?=$|\0\d+[ce]\x7F)/gu, (_, lead, substr) => {
32
32
  new noinclude_1.NoincludeToken(substr, config, accum);
33
- return `\0${accum.length}c\x7F`;
33
+ return `${lead}\0${accum.length}c\x7F`;
34
34
  });
35
35
  super(wikitext, config, accum, {});
36
36
  }
@@ -14,7 +14,7 @@ class QuoteToken extends base_1.NowikiBaseToken {
14
14
  let refError;
15
15
  if (previousSibling?.type === 'text' && previousSibling.data.endsWith(`'`)) {
16
16
  refError = (0, lint_1.generateForSelf)(this, { start }, message);
17
- const { startIndex: endIndex, startLine: endLine, startCol: endCol } = refError, [{ length }] = /(?<!')'+$/u.exec(previousSibling.data), startIndex = start - length;
17
+ const { startIndex: endIndex, startLine: endLine, startCol: endCol } = refError, [, { length }] = /(?:^|[^'])('+)$/u.exec(previousSibling.data), startIndex = start - length;
18
18
  errors.push({
19
19
  ...refError,
20
20
  startIndex,
@@ -10,7 +10,7 @@ const index_1 = require("./index");
10
10
  * @param name 预定的参数名
11
11
  */
12
12
  const getName = (name) => name.toString(new Set(['comment', 'noinclude', 'include']))
13
- .replace(/^[ \t\n\0\v]+|(?<=[^ \t\n\0\v])[ \t\n\0\v]+$/gu, '');
13
+ .replace(/^[ \t\n\0\v]+|([^ \t\n\0\v])[ \t\n\0\v]+$/gu, '$1');
14
14
  /**
15
15
  * 模板或魔术字参数
16
16
  * @classdesc `{childNodes: [Token, Token]}`
@@ -59,6 +59,8 @@ class ParameterToken extends index_1.Token {
59
59
  }
60
60
  /** @override */
61
61
  lint(start = this.getAbsoluteIndex()) {
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
63
+ /https?:\/\/(?:\[[\da-f:.]+\]|[^[\]<>"\t\n\p{Zs}])(?:[^[\]<>"\0\t\n\p{Zs}]|\0\d+c\x7F)*$/iu;
62
64
  const errors = super.lint(start), { firstChild } = this, link = new RegExp(`https?://${string_1.extUrlCharFirst}${string_1.extUrlChar}$`, 'iu')
63
65
  .exec(firstChild.toString(new Set(['comment', 'noinclude', 'include'])))?.[0];
64
66
  if (link && new URL(link).search) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TdToken = void 0;
4
4
  const lint_1 = require("../../util/lint");
5
+ const constants_1 = require("../../util/constants");
5
6
  const Parser = require("../../index");
6
7
  const index_1 = require("../index");
7
8
  const base_1 = require("./base");
@@ -38,7 +39,7 @@ class TdToken extends base_1.TableBaseToken {
38
39
  }
39
40
  /** 表格语法信息 */
40
41
  #getSyntax() {
41
- const syntax = this.firstChild.text(), char = syntax.at(-1);
42
+ const syntax = this.firstChild.text(), char = syntax.slice(-1);
42
43
  let subtype = 'td';
43
44
  if (char === '!') {
44
45
  subtype = 'th';
@@ -51,7 +52,7 @@ class TdToken extends base_1.TableBaseToken {
51
52
  /** @private */
52
53
  afterBuild() {
53
54
  if (this.#innerSyntax.includes('\0')) {
54
- this.#innerSyntax = this.buildFromStr(this.#innerSyntax, 'string');
55
+ this.#innerSyntax = this.buildFromStr(this.#innerSyntax, constants_1.BuildMethod.String);
55
56
  }
56
57
  }
57
58
  /** @private */
@@ -76,8 +77,11 @@ class TdToken extends base_1.TableBaseToken {
76
77
  const errors = super.lint(start);
77
78
  start += this.getRelativeIndex(this.length - 1);
78
79
  for (const child of this.lastChild.childNodes) {
79
- if (child.type === 'text' && child.data.includes('|')) {
80
- errors.push((0, lint_1.generateForChild)(child, { start }, 'additional "|" in a table cell', 'warning'));
80
+ if (child.type === 'text') {
81
+ const { data } = child;
82
+ if (data.includes('|')) {
83
+ errors.push((0, lint_1.generateForChild)(child, { start }, 'additional "|" in a table cell', data.includes('||') ? 'error' : 'warning'));
84
+ }
81
85
  }
82
86
  }
83
87
  return errors;
@@ -9,6 +9,7 @@ import type { LintError } from '../../base';
9
9
  */
10
10
  export declare class ExtToken extends TagPairToken {
11
11
  readonly type = "ext";
12
+ closed: true;
12
13
  readonly childNodes: [AttributesToken, Token];
13
14
  abstract get firstChild(): AttributesToken;
14
15
  abstract get lastChild(): Token;
@@ -29,9 +29,7 @@ class ExtToken extends index_2.TagPairToken {
29
29
  * @param closed 是否封闭
30
30
  */
31
31
  constructor(name, attr, inner, closed, config = Parser.getConfig(), accum = []) {
32
- const lcName = name.toLowerCase(), attrToken = new attributes_1.AttributesToken(!attr || attr.trimStart() !== attr
33
- ? attr
34
- : ` ${attr}`, 'ext-attrs', lcName, config, accum), newConfig = { ...config, ext: del(config.ext, lcName), excludes: [...config.excludes ?? []] };
32
+ const lcName = name.toLowerCase(), attrToken = new attributes_1.AttributesToken(!attr || attr.trimStart() !== attr ? attr : ` ${attr}`, 'ext-attrs', lcName, config, accum), newConfig = { ...config, ext: del(config.ext, lcName), excludes: [...config.excludes ?? []] };
35
33
  let innerToken;
36
34
  switch (lcName) {
37
35
  case 'tab':
@@ -6,11 +6,11 @@ export declare abstract class TagPairToken extends Token {
6
6
  #private;
7
7
  type: 'ext' | 'include';
8
8
  readonly name: string;
9
+ closed: boolean;
10
+ selfClosing: boolean;
9
11
  readonly childNodes: [AstNodes, AstNodes];
10
12
  abstract get firstChild(): AstNodes;
11
13
  abstract get lastChild(): AstNodes;
12
- /** 是否闭合 */
13
- get closed(): boolean;
14
14
  /**
15
15
  * @param name 标签名
16
16
  * @param attr 标签属性
@@ -5,13 +5,9 @@ const Parser = require("../../index");
5
5
  const index_1 = require("../index");
6
6
  /** 成对标签 */
7
7
  class TagPairToken extends index_1.Token {
8
- #selfClosing;
9
- #closed;
10
8
  #tags;
11
- /** 是否闭合 */
12
- get closed() {
13
- return this.#closed;
14
- }
9
+ closed;
10
+ selfClosing;
15
11
  /**
16
12
  * @param name 标签名
17
13
  * @param attr 标签属性
@@ -22,23 +18,23 @@ class TagPairToken extends index_1.Token {
22
18
  super(undefined, config);
23
19
  this.setAttribute('name', name.toLowerCase());
24
20
  this.#tags = [name, closed || name];
25
- this.#selfClosing = closed === undefined;
26
- this.#closed = closed !== '';
21
+ this.closed = closed !== '';
22
+ this.selfClosing = closed === undefined;
27
23
  this.append(attr, inner);
28
24
  const index = typeof attr === 'string' ? -1 : accum.indexOf(attr);
29
25
  accum.splice(index === -1 ? Infinity : index, 0, this);
30
26
  }
31
27
  /** @private */
32
28
  toString(omit) {
33
- const { firstChild, lastChild, } = this, [opening, closing] = this.#tags;
34
- return this.#selfClosing
29
+ const { selfClosing, firstChild, lastChild, } = this, [opening, closing] = this.#tags;
30
+ return selfClosing
35
31
  ? `<${opening}${firstChild.toString(omit)}/>`
36
32
  : `<${opening}${firstChild.toString(omit)}>${lastChild.toString(omit)}${this.closed ? `</${closing}>` : ''}`;
37
33
  }
38
34
  /** @override */
39
35
  text() {
40
36
  const [opening, closing] = this.#tags;
41
- return this.#selfClosing
37
+ return this.selfClosing
42
38
  ? `<${opening}${this.firstChild.text()}/>`
43
39
  : `<${opening}${super.text('>')}${this.closed ? `</${closing}>` : ''}`;
44
40
  }
@@ -11,7 +11,7 @@ import type { LintError } from '../base';
11
11
  export declare class TranscludeToken extends Token {
12
12
  #private;
13
13
  type: 'template' | 'magic-word';
14
- readonly modifier = "";
14
+ readonly modifier: string;
15
15
  readonly childNodes: [AtomToken | SyntaxToken, ...ParameterToken[]] | [SyntaxToken, AtomToken, AtomToken, ...ParameterToken[]];
16
16
  abstract get firstChild(): AtomToken | SyntaxToken;
17
17
  abstract get lastChild(): AtomToken | SyntaxToken | ParameterToken;
@@ -4,6 +4,7 @@ exports.TranscludeToken = void 0;
4
4
  const string_1 = require("../util/string");
5
5
  const lint_1 = require("../util/lint");
6
6
  const debug_1 = require("../util/debug");
7
+ const constants_1 = require("../util/constants");
7
8
  const Parser = require("../index");
8
9
  const index_1 = require("./index");
9
10
  const parameter_1 = require("./parameter");
@@ -117,12 +118,12 @@ class TranscludeToken extends index_1.Token {
117
118
  /** 获取模板或模块名 */
118
119
  #getTitle() {
119
120
  const isTemplate = this.type === 'template', child = this.childNodes[isTemplate ? 0 : 1];
120
- return child && this.normalizeTitle(child.text(), isTemplate ? 10 : 828);
121
+ return this.normalizeTitle(child.text(), isTemplate ? 10 : 828);
121
122
  }
122
123
  /** @private */
123
124
  afterBuild() {
124
125
  if (this.modifier.includes('\0')) {
125
- this.setAttribute('modifier', this.buildFromStr(this.modifier, 'string'));
126
+ this.setAttribute('modifier', this.buildFromStr(this.modifier, constants_1.BuildMethod.String));
126
127
  }
127
128
  }
128
129
  /** @private */
@@ -161,12 +162,7 @@ class TranscludeToken extends index_1.Token {
161
162
  return errors;
162
163
  }
163
164
  const title = this.#getTitle();
164
- if (!title) {
165
- rect = { start, ...this.getRootNode().posFromIndex(start) };
166
- errors.push((0, lint_1.generateForSelf)(this, rect, 'missing module name'));
167
- return errors;
168
- }
169
- else if (title.fragment !== undefined) {
165
+ if (title.fragment !== undefined) {
170
166
  rect = { start, ...this.getRootNode().posFromIndex(start) };
171
167
  errors.push((0, lint_1.generateForChild)(childNodes[type === 'template' ? 0 : 1], rect, 'useless fragment'));
172
168
  }
@@ -230,7 +226,7 @@ class TranscludeToken extends index_1.Token {
230
226
  * @param copy 是否返回一个备份
231
227
  */
232
228
  getArgs(key, exact = false, copy = true) {
233
- const keyStr = String(key).replace(/^[ \t\n\0\v]+|(?<=[^ \t\n\0\v])[ \t\n\0\v]+$/gu, '');
229
+ const keyStr = String(key).replace(/^[ \t\n\0\v]+|([^ \t\n\0\v])[ \t\n\0\v]+$/gu, '$1');
234
230
  let args;
235
231
  if (this.#args.has(keyStr)) {
236
232
  args = this.#args.get(keyStr);
@@ -1,4 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_STAGE = void 0;
3
+ exports.BuildMethod = exports.MAX_STAGE = void 0;
4
4
  exports.MAX_STAGE = 11;
5
+ var BuildMethod;
6
+ (function (BuildMethod) {
7
+ BuildMethod[BuildMethod["String"] = 0] = "String";
8
+ BuildMethod[BuildMethod["Text"] = 1] = "Text";
9
+ })(BuildMethod || (exports.BuildMethod = BuildMethod = {}));
@@ -22,7 +22,8 @@ exports.isToken = isToken;
22
22
  * @param inserted 插入的子节点
23
23
  */
24
24
  const setChildNodes = (parent, position, deleteCount, inserted = []) => {
25
- const { childNodes } = parent, removed = childNodes.splice(position, deleteCount, ...inserted);
25
+ const childNodes = [...parent.childNodes], removed = childNodes.splice(position, deleteCount, ...inserted);
26
+ parent.setAttribute('childNodes', childNodes);
26
27
  for (const node of inserted) {
27
28
  node.setAttribute('parentNode', parent);
28
29
  }
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noWrap = exports.decodeHtml = exports.text = exports.escapeRegExp = exports.removeComment = exports.extUrlChar = exports.extUrlCharFirst = void 0;
4
- exports.extUrlCharFirst = '(?:\\[[\\da-f:.]+\\]|[^[\\]<>"\\0-\\x1F\\x7F\\p{Zs}\\uFFFD])';
5
- exports.extUrlChar = '(?:[^[\\]<>"\\0-\\x1F\\x7F\\p{Zs}\\uFFFD]|\\0\\d+[c!~]\\x7F)*';
4
+ exports.extUrlCharFirst = '(?:\\[[\\da-f:.]+\\]|[^[\\]<>"\0-\x1F\x7F\\p{Zs}\uFFFD])';
5
+ exports.extUrlChar = '(?:[^[\\]<>"\0-\x1F\x7F\\p{Zs}\uFFFD]|\0\\d+[c!~]\x7F)*';
6
6
  /**
7
7
  * 生成正则替换函数
8
8
  * @param regex 正则表达式
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.3.5",
3
+ "version": "2.3.8",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -19,7 +19,8 @@
19
19
  "/config/",
20
20
  "/i18n/",
21
21
  "/bin/cli",
22
- "/dist/"
22
+ "/dist/",
23
+ "!/dist/test/"
23
24
  ],
24
25
  "bin": {
25
26
  "wikilint": "./bin/cli.js"
@@ -30,15 +31,15 @@
30
31
  "url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
31
32
  },
32
33
  "scripts": {
33
- "prepublishOnly": "npm run build; rm dist/internal.js dist/base.js dist/[bmpu]*/*.d.ts; rm -r dist/test",
34
- "build": "rm -rf dist/; tsc; grep -rl --include='*.d.ts' '@private' dist/ | xargs gsed -i '/@private/,+1d'",
34
+ "prepublishOnly": "npm run build && rm dist/internal.js dist/base.js dist/[bmpu]*/*.d.ts",
35
+ "build": "rm -rf dist/; tsc && grep -rl --include='*.d.ts' '@private' dist/ | xargs gsed -i '/@private/,+1d'",
35
36
  "diff": "git diff --ignore-all-space --color-moved",
36
37
  "lint:ts": "tsc --noEmit && eslint --cache .",
37
38
  "lint:json": "ajv -s config/.schema.json -d 'config/*.json' --strict=true --strict-required=false",
38
39
  "lint": "npm run lint:ts && npm run lint:json",
39
40
  "test": "npx wikilint test/single-page.wiki",
40
41
  "test:real": "node dist/test/real.js",
41
- "test:single": "node dist/test/single.js; node --prof dist/test/single.js && node --prof-process isolate-0x*-v8.log > test/processed.txt && rm isolate-0x*-v8.log"
42
+ "test:single": "node dist/test/single.js && node --prof dist/test/single.js && node --prof-process isolate-0x*-v8.log > test/processed.txt && rm isolate-0x*-v8.log"
42
43
  },
43
44
  "devDependencies": {
44
45
  "@cypress/request": "^3.0.1",
@@ -49,6 +50,7 @@
49
50
  "@typescript-eslint/parser": "^6.12.0",
50
51
  "ajv-cli": "^5.0.0",
51
52
  "eslint": "^8.56.0",
53
+ "eslint-plugin-es-x": "^7.3.0",
52
54
  "eslint-plugin-eslint-comments": "^3.2.0",
53
55
  "eslint-plugin-jsdoc": "^47.0.2",
54
56
  "eslint-plugin-json-es": "^1.5.7",