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/link/index.js CHANGED
@@ -62,12 +62,12 @@ class LinkToken extends Token {
62
62
 
63
63
  /** 链接显示文字 */
64
64
  get innerText() {
65
- if (this.type !== 'link') {
66
- return undefined;
65
+ if (this.type === 'link') {
66
+ return this.childNodes.length > 1
67
+ ? this.lastChild.text()
68
+ : this.firstChild.text().replace(/^\s*:/u, '');
67
69
  }
68
- return this.childNodes.length > 1
69
- ? this.lastElementChild.text()
70
- : this.firstElementChild.text().replace(/^\s*:/u, '');
70
+ return undefined;
71
71
  }
72
72
 
73
73
  /**
@@ -92,7 +92,7 @@ class LinkToken extends Token {
92
92
 
93
93
  /** 生成Title对象 */
94
94
  #getTitle() {
95
- return this.normalizeTitle(this.firstElementChild.text());
95
+ return this.normalizeTitle(this.firstChild.text());
96
96
  }
97
97
 
98
98
  /** @override */
@@ -102,7 +102,7 @@ class LinkToken extends Token {
102
102
  /** @type {this & {constructor: typeof LinkToken}} */
103
103
  const {constructor} = this,
104
104
  token = new constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
105
- token.firstElementChild.safeReplaceWith(link);
105
+ token.firstChild.safeReplaceWith(link);
106
106
  token.append(...linkText);
107
107
  return token.afterBuild();
108
108
  });
@@ -161,6 +161,11 @@ class LinkToken extends Token {
161
161
  return 1;
162
162
  }
163
163
 
164
+ /** @override */
165
+ print() {
166
+ return super.print(this.type === 'gallery-image' ? {sep: '|'} : {pre: '[[', post: ']]', sep: '|'});
167
+ }
168
+
164
169
  /** @override */
165
170
  text() {
166
171
  const str = super.text('|');
@@ -178,14 +183,14 @@ class LinkToken extends Token {
178
183
  link = `:${link}`;
179
184
  }
180
185
  const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
181
- {childNodes: {length}, firstElementChild} = root;
182
- if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childNodes.length !== 1) {
186
+ {childNodes: {length}, firstChild: wikiLink} = root,
187
+ {type, firstChild, childNodes: {length: linkLength}} = wikiLink;
188
+ if (length !== 1 || type !== this.type || linkLength !== 1) {
183
189
  const msgs = {link: '内链', file: '文件链接', category: '分类', 'gallery-image': '文件链接'};
184
190
  throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
185
191
  }
186
- const {firstChild} = firstElementChild;
187
- firstElementChild.destroy(true);
188
- this.firstElementChild.safeReplaceWith(firstChild);
192
+ wikiLink.destroy(true);
193
+ this.firstChild.safeReplaceWith(firstChild);
189
194
  }
190
195
 
191
196
  /**
@@ -206,15 +211,13 @@ class LinkToken extends Token {
206
211
  link = link.slice(1);
207
212
  }
208
213
  const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
209
- /** @type {Token & {firstElementChild: LinkToken}} */ {childNodes: {length}, firstElementChild} = root;
210
- if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childNodes.length !== 1
211
- || firstElementChild.interwiki !== lang.toLowerCase()
212
- ) {
214
+ /** @type {Token & {firstChild: LinkToken}} */ {childNodes: {length}, firstChild: wikiLink} = root,
215
+ {type, childNodes: {length: linkLength}, interwiki, firstChild} = wikiLink;
216
+ if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
213
217
  throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
214
218
  }
215
- const {firstChild} = firstElementChild;
216
- firstElementChild.destroy(true);
217
- this.firstElementChild.safeReplaceWith(firstChild);
219
+ wikiLink.destroy(true);
220
+ this.firstChild.safeReplaceWith(firstChild);
218
221
  }
219
222
 
