wikiparser-node 0.3.1 → 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 (80) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +13 -17
  3. package/config/llwiki.json +11 -79
  4. package/config/moegirl.json +7 -1
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +130 -97
  7. package/lib/element.js +410 -518
  8. package/lib/node.js +493 -115
  9. package/lib/ranges.js +27 -19
  10. package/lib/text.js +175 -0
  11. package/lib/title.js +14 -6
  12. package/mixin/attributeParent.js +70 -24
  13. package/mixin/fixedToken.js +18 -10
  14. package/mixin/hidden.js +6 -4
  15. package/mixin/sol.js +39 -12
  16. package/package.json +17 -4
  17. package/parser/brackets.js +18 -18
  18. package/parser/commentAndExt.js +16 -14
  19. package/parser/converter.js +14 -13
  20. package/parser/externalLinks.js +12 -11
  21. package/parser/hrAndDoubleUnderscore.js +24 -14
  22. package/parser/html.js +8 -7
  23. package/parser/links.js +13 -13
  24. package/parser/list.js +12 -11
  25. package/parser/magicLinks.js +11 -10
  26. package/parser/quotes.js +6 -5
  27. package/parser/selector.js +175 -0
  28. package/parser/table.js +31 -24
  29. package/src/arg.js +91 -43
  30. package/src/atom/hidden.js +5 -2
  31. package/src/atom/index.js +17 -9
  32. package/src/attribute.js +210 -101
  33. package/src/converter.js +78 -43
  34. package/src/converterFlags.js +104 -45
  35. package/src/converterRule.js +136 -78
  36. package/src/extLink.js +81 -27
  37. package/src/gallery.js +63 -20
  38. package/src/heading.js +58 -20
  39. package/src/html.js +138 -48
  40. package/src/imageParameter.js +93 -58
  41. package/src/index.js +314 -186
  42. package/src/link/category.js +22 -54
  43. package/src/link/file.js +83 -32
  44. package/src/link/galleryImage.js +21 -7
  45. package/src/link/index.js +170 -81
  46. package/src/magicLink.js +64 -14
  47. package/src/nowiki/comment.js +36 -10
  48. package/src/nowiki/dd.js +37 -22
  49. package/src/nowiki/doubleUnderscore.js +21 -7
  50. package/src/nowiki/hr.js +11 -7
  51. package/src/nowiki/index.js +16 -9
  52. package/src/nowiki/list.js +2 -2
  53. package/src/nowiki/noinclude.js +8 -4
  54. package/src/nowiki/quote.js +38 -7
  55. package/src/onlyinclude.js +24 -7
  56. package/src/parameter.js +102 -62
  57. package/src/syntax.js +23 -20
  58. package/src/table/index.js +282 -174
  59. package/src/table/td.js +112 -61
  60. package/src/table/tr.js +135 -74
  61. package/src/tagPair/ext.js +30 -23
  62. package/src/tagPair/include.js +26 -11
  63. package/src/tagPair/index.js +72 -29
  64. package/src/transclude.js +235 -127
  65. package/tool/index.js +42 -32
  66. package/util/debug.js +21 -18
  67. package/util/diff.js +76 -0
  68. package/util/lint.js +40 -0
  69. package/util/string.js +56 -26
  70. package/.eslintrc.json +0 -319
  71. package/errors/README +0 -1
  72. package/jsconfig.json +0 -7
  73. package/printed/README +0 -1
  74. package/typings/element.d.ts +0 -28
  75. package/typings/index.d.ts +0 -52
  76. package/typings/node.d.ts +0 -23
  77. package/typings/parser.d.ts +0 -9
  78. package/typings/table.d.ts +0 -14
  79. package/typings/token.d.ts +0 -22
  80. package/typings/tool.d.ts +0 -10
package/src/link/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const Title = require('../../lib/title'), // eslint-disable-line no-unused-vars
4
- {text, noWrap} = require('../../util/string'),
3
+ const Title = require('../../lib/title'),
4
+ {noWrap} = require('../../util/string'),
5
5
  {undo} = require('../../util/debug'),
6
- /** @type {Parser} */ Parser = require('../..'),
6
+ Parser = require('../..'),
7
+ AstText = require('../../lib/text'),
7
8
  Token = require('..');
8
9
 
9
10
  /**
@@ -12,14 +13,67 @@ const Title = require('../../lib/title'), // eslint-disable-line no-unused-vars
12
13
  */
