wikiparser-node 0.0.1 → 0.1.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.
package/src/magicLink.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
4
- /** @type {Parser} */ Parser = require('..'),
3
+ const /** @type {Parser} */ Parser = require('..'),
5
4
  Token = require('.');
6
5
 
7
6
  /**
@@ -15,10 +14,9 @@ class MagicLinkToken extends Token {
15
14
  get protocol() {
16
15
  return this.text().match(this.#protocolRegex)?.[0];
17
16
  }
18
- /** @param {string} value */
19
17
  set protocol(value) {
20
18
  if (typeof value !== 'string') {
21
- typeError(this, 'protocol', 'String');
19
+ this.typeError('protocol', 'String');
22
20
  }
23
21
  if (!new RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
24
22
  throw new RangeError(`非法的外链协议:${value}`);
@@ -38,6 +36,14 @@ class MagicLinkToken extends Token {
38
36
  this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'i');
39
37
  }
40
38
 
39
+ afterBuild() {
40
+ const ParameterToken = require('./parameter'), // eslint-disable-line no-unused-vars
41
+ /** @type {ParameterToken} */ parameter = this.closest('parameter');
42
+ if (parameter?.getValue() === this.text()) {
43
+ this.replaceWith(this.toString());
44
+ }
45
+ }
46
+
41
47
  getUrl() {
42
48
  const url = this.text();
43
49
  try {
@@ -30,7 +30,7 @@ class CommentToken extends hidden(NowikiToken) {
30
30
  toString() {
31
31
  const {firstChild, closed, nextSibling} = this;
32
32
  if (!closed && nextSibling) {
33
- Parser.error('自动闭合HTML注释', firstChild.replaceAll('\n', '\\n'));
33
+ Parser.error('自动闭合HTML注释', this);
34
34
  this.closed = true;
35
35
  }
36
36
  return `<!--${firstChild}${this.closed ? '-->' : ''}`;
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('../..'),
4
+ NowikiToken = require('.');
5
+
6
+ /**
7
+ * :
8
+ * @classdesc `{childNodes: [string]}`
9
+ */
10
+ class DdToken extends NowikiToken {
11
+ type = 'dd';
12
+ dt;
13
+ ul;
14
+ ol;
15
+ indent;
16
+
17
+ /** @param {string} str */
18
+ #update(str) {
19
+ this.setAttribute('ul', str.includes('*')).setAttribute('ol', str.includes('#'))
20
+ .setAttribute('dt', str.includes(';')).setAttribute('indent', str.split(':').length - 1);
21
+ }
22
+
23
+ /**
24
+ * @param {string} str
25
+ * @param {accum} accum
26
+ */
27
+ constructor(str, config = Parser.getConfig(), accum = []) {
28
+ super(str, config, accum);
29
+ this.seal(['dt', 'ul', 'ol', 'indent']).#update(str);
30
+ }
31
+
32
+ /** @returns {[number, string][]} */
33
+ plain() {
34
+ return [];
35
+ }
36
+
37
+ /** @param {string} str */
38
+ setText(str) {
39
+ const src = this.type === 'dd' ? ':' : ';:*#';
40
+ if (new RegExp(`[^${src}]`).test(str)) {
41
+ throw new RangeError(`${this.constructor.name} 仅能包含${src.split('').map(c => `"${c}"`).join('、')}!`);
42
+ }
43
+ this.#update(str);
44
+ return super.setText(str);
45
+ }
46
+ }
47
+
48
+ Parser.classes.DdToken = __filename;
49
+ module.exports = DdToken;
package/src/nowiki/hr.js CHANGED
@@ -1,13 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('../..'),
3
+ const sol = require('../../mixin/sol'),
4
+ /** @type {Parser} */ Parser = require('../..'),
4
5
  NowikiToken = require('.');
5
6
 
6
7
  /**
7
8
  * `<hr>`
8
9
  * @classdesc `{childNodes: [string]}`
9
10
  */
10
- class HrToken extends NowikiToken {
11
+ class HrToken extends sol(NowikiToken) {
11
12
  type = 'hr';
12
13
 
13
14
  /**
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const sol = require('../../mixin/sol'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ DdToken = require('./dd');
6
+
7
+ /**
8
+ * ;:*#
9
+ * @classdesc `{childNodes: [string]}`
10
+ */
11
+ class ListToken extends sol(DdToken) {
12
+ type = 'list';
13
+ }
14
+
15
+ Parser.classes.ListToken = __filename;
16
+ module.exports = ListToken;
package/src/parameter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
3
+ const {noWrap} = require('../util/string'),
4
4
  fixedToken = require('../mixin/fixedToken'),
5
5
  /** @type {Parser} */ Parser = require('..'),
6
6
  Token = require('.');
@@ -116,7 +116,7 @@ class ParameterToken extends fixedToken(Token) {
116
116
  || firstElementChild.childElementCount !== 2
117
117
  || lastElementChild.anon !== this.anon || lastElementChild.name !== '1'
118
118
  ) {
119
- throw new SyntaxError(`非法的模板参数:${value.replaceAll('\n', '\\n')}`);
119
+ throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
120
120
  }
121
121
  const newValue = lastElementChild.lastChild;
122
122
  root.destroy();
@@ -128,7 +128,7 @@ class ParameterToken extends fixedToken(Token) {
128
128
  /** @param {string} key */
129
129
  rename(key, force = false) {
130
130
  if (typeof key !== 'string') {
131
- typeError(this, 'rename', 'String');
131
+ this.typeError('rename', 'String');
132
132
  }
133
133
  const {parentNode} = this;
134
134
  // 必须检测是否是TranscludeToken
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../../util/debug'),
4
- assert = require('assert/strict'),
3
+ const assert = require('assert/strict'),
4
+ {noWrap} = require('../../util/string'),
5
5
  /** @type {Parser} */ Parser = require('../..'),
6
6
  Token = require('..'), // eslint-disable-line no-unused-vars
7
7
  TrToken = require('./tr'),
@@ -70,40 +70,45 @@ class TableToken extends TrToken {
70
70
  }
71
71
 
72
72
  /**
73
- * @template {string|Token} T
73
+ * @template {TrToken|SyntaxToken} T
74
74
  * @param {T} token
75
75
  * @returns {T}
76
76
  * @complexity `n`
77
77
  */
78
78
  insertAt(token, i = this.childNodes.length) {
79
- const previous = this.childNodes.at(i - 1),
79
+ const previous = this.children.at(i - 1),
80
80
  {closingPattern} = TableToken;
81
- if (token instanceof TrToken && token.type === 'td' && previous instanceof TrToken && previous.type === 'tr') {
81
+ if (token.type === 'td' && previous.type === 'tr') {
82
82
  Parser.warn('改为将单元格插入当前行。');
83
83
  return previous.appendChild(token);
84
84
  } else if (!Parser.running && i === this.childNodes.length && token instanceof SyntaxToken
85
85
  && (token.getAttribute('pattern') !== closingPattern || !closingPattern.test(token.text()))
86
86
  ) {
87
- throw new SyntaxError(`表格的闭合部分不符合语法!${token.toString().replaceAll('\n', '\\n')}`);
87
+ throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(token.toString())}`);
88
88
  }
89
89
  return super.insertAt(token, i);
90
90
  }
91
91
 
92
92
  /** @complexity `n` */
93
- close(syntax = '\n|}') {
93
+ close(syntax = '\n|}', halfParsed = false) {
94
+ halfParsed &&= Parser.running;
94
95
  const config = this.getAttribute('config'),
95
- inner = Parser.parse(syntax, this.getAttribute('include'), 2, config),
96
- {lastElementChild} = this;
97
- if (!TableToken.closingPattern.test(inner.text())) {
98
- throw new SyntaxError(`表格的闭合部分不符合语法!${syntax.replaceAll('\n', '\\n')}`);
96
+ accum = this.getAttribute('accum'),
97
+ inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
98
+ {lastElementChild} = this,
99
+ {closingPattern} = TableToken;
100
+ if (!halfParsed && !closingPattern.test(inner.text())) {
101
+ throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
99
102
  } else if (lastElementChild instanceof SyntaxToken) {
100
103
  lastElementChild.replaceChildren(...inner.childNodes);
101
104
  } else {
102
105
  this.appendChild(Parser.run(() => {
103
- const token = new SyntaxToken(undefined, TableToken.closingPattern, 'table-syntax', config, [], {
106
+ const token = new SyntaxToken(syntax, closingPattern, 'table-syntax', config, accum, {
104
107
  'Stage-1': ':', '!ExtToken': '', TranscludeToken: ':',
105
108
  });
106
- token.replaceChildren(...inner.childNodes);
109
+ if (inner) {
110
+ token.replaceChildren(...inner.childNodes);
111
+ }
107
112
  return token;
108
113
  }));
109
114
  }
@@ -132,7 +137,7 @@ class TableToken extends TrToken {
132
137
  */
133
138
  getNthRow(n, force = false, insert = false) {
134
139
  if (typeof n !== 'number') {
135
- typeError(this, 'getNthRow', 'Number');
140
+ this.typeError('getNthRow', 'Number');
136
141
  }
137
142
  const nRows = this.getRowCount(),
138
143
  isRow = super.getRowCount();
@@ -240,7 +245,7 @@ class TableToken extends TrToken {
240
245
  */
241
246
  toRenderedCoords({row, column}) {
242
247
  if (typeof row !== 'number' || typeof column !== 'number') {
243
- typeError(this, 'toRenderedCoords', 'Number');
248
+ this.typeError('toRenderedCoords', 'Number');
244
249
  }
245
250
  const rowLayout = this.getLayout({row, column})[row],
246
251
  x = rowLayout?.findIndex(coords => cmpCoords(coords, {row, column}) === 0);
@@ -253,7 +258,7 @@ class TableToken extends TrToken {
253
258
  */
254
259
  toRawCoords({x, y}) {
255
260
  if (typeof x !== 'number' || typeof y !== 'number') {
256
- typeError(this, 'toRawCoords', 'Number');
261
+ this.typeError('toRawCoords', 'Number');
257
262
  }
258
263
  const rowLayout = this.getLayout({x, y})[y],
259
264
  coords = rowLayout?.[x];
@@ -272,7 +277,7 @@ class TableToken extends TrToken {
272
277
  */
273
278
  getFullRow(y) {
274
279
  if (typeof y !== 'number') {
275
- typeError(this, 'getFullRow', 'Number');
280
+ this.typeError('getFullRow', 'Number');
276
281
  }
277
282
  const rows = this.getAllRows();
278
283
  return new Map(
@@ -286,7 +291,7 @@ class TableToken extends TrToken {
286
291
  */
287
292
  getFullCol(x) {
288
293
  if (typeof x !== 'number') {
289
- typeError(this, 'getFullCol', 'Number');
294
+ this.typeError('getFullCol', 'Number');
290
295
  }
291
296
  const layout = this.getLayout(),
292
297
  colLayout = layout.map(row => row[x]).filter(coords => coords),
@@ -412,14 +417,14 @@ class TableToken extends TrToken {
412
417
  /** @complexity `n` */
413
418
  #prependTableRow() {
414
419
  const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
415
- {childNodes} = this,
416
- [,, plain] = childNodes,
417
- start = typeof plain === 'string' || plain.isPlain() ? 3 : 2,
418
- /** @type {TdToken[]} */ children = childNodes.slice(start),
419
- index = children.findIndex(({type}) => type !== 'td');
420
+ {children} = this,
421
+ [,, plain] = children,
422
+ start = plain?.isPlain() ? 3 : 2,
423
+ /** @type {TdToken[]} */ tdChildren = children.slice(start),
424
+ index = tdChildren.findIndex(({type}) => type !== 'td');
420
425
  this.insertAt(row, index === -1 ? -1 : index + start);
421
426
  Parser.run(() => {
422
- for (const cell of children.slice(0, index === -1 ? undefined : index)) {
427
+ for (const cell of tdChildren.slice(0, index === -1 ? undefined : index)) {
423
428
  if (cell.subtype !== 'caption') {
424
429
  row.appendChild(cell);
425
430
  }
@@ -438,7 +443,7 @@ class TableToken extends TrToken {
438
443
  */
439
444
  insertTableRow(y, attr = {}, inner = undefined, subtype = 'td', innerAttr = {}) {
440
445
  if (typeof attr !== 'object') {
441
- typeError(this, 'insertTableRow', 'Object');
446
+ this.typeError('insertTableRow', 'Object');
442
447
  }
443
448
  let reference = this.getNthRow(y, false, true);
444
449
  /** @type {TrToken & AttributeToken}} */
@@ -482,7 +487,7 @@ class TableToken extends TrToken {
482
487
  */
483
488
  insertTableCol(x, inner, subtype = 'td', attr = {}) {
484
489
  if (typeof x !== 'number') {
485
- typeError(this, 'insertTableCol', 'Number');
490
+ this.typeError('insertTableCol', 'Number');
486
491
  }
487
492
  const layout = this.getLayout(),
488
493
  rowLength = layout.map(({length}) => length),
@@ -565,7 +570,7 @@ class TableToken extends TrToken {
565
570
  */
566
571
  mergeCells(xlim, ylim) {
567
572
  if ([...xlim, ...ylim].some(arg => typeof arg !== 'number')) {
568
- typeError(this, 'mergeCells', 'Number');
573
+ this.typeError('mergeCells', 'Number');
569
574
  }
570
575
  const layout = this.getLayout(),
571
576
  maxCol = Math.max(...layout.map(({length}) => length));
@@ -711,7 +716,7 @@ class TableToken extends TrToken {
711
716
  */
712
717
  moveTableRowBefore(y, before) {
713
718
  if (typeof y !== 'number' || typeof before !== 'number') {
714
- typeError(this, 'moveTableRowBefore', 'Number');
719
+ this.typeError('moveTableRowBefore', 'Number');
715
720
  }
716
721
  const layout = this.getLayout(),
717
722
  /**
@@ -748,7 +753,7 @@ class TableToken extends TrToken {
748
753
  */
749
754
  moveTableRowAfter(y, after) {
750
755
  if (typeof y !== 'number' || typeof after !== 'number') {
751
- typeError(this, 'moveTableRowAfter', 'Number');
756
+ this.typeError('moveTableRowAfter', 'Number');
752
757
  }
753
758
  const layout = this.getLayout(),
754
759
  afterToken = this.getNthRow(after),
@@ -797,7 +802,7 @@ class TableToken extends TrToken {
797
802
  */
798
803
  #moveCol(x, reference, after = false) {
799
804
  if (typeof x !== 'number' || typeof reference !== 'number') {
800
- typeError(this, `moveTableCol${after ? 'After' : 'Before'}`, 'Number');
805
+ this.typeError(`moveTableCol${after ? 'After' : 'Before'}`, 'Number');
801
806
  }
802
807
  const layout = this.getLayout(),
803
808
  /** @type {(rowLayout: TableCoords[], i: number, oneCol?: boolean) => boolean} */
package/src/table/td.js CHANGED
@@ -110,7 +110,7 @@ class TdToken extends fixedToken(TrToken) {
110
110
  */
111
111
  static create(inner, subtype = 'td', attr = {}, include = false, config = Parser.getConfig()) {
112
112
  if (typeof inner !== 'string' && (!(inner instanceof Token) || !inner.isPlain()) || typeof attr !== 'object') {
113
- throw new TypeError('TdToken.create 方法仅接受 String、Token、Object 作为输入参数!');
113
+ typeError(this, 'create', 'String', 'Token', 'Object');
114
114
  } else if (!['td', 'th', 'caption'].includes(subtype)) {
115
115
  throw new RangeError('单元格的子类型只能为 "td"、"th" 或 "caption"!');
116
116
  } else if (typeof inner === 'string') {
@@ -231,7 +231,7 @@ class TdToken extends fixedToken(TrToken) {
231
231
  */
232
232
  setAttr(key, value) {
233
233
  if (typeof key !== 'string') {
234
- typeError(this, 'setAttr', 'String');
234
+ this.typeError('setAttr', 'String');
235
235
  }
236
236
  key = key.toLowerCase().trim();
237
237
  if (typeof value === 'number' && ['rowspan', 'colspan'].includes(key)) {
package/src/table/tr.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const attributeParent = require('../../mixin/attributeParent'),
4
- {typeError} = require('../../util/debug'),
5
4
  /** @type {Parser} */ Parser = require('../..'),
6
5
  Token = require('..'),
7
6
  SyntaxToken = require('../syntax'),
@@ -49,15 +48,13 @@ class TrToken extends attributeParent(Token, 1) {
49
48
  }
50
49
 
51
50
  #correct() {
52
- const [,, child] = this.childNodes;
53
- if (typeof child === 'string' && !child.startsWith('\n')) {
54
- this.setText(`\n${child}`, 2);
55
- } else if (typeof child !== 'string' && child?.isPlain()) {
51
+ const [,, child] = this.children;
52
+ if (child?.isPlain()) {
56
53
  const {firstChild} = child;
57
54
  if (typeof firstChild !== 'string') {
58
55
  child.prepend('\n');
59
56
  } else if (!firstChild.startsWith('\n')) {
60
- child.setText(`\n${firstChild}`, 0);
57
+ child.setText(`\n${firstChild}`);
61
58
  }
62
59
  }
63
60
  }
@@ -75,9 +72,6 @@ class TrToken extends attributeParent(Token, 1) {
75
72
 
76
73
  /** @param {SyntaxToken} syntax */
77
74
  static escape(syntax) {
78
- if (!(syntax instanceof SyntaxToken)) {
79
- typeError('SyntaxToken');
80
- }
81
75
  const wikitext = syntax.childNodes.map(child => typeof child === 'string'
82
76
  ? child.replaceAll('{|', '{{(!}}').replaceAll('|}', '{{!)}}').replaceAll('||', '{{!!}}')
83
77
  .replaceAll('|', '{{!}}')
@@ -131,7 +125,7 @@ class TrToken extends attributeParent(Token, 1) {
131
125
  */
132
126
  insertAt(token, i = this.childNodes.length) {
133
127
  if (!Parser.running && !(token instanceof TrToken)) {
134
- typeError(this, 'insertAt', 'TrToken');
128
+ this.typeError('insertAt', 'TrToken');
135
129
  }
136
130
  const TdToken = require('./td'),
137
131
  child = this.childNodes.at(i);
@@ -201,7 +195,7 @@ class TrToken extends attributeParent(Token, 1) {
201
195
  */
202
196
  getNthCol(n, insert = false) {
203
197
  if (typeof n !== 'number') {
204
- typeError(this, 'getNthCol', 'Number');
198
+ this.typeError('getNthCol', 'Number');
205
199
  }
206
200
  const nCols = this.getColCount();
207
201
  n = n < 0 ? n + nCols : n;
@@ -52,7 +52,7 @@ class TagPairToken extends fixedToken(Token) {
52
52
  const {closed, firstChild, lastChild, nextSibling, name, selfClosing} = this,
53
53
  [opening, closing] = this.#tags;
54
54
  if (!closed && nextSibling) {
55
- Parser.error(`自动闭合 <${name}>`, String(lastChild).replaceAll('\n', '\\n'));
55
+ Parser.error(`自动闭合 <${name}>`, lastChild);
56
56
  this.closed = true;
57
57
  }
58
58
  return selfClosing
package/src/transclude.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {removeComment, escapeRegExp, text} = require('../util/string'),
4
- {typeError, externalUse} = require('../util/debug'),
3
+ const {removeComment, escapeRegExp, text, noWrap} = require('../util/string'),
4
+ {externalUse} = require('../util/debug'),
5
5
  /** @type {Parser} */ Parser = require('..'),
6
6
  Token = require('.'),
7
7
  ParameterToken = require('./parameter');
@@ -19,7 +19,7 @@ class TranscludeToken extends Token {
19
19
  /** @complexity `n` */
20
20
  setModifier(modifier = '') {
21
21
  if (typeof modifier !== 'string') {
22
- typeError(this, 'setModifier', 'String');
22
+ this.typeError('setModifier', 'String');
23
23
  }
24
24
  const [,, raw, subst] = this.getAttribute('config').parserFunction,
25
25
  lcModifier = modifier.trim().toLowerCase(),
@@ -291,7 +291,7 @@ class TranscludeToken extends Token {
291
291
  */
292
292
  getArgs(key, exact = false, copy = true) {
293
293
  if (!['string', 'number'].includes(typeof key)) {
294
- typeError(this, 'getArgs', 'String', 'Number');
294
+ this.typeError('getArgs', 'String', 'Number');
295
295
  } else if (!copy && !Parser.debugging && externalUse('getArgs')) {
296
296
  this.debugOnly('getArgs');
297
297
  }
@@ -383,7 +383,7 @@ class TranscludeToken extends Token {
383
383
  if (length !== 1 || !firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
384
384
  || firstElementChild.childElementCount !== 2 || !firstElementChild.lastElementChild.anon
385
385
  ) {
386
- throw new SyntaxError(`非法的匿名参数:${val.replaceAll('\n', '\\n')}`);
386
+ throw new SyntaxError(`非法的匿名参数:${noWrap(val)}`);
387
387
  }
388
388
  return this.appendChild(firstElementChild.lastChild);
389
389
  }
@@ -395,7 +395,7 @@ class TranscludeToken extends Token {
395
395
  */
396
396
  setValue(key, value) {
397
397
  if (typeof key !== 'string') {
398
- typeError(this, 'setValue', 'String');
398
+ this.typeError('setValue', 'String');
399
399
  } else if (!this.matches('template, magic-word#invoke')) {
400
400
  throw new Error(`${this.constructor.name}.setValue 方法仅供模板使用!`);
401
401
  }
@@ -411,7 +411,7 @@ class TranscludeToken extends Token {
411
411
  if (length !== 1 || !firstElementChild?.matches('template#T')
412
412
  || firstElementChild.childElementCount !== 2 || firstElementChild.lastElementChild.name !== key
413
413
  ) {
414
- throw new SyntaxError(`非法的命名参数:${key}=${value.replaceAll('\n', '\\n')}`);
414
+ throw new SyntaxError(`非法的命名参数:${key}=${noWrap(value)}`);
415
415
  }
416
416
  this.appendChild(firstElementChild.lastChild);
417
417
  }
@@ -432,7 +432,7 @@ class TranscludeToken extends Token {
432
432
  if (this.type === 'magic-word') {
433
433
  throw new Error(`${this.constructor.name}.replaceTemplate 方法仅用于更换模板!`);
434
434
  } else if (typeof title !== 'string') {
435
- typeError(this, 'replaceTemplate', 'String');
435
+ this.typeError('replaceTemplate', 'String');
436
436
  }
437
437
  const root = Parser.parse(`{{${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
438
438
  {childNodes: {length}, firstElementChild} = root;
@@ -447,7 +447,7 @@ class TranscludeToken extends Token {
447
447
  if (this.type !== 'magic-word' || this.name !== 'invoke') {
448
448
  throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
449
449
  } else if (typeof title !== 'string') {
450
- typeError(this, 'replaceModule', 'String');
450
+ this.typeError('replaceModule', 'String');
451
451
  }
452
452
  const root = Parser.parse(`{{#invoke:${title}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
453
453
  {childNodes: {length}, firstElementChild} = root;
@@ -470,7 +470,7 @@ class TranscludeToken extends Token {
470
470
  if (this.type !== 'magic-word' || this.name !== 'invoke') {
471
471
  throw new Error(`${this.constructor.name}.replaceModule 方法仅用于更换模块!`);
472
472
  } else if (typeof func !== 'string') {
473
- typeError(this, 'replaceFunction', 'String');
473
+ this.typeError('replaceFunction', 'String');
474
474
  } else if (this.childElementCount < 2) {
475
475
  throw new Error('尚未指定模块名称!');
476
476
  }
@@ -582,7 +582,10 @@ class TranscludeToken extends Token {
582
582
  Parser.error(`${this.type === 'template'
583
583
  ? this.name
584
584
  : this.normalizeTitle(this.children[1]?.text() ?? '', 828).title
585
- } 还留有 ${remaining} 个重复的 ${key} 参数!`);
585
+ } 还留有 ${remaining} 个重复的 ${key} 参数:${[...this.getArgs(key)].map(arg => {
586
+ const {top, left} = arg.getBoundingClientRect();
587
+ return `第 ${top} 行第 ${left} 列`;
588
+ }).join('、')}`);
586
589
  duplicatedKeys.push(key);
587
590
  continue;
588
591
  }