wikilint 2.46.1 → 2.46.3

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 (42) hide show
  1. package/README.md +19 -0
  2. package/coverage/badge.svg +1 -1
  3. package/dist/base.d.mts +2 -3
  4. package/dist/base.d.ts +2 -3
  5. package/dist/bin/cli.js +11 -0
  6. package/dist/bin/config.js +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/lib/lsp.d.ts +2 -2
  9. package/dist/lib/lsp.js +29 -26
  10. package/dist/lib/text.js +5 -3
  11. package/dist/lib/title.js +4 -5
  12. package/dist/mixin/attributesParent.js +3 -0
  13. package/dist/mixin/elementLike.js +4 -1
  14. package/dist/mixin/gapped.js +4 -0
  15. package/dist/mixin/hidden.js +4 -0
  16. package/dist/mixin/noEscape.js +4 -0
  17. package/dist/mixin/nodeLike.js +3 -0
  18. package/dist/mixin/padded.js +4 -0
  19. package/dist/mixin/paramLineParent.js +4 -1
  20. package/dist/parser/commentAndExt.js +4 -4
  21. package/dist/parser/links.js +3 -3
  22. package/dist/parser/magicLinks.js +4 -5
  23. package/dist/src/arg.js +6 -7
  24. package/dist/src/attribute.js +4 -4
  25. package/dist/src/attributes.js +1 -1
  26. package/dist/src/heading.js +1 -1
  27. package/dist/src/imageParameter.js +11 -13
  28. package/dist/src/index.js +1 -1
  29. package/dist/src/link/base.js +5 -11
  30. package/dist/src/magicLink.js +0 -1
  31. package/dist/src/multiLine/gallery.js +19 -17
  32. package/dist/src/nested.js +1 -1
  33. package/dist/src/nowiki/quote.js +2 -2
  34. package/dist/src/table/td.js +1 -1
  35. package/dist/src/tagPair/ext.js +2 -2
  36. package/dist/src/tagPair/include.js +1 -1
  37. package/dist/src/tagPair/translate.js +2 -2
  38. package/dist/src/transclude.js +41 -32
  39. package/dist/util/lint.js +1 -1
  40. package/dist/util/sharable.js +0 -1
  41. package/dist/util/string.js +1 -8
  42. package/package.json +10 -10
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![CI](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/node.js.yml/badge.svg)](https://github.com/bhsd-harry/wikiparser-node/actions/workflows/node.js.yml)
8
8
  [![NPM downloads](https://img.shields.io/npm/dm/wikilint)](https://www.npmjs.com/package/wikilint)
9
9
  ![Coverage](./coverage/badge.svg)
10
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13661/badge)](https://www.bestpractices.dev/projects/13661)
10
11
 
11
12
  This is a minimal version of [WikiParser-Node](https://www.npmjs.com/package/wikiparser-node). The [Wikitext LSP](https://www.npmjs.com/package/wikitext-lsp) and the [WikiParser Language Server](https://marketplace.visualstudio.com/items?itemName=Bhsd.vscode-extension-wikiparser) VS Code extension is written based on this package.
12
13
 
@@ -39,6 +40,7 @@ npm i mathoid-texvcjs vscode-css-languageservice
39
40
 
40
41
  | Option | Argument | Description | Default |
41
42
  | :----- | :------- | :---------- | :------ |
43
+ | `--` | | End of options | |
42
44
  | `-c`, `--config` | \<path or preset config\> | Choose parser's [configuration](https://github.com/bhsd-harry/wikiparser-node/wiki/Parser-%28EN%29#config) | `default` |
43
45
  | `--cache` | | Enable caching | `false` |
44
46
  | `--cache-file` | \<path\> | Specify cache file and enable caching | `.wikilintcache` |
@@ -73,3 +75,20 @@ The generated configuration file will be saved in the `config` directory. You ca
73
75
  # For example:
74
76
  npx wikilint --config frwiki *.wiki
75
77
  ```
78
+
79
+ ## Use with pre-commit
80
+
81
+ To use `wikilint` with [pre-commit](https://pre-commit.com/), add the following to your [`.pre-commit-config.yaml`](https://pre-commit.com/#adding-pre-commit-plugins-to-your-project):
82
+
83
+ ```yaml
84
+ repos:
85
+ - repo: local
86
+ hooks:
87
+ - id: wikilint
88
+ name: WikiLint
89
+ # You can add any CLI options as needed
90
+ entry: wikilint
91
+ language: node
92
+ additional_dependencies: ['wikilint@2.46.2']
93
+ files: \.(mediawiki|wiki|wikitext)$
94
+ ```
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="98" height="20" role="img" aria-label="Coverage: 96%"><title>Coverage: 96%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="98" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="35" height="20" fill="#4c1"/><rect width="98" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="795" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">96%</text><text x="795" y="140" transform="scale(.1)" fill="#fff" textLength="250">96%</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="98" height="20" aria-label="Coverage: 95%"><title>Coverage: 95%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="98" height="20" fill="#fff" rx="3"/></clipPath><g clip-path="url(#r)"><path fill="#555" d="M0 0h63v20H0z"/><path fill="#4c1" d="M63 0h35v20H63z"/><path fill="url(#s)" d="M0 0h98v20H0z"/></g><g fill="#fff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="110" text-anchor="middle" text-rendering="geometricPrecision"><text x="325" y="150" fill="#010101" fill-opacity=".3" aria-hidden="true" textLength="530" transform="scale(.1)">Coverage</text><text x="325" y="140" textLength="530" transform="scale(.1)">Coverage</text><text x="795" y="150" fill="#010101" fill-opacity=".3" aria-hidden="true" textLength="250" transform="scale(.1)">95%</text><text x="795" y="140" textLength="250" transform="scale(.1)">95%</text></g></svg>
package/dist/base.d.mts CHANGED
@@ -128,11 +128,10 @@ export interface LanguageService {
128
128
  * Provide color decorators
129
129
  *
130
130
  * 提供颜色指示
131
- * @param rgba color parser / 颜色解析函数
132
131
  * @param text source Wikitext / 源代码
133
- * @param hsl whether HSL colors are treated / 是否允许HSL颜色
132
+ * @param rgba color parser / 颜色解析函数
134
133
  */
135
- provideDocumentColors(rgba: (s: string) => [number, number, number, number] | [], text: string, hsl?: boolean): Promise<ColorInformation[]>;
134
+ provideDocumentColors(text: string, rgba?: (s: string) => [number, number, number, number] | []): Promise<ColorInformation[]>;
136
135
  /**
137
136
  * Provide color pickers
138
137
  *
package/dist/base.d.ts CHANGED
@@ -128,11 +128,10 @@ export interface LanguageService {
128
128
  * Provide color decorators
129
129
  *
130
130
  * 提供颜色指示
131
- * @param rgba color parser / 颜色解析函数
132
131
  * @param text source Wikitext / 源代码
133
- * @param hsl whether HSL colors are treated / 是否允许HSL颜色
132
+ * @param rgba color parser / 颜色解析函数
134
133
  */
135
- provideDocumentColors(rgba: (s: string) => [number, number, number, number] | [], text: string, hsl?: boolean): Promise<ColorInformation[]>;
134
+ provideDocumentColors(text: string, rgba?: (s: string) => [number, number, number, number] | []): Promise<ColorInformation[]>;
136
135
  /**
137
136
  * Provide color pickers
138
137
  *
package/dist/bin/cli.js CHANGED
@@ -11,6 +11,7 @@ const util_1 = __importDefault(require("util"));
11
11
  const index_1 = __importDefault(require("../index"));
12
12
  const man = `
13
13
  Available options:
14
+ -- End of options
14
15
  -c, --config <path or preset config> Choose parser's configuration
15
16
  --cache Enable caching
16
17
  --cache-file <path> Specify cache file
@@ -228,6 +229,10 @@ for (let i = 2; i < argv.length; i++) {
228
229
  process.exit(0);
229
230
  break;
230
231
  }
232
+ case '--':
233
+ files = [...files, ...argv.slice(i + 1)];
234
+ i = argv.length;
235
+ break;
231
236
  default: {
232
237
  let known = true;
233
238
  if (option.includes('=')) {
@@ -277,6 +282,9 @@ if (quiet && strict) {
277
282
  }
278
283
  if (recursive) {
279
284
  files = files.flatMap(file => {
285
+ if (!fs_1.default.existsSync(file)) {
286
+ return [];
287
+ }
280
288
  if (fs_1.default.statSync(file).isFile()) {
281
289
  return file;
282
290
  }
@@ -347,6 +355,9 @@ const { mtimeMs } = fs_1.default.statSync(config), obj = cache?.[include ? 'incl
347
355
  process.exit(0);
348
356
  }
349
357
  for (const file of new Set(files.map(f => path_1.default.resolve(f)))) {
358
+ if (!fs_1.default.existsSync(file)) {
359
+ continue;
360
+ }
350
361
  const stat = fs_1.default.statSync(file);
351
362
  if (stat.isDirectory()) {
352
363
  console.error(`"${file}" is a directory. Please use -r or --recursive to lint directories.`);
@@ -41,7 +41,7 @@ const filterGadget = (id) => {
41
41
  const n = Number(id);
42
42
  return n < 2300 || n > 2303; // Gadget, Gadget talk, Gadget definition, Gadget definition talk
43
43
  };
44
- const pkg = 'wikilint', version = '2.46.1';
44
+ const pkg = 'wikilint', version = '2.46.3';
45
45
  /**
46
46
  * Get the parser configuration for a Wikimedia Foundation project.
47
47
  * @param site site nickname
package/dist/index.js CHANGED
@@ -135,7 +135,7 @@ const Parser = {
135
135
  /** @implements */
136
136
  msg(msg, arg = '') {
137
137
  LINT: return msg
138
- && (this.i18n[msg] ?? msg).replace('$1', this.msg(arg));
138
+ && (this.i18n[msg] ?? msg).replace('$1', () => this.msg(arg));
139
139
  },
140
140
  /** @implements */
141
141
  normalizeTitle(title, defaultNs = 0, include, config = Parser.getConfig(), opt, accum = []) {
@@ -191,6 +191,7 @@ const Parser = {
191
191
  }
192
192
  maxStage ??= constants_1.MAX_STAGE;
193
193
  config ??= this.getConfig();
194
+ // eslint-disable-next-line unicorn/prefer-smaller-scope
194
195
  let types;
195
196
  LSP: if (typeof maxStage !== 'number') {
196
197
  types = Array.isArray(maxStage) ? maxStage : [maxStage];
package/dist/lib/lsp.d.ts CHANGED
@@ -22,10 +22,10 @@ export declare class LanguageService implements LanguageServiceBase {
22
22
  * Provide color decorators
23
23
  *
24
24
  * 提供颜色指示
25
- * @param rgba color parser / 颜色解析函数
26
25
  * @param text source Wikitext / 源代码
26
+ * @param rgba color parser / 颜色解析函数
27
27
  */
28
- provideDocumentColors(rgba: (s: string) => [number, number, number, number] | [], text: string): Promise<ColorInformation[]>;
28
+ provideDocumentColors(text: string, rgba?: (color: string) => [number, number, number, number] | []): Promise<ColorInformation[]>;
29
29
  /**
30
30
  * Provide color pickers
31
31
  *
package/dist/lib/lsp.js CHANGED
@@ -324,6 +324,7 @@ function getStylelintPos(rect, bottom, lineOrCode, columnOrOffset) {
324
324
  return (0, lint_1.getEndPos)(top, left, ...adjustPos(height, width, lineOrCode - start, columnOrOffset));
325
325
  }
326
326
  const lines = lineOrCode.slice(0, columnOrOffset).split(/\r?\n/u);
327
+ // eslint-disable-next-line unicorn/no-useless-recursion
327
328
  return getStylelintPos(rect, bottom, lines.length, lines.at(-1).length);
328
329
  }
329
330
  /**
@@ -497,10 +498,13 @@ class LanguageService {
497
498
  * Provide color decorators
498
499
  *
499
500
  * 提供颜色指示
500
- * @param rgba color parser / 颜色解析函数
501
501
  * @param text source Wikitext / 源代码
502
+ * @param rgba color parser / 颜色解析函数
502
503
  */
503
- async provideDocumentColors(rgba, text) {
504
+ async provideDocumentColors(text, rgba = color_1.rgba) {
505
+ if (typeof text === 'function' && typeof rgba === 'string') {
506
+ [rgba, text] = [text, rgba];
507
+ }
504
508
  const root = await this.#queue(text);
505
509
  return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse().flatMap(token => {
506
510
  const { type, childNodes,
@@ -576,7 +580,7 @@ class LanguageService {
576
580
  + '|'
577
581
  // attribute key ($8, $10)
578
582
  + String.raw `<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\9))?(?=\s))*)?\s(\w*)`
579
- + ')$', 'iu'), allSwitches = doubleUnderscore.slice(0, 2).flat();
583
+ + ')$', 'iu'), allSwitches = doubleUnderscore.slice(0, 2).flat().map(s => s.toUpperCase());
580
584
  this.#completionConfig = [
581
585
  {
582
586
  re,
@@ -607,7 +611,7 @@ class LanguageService {
607
611
  const { re, allTags, functions, switches, jaSwitches, protocols, params, tags, ext, } = this.#prepareCompletionConfig(), { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], mt = re.exec(curLine?.slice(0, character) ?? ''), [, , iAlias, sAlias] = this.config.doubleUnderscore;
608
612
  if (mt?.[1] !== undefined) { // tag
609
613
  const closing = mt[1].startsWith('/');
610
- return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
614
+ return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trimStart().startsWith('>') ? '>' : '');
611
615
  }
612
616
  else if (mt?.[4] || mt?.[5] && jaSwitches.length > 0) { // behavior switch
613
617
  const isJa = mt[5] !== undefined;
@@ -639,11 +643,12 @@ class LanguageService {
639
643
  return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
640
644
  .map(({ name }) => name), 'Variable', match, position);
641
645
  }
642
- const [insensitive, sensitive] = this.config.parserFunction, next = curLine.charAt(character), colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
646
+ const [insensitive, sensitive] = this.config.parserFunction, colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
643
647
  if (mt[2] === '[[') { // link
644
648
  return getCompletion(root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position);
645
649
  }
646
650
  // parser function or template
651
+ const next = curLine.charAt(character);
647
652
  let words = functions;
648
653
  if (next === ':') {
649
654
  words = functions.filter(s => !s.endsWith(':'));
@@ -687,20 +692,20 @@ class LanguageService {
687
692
  }
688
693
  ({ type, parentNode } = cur);
689
694
  }
690
- if (mt?.[7] !== undefined || type === 'image-parameter') { // image parameter
695
+ if (type === 'image-parameter' || mt?.[7] !== undefined) { // image parameter
691
696
  const index = root.indexFromPos(line, character), match = mt?.[7]?.trimStart()
692
697
  ?? this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), equal = this.#text[index] === '=';
693
698
  return getCompletion(params, 'Property', match, position)
694
699
  .filter(({ label }) => !equal || !/[= ]$/u.test(label));
695
700
  }
696
- else if (mt?.[8] !== undefined || type === 'attr-key') { // attribute key
697
- const tag = mt?.[8]?.toLowerCase() ?? parentNode.tag, key = mt?.[10]
698
- ?? cur.toString().slice(0, character - root.posFromIndex(cur.getAbsoluteIndex()).left);
701
+ else if (type === 'attr-key' || mt?.[8] !== undefined) { // attribute key
702
+ const tag = mt?.[8]?.toLowerCase() ?? parentNode.tag;
699
703
  if (!tags.has(tag)) {
700
704
  return undefined;
701
705
  }
702
- const thisHtmlAttrs = sharable_1.htmlAttrs[tag], thisExtAttrs = sharable_1.extAttrs[tag], extCompletion = thisExtAttrs && getCompletion(thisExtAttrs, 'Field', key, position);
703
- return ext.includes(tag) && !thisHtmlAttrs
706
+ const key = mt?.[10]
707
+ ?? cur.toString().slice(0, character - root.posFromIndex(cur.getAbsoluteIndex()).left), thisHtmlAttrs = sharable_1.htmlAttrs[tag], thisExtAttrs = sharable_1.extAttrs[tag], extCompletion = thisExtAttrs && getCompletion(thisExtAttrs, 'Field', key, position);
708
+ return !thisHtmlAttrs && ext.includes(tag)
704
709
  ? extCompletion
705
710
  : [
706
711
  ...extCompletion ?? [],
@@ -871,18 +876,16 @@ class LanguageService {
871
876
  source: 'Stylelint',
872
877
  code: rule,
873
878
  message: msg.slice(0, msg.lastIndexOf('(') - 1),
874
- ...fix
875
- ? {
876
- data: [
877
- {
878
- range: getStylelintPos(rects[i], bottoms[i], code, fix.range),
879
- newText: fix.text,
880
- title: `Fix: ${rule}`,
881
- fix: true,
882
- },
883
- ],
884
- }
885
- : {},
879
+ ...fix && {
880
+ data: [
881
+ {
882
+ range: getStylelintPos(rects[i], bottoms[i], code, fix.range),
883
+ newText: fix.text,
884
+ title: `Fix: ${rule}`,
885
+ fix: true,
886
+ },
887
+ ],
888
+ },
886
889
  };
887
890
  });
888
891
  }
@@ -1321,7 +1324,7 @@ class LanguageService {
1321
1324
  f = offsetNode.text().trim();
1322
1325
  colon = parentNode.getAttribute('colon');
1323
1326
  }
1324
- else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
1327
+ else if (length === 1 && offsetNode.is('magic-word') && !offsetNode.modifier
1325
1328
  && (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
1326
1329
  info = this.#getParserFunction(name);
1327
1330
  f = offsetNode.firstChild.text().trim();
@@ -1432,9 +1435,9 @@ class LanguageService {
1432
1435
  label: `{{${f}${params.length === 0 ? '' : colon}${params.map(({ label }) => label).join('|')}}}`,
1433
1436
  parameters: params.map(({ label, const: c }) => ({
1434
1437
  label,
1435
- ...c ? { documentation: 'Predefined parameter' } : undefined,
1438
+ ...c && { documentation: 'Predefined parameter' },
1436
1439
  })),
1437
- ...params.length < n ? { activeParameter: Math.min(j, params.length - 1) } : undefined,
1440
+ ...params.length < n && { activeParameter: Math.min(j, params.length - 1) },
1438
1441
  })),
1439
1442
  activeParameter: j,
1440
1443
  };
package/dist/lib/text.js CHANGED
@@ -100,7 +100,8 @@ class AstText extends node_1.AstNode {
100
100
  }
101
101
  }
102
102
  errorRegex ??= parentNode.isPlain() && !noLinkTypes.has(type) ? errorSyntax : errorSyntaxUrl;
103
- if (data.search(errorRegex) === -1) {
103
+ errorRegex.lastIndex = 0;
104
+ if (!errorRegex.test(data)) {
104
105
  return [];
105
106
  }
106
107
  errorRegex.lastIndex = 0;
@@ -127,7 +128,7 @@ class AstText extends node_1.AstNode {
127
128
  }
128
129
  else if (error.startsWith('<') && !tags.has(tag.toLowerCase())
129
130
  || lbrackInExtLinkText && (/&(?:rbrack|#93|#x5[Dd];);/u.test(data.slice(index + 1))
130
- || nextSibling?.is('ext') && nextName === 'nowiki'
131
+ || nextName === 'nowiki' && nextSibling?.is('ext')
131
132
  && nextSibling.innerText?.includes(']'))) {
132
133
  continue;
133
134
  }
@@ -142,7 +143,7 @@ class AstText extends node_1.AstNode {
142
143
  errorRegex.lastIndex--;
143
144
  }
144
145
  // Rule & Severity
145
- let startIndex = start + index, endIndex = startIndex + length, rule, severity, endLine, endCol;
146
+ let startIndex = start + index, endIndex = startIndex + length, rule, severity;
146
147
  const nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], leftBracket = lbrace || lbrack, lConverter = error === '{' && previousChar === '-' && variants.length > 0, rConverter = error === '}' && nextChar === '-' && variants.length > 0, brokenExtLink = lbrack && nextType === 'free-ext-link' && !data.slice(index + 1).trim()
147
148
  || rbrack && previousType === 'free-ext-link' && index === 0;
148
149
  if (magicLink) {
@@ -222,6 +223,7 @@ class AstText extends node_1.AstNode {
222
223
  }
223
224
  // LintError
224
225
  const pos = this.posFromIndex(index), { line: startLine, character: startCol } = (0, lint_1.getEndPos)(top, left, pos.top + 1, pos.left);
226
+ let endLine, endCol;
225
227
  if (char === '=') {
226
228
  const lineEnd = data.indexOf('\n', index);
227
229
  let sibling = nextSibling, line;
package/dist/lib/title.js CHANGED
@@ -8,7 +8,7 @@ const string_1 = require("../util/string");
8
8
  * @param title 标题
9
9
  */
10
10
  const resolve = (title) => {
11
- const [, { length }, sub] = /^((?:\.\.\/)*)([\s\S]*)/u.exec(title);
11
+ const [, { length }, sub] = /^((?:\.\.\/)*)(.*)/su.exec(title);
12
12
  return [length / 3, sub];
13
13
  };
14
14
  /**
@@ -188,10 +188,9 @@ class Title {
188
188
  }
189
189
  const { title, fragment } = this;
190
190
  if (title) {
191
- return this.#path.replace('$1', encodeURIComponent(title)
192
- + (fragment
193
- ? `#${encodeURIComponent(fragment)}`
194
- : ''));
191
+ return this.#path.replace('$1', () => encodeURIComponent(title) + (fragment
192
+ ? `#${encodeURIComponent(fragment)}`
193
+ : ''));
195
194
  }
196
195
  return fragment === undefined ? '' : `#${encodeURIComponent(fragment)}`;
197
196
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.attributesParent = void 0;
4
+ /* NOT FOR BROWSER ONLY */
4
5
  const debug_1 = require("../util/debug");
5
6
  /**
6
7
  * 子节点含有AttributesToken的类
@@ -20,7 +21,9 @@ const attributesParent = (i = 0) => (constructor) => {
20
21
  return this.#getAttributesChild().getAttr(key);
21
22
  }
22
23
  }
24
+ /* NOT FOR BROWSER ONLY */
23
25
  (0, debug_1.mixin)(AttributesParent, constructor);
26
+ /* NOT FOR BROWSER ONLY END */
24
27
  return AttributesParent;
25
28
  }
26
29
  };
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.elementLike = void 0;
4
- const debug_1 = require("../util/debug");
5
4
  const selector_1 = require("../util/selector");
5
+ /* NOT FOR BROWSER ONLY */
6
+ const debug_1 = require("../util/debug");
6
7
  /** @ignore */
7
8
  const elementLike = (constructor) => {
8
9
  LINT: {
@@ -67,7 +68,9 @@ const elementLike = (constructor) => {
67
68
  }
68
69
  }
69
70
  }
71
+ /* NOT FOR BROWSER ONLY */
70
72
  (0, debug_1.mixin)(ElementLike, constructor);
73
+ /* NOT FOR BROWSER ONLY END */
71
74
  return ElementLike;
72
75
  }
73
76
  };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ /* NOT FOR BROWSER ONLY */
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.gapped = void 0;
4
5
  const debug_1 = require("../util/debug");
6
+ /* NOT FOR BROWSER ONLY END */
5
7
  /**
6
8
  * 给定 gap 的类
7
9
  * @param gap
@@ -12,7 +14,9 @@ const gapped = (gap = 1) => (constructor) => {
12
14
  return gap;
13
15
  }
14
16
  }
17
+ /* NOT FOR BROWSER ONLY */
15
18
  (0, debug_1.mixin)(GappedToken, constructor);
19
+ /* NOT FOR BROWSER ONLY END */
16
20
  return GappedToken;
17
21
  };
18
22
  exports.gapped = gapped;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hiddenToken = void 0;
4
+ /* NOT FOR BROWSER ONLY */
4
5
  const debug_1 = require("../util/debug");
6
+ /* NOT FOR BROWSER ONLY END */
5
7
  /**
6
8
  * 解析后不可见的类
7
9
  * @param linter 是否覆写 lint 方法
@@ -16,7 +18,9 @@ const hiddenToken = (linter = true, html = true) => (constructor) => {
16
18
  LINT: return linter ? [] : super.lint(start);
17
19
  }
18
20
  }
21
+ /* NOT FOR BROWSER ONLY */
19
22
  (0, debug_1.mixin)(AnyHiddenToken, constructor);
23
+ /* NOT FOR BROWSER ONLY END */
20
24
  return AnyHiddenToken;
21
25
  };
22
26
  exports.hiddenToken = hiddenToken;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ /* NOT FOR BROWSER ONLY */
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.noEscape = void 0;
4
5
  const debug_1 = require("../util/debug");
6
+ /* NOT FOR BROWSER ONLY END */
5
7
  /**
6
8
  * 不需要转义的类
7
9
  * @ignore
@@ -13,7 +15,9 @@ const noEscape = (constructor) => {
13
15
  //
14
16
  }
15
17
  }
18
+ /* NOT FOR BROWSER ONLY */
16
19
  (0, debug_1.mixin)(NoEscapeToken, constructor);
20
+ /* NOT FOR BROWSER ONLY END */
17
21
  return NoEscapeToken;
18
22
  }
19
23
  };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nodeLike = void 0;
4
+ /* NOT FOR BROWSER ONLY */
4
5
  const debug_1 = require("../util/debug");
5
6
  /** @ignore */
6
7
  const nodeLike = (constructor) => {
@@ -18,7 +19,9 @@ const nodeLike = (constructor) => {
18
19
  LINT: return this.getDimension().width;
19
20
  }
20
21
  }
22
+ /* NOT FOR BROWSER ONLY */
21
23
  (0, debug_1.mixin)(NodeLike, constructor);
24
+ /* NOT FOR BROWSER ONLY END */
22
25
  return NodeLike;
23
26
  };
24
27
  exports.nodeLike = nodeLike;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ /* NOT FOR BROWSER ONLY */
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.padded = void 0;
4
5
  const debug_1 = require("../util/debug");
6
+ /* NOT FOR BROWSER ONLY END */
5
7
  /**
6
8
  * 给定 padding 的类
7
9
  * @param padding padding 字符串
@@ -13,7 +15,9 @@ const padded = ({ length }) => (constructor) => {
13
15
  return key === 'padding' ? length : super.getAttribute(key);
14
16
  }
15
17
  }
18
+ /* NOT FOR BROWSER ONLY */
16
19
  (0, debug_1.mixin)(PaddedToken, constructor);
20
+ /* NOT FOR BROWSER ONLY END */
17
21
  return PaddedToken;
18
22
  };
19
23
  exports.padded = padded;
@@ -4,10 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.paramLineParent = void 0;
7
- const debug_1 = require("../util/debug");
8
7
  const lint_1 = require("../util/lint");
9
8
  const rect_1 = require("../lib/rect");
10
9
  const index_1 = __importDefault(require("../index"));
10
+ /* NOT FOR BROWSER ONLY */
11
+ const debug_1 = require("../util/debug");
11
12
  /**
12
13
  * 子节点为ParamLineToken的类
13
14
  * @ignore
@@ -44,7 +45,9 @@ const paramLineParent = (constructor) => {
44
45
  return errors;
45
46
  }
46
47
  }
48
+ /* NOT FOR BROWSER ONLY */
47
49
  (0, debug_1.mixin)(ParamLineParent, constructor);
50
+ /* NOT FOR BROWSER ONLY END */
48
51
  return ParamLineParent;
49
52
  }
50
53
  };
@@ -10,8 +10,8 @@ const ext_1 = require("../src/tagPair/ext");
10
10
  const comment_1 = require("../src/nowiki/comment");
11
11
  const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', { length } = onlyincludeLeft, getExtRegex = [false, true].map(includeOnly => {
12
12
  const noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly';
13
- return (0, common_1.getRegex)(exts => new RegExp(String.raw `<!--[\s\S]*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${exts // eslint-disable-next-line unicorn/prefer-string-raw
14
- })(\s[^>]*?)?(?:/>|>([\s\S]*?)</(${'\\1'}\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>([\s\S]*?)(?:</(${includeRegex}\s*)>|$))`, 'giu'));
13
+ return (0, common_1.getRegex)(exts => new RegExp(String.raw `<!--.*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${exts // eslint-disable-next-line unicorn/prefer-string-raw
14
+ })(\s[^>]*?)?(?:/>|>(.*?)</(${'\\1'}\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>(.*?)(?:</(${includeRegex}\s*)>|$))`, 'gisu'));
15
15
  });
16
16
  /**
17
17
  * 更新`<onlyinclude>`和`</onlyinclude>`的位置
@@ -65,10 +65,10 @@ const parseCommentAndExt = (wikitext, config, accum, includeOnly) => {
65
65
  const stack = [];
66
66
  newExt = ext.filter(e => e !== 'translate' && e !== 'tvar');
67
67
  newConfig = { ...config, ext: newExt };
68
- wikitext = wikitext.replace(/<nowiki>[\s\S]*?<\/nowiki>/giu, m => {
68
+ wikitext = wikitext.replace(/<nowiki>.*?<\/nowiki>/gisu, m => {
69
69
  stack.push(m);
70
70
  return `\0${stack.length - 1}\x7F`;
71
- }).replace(/<translate( nowrap)?>([\s\S]*?)<\/translate>/gu, (_, p1, p2) => {
71
+ }).replace(/<translate( nowrap)?>(.*?)<\/translate>/gsu, (_, p1, p2) => {
72
72
  const l = accum.length;
73
73
  // @ts-expect-error abstract class
74
74
  new TranslateToken(p1, p2 && (0, string_1.restore)(p2, stack), newConfig, accum);
@@ -11,7 +11,7 @@ const externalLinks_1 = require("./externalLinks");
11
11
  const index_2 = require("../src/link/index");
12
12
  const file_1 = require("../src/link/file");
13
13
  const category_1 = require("../src/link/category");
14
- const regexImg = /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])+)(\||\0\d+!\x7F)([\s\S]*)$/u;
14
+ const regexImg = /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])+)(\||\0\d+!\x7F)(.*)$/su;
15
15
  /**
16
16
  * 解析内部链接
17
17
  * @param wikitext
@@ -23,8 +23,8 @@ const regexImg = /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])+)(\||\0\d+!\x7F)([\s\S]*)$/u;
23
23
  const parseLinks = (wikitext, config, accum, page, tidy) => {
24
24
  config.regexLinks ??= new RegExp(String.raw `^\s*(?:${config.protocol}|//)`, 'iu');
25
25
  const regex = config.inExt
26
- ? /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])+)(?:(\||\0\d+!\x7F)([\s\S]*?[^\]]))?\]\]([\s\S]*)$/u
27
- : /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])*)(?:(\||\0\d+!\x7F)([\s\S]*?[^\]])?)?\]\]([\s\S]*)$/u, bits = wikitext.split('[[');
26
+ ? /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])+)(?:(\||\0\d+!\x7F)(.*?[^\]]))?\]\](.*)$/su
27
+ : /^((?:(?!\0\d+!\x7F)[^\n[\]{}|])*)(?:(\||\0\d+!\x7F)(.*?[^\]])?)?\]\](.*)$/su, bits = wikitext.split('[[');
28
28
  let s = bits.shift();
29
29
  for (let i = 0; i < bits.length; i++) {
30
30
  let mightBeImg = false, link, delimiter, text, after;
@@ -31,11 +31,10 @@ const parseMagicLinks = (wikitext, config, accum) => {
31
31
  }
32
32
  const sep = url.includes('(') ? /[^,;\\.:!?][,;\\.:!?]+$/u : /[^,;\\.:!?)][,;\\.:!?)]+$/u, sepChars = sep.exec(url);
33
33
  if (sepChars) {
34
- let correction = 1;
35
- if (sepChars[0][1] === ';'
36
- && /&(?:[a-z]+|#x[\da-f]+|#\d+)$/iu.test(url.slice(0, sepChars.index))) {
37
- correction = 2;
38
- }
34
+ const correction = sepChars[0][1] === ';'
35
+ && /&(?:[a-z]+|#x[\da-f]+|#\d+)$/iu.test(url.slice(0, sepChars.index))
36
+ ? 2
37
+ : 1;
39
38
  trail = url.slice(sepChars.index + correction) + trail;
40
39
  url = url.slice(0, sepChars.index + correction);
41
40
  }
package/dist/src/arg.js CHANGED
@@ -76,20 +76,19 @@ let ArgToken = (() => {
76
76
  super(undefined, config, accum, {});
77
77
  for (let i = 0; i < parts.length; i++) {
78
78
  const part = parts[i];
79
+ let token;
79
80
  if (i === 0) {
80
- const token = new atom_1.AtomToken(part, 'arg-name', config, accum, {});
81
- super.insertAt(token);
81
+ token = new atom_1.AtomToken(part, 'arg-name', config, accum, {});
82
82
  }
83
83
  else if (i > 1) {
84
- const token = new hidden_1.HiddenToken(part, config, accum);
85
- super.insertAt(token);
84
+ token = new hidden_1.HiddenToken(part, config, accum);
86
85
  }
87
86
  else {
88
- const token = new index_2.Token(part, config, accum);
87
+ token = new index_2.Token(part, config, accum);
89
88
  token.type = 'arg-default';
90
89
  token.setAttribute('stage', 2);
91
- super.insertAt(token);
92
90
  }
91
+ super.insertAt(token);
93
92
  }
94
93
  }
95
94
  /** @private */
@@ -122,7 +121,7 @@ let ArgToken = (() => {
122
121
  let rule = 'no-arg', s = lintConfig.getSeverity(rule);
123
122
  if (s && !this.getAttribute('include')) {
124
123
  const e = (0, lint_1.generateForSelf)(this, rect, rule, 'unexpected-argument', s), [, argDefault] = this.childNodes;
125
- if (lintConfig.computeEditInfo && argDefault) {
124
+ if (argDefault && lintConfig.computeEditInfo) {
126
125
  e.suggestions = [(0, lint_1.fixBy)(e, 'expand', argDefault.text())];
127
126
  }
128
127
  errors.push(e);
@@ -66,7 +66,7 @@ class AttributeToken extends index_2.Token {
66
66
  }
67
67
  else if (tag === 'gallery' && key === 'caption'
68
68
  || tag === 'ref' && key === 'details'
69
- || (tag === 'mapframe' || tag === 'maplink') && key === 'text'
69
+ || key === 'text' && (tag === 'mapframe' || tag === 'maplink')
70
70
  || tag === 'choose' && (key === 'before' || key === 'after')) {
71
71
  const newConfig = {
72
72
  ...config,
@@ -203,7 +203,7 @@ class AttributeToken extends index_2.Token {
203
203
  lint(start = this.getAbsoluteIndex(), re) {
204
204
  LINT: {
205
205
  const errors = super.lint(start, re), { balanced, firstChild, lastChild, name, tag } = this, rect = new rect_1.BoundingRect(this, start), rules = ['unclosed-quote', 'obsolete-attr'], { lintConfig } = index_1.default, s = rules.map(rule => lintConfig.getSeverity(rule, name));
206
- if (s[0] && !balanced) {
206
+ if (!balanced && s[0]) {
207
207
  const e = (0, lint_1.generateForChild)(lastChild, rect, rules[0], 'unclosed-quotes', s[0]);
208
208
  e.startIndex--;
209
209
  e.startCol--;
@@ -221,8 +221,8 @@ class AttributeToken extends index_2.Token {
221
221
  }
222
222
  /* NOT FOR BROWSER ONLY */
223
223
  const rule = 'invalid-css', sError = lintConfig.getSeverity(rule), sWarn = lintConfig.getSeverity(rule, 'warn');
224
- if ((sError || sWarn)
225
- && name === 'style'
224
+ if (name === 'style'
225
+ && (sError || sWarn)
226
226
  && lastChild.length === 1 && lastChild.firstChild.type === 'text') {
227
227
  const cssLSP = (0, document_1.loadCssLSP)();
228
228
  if (cssLSP) {
@@ -62,7 +62,7 @@ class AttributesToken extends index_2.Token {
62
62
  this.#type = type;
63
63
  this.setAttribute('name', name);
64
64
  if (attr) {
65
- const regex = /([^\s/](?:(?!\0\d+~\x7F)[^\s/=])*)(?:((?:\s(?:\s|\0\d+[cn]\x7F)*)?(?:=|\0\d+~\x7F)(?:\s|\0\d+[cn]\x7F)*)(?:(["'])([\s\S]*?)(\3|$)|(\S*)))?/gu;
65
+ const regex = /([^\s/](?:(?!\0\d+~\x7F)[^\s/=])*)(?:((?:\s(?:\s|\0\d+[cn]\x7F)*)?(?:=|\0\d+~\x7F)(?:\s|\0\d+[cn]\x7F)*)(?:(["'])(.*?)(\3|$)|(\S*)))?/gsu;
66
66
  let out = '', mt = regex.exec(attr), lastIndex = 0;
67
67
  const insertDirty = /** 插入无效属性 */ () => {
68
68
  if (out) {
@@ -126,7 +126,7 @@ let HeadingToken = (() => {
126
126
  }
127
127
  errors.push(e);
128
128
  }
129
- if (severities[1] && unbalanced) {
129
+ if (unbalanced && severities[1]) {
130
130
  const msg = index_1.default.msg('unbalanced-in-section-header', '"="'), e = (0, lint_1.generateForChild)(firstChild, rect, rules[1], msg, severities[1]);
131
131
  if (!computeEditInfo || innerStr === '=') {
132
132
  //
@@ -65,24 +65,19 @@ const getUrl = (link) => {
65
65
  const pdfExtensions = new Set(['djvu', 'djv', 'pdf']);
66
66
  function validate(key, val, config, extOrType, halfParsed, accum) {
67
67
  val = (0, string_1.removeComment)(val).trim();
68
- let value = val.replace(key === 'link' ? /\0\d+[tq]\x7F/gu : /\0\d+t\x7F/gu, '').trim();
68
+ const value = val.replace(key === 'link' ? /\0\d+[tq]\x7F/gu : /\0\d+t\x7F/gu, '').trim();
69
69
  switch (key) {
70
70
  case 'width':
71
71
  return !value && Boolean(val) || /^(?:\d+x?|\d*x\d+)(?:\s*px)?$/u.test(value);
72
72
  case 'link': {
73
73
  const isGalleryImage = extOrType === 'gallery-image';
74
- let nowiki = true;
75
74
  if (!value) {
76
75
  return val;
77
76
  }
78
77
  else if (getUrlLikeRegex(config.protocol).test(value)) {
79
78
  return getUrlRegex(config.protocol).test(value) ? val : isGalleryImage;
80
79
  }
81
- else if (value.startsWith('[[') && value.endsWith(']]')) {
82
- value = value.slice(2, -2);
83
- nowiki = false;
84
- }
85
- const title = index_1.default.normalizeTitle(value, 0, false, config, { halfParsed, decode: true, selfLink: true, page: '', nowiki }, accum);
80
+ const title = index_1.default.normalizeTitle(value, 0, false, config, { halfParsed, decode: true, selfLink: true, page: '', nowiki: true }, accum);
86
81
  return title.valid ? title : isGalleryImage;
87
82
  }
88
83
  case 'lang':
@@ -135,18 +130,20 @@ let ImageParameterToken = (() => {
135
130
  /** image link / 图片链接 */
136
131
  get link() {
137
132
  LINT: {
138
- if (this.name !== 'link') {
133
+ const { name, parentNode, childNodes } = this;
134
+ if (name !== 'link') {
139
135
  return undefined;
140
136
  }
141
- const value = super.text().trim();
137
+ const value = (0, string_1.text)(childNodes.flatMap(child => child.is('link') ? child.lastChild.childNodes : child)
138
+ .filter(({ type }) => type !== 'html')).trim();
142
139
  return debug_1.Shadow.run(() => {
143
140
  const config = this.getAttribute('config'), accum = [], token = new index_2.Token(value, config, accum);
144
141
  token.parseOnce(0, this.getAttribute('include')).parseOnce();
145
142
  if (/^\0\d+m\x7F/u.test(token.firstChild.toString())) {
146
143
  return value;
147
144
  }
148
- const link = validate('link', value, config, this.parentNode?.type, undefined, accum);
149
- return link === true ? undefined : link;
145
+ const link = validate('link', value, config, parentNode?.type, undefined, accum);
146
+ return typeof link === 'boolean' ? undefined : link;
150
147
  }, index_1.default);
151
148
  }
152
149
  }
@@ -198,7 +195,8 @@ let ImageParameterToken = (() => {
198
195
  }
199
196
  /** @private */
200
197
  toString(skip) {
201
- return this.#syntax ? this.#syntax.replace('$1', super.toString(skip)) : super.toString(skip);
198
+ const str = super.toString(skip);
199
+ return this.#syntax ? this.#syntax.replace('$1', () => str) : str;
202
200
  }
203
201
  /** @private */
204
202
  text() {
@@ -210,7 +208,7 @@ let ImageParameterToken = (() => {
210
208
  if (name !== 'caption') {
211
209
  str = str.trim();
212
210
  }
213
- return this.#syntax ? this.#syntax.replace('$1', str).trim() : str;
211
+ return this.#syntax ? this.#syntax.replace('$1', () => str).trim() : str;
214
212
  }
215
213
  /** @private */
216
214
  isPlain() {
package/dist/src/index.js CHANGED
@@ -445,7 +445,7 @@ class Token extends element_1.AstElement {
445
445
  break;
446
446
  }
447
447
  else if (rules && !rules.has(rule)) {
448
- continue;
448
+ //
449
449
  }
450
450
  else if (line === startLine) {
451
451
  return false;
@@ -131,18 +131,12 @@ let LinkBaseToken = (() => {
131
131
  }
132
132
  /** @private */
133
133
  text() {
134
- const { length, firstChild, childNodes, type, } = this, target = firstChild.text();
135
- let str;
136
- if (length === 1) {
137
- str =
138
- type === 'link' ?
139
- (0, string_1.trimStart)(target) :
140
- target.trim();
141
- }
142
- else {
143
- str = `${target.trim()}|${(0, string_1.text)(childNodes.slice(1)
134
+ const { length, firstChild, childNodes, type, } = this, target = firstChild.text(), str = length === 1
135
+ ? target[type === 'link' ?
136
+ 'trimStart' :
137
+ 'trim']()
138
+ : `${target.trim()}|${(0, string_1.text)(childNodes.slice(1)
144
139
  .filter(({ type: t, name }) => t !== 'image-parameter' || name !== 'invalid'), '|')}`;
145
- }
146
140
  if (this.#bracket) {
147
141
  return `[[${str}]]`;
148
142
  }
@@ -63,7 +63,6 @@ class MagicLinkToken extends index_2.Token {
63
63
  if (this.type === 'magic-link') {
64
64
  const { link } = this;
65
65
  if (link.startsWith('ISBN')) {
66
- // eslint-disable-next-line unicorn/no-useless-spread
67
66
  const digits = [...link.slice(5)].map(s => s === 'X' ? 10 : Number(s));
68
67
  return digits.length === 10
69
68
  ? digits.reduce((sum, d, i) => sum + d * (10 - i), 0) % 11 !== 0
@@ -17,24 +17,26 @@ class GalleryToken extends index_2.MultiLineToken {
17
17
  /** @param inner 标签内部wikitext */
18
18
  constructor(inner, config, accum = []) {
19
19
  super(undefined, config, accum, {});
20
- for (const line of inner?.split('\n') ?? []) {
21
- const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
22
- if (!matches) {
23
- super.insertAt((line.trim()
20
+ if (inner) {
21
+ for (const line of inner.split('\n')) {
22
+ const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
23
+ if (!matches) {
24
+ super.insertAt((line.trim()
25
+ // @ts-expect-error abstract class
26
+ ? new commentLine_1.CommentLineToken(line, config, accum)
27
+ : line));
28
+ continue;
29
+ }
30
+ const [, file, alt] = matches;
31
+ if (this.#checkFile(file)) {
24
32
  // @ts-expect-error abstract class
25
- ? new commentLine_1.CommentLineToken(line, config, accum)
26
- : line));
27
- continue;
28
- }
29
- const [, file, alt] = matches;
30
- if (this.#checkFile(file)) {
31
- // @ts-expect-error abstract class
32
- super.insertAt(new galleryImage_1.GalleryImageToken('gallery', file, alt, config, accum));
33
- }
34
- else {
35
- super.insertAt(
36
- // @ts-expect-error abstract class
37
- new commentLine_1.CommentLineToken(line, config, accum));
33
+ super.insertAt(new galleryImage_1.GalleryImageToken('gallery', file, alt, config, accum));
34
+ }
35
+ else {
36
+ super.insertAt(
37
+ // @ts-expect-error abstract class
38
+ new commentLine_1.CommentLineToken(line, config, accum));
39
+ }
38
40
  }
39
41
  }
40
42
  }
@@ -64,7 +64,7 @@ class NestedToken extends index_2.Token {
64
64
  if (!s) {
65
65
  return errors;
66
66
  }
67
- const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-content', name), regex = typeof this.#regex === 'boolean' ? lintRegex[this.#regex ? 1 : 0] : /^<!--[\s\S]*-->$/u, ignored = childNodes.filter(child => {
67
+ const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-content', name), regex = typeof this.#regex === 'boolean' ? lintRegex[this.#regex ? 1 : 0] : /^<!--.*-->$/su, ignored = childNodes.filter(child => {
68
68
  const { type, name: n } = child;
69
69
  if (type === 'ext') {
70
70
  return !this.#tags.includes(n);
@@ -34,8 +34,8 @@ class QuoteToken extends base_1.NowikiBaseToken {
34
34
  */
35
35
  get closing() {
36
36
  LINT: return {
37
- ...this.bold ? { bold: this.#closing.bold } : undefined,
38
- ...this.italic ? { italic: this.#closing.italic } : undefined,
37
+ ...this.bold && { bold: this.#closing.bold },
38
+ ...this.italic && { italic: this.#closing.italic },
39
39
  };
40
40
  }
41
41
  /** @param closing 是否闭合 */
@@ -160,7 +160,7 @@ let TdToken = (() => {
160
160
  if (computeEditInfo || fix) {
161
161
  if (double) {
162
162
  const syntax = { caption: '|+', td: '|', th: '!' }[this.subtype];
163
- e.fix = (0, lint_1.fixBy)(e, 'newline', data.replace(/\|\|/gu, `\n${syntax}`));
163
+ e.fix = (0, lint_1.fixBy)(e, 'newline', data.replace(/\|\|/gu, () => `\n${syntax}`));
164
164
  }
165
165
  else if (computeEditInfo) {
166
166
  e.suggestions = [(0, lint_1.fixByPipe)(e.startIndex, data)];
@@ -152,14 +152,14 @@ let ExtToken = (() => {
152
152
  // NestedToken 依赖 ExtToken
153
153
  const { NestedToken } = require('../nested');
154
154
  // @ts-expect-error abstract class
155
- innerToken = new NestedToken(inner, /<(option|choicetemplate)(\s[^>]*?)?(?:\/>|>([\s\S]*?)<\/(\1)>)/gu, ['option', 'choicetemplate'], newConfig, accum);
155
+ innerToken = new NestedToken(inner, /<(option|choicetemplate)(\s[^>]*?)?(?:\/>|>(.*?)<\/(\1)>)/gsu, ['option', 'choicetemplate'], newConfig, accum);
156
156
  break;
157
157
  }
158
158
  case 'combobox': {
159
159
  // NestedToken 依赖 ExtToken
160
160
  const { NestedToken } = require('../nested');
161
161
  // @ts-expect-error abstract class
162
- innerToken = new NestedToken(inner, /<(combooption)(\s[^>]*?)?(?:\/>|>([\s\S]*?)<\/(combooption\s*)>)/giu, ['combooption'], newConfig, accum);
162
+ innerToken = new NestedToken(inner, /<(combooption)(\s[^>]*?)?(?:\/>|>(.*?)<\/(combooption\s*)>)/gisu, ['combooption'], newConfig, accum);
163
163
  break;
164
164
  }
165
165
  case 'gallery': {
@@ -91,7 +91,7 @@ let IncludeToken = (() => {
91
91
  }
92
92
  errors.push(e);
93
93
  }
94
- if (s[1] && !closed) {
94
+ if (!closed && s[1]) {
95
95
  const e = (0, lint_1.generateForSelf)(this, rect, rules[1], index_1.default.msg('unclosed', `<${name}>`), s[1]);
96
96
  if (computeEditInfo) {
97
97
  e.suggestions = [(0, lint_1.fixByClose)(e.endIndex, `</${name}>`)];
@@ -22,13 +22,13 @@ class TranslateToken extends index_2.TagPairToken {
22
22
  */
23
23
  constructor(attr, inner, config, accum = []) {
24
24
  const attrToken = new syntax_1.SyntaxToken(attr, 'translate-attr', config, accum);
25
- inner = inner?.replace(/<tvar(\|[^>]+)>([\s\S]*?)<\/>/gu, (_, p1, p2) => {
25
+ inner = inner?.replace(/<tvar(\|[^>]+)>(.*?)<\/>/gsu, (_, p1, p2) => {
26
26
  // @ts-expect-error abstract class
27
27
  new tvar_1.TvarToken('tvar', p1, false, config, accum);
28
28
  // @ts-expect-error abstract class
29
29
  new tvar_1.TvarToken('', '', true, config, accum);
30
30
  return `\0${accum.length - 2}n\x7F${p2}\0${accum.length}n\x7F`;
31
- }).replace(/<(tvar)(\s+name\s*=(?:\s*(?:(["'])[\s\S]*?\3|[^"'\s>]+))?\s*)>([\s\S]*?)<\/(tvar)(\s*)>/giu, (_, p1, p2, __, p3, p4, p5) => {
31
+ }).replace(/<(tvar)(\s+name\s*=(?:\s*(?:(["']).*?\3|[^"'\s>]+))?\s*)>(.*?)<\/(tvar)(\s*)>/gisu, (_, p1, p2, __, p3, p4, p5) => {
32
32
  // @ts-expect-error abstract class
33
33
  new tvar_1.TvarToken(p1, p2, false, config, accum);
34
34
  // @ts-expect-error abstract class
@@ -113,7 +113,7 @@ let TranscludeToken = (() => {
113
113
  const m = /^(?:\s|\0\d+[cn]\x7F)*\0(\d+)h\x7F(?:\s|\0\d+[cn]\x7F)*/u.exec(title);
114
114
  if (m) {
115
115
  heading = Number(m[1]);
116
- title = title.replace(`\0${heading}h\x7F`, accum[heading].toString().replace(/^\n/u, ''));
116
+ title = title.replace(`\0${heading}h\x7F`, () => accum[heading].toString().replace(/^\n/u, ''));
117
117
  }
118
118
  super(undefined, config, accum, {});
119
119
  const { parserFunction, variable, functionHook } = config, argSubst = /^(?:\s|\0\d+[cn]\x7F)*\0\d+s\x7F/u.exec(title)?.[0];
@@ -129,7 +129,7 @@ let TranscludeToken = (() => {
129
129
  }
130
130
  const colon = title.search(/[::]/u), fullWidth = title[colon] === ':', isFunction = colon !== -1;
131
131
  if (isFunction || parts.length === 0 && !this.#raw) {
132
- const magicWord = isFunction ? title.slice(0, colon) : title, arg = isFunction && title.slice(colon + 1), cleaned = (0, string_1.removeComment)(magicWord), name = isFunction ? (0, string_1.trimStart)(cleaned) + (fullWidth ? ':' : '') : cleaned.trim(),
132
+ const magicWord = isFunction ? title.slice(0, colon) : title, arg = isFunction && title.slice(colon + 1), cleaned = (0, string_1.removeComment)(magicWord), name = isFunction ? cleaned.trimStart() + (fullWidth ? ':' : '') : cleaned.trim(),
133
133
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
134
134
  [, isSensitive, canonicalName] = (0, debug_1.getMagicWordInfo)(name, parserFunction), isFunc = !('functionHook' in config) || functionHook.includes(canonicalName), isVar = variable.includes(canonicalName);
135
135
  if (isFunction ? canonicalName && isFunc : isVar) {
@@ -274,41 +274,43 @@ let TranscludeToken = (() => {
274
274
  /** @private */
275
275
  lint(start = this.getAbsoluteIndex(), re) {
276
276
  LINT: {
277
- const errors = super.lint(start, re);
278
- if (!this.isTemplate()) {
279
- return errors;
280
- }
281
- const { type, childNodes, length: l } = this, rect = new rect_1.BoundingRect(this, start), { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, invoke = type === 'magic-word';
282
- let rule = 'no-ignored', s = lintConfig.getSeverity(rule, 'fragment');
283
- if (invoke && !this.#getTitle().valid) {
284
- rule = 'invalid-invoke';
285
- s = lintConfig.getSeverity(rule, 'name');
286
- if (s) {
287
- errors.push((0, lint_1.generateForChild)(childNodes[1], rect, rule, 'illegal-module', s));
277
+ const errors = super.lint(start, re), { type, childNodes, length: l, name } = this, rect = new rect_1.BoundingRect(this, start), { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, isTemplate = this.isTemplate();
278
+ let rule, s;
279
+ if (isTemplate) {
280
+ const invoke = type === 'magic-word';
281
+ if (invoke && !this.#getTitle().valid) {
282
+ rule = 'invalid-invoke';
283
+ s = lintConfig.getSeverity(rule, 'name');
284
+ if (s) {
285
+ errors.push((0, lint_1.generateForChild)(childNodes[1], rect, rule, 'illegal-module', s));
286
+ }
288
287
  }
289
- }
290
- else if (s) {
291
- const child = childNodes[invoke ? 1 : 0], i = child.childNodes
292
- .findIndex(c => c.type === 'text' && (0, string_1.decodeHtml)(c.data).includes('#')), textNode = child.childNodes[i];
293
- if (textNode) {
294
- const e = (0, lint_1.generateForChild)(child, rect, rule, 'useless-fragment', s);
295
- if (computeEditInfo) {
296
- e.suggestions = [
297
- (0, lint_1.fixByRemove)(e, child.getRelativeIndex(i) + textNode.data.indexOf('#')),
298
- ];
288
+ else {
289
+ rule = 'no-ignored';
290
+ s = lintConfig.getSeverity(rule, 'fragment');
291
+ if (s) {
292
+ const child = childNodes[invoke ? 1 : 0], i = child.childNodes
293
+ .findIndex(c => c.type === 'text' && (0, string_1.decodeHtml)(c.data).includes('#')), textNode = child.childNodes[i];
294
+ if (textNode) {
295
+ const e = (0, lint_1.generateForChild)(child, rect, rule, 'useless-fragment', s);
296
+ if (computeEditInfo) {
297
+ e.suggestions = [
298
+ (0, lint_1.fixByRemove)(e, child.getRelativeIndex(i) + textNode.data.indexOf('#')),
299
+ ];
300
+ }
301
+ errors.push(e);
302
+ }
299
303
  }
300
- errors.push(e);
301
304
  }
302
- }
303
- rule = 'invalid-invoke';
304
- s = lintConfig.getSeverity(rule, 'function');
305
- if (s && invoke && l === 2) {
306
- errors.push((0, lint_1.generateForSelf)(this, rect, rule, 'missing-function', s));
307
- return errors;
305
+ rule = 'invalid-invoke';
306
+ s = lintConfig.getSeverity(rule, 'function');
307
+ if (s && invoke && l === 2) {
308
+ errors.push((0, lint_1.generateForSelf)(this, rect, rule, 'missing-function', s));
309
+ }
308
310
  }
309
311
  rule = 'no-duplicate';
310
312
  s = lintConfig.getSeverity(rule, 'parameter');
311
- if (s) {
313
+ if (s && (isTemplate || name === 'tag')) {
312
314
  const duplicatedArgs = this.getDuplicatedArgs()
313
315
  .filter(([, parameter]) => !parameter[0].querySelector('ext'));
314
316
  for (const [, args] of duplicatedArgs) {
@@ -378,7 +380,14 @@ let TranscludeToken = (() => {
378
380
  */
379
381
  getDuplicatedArgs() {
380
382
  LINT: {
381
- return [...this.#args].filter(([, { size }]) => size > 1).map(([key, args]) => [key, [...args]]);
383
+ const isTemplate = this.isTemplate();
384
+ const duplicatedArgs = [...this.#args].filter(([, { size }]) => size > 1)
385
+ .map(([key, args]) => [key, [...args]]);
386
+ return isTemplate
387
+ ? duplicatedArgs
388
+ : duplicatedArgs
389
+ .map(([key, args]) => [key, args.filter(({ anon }) => !anon)])
390
+ .filter(([, args]) => args.length > 1);
382
391
  }
383
392
  }
384
393
  /**
package/dist/util/lint.js CHANGED
@@ -16,7 +16,7 @@ const tableTags = new Set(['tr', 'td', 'th', 'caption']), tableTemplates = new S
16
16
  const isFostered = (token) => {
17
17
  const first = token.childNodes.find(child => child.text().trim());
18
18
  if (!first
19
- || first.type === 'text' && first.data.trim().startsWith('!')
19
+ || first.type === 'text' && first.data.trimStart().startsWith('!')
20
20
  || first.is('magic-word') && first.name === '!'
21
21
  || first.is('template') && tableTemplates.has(first.name)
22
22
  || first.is('html') && tableTags.has(first.name)) {
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extParams = exports.obsoleteAttrs = exports.extAttrs = exports.htmlAttrs = exports.commonHtmlAttrs = void 0;
4
- /* eslint-disable unicorn/no-unreadable-iife */
5
4
  const blockAttrs = new Set(['align']), citeAttrs = new Set(['cite']), citeAndAttrs = new Set(['cite', 'datetime']), widthAttrs = new Set(['width']), trAttrs = new Set(['bgcolor', 'align', 'valign']), obsoleteTdAttrs = new Set([...trAttrs, 'axis', 'height', 'nowrap', 'width']), tdAttrs = new Set([...obsoleteTdAttrs, 'abbr', 'headers', 'scope', 'rowspan', 'colspan']), typeAttrs = new Set(['type']), obsoleteTableAttrs = new Set(['summary', 'align', 'bgcolor', 'cellpadding', 'cellspacing', 'frame', 'rules', 'width']), brAttrs = new Set(['clear']), chemAttrs = new Set(['id', 'qid', 'forcemathmode', 'type', 'display']), syntaxHighlightAttrs = new Set([
6
5
  'enclose',
7
6
  'inline',
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toLowerCase = exports.decodeNumber = exports.decodeHtml = exports.decodeHtmlBasic = exports.text = exports.removeCommentLine = exports.removeComment = exports.tidy = exports.trimStart = exports.trimLc = exports.extUrlChar = exports.extUrlCharFirst = exports.zs = void 0;
3
+ exports.toLowerCase = exports.decodeNumber = exports.decodeHtml = exports.decodeHtmlBasic = exports.text = exports.removeCommentLine = exports.removeComment = exports.tidy = exports.trimLc = exports.extUrlChar = exports.extUrlCharFirst = exports.zs = void 0;
4
4
  exports.restore = restore;
5
- const common_1 = require("@bhsd/common");
6
5
  exports.zs = String.raw ` \xA0\u1680\u2000-\u200A\u202F\u205F\u3000`;
7
6
  const commonExtUrlChar = String.raw `[^[\]<>"\0-\x1F\x7F${exports.zs}\uFFFD]`;
8
7
  exports.extUrlCharFirst = String.raw `(?:\[[\da-f:.]+\]|${commonExtUrlChar})`;
@@ -13,12 +12,6 @@ exports.extUrlChar = String.raw `(?:${commonExtUrlChar}|\0\d+[cn!~]\x7F)*`;
13
12
  */
14
13
  const trimLc = (s) => s.trim().toLowerCase();
15
14
  exports.trimLc = trimLc;
16
- /**
17
- * trim starting whitespaces
18
- * @param s 字符串
19
- */
20
- const trimStart = (s) => s.slice((0, common_1.numLeadingSpaces)(s));
21
- exports.trimStart = trimStart;
22
15
  function restore(s, stack, translate) {
23
16
  if (translate === 1) {
24
17
  return s.replace(/\0(\d+)g\x7F/gu, (_, p1) => restore(String(stack[p1]), stack, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.46.1",
3
+ "version": "2.46.3",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -98,24 +98,24 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
- "@bhsd/cm-util": "^2.3.0",
102
- "@bhsd/common": "^4.0.0",
101
+ "@bhsd/cm-util": "^2.4.1",
102
+ "@bhsd/common": "^4.1.0",
103
103
  "@bhsd/stylelint-util": "^1.1.0",
104
104
  "culori": "^4.0.2",
105
105
  "entities": "^8.0.0",
106
106
  "vscode-languageserver-types": "^3.18.0"
107
107
  },
108
108
  "devDependencies": {
109
- "@bhsd/code-standard": "^3.1.0",
109
+ "@bhsd/code-standard": "^4.1.0",
110
110
  "@bhsd/nodejs": "^1.2.1",
111
- "@bhsd/test-util": "^3.1.0",
112
- "c8": "^11.0.0",
111
+ "@bhsd/test-util": "^3.1.1",
112
+ "c8": "^12.0.0",
113
113
  "diff2html-cli": "^5.2.15",
114
- "esbuild": "^0.28.1",
115
- "eslint": "^10.7.0",
116
- "markdownlint-cli2": "^0.23.0",
114
+ "esbuild": "^0.28.2",
115
+ "eslint": "^10.8.1",
116
+ "markdownlint-cli2": "^0.23.2",
117
117
  "mathoid-texvcjs": "^0.6.0",
118
- "stylelint": "^17.14.0",
118
+ "stylelint": "^17.14.1",
119
119
  "typescript": "^6.0.3",
120
120
  "v8r": "^6.1.0",
121
121
  "vscode-css-languageservice": "^6.3.10",