wikiparser-node 0.9.2-b → 0.10.0-m

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/config/.schema.json +127 -0
  2. package/config/default.json +831 -0
  3. package/config/llwiki.json +595 -0
  4. package/config/moegirl.json +685 -0
  5. package/config/zhwiki.json +803 -0
  6. package/i18n/zh-hans.json +1 -0
  7. package/i18n/zh-hant.json +1 -0
  8. package/index.js +81 -0
  9. package/lib/element.js +136 -0
  10. package/lib/node.js +236 -0
  11. package/lib/text.js +168 -0
  12. package/lib/title.js +56 -0
  13. package/mixin/hidden.js +18 -0
  14. package/package.json +48 -47
  15. package/parser/brackets.js +125 -0
  16. package/parser/commentAndExt.js +58 -0
  17. package/parser/converter.js +45 -0
  18. package/parser/externalLinks.js +32 -0
  19. package/parser/hrAndDoubleUnderscore.js +48 -0
  20. package/parser/html.js +41 -0
  21. package/parser/links.js +93 -0
  22. package/parser/list.js +58 -0
  23. package/parser/magicLinks.js +40 -0
  24. package/parser/quotes.js +63 -0
  25. package/parser/table.js +113 -0
  26. package/src/arg.js +93 -0
  27. package/src/atom/hidden.js +11 -0
  28. package/src/atom/index.js +26 -0
  29. package/src/attribute.js +290 -0
  30. package/src/attributes.js +150 -0
  31. package/src/converter.js +70 -0
  32. package/src/converterFlags.js +97 -0
  33. package/src/converterRule.js +74 -0
  34. package/src/extLink.js +60 -0
  35. package/src/gallery.js +95 -0
  36. package/src/hasNowiki/index.js +32 -0
  37. package/src/hasNowiki/pre.js +28 -0
  38. package/src/heading.js +83 -0
  39. package/src/html.js +139 -0
  40. package/src/imageParameter.js +141 -0
  41. package/src/imagemap.js +140 -0
  42. package/src/imagemapLink.js +29 -0
  43. package/src/index.js +406 -0
  44. package/src/link/category.js +13 -0
  45. package/src/link/file.js +132 -0
  46. package/src/link/galleryImage.js +62 -0
  47. package/src/link/index.js +119 -0
  48. package/src/magicLink.js +66 -0
  49. package/src/nested/choose.js +23 -0
  50. package/src/nested/combobox.js +22 -0
  51. package/src/nested/index.js +66 -0
  52. package/src/nested/references.js +22 -0
  53. package/src/nowiki/comment.js +47 -0
  54. package/src/nowiki/dd.js +13 -0
  55. package/src/nowiki/doubleUnderscore.js +26 -0
  56. package/src/nowiki/hr.js +22 -0
  57. package/src/nowiki/index.js +34 -0
  58. package/src/nowiki/list.js +13 -0
  59. package/src/nowiki/noinclude.js +14 -0
  60. package/src/nowiki/quote.js +55 -0
  61. package/src/onlyinclude.js +39 -0
  62. package/src/paramTag/index.js +66 -0
  63. package/src/paramTag/inputbox.js +32 -0
  64. package/src/parameter.js +97 -0
  65. package/src/syntax.js +23 -0
  66. package/src/table/index.js +46 -0
  67. package/src/table/td.js +118 -0
  68. package/src/table/tr.js +74 -0
  69. package/src/tagPair/ext.js +125 -0
  70. package/src/tagPair/include.js +26 -0
  71. package/src/tagPair/index.js +77 -0
  72. package/src/transclude.js +336 -0
  73. package/util/base.js +17 -0
  74. package/util/diff.js +76 -0
  75. package/util/lint.js +55 -0
  76. package/util/string.js +75 -0
  77. package/bundle/bundle.min.js +0 -38
  78. package/extensions/editor.css +0 -62
  79. package/extensions/editor.js +0 -328
  80. package/extensions/ui.css +0 -119
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ QuoteToken = require('../src/nowiki/quote');
5
+
6
+ /**
7
+ * 解析单引号
8
+ * @param {string} text wikitext
9
+ * @param {accum} accum
10
+ */
11
+ const parseQuotes = (text, config = Parser.getConfig(), accum = []) => {
12
+ const arr = text.split(/('{2,})/u),
13
+ {length} = arr;
14
+ if (length === 1) {
15
+ return text;
16
+ }
17
+ let nBold = 0,
18
+ nItalic = 0,
19
+ firstSingle, firstMulti, firstSpace;
20
+ for (let i = 1; i < length; i += 2) {
21
+ const {length: len} = arr[i];
22
+ switch (len) {
23
+ case 2:
24
+ nItalic++;
25
+ break;
26
+ case 4:
27
+ arr[i - 1] += `'`;
28
+ arr[i] = `'''`;
29
+ // fall through
30
+ case 3:
31
+ nBold++;
32
+ if (firstSingle) {
33
+ break;
34
+ } else if (arr[i - 1].endsWith(' ')) {
35
+ if (!firstMulti && !firstSpace) {
36
+ firstSpace = i;
37
+ }
38
+ } else if (arr[i - 1].at(-2) === ' ') {
39
+ firstSingle = i;
40
+ } else {
41
+ firstMulti ||= i;
42
+ }
43
+ break;
44
+ default:
45
+ arr[i - 1] += `'`.repeat(len - 5);
46
+ arr[i] = `'''''`;
47
+ nItalic++;
48
+ nBold++;
49
+ }
50
+ }
51
+ if (nItalic % 2 === 1 && nBold % 2 === 1) {
52
+ const i = firstSingle ?? firstMulti ?? firstSpace;
53
+ arr[i] = `''`;
54
+ arr[i - 1] += `'`;
55
+ }
56
+ for (let i = 1; i < length; i += 2) {
57
+ new QuoteToken(arr[i].length, config, accum);
58
+ arr[i] = `\0${accum.length - 1}q\x7F`;
59
+ }
60
+ return arr.join('');
61
+ };
62
+
63
+ module.exports = parseQuotes;
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ AstText = require('../lib/text'),
5
+ Token = require('../src'),
6
+ TableToken = require('../src/table'),
7
+ TrToken = require('../src/table/tr'),
8
+ TdToken = require('../src/table/td'),
9
+ DdToken = require('../src/nowiki/dd');
10
+
11
+ /**
12
+ * 解析表格,注意`tr`和`td`包含开头的换行
13
+ * @param {Token & {firstChild: AstText}} root 根节点
14
+ * @param {accum} accum
15
+ */
16
+ const parseTable = ({firstChild: {data}, type, name}, config = Parser.getConfig(), accum = []) => {
17
+ const /** @type {TrToken[]} */ stack = [],
18
+ lines = data.split('\n');
19
+ let out = type === 'root' || type === 'parameter-value' || type === 'ext-inner' && name === 'poem'
20
+ ? ''
21
+ : `\n${lines.shift()}`;
22
+
23
+ /**
24
+ * 向表格中插入纯文本
25
+ * @param {string} str 待插入的文本
26
+ * @param {TrToken} top 当前解析的表格或表格行
27
+ */
28
+ const push = (str, top) => {
29
+ if (!top) {
30
+ out += str;
31
+ return;
32
+ }
33
+ const /** @type {Token}} */ {lastChild} = top;
34
+ if (lastChild.constructor === Token) {
35
+ lastChild.setText(String(lastChild) + str);
36
+ } else {
37
+ const token = new Token(str, config, true, accum);
38
+ token.type = 'table-inter';
39
+ top.insertAt(token.setAttribute('stage', 3));
40
+ }
41
+ };
42
+ for (const outLine of lines) {
43
+ let top = stack.pop();
44
+ const [spaces] = /^(?:\s|\0\d+c\x7F)*/u.exec(outLine),
45
+ line = outLine.slice(spaces.length),
46
+ matchesStart = /^(:*)((?:\s|\0\d+c\x7F)*)(\{\||\{(?:\0\d+c\x7F)*\0\d+!\x7F|\0\d+\{\x7F)(.*)$/u.exec(line);
47
+ if (matchesStart) {
48
+ while (top && top.type !== 'td') {
49
+ top = stack.pop();
50
+ }
51
+ const [, indent, moreSpaces, tableSyntax, attr] = matchesStart;
52
+ if (indent) {
53
+ new DdToken(indent, config, accum);
54
+ }
55
+ push(`\n${spaces}${indent && `\0${accum.length - 1}d\x7F`}${moreSpaces}\0${accum.length}b\x7F`, top);
56
+ const table = new TableToken(tableSyntax, attr, config, accum);
57
+ stack.push(...top ? [top] : [], table);
58
+ continue;
59
+ } else if (!top) {
60
+ out += `\n${outLine}`;
61
+ continue;
62
+ }
63
+ const matches = // eslint-disable-line operator-linebreak
64
+ /^(?:(\|\}|\0\d+!\x7F\}|\0\d+\}\x7F)|(\|-+|\0\d+!\x7F-+|\0\d+-\x7F-*)(?!-)|(!|(?:\||\0\d+!\x7F)\+?))(.*)$/u
65
+ .exec(line);
66
+ if (!matches) {
67
+ push(`\n${outLine}`, top);
68
+ stack.push(...top ? [top] : []);
69
+ continue;
70
+ }
71
+ const [, closing, row, cell, attr] = matches;
72
+ if (closing) {
73
+ while (!(top instanceof TableToken)) {
74
+ top = stack.pop();
75
+ }
76
+ top.close(`\n${spaces}${closing}`, true);
77
+ push(attr, stack.at(-1));
78
+ } else if (row) {
79
+ if (top.type === 'td') {
80
+ top = stack.pop();
81
+ }
82
+ if (top.type === 'tr') {
83
+ top = stack.pop();
84
+ }
85
+ const tr = new TrToken(`\n${spaces}${row}`, attr, config, accum);
86
+ stack.push(top, tr);
87
+ top.insertAt(tr);
88
+ } else {
89
+ if (top.type === 'td') {
90
+ top = stack.pop();
91
+ }
92
+ const regex = cell === '!'
93
+ ? /!!|(?:\||\0\d+!\x7F){2}|\0\d+\+\x7F/gu
94
+ : /(?:\||\0\d+!\x7F){2}|\0\d+\+\x7F/gu;
95
+ let mt = regex.exec(attr),
96
+ lastIndex = 0,
97
+ lastSyntax = `\n${spaces}${cell}`;
98
+ while (mt) {
99
+ const td = new TdToken(lastSyntax, attr.slice(lastIndex, mt.index), config, accum);
100
+ top.insertAt(td);
101
+ ({lastIndex} = regex);
102
+ [lastSyntax] = mt;
103
+ mt = regex.exec(attr);
104
+ }
105
+ const td = new TdToken(lastSyntax, attr.slice(lastIndex), config, accum);
106
+ stack.push(top, td);
107
+ top.insertAt(td);
108
+ }
109
+ }
110
+ return out.slice(1);
111
+ };
112
+
113
+ module.exports = parseTable;
package/src/arg.js ADDED
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const {text} = require('../util/string'),
4
+ {generateForSelf, generateForChild} = require('../util/lint'),
5
+ Parser = require('..'),
6
+ Token = require('.');
7
+
8
+ /**
9
+ * `{{{}}}`包裹的参数
10
+ * @classdesc `{childNodes: [AtomToken, ?Token, ...HiddenToken]}`
11
+ */
12
+ class ArgToken extends Token {
13
+ type = 'arg';
14
+
15
+ /** default */
16
+ get default() {
17
+ return this.childNodes[1]?.text() ?? false;
18
+ }
19
+
20
+ /**
21
+ * @param {string[]} parts 以'|'分隔的各部分
22
+ * @param {accum} accum
23
+ * @complexity `n`
24
+ */
25
+ constructor(parts, config = Parser.getConfig(), accum = []) {
26
+ super(undefined, config, true, accum, {
27
+ });
28
+ for (let i = 0; i < parts.length; i++) {
29
+ if (i === 0 || i > 1) {
30
+ const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden');
31
+ const token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
32
+ });
33
+ super.insertAt(token);
34
+ } else {
35
+ const token = new Token(parts[i], config, true, accum);
36
+ token.type = 'arg-default';
37
+ super.insertAt(token.setAttribute('stage', 2));
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @override
44
+ */
45
+ toString(selector) {
46
+ return `{{{${super.toString(selector, '|')}}}}`;
47
+ }
48
+
49
+ /**
50
+ * @override
51
+ * @returns {string}
52
+ */
53
+ text() {
54
+ return `{{{${text(this.childNodes.slice(0, 2), '|')}}}}`;
55
+ }
56
+
57
+ /** @override */
58
+ getPadding() {
59
+ return 3;
60
+ }
61
+
62
+ /** @override */
63
+ getGaps() {
64
+ return 1;
65
+ }
66
+
67
+ /**
68
+ * @override
69
+ * @param {number} start 起始位置
70
+ * @returns {LintError[]}
71
+ */
72
+ lint(start = this.getAbsoluteIndex()) {
73
+ if (!this.getAttribute('include')) {
74
+ return [generateForSelf(this, {start}, 'unexpected template argument')];
75
+ }
76
+ const {childNodes: [argName, argDefault, ...rest]} = this,
77
+ errors = argName.lint(start + 3);
78
+ if (argDefault) {
79
+ errors.push(...argDefault.lint(start + 4 + String(argName).length));
80
+ }
81
+ if (rest.length > 0) {
82
+ const rect = {start, ...this.getRootNode().posFromIndex(start)};
83
+ errors.push(...rest.map(child => {
84
+ const error = generateForChild(child, rect, 'invisible content inside triple brackets'),
85
+ {startIndex, startCol, excerpt} = error;
86
+ return {...error, startIndex: startIndex - 1, startCol: startCol - 1, excerpt: `|${excerpt}`};
87
+ }));
88
+ }
89
+ return errors;
90
+ }
91
+ }
92
+
93
+ module.exports = ArgToken;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const hidden = require('../../mixin/hidden'),
4
+ AtomToken = require('.');
5
+
6
+ /** 不可见的节点 */
7
+ class HiddenToken extends hidden(AtomToken) {
8
+ type = 'hidden';
9
+ }
10
+
11
+ module.exports = HiddenToken;
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ Token = require('..');
5
+
6
+ /**
7
+ * 不会被继续解析的plain Token
8
+ * @classdesc `{childNodes: ...AstText|Token}`
9
+ */
10
+ class AtomToken extends Token {
11
+ type = 'plain';
12
+
13
+ /**
14
+ * @param {string} wikitext wikitext
15
+ * @param {string|undefined} type Token.type
16
+ * @param {accum} accum
17
+ */
18
+ constructor(wikitext, type, config = Parser.getConfig(), accum = [], acceptable = undefined) {
19
+ super(wikitext, config, true, accum, acceptable);
20
+ if (type) {
21
+ this.type = type;
22
+ }
23
+ }
24
+ }
25
+
26
+ module.exports = AtomToken;
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ const {generateForChild} = require('../util/lint'),
4
+ {removeComment} = require('../util/string'),
5
+ Parser = require('..'),
6
+ Token = require('.'),
7
+ AtomToken = require('./atom');
8
+
9
+ const commonHtmlAttrs = new Set([
10
+ 'id',
11
+ 'class',
12
+ 'style',
13
+ 'lang',
14
+ 'dir',
15
+ 'title',
16
+ 'tabindex',
17
+ 'aria-describedby',
18
+ 'aria-flowto',
19
+ 'aria-hidden',
20
+ 'aria-label',
21
+ 'aria-labelledby',
22
+ 'aria-owns',
23
+ 'role',
24
+ 'about',
25
+ 'property',
26
+ 'resource',
27
+ 'datatype',
28
+ 'typeof',
29
+ 'itemid',
30
+ 'itemprop',
31
+ 'itemref',
32
+ 'itemscope',
33
+ 'itemtype',
34
+ ]),
35
+ blockAttrs = new Set(['align']),
36
+ citeAttrs = new Set(['cite']),
37
+ citeAndAttrs = new Set(['cite', 'datetime']),
38
+ widthAttrs = new Set(['width']),
39
+ tdAttrs = new Set(
40
+ ['align', 'valign', 'abbr', 'axis', 'headers', 'scope', 'rowspan', 'colspan', 'width', 'height', 'bgcolor'],
41
+ ),
42
+ typeAttrs = new Set(['type']),
43
+ htmlAttrs = {
44
+ div: blockAttrs,
45
+ h1: blockAttrs,
46
+ h2: blockAttrs,
47
+ h3: blockAttrs,
48
+ h4: blockAttrs,
49
+ h5: blockAttrs,
50
+ h6: blockAttrs,
51
+ blockquote: citeAttrs,
52
+ q: citeAttrs,
53
+ p: blockAttrs,
54
+ br: new Set(['clear']),
55
+ pre: widthAttrs,
56
+ ins: citeAndAttrs,
57
+ del: citeAndAttrs,
58
+ ul: typeAttrs,
59
+ ol: new Set(['type', 'start', 'reversed']),
60
+ li: new Set(['type', 'value']),
61
+ table: new Set(
62
+ ['summary', 'width', 'border', 'frame', 'rules', 'cellspacing', 'cellpadding', 'align', 'bgcolor'],
63
+ ),
64
+ caption: blockAttrs,
65
+ tr: new Set(['bgcolor', 'align', 'valign']),
66
+ td: tdAttrs,
67
+ th: tdAttrs,
68
+ img: new Set(['alt', 'src', 'width', 'height', 'srcset']),
69
+ font: new Set(['size', 'color', 'face']),
70
+ hr: widthAttrs,
71
+ rt: new Set(['rbspan']),
72
+ data: new Set(['value']),
73
+ time: new Set(['datetime']),
74
+ meta: new Set(['itemprop', 'content']),
75
+ link: new Set(['itemprop', 'href', 'title']),
76
+ gallery: new Set(['mode', 'showfilename', 'caption', 'perrow', 'widths', 'heights', 'showthumbnails', 'type']),
77
+ poem: new Set(['compact', 'align']),
78
+ categorytree: new Set([
79
+ 'align',
80
+ 'hideroot',
81
+ 'onlyroot',
82
+ 'depth',
83
+ 'mode',
84
+ 'hideprefix',
85
+ 'namespaces',
86
+ 'showcount',
87
+ 'notranslations',
88
+ ]),
89
+ combooption: new Set(['name', 'for', 'inline', 'align']),
90
+ },
91
+ empty = new Set(),
92
+ extAttrs = {
93
+ nowiki: empty,
94
+ indicator: new Set(['name']),
95
+ langconvert: new Set(['from', 'to']),
96
+ ref: new Set(['group', 'name', 'extends', 'follow', 'dir']),
97
+ references: new Set(['group', 'responsive']),
98
+ charinsert: new Set(['label']),
99
+ choose: new Set(['uncached', 'before', 'after']),
100
+ option: new Set(['weight']),
101
+ imagemap: empty,
102
+ inputbox: empty,
103
+ templatestyles: new Set(['src', 'wrapper']),
104
+ dynamicpagelist: empty,
105
+ poll: new Set(['id', 'show-results-before-voting']),
106
+ sm2: typeAttrs,
107
+ flashmp3: typeAttrs,
108
+ tab: new Set([
109
+ 'nested',
110
+ 'name',
111
+ 'index',
112
+ 'class',
113
+ 'block',
114
+ 'inline',
115
+ 'openname',
116
+ 'closename',
117
+ 'collapsed',
118
+ 'dropdown',
119
+ 'style',
120
+ 'bgcolor',
121
+ 'container',
122
+ 'id',
123
+ 'title',
124
+ ]),
125
+ tabs: new Set(['plain', 'class', 'container', 'id', 'title', 'style']),
126
+ combobox: new Set(['placeholder', 'value', 'id', 'class', 'text', 'dropdown', 'style']),
127
+ },
128
+ insecureStyle = new RegExp(
129
+ `${
130
+ 'expression'
131
+ }|${
132
+ '(?:filter|accelerator|-o-link(?:-source)?|-o-replace)\\s*:'
133
+ }|${
134
+ '(?:url|image(?:-set)?)\\s*\\('
135
+ }|${
136
+ 'attr\\s*\\([^)]+[\\s,]url'
137
+ }`,
138
+ 'u',
139
+ );
140
+
141
+ /**
142
+ * 扩展和HTML标签属性
143
+ * @classdesc `{childNodes: [AtomToken, Token|AtomToken]}`
144
+ */
145
+ class AttributeToken extends Token {
146
+ #equal;
147
+ #quotes;
148
+ #tag;
149
+
150
+ /** 引号是否匹配 */
151
+ get balanced() {
152
+ return !this.#equal || this.#quotes[0] === this.#quotes[1];
153
+ }
154
+
155
+ /** getValue()的getter */
156
+ get value() {
157
+ return this.getValue();
158
+ }
159
+
160
+ /** 标签名 */
161
+ get tag() {
162
+ return this.#tag;
163
+ }
164
+
165
+ /**
166
+ * @param {'ext-attr'|'html-attr'|'table-attr'} type 标签类型
167
+ * @param {string} tag 标签名
168
+ * @param {string} key 属性名
169
+ * @param {string} equal 等号
170
+ * @param {string} value 属性值
171
+ * @param {string[]} quotes 引号
172
+ * @param {accum} accum
173
+ */
174
+ constructor(type, tag, key, equal = '', value = '', quotes = [], config = Parser.getConfig(), accum = []) {
175
+ const keyToken = new AtomToken(key, 'attr-key', config, accum, {
176
+ });
177
+ let valueToken;
178
+ if (key === 'title') {
179
+ valueToken = new Token(value, config, true, accum, {
180
+ }).setAttribute('type', 'attr-value').setAttribute('stage', Parser.MAX_STAGE - 1);
181
+ } else if (tag === 'gallery' && key === 'caption') {
182
+ /** @type {ParserConfig} */
183
+ const newConfig = {...config, excludes: [...config.excludes, 'quote', 'extLink', 'magicLink', 'list']};
184
+ valueToken = new Token(value, newConfig, true, accum, {
185
+ }).setAttribute('type', 'attr-value').setAttribute('stage', 5);
186
+ } else if (tag === 'choose' && (key === 'before' || key === 'after')) {
187
+ /** @type {ParserConfig} */
188
+ const newConfig = {...config, excludes: [...config.excludes, 'heading', 'html', 'table', 'hr', 'list']};
189
+ valueToken = new Token(value, newConfig, true, accum, {
190
+ }).setAttribute('type', 'attr-value').setAttribute('stage', 1);
191
+ } else {
192
+ valueToken = new AtomToken(value, 'attr-value', config, accum, {
193
+ });
194
+ }
195
+ super(undefined, config, true, accum);
196
+ this.type = type;
197
+ this.append(keyToken, valueToken);
198
+ this.#equal = equal;
199
+ this.#quotes = quotes;
200
+ this.#tag = tag;
201
+ this.setAttribute('name', removeComment(key).trim().toLowerCase());
202
+ }
203
+
204
+ /** @override */
205
+ afterBuild() {
206
+ if (this.#equal.includes('\0')) {
207
+ this.#equal = this.getAttribute('buildFromStr')(this.#equal, 'string');
208
+ }
209
+ if (this.parentNode) {
210
+ this.#tag = this.parentNode.name;
211
+ }
212
+ this.setAttribute('name', this.firstChild.text().trim().toLowerCase());
213
+ }
214
+
215
+ /**
216
+ * @override
217
+ * @returns {string}
218
+ */
219
+ toString(selector) {
220
+ const [quoteStart = '', quoteEnd = ''] = this.#quotes;
221
+ return this.#equal
222
+ ? `${super.toString(selector, `${this.#equal}${quoteStart}`)}${quoteEnd}`
223
+ : this.firstChild.toString(selector);
224
+ }
225
+
226
+ /**
227
+ * @override
228
+ * @returns {string}
229
+ */
230
+ text() {
231
+ return this.#equal ? `${super.text(`${this.#equal.trim()}"`)}"` : this.firstChild.text();
232
+ }
233
+
234
+ /** @override */
235
+ getGaps() {
236
+ return this.#equal ? this.#equal.length + (this.#quotes[0]?.length ?? 0) : 0;
237
+ }
238
+
239
+ /** @override */
240
+ print() {
241
+ const [quoteStart = '', quoteEnd = ''] = this.#quotes;
242
+ return this.#equal ? super.print({sep: `${this.#equal}${quoteStart}`, post: quoteEnd}) : super.print();
243
+ }
244
+
245
+ /**
246
+ * @override
247
+ * @param {number} start 起始位置
248
+ */
249
+ lint(start = this.getAbsoluteIndex()) {
250
+ const errors = super.lint(start),
251
+ {balanced, firstChild, lastChild, type, name, parentNode, value} = this,
252
+ tagName = parentNode?.name;
253
+ let rect;
254
+ if (!balanced) {
255
+ const root = this.getRootNode();
256
+ rect = {start, ...root.posFromIndex(start)};
257
+ const e = generateForChild(lastChild, rect, 'unclosed quotes', 'warning'),
258
+ startIndex = e.startIndex - 1,
259
+ startCol = e.startCol - 1;
260
+ errors.push({...e, startIndex, startCol, excerpt: String(root).slice(startIndex, startIndex + 50)});
261
+ }
262
+ if (!/\{\{[^{]+\}\}/u.test(name) && (
263
+ type === 'ext-attr' && !(tagName in htmlAttrs) && extAttrs[tagName] && !extAttrs[tagName].has(name)
264
+ || (type === 'html-attr' || type === 'table-attr' || tagName in htmlAttrs) && !htmlAttrs[tagName]?.has(name)
265
+ && !/^(?:xmlns:[\w:.-]+|data-[^:]*)$/u.test(name)
266
+ && (tagName === 'meta' || tagName === 'link' || !commonHtmlAttrs.has(name))
267
+ )) {
268
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
269
+ errors.push(generateForChild(firstChild, rect, 'illegal attribute name'));
270
+ } else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
271
+ rect ||= {start, ...this.getRootNode().posFromIndex(start)};
272
+ errors.push(generateForChild(lastChild, rect, 'insecure style'));
273
+ }
274
+ return errors;
275
+ }
276
+
277
+ /** 获取属性值 */
278
+ getValue() {
279
+ if (this.#equal) {
280
+ const value = this.lastChild.text();
281
+ if (this.#quotes[1]) {
282
+ return value;
283
+ }
284
+ return this.#quotes[0] ? value.trimEnd() : value.trim();
285
+ }
286
+ return true;
287
+ }
288
+ }
289
+
290
+ module.exports = AttributeToken;