220
223
  /**
@@ -228,16 +231,16 @@ class LinkToken extends Token {
228
231
  const include = this.getAttribute('include'),
229
232
  config = this.getAttribute('config'),
230
233
  root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
231
- {childNodes: {length}, firstElementChild} = root;
232
- if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childNodes.length !== 1) {
234
+ {childNodes: {length}, firstChild: wikiLink} = root,
235
+ {type, childNodes: {length: linkLength}, firstChild} = wikiLink;
236
+ if (length !== 1 || type !== 'link' || linkLength !== 1) {
233
237
  throw new SyntaxError(`非法的 fragment:${fragment}`);
234
238
  }
235
239
  if (page) {
236
240
  Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
237
241
  }
238
- const {firstChild} = firstElementChild;
239
- firstElementChild.destroy(true);
240
- this.firstElementChild.safeReplaceWith(firstChild);
242
+ wikiLink.destroy(true);
243
+ this.firstChild.safeReplaceWith(firstChild);
241
244
  }
242
245
 
243
246
  /**
@@ -268,25 +271,25 @@ class LinkToken extends Token {
268
271
  */
269
272
  setLinkText(linkText = '') {
270
273
  linkText = String(linkText);
271
- let lastElementChild;
274
+ let lastChild;
272
275
  const config = this.getAttribute('config');
273
276
  if (linkText) {
274
277
  const root = Parser.parse(`[[${
275
278
  this.type === 'category' ? 'Category:' : ''
276
279
  }L|${linkText}]]`, this.getAttribute('include'), 6, config),
277
- {childNodes: {length}, firstElementChild} = root;
278
- if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childNodes.length !== 2) {
280
+ {childNodes: {length}, firstChild: wikiLink} = root;
281
+ if (length !== 1 || wikiLink.type !== this.type || wikiLink.childNodes.length !== 2) {
279
282
  throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
280
283
  }
281
- ({lastElementChild} = firstElementChild);
284
+ ({lastChild} = wikiLink);
282
285
  } else {
283
- lastElementChild = Parser.run(() => new Token('', config));
284
- lastElementChild.setAttribute('stage', 7).type = 'link-text';
286
+ lastChild = Parser.run(() => new Token('', config));
287
+ lastChild.setAttribute('stage', 7).type = 'link-text';
285
288
  }
286
289
  if (this.childNodes.length === 1) {
287
- this.appendChild(lastElementChild);
290
+ this.appendChild(lastChild);
288
291
  } else {
289
- this.lastElementChild.safeReplaceWith(lastElementChild);
292
+ this.lastChild.safeReplaceWith(lastChild);
290
293
  }
291
294
  }
292
295
 
