wikiparser-node 0.5.0 → 0.6.1

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 (63) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +74 -65
  4. package/lib/element.js +125 -152
  5. package/lib/node.js +251 -223
  6. package/lib/ranges.js +2 -2
  7. package/lib/text.js +64 -64
  8. package/lib/title.js +8 -7
  9. package/mixin/hidden.js +2 -0
  10. package/mixin/sol.js +1 -2
  11. package/package.json +4 -3
  12. package/parser/brackets.js +8 -2
  13. package/parser/externalLinks.js +1 -1
  14. package/parser/hrAndDoubleUnderscore.js +4 -4
  15. package/parser/links.js +7 -7
  16. package/parser/table.js +12 -10
  17. package/src/arg.js +53 -48
  18. package/src/atom/index.js +7 -5
  19. package/src/attribute.js +91 -80
  20. package/src/charinsert.js +91 -0
  21. package/src/converter.js +22 -11
  22. package/src/converterFlags.js +72 -62
  23. package/src/converterRule.js +49 -49
  24. package/src/extLink.js +30 -28
  25. package/src/gallery.js +56 -32
  26. package/src/hasNowiki/index.js +42 -0
  27. package/src/hasNowiki/pre.js +40 -0
  28. package/src/heading.js +15 -11
  29. package/src/html.js +38 -38
  30. package/src/imageParameter.js +64 -48
  31. package/src/imagemap.js +205 -0
  32. package/src/imagemapLink.js +43 -0
  33. package/src/index.js +222 -124
  34. package/src/link/category.js +4 -8
  35. package/src/link/file.js +95 -59
  36. package/src/link/galleryImage.js +74 -10
  37. package/src/link/index.js +61 -39
  38. package/src/magicLink.js +21 -22
  39. package/src/nested/choose.js +24 -0
  40. package/src/nested/combobox.js +23 -0
  41. package/src/nested/index.js +88 -0
  42. package/src/nested/references.js +23 -0
  43. package/src/nowiki/comment.js +17 -17
  44. package/src/nowiki/dd.js +2 -2
  45. package/src/nowiki/doubleUnderscore.js +14 -14
  46. package/src/nowiki/index.js +12 -0
  47. package/src/onlyinclude.js +10 -8
  48. package/src/paramTag/index.js +83 -0
  49. package/src/paramTag/inputbox.js +42 -0
  50. package/src/parameter.js +32 -18
  51. package/src/syntax.js +9 -1
  52. package/src/table/index.js +33 -32
  53. package/src/table/td.js +51 -57
  54. package/src/table/tr.js +6 -6
  55. package/src/tagPair/ext.js +58 -40
  56. package/src/tagPair/include.js +1 -1
  57. package/src/tagPair/index.js +21 -20
  58. package/src/transclude.js +158 -143
  59. package/tool/index.js +720 -439
  60. package/util/base.js +17 -0
  61. package/util/debug.js +1 -1
  62. package/util/diff.js +1 -1
  63. package/util/string.js +20 -20
package/lib/text.js CHANGED
@@ -23,6 +23,68 @@ class AstText extends AstNode {
23
23
  });
24
24
  }
25
25
 
