wikiparser-node 1.13.9 → 1.14.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.
@@ -94,7 +94,16 @@ transclude_1.TranscludeToken.prototype.fixDuplication =
94
94
  if (args.length <= 1) {
95
95
  continue;
96
96
  }
97
- const values = Map.groupBy(args, (arg) => arg.getValue().trim());
97
+ const values = new Map();
98
+ for (const arg of args) {
99
+ const val = arg.getValue().trim();
100
+ if (values.has(val)) {
101
+ values.get(val).push(arg);
102
+ }
103
+ else {
104
+ values.set(val, [arg]);
105
+ }
106
+ }
98
107
  let noMoreAnon = anonCount === 0 || !key.trim() || isNaN(key);
99
108
  const emptyArgs = values.get('') ?? [], duplicatedArgs = [...values].filter(([val, { length }]) => val && length > 1).flatMap(([, curArgs]) => {
100
109
  const anonIndex = noMoreAnon ? -1 : curArgs.findIndex(({ anon }) => anon);
package/dist/base.d.ts CHANGED
@@ -63,9 +63,7 @@ export interface LintError {
63
63
  endLine: number;
64
64
  endCol: number;
65
65
  fix?: LintError.Fix;
66
- suggestions?: (LintError.Fix & {
67
- desc: string;
68
- })[];
66
+ suggestions?: LintError.Fix[];
69
67
  }
70
68
  export type AST = Record<string, string | number | boolean> & {
71
69
  range: [number, number];
package/dist/lib/text.js CHANGED
@@ -148,7 +148,7 @@ class AstText extends node_1.AstNode {
148
148
  return [];
149
149
  }
150
150
  errorRegex.lastIndex = 0;
151
- const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), { ext, html } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), tags = new Set(['onlyinclude', 'noinclude', 'includeonly', ext, html, disallowedTags].flat(2));
151
+ const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), rootStr = root.toString(), { ext, html } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), tags = new Set(['onlyinclude', 'noinclude', 'includeonly', ext, html, disallowedTags].flat(2));
152
152
  for (let mt = errorRegex.exec(data); mt; mt = errorRegex.exec(data)) {
153
153
  const [, tag, prefix] = mt;
154
154
  let { index } = mt, error = mt[0].toLowerCase();
@@ -168,7 +168,7 @@ class AstText extends node_1.AstNode {
168
168
  else if (char === ']' && (index || length > 1)) {
169
169
  errorRegex.lastIndex--;
170
170
  }
171
- const startIndex = start + index, endIndex = startIndex + length, rootStr = root.toString(), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && !(char === '<' && !/[\s/>]/u.test(nextChar ?? '')
171
+ const startIndex = start + index, endIndex = startIndex + length, nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && !(char === '<' && !/[\s/>]/u.test(nextChar ?? '')
172
172
  || isHtmlAttrVal && (char === '[' || char === ']')
173
173
  || magicLink && type === 'parameter-value')
174
174
  || char === '{' && (nextChar === char || previousChar === '-')
@@ -213,39 +213,31 @@ class AstText extends node_1.AstNode {
213
213
  endCol: startCol + length,
214
214
  };
215
215
  if (char === '<') {
216
- e.suggestions = [
217
- {
218
- desc: 'escape',
219
- range: [startIndex, startIndex + 1],
220
- text: '&lt;',
221
- },
222
- ];
216
+ e.suggestions = [{ desc: 'escape', range: [startIndex, startIndex + 1], text: '&lt;' }];
223
217
  }
224
218
  else if (char === 'h'
225
219
  && !(type === 'ext-link-text' || type === 'link-text')
226
220
  && /[\p{L}\d_]/u.test(previousChar || '')) {
227
- e.suggestions = [
228
- {
229
- desc: 'whitespace',
230
- range: [startIndex, startIndex],
231
- text: ' ',
232
- },
233
- ];
221
+ e.suggestions = [{ desc: 'whitespace', range: [startIndex, startIndex], text: ' ' }];
234
222
  }
235
223
  else if (char === '[' && type === 'ext-link-text') {
236
224
  const i = parentNode.getAbsoluteIndex() + parentNode.toString().length;
237
- e.suggestions = [
238
- {
239
- desc: 'escape',
240
- range: [i, i + 1],
241
- text: '&#93;',
242
- },
243
- ];
225
+ e.suggestions = [{ desc: 'escape', range: [i, i + 1], text: '&#93;' }];
244
226
  }
245
227
  else if (char === ']' && previousType === 'free-ext-link' && severity === 'error') {
246
228
  const i = start - previousSibling.toString().length;
247
229
  e.fix = { range: [i, i], text: '[', desc: 'left bracket' };
248
230
  }
231
+ else if (magicLink) {
232
+ e.suggestions = [
233
+ ...mt[0] === error
234
+ ? []
235
+ : [{ desc: 'uppercase', range: [startIndex, endIndex], text: error }],
236
+ ...nextChar === ':' || nextChar === ':'
237
+ ? [{ desc: 'whitespace', range: [endIndex, endIndex + 1], text: ' ' }]
238
+ : [],
239
+ ];
240
+ }
249
241
  errors.push(e);
250
242
  }
251
243
  return errors;
package/dist/src/arg.js CHANGED
@@ -95,7 +95,7 @@ class ArgToken extends index_2.Token {
95
95
  if (!this.getAttribute('include')) {
96
96
  const e = (0, lint_1.generateForSelf)(this, { start }, 'no-arg', 'unexpected template argument');
97
97
  if (argDefault) {
98
- e.fix = { range: [start, e.endIndex], text: argDefault.text(), desc: 'expand' };
98
+ e.suggestions = [{ range: [start, e.endIndex], text: argDefault.text(), desc: 'expand' }];
99
99
  }
100
100
  return [e];
101
101
  }
@@ -110,16 +110,8 @@ class ArgToken extends index_2.Token {
110
110
  e.startIndex--;
111
111
  e.startCol--;
112
112
  e.suggestions = [
113
- {
114
- desc: 'remove',
115
- range: [e.startIndex, e.endIndex],
116
- text: '',
117
- },
118
- {
119
- desc: 'escape',
120
- range: [e.startIndex, e.startIndex + 1],
121
- text: '{{!}}',
122
- },
113
+ { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' },
114
+ { desc: 'escape', range: [e.startIndex, e.startIndex + 1], text: '{{!}}' },
123
115
  ];
124
116
  return e;
125
117
  }));
@@ -180,16 +180,10 @@ let AttributeToken = (() => {
180
180
  const e = (0, lint_1.generateForChild)(lastChild, rect, 'unclosed-quote', index_1.default.msg('unclosed $1', 'quotes'), 'warning');
181
181
  e.startIndex--;
182
182
  e.startCol--;
183
- const fix = { range: [e.endIndex, e.endIndex], text: this.#quotes[0], desc: 'close' };
184
- if (lastChild.childNodes.some(({ type: t, data }) => t === 'text' && /\s/u.test(data))) {
185
- e.suggestions = [fix];
186
- }
187
- else {
188
- e.fix = fix;
189
- }
183
+ e.suggestions = [{ range: [e.endIndex, e.endIndex], text: this.#quotes[0], desc: 'close' }];
190
184
  errors.push(e);
191
185
  }
192
- const attrs = sharable_1.extAttrs[tag], attrs2 = sharable_1.htmlAttrs[tag];
186
+ const attrs = sharable_1.extAttrs[tag], attrs2 = sharable_1.htmlAttrs[tag], { length } = this.toString();
193
187
  if (!attrs?.has(name)
194
188
  && !attrs2?.has(name)
195
189
  // 不是未定义的扩展标签或包含嵌入的HTML标签
@@ -197,7 +191,10 @@ let AttributeToken = (() => {
197
191
  && (type === 'ext-attr' && !attrs2
198
192
  || !/^(?:xmlns:[\w:.-]+|data-(?!ooui|mw|parsoid)[^:]*)$/u.test(name)
199
193
  && (tag === 'meta' || tag === 'link' || !sharable_1.commonHtmlAttrs.has(name)))) {
200
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'illegal-attr', 'illegal attribute name'));
194
+ errors.push({
195
+ ...(0, lint_1.generateForChild)(firstChild, rect, 'illegal-attr', 'illegal attribute name'),
196
+ suggestions: [{ desc: 'remove', range: [start, start + length], text: '' }],
197
+ });
201
198
  }
202
199
  else if (sharable_1.obsoleteAttrs[tag]?.has(name)) {
203
200
  errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
@@ -208,16 +205,8 @@ let AttributeToken = (() => {
208
205
  else if (name === 'tabindex' && typeof value === 'string' && value !== '0') {
209
206
  const e = (0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'nonzero tabindex');
210
207
  e.suggestions = [
211
- {
212
- desc: 'remove',
213
- range: [start, e.endIndex],
214
- text: '',
215
- },
216
- {
217
- desc: '0 tabindex',
218
- range: [e.startIndex, e.endIndex],
219
- text: '0',
220
- },
208
+ { desc: 'remove', range: [start, start + length], text: '' },
209
+ { desc: '0 tabindex', range: [e.startIndex, e.endIndex], text: '0' },
221
210
  ];
222
211
  errors.push(e);
223
212
  }
@@ -168,8 +168,11 @@ class AttributesToken extends index_2.Token {
168
168
  lint(start = this.getAbsoluteIndex(), re) {
169
169
  const errors = super.lint(start, re), { parentNode, childNodes } = this, attrs = new Map(), duplicated = new Set(), rect = new rect_1.BoundingRect(this, start);
170
170
  if (parentNode?.type === 'html' && parentNode.closing && this.text().trim()) {
171
- const e = (0, lint_1.generateForSelf)(this, rect, 'no-ignored', 'attributes of a closing tag');
172
- e.fix = { range: [start, e.endIndex], text: '', desc: 'remove' };
171
+ const e = (0, lint_1.generateForSelf)(this, rect, 'no-ignored', 'attributes of a closing tag'), index = parentNode.getAbsoluteIndex();
172
+ e.suggestions = [
173
+ { desc: 'remove', range: [start, e.endIndex], text: '' },
174
+ { desc: 'open', range: [index + 1, index + 2], text: '' },
175
+ ];
173
176
  errors.push(e);
174
177
  }
175
178
  for (const attr of childNodes) {
@@ -187,20 +190,27 @@ class AttributesToken extends index_2.Token {
187
190
  const str = attr.text().trim();
188
191
  if (str) {
189
192
  const e = (0, lint_1.generateForChild)(attr, rect, 'no-ignored', 'containing invalid attribute', /[\p{L}\d]/u.test(str) ? 'error' : 'warning');
190
- e.suggestions = [
191
- {
192
- desc: 'remove',
193
- range: [e.startIndex, e.endIndex],
194
- text: ' ',
195
- },
196
- ];
193
+ e.suggestions = [{ desc: 'remove', range: [e.startIndex, e.endIndex], text: ' ' }];
197
194
  errors.push(e);
198
195
  }
199
196
  }
200
197
  }
201
198
  if (duplicated.size > 0) {
202
199
  for (const key of duplicated) {
203
- errors.push(...attrs.get(key).map(attr => (0, lint_1.generateForChild)(attr, rect, 'no-duplicate', index_1.default.msg('duplicated $1 attribute', key))));
200
+ const pairs = attrs.get(key).map(attr => {
201
+ const value = attr.getValue();
202
+ return [attr, value === true ? '' : value];
203
+ });
204
+ errors.push(...pairs.map(([attr, value], i) => {
205
+ const e = (0, lint_1.generateForChild)(attr, rect, 'no-duplicate', index_1.default.msg('duplicated $1 attribute', key)), remove = { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' };
206
+ if (!value || pairs.slice(0, i).some(([, v]) => v === value)) {
207
+ e.fix = remove;
208
+ }
209
+ else {
210
+ e.suggestions = [remove];
211
+ }
212
+ return e;
213
+ }));
204
214
  }
205
215
  }
206
216
  return errors;
@@ -91,13 +91,7 @@ class ConverterFlagsToken extends index_2.Token {
91
91
  e.fix = { range: [e.startIndex, e.endIndex], text: flag.toUpperCase(), desc: 'uppercase' };
92
92
  }
93
93
  else {
94
- e.suggestions = [
95
- {
96
- desc: 'remove',
97
- range: [e.startIndex - (i && 1), e.endIndex],
98
- text: '',
99
- },
100
- ];
94
+ e.suggestions = [{ desc: 'remove', range: [e.startIndex - (i && 1), e.endIndex], text: '' }];
101
95
  }
102
96
  errors.push(e);
103
97
  }
@@ -92,16 +92,8 @@ class GalleryToken extends index_2.Token {
92
92
  startCol,
93
93
  endCol: startCol + length,
94
94
  suggestions: [
95
- {
96
- desc: 'remove',
97
- range: [start, endIndex],
98
- text: '',
99
- },
100
- {
101
- desc: 'comment',
102
- range: [start, endIndex],
103
- text: `<!--${str}-->`,
104
- },
95
+ { desc: 'remove', range: [start, endIndex], text: '' },
96
+ { desc: 'comment', range: [start, endIndex], text: `<!--${str}-->` },
105
97
  ],
106
98
  });
107
99
  }
@@ -135,21 +135,55 @@ let HeadingToken = (() => {
135
135
  }
136
136
  /** @private */
137
137
  lint(start = this.getAbsoluteIndex(), re) {
138
- const errors = super.lint(start, re), { firstChild, level } = this, innerStr = firstChild.toString(), quotes = firstChild.childNodes.filter((0, debug_1.isToken)('quote')), boldQuotes = quotes.filter(({ bold }) => bold), italicQuotes = quotes.filter(({ italic }) => italic), rect = new rect_1.BoundingRect(this, start);
138
+ const errors = super.lint(start, re), { firstChild, level } = this, innerStr = firstChild.toString(), unbalancedStart = innerStr.startsWith('='), unbalanced = unbalancedStart || innerStr.endsWith('='), quotes = firstChild.childNodes.filter((0, debug_1.isToken)('quote')), boldQuotes = quotes.filter(({ bold }) => bold), italicQuotes = quotes.filter(({ italic }) => italic), rect = new rect_1.BoundingRect(this, start);
139
139
  if (this.level === 1) {
140
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'h1', '<h1>'));
140
+ const e = (0, lint_1.generateForChild)(firstChild, rect, 'h1', '<h1>');
141
+ if (!unbalanced) {
142
+ e.suggestions = [{ desc: 'h2', range: [e.startIndex, e.endIndex], text: `=${innerStr}=` }];
143
+ }
144
+ errors.push(e);
141
145
  }
142
- if (innerStr.startsWith('=') || innerStr.endsWith('=')) {
143
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'unbalanced-header', index_1.default.msg('unbalanced $1 in a section header', '"="')));
146
+ if (unbalanced) {
147
+ const e = (0, lint_1.generateForChild)(firstChild, rect, 'unbalanced-header', index_1.default.msg('unbalanced $1 in a section header', '"="'));
148
+ if (innerStr === '=') {
149
+ //
150
+ }
151
+ else if (unbalancedStart) {
152
+ const [extra] = /^=+/u.exec(innerStr);
153
+ e.suggestions = [
154
+ { desc: `h${level}`, range: [e.startIndex, e.startIndex + extra.length], text: '' },
155
+ { desc: `h${level + extra.length}`, range: [e.endIndex, e.endIndex], text: extra },
156
+ ];
157
+ }
158
+ else {
159
+ const extra = /[^=](=+)$/u.exec(innerStr)[1];
160
+ e.suggestions = [
161
+ { desc: `h${level}`, range: [e.endIndex - extra.length, e.endIndex], text: '' },
162
+ { desc: `h${level + extra.length}`, range: [e.startIndex, e.startIndex], text: extra },
163
+ ];
164
+ }
165
+ errors.push(e);
144
166
  }
145
167
  if (this.closest('html-attrs,table-attrs')) {
146
168
  errors.push((0, lint_1.generateForSelf)(this, rect, 'parsing-order', 'section header in a HTML tag'));
147
169
  }
170
+ const rootStr = this.getRootNode().toString();
148
171
  if (boldQuotes.length % 2) {
149
- errors.push((0, lint_1.generateForChild)(boldQuotes[boldQuotes.length - 1], { ...rect, start: start + level, left: rect.left + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'bold apostrophes')));
172
+ const e = (0, lint_1.generateForChild)(boldQuotes[boldQuotes.length - 1], { ...rect, start: start + level, left: rect.left + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'bold apostrophes')), end = start + level + innerStr.length;
173
+ if (rootStr.slice(e.endIndex, end).trim()) {
174
+ e.suggestions = [{ desc: 'close', range: [end, end], text: "'''" }];
175
+ }
176
+ else {
177
+ e.fix = { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' };
178
+ }
179
+ errors.push(e);
150
180
  }
151
181
  if (italicQuotes.length % 2) {
152
- errors.push((0, lint_1.generateForChild)(italicQuotes[italicQuotes.length - 1], { start: start + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'italic apostrophes')));
182
+ const e = (0, lint_1.generateForChild)(italicQuotes[italicQuotes.length - 1], { start: start + level }, 'format-leakage', index_1.default.msg('unbalanced $1 in a section header', 'italic apostrophes')), end = start + level + innerStr.length;
183
+ e.fix = rootStr.slice(e.endIndex, end).trim()
184
+ ? { desc: 'close', range: [end, end], text: "''" }
185
+ : { desc: 'remove', range: [e.startIndex, e.endIndex], text: '' };
186
+ errors.push(e);
153
187
  }
154
188
  return errors;
155
189
  }
@@ -205,7 +239,7 @@ let HeadingToken = (() => {
205
239
  if (headings?.has(lcId)) {
206
240
  let i = 2;
207
241
  for (; headings.has(`${lcId}_${i}`); i++) {
208
- // pass
242
+ //
209
243
  }
210
244
  id = `${id}_${i}`;
211
245
  headings.add(`${lcId}_${i}`);
package/dist/src/html.js CHANGED
@@ -182,10 +182,15 @@ let HtmlToken = (() => {
182
182
  lint(start = this.getAbsoluteIndex(), re) {
183
183
  const errors = super.lint(start, re), rect = new rect_1.BoundingRect(this, start);
184
184
  if (this.name === 'h1' && !this.closing) {
185
- errors.push((0, lint_1.generateForSelf)(this, rect, 'h1', '<h1>'));
185
+ errors.push({
186
+ ...(0, lint_1.generateForSelf)(this, rect, 'h1', '<h1>'),
187
+ suggestions: [{ desc: 'h2', range: [start + 2, start + 3], text: '2' }],
188
+ });
186
189
  }
187
190
  if (this.closest('table-attrs')) {
188
- errors.push((0, lint_1.generateForSelf)(this, rect, 'parsing-order', 'HTML tag in table attributes'));
191
+ const e = (0, lint_1.generateForSelf)(this, rect, 'parsing-order', 'HTML tag in table attributes');
192
+ e.fix = { desc: 'remove', range: [start, e.endIndex], text: '' };
193
+ errors.push(e);
189
194
  }
190
195
  try {
191
196
  this.findMatchingTag();
@@ -193,38 +198,53 @@ let HtmlToken = (() => {
193
198
  catch (e) {
194
199
  if (e instanceof SyntaxError) {
195
200
  const { message } = e;
196
- const msg = message.split(':')[0].toLowerCase(), error = (0, lint_1.generateForSelf)(this, rect, 'unmatched-tag', msg);
197
- if (msg === 'unclosed tag' && !this.closest('heading-title')) {
198
- if (formattingTags.has(this.name)) {
199
- const childNodes = this.parentNode?.childNodes, i = childNodes?.indexOf(this);
200
- if (!childNodes?.slice(0, i).some(({ type, name }) => type === 'html' && name === this.name)) {
201
+ const msg = message.split(':')[0].toLowerCase(), error = (0, lint_1.generateForSelf)(this, rect, 'unmatched-tag', msg), noSelfClosing = {
202
+ desc: 'no self-closing',
203
+ range: [error.endIndex - 2, error.endIndex - 1],
204
+ text: '',
205
+ };
206
+ switch (msg) {
207
+ case 'unclosed tag': {
208
+ const childNodes = this.parentNode?.childNodes;
209
+ if (formattingTags.has(this.name)
210
+ && childNodes?.slice(0, childNodes.indexOf(this))
211
+ .some(({ type, name }) => type === 'html' && name === this.name)) {
212
+ error.suggestions = [{ desc: 'close', range: [start + 1, start + 1], text: '/' }];
213
+ }
214
+ else if (!this.closest('heading-title')) {
201
215
  error.severity = 'warning';
202
216
  }
217
+ break;
203
218
  }
204
- else {
205
- error.severity = 'warning';
219
+ case 'unmatched closing tag': {
220
+ const ancestor = this.closest('magic-word');
221
+ if (ancestor && magicWords.has(ancestor.name)) {
222
+ error.severity = 'warning';
223
+ }
224
+ else {
225
+ error.suggestions = [{ desc: 'remove', range: [start, error.endIndex], text: '' }];
226
+ }
227
+ break;
206
228
  }
207
- }
208
- else if (msg === 'unmatched closing tag') {
209
- const ancestor = this.closest('magic-word');
210
- if (ancestor && magicWords.has(ancestor.name)) {
211
- error.severity = 'warning';
229
+ case 'tag that is both closing and self-closing': {
230
+ const { html: [normalTags, , voidTags] } = this.getAttribute('config'), open = { desc: 'open', range: [start + 1, start + 2], text: '' };
231
+ if (voidTags.includes(this.name)) {
232
+ error.fix = open;
233
+ }
234
+ else if (normalTags.includes(this.name)) {
235
+ error.fix = noSelfClosing;
236
+ }
237
+ else {
238
+ error.suggestions = [open, noSelfClosing];
239
+ }
240
+ break;
212
241
  }
213
- else {
242
+ case 'invalid self-closing tag':
214
243
  error.suggestions = [
215
- {
216
- desc: 'remove',
217
- range: [start, error.endIndex],
218
- text: '',
219
- },
244
+ noSelfClosing,
245
+ { desc: 'close', range: [error.endIndex - 2, error.endIndex], text: `></${this.name}>` },
220
246
  ];
221
- }
222
- }
223
- else if (msg === 'tag that is both closing and self-closing') {
224
- const { html: [, , voidTags] } = this.getAttribute('config');
225
- if (voidTags.includes(this.name)) {
226
- error.fix = { range: [start + 1, start + 2], text: '', desc: 'open' };
227
- }
247
+ // no default
228
248
  }
229
249
  errors.push(error);
230
250
  }
@@ -185,7 +185,9 @@ class ImageParameterToken extends index_2.Token {
185
185
  errors.push(e);
186
186
  }
187
187
  else if (typeof link === 'object' && link.encoded) {
188
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'url-encoding', 'unnecessary URL encoding in an internal link'));
188
+ const e = (0, lint_1.generateForSelf)(this, { start }, 'url-encoding', 'unnecessary URL encoding in an internal link');
189
+ e.suggestions = [{ desc: 'decode', range: [start, e.endIndex], text: (0, string_1.rawurldecode)(this.text()) }];
190
+ errors.push(e);
189
191
  }
190
192
  return errors;
191
193
  }
@@ -101,7 +101,14 @@ class ImagemapToken extends index_2.Token {
101
101
  errors.push(...this.childNodes.filter(child => {
102
102
  const str = child.toString().trim();
103
103
  return child.type === 'noinclude' && str && !str.startsWith('#');
104
- }).map(child => (0, lint_1.generateForChild)(child, rect, 'invalid-imagemap', 'invalid link in <imagemap>')));
104
+ }).map(child => {
105
+ const e = (0, lint_1.generateForChild)(child, rect, 'invalid-imagemap', 'invalid link in <imagemap>');
106
+ e.suggestions = [
107
+ { desc: 'remove', range: [e.startIndex - 1, e.endIndex], text: '' },
108
+ { desc: 'comment', range: [e.startIndex, e.startIndex], text: '# ' },
109
+ ];
110
+ return e;
111
+ }));
105
112
  }
106
113
  else {
107
114
  errors.push((0, lint_1.generateForSelf)(this, rect, 'invalid-imagemap', '<imagemap> without an image'));
@@ -3,7 +3,7 @@ import { AstElement } from '../lib/element';
3
3
  import { AstText } from '../lib/text';
4
4
  import type { LintError, TokenTypes } from '../base';
5
5
  import type { Title } from '../lib/title';
6
- import type { AstNodes, ExtToken, IncludeToken, HtmlToken, TranscludeToken, CommentToken, FileToken, LinkToken, RedirectTargetToken, ExtLinkToken, MagicLinkToken, ImageParameterToken } from '../internal';
6
+ import type { AstNodes, IncludeToken, HtmlToken, ExtToken, TranscludeToken, CommentToken, FileToken, LinkToken, RedirectTargetToken, ExtLinkToken, MagicLinkToken, ImageParameterToken } from '../internal';
7
7
  import { Ranges } from '../lib/ranges';
8
8
  import { AstRange } from '../lib/range';
9
9
  import type { Range } from '../lib/ranges';
package/dist/src/index.js CHANGED
@@ -446,47 +446,35 @@ class Token extends element_1.AstElement {
446
446
  index_1.default.viewOnly = true;
447
447
  let errors = super.lint(start, re);
448
448
  if (this.type === 'root') {
449
- const record = new Map(), selector = 'category,html-attr#id,ext-attr#id,table-attr#id,ext-attr#name';
449
+ const record = new Map(), selector = 'category,html-attr#id,ext-attr#id,table-attr#id';
450
450
  for (const cat of this.querySelectorAll(selector)) {
451
451
  let key;
452
452
  if (cat.type === 'category') {
453
453
  key = cat.name;
454
454
  }
455
455
  else {
456
- const value = cat.getValue(), attrs = cat.parentNode;
457
- if (cat.name === 'id') {
458
- key = `#${value === true ? '' : value}`;
456
+ const value = cat.getValue();
457
+ if (value && value !== true) {
458
+ key = `#${value}`;
459
459
  }
460
- else if (cat.tag === 'ref' && value !== true && value
461
- && attrs.parentNode.innerText) {
462
- const group = attrs.getAttr('group');
463
- key = `${typeof group === 'string' && group || ' '}#${value}`;
460
+ }
461
+ if (key) {
462
+ const thisCat = record.get(key);
463
+ if (thisCat) {
464
+ thisCat.add(cat);
464
465
  }
465
466
  else {
466
- continue;
467
+ record.set(key, new Set([cat]));
467
468
  }
468
469
  }
469
- const thisCat = record.get(key);
470
- if (thisCat) {
471
- thisCat.add(cat);
472
- }
473
- else {
474
- record.set(key, new Set([cat]));
475
- }
476
470
  }
477
471
  for (const [key, value] of record) {
478
472
  if (value.size > 1 && !key.startsWith('#mw-customcollapsible-')) {
479
- const isCat = !key.includes('#'), msg = `duplicated ${isCat ? 'category' : 'id/name'}`, severity = key.startsWith('#') ? 'warning' : 'error';
473
+ const isCat = !key.startsWith('#'), msg = `duplicated ${isCat ? 'category' : 'id'}`, severity = isCat ? 'error' : 'warning';
480
474
  errors.push(...[...value].map(cat => {
481
475
  const e = (0, lint_1.generateForSelf)(cat, { start: cat.getAbsoluteIndex() }, 'no-duplicate', msg, severity);
482
476
  if (isCat) {
483
- e.suggestions = [
484
- {
485
- desc: 'remove',
486
- range: [e.startIndex, e.endIndex],
487
- text: '',
488
- },
489
- ];
477
+ e.suggestions = [{ desc: 'remove', range: [e.startIndex, e.endIndex], text: '' }];
490
478
  }
491
479
  return e;
492
480
  }));
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LinkBaseToken = void 0;
4
4
  const lint_1 = require("../../util/lint");
5
5
  const constants_1 = require("../../util/constants");
6
+ const string_1 = require("../../util/string");
6
7
  const rect_1 = require("../../lib/rect");
7
8
  const index_1 = require("../../index");
8
9
  const index_2 = require("../index");
9
10
  const atom_1 = require("../atom");
10
11
  /* NOT FOR BROWSER */
11
12
  const debug_1 = require("../../util/debug");
12
- const string_1 = require("../../util/string");
13
+ const string_2 = require("../../util/string");
13
14
  const html_1 = require("../../util/html");
14
15
  /* NOT FOR BROWSER END */
15
16
  /**
@@ -156,7 +157,9 @@ class LinkBaseToken extends index_2.Token {
156
157
  errors.push((0, lint_1.generateForChild)(target, rect, 'unknown-page', 'template in an internal link target', 'warning'));
157
158
  }
158
159
  if (encoded) {
159
- errors.push((0, lint_1.generateForChild)(target, rect, 'url-encoding', 'unnecessary URL encoding in an internal link'));
160
+ const e = (0, lint_1.generateForChild)(target, rect, 'url-encoding', 'unnecessary URL encoding in an internal link');
161
+ e.suggestions = [{ desc: 'decode', range: [e.startIndex, e.endIndex], text: (0, string_1.rawurldecode)(target.text()) }];
162
+ errors.push(e);
160
163
  }
161
164
  if (type === 'link' || type === 'category') {
162
165
  const textNode = linkText?.childNodes.find((c) => c.type === 'text' && c.data.includes('|'));
@@ -221,7 +224,7 @@ class LinkBaseToken extends index_2.Token {
221
224
  setFragment(fragment) {
222
225
  const { type, name } = this;
223
226
  if (fragment === undefined || isLink(type)) {
224
- fragment &&= (0, string_1.encode)(fragment);
227
+ fragment &&= (0, string_2.encode)(fragment);
225
228
  this.setTarget(`${name}${fragment === undefined ? '' : `#${fragment}`}`);
226
229
  }
227
230
  }
@@ -252,7 +255,7 @@ class LinkBaseToken extends index_2.Token {
252
255
  const { link, length, lastChild, type } = this, title = link.getTitleAttr();
253
256
  return (0, html_1.font)(this, `<a${link.interwiki && ' class="extiw"'} href="${link.getUrl()}"${title && ` title="${title}"`}>${type === 'link' && length > 1
254
257
  ? lastChild.toHtmlInternal({ ...opt, nowrap: true })
255
- : (0, string_1.sanitize)(this.innerText)}</a>`);
258
+ : (0, string_2.sanitize)(this.innerText)}</a>`);
256
259
  }
257
260
  return '';
258
261
  }
@@ -150,7 +150,11 @@ class FileToken extends base_1.LinkBaseToken {
150
150
  * @param msg 消息键
151
151
  * @param p1 替换$1
152
152
  */
153
- const generate = (msg, p1) => (arg) => (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', index_1.default.msg(`${msg} image $1 parameter`, p1));
153
+ const generate = (msg, p1) => (arg) => {
154
+ const e = (0, lint_1.generateForChild)(arg, rect, 'no-duplicate', index_1.default.msg(`${msg} image $1 parameter`, p1));
155
+ e.suggestions = [{ desc: 'remove', range: [e.startIndex - 1, e.endIndex], text: '' }];
156
+ return e;
157
+ };
154
158
  for (const key of keys) {
155
159
  if (key === 'invalid' || key === 'width' && unscaled) {
156
160
  continue;
@@ -108,7 +108,9 @@ let GalleryImageToken = (() => {
108
108
  lint(start = this.getAbsoluteIndex(), re) {
109
109
  const errors = super.lint(start, re), { ns, interwiki } = this.getAttribute('title');
110
110
  if (interwiki || ns !== 6) {
111
- errors.push((0, lint_1.generateForSelf)(this, { start }, 'invalid-gallery', 'invalid gallery image'));
111
+ const e = (0, lint_1.generateForSelf)(this, { start }, 'invalid-gallery', 'invalid gallery image');
112
+ e.suggestions = [{ desc: 'prefix', range: [start, start], text: 'File:' }];
113
+ errors.push(e);
112
114
  }
113
115
  return errors;
114
116
  }