13
14
  class LinkToken extends Token {
14
15
  type = 'link';
15
- selfLink;
16
- fragment;
17
- interwiki;
16
+
17
+ /** 完整链接,和FileToken保持一致 */
18
+ get link() {
19
+ return String(this.#getTitle());
20
+ }
21
+
22
+ set link(link) {
23
+ this.setTarget(link);
24
+ }
25
+
26
+ /** 是否链接到自身 */
27
+ get selfLink() {
28
+ return !this.#getTitle().title;
29
+ }
30
+
31
+ set selfLink(selfLink) {
32
+ if (selfLink === true) {
33
+ this.asSelfLink();
34
+ }
35
+ }
36
+
37
+ /** fragment */
38
+ get fragment() {
39
+ return this.#getTitle().fragment;
40
+ }
41
+
42
+ set fragment(fragment) {
43
+ this.setFragment(fragment);
44
+ }
45
+
46
+ /** interwiki */
47
+ get interwiki() {
48
+ return this.#getTitle().interwiki;
49
+ }
50
+
51
+ set interwiki(interwiki) {
52
+ if (typeof interwiki !== 'string') {
53
+ this.typeError('set interwiki', 'String');
54
+ }
55
+ const {prefix, main, fragment} = this.#getTitle(),
56
+ link = `${interwiki}:${prefix}${main}${fragment && '#'}${fragment}`;
57
+ if (interwiki && !this.isInterwiki(link)) {
58
+ throw new RangeError(`${interwiki} 不是合法的跨维基前缀!`);
59
+ }
60
+ this.setTarget(link);
61
+ }
62
+
63
+ /** 链接显示文字 */
64
+ get innerText() {
65
+ if (this.type === 'link') {
66
+ return this.childNodes.length > 1
67
+ ? this.lastChild.text()
68
+ : this.firstChild.text().replace(/^\s*:/u, '');
69
+ }
70
+ return undefined;
71
+ }
18
72
 
19
73
  /**
20
- * @param {string} link
21
- * @param {string|undefined} linkText
22
- * @param {Title} title
74
+ * @param {string} link 链接标题
75
+ * @param {string|undefined} linkText 链接显示文字
76
+ * @param {Title} title 链接标题对象
23
77
  * @param {accum} accum
24
78
  */
25
79
  constructor(link, linkText, title, config = Parser.getConfig(), accum = []) {
@@ -33,149 +87,175 @@ class LinkToken extends Token {
33
87
  inner.type = 'link-text';
34
88
  this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
35
89
  }
36
- this.selfLink = !title.title;
37
- this.fragment = title.fragment;
38
- this.interwiki = title.interwiki;
39
- this.setAttribute('name', title.title).seal(['selfLink', 'fragment', 'interwiki']).protectChildren(0);
90
+ this.setAttribute('name', title.title).getAttribute('protectChildren')(0);
40
91
  }
41
92
 
93
+ /** 生成Title对象 */
94
+ #getTitle() {
95
+ return this.normalizeTitle(this.firstChild.text());
96
+ }
97
+
98
+ /** @override */
42
99
  cloneNode() {
43
- const [link, ...linkText] = this.cloneChildren();
100
+ const [link, ...linkText] = this.cloneChildNodes();
44
101
  return Parser.run(() => {
45
- const /** @type {typeof LinkToken} */ Constructor = this.constructor,
46
- token = new Constructor('', undefined, {
47
- title: this.name, interwiki: this.interwiki, fragment: this.fragment,
48
- }, this.getAttribute('config'));
49
- token.firstElementChild.safeReplaceWith(link);
102
+ /** @type {this & {constructor: typeof LinkToken}} */
103
+ const {constructor} = this,
104
+ token = new constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
105
+ token.firstChild.safeReplaceWith(link);
50
106
  token.append(...linkText);
51
107
  return token.afterBuild();
52
108
  });
53
109
  }
54
110
 
111
+ /**
112
+ * @override
113
+ * @throws `Error` 非法的内链目标
114
+ * @throws `Error` 不可更改命名空间
115
+ */
55
116
  afterBuild() {
56
- if (this.name.includes('\0')) {
57
- this.setAttribute('name', text(this.buildFromStr(this.name)));
58
- }
59
- if (this.fragment.includes('\0')) {
60
- this.setAttribute('fragment', text(this.buildFromStr(this.fragment)));
61
- }
62
- const that = this;
63
117
  const /** @type {AstListener} */ linkListener = (e, data) => {
64
118
  const {prevTarget} = e;
65
119
  if (prevTarget?.type === 'link-target') {
66
120
  const name = prevTarget.text(),
67
- {title, interwiki, fragment, ns, valid} = that.normalizeTitle(name);
121
+ {title, interwiki, ns, valid} = this.normalizeTitle(name);
68
122
  if (!valid) {
69
123
  undo(e, data);
70
124
  throw new Error(`非法的内链目标:${name}`);
71
- } else if (that.type === 'category' && (interwiki || ns !== 14)
72
- || that.type === 'file' && (interwiki || ns !== 6)
125
+ } else if (this.type === 'category' && (interwiki || ns !== 14)
126
+ || this.type === 'file' && (interwiki || ns !== 6)
73
127
  ) {
74
128
  undo(e, data);
75
- throw new Error(`${that.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
76
- } else if (that.type === 'link' && !interwiki && [6, 14].includes(ns) && !name.trim().startsWith(':')) {
77
- const {firstChild} = prevTarget;
78
- if (typeof firstChild === 'string') {
79
- prevTarget.setText(`:${firstChild}`);
129
+ throw new Error(`${this.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
130
+ } else if (this.type === 'link' && !interwiki && (ns === 6 || ns === 14) && name.trim()[0] !== ':') {
131
+ const /** @type {{firstChild: AstText}} */ {firstChild} = prevTarget;
132
+ if (firstChild.type === 'text') {
133
+ firstChild.insertData(0, ':');
80
134
  } else {
81
135
  prevTarget.prepend(':');
82
136
  }
83
137
  }
84
- that.setAttribute('selfLink', !title).setAttribute('interwiki', interwiki)
85
- .setAttribute('name', title).setAttribute('fragment', fragment);
138
+ this.setAttribute('name', title);
86
139
  }
87
140
  };
88
141
  this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
89
142
  return this;
90
143
  }
91
144
 
92
- toString() {
93
- const str = super.toString('|');
94
- return this.type === 'gallery-image' ? str : `[[${str}]]`;
145
+ /**
146
+ * @override
147
+ * @param {string} selector
148
+ */
149
+ toString(selector) {
150
+ const str = super.toString(selector, '|');
151
+ return this.type === 'gallery-image' || selector && this.matches(selector) ? str : `[[${str}]]`;
95
152
  }
96
153
 
154
+ /** @override */
97
155
  getPadding() {
98
156
  return 2;
99
157
  }
100
158
 
159
+ /** @override */
101
160
  getGaps() {
102
161
  return 1;
103
162
  }
104
163
 
164
+ /** @override */
165
+ print() {
166
+ return super.print(this.type === 'gallery-image' ? {sep: '|'} : {pre: '[[', post: ']]', sep: '|'});
167
+ }
168
+
169
+ /** @override */
105
170
  text() {
106
171
  const str = super.text('|');
107
172
  return this.type === 'gallery-image' ? str : `[[${str}]]`;
108
173
  }
109
174
 
110
- /** @param {string} link */
175
+ /**
176
+ * 设置链接目标
177
+ * @param {string} link 链接目标
178
+ * @throws `SyntaxError` 非法的链接目标
179
+ */
111
180
  setTarget(link) {
112
181
  link = String(link);
113
- if (link.type === 'link' && !/^\s*[:#]/.test(link)) {
182
+ if (this.type === 'link' && !/^\s*[:#]/u.test(link)) {
114
183
  link = `:${link}`;
115
184
  }
116
185
  const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
117
- {childNodes: {length}, firstElementChild} = root;
118
- 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) {
119
189
  const msgs = {link: '内链', file: '文件链接', category: '分类', 'gallery-image': '文件链接'};
120
190
  throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
121
191
  }
122
- const {firstChild} = firstElementChild;
123
- root.destroy();
124
- firstElementChild.destroy();
125
- this.firstElementChild.safeReplaceWith(firstChild);
192
+ wikiLink.destroy(true);
193
+ this.firstChild.safeReplaceWith(firstChild);
126
194
  }
127
195
 
128
196
  /**
129
- * @param {string} lang
130
- * @param {string} link
197
+ * 设置跨语言链接
198
+ * @param {string} lang 语言前缀
199
+ * @param {string} link 页面标题
200
+ * @throws `SyntaxError` 非法的跨语言链接
131
201
  */
132
202
  setLangLink(lang, link) {
133
203
  if (typeof lang !== 'string') {
134
204
  this.typeError('setLangLink', 'String');
135
205
  }
136
206
  link = String(link).trim();
137
- if (link.startsWith('#')) {
207
+ const [char] = link;
208
+ if (char === '#') {
138
209
  throw new SyntaxError(`跨语言链接不能仅为fragment!`);
139
- } else if (link.startsWith(':')) {
210
+ } else if (char === ':') {
140
211
  link = link.slice(1);
141
212
  }
142
213
  const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
143
- /** @type {Token & {firstElementChild: LinkToken}} */ {childNodes: {length}, firstElementChild} = root;
144
- if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childNodes.length !== 1
145
- || firstElementChild.interwiki !== lang.toLowerCase()
146
- ) {
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()) {
147
217
  throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
148
218
  }
149
- const {firstChild} = firstElementChild;
150
- root.destroy();
151
- firstElementChild.destroy();
152
- this.firstElementChild.safeReplaceWith(firstChild);
219
+ wikiLink.destroy(true);
220
+ this.firstChild.safeReplaceWith(firstChild);
153
221
  }
154
222
 
155
- /** @param {string} fragment */
223
+ /**
224
+ * 设置fragment
225
+ * @param {string} fragment fragment
226
+ * @param {boolean} page 是否是其他页面
227
+ * @throws `SyntaxError` 非法的fragment
228
+ */
156
229
  #setFragment(fragment, page = true) {
157
- fragment = String(fragment).replace(/[<>[]#|=!\]/g, p => encodeURIComponent(p));
230
+ fragment = String(fragment).replaceAll(/[<>[\]#|=]/gu, p => encodeURIComponent(p));
158
231
  const include = this.getAttribute('include'),
159
232
  config = this.getAttribute('config'),
160
233
  root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
161
- {childNodes: {length}, firstElementChild} = root;
162
- 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) {
163
237
  throw new SyntaxError(`非法的 fragment:${fragment}`);
164
238
  }
165
239
  if (page) {
166
240
  Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
167
241
  }
168
- const {firstChild} = firstElementChild;
169
- root.destroy();
170
- firstElementChild.destroy();
171
- this.firstElementChild.safeReplaceWith(firstChild);
242
+ wikiLink.destroy(true);
243
+ this.firstChild.safeReplaceWith(firstChild);
172
244
  }
173
245
 
174
- /** @param {string} fragment */
246
+ /**
247
+ * 设置fragment
248
+ * @param {string} fragment fragment
249
+ */
175
250
  setFragment(fragment) {
176
251
  this.#setFragment(fragment);
177
252
  }
178
253
 
254
+ /**
255
+ * 修改为到自身的链接
256
+ * @param {string} fragment fragment
257
+ * @throws `RangeError` 空fragment
258
+ */
179
259
  asSelfLink(fragment = this.fragment) {
180
260
  fragment = String(fragment);
181
261
  if (!fragment.trim()) {
@@ -184,46 +264,55 @@ class LinkToken extends Token {
184
264
  this.#setFragment(fragment, false);
185
265
  }
186
266
 
267
+ /**
268
+ * 设置链接显示文字
269
+ * @param {string} linkText 链接显示文字
270
+ * @throws `SyntaxError` 非法的链接显示文字
271
+ */
187
272
  setLinkText(linkText = '') {
188
273
  linkText = String(linkText);
189
- let lastElementChild;
274
+ let lastChild;
190
275
  const config = this.getAttribute('config');
191
276
  if (linkText) {
192
277
  const root = Parser.parse(`[[${
193
278
  this.type === 'category' ? 'Category:' : ''
194
279
  }L|${linkText}]]`, this.getAttribute('include'), 6, config),
195
- {childNodes: {length}, firstElementChild} = root;
196
- 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) {
197
282
  throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
198
283
  }
199
- ({lastElementChild} = firstElementChild);
284
+ ({lastChild} = wikiLink);
200
285
  } else {
201
- lastElementChild = Parser.run(() => new Token('', config));
202
- lastElementChild.setAttribute('stage', 7).type = 'link-text';
286
+ lastChild = Parser.run(() => new Token('', config));
287
+ lastChild.setAttribute('stage', 7).type = 'link-text';
203
288
  }
204
289
  if (this.childNodes.length === 1) {
205
- this.appendChild(lastElementChild);
290
+ this.appendChild(lastChild);
206
291
  } else {
207
- this.lastElementChild.safeReplaceWith(lastElementChild);
292
+ this.lastChild.safeReplaceWith(lastChild);
208
293
  }
209
294
  }
210
295
 
296
+ /**
297
+ * 自动生成管道符后的链接文字
298
+ * @throws `Error` 带有"#"或"%"时不可用
299
+ */
211
300
  pipeTrick() {
212
- const linkText = this.firstElementChild.text();
213
- if (/[#%]/.test(linkText)) {
301
+ const linkText = this.firstChild.text();
302
+ if (linkText.includes('#') || linkText.includes('%')) {
214
303
  throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
215
304
  }
216
- const m1 = /^:?(?:[ \w\x80-\xff-]+:)?([^(]+)\(.+\)$/.exec(linkText);
305
+ const m1 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)\(.+\)$/u.exec(linkText);
217
306
  if (m1) {
218
307
  this.setLinkText(m1[1].trim());
219
308
  return;
220
309
  }
221
- const m2 = /^:?(?:[ \w\x80-\xff-]+:)?([^(]+)(.+)$/.exec(linkText);
310
+ const m2 = /^:?(?:[ \w\x80-\xFF-]+:)?([^(]+)(.+)$/u.exec(linkText);
222
311
  if (m2) {
223
312
  this.setLinkText(m2[1].trim());
224
313
  return;
225
314
  }
226
- const m3 = /^:?(?:[ \w\x80-\xff-]+:)?(.+?)(?:(?<!\()\(.+\))?(?:, |,|، )./.exec(linkText);
315
+ const m3 = /^:?(?:[ \w\x80-\xFF-]+:)?(.+?)(?:(?<!\()\(.+\))?(?:, |,|، )./u.exec(linkText);
227
316
  if (m3) {
228
317
  this.setLinkText(m3[1].trim());
229
318
  return;
package/src/magicLink.js CHANGED
@@ -1,52 +1,94 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const {generateForChild} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ AstText = require('../lib/text'),
4
6
  Token = require('.');
5
7
 
6
8
  /**
7
9
  * 自由外链
8
- * @classdesc `{childNodes: [...string|CommentToken|IncludeToken|NoincludeToken]}`
10
+ * @classdesc `{childNodes: [...AstText|CommentToken|IncludeToken|NoincludeToken]}`
9
11
  */
10
12
  class MagicLinkToken extends Token {
11
13
  type = 'free-ext-link';
12
14
  #protocolRegex;
13
15
 
16
+ /** 协议 */
14
17
  get protocol() {
15
18
  return this.#protocolRegex.exec(this.text())?.[0];
16
19
  }
20
+
17
21
  set protocol(value) {
18
22
  if (typeof value !== 'string') {
19
23
  this.typeError('protocol', 'String');
20
24
  }
21
- if (!RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
25
+ if (!new RegExp(`${this.#protocolRegex.source}$`, 'iu').test(value)) {
22
26
  throw new RangeError(`非法的外链协议:${value}`);
23
27
  }
24
28
  this.replaceChildren(this.text().replace(this.#protocolRegex, value));
25
29
  }
26
30
 
31
+ /** 和内链保持一致 */
32
+ get link() {
33
+ return this.text();
34
+ }
35
+
36
+ set link(url) {
37
+ this.setTarget(url);
38
+ }
39
+
27
40
  /**
28
- * @param {string} url
41
+ * @param {string} url 网址
42
+ * @param {boolean} doubleSlash 是否接受"//"作为协议
29
43
  * @param {accum} accum
30
44
  */
31
- constructor(url, doubleSlash = false, config = Parser.getConfig(), accum = []) {
45
+ constructor(url, doubleSlash, config = Parser.getConfig(), accum = []) {
32
46
  super(url, config, true, accum, {'Stage-1': ':', '!ExtToken': ''});
33
47
  if (doubleSlash) {
34
48
  this.type = 'ext-link-url';
35
49
  }
36
- this.#protocolRegex = RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'i');
50
+ this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'iu');
37
51
  }
38
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
+
79
+ /** @override */
39
80
  afterBuild() {
40
- const ParameterToken = require('./parameter'),
41
- /** @type {ParameterToken} */ parameter = this.closest('parameter');
81
+ const ParameterToken = require('./parameter');
82
+ const /** @type {ParameterToken} */ parameter = this.closest('parameter');
42
83
  if (parameter?.getValue() === this.text()) {
43
- this.replaceWith(this.toString());
84
+ this.replaceWith(...this.childNodes);
44
85
  }
45
86
  return this;
46
87
  }
47
88
 
89
+ /** @override */
48
90
  cloneNode() {
49
- const cloned = this.cloneChildren(),
91
+ const cloned = this.cloneChildNodes(),
50
92
  token = Parser.run(() => new MagicLinkToken(
51
93
  undefined, this.type === 'ext-link-url', this.getAttribute('config'),
52
94
  ));
@@ -55,6 +97,10 @@ class MagicLinkToken extends Token {
55
97
  return token;
56
98
  }
57
99
 
100
+ /**
101
+ * 获取网址
102
+ * @throws `Error` 非标准协议
103
+ */
58
104
  getUrl() {
59
105
  let url = this.text();
60
106
  if (url.startsWith('//')) {
@@ -70,15 +116,19 @@ class MagicLinkToken extends Token {
70
116
  }
71
117
  }
72
118
 
73
- /** @param {string|URL} url */
119
+ /**
120
+ * 设置外链目标
121
+ * @param {string|URL} url 含协议的网址
122
+ * @throws `SyntaxError` 非法的自由外链目标
123
+ */
74
124
  setTarget(url) {
75
125
  url = String(url);
76
126
  const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
77
- {childNodes: {length}, firstElementChild} = root;
78
- if (length !== 1 || firstElementChild?.type !== 'free-ext-link') {
127
+ {childNodes: {length}, firstChild: freeExtLink} = root;
128
+ if (length !== 1 || freeExtLink.type !== 'free-ext-link') {
79
129
  throw new SyntaxError(`非法的自由外链目标:${url}`);
80
130
  }
81
- this.replaceChildren(...firstElementChild.childNodes);
131
+ this.replaceChildren(...freeExtLink.childNodes);
82
132
  }
83
133
  }
84
134
 
@@ -1,44 +1,70 @@
1
1
  'use strict';
2
2
 
3
3
  const hidden = require('../../mixin/hidden'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ {generateForSelf} = require('../../util/lint'),
5
+ Parser = require('../..'),
5
6
  NowikiToken = require('.');
6
7
 
7
8
  /**
8
9
  * HTML注释,不可见
9
- * @classdesc `{childNodes: [string]}`
10
+ * @classdesc `{childNodes: [AstText]}`
10
11
  */
11
12
  class CommentToken extends hidden(NowikiToken) {
12
13
  type = 'comment';
13
14
  closed;
14
15
 
16
+ /** 内部wikitext */
17
+ get innerText() {
18
+ return String(this.firstChild);
19
+ }
20
+
15
21
  /**
16
- * @param {string} wikitext
22
+ * @param {string} wikitext wikitext
23
+ * @param {boolean} closed 是否闭合
17
24
  * @param {accum} accum
18
25
  */
19
26
  constructor(wikitext, closed = true, config = Parser.getConfig(), accum = []) {
20
27
  super(wikitext, config, accum);
21
28
  this.closed = closed;
29
+ Object.defineProperty(this, 'closed', {enumerable: false});
22
30
  }
23
31
 
24
- /** @this {CommentToken & {firstChild: string}} */
32
+ /** @override */
25
33
  cloneNode() {
26
- return Parser.run(() => new CommentToken(this.firstChild, this.closed, this.getAttribute('config')));
34
+ return Parser.run(() => new CommentToken(String(this.firstChild), this.closed, this.getAttribute('config')));
27
35
  }
28
36
 
29
- /** @this {CommentToken & {firstChild: string}} */
30
- toString() {
31
- const {firstChild, closed, nextSibling} = this;
32
- if (!closed && nextSibling) {
37
+ /**
38
+ * @override
39
+ * @param {string} selector
40
+ */
41
+ toString(selector) {
42
+ if (!this.closed && this.nextSibling) {
33
43
  Parser.error('自动闭合HTML注释', this);
34
44
  this.closed = true;
35
45
  }
36
- return `<!--${firstChild}${this.closed ? '-->' : ''}`;
46
+ return selector && this.matches(selector)
47
+ ? ''
48
+ : `<!--${String(this.firstChild)}${this.closed ? '-->' : ''}`;
37
49
  }
38
50
 
51
+ /** @override */
39
52
  getPadding() {
40
53
  return 4;
41
54
  }
55
+
56
+ /** @override */
57
+ print() {
58
+ return super.print({pre: '&lt;!--', post: this.closed ? '--&gt;' : ''});
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
+ }
42
68
  }
43
69
 
44
70
  Parser.classes.CommentToken = __filename;