@@ -295,7 +298,7 @@ class LinkToken extends Token {
295
298
  * @throws `Error` 带有"#"或"%"时不可用
296
299
  */
297
300
  pipeTrick() {
298
- const linkText = this.firstElementChild.text();
301
+ const linkText = this.firstChild.text();
299
302
  if (linkText.includes('#') || linkText.includes('%')) {
300
303
  throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
301
304
  }
package/src/magicLink.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const Parser = require('..'),
3
+ const {generateForChild} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ AstText = require('../lib/text'),
4
6
  Token = require('.');
5
7
 
6
8
  /**
@@ -48,6 +50,32 @@ class MagicLinkToken extends Token {
48
50
  this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'iu');
49
51
  }
50
52
 
53
+ /**
54
+ * @override
55
+ * @param {number} start 起始位置
56
+ */
57
+ lint(start = 0) {
58
+ const errors = super.lint(start);
59
+ let /** @type {{top: number, left: number}} */ rect;
60
+ for (const child of this.childNodes) {
61
+ const str = String(child);
62
+ if (child.type !== 'text' || !/[,;。:!?()【】]/u.test(str)) {
63
+ continue;
64
+ }
65
+ rect ||= this.getRootNode().posFromIndex(start);
66
+ const refError = generateForChild(child, rect, 'URL中的全角标点', 'warning');
67
+ errors.push(...[...str.matchAll(/[,;。:!?()【】]/gu)].map(({index}) => {
68
+ const lines = str.slice(0, index).split('\n'),
69
+ {length: top} = lines,
70
+ {length: left} = lines.at(-1),
71
+ startLine = refError.startLine + top - 1,
72
+ startCol = top > 1 ? left : refError.startCol + left;
73
+ return {...refError, startLine, endLine: startLine, startCol, endCol: startCol + 1};
74
+ }));
75
+ }
76
+ return errors;
77
+ }
78
+
51
79
  /** @override */
52
80
  afterBuild() {
53
81
  const ParameterToken = require('./parameter');
@@ -96,11 +124,11 @@ class MagicLinkToken extends Token {
96
124
  setTarget(url) {
97
125
  url = String(url);
98
126
  const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
99
- {childNodes: {length}, firstElementChild} = root;
100
- if (length !== 1 || firstElementChild?.type !== 'free-ext-link') {
127
+ {childNodes: {length}, firstChild: freeExtLink} = root;
128
+ if (length !== 1 || freeExtLink.type !== 'free-ext-link') {
101
129
  throw new SyntaxError(`非法的自由外链目标:${url}`);
102
130
  }
103
- this.replaceChildren(...firstElementChild.childNodes);
131
+ this.replaceChildren(...freeExtLink.childNodes);
104
132
  }
105
133
  }
106
134
 
@@ -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
  NowikiToken = require('.');
6
7
 
@@ -51,6 +52,19 @@ class CommentToken extends hidden(NowikiToken) {
51
52
  getPadding() {
52
53
  return 4;
53
54
  }
55
+
56
+ /** @override */
57
+ print() {
58
+ return super.print({pre: '<!--', post: this.closed ? '-->' : ''});
59
+ }
60
+
61
+ /**
62
+ * @override
63
+ * @param {number} start 起始位置
64
+ */
65
+ lint(start = 0) {
66
+ return this.closed ? [] : [generateForSelf(this, this.getRootNode().posFromIndex(start), '未闭合的HTML注释')];
67
+ }
54
68
  }
55
69
 
56
70
  Parser.classes.CommentToken = __filename;
@@ -38,6 +38,11 @@ class DoubleUnderscoreToken extends hidden(NowikiToken) {
38
38
  return 2;
39
39
  }
40
40
 
41
+ /** @override */
42
+ print() {
43
+ return super.print({pre: '__', post: '__'});
44
+ }
45
+
41
46
  /**
42
47
  * @override
43
48
  * @throws `Error` 禁止修改
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const Parser = require('../..'),
3
+ const {generateForSelf} = require('../../util/lint'),
4
+ Parser = require('../..'),
5
+ AstText = require('../../lib/text'),
4
6
  NowikiToken = require('.');
5
7
 
6
8
  /**
@@ -19,6 +21,31 @@ class QuoteToken extends NowikiToken {
19
21
  this.setAttribute('name', String(n));
20
22
  }
21
23
 
24
+ /**
25
+ * @override
26
+ * @this {AstText}
27
+ * @param {number} start 起始位置
28
+ */
29
+ lint(start = 0) {
30
+ const {previousSibling, nextSibling} = this,
31
+ message = `孤立的"'"`,
32
+ /** @type {LintError[]} */ errors = [];
33
+ let /** @type {LintError} */ refError;
34
+ if (previousSibling?.type === 'text' && previousSibling.data.at(-1) === "'") {
35
+ refError = generateForSelf(this, this.getRootNode().posFromIndex(start), '');
36
+ const {startLine, startCol} = refError,
37
+ [{length}] = previousSibling.data.match(/(?<!')'+$/u);
38
+ errors.push({message, startLine, startCol: startCol - length, endLine: startLine, endCol: startCol});
39
+ }
40
+ if (nextSibling?.type === 'text' && nextSibling.data[0] === "'") {
41
+ refError ||= generateForSelf(this, this.getRootNode().posFromIndex(start), '');
42
+ const {endLine, endCol} = refError,
43
+ [{length}] = nextSibling.data.match(/^'+/u);
44
+ errors.push({message, startLine: endLine, startCol: endCol, endLine, endCol: endCol + length});
45
+ }
46
+ return errors;
47
+ }
48
+
22
49
  /**
23
50
  * @override
24
51
  * @param {string} str 新文本
@@ -44,6 +44,11 @@ class OnlyincludeToken extends Token {
44
44
  return 13;
45
45
  }
46
46
 
47
+ /** @override */
48
+ print() {
49
+ return super.print({pre: '&lt;onlyinclude&gt;', post: '&lt;/onlyinclude&gt;'});
50
+ }
51
+
47
52
  /** @override */
48
53
  isPlain() {
49
54
  return true;
package/src/parameter.js CHANGED
@@ -14,7 +14,7 @@ class ParameterToken extends fixedToken(Token) {
14
14
 
15
15
  /** 是否是匿名参数 */
16
16
  get anon() {
17
- return this.firstElementChild.childNodes.length === 0;
17
+ return this.firstChild.childNodes.length === 0;
18
18
  }
19
19
 
20
20
  /** getValue()的getter */
@@ -48,8 +48,8 @@ class ParameterToken extends fixedToken(Token) {
48
48
  config = this.getAttribute('config');
49
49
  return Parser.run(() => {
50
50
  const token = new ParameterToken(this.anon ? Number(this.name) : undefined, undefined, config);
51
- token.firstElementChild.safeReplaceWith(key);
52
- token.lastElementChild.safeReplaceWith(value);
51
+ token.firstChild.safeReplaceWith(key);
52
+ token.lastChild.safeReplaceWith(value);
53
53
  return token.afterBuild();
54
54
  });
55
55
  }
@@ -57,7 +57,7 @@ class ParameterToken extends fixedToken(Token) {
57
57
  /** @override */
58
58
  afterBuild() {
59
59
  if (!this.anon) {
60
- const name = this.firstElementChild.text().trim(),
60
+ const name = this.firstChild.text().trim(),
61
61
  {parentNode} = this;
62
62
  this.setAttribute('name', name);
63
63
  if (parentNode && parentNode instanceof require('./transclude')) {
@@ -67,9 +67,9 @@ class ParameterToken extends fixedToken(Token) {
67
67
  }
68
68
  const /** @type {AstListener} */ parameterListener = ({prevTarget}, data) => {
69
69
  if (!this.anon) { // 匿名参数不管怎么变动还是匿名
70
- const {firstElementChild, name} = this;
71
- if (prevTarget === firstElementChild) {
72
- const newKey = firstElementChild.text().trim();
70
+ const {firstChild, name} = this;
71
+ if (prevTarget === firstChild) {
72
+ const newKey = firstChild.text().trim();
73
73
  data.oldKey = name;
74
74
  data.newKey = newKey;
75
75
  this.setAttribute('name', newKey);
@@ -87,7 +87,7 @@ class ParameterToken extends fixedToken(Token) {
87
87
  */
88
88
  toString(selector) {
89
89
  return this.anon && !(selector && this.matches(selector))
90
- ? this.lastElementChild.toString(selector)
90
+ ? this.lastChild.toString(selector)
91
91
  : super.toString(selector, '=');
92
92
  }
93
93
 
@@ -96,12 +96,17 @@ class ParameterToken extends fixedToken(Token) {
96
96
  return this.anon ? 0 : 1;
97
97
  }
98
98
 
99
+ /** @override */
100
+ print() {
101
+ return super.print({sep: this.anon ? '' : '='});
102
+ }
103
+
99
104
  /**
100
105
  * @override
101
106
  * @returns {string}
102
107
  */
103
108
  text() {
104
- return this.anon ? this.lastElementChild.text() : super.text('=');
109
+ return this.anon ? this.lastChild.text() : super.text('=');
105
110
  }
106
111
 
107
112
  /**
@@ -114,37 +119,46 @@ class ParameterToken extends fixedToken(Token) {
114
119
  return this.replaceWith(token);
115
120
  }
116
121
 
117
- /** 获取参数值 */
122
+ /**
123
+ * 获取参数值
124
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
125
+ */
118
126
  getValue() {
119
- const value = this.lastElementChild.text();
120
- return this.anon && this.parentNode?.matches('template, magic-word#invoke') ? value : value.trim();
127
+ const TranscludeToken = require('./transclude');
128
+ const value = this.lastChild.text();
129
+ return this.anon && this.parentNode?.isTemplate() ? value : value.trim();
121
130
  }
122
131
 
123
132
  /**
124
133
  * 设置参数值
134
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
125
135
  * @param {string} value 参数值
126
136
  * @throws `SyntaxError` 非法的模板参数
127
137
  */
128
138
  setValue(value) {
129
139
  value = String(value);
130
- const templateLike = this.parentNode?.matches('template, magic-word#invoke'),
140
+ const TranscludeToken = require('./transclude');
141
+ const templateLike = this.parentNode?.isTemplate(),
131
142
  wikitext = `{{${templateLike ? ':T|' : 'lc:'}${this.anon ? '' : '1='}${value}}}`,
132
143
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
133
- {childNodes: {length}, firstElementChild} = root,
134
- /** @type {ParameterToken} */ lastElementChild = firstElementChild?.lastElementChild;
135
- if (length !== 1 || !firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
136
- || firstElementChild.childNodes.length !== 2
137
- || lastElementChild.anon !== this.anon || lastElementChild.name !== '1'
144
+ {childNodes: {length}, firstChild: transclude} = root,
145
+ /** @type {Token & {lastChild: ParameterToken}} */
146
+ {lastChild: parameter, type, name, childNodes: {length: transcludeLength}} = transclude,
147
+ targetType = templateLike ? 'template' : 'magic-word',
148
+ targetName = templateLike ? 'T' : 'lc';
149
+ if (length !== 1 || type !== targetType || name !== targetName || transcludeLength !== 2
150
+ || parameter.anon !== this.anon || parameter.name !== '1'
138
151
  ) {
139
152
  throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
140
153
  }
141
- const {lastChild} = lastElementChild;
142
- lastElementChild.destroy(true);
143
- this.lastElementChild.safeReplaceWith(lastChild);
154
+ const {lastChild} = parameter;
155
+ parameter.destroy(true);
156
+ this.lastChild.safeReplaceWith(lastChild);
144
157
  }
145
158
 
146
159
  /**
147
160
  * 修改参数名
161
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
148
162
  * @param {string} key 新参数名
149
163
  * @param {boolean} force 是否无视冲突命名
150
164
  * @throws `Error` 仅用于模板参数
@@ -155,31 +169,30 @@ class ParameterToken extends fixedToken(Token) {
155
169
  if (typeof key !== 'string') {
156
170
  this.typeError('rename', 'String');
157
171
  }
172
+ const TranscludeToken = require('./transclude');
158
173
  const {parentNode} = this;
159
174
  // 必须检测是否是TranscludeToken
160
- if (!parentNode || !parentNode.matches('template, magic-word#invoke')
161
- || !(parentNode instanceof require('./transclude'))
162
- ) {
175
+ if (!parentNode?.isTemplate() || !(parentNode instanceof require('./transclude'))) {
163
176
  throw new Error(`${this.constructor.name}.rename 方法仅用于模板参数!`);
164
177
  }
165
178
  const root = Parser.parse(`{{:T|${key}=}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
166
- {childNodes: {length}, firstElementChild} = root;
167
- if (length !== 1 || !firstElementChild?.matches('template#T') || firstElementChild.childNodes.length !== 2) {
179
+ {childNodes: {length}, firstChild: template} = root,
180
+ {type, name, lastChild: parameter, childNodes: {length: templateLength}} = template;
181
+ if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2) {
168
182
  throw new SyntaxError(`非法的模板参数名:${key}`);
169
183
  }
170
- const {lastElementChild} = firstElementChild,
171
- {name, firstChild} = lastElementChild;
172
- if (this.name === name) {
173
- Parser.warn('未改变实际参数名', name);
174
- } else if (parentNode.hasArg(name)) {
184
+ const {name: parameterName, firstChild} = parameter;
185
+ if (this.name === parameterName) {
186
+ Parser.warn('未改变实际参数名', parameterName);
187
+ } else if (parentNode.hasArg(parameterName)) {
175
188
  if (force) {
176
- Parser.warn('参数更名造成重复参数', name);
189
+ Parser.warn('参数更名造成重复参数', parameterName);
177
190
  } else {
178
- throw new RangeError(`参数更名造成重复参数:${name}`);
191
+ throw new RangeError(`参数更名造成重复参数:${parameterName}`);
179
192
  }
180
193
  }
181
- lastElementChild.destroy(true);
182
- this.firstElementChild.safeReplaceWith(firstChild);
194
+ parameter.destroy(true);
195
+ this.firstChild.safeReplaceWith(firstChild);
183
196
  }
184
197
  }
185
198
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const assert = require('assert/strict'),
4
4
  {noWrap} = require('../../util/string'),
5
+ {generateForChild} = require('../../util/lint'),
5
6
  Parser = require('../..'),
6
7
  Token = require('..'),
7
8
  TrToken = require('./tr'),
@@ -123,7 +124,7 @@ class TableToken extends TrToken {
123
124
 
124
125
  /** 表格是否闭合 */
125
126
  get closed() {
126
- return this.lastElementChild.type === 'table-syntax';
127
+ return this.lastChild.type === 'table-syntax';
127
128
  }
128
129
 
129
130
  set closed(closed) {
@@ -144,6 +145,18 @@ class TableToken extends TrToken {
144
145
  });
145
146
  }
146
147
 
148
+ /**
149
+ * @override
150
+ * @param {number} start 起始位置
151
+ */
152
+ lint(start = 0) {
153
+ const errors = super.lint(start);
154
+ if (!this.closed) {
155
+ errors.push(generateForChild(this.firstChild, this.getRootNode().posFromIndex(start), '未闭合的表格'));
156
+ }
157
+ return errors;
158
+ }
159
+
147
160
  /**
148
161
  * @override
149
162
  * @template {TrToken|SyntaxToken} T
@@ -154,7 +167,7 @@ class TableToken extends TrToken {
154
167
  * @throws `SyntaxError` 表格的闭合部分非法
155
168
  */
156
169
  insertAt(token, i = this.childNodes.length) {
157
- const previous = this.children.at(i - 1);
170
+ const previous = this.childNodes.at(i - 1);
158
171
  if (token.type === 'td' && previous.type === 'tr') {
159
172
  Parser.warn('改为将单元格插入当前行。');
160
173
  return previous.appendChild(token);
@@ -177,11 +190,11 @@ class TableToken extends TrToken {
177
190
  const config = this.getAttribute('config'),
178
191
  accum = this.getAttribute('accum'),
179
192
  inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
180
- {lastElementChild} = this;
193
+ {lastChild} = this;
181
194
  if (!halfParsed && !closingPattern.test(inner.text())) {
182
195
  throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
183
- } else if (lastElementChild instanceof SyntaxToken) {
184
- lastElementChild.replaceChildren(...inner.childNodes);
196
+ } else if (lastChild instanceof SyntaxToken) {
197
+ lastChild.replaceChildren(...inner.childNodes);
185
198
  } else {
186
199
  this.appendChild(Parser.run(() => {
187
200
  const token = new SyntaxToken(syntax, closingPattern, 'table-syntax', config, accum, {
@@ -202,7 +215,7 @@ class TableToken extends TrToken {
202
215
  */
203
216
  getRowCount() {
204
217
  return super.getRowCount()
205
- + this.children.filter(child => child.type === 'tr' && child.getRowCount()).length;
218
+ + this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()).length;
206
219
  }
207
220
 
208
221
  /** @override */
@@ -241,7 +254,7 @@ class TableToken extends TrToken {
241
254
  } else if (isRow) {
242
255
  n--;
243
256
  }
244
- for (const child of this.children.slice(2)) {
257
+ for (const child of this.childNodes.slice(2)) {
245
258
  if (child.type === 'tr' && child.getRowCount()) {
246
259
  n--;
247
260
  if (n < 0) {
@@ -262,7 +275,7 @@ class TableToken extends TrToken {
262
275
  getAllRows() {
263
276
  return [
264
277
  ...super.getRowCount() ? [this] : [],
265
- ...this.children.filter(child => child.type === 'tr' && child.getRowCount()),
278
+ ...this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()),
266
279
  ];
267
280
  }
268
281
 
@@ -295,7 +308,7 @@ class TableToken extends TrToken {
295
308
  let j = 0,
296
309
  k = 0,
297
310
  last;
298
- for (const cell of rows[i].children.slice(2)) {
311
+ for (const cell of rows[i].childNodes.slice(2)) {
299
312
  if (cell instanceof TdToken) {
300
313
  if (cell.isIndependent()) {
301
314
  last = cell.subtype !== 'caption';
@@ -365,12 +378,12 @@ class TableToken extends TrToken {
365
378
  coords = rowLayout?.[x];
366
379
  if (coords) {
367
380
  return {...coords, start: coords.row === y && rowLayout[x - 1] !== coords};
368
- } else if (!rowLayout && y === 0) {
369
- return {row: 0, column: 0, start: true};
381
+ } else if (rowLayout || y > 0) {
382
+ return x === rowLayout?.length
383
+ ? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
384
+ : undefined;
370
385
  }
371
- return x === rowLayout?.length
372
- ? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
373
- : undefined;
386
+ return {row: 0, column: 0, start: true};
374
387
  }
375
388
 
376
389
  /**
@@ -474,7 +487,7 @@ class TableToken extends TrToken {
474
487
  if (coords.column === undefined) {
475
488
  const {x, y} = coords;
476
489
  coords = this.toRawCoords(coords);
477
- if (!coords?.start) { // eslint-disable-line unicorn/consistent-destructuring
490
+ if (!coords?.start) {
478
491
  throw new RangeError(`指定的坐标不是单元格起始点:(${x}, ${y})`);
479
492
  }
480
493
  }
@@ -490,10 +503,10 @@ class TableToken extends TrToken {
490
503
  */
491
504
  #prependTableRow() {
492
505
  const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
493
- {children} = this,
494
- [,, plain] = children,
506
+ {childNodes} = this,
507
+ [,, plain] = childNodes,
495
508
  start = plain?.isPlain() ? 3 : 2,
496
- /** @type {TdToken[]} */ tdChildren = children.slice(start),
509
+ /** @type {TdToken[]} */ tdChildren = childNodes.slice(start),
497
510
  index = tdChildren.findIndex(({type}) => type !== 'td');
498
511
  this.insertAt(row, index === -1 ? -1 : index + start);
499
512
  Parser.run(() => {
@@ -631,11 +644,11 @@ class TableToken extends TrToken {
631
644
  */
632
645
  removeTableCol(x) {
633
646
  for (const [token, start] of this.getFullCol(x)) {
634
- const {colspan, lastElementChild} = token;
647
+ const {colspan, lastChild} = token;
635
648
  if (colspan > 1) {
636
649
  token.colspan = colspan - 1;
637
650
  if (start) {
638
- lastElementChild.replaceChildren();
651
+ lastChild.replaceChildren();
639
652
  }
640
653
  } else {
641
654
  token.remove();
@@ -703,7 +716,7 @@ class TableToken extends TrToken {
703
716
  if (x !== undefined) {
704
717
  coords = this.toRawCoords(coords);
705
718
  }
706
- if (coords.start === false || x === undefined) { // eslint-disable-line unicorn/consistent-destructuring
719
+ if (coords.start === false || x === undefined) {
707
720
  ({x, y} = this.toRenderedCoords(coords));
708
721
  }
709
722
  const splitting = {rowspan: 1, colspan: 1};
@@ -850,7 +863,7 @@ class TableToken extends TrToken {
850
863
  const layout = this.getLayout(),
851
864
  afterToken = this.getNthRow(after),
852
865
  /** @type {TdToken[]} */
853
- cells = afterToken.children.filter(child => child instanceof TdToken && child.subtype !== 'caption');
866
+ cells = afterToken.childNodes.filter(child => child instanceof TdToken && child.subtype !== 'caption');
854
867
 
855
868
  /**
856
869
  * @type {(i: number, oneRow?: boolean) => number[]}
@@ -926,7 +939,7 @@ class TableToken extends TrToken {
926
939
  if (start) {
927
940
  const original = token;
928
941
  token = token.cloneNode();
929
- original.lastElementChild.replaceChildren();
942
+ original.lastChild.replaceChildren();
930
943
  token.colspan = 1;
931
944
  }
932
945
  }
@@ -934,7 +947,7 @@ class TableToken extends TrToken {
934
947
  const col = rowLayout.slice(reference + Number(after)).find(({row}) => row === i)?.column;
935
948
  rowToken.insertBefore(
936
949
  token, col === undefined && rowToken.type === 'table'
937
- ? rowToken.children.slice(2).find(isRowEnd)
950
+ ? rowToken.childNodes.slice(2).find(isRowEnd)
938
951
  : col !== undefined && rowToken.getNthCol(col),
939
952
  );
940
953
  }