26
+ /** 输出字符串 */
27
+ toString() {
28
+ return this.data;
29
+ }
30
+
31
+ /**
32
+ * 修改内容
33
+ * @param {string} text 新内容
34
+ */
35
+ #setData(text) {
36
+ text = String(text);
37
+ const {data} = this,
38
+ e = new Event('text', {bubbles: true});
39
+ this.setAttribute('data', text);
40
+ if (data !== text) {
41
+ this.dispatchEvent(e, {oldText: data, newText: text});
42
+ }
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * 替换字符串
48
+ * @param {string} text 替换的字符串
49
+ */
50
+ replaceData(text = '') {
51
+ this.#setData(text);
52
+ }
53
+
54
+ static errorSyntax = /[{}]+|\[{2,}|\[(?!(?:(?!https?\b)[^[])*\])|(?<=^|\])([^[]*?)\]+|<(?=\s*\/?\w+[\s/>])/giu;
55
+
56
+ /**
57
+ * Linter
58
+ * @param {number} start 起始位置
59
+ * @returns {LintError[]}
60
+ */
61
+ lint(start = 0) {
62
+ const {data} = this,
63
+ errors = [...data.matchAll(AstText.errorSyntax)];
64
+ if (errors.length > 0) {
65
+ const {top, left} = this.getRootNode().posFromIndex(start);
66
+ return errors.map(({0: error, 1: prefix, index}) => {
67
+ if (prefix) {
68
+ index += prefix.length;
69
+ error = error.slice(prefix.length);
70
+ }
71
+ const lines = data.slice(0, index).split('\n'),
72
+ startLine = lines.length + top - 1,
73
+ {length} = lines.at(-1),
74
+ startCol = lines.length > 1 ? length : left + length;
75
+ return {
76
+ message: `孤立的"${error[0]}"`,
77
+ severity: error[0] === '{' || error[0] === '}' ? 'error' : 'warning',
78
+ startLine,
79
+ endLine: startLine,
80
+ startCol,
81
+ endCol: startCol + error.length,
82
+ };
83
+ });
84
+ }
85
+ return [];
86
+ }
87
+
26
88
  /** 复制 */
27
89
  cloneNode() {
28
90
  return new AstText(this.data);
@@ -43,31 +105,11 @@ class AstText extends AstNode {
43
105
  : super.getAttribute(key);
44
106
  }
45
107
 
46
- /** 输出字符串 */
47
- toString() {
48
- return this.data;
49
- }
50
-
51
108
  /** @override */
52
109
  text() {
53
110
  return this.data;
54
111
  }
55
112
 
56
- /**
57
- * 修改内容
58
- * @param {string} text 新内容
59
- */
60
- #setData(text) {
61
- text = String(text);
62
- const {data} = this,
63
- e = new Event('text', {bubbles: true});
64
- this.setAttribute('data', text);
65
- if (data !== text) {
66
- this.dispatchEvent(e, {oldText: data, newText: text});
67
- }
68
- return this;
69
- }
70
-
71
113
  /**
72
114
  * 在后方添加字符串
73
115
  * @param {string} text 添加的字符串
@@ -94,14 +136,6 @@ class AstText extends AstNode {
94
136
  this.#setData(this.data.slice(0, offset) + text + this.data.slice(offset));
95
137
  }
96
138
 
97
- /**
98
- * 替换字符串
99
- * @param {string} text 替换的字符串
100
- */
101
- replaceData(text = '') {
102
- this.#setData(text);
103
- }
104
-
105
139
  /**
106
140
  * 提取子串
107
141
  * @param {number} offset 起始位置
@@ -118,9 +152,9 @@ class AstText extends AstNode {
118
152
  * @throws `Error` 没有父节点
119
153
  */
120
154
  splitText(offset) {
121
- if (typeof offset !== 'number') {
155
+ if (!Number.isInteger(offset)) {
122
156
  this.typeError('splitText', 'Number');
123
- } else if (offset > this.length || offset < -this.length || !Number.isInteger(offset)) {
157
+ } else if (offset > this.length || offset < -this.length) {
124
158
  throw new RangeError(`错误的断开位置!${offset}`);
125
159
  }
126
160
  const {parentNode, data} = this;
@@ -135,40 +169,6 @@ class AstText extends AstNode {
135
169
  parentNode.setAttribute('childNodes', childNodes);
136
170
  return newText;
137
171
  }
138
-
139
- static errorSyntax = /[{}]+|\[{2,}|\[(?!(?:(?!https?\b)[^[])*\])|(?<=^|\])([^[]*?)\]+|<(?=\s*\/?\w+[\s/>])/giu;
140
-
141
- /**
142
- * Linter
143
- * @param {number} start 起始位置
144
- * @returns {LintError[]}
145
- */
146
- lint(start = 0) {
147
- const {data} = this,
148
- errors = [...data.matchAll(AstText.errorSyntax)];
149
- if (errors.length > 0) {
150
- const {top, left} = this.getRootNode().posFromIndex(start);
151
- return errors.map(({0: error, 1: prefix, index}) => {
152
- if (prefix) {
153
- index += prefix.length;
154
- error = error.slice(prefix.length);
155
- }
156
- const lines = data.slice(0, index).split('\n'),
157
- startLine = lines.length + top - 1,
158
- {length} = lines.at(-1),
159
- startCol = lines.length > 1 ? length : left + length;
160
- return {
161
- message: `孤立的"${error[0]}"`,
162
- severity: error[0] === '{' || error[0] === '}' ? 'error' : 'warning',
163
- startLine,
164
- endLine: startLine,
165
- startCol,
166
- endCol: startCol + error.length,
167
- };
168
- });
169
- }
170
- return [];
171
- }
172
172
  }
173
173
 
174
174
  Parser.classes.AstText = __filename;
package/lib/title.js CHANGED
@@ -4,13 +4,13 @@ const Parser = require('..');
4
4
 
5
5
  /** MediaWiki页面标题对象 */
6
6
  class Title {
7
+ valid = true;
8
+ ns = 0;
7
9
  title = '';
8
10
  main = '';
9
11
  prefix = '';
10
- ns = 0;
11
12
  interwiki = '';
12
13
  fragment = '';
13
- valid = true;
14
14
 
15
15
  /**
16
16
  * @param {string} title 标题(含或不含命名空间前缀)
@@ -39,24 +39,25 @@ class Title {
39
39
  }
40
40
  this.ns = nsid[namespace.toLowerCase()];
41
41
  const i = title.indexOf('#');
42
+ let fragment = '';
42
43
  if (i !== -1) {
43
- const fragment = title.slice(i + 1).trimEnd();
44
+ fragment = title.slice(i + 1).trimEnd();
44
45
  if (fragment.includes('%')) {
45
46
  try {
46
- this.fragment = decodeURIComponent(fragment);
47
+ fragment = decodeURIComponent(fragment);
47
48
  } catch {}
48
49
  } else if (fragment.includes('.')) {
49
50
  try {
50
- this.fragment = decodeURIComponent(fragment.replaceAll('.', '%'));
51
+ fragment = decodeURIComponent(fragment.replaceAll('.', '%'));
51
52
  } catch {}
52
53
  }
53
- this.fragment ||= fragment;
54
54
  title = title.slice(0, i).trim();
55
55
  }
56
+ this.valid = Boolean(title || fragment) && !/\0\d+[eh!+-]\x7F|[<>[\]{}|]/u.test(title);
56
57
  this.main = title && `${title[0].toUpperCase()}${title.slice(1)}`;
57
58
  this.prefix = `${namespace}${namespace && ':'}`;
58
59
  this.title = `${iw ? `${this.interwiki}:` : ''}${this.prefix}${this.main.replaceAll(' ', '_')}`;
59
- this.valid = Boolean(this.main || this.fragment) && !/\0\d+[eh!+-]\x7F|[<>[\]{}|]/u.test(this.title);
60
+ this.fragment = fragment;
60
61
  }
61
62
 
62
63
  /** @override */
package/mixin/hidden.js CHANGED
@@ -9,6 +9,8 @@ const Parser = require('..');
9
9
  * @returns {T}
10
10
  */
11
11
  const hidden = Constructor => class extends Constructor {
12
+ static hidden = true;
13
+
12
14
  /** 没有可见部分 */
13
15
  text() { // eslint-disable-line class-methods-use-this
14
16
  return '';
package/mixin/sol.js CHANGED
@@ -18,8 +18,7 @@ const sol = Constructor => class SolToken extends Constructor {
18
18
  #isRoot(includeHeading) {
19
19
  const {parentNode, type} = this;
20
20
  return parentNode?.type === 'root'
21
- || parentNode?.type === 'ext-inner' && parentNode?.name === 'poem'
22
- && (includeHeading || type !== 'heading');
21
+ || parentNode?.type === 'ext-inner' && (includeHeading || type !== 'heading' && parentNode.name === 'poem');
23
22
  }
24
23
 
25
24
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikiparser-node",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "A Node.js parser for MediaWiki markup with AST",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -28,8 +28,9 @@
28
28
  "url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
29
29
  },
30
30
  "scripts": {
31
- "test": "node test/test.js",
32
- "real": "node test/real.js"
31
+ "test": "eslint . && node test/test.js",
32
+ "real": "node test/real.js",
33
+ "single": "node --prof test/single.js && node --prof-process isolate-0x*-v8.log > test/processed.txt && rm isolate-0x*-v8.log"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^17.0.23",
@@ -57,12 +57,18 @@ const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
57
57
  {length} = accum;
58
58
  lastIndex = curIndex + close.length; // 这不是最终的lastIndex
59
59
  parts.at(-1).push(text.slice(topPos, curIndex));
60
- const ch = close.length === 2 ? marks[removeComment(parts[0][0])] ?? 't' : 't'; // 标记{{!}}等
61
- let skip = false;
60
+ let skip = false,
61
+ ch = 't';
62
62
  if (close.length === 3) {
63
63
  const ArgToken = require('../src/arg');
64
64
  new ArgToken(parts.map(part => part.join('=')), config, accum);
65
65
  } else {
66
+ const name = removeComment(parts[0][0]);
67
+ if (name in marks) {
68
+ ch = marks[name]; // 标记{{!}}等
69
+ } else if (/^(?:fullurl|canonicalurl|filepath):./iu.test(name)) {
70
+ ch = 'm';
71
+ }
66
72
  try {
67
73
  const TranscludeToken = require('../src/transclude');
68
74
  new TranscludeToken(parts[0][0], parts.slice(1), config, accum);
@@ -11,7 +11,7 @@ const {extUrlChar} = require('../util/string'),
11
11
  const parseExternalLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
12
12
  const ExtLinkToken = require('../src/extLink');
13
13
  const regex = new RegExp(
14
- `\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0A-\x1F\uFFFD]*)\\]`,
14
+ `\\[((?:${config.protocol}|//)${extUrlChar}|\0\\d+m\x7F)(\\p{Zs}*)([^\\]\x01-\x08\x0A-\x1F\uFFFD]*)\\]`,
15
15
  'giu',
16
16
  );
17
17
  return wikitext.replaceAll(regex, /** @type {function(...string): string} */ (_, url, space, text) => {
@@ -1,8 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const Parser = require('..'),
4
- AstText = require('../lib/text'),
5
- Token = require('../src');
3
+ const Parser = require('..');
6
4
 
7
5
  /**
8
6
  * 解析\<hr\>和状态开关
@@ -10,7 +8,9 @@ const Parser = require('..'),
10
8
  * @param {accum} accum
11
9
  */
12
10
  const parseHrAndDoubleUnderscore = ({firstChild: {data}, type, name}, config = Parser.getConfig(), accum = []) => {
13
- const HrToken = require('../src/nowiki/hr'),
11
+ const AstText = require('../lib/text'),
12
+ Token = require('../src'),
13
+ HrToken = require('../src/nowiki/hr'),
14
14
  DoubleUnderscoreToken = require('../src/nowiki/doubleUnderscore');
15
15
  const {doubleUnderscore} = config;
16
16
  if (type !== 'root' && (type !== 'ext-inner' || name !== 'poem')) {
package/parser/links.js CHANGED
@@ -9,17 +9,17 @@ const Parser = require('..');
9
9
  */
10
10
  const parseLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
11
11
  const parseQuotes = require('./quotes.js');
12
- const regex = /^([^\n<>[\]{}|]+)(?:\|(.*?[^\]]))?\]\](.*)$/su,
13
- regexImg = /^([^\n<>[\]{}|]+)\|(.*)$/su,
12
+ const regex = /^((?:(?!\0\d+!\x7F)[^\n<>[\]{}|])+)(?:(\||\0\d+!\x7F)(.*?[^\]]))?\]\](.*)$/su,
13
+ regexImg = /^((?:(?!\0\d+!\x7F)[^\n<>[\]{}|])+)(\||\0\d+!\x7F)(.*)$/su,
14
14
  regexExt = new RegExp(`^\\s*(?:${config.protocol})`, 'iu'),
15
15
  bits = wikitext.split('[[');
16
16
  let s = bits.shift();
17
17
  for (let i = 0; i < bits.length; i++) {
18
- let mightBeImg, link, text, after;
18
+ let mightBeImg, link, delimiter, text, after;
19
19
  const x = bits[i],
20
20
  m = regex.exec(x);
21
21
  if (m) {
22
- [, link, text, after] = m;
22
+ [, link, delimiter, text, after] = m;
23
23
  if (after[0] === ']' && text?.includes('[')) {
24
24
  text += ']';
25
25
  after = after.slice(1);
@@ -28,7 +28,7 @@ const parseLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
28
28
  const m2 = regexImg.exec(x);
29
29
  if (m2) {
30
30
  mightBeImg = true;
31
- [, link, text] = m2;
31
+ [, link, delimiter, text] = m2;
32
32
  }
33
33
  }
34
34
  if (link === undefined || regexExt.test(link) || /\0\d+[exhbru]\x7F/u.test(link)) {
@@ -74,7 +74,7 @@ const parseLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
74
74
  }
75
75
  text = parseLinks(text, config, accum);
76
76
  if (!found) {
77
- s += `[[${link}|${text}`;
77
+ s += `[[${link}${delimiter}${text}`;
78
78
  continue;
79
79
  }
80
80
  }
@@ -88,7 +88,7 @@ const parseLinks = (wikitext, config = Parser.getConfig(), accum = []) => {
88
88
  LinkToken = require('../src/link/category');
89
89
  }
90
90
  }
91
- new LinkToken(link, text, title, config, accum);
91
+ new LinkToken(link, text, title, config, accum, delimiter);
92
92
  }
93
93
  return s;
94
94
  };
package/parser/table.js CHANGED
@@ -5,8 +5,7 @@ const Parser = require('..'),
5
5
  Token = require('../src'),
6
6
  TableToken = require('../src/table'),
7
7
  TrToken = require('../src/table/tr'),
8
- TdToken = require('../src/table/td'),
9
- DdToken = require('../src/nowiki/dd');
8
+ TdToken = require('../src/table/td');
10
9
 
11
10
  /**
12
11
  * 解析表格,注意`tr`和`td`包含开头的换行
@@ -16,7 +15,9 @@ const Parser = require('..'),
16
15
  const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(), accum = []) => {
17
16
  const /** @type {TrToken[]} */ stack = [],
18
17
  lines = data.split('\n');
19
- let out = type === 'root' || type === 'ext-inner' && name === 'poem' ? '' : `\n${lines.shift()}`;
18
+ let out = type === 'root' || type === 'parameter-value' || type === 'ext-inner' && name === 'poem'
19
+ ? ''
20
+ : `\n${lines.shift()}`;
20
21
 
21
22
  /**
22
23
  * 向表格中插入纯文本
@@ -28,13 +29,13 @@ const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(
28
29
  out += str;
29
30
  return;
30
31
  }
31
- const /** @type {Token & {firstChild: AstText}} */ {lastChild} = top;
32
- if (lastChild.isPlain()) {
33
- lastChild.firstChild.appendData(str);
32
+ const /** @type {Token}} */ {lastChild} = top;
33
+ if (lastChild.constructor === Token) {
34
+ lastChild.setText(String(lastChild) + str);
34
35
  } else {
35
36
  const token = new Token(str, config, true, accum);
36
37
  token.type = 'table-inter';
37
- top.appendChild(token.setAttribute('stage', 3));
38
+ top.insertAt(token.setAttribute('stage', 3));
38
39
  }
39
40
  };
40
41
  for (const outLine of lines) {
@@ -48,6 +49,7 @@ const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(
48
49
  }
49
50
  const [, indent, moreSpaces, tableSyntax, attr] = matchesStart;
50
51
  if (indent) {
52
+ const DdToken = require('../src/nowiki/dd');
51
53
  new DdToken(indent, config, accum);
52
54
  }
53
55
  push(`\n${spaces}${indent && `\0${accum.length - 1}d\x7F`}${moreSpaces}\0${accum.length}b\x7F`, top);
@@ -82,7 +84,7 @@ const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(
82
84
  }
83
85
  const tr = new TrToken(`\n${spaces}${row}`, attr, config, accum);
84
86
  stack.push(top, tr);
85
- top.appendChild(tr);
87
+ top.insertAt(tr);
86
88
  } else {
87
89
  if (top.type === 'td') {
88
90
  top = stack.pop();
@@ -95,14 +97,14 @@ const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(
95
97
  lastSyntax = `\n${spaces}${cell}`;
96
98
  while (mt) {
97
99
  const td = new TdToken(lastSyntax, attr.slice(lastIndex, mt.index), config, accum);
98
- top.appendChild(td);
100
+ top.insertAt(td);
99
101
  ({lastIndex} = regex);
100
102
  [lastSyntax] = mt;
101
103
  mt = regex.exec(attr);
102
104
  }
103
105
  const td = new TdToken(lastSyntax, attr.slice(lastIndex), config, accum);
104
106
  stack.push(top, td);
105
- top.appendChild(td);
107
+ top.insertAt(td);
106
108
  }
107
109
  }
108
110
  return out.slice(1);
package/src/arg.js CHANGED
@@ -7,11 +7,16 @@ const {text, noWrap} = require('../util/string'),
7
7
 
8
8
  /**
9
9
  * `{{{}}}`包裹的参数
10
- * @classdesc `{childNodes: [AtomToken, Token, ...HiddenToken]}`
10
+ * @classdesc `{childNodes: [AtomToken, ?Token, ...HiddenToken]}`
11
11
  */
12
12
  class ArgToken extends Token {
13
13
  type = 'arg';
14
14
 
15
+ /** default */
16
+ get default() {
17
+ return this.childNodes[1]?.text() ?? false;
18
+ }
19
+
15
20
  /**
16
21
  * @param {string[]} parts 以'|'分隔的各部分
17
22
  * @param {accum} accum
@@ -25,39 +30,16 @@ class ArgToken extends Token {
25
30
  const token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
26
31
  'Stage-2': ':', '!HeadingToken': '',
27
32
  });
28
- this.appendChild(token);
33
+ super.insertAt(token);
29
34
  } else {
30
35
  const token = new Token(parts[i], config, true, accum);
31
36
  token.type = 'arg-default';
32
- this.appendChild(token.setAttribute('stage', 2));
37
+ super.insertAt(token.setAttribute('stage', 2));
33
38
  }
34
39
  }
35
40
  this.getAttribute('protectChildren')(0);
36
41
  }
37
42
 
38
- /** @override */
39
- cloneNode() {
40
- const [name, ...cloned] = this.cloneChildNodes();
41
- return Parser.run(() => {
42
- const token = new ArgToken([''], this.getAttribute('config'));
43
- token.firstChild.safeReplaceWith(name);
44
- token.append(...cloned);
45
- return token.afterBuild();
46
- });
47
- }
48
-
49
- /** @override */
50
- afterBuild() {
51
- this.setAttribute('name', this.firstChild.text().trim());
52
- const /** @type {AstListener} */ argListener = ({prevTarget}) => {
53
- if (prevTarget === this.firstChild) {
54
- this.setAttribute('name', prevTarget.text().trim());
55
- }
56
- };
57
- this.addEventListener(['remove', 'insert', 'replace', 'text'], argListener);
58
- return this;
59
- }
60
-
61
43
  /**
62
44
  * @override
63
45
  * @param {string} selector
@@ -81,6 +63,47 @@ class ArgToken extends Token {
81
63
  return super.print({pre: '{{{', post: '}}}', sep: '|'});
82
64
  }
83
65
 
66
+ /**
67
+ * @override
68
+ * @param {number} start 起始位置
69
+ * @returns {LintError[]}
70
+ */
71
+ lint(start = 0) {
72
+ const {childNodes: [argName, argDefault, ...rest]} = this,
73
+ errors = argName.lint(start + 3);
74
+ if (argDefault) {
75
+ errors.push(...argDefault.lint(start + 4 + String(argName).length));
76
+ }
77
+ if (rest.length > 0) {
78
+ const rect = this.getRootNode().posFromIndex(start);
79
+ errors.push(...rest.map(child => generateForChild(child, rect, '三重括号内的不可见部分')));
80
+ }
81
+ return errors;
82
+ }
83
+
84
+ /** @override */
85
+ cloneNode() {
86
+ const [name, ...cloned] = this.cloneChildNodes();
87
+ return Parser.run(() => {
88
+ const token = new ArgToken([''], this.getAttribute('config'));
89
+ token.firstChild.safeReplaceWith(name);
90
+ token.append(...cloned);
91
+ return token.afterBuild();
92
+ });
93
+ }
94
+
95
+ /** @override */
96
+ afterBuild() {
97
+ this.setAttribute('name', this.firstChild.text().trim());
98
+ const /** @type {AstListener} */ argListener = ({prevTarget}) => {
99
+ if (prevTarget === this.firstChild) {
100
+ this.setAttribute('name', prevTarget.text().trim());
101
+ }
102
+ };
103
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], argListener);
104
+ return this;
105
+ }
106
+
84
107
  /**
85
108
  * @override
86
109
  * @returns {string}
@@ -101,24 +124,6 @@ class ArgToken extends Token {
101
124
  });
102
125
  }
103
126
 
104
- /**
105
- * @override
106
- * @param {number} start 起始位置
107
- * @returns {LintError[]}
108
- */
109
- lint(start = 0) {
110
- const {childNodes: [argName, argDefault, ...rest]} = this,
111
- errors = argName.lint(start + 3);
112
- if (argDefault) {
113
- errors.push(...argDefault.lint(start + 4 + String(argName).length));
114
- }
115
- if (rest.length > 0) {
116
- const rect = this.getRootNode().posFromIndex(start);
117
- errors.push(...rest.map(child => generateForChild(child, rect, '三重括号内的不可见部分')));
118
- }
119
- return errors;
120
- }
121
-
122
127
  /**
123
128
  * 移除子节点,且在移除`arg-default`子节点时自动移除全部多余子节点
124
129
  * @param {number} i 移除位置
@@ -139,7 +144,7 @@ class ArgToken extends Token {
139
144
  */
140
145
  insertAt(token, i = this.childNodes.length) {
141
146
  const j = i < 0 ? i + this.childNodes.length : i;
142
- if (j > 1 && !Parser.running) {
147
+ if (j > 1) {
143
148
  throw new RangeError(`${this.constructor.name} 不可插入多余的子节点!`);
144
149
  }
145
150
  super.insertAt(token, i);
@@ -157,7 +162,7 @@ class ArgToken extends Token {
157
162
  setName(name) {
158
163
  name = String(name);
159
164
  const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
160
- {childNodes: {length}, firstChild: arg} = root;
165
+ {length, firstChild: arg} = root;
161
166
  if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 1) {
162
167
  throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
163
168
  }
@@ -174,7 +179,7 @@ class ArgToken extends Token {
174
179
  setDefault(value) {
175
180
  value = String(value);
176
181
  const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
177
- {childNodes: {length}, firstChild: arg} = root;
182
+ {length, firstChild: arg} = root;
178
183
  if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 2) {
179
184
  throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
180
185
  }
@@ -184,7 +189,7 @@ class ArgToken extends Token {
184
189
  if (oldDefault) {
185
190
  oldDefault.safeReplaceWith(lastChild);
186
191
  } else {
187
- this.appendChild(lastChild);
192
+ this.insertAt(lastChild);
188
193
  }
189
194
  }
190
195
  }
package/src/atom/index.js CHANGED
@@ -5,7 +5,7 @@ const Parser = require('../..'),
5
5
 
6
6
  /**
7
7
  * 不会被继续解析的plain Token
8
- * @classdesc `{childNodes: (AstText|Token)[]}`
8
+ * @classdesc `{childNodes: ...AstText|Token}`
9
9
  */
10
10
  class AtomToken extends Token {
11
11
  type = 'plain';
@@ -30,10 +30,12 @@ class AtomToken extends Token {
30
30
  cloneNode() {
31
31
  const cloned = this.cloneChildNodes(),
32
32
  config = this.getAttribute('config'),
33
- acceptable = this.getAttribute('acceptable'),
34
- token = Parser.run(() => new this.constructor(undefined, this.type, config, [], acceptable));
35
- token.append(...cloned);
36
- return token;
33
+ acceptable = this.getAttribute('acceptable');
34
+ return Parser.run(() => {
35
+ const token = new this.constructor(undefined, this.type, config, [], acceptable);
36
+ token.append(...cloned);
37
+ return token;
38
+ });
37
39
  }
38
40
  }
39
41