wikiparser-node 0.4.0 → 0.5.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 (65) hide show
  1. package/index.js +25 -2
  2. package/lib/element.js +69 -185
  3. package/lib/node.js +159 -1
  4. package/lib/ranges.js +1 -2
  5. package/lib/text.js +35 -6
  6. package/lib/title.js +1 -1
  7. package/mixin/fixedToken.js +4 -4
  8. package/mixin/sol.js +17 -7
  9. package/package.json +11 -1
  10. package/parser/commentAndExt.js +1 -1
  11. package/parser/converter.js +1 -1
  12. package/parser/externalLinks.js +1 -1
  13. package/parser/hrAndDoubleUnderscore.js +6 -5
  14. package/parser/links.js +1 -2
  15. package/parser/magicLinks.js +1 -1
  16. package/parser/selector.js +5 -5
  17. package/parser/table.js +12 -12
  18. package/src/arg.js +44 -20
  19. package/src/attribute.js +34 -7
  20. package/src/converter.js +13 -5
  21. package/src/converterFlags.js +42 -5
  22. package/src/converterRule.js +25 -19
  23. package/src/extLink.js +20 -14
  24. package/src/gallery.js +35 -4
  25. package/src/heading.js +28 -9
  26. package/src/html.js +46 -18
  27. package/src/imageParameter.js +13 -7
  28. package/src/index.js +22 -15
  29. package/src/link/category.js +6 -6
  30. package/src/link/file.js +25 -5
  31. package/src/link/index.js +36 -33
  32. package/src/magicLink.js +32 -4
  33. package/src/nowiki/comment.js +14 -0
  34. package/src/nowiki/doubleUnderscore.js +5 -0
  35. package/src/nowiki/quote.js +28 -1
  36. package/src/onlyinclude.js +5 -0
  37. package/src/parameter.js +48 -35
  38. package/src/table/index.js +37 -24
  39. package/src/table/td.js +23 -17
  40. package/src/table/tr.js +47 -30
  41. package/src/tagPair/ext.js +4 -5
  42. package/src/tagPair/include.js +10 -0
  43. package/src/tagPair/index.js +8 -0
  44. package/src/transclude.js +79 -46
  45. package/tool/index.js +1 -1
  46. package/{test/util.js → util/diff.js} +14 -18
  47. package/util/lint.js +40 -0
  48. package/util/string.js +20 -3
  49. package/.eslintrc.json +0 -714
  50. package/errors/README +0 -1
  51. package/jsconfig.json +0 -7
  52. package/printed/README +0 -1
  53. package/printed/example.json +0 -120
  54. package/test/api.js +0 -83
  55. package/test/real.js +0 -133
  56. package/test/test.js +0 -28
  57. package/typings/api.d.ts +0 -13
  58. package/typings/array.d.ts +0 -28
  59. package/typings/event.d.ts +0 -24
  60. package/typings/index.d.ts +0 -94
  61. package/typings/node.d.ts +0 -29
  62. package/typings/parser.d.ts +0 -16
  63. package/typings/table.d.ts +0 -14
  64. package/typings/token.d.ts +0 -22
  65. package/typings/tool.d.ts +0 -11
package/src/table/td.js CHANGED
@@ -49,12 +49,12 @@ class TdToken extends fixedToken(TrToken) {
49
49
 
50
50
  /** 内部wikitext */
51
51
  get innerText() {
52
- return this.lastElementChild.text();
52
+ return this.lastChild.text();
53
53
  }
54
54
 
55
55
  /** 是否位于行首 */
56
56
  isIndependent() {
57
- return this.firstElementChild.text()[0] === '\n';
57
+ return this.firstChild.text()[0] === '\n';
58
58
  }
59
59
 
60
60
  /**
@@ -63,7 +63,7 @@ class TdToken extends fixedToken(TrToken) {
63
63
  * @complexity `n`
64
64
  */
65
65
  getSyntax() {
66
- const syntax = this.firstElementChild.text(),
66
+ const syntax = this.firstChild.text(),
67
67
  esc = syntax.includes('{{'),
68
68
  char = syntax.at(-1);
69
69
  let subtype = 'td';
@@ -75,13 +75,13 @@ class TdToken extends fixedToken(TrToken) {
75
75
  if (this.isIndependent()) {
76
76
  return {subtype, escape: esc, correction: false};
77
77
  }
78
- const {previousElementSibling} = this;
79
- if (previousElementSibling?.type !== 'td') {
78
+ const {previousSibling} = this;
79
+ if (previousSibling?.type !== 'td') {
80
80
  return {subtype, escape: esc, correction: true};
81
81
  }
82
- const result = previousElementSibling.getSyntax();
82
+ const result = previousSibling.getSyntax();
83
83
  result.escape ||= esc;
84
- result.correction = previousElementSibling.lastElementChild
84
+ result.correction = previousSibling.lastChild
85
85
  .toString('comment, ext, include, noinclude, arg, template, magic-word, html')
86
86
  .includes('\n');
87
87
  if (subtype === 'th' && result.subtype !== 'th') {
@@ -144,7 +144,7 @@ class TdToken extends fixedToken(TrToken) {
144
144
  }
145
145
  const token = Parser.run(() => new TdToken('\n|', undefined, config));
146
146
  token.setSyntax(subtype);
147
- token.lastElementChild.safeReplaceWith(inner);
147
+ token.lastChild.safeReplaceWith(inner);
148
148
  for (const [k, v] of Object.entries(attr)) {
149
149
  token.setAttr(k, v);
150
150
  }
@@ -201,7 +201,7 @@ class TdToken extends fixedToken(TrToken) {
201
201
  * @complexity `n`
202
202
  */
203
203
  #correct() {
204
- if (String(this.children[1])) {
204
+ if (String(this.childNodes[1])) {
205
205
  this.#innerSyntax ||= '|';
206
206
  }
207
207
  const {subtype, escape, correction} = this.getSyntax();
@@ -229,7 +229,7 @@ class TdToken extends fixedToken(TrToken) {
229
229
  */
230
230
  toString(selector) {
231
231
  this.#correct();
232
- const {children: [syntax, attr, inner]} = this;
232
+ const {childNodes: [syntax, attr, inner]} = this;
233
233
  return selector && this.matches(selector)
234
234
  ? ''
235
235
  : `${syntax.toString(selector)}${attr.toString(selector)}${this.#innerSyntax}${inner.toString(selector)}`;
@@ -241,11 +241,17 @@ class TdToken extends fixedToken(TrToken) {
241
241
  */
242
242
  getGaps(i = 0) {
243
243
  i = i < 0 ? i + this.childNodes.length : i;
244
- if (i !== 1) {
245
- return 0;
244
+ if (i === 1) {
245
+ this.#correct();
246
+ return this.#innerSyntax.length;
246
247
  }
247
- this.#correct();
248
- return this.#innerSyntax.length;
248
+ return 0;
249
+ }
250
+
251
+ /** @override */
252
+ print() {
253
+ const {childNodes: [syntax, attr, inner]} = this;
254
+ return `<span class="wpb-td">${syntax.print()}${attr.print()}${this.#innerSyntax}${inner.print()}</span>`;
249
255
  }
250
256
 
251
257
  /**
@@ -255,7 +261,7 @@ class TdToken extends fixedToken(TrToken) {
255
261
  */
256
262
  text() {
257
263
  this.#correct();
258
- const {children: [syntax, attr, inner]} = this;
264
+ const {childNodes: [syntax, attr, inner]} = this;
259
265
  return `${syntax.text()}${attr.text()}${this.#innerSyntax}${inner.text()}`;
260
266
  }
261
267
 
@@ -286,7 +292,7 @@ class TdToken extends fixedToken(TrToken) {
286
292
  value = value === 1 ? false : String(value);
287
293
  }
288
294
  const /** @type {boolean} */ result = super.setAttr(key, value);
289
- if (!String(this.children[1])) {
295
+ if (!String(this.childNodes[1])) {
290
296
  this.#innerSyntax = '';
291
297
  }
292
298
  return result;
@@ -295,7 +301,7 @@ class TdToken extends fixedToken(TrToken) {
295
301
  /** @override */
296
302
  escape() {
297
303
  super.escape();
298
- if (String(this.children[1])) {
304
+ if (String(this.childNodes[1])) {
299
305
  this.#innerSyntax ||= '{{!}}';
300
306
  }
301
307
  if (this.#innerSyntax === '|') {
package/src/table/tr.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const attributeParent = require('../../mixin/attributeParent'),
3
+ const {generateForChild} = require('../../util/lint'),
4
+ attributeParent = require('../../mixin/attributeParent'),
4
5
  Parser = require('../..'),
5
6
  AstText = require('../../lib/text'),
6
7
  Token = require('..'),
@@ -14,11 +15,10 @@ const openingPattern = /^\n[^\S\n]*(?:\|-+|\{\{\s*!\s*\}\}-+|\{\{\s*!-\s*\}\}-*)
14
15
  * @param {SyntaxToken} syntax 表格语法节点
15
16
  */
16
17
  const escapeTable = syntax => {
17
- const wikitext = syntax.childNodes.map(
18
+ const templates = {'{|': '(!', '|}': '!)', '||': '!!', '|': '!'},
19
+ wikitext = syntax.childNodes.map(
18
20
  child => child.type === 'text'
19
- ? String(child).replaceAll('{|', '{{(!}}').replaceAll('|}', '{{!)}}')
20
- .replaceAll('||', '{{!!}}')
21
- .replaceAll('|', '{{!}}')
21
+ ? String(child).replaceAll(/\{\||\|\}|\|{2}|\|/gu, p => `{{${templates[p]}}}`)
22
22
  : String(child),
23
23
  ).join(''),
24
24
  token = Parser.parse(wikitext, syntax.getAttribute('include'), 2, syntax.getAttribute('config'));
@@ -49,6 +49,23 @@ class TrToken extends attributeParent(Token, 1) {
49
49
  this.getAttribute('protectChildren')(0, 1);
50
50
  }
51
51
 
52
+ /**
53
+ * @override
54
+ * @param {number} start 起始位置
55
+ */
56
+ lint(start = 0) {
57
+ const errors = super.lint(start),
58
+ inter = this.childNodes.find(({type}) => type === 'table-inter'),
59
+ str = String(inter).trim();
60
+ if (inter && str && !/^<!--.*-->$/u.test(str)) {
61
+ const error = generateForChild(inter, this.getRootNode().posFromIndex(start), '将被移出表格的内容');
62
+ error.startLine++;
63
+ error.startCol = 0;
64
+ errors.push(error);
65
+ }
66
+ return errors;
67
+ }
68
+
52
69
  /**
53
70
  * @override
54
71
  * @this {TrToken & {constructor: typeof TrToken}}
@@ -57,10 +74,10 @@ class TrToken extends attributeParent(Token, 1) {
57
74
  const [syntax, attr, inner, ...cloned] = this.cloneChildNodes();
58
75
  return Parser.run(() => {
59
76
  const token = new this.constructor(undefined, undefined, this.getAttribute('config'));
60
- token.firstElementChild.safeReplaceWith(syntax);
61
- token.children[1].safeReplaceWith(attr);
77
+ token.firstChild.safeReplaceWith(syntax);
78
+ token.childNodes[1].safeReplaceWith(attr);
62
79
  if (token.type === 'td') { // TdToken
63
- token.children[2].safeReplaceWith(inner);
80
+ token.childNodes[2].safeReplaceWith(inner);
64
81
  } else if (inner !== undefined) {
65
82
  token.appendChild(inner);
66
83
  }
@@ -71,13 +88,13 @@ class TrToken extends attributeParent(Token, 1) {
71
88
 
72
89
  /** 修复简单的表格语法错误 */
73
90
  #correct() {
74
- const {children: [,, child]} = this;
91
+ const {childNodes: [,, child]} = this;
75
92
  if (child?.isPlain()) {
76
- const /** @type {{firstChild: AstText}} */ {firstChild: {type, data}} = child;
77
- if (type !== 'text') {
93
+ const /** @type {{firstChild: AstText}} */ {firstChild} = child;
94
+ if (firstChild.type !== 'text') {
78
95
  child.prepend('\n');
79
- } else if (data[0] !== '\n') {
80
- child.setText(`\n${data}`);
96
+ } else if (firstChild.data[0] !== '\n') {
97
+ firstChild.insertData(0, '\n');
81
98
  }
82
99
  }
83
100
  }
@@ -103,7 +120,7 @@ class TrToken extends attributeParent(Token, 1) {
103
120
  * @complexity `n`
104
121
  */
105
122
  escape() {
106
- for (const child of this.children) {
123
+ for (const child of this.childNodes) {
107
124
  if (child instanceof SyntaxToken) {
108
125
  escapeTable(child);
109
126
  } else if (child instanceof TrToken) {
@@ -118,10 +135,10 @@ class TrToken extends attributeParent(Token, 1) {
118
135
  * @param {boolean} esc 是否需要转义
119
136
  */
120
137
  setSyntax(syntax, esc) {
121
- const {firstElementChild} = this;
122
- firstElementChild.replaceChildren(syntax);
138
+ const {firstChild} = this;
139
+ firstChild.replaceChildren(syntax);
123
140
  if (esc) {
124
- escapeTable(firstElementChild);
141
+ escapeTable(firstChild);
125
142
  }
126
143
  }
127
144
 
@@ -134,9 +151,9 @@ class TrToken extends attributeParent(Token, 1) {
134
151
  const TdToken = require('./td');
135
152
  const child = this.childNodes.at(i);
136
153
  if (child instanceof TdToken && child.isIndependent()) {
137
- const {nextElementSibling} = child;
138
- if (nextElementSibling?.type === 'td') {
139
- nextElementSibling.independence();
154
+ const {nextSibling} = child;
155
+ if (nextSibling?.type === 'td') {
156
+ nextSibling.independence();
140
157
  }
141
158
  }
142
159
  return super.removeAt(i);
@@ -169,14 +186,14 @@ class TrToken extends attributeParent(Token, 1) {
169
186
  */
170
187
  getRowCount() {
171
188
  const TdToken = require('./td');
172
- return Number(this.children.some(
173
- child => child instanceof TdToken && child.isIndependent() && child.firstElementChild.text().at(-1) !== '+',
189
+ return Number(this.childNodes.some(
190
+ child => child instanceof TdToken && child.isIndependent() && child.firstChild.text().at(-1) !== '+',
174
191
  ));
175
192
  }
176
193
 
177
194
  /**
178
195
  * 获取相邻行
179
- * @param {(children: Token[], index: number) => Token[]} subset 筛选兄弟节点的方法
196
+ * @param {(childNodes: Token[], index: number) => Token[]} subset 筛选兄弟节点的方法
180
197
  * @complexity `n`
181
198
  */
182
199
  #getSiblingRow(subset) {
@@ -184,9 +201,9 @@ class TrToken extends attributeParent(Token, 1) {
184
201
  if (!parentNode) {
185
202
  return undefined;
186
203
  }
187
- const {children} = parentNode,
188
- index = children.indexOf(this);
189
- for (const child of subset(children, index)) {
204
+ const {childNodes} = parentNode,
205
+ index = childNodes.indexOf(this);
206
+ for (const child of subset(childNodes, index)) {
190
207
  if (child instanceof TrToken && child.getRowCount()) {
191
208
  return child;
192
209
  }
@@ -199,7 +216,7 @@ class TrToken extends attributeParent(Token, 1) {
199
216
  * @complexity `n`
200
217
  */
201
218
  getNextRow() {
202
- return this.#getSiblingRow((children, index) => children.slice(index + 1));
219
+ return this.#getSiblingRow((childNodes, index) => childNodes.slice(index + 1));
203
220
  }
204
221
 
205
222
  /**
@@ -207,7 +224,7 @@ class TrToken extends attributeParent(Token, 1) {
207
224
  * @complexity `n`
208
225
  */
209
226
  getPreviousRow() {
210
- return this.#getSiblingRow((children, index) => children.slice(0, index).reverse());
227
+ return this.#getSiblingRow((childNodes, index) => childNodes.slice(0, index).reverse());
211
228
  }
212
229
 
213
230
  /**
@@ -218,7 +235,7 @@ class TrToken extends attributeParent(Token, 1) {
218
235
  const TdToken = require('./td');
219
236
  let count = 0,
220
237
  last = 0;
221
- for (const child of this.children) {
238
+ for (const child of this.childNodes) {
222
239
  if (child instanceof TdToken) {
223
240
  last = child.isIndependent() ? Number(child.subtype !== 'caption') : last;
224
241
  count += last;
@@ -246,7 +263,7 @@ class TrToken extends attributeParent(Token, 1) {
246
263
  }
247
264
  const TdToken = require('./td');
248
265
  let last = 0;
249
- for (const child of this.children.slice(2)) {
266
+ for (const child of this.childNodes.slice(2)) {
250
267
  if (child instanceof TdToken) {
251
268
  if (child.isIndependent()) {
252
269
  last = Number(child.subtype !== 'caption');
@@ -81,8 +81,7 @@ class ExtToken extends attributeParent(TagPairToken) {
81
81
  innerToken = new NowikiToken(inner, config);
82
82
  }
83
83
  }
84
- innerToken.type = 'ext-inner';
85
- innerToken.setAttribute('name', lcName);
84
+ innerToken.setAttribute('name', lcName).type = 'ext-inner';
86
85
  if (lcName === 'pre') {
87
86
  innerToken.setAttribute('stage', Parser.MAX_STAGE - 1);
88
87
  }
@@ -91,12 +90,12 @@ class ExtToken extends attributeParent(TagPairToken) {
91
90
 
92
91
  /** @override */
93
92
  cloneNode() {
94
- const inner = this.lastElementChild.cloneNode(),
93
+ const inner = this.lastChild.cloneNode(),
95
94
  tags = this.getAttribute('tags'),
96
95
  config = this.getAttribute('config'),
97
- attr = String(this.firstElementChild),
96
+ attr = String(this.firstChild),
98
97
  token = Parser.run(() => new ExtToken(tags[0], attr, '', this.selfClosing ? undefined : tags[1], config));
99
- token.lastElementChild.safeReplaceWith(inner);
98
+ token.lastChild.safeReplaceWith(inner);
100
99
  return token;
101
100
  }
102
101
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const hidden = require('../../mixin/hidden'),
4
+ {generateForSelf} = require('../../util/lint'),
4
5
  Parser = require('../..'),
5
6
  TagPairToken = require('.');
6
7
 
@@ -22,6 +23,15 @@ class IncludeToken extends hidden(TagPairToken) {
22
23
  super(name, attr, inner ?? '', inner === undefined ? closed : closed ?? '', config, accum, {AstText: [0, 1]});
23
24
  }
24
25
 
26
+ /**
27
+ * @override
28
+ * @param {number} start 起始位置
29
+ * @returns {LintError[]}
30
+ */
31
+ lint(start = 0) {
32
+ return this.closed ? [] : [generateForSelf(this, this.getRootNode().posFromIndex(start), '未闭合的标签')];
33
+ }
34
+
25
35
  /** @override */
26
36
  cloneNode() {
27
37
  const tags = this.getAttribute('tags'),
@@ -101,6 +101,14 @@ class TagPairToken extends fixedToken(Token) {
101
101
  return 1;
102
102
  }
103
103
 
104
+ /** @override */
105
+ print() {
106
+ const [opening, closing] = this.#tags;
107
+ return super.print(this.#selfClosing
108
+ ? {pre: `&lt;${opening}`, post: '/&gt;'}
109
+ : {pre: `&lt;${opening}`, sep: '&gt;', post: this.#closed ? `&lt;/${closing}&gt;` : ''});
110
+ }
111
+
104
112
  /**
105
113
  * @override
106
114
  * @returns {string}
package/src/transclude.js CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const {removeComment, escapeRegExp, text, noWrap} = require('../util/string'),
3
+ const {removeComment, escapeRegExp, text, noWrap, print} = require('../util/string'),
4
4
  {externalUse} = require('../util/debug'),
5
+ {generateForChild} = require('../util/lint'),
5
6
  Parser = require('..'),
6
7
  Token = require('.'),
7
8
  ParameterToken = require('./parameter');
@@ -64,7 +65,7 @@ class TranscludeToken extends Token {
64
65
  isSensitive = sensitive.includes(name);
65
66
  if (isSensitive || insensitive.includes(name.toLowerCase())) {
66
67
  this.setAttribute('name', name.toLowerCase().replace(/^#/u, '')).type = 'magic-word';
67
- const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? '' : 'i'),
68
+ const pattern = new RegExp(`^\\s*${name}\\s*$`, isSensitive ? 'u' : 'iu'),
68
69
  token = new SyntaxToken(magicWord, pattern, 'magic-word-name', config, accum, {
69
70
  'Stage-1': ':', '!ExtToken': '',
70
71
  });
@@ -98,7 +99,7 @@ class TranscludeToken extends Token {
98
99
  const token = new AtomToken(title, 'template-name', config, accum, {'Stage-2': ':', '!HeadingToken': ''});
99
100
  this.appendChild(token);
100
101
  }
101
- const templateLike = this.matches('template, magic-word#invoke');
102
+ const templateLike = this.isTemplate();
102
103
  let i = 1;
103
104
  for (const part of parts) {
104
105
  if (!templateLike) {
@@ -121,7 +122,7 @@ class TranscludeToken extends Token {
121
122
  return Parser.run(() => {
122
123
  const token = new TranscludeToken(this.type === 'template' ? '' : first.text(), [], config);
123
124
  token.setModifier(this.modifier);
124
- token.firstElementChild.safeReplaceWith(first);
125
+ token.firstChild.safeReplaceWith(first);
125
126
  token.afterBuild();
126
127
  token.append(...cloned);
127
128
  return token;
@@ -133,7 +134,7 @@ class TranscludeToken extends Token {
133
134
  if (this.name.includes('\0')) {
134
135
  this.setAttribute('name', text(this.getAttribute('buildFromStr')(this.name)));
135
136
  }
136
- if (this.matches('template, magic-word#invoke')) {
137
+ if (this.isTemplate()) {
137
138
  /**
138
139
  * 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
139
140
  * 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
@@ -146,7 +147,7 @@ class TranscludeToken extends Token {
146
147
  delete data.oldKey;
147
148
  delete data.newKey;
148
149
  }
149
- if (prevTarget === this.firstElementChild && this.type === 'template') {
150
+ if (prevTarget === this.firstChild && this.type === 'template') {
150
151
  this.setAttribute('name', this.normalizeTitle(prevTarget.text(), 10).title);
151
152
  } else if (oldKey !== newKey && prevTarget instanceof ParameterToken) {
152
153
  const oldArgs = this.getArgs(oldKey, false, false);
@@ -196,10 +197,10 @@ class TranscludeToken extends Token {
196
197
  if (selector && this.matches(selector)) {
197
198
  return '';
198
199
  }
199
- const {children, childNodes: {length}, firstChild, modifier} = this;
200
+ const {childNodes, firstChild, modifier} = this;
200
201
  return `{{${modifier}${modifier && ':'}${
201
202
  this.type === 'magic-word'
202
- ? `${String(firstChild)}${length > 1 ? ':' : ''}${children.slice(1).map(String).join('|')}`
203
+ ? `${String(firstChild)}${childNodes.length > 1 ? ':' : ''}${childNodes.slice(1).map(String).join('|')}`
203
204
  : super.toString(selector, '|')
204
205
  }}}`;
205
206
  }
@@ -214,16 +215,50 @@ class TranscludeToken extends Token {
214
215
  return 1;
215
216
  }
216
217
 
218
+ /** @override */
219
+ print() {
220
+ const {childNodes, firstChild, modifier} = this;
221
+ return `<span class="wpb-${this.type}">{{${modifier}${modifier && ':'}${
222
+ this.type === 'magic-word'
223
+ ? `${firstChild.print()}${childNodes.length > 1 ? ':' : ''}${print(childNodes.slice(1), {sep: '|'})}`
224
+ : print(childNodes, {sep: '|'})
225
+ }}}</span>`;
226
+ }
227
+
228
+ /**
229
+ * @override
230
+ * @param {number} start 起始位置
231
+ */
232
+ lint(start = 0) {
233
+ const errors = super.lint(start);
234
+ if (!this.isTemplate()) {
235
+ return errors;
236
+ }
237
+ const duplicatedArgs = this.getDuplicatedArgs();
238
+ if (duplicatedArgs.length > 0) {
239
+ const rect = this.getRootNode().posFromIndex(start);
240
+ errors.push(...duplicatedArgs.flatMap(([, args]) => [...args]).map(
241
+ arg => generateForChild(arg, rect, '重复参数'),
242
+ ));
243
+ }
244
+ return errors;
245
+ }
246
+
247
+ /** 是否是模板 */
248
+ isTemplate() {
249
+ return this.type === 'template' || this.type === 'magic-word' && this.name === 'invoke';
250
+ }
251
+
217
252
  /**
218
253
  * @override
219
254
  * @returns {string}
220
255
  * @complexity `n`
221
256
  */
222
257
  text() {
223
- const {children, childNodes: {length}, firstElementChild, modifier} = this;
258
+ const {childNodes, firstChild, modifier} = this;
224
259
  return `{{${modifier}${modifier && ':'}${
225
260
  this.type === 'magic-word'
226
- ? `${firstElementChild.text()}${length > 1 ? ':' : ''}${text(children.slice(1), '|')}`
261
+ ? `${firstChild.text()}${childNodes.length > 1 ? ':' : ''}${text(childNodes.slice(1), '|')}`
227
262
  : super.text('|')
228
263
  }}}`;
229
264
  }
@@ -298,7 +333,7 @@ class TranscludeToken extends Token {
298
333
  * @complexity `n`
299
334
  */
300
335
  getAllArgs() {
301
- return this.children.filter(child => child instanceof ParameterToken);
336
+ return this.childNodes.filter(child => child instanceof ParameterToken);
302
337
  }
303
338
 
304
339
  /**
@@ -414,14 +449,16 @@ class TranscludeToken extends Token {
414
449
  */
415
450
  newAnonArg(val) {
416
451
  val = String(val);
417
- const templateLike = this.matches('template, magic-word#invoke'),
452
+ const templateLike = this.isTemplate(),
418
453
  wikitext = `{{${templateLike ? ':T|' : 'lc:'}${val}}}`,
419
454
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
420
- {childNodes: {length}, firstElementChild} = root;
421
- if (length === 1 && firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
422
- && firstElementChild.childNodes.length === 2 && firstElementChild.lastElementChild.anon
423
- ) {
424
- return this.appendChild(firstElementChild.lastChild);
455
+ {childNodes: {length}, firstChild: transclude} = root,
456
+ /** @type {Token & {lastChild: ParameterToken}} */
457
+ {type, name, childNodes: {length: transcludeLength}, lastChild} = transclude,
458
+ targetType = templateLike ? 'template' : 'magic-word',
459
+ targetName = templateLike ? 'T' : 'lc';
460
+ if (length === 1 && type === targetType && name === targetName && transcludeLength === 2 && lastChild.anon) {
461
+ return this.appendChild(lastChild);
425
462
  }
426
463
  throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
427
464
  }
@@ -437,7 +474,7 @@ class TranscludeToken extends Token {
437
474
  setValue(key, value) {
438
475
  if (typeof key !== 'string') {
439
476
  this.typeError('setValue', 'String');
440
- } else if (!this.matches('template, magic-word#invoke')) {
477
+ } else if (!this.isTemplate()) {
441
478
  throw new Error(`${this.constructor.name}.setValue 方法仅供模板使用!`);
442
479
  }
443
480
  const token = this.getArg(key);
@@ -448,13 +485,12 @@ class TranscludeToken extends Token {
448
485
  }
449
486
  const wikitext = `{{:T|${key}=${value}}}`,
450
487
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
451
- {childNodes: {length}, firstElementChild} = root;
452
- if (length !== 1 || !firstElementChild?.matches('template#T')
453
- || firstElementChild.childNodes.length !== 2 || firstElementChild.lastElementChild.name !== key
454
- ) {
488
+ {childNodes: {length}, firstChild: template} = root,
489
+ {type, name, childNodes: {length: templateLength}, lastChild: parameter} = template;
490
+ if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2 || parameter.name !== key) {
455
491
  throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
456
492
  }
457
- this.appendChild(firstElementChild.lastChild);
493
+ this.appendChild(parameter);
458
494
  }
459
495
 
460
496
  /**
@@ -463,11 +499,11 @@ class TranscludeToken extends Token {
463
499
  * @throws `Error` 仅用于模板
464
500
  */
465
501
  anonToNamed() {
466
- if (!this.matches('template, magic-word#invoke')) {
502
+ if (!this.isTemplate()) {
467
503
  throw new Error(`${this.constructor.name}.anonToNamed 方法仅供模板使用!`);
468
504
  }
469
505
  for (const token of this.getAnonArgs()) {
470
- token.firstElementChild.replaceChildren(token.name);
506
+ token.firstChild.replaceChildren(token.name);
471
507
  }
472
508
  }
473
509
 
@@ -484,11 +520,11 @@ class TranscludeToken extends Token {
484
520
  this.typeError('replaceTemplate', 'String');
485
521
  }
486
522
  const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
487
- {childNodes: {length}, firstElementChild} = root;
488
- if (length !== 1 || firstElementChild?.type !== 'template' || firstElementChild.childNodes.length !== 1) {
523
+ {childNodes: {length}, firstChild: template} = root;
524
+ if (length !== 1 || template.type !== 'template' || template.childNodes.length !== 1) {
489
525
  throw new SyntaxError(`非法的模板名称:${title}`);
490
526
  }
491
- this.firstElementChild.replaceChildren(...firstElementChild.firstElementChild.childNodes);
527
+ this.firstChild.replaceChildren(...template.firstChild.childNodes);
492
528
  }
493
529
 
494
530
  /**
@@ -504,16 +540,14 @@ class TranscludeToken extends Token {
504
540
  this.typeError('replaceModule', 'String');
505
541
  }
506
542
  const root = Parser.parse(`{{#invoke:${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
507
- {childNodes: {length}, firstElementChild} = root;
508
- if (length !== 1 || !firstElementChild?.matches('magic-word#invoke')
509
- || firstElementChild.childNodes.length !== 2
510
- ) {
543
+ {childNodes: {length}, firstChild: invoke} = root,
544
+ {type, name, childNodes: {length: invokeLength}, lastChild} = invoke;
545
+ if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 2) {
511
546
  throw new SyntaxError(`非法的模块名称:${title}`);
512
547
  } else if (this.childNodes.length > 1) {
513
- this.children[1].replaceChildren(...firstElementChild.lastElementChild.childNodes);
548
+ this.childNodes[1].replaceChildren(...lastChild.childNodes);
514
549
  } else {
515
- const {lastChild} = firstElementChild;
516
- firstElementChild.destroy(true);
550
+ invoke.destroy(true);
517
551
  this.appendChild(lastChild);
518
552
  }
519
553
  }
@@ -536,16 +570,14 @@ class TranscludeToken extends Token {
536
570
  const root = Parser.parse(
537
571
  `{{#invoke:M|${func}}}`, this.getAttribute('include'), 2, this.getAttribute('config'),
538
572
  ),
539
- {childNodes: {length}, firstElementChild} = root;
540
- if (length !== 1 || !firstElementChild?.matches('magic-word#invoke')
541
- || firstElementChild.childNodes.length !== 3
542
- ) {
573
+ {childNodes: {length}, firstChild: invoke} = root,
574
+ {type, name, childNodes: {length: invokeLength}, lastChild} = invoke;
575
+ if (length !== 1 || type !== 'magic-word' || name !== 'invoke' || invokeLength !== 3) {
543
576
  throw new SyntaxError(`非法的模块函数名:${func}`);
544
577
  } else if (this.childNodes.length > 2) {
545
- this.children[2].replaceChildren(...firstElementChild.lastElementChild.childNodes);
578
+ this.childNodes[2].replaceChildren(...lastChild.childNodes);
546
579
  } else {
547
- const {lastChild} = firstElementChild;
548
- firstElementChild.destroy(true);
580
+ invoke.destroy(true);
549
581
  this.appendChild(lastChild);
550
582
  }
551
583
  }
@@ -556,7 +588,7 @@ class TranscludeToken extends Token {
556
588
  * @throws `Error` 仅用于模板
557
589
  */
558
590
  hasDuplicatedArgs() {
559
- if (this.matches('template, magic-word#invoke')) {
591
+ if (this.isTemplate()) {
560
592
  return this.getAllArgs().length - this.getKeys().length;
561
593
  }
562
594
  throw new Error(`${this.constructor.name}.hasDuplicatedArgs 方法仅供模板使用!`);
@@ -565,10 +597,11 @@ class TranscludeToken extends Token {
565
597
  /**
566
598
  * 获取重名参数
567
599
  * @complexity `n`
600
+ * @returns {[string, Set<ParameterToken>][]}
568
601
  * @throws `Error` 仅用于模板
569
602
  */
570
603
  getDuplicatedArgs() {
571
- if (this.matches('template, magic-word#invoke')) {
604
+ if (this.isTemplate()) {
572
605
  return Object.entries(this.#args).filter(([, {size}]) => size > 1).map(([key, args]) => [key, new Set(args)]);
573
606
  }
574
607
  throw new Error(`${this.constructor.name}.getDuplicatedArgs 方法仅供模板使用!`);
@@ -652,7 +685,7 @@ class TranscludeToken extends Token {
652
685
  if (remaining > 1) {
653
686
  Parser.error(`${this.type === 'template'
654
687
  ? this.name
655
- : this.normalizeTitle(this.children[1]?.text() ?? '', 828).title
688
+ : this.normalizeTitle(this.childNodes[1]?.text() ?? '', 828).title
656
689
  } 还留有 ${remaining} 个重复的 ${key} 参数:${[...this.getArgs(key)].map(arg => {
657
690
  const {top, left} = arg.getBoundingClientRect();
658
691
  return `第 ${top} 行第 ${left} 列`;
@@ -680,7 +713,7 @@ class TranscludeToken extends Token {
680
713
  config = this.getAttribute('config'),
681
714
  parsed = Parser.parse(stripped, include, 4, config);
682
715
  const TableToken = require('./table');
683
- for (const table of parsed.children) {
716
+ for (const table of parsed.childNodes) {
684
717
  if (table instanceof TableToken) {
685
718
  table.escape();
686
719
  }
package/tool/index.js CHANGED
@@ -855,7 +855,7 @@ const $ = tokens => {
855
855
  /** @param {PropertyKey} prop */
856
856
  get(obj, prop) {
857
857
  if (prop === Symbol.iterator || typeof obj[prop] !== 'function'
858
- || !prop.startsWith('_') && Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop)
858
+ || prop[0] !== '_' && Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop)
859
859
  || !externalUse(prop, true)
860
860
  ) {
861
861
  return obj[prop];