wikilint 2.18.3 → 2.19.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.
package/dist/base.d.mts CHANGED
@@ -15,6 +15,7 @@ export interface Config {
15
15
  readonly variants: string[];
16
16
  readonly articlePath?: string;
17
17
  }
18
+ export type ConfigData = Omit<Config, 'excludes'>;
18
19
  export type TokenTypes = 'root' | 'plain' | 'redirect' | 'redirect-syntax' | 'redirect-target' | 'onlyinclude' | 'noinclude' | 'include' | 'comment' | 'ext' | 'ext-attrs' | 'ext-attr-dirty' | 'ext-attr' | 'attr-key' | 'attr-value' | 'ext-inner' | 'arg' | 'arg-name' | 'arg-default' | 'hidden' | 'magic-word' | 'magic-word-name' | 'invoke-function' | 'invoke-module' | 'template' | 'template-name' | 'parameter' | 'parameter-key' | 'parameter-value' | 'heading' | 'heading-title' | 'heading-trail' | 'html' | 'html-attrs' | 'html-attr-dirty' | 'html-attr' | 'table' | 'tr' | 'td' | 'table-syntax' | 'table-attrs' | 'table-attr-dirty' | 'table-attr' | 'table-inter' | 'td-inner' | 'hr' | 'double-underscore' | 'link' | 'link-target' | 'link-text' | 'category' | 'file' | 'gallery-image' | 'imagemap-image' | 'image-parameter' | 'quote' | 'ext-link' | 'ext-link-text' | 'ext-link-url' | 'free-ext-link' | 'magic-link' | 'list' | 'dd' | 'list-range' | 'converter' | 'converter-flags' | 'converter-flag' | 'converter-rule' | 'converter-rule-variant' | 'converter-rule-to' | 'converter-rule-from' | 'param-line' | 'imagemap-link';
19
20
  export declare const stages: {
20
21
  redirect: number;
@@ -250,7 +251,7 @@ export interface LanguageService {
250
251
  setTargetWikipedia(wiki: string): Promise<void>;
251
252
  }
252
253
  export interface Parser {
253
- config: Config | string;
254
+ config: ConfigData | string;
254
255
  i18n: Record<string, string> | string | undefined;
255
256
  /**
256
257
  * Get the current parser configuration
@@ -258,7 +259,7 @@ export interface Parser {
258
259
  * 获取当前的解析设置
259
260
  * @param config unprocessed parser configuration / 未处理的解析设置
260
261
  */
261
- getConfig(config?: Config): Config;
262
+ getConfig(config?: ConfigData): Config;
262
263
  /**
263
264
  * Parse wikitext
264
265
  *
package/dist/base.d.ts CHANGED
@@ -15,6 +15,7 @@ export interface Config {
15
15
  readonly variants: string[];
16
16
  readonly articlePath?: string;
17
17
  }
18
+ export type ConfigData = Omit<Config, 'excludes'>;
18
19
  export type TokenTypes = 'root' | 'plain' | 'redirect' | 'redirect-syntax' | 'redirect-target' | 'onlyinclude' | 'noinclude' | 'include' | 'comment' | 'ext' | 'ext-attrs' | 'ext-attr-dirty' | 'ext-attr' | 'attr-key' | 'attr-value' | 'ext-inner' | 'arg' | 'arg-name' | 'arg-default' | 'hidden' | 'magic-word' | 'magic-word-name' | 'invoke-function' | 'invoke-module' | 'template' | 'template-name' | 'parameter' | 'parameter-key' | 'parameter-value' | 'heading' | 'heading-title' | 'heading-trail' | 'html' | 'html-attrs' | 'html-attr-dirty' | 'html-attr' | 'table' | 'tr' | 'td' | 'table-syntax' | 'table-attrs' | 'table-attr-dirty' | 'table-attr' | 'table-inter' | 'td-inner' | 'hr' | 'double-underscore' | 'link' | 'link-target' | 'link-text' | 'category' | 'file' | 'gallery-image' | 'imagemap-image' | 'image-parameter' | 'quote' | 'ext-link' | 'ext-link-text' | 'ext-link-url' | 'free-ext-link' | 'magic-link' | 'list' | 'dd' | 'list-range' | 'converter' | 'converter-flags' | 'converter-flag' | 'converter-rule' | 'converter-rule-variant' | 'converter-rule-to' | 'converter-rule-from' | 'param-line' | 'imagemap-link';
19
20
  export declare const stages: {
20
21
  redirect: number;
@@ -250,7 +251,7 @@ export interface LanguageService {
250
251
  setTargetWikipedia(wiki: string): Promise<void>;
251
252
  }
252
253
  export interface Parser {
253
- config: Config | string;
254
+ config: ConfigData | string;
254
255
  i18n: Record<string, string> | string | undefined;
255
256
  /**
256
257
  * Get the current parser configuration
@@ -258,7 +259,7 @@ export interface Parser {
258
259
  * 获取当前的解析设置
259
260
  * @param config unprocessed parser configuration / 未处理的解析设置
260
261
  */
261
- getConfig(config?: Config): Config;
262
+ getConfig(config?: ConfigData): Config;
262
263
  /**
263
264
  * Parse wikitext
264
265
  *
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Config, LintError, TokenTypes, Parser as ParserBase, Stage } from './base';
1
+ import type { Config, ConfigData, LintError, TokenTypes, Parser as ParserBase, Stage } from './base';
2
2
  import type { Title, TitleOptions } from './lib/title';
3
3
  import type { LanguageService, QuickFixData } from './lib/lsp';
4
4
  import type { Token } from './internal';
@@ -23,11 +23,20 @@ declare interface Parser extends ParserBase {
23
23
  * @since v1.16.1
24
24
  */
25
25
  createLanguageService(uri: object): LanguageService;
26
+ /**
27
+ * Get the parser configuration for a MediaWiki project with Extension:CodeMirror installed
28
+ *
29
+ * 获取一个安装了CodeMirror扩展的MediaWiki项目的解析设置
30
+ * @param site site nickname / 网站别名
31
+ * @param url script path / 脚本路径
32
+ * @since v1.18.4
33
+ */
34
+ fetchConfig(site: string, url: string): Promise<Config>;
26
35
  }
27
36
  declare const Parser: Parser;
28
37
  // @ts-expect-error mixed export styles
29
38
  export = Parser;
30
39
  export default Parser;
31
- export type { Config, LintError, TokenTypes, LanguageService, QuickFixData, };
40
+ export type { Config, ConfigData, LintError, TokenTypes, LanguageService, QuickFixData, };
32
41
  export type * from './internal';
33
42
  declare global { type Acceptable = unknown; }
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ const string_1 = require("./util/string");
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const diff_1 = require("./util/diff");
14
+ const config_1 = __importDefault(require("./bin/config"));
14
15
  /* NOT FOR BROWSER ONLY */
15
16
  /**
16
17
  * 从根路径require
@@ -175,6 +176,12 @@ const Parser = {
175
176
  return tasks.get(uri) ?? new LanguageService(uri);
176
177
  }
177
178
  },
179
+ /* NOT FOR BROWSER ONLY */
180
+ /** @implements */
181
+ async fetchConfig(site, url) {
182
+ return this.getConfig(await (0, config_1.default)(site, url, false, true));
183
+ },
184
+ /* NOT FOR BROWSER ONLY */
178
185
  };
179
186
  const def = {
180
187
  default: { value: Parser },
@@ -3,6 +3,10 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
3
3
  import type { JSONDocument } from 'vscode-json-languageservice';
4
4
  import type { Stylesheet } from 'vscode-css-languageservice';
5
5
  import type { Token } from '../internal';
6
+ declare interface Jax {
7
+ tex2mml(tex: string): string;
8
+ }
9
+ export declare const MathJax: Promise<Jax | undefined>;
6
10
  export declare const jsonTags: string[];
7
11
  export declare const jsonLSP: import("vscode-json-languageservice").LanguageService | undefined;
8
12
  export declare const cssLSP: import("vscode-css-languageservice").LanguageService | undefined;
@@ -3,9 +3,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EmbeddedCSSDocument = exports.EmbeddedJSONDocument = exports.stylelint = exports.cssLSP = exports.jsonLSP = exports.jsonTags = void 0;
6
+ exports.EmbeddedCSSDocument = exports.EmbeddedJSONDocument = exports.stylelint = exports.cssLSP = exports.jsonLSP = exports.jsonTags = exports.MathJax = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const common_1 = require("@bhsd/common");
9
+ exports.MathJax = (async () => {
10
+ try {
11
+ const jax = require('mathjax');
12
+ return await jax.init({
13
+ loader: {
14
+ load: ['input/tex', '[tex]/mhchem'],
15
+ },
16
+ tex: {
17
+ packages: { '[+]': ['mhchem'] },
18
+ /** @ignore */
19
+ formatError(_, error) {
20
+ throw error;
21
+ },
22
+ },
23
+ startup: { typeset: false },
24
+ });
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ })();
9
30
  exports.jsonTags = ['templatedata', 'mapframe', 'maplink'];
10
31
  exports.jsonLSP = (() => {
11
32
  try {
package/dist/lib/lsp.js CHANGED
@@ -16,13 +16,12 @@ const util_1 = __importDefault(require("util"));
16
16
  const child_process_1 = require("child_process");
17
17
  const crypto_1 = require("crypto");
18
18
  const stylelint_1 = require("@bhsd/common/dist/stylelint");
19
- const config_1 = __importDefault(require("../bin/config"));
20
19
  const document_1 = require("./document");
21
20
  /** @see https://www.npmjs.com/package/stylelint-config-recommended */
22
21
  const cssRules = {
23
22
  'block-no-empty': null,
24
23
  'property-no-unknown': null,
25
- }, jsonSelector = document_1.jsonTags.map(s => `ext-inner#${s}`).join(), scores = new Map();
24
+ }, jsonSelector = document_1.jsonTags.map(s => `ext#${s}`).join(), mathSelector = ['math', 'chem', 'ce'].map(s => `ext#${s}`).join(), scores = new Map();
26
25
  let colors;
27
26
  /* NOT FOR BROWSER ONLY END */
28
27
  exports.tasks = new WeakMap();
@@ -769,8 +768,11 @@ class LanguageService {
769
768
  });
770
769
  })() :
771
770
  [], jsonDiagnostics = document_1.jsonLSP ?
772
- await Promise.all(root.querySelectorAll(jsonSelector).map(async (token) => {
773
- const textDoc = new document_1.EmbeddedJSONDocument(root, token), severityLevel = token.name === 'templatedata' ? 'error' : 'ignore', e = (await document_1.jsonLSP.doValidation(textDoc, textDoc.jsonDoc, {
771
+ await Promise.all(root.querySelectorAll(jsonSelector).map(async ({ name, lastChild, selfClosing }) => {
772
+ if (selfClosing) {
773
+ return [];
774
+ }
775
+ const textDoc = new document_1.EmbeddedJSONDocument(root, lastChild), severityLevel = name === 'templatedata' ? 'error' : 'ignore', e = (await document_1.jsonLSP.doValidation(textDoc, textDoc.jsonDoc, {
774
776
  comments: severityLevel,
775
777
  trailingCommas: severityLevel,
776
778
  })).map((error) => ({
@@ -825,6 +827,32 @@ class LanguageService {
825
827
  }));
826
828
  }
827
829
  }
830
+ const MathJax = await document_1.MathJax, mathDiagnostics = MathJax
831
+ ? root.querySelectorAll(mathSelector)
832
+ .map(({ selfClosing, innerText, lastChild, name }) => {
833
+ if (selfClosing) {
834
+ return [];
835
+ }
836
+ try {
837
+ MathJax.tex2mml(name === 'math' ? innerText : String.raw `\ce{${innerText}}`);
838
+ return [];
839
+ }
840
+ catch (e) {
841
+ if (e && typeof e === 'object' && 'id' in e && 'message' in e) {
842
+ return [
843
+ {
844
+ range: createNodeRange(lastChild),
845
+ severity: 1,
846
+ source: 'MathJax',
847
+ code: e.id,
848
+ message: e.message,
849
+ },
850
+ ];
851
+ }
852
+ return [];
853
+ }
854
+ })
855
+ : [];
828
856
  /* NOT FOR BROWSER ONLY END */
829
857
  return [
830
858
  diagnostics,
@@ -832,6 +860,7 @@ class LanguageService {
832
860
  jsonDiagnostics,
833
861
  /* NOT FOR BROWSER ONLY */
834
862
  lilypondDiagnostics,
863
+ mathDiagnostics,
835
864
  ].flat(2);
836
865
  }
837
866
  /**
@@ -884,8 +913,10 @@ class LanguageService {
884
913
  }
885
914
  /* NOT FOR BROWSER ONLY */
886
915
  if (document_1.jsonLSP) {
887
- for (const token of root.querySelectorAll(jsonSelector)) {
888
- ranges.push(...document_1.jsonLSP.getFoldingRanges(new document_1.EmbeddedJSONDocument(root, token)));
916
+ for (const { selfClosing, lastChild } of root.querySelectorAll(jsonSelector)) {
917
+ if (!selfClosing) {
918
+ ranges.push(...document_1.jsonLSP.getFoldingRanges(new document_1.EmbeddedJSONDocument(root, lastChild)));
919
+ }
889
920
  }
890
921
  }
891
922
  /* NOT FOR BROWSER ONLY END */
@@ -1116,13 +1147,13 @@ class LanguageService {
1116
1147
  }
1117
1148
  else if (type === 'magic-word-name') {
1118
1149
  info = this.#getParserFunction(parentNode.name);
1119
- f = offsetNode.toString(true).trim();
1150
+ f = offsetNode.text().trim();
1120
1151
  colon = parentNode.getAttribute('colon');
1121
1152
  }
1122
1153
  else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
1123
1154
  && (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
1124
1155
  info = this.#getParserFunction(name);
1125
- f = offsetNode.firstChild.toString(true).trim();
1156
+ f = offsetNode.firstChild.text().trim();
1126
1157
  colon = offsetNode.getAttribute('colon');
1127
1158
  }
1128
1159
  else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
@@ -1209,7 +1240,7 @@ class LanguageService {
1209
1240
  }
1210
1241
  const n = childNodes.length - 1, candidates = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
1211
1242
  && params.every(({ label, const: c }, i) => {
1212
- const p = c && i < n && childNodes[i + 1]?.toString(true).trim();
1243
+ const p = c && i < n && childNodes[i + 1]?.text().trim();
1213
1244
  return !p || label.toLowerCase().includes(p.toLowerCase());
1214
1245
  }));
1215
1246
  if (candidates.length === 0) {
@@ -1222,7 +1253,7 @@ class LanguageService {
1222
1253
  break;
1223
1254
  }
1224
1255
  }
1225
- const f = firstChild.toString(true).trim(), colon = lastChild.getAttribute('colon');
1256
+ const f = firstChild.text().trim(), colon = lastChild.getAttribute('colon');
1226
1257
  return {
1227
1258
  signatures: candidates.map((params) => ({
1228
1259
  label: `{{${f}${params.length === 0 ? '' : colon}${params.map(({ label }) => label).join('|')}}}`,
@@ -1337,7 +1368,7 @@ class LanguageService {
1337
1368
  this.config = index_1.default.getConfig(config);
1338
1369
  }
1339
1370
  catch {
1340
- this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`, false, true));
1371
+ this.config = await index_1.default.fetchConfig(site, `${mt[0]}/w`);
1341
1372
  }
1342
1373
  Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
1343
1374
  }
package/dist/lib/text.js CHANGED
@@ -74,7 +74,6 @@ class AstText extends node_1.AstNode {
74
74
  constructor(text) {
75
75
  super();
76
76
  Object.defineProperties(this, {
77
- childNodes: { enumerable: false, configurable: false },
78
77
  data: {
79
78
  value: text,
80
79
  },
@@ -132,12 +131,13 @@ class AstText extends node_1.AstNode {
132
131
  ]);
133
132
  for (let mt = errorRegex.exec(data); mt; mt = errorRegex.exec(data)) {
134
133
  const [, tag, prefix] = mt;
135
- let { index } = mt, error = mt[0].toLowerCase();
134
+ let { index, 0: error } = mt;
136
135
  if (prefix && prefix !== ']') {
137
136
  const { length } = prefix;
138
137
  index += length;
139
138
  error = error.slice(length);
140
139
  }
140
+ error = error.toLowerCase();
141
141
  const { 0: char, length } = error, magicLink = char === 'r' || char === 'p' || char === 'i';
142
142
  if (char === '<' && !tags.has(tag.toLowerCase())
143
143
  || char === '[' && type === 'ext-link-text' && (/&(?:rbrack|#93|#x5[Dd];);/u.test(data.slice(index + 1))
@@ -8,14 +8,33 @@ const transclude_1 = require("../src/transclude");
8
8
  const arg_1 = require("../src/arg");
9
9
  /* NOT FOR BROWSER ONLY */
10
10
  const v8_1 = require("v8");
11
- const MAXHEAP = (0, v8_1.getHeapStatistics)().total_available_size * 0.9, MAXITER = MAXHEAP / 2e5;
11
+ const MAXHEAP = (0, v8_1.getHeapStatistics)().heap_size_limit * 0.9;
12
12
  /* NOT FOR BROWSER ONLY END */
13
13
  const closes = {
14
14
  '=': String.raw `\n(?!(?:[^\S\n]|\0\d+[cn]\x7F)*\n)`,
15
15
  '{': String.raw `\}{2,}|\|`,
16
16
  '-': String.raw `\}-`,
17
17
  '[': String.raw `\]\]`,
18
- }, openBraces = String.raw `|\{{2,}`, marks = new Map([['!', '!'], ['!!', '+'], ['(!', '{'], ['!)', '}'], ['!-', '-'], ['=', '~'], ['server', 'm']]), getExecRegex = (0, common_1.getRegex)(s => new RegExp(s, 'gmu'));
18
+ }, lbrack = String.raw `\[(?!\[)`, newline = String.raw `\n(?![=\0])`, openBraces = String.raw `|\{{2,}`, marks = new Map([['!', '!'], ['!!', '+'], ['(!', '{'], ['!)', '}'], ['!-', '-'], ['=', '~'], ['server', 'm']]), getExecRegex = (0, common_1.getRegex)(s => new RegExp(s, 'gmu'));
19
+ let reReplace;
20
+ /* NOT FOR BROWSER ONLY */
21
+ try {
22
+ reReplace = new RegExp(String.raw `(?<!\{)\{\{((?:[^\n{}[]|${lbrack}|${newline})*)\}\}` // eslint-disable-line prefer-template
23
+ + '|'
24
+ + String.raw `\{\{((?:[^\n{}[]|${lbrack}|${newline})*)\}\}(?!\})`
25
+ + '|'
26
+ + String.raw `\[\[(?:[^\n[\]{]|${newline})*\]\]`
27
+ + '|'
28
+ + String.raw `-\{(?:[^\n{}[]|${lbrack}|${newline})*\}-`, 'gu');
29
+ }
30
+ catch {
31
+ /* NOT FOR BROWSER ONLY END */
32
+ reReplace = new RegExp(String.raw `\{\{((?:[^\n{}[]|${lbrack}|${newline})*)\}\}(?!\})` // eslint-disable-line prefer-template
33
+ + '|'
34
+ + String.raw `\[\[(?:[^\n[\]{]|${newline})*\]\]`
35
+ + '|'
36
+ + String.raw `-\{(?:[^\n{}[]|${lbrack}|${newline})*\}-`, 'gu');
37
+ }
19
38
  /**
20
39
  * 获取模板或魔术字对应的字符
21
40
  * @param s 模板或魔术字名
@@ -42,7 +61,7 @@ const getSymbol = (s) => {
42
61
  * @throws `TranscludeToken.constructor()`
43
62
  */
44
63
  const parseBraces = (wikitext, config, accum) => {
45
- const source = String.raw `${config.excludes?.includes('heading') ? '' : String.raw `^((?:\0\d+[cno]\x7F)*)={1,6}|`}\[\[|-\{(?!\{)`, { parserFunction: [, , , subst] } = config, stack = [], linkStack = [];
64
+ const source = String.raw `${config.excludes.includes('heading') ? '' : String.raw `^((?:\0\d+[cno]\x7F)*)={1,6}|`}\[\[|-\{(?!\{)`, { parserFunction: [, , , subst] } = config, stack = [], linkStack = [];
46
65
  /**
47
66
  * 恢复内链
48
67
  * @param s 不含内链的字符串
@@ -58,36 +77,43 @@ const parseBraces = (wikitext, config, accum) => {
58
77
  const push = (text, parts, lastIndex, index) => {
59
78
  parts[parts.length - 1].push(restore(text.slice(lastIndex, index)));
60
79
  };
61
- wikitext = wikitext.replace(/\{\{([^\n{}[]*)\}\}(?!\})|\[\[[^\n[\]{]*\]\]|-\{[^\n{}[]*\}-/gu, (m, p1) => {
62
- if (p1 !== undefined) {
63
- try {
64
- const { length } = accum, parts = p1.split('|');
65
- // @ts-expect-error abstract class
66
- new transclude_1.TranscludeToken(parts[0], parts.slice(1).map(part => {
67
- const i = part.indexOf('=');
68
- return i === -1 ? [part] : [part.slice(0, i), part.slice(i + 1)];
69
- }), config, accum);
70
- return `\0${length}${getSymbol(parts[0])}\x7F`;
71
- }
72
- catch (e) {
73
- /* istanbul ignore if */
74
- if (!(e instanceof SyntaxError) || e.message !== 'Invalid template name') {
75
- throw e;
80
+ let replaced;
81
+ do {
82
+ if (replaced !== undefined) {
83
+ wikitext = replaced;
84
+ }
85
+ replaced = wikitext.replace(reReplace, (m, p1, p2) => {
86
+ if (p1 !== undefined || typeof p2 === 'string') {
87
+ try {
88
+ const { length } = accum, parts = (p1 ?? p2).split('|');
89
+ // @ts-expect-error abstract class
90
+ new transclude_1.TranscludeToken(restore(parts[0]), parts.slice(1).map(part => {
91
+ const i = part.indexOf('=');
92
+ return (i === -1 ? [part] : [part.slice(0, i), part.slice(i + 1)]).map(restore);
93
+ }), config, accum);
94
+ return `\0${length}${getSymbol(parts[0])}\x7F`;
95
+ }
96
+ catch (e) {
97
+ /* istanbul ignore if */
98
+ if (!(e instanceof SyntaxError) || e.message !== 'Invalid template name') {
99
+ throw e;
100
+ }
76
101
  }
77
102
  }
78
- }
79
- linkStack.push(m);
80
- return `\0${linkStack.length - 1}\x7F`;
81
- });
103
+ linkStack.push(restore(m));
104
+ return `\0${linkStack.length - 1}\x7F`;
105
+ });
106
+ } while (replaced !== wikitext);
107
+ wikitext = replaced;
82
108
  const lastBraces = wikitext.lastIndexOf('}}') - wikitext.length;
83
109
  let moreBraces = lastBraces + wikitext.length !== -1;
84
- let regex = getExecRegex(source + (moreBraces ? openBraces : '')), mt = regex.exec(wikitext), iter = 0, lastIndex;
110
+ let regex = getExecRegex(source + (moreBraces ? openBraces : '')), mt = regex.exec(wikitext), lastIndex;
85
111
  while (mt
86
112
  || lastIndex !== undefined && lastIndex <= wikitext.length
87
113
  && stack[stack.length - 1]?.[0]?.startsWith('=')) {
88
114
  /* NOT FOR BROWSER ONLY */
89
- if (iter++ > MAXITER && process.memoryUsage().heapUsed > MAXHEAP) {
90
- throw new RangeError('Maximum iteration exceeded');
115
+ if (process.memoryUsage().heapUsed > MAXHEAP) {
116
+ throw new RangeError('Maximum heap size exceeded');
91
117
  }
92
118
  /* NOT FOR BROWSER ONLY END */
93
119
  if (mt?.[1]) {
@@ -106,11 +132,13 @@ const parseBraces = (wikitext, config, accum) => {
106
132
  const rmt = /^(={1,6})(.+)\1((?:\s|\0\d+[cn]\x7F)*)$/u
107
133
  .exec(wikitext.slice(index, curIndex));
108
134
  if (rmt) {
109
- wikitext = `${wikitext.slice(0, index)}\0${accum.length}h\x7F${wikitext.slice(curIndex)}`;
110
- lastIndex = index + 4 + String(accum.length).length;
111
135
  rmt[2] = restore(rmt[2]);
112
- // @ts-expect-error abstract class
113
- new heading_1.HeadingToken(rmt[1].length, rmt.slice(2), config, accum);
136
+ if (!rmt[2].includes('\n')) {
137
+ wikitext = `${wikitext.slice(0, index)}\0${accum.length}h\x7F${wikitext.slice(curIndex)}`;
138
+ lastIndex = index + 4 + String(accum.length).length;
139
+ // @ts-expect-error abstract class
140
+ new heading_1.HeadingToken(rmt[1].length, rmt.slice(2), config, accum);
141
+ }
114
142
  }
115
143
  }
116
144
  }
@@ -9,7 +9,7 @@ const ext_1 = require("../src/tagPair/ext");
9
9
  const comment_1 = require("../src/nowiki/comment");
10
10
  const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', { length } = onlyincludeLeft, getRegex = [false, true].map(includeOnly => {
11
11
  const noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly';
12
- return (0, common_1.getObjRegex)(ext => new RegExp(String.raw `<!--[\s\S]*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${ext.join('|')})(\s[^>]*?)?(?:/>|>([\s\S]*?)</(\1\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>([\s\S]*?)(?:</(${includeRegex}\s*)>|$))`, 'giu'));
12
+ return (0, common_1.getRegex)(ext => new RegExp(String.raw `<!--[\s\S]*?(?:-->|$)|<${noincludeRegex}(?:\s[^>]*)?/?>|</${noincludeRegex}\s*>|<(${ext.join('|')})(\s[^>]*?)?(?:/>|>([\s\S]*?)</(\1\s*)>)|<(${includeRegex})(\s[^>]*?)?(?:/>|>([\s\S]*?)(?:</(${includeRegex}\s*)>|$))`, 'giu'));
13
13
  });
14
14
  /**
15
15
  * 更新`<onlyinclude>`和`</onlyinclude>`的位置
package/dist/src/arg.js CHANGED
@@ -61,7 +61,7 @@ class ArgToken extends index_1.Token {
61
61
  /** 设置name */
62
62
  #setName() {
63
63
  // eslint-disable-next-line no-unused-labels
64
- LSP: this.setAttribute('name', this.firstChild.toString(true).trim());
64
+ LSP: this.setAttribute('name', this.firstChild.text().trim());
65
65
  }
66
66
  /** @private */
67
67
  afterBuild() {
@@ -80,7 +80,7 @@ class AttributeToken extends index_2.Token {
80
80
  if (this.parentNode) {
81
81
  this.#tag = this.parentNode.name;
82
82
  }
83
- this.setAttribute('name', this.firstChild.toString(true).trim().toLowerCase());
83
+ this.setAttribute('name', this.firstChild.text().trim().toLowerCase());
84
84
  super.afterBuild();
85
85
  }
86
86
  /** @private */
@@ -14,7 +14,7 @@ const getUrlRegex = (0, common_1.getRegex)(protocol => new RegExp(String.raw `^(
14
14
  const getSyntaxRegex = (0, common_1.getRegex)(syntax => new RegExp(String.raw `^(\s*(?!\s))${syntax.replace('$1', '(.*)')}${syntax.endsWith('$1') ? '(?=$|\n)' : ''}(\s*)$`, 'u'));
15
15
  exports.galleryParams = new Set(['alt', 'link', 'lang', 'page', 'caption']);
16
16
  function validate(key, val, config, halfParsed, ext) {
17
- val = val.trim();
17
+ val = (0, string_1.removeComment)(val).trim();
18
18
  let value = val.replace(key === 'link' ? /\0\d+[tq]\x7F/gu : /\0\d+t\x7F/gu, '').trim();
19
19
  switch (key) {
20
20
  case 'width':
@@ -81,10 +81,12 @@ class ImageParameterToken extends index_2.Token {
81
81
  this.setAttribute('name', param[1]);
82
82
  return;
83
83
  }
84
- super(str, {
85
- ...config,
86
- excludes: [...config.excludes ?? [], 'list'],
87
- }, accum);
84
+ super(str, config.excludes.includes('list')
85
+ ? config
86
+ : {
87
+ ...config,
88
+ excludes: [...config.excludes, 'list'],
89
+ }, accum);
88
90
  this.setAttribute('name', 'caption');
89
91
  this.setAttribute('stage', 7);
90
92
  }
package/dist/src/index.js CHANGED
@@ -224,7 +224,7 @@ class Token extends element_1.AstElement {
224
224
  }
225
225
  /** 解析HTML标签 */
226
226
  #parseHtml() {
227
- if (this.#config.excludes?.includes('html')) {
227
+ if (this.#config.excludes.includes('html')) {
228
228
  return;
229
229
  }
230
230
  const { parseHtml } = require('../parser/html');
@@ -232,7 +232,7 @@ class Token extends element_1.AstElement {
232
232
  }
233
233
  /** 解析表格 */
234
234
  #parseTable() {
235
- if (this.#config.excludes?.includes('table')) {
235
+ if (this.#config.excludes.includes('table')) {
236
236
  return;
237
237
  }
238
238
  const { parseTable } = require('../parser/table');
@@ -240,7 +240,7 @@ class Token extends element_1.AstElement {
240
240
  }
241
241
  /** 解析`<hr>`和状态开关 */
242
242
  #parseHrAndDoubleUnderscore() {
243
- if (this.#config.excludes?.includes('hr')) {
243
+ if (this.#config.excludes.includes('hr')) {
244
244
  return;
245
245
  }
246
246
  const { parseHrAndDoubleUnderscore } = require('../parser/hrAndDoubleUnderscore');
@@ -259,7 +259,7 @@ class Token extends element_1.AstElement {
259
259
  * @param tidy 是否整理
260
260
  */
261
261
  #parseQuotes(tidy) {
262
- if (this.#config.excludes?.includes('quote')) {
262
+ if (this.#config.excludes.includes('quote')) {
263
263
  return;
264
264
  }
265
265
  const { parseQuotes } = require('../parser/quotes');
@@ -271,7 +271,7 @@ class Token extends element_1.AstElement {
271
271
  }
272
272
  /** 解析外部链接 */
273
273
  #parseExternalLinks() {
274
- if (this.#config.excludes?.includes('extLink')) {
274
+ if (this.#config.excludes.includes('extLink')) {
275
275
  return;
276
276
  }
277
277
  const { parseExternalLinks } = require('../parser/externalLinks');
@@ -279,7 +279,7 @@ class Token extends element_1.AstElement {
279
279
  }
280
280
  /** 解析自由外链 */
281
281
  #parseMagicLinks() {
282
- if (this.#config.excludes?.includes('magicLink')) {
282
+ if (this.#config.excludes.includes('magicLink')) {
283
283
  return;
284
284
  }
285
285
  const { parseMagicLinks } = require('../parser/magicLinks');
@@ -287,7 +287,7 @@ class Token extends element_1.AstElement {
287
287
  }
288
288
  /** 解析列表 */
289
289
  #parseList() {
290
- if (this.#config.excludes?.includes('list')) {
290
+ if (this.#config.excludes.includes('list')) {
291
291
  return;
292
292
  }
293
293
  const { parseList } = require('../parser/list');
@@ -132,7 +132,7 @@ class LinkBaseToken extends index_2.Token {
132
132
  }
133
133
  /** @private */
134
134
  getTitle(temporary, halfParsed) {
135
- return this.normalizeTitle(this.firstChild.toString(true), 0, { halfParsed, temporary, decode: true, selfLink: true });
135
+ return this.normalizeTitle(this.firstChild.text(), 0, { halfParsed, temporary, decode: true, selfLink: true });
136
136
  }
137
137
  }
138
138
  exports.LinkBaseToken = LinkBaseToken;
@@ -12,12 +12,17 @@ const index_2 = require("./index");
12
12
  class InputboxToken extends index_2.ParamTagToken {
13
13
  /** @class */
14
14
  constructor(include, wikitext, config = index_1.default.getConfig(), accum = []) {
15
- const placeholder = Symbol('InputboxToken'), { length } = accum;
15
+ const placeholder = Symbol('InputboxToken'), newConfig = config.excludes.includes('heading')
16
+ ? config
17
+ : {
18
+ ...config,
19
+ excludes: [...config.excludes, 'heading'],
20
+ }, { length } = accum;
16
21
  accum.push(placeholder);
17
- wikitext &&= (0, commentAndExt_1.parseCommentAndExt)(wikitext, config, accum, include);
18
- wikitext &&= (0, braces_1.parseBraces)(wikitext, config, accum);
22
+ wikitext &&= (0, commentAndExt_1.parseCommentAndExt)(wikitext, newConfig, accum, include);
23
+ wikitext &&= (0, braces_1.parseBraces)(wikitext, newConfig, accum);
19
24
  accum.splice(length, 1);
20
- super(include, wikitext, config, accum, {});
25
+ super(include, wikitext, newConfig, accum, {});
21
26
  }
22
27
  }
23
28
  exports.InputboxToken = InputboxToken;
@@ -11,6 +11,7 @@ export type TableTokens = TableToken | TrToken | TdToken;
11
11
  export declare const isRowEnd: ({ type }: Token) => boolean;
12
12
  /** @extends {Array<TableCoords[]>} */
13
13
  export declare class Layout extends Array<TableCoords[]> {
14
+ abstract static from(arr: TableCoords[][]): Layout;
14
15
  }
15
16
  /**
16
17
  * table
@@ -108,7 +108,7 @@ class TableToken extends trBase_1.TrBaseToken {
108
108
  * @param stop.y stop at the column / 中止列
109
109
  */
110
110
  getLayout(stop) {
111
- const rows = this.getAllRows(), { length } = rows, layout = new Layout(...emptyArray(length, () => []));
111
+ const rows = this.getAllRows(), { length } = rows, layout = Layout.from(emptyArray(length, () => []));
112
112
  for (let i = 0; i < layout.length; i++) {
113
113
  const rowLayout = layout[i];
114
114
  let j = 0, k = 0, last;
@@ -4,7 +4,34 @@ exports.TrBaseToken = void 0;
4
4
  const lint_1 = require("../../util/lint");
5
5
  const base_1 = require("./base");
6
6
  const td_1 = require("./td");
7
- const tableTags = new Set(['tr', 'td', 'th', 'caption']);
7
+ const tableTags = new Set(['tr', 'td', 'th', 'caption']), tableTemplates = new Set(['Template:!!', 'Template:!-']);
8
+ /**
9
+ * Check if the content is fostered
10
+ * @param token
11
+ */
12
+ const isFostered = (token) => {
13
+ const first = token.childNodes.find(child => child.text().trim());
14
+ if (!first
15
+ || first.type === 'text' && first.data.trim().startsWith('!')
16
+ || first.type === 'magic-word' && first.name === '!'
17
+ || first.type === 'template' && tableTemplates.has(first.name)
18
+ || first.is('html') && tableTags.has(first.name)) {
19
+ return false;
20
+ }
21
+ else if (first.is('arg')) {
22
+ return first.length > 1 && isFostered(first.childNodes[1]);
23
+ }
24
+ else if (first.is('magic-word')) {
25
+ try {
26
+ const severity = first.getPossibleValues().map(isFostered);
27
+ return severity.includes('error')
28
+ ? 'error'
29
+ : severity.includes('warning') && 'warning';
30
+ }
31
+ catch { }
32
+ }
33
+ return first.type === 'template' || first.type === 'magic-word' && first.name === 'invoke' ? 'warning' : 'error';
34
+ };
8
35
  /**
9
36
  * table row or table
10
37
  *
@@ -17,23 +44,11 @@ class TrBaseToken extends base_1.TableBaseToken {
17
44
  if (!inter) {
18
45
  return errors;
19
46
  }
20
- const first = inter.childNodes.find(child => child.text().trim()), tdPattern = /^\s*(?:!|\{\{\s*![!-]?\s*\}\})/u;
21
- if (!first
22
- || tdPattern.test(first.toString())
23
- || first.is('arg') && tdPattern.test(first.default || '')
24
- || first.is('html') && tableTags.has(first.name)) {
47
+ const severity = isFostered(inter);
48
+ if (!severity) {
25
49
  return errors;
26
50
  }
27
- else if (first.is('magic-word')) {
28
- try {
29
- if (first.getPossibleValues().every(token => tdPattern.test(token.text()))) {
30
- return errors;
31
- }
32
- }
33
- catch { }
34
- }
35
- const error = (0, lint_1.generateForChild)(inter, { start }, 'fostered-content', 'content to be moved out from the table');
36
- error.severity = first.type === 'template' ? 'warning' : 'error';
51
+ const error = (0, lint_1.generateForChild)(inter, { start }, 'fostered-content', 'content to be moved out from the table', severity);
37
52
  error.startIndex++;
38
53
  error.startLine++;
39
54
  error.startCol = 0;
@@ -98,7 +98,7 @@ let ExtToken = (() => {
98
98
  attrToken = new attributes_1.AttributesToken(!attr || /^\s/u.test(attr) ? attr : ` ${attr}`, 'ext-attrs', lcName, config, accum), newConfig = {
99
99
  ...config,
100
100
  ext: del(config.ext, lcName),
101
- excludes: [...config.excludes ?? []],
101
+ excludes: [...config.excludes],
102
102
  };
103
103
  let innerToken;
104
104
  switch (lcName) {
@@ -127,11 +127,11 @@ let ExtToken = (() => {
127
127
  innerToken = new index_4.ParamTagToken(include, inner, newConfig, accum);
128
128
  break;
129
129
  case 'inputbox':
130
- newConfig.excludes.push('heading');
131
130
  // @ts-expect-error abstract class
132
131
  innerToken = new inputbox_1.InputboxToken(include, inner, newConfig, accum);
133
132
  break;
134
133
  case 'references': {
134
+ // NestedToken 依赖 ExtToken
135
135
  const { NestedToken } = require('../nested');
136
136
  newConfig.excludes.push('heading');
137
137
  // @ts-expect-error abstract class
@@ -139,12 +139,14 @@ let ExtToken = (() => {
139
139
  break;
140
140
  }
141
141
  case 'choose': {
142
+ // NestedToken 依赖 ExtToken
142
143
  const { NestedToken } = require('../nested');
143
144
  // @ts-expect-error abstract class
144
145
  innerToken = new NestedToken(inner, /<(option|choicetemplate)(\s[^>]*?)?(?:\/>|>([\s\S]*?)<\/(\1)>)/gu, ['option', 'choicetemplate'], newConfig, accum);
145
146
  break;
146
147
  }
147
148
  case 'combobox': {
149
+ // NestedToken 依赖 ExtToken
148
150
  const { NestedToken } = require('../nested');
149
151
  // @ts-expect-error abstract class
150
152
  innerToken = new NestedToken(inner, /<(combooption)(\s[^>]*?)?(?:\/>|>([\s\S]*?)<\/(combooption\s*)>)/giu, ['combooption'], newConfig, accum);
@@ -144,7 +144,7 @@ class TranscludeToken extends index_1.Token {
144
144
  }
145
145
  /** 获取模板或模块名 */
146
146
  #getTitle() {
147
- const isTemplate = this.type === 'template', title = this.normalizeTitle(this.childNodes[isTemplate ? 0 : 1].toString(true), isTemplate ? 10 : 828, { temporary: true });
147
+ const isTemplate = this.type === 'template', title = this.normalizeTitle(this.childNodes[isTemplate ? 0 : 1].text(), isTemplate ? 10 : 828, { temporary: true });
148
148
  return title;
149
149
  }
150
150
  /**
@@ -162,7 +162,7 @@ class TranscludeToken extends index_1.Token {
162
162
  return [
163
163
  this.#getTitle().title,
164
164
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
165
- this.childNodes[2]?.toString(true).trim(),
165
+ this.childNodes[2]?.text().trim(),
166
166
  ];
167
167
  }
168
168
  }
@@ -344,7 +344,7 @@ class TranscludeToken extends index_1.Token {
344
344
  if (type === 'template') {
345
345
  throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
346
346
  }
347
- let start;
347
+ let start, queue;
348
348
  switch (name) {
349
349
  case 'if':
350
350
  case 'ifexist':
@@ -355,10 +355,18 @@ class TranscludeToken extends index_1.Token {
355
355
  case 'ifeq':
356
356
  start = 3;
357
357
  break;
358
+ case 'switch': {
359
+ const parameters = childNodes.slice(2), last = parameters[parameters.length - 1];
360
+ queue = [
361
+ ...parameters.filter(({ anon }) => !anon),
362
+ ...last?.anon ? [last] : [],
363
+ ].map(({ lastChild }) => lastChild);
364
+ break;
365
+ }
358
366
  default:
359
367
  throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
360
368
  }
361
- const queue = childNodes.slice(start, start + 2).map(({ childNodes: [, value] }) => value);
369
+ queue ??= childNodes.slice(start, start + 2).map(({ lastChild }) => lastChild);
362
370
  for (let i = 0; i < queue.length;) {
363
371
  const { length, 0: first } = queue[i].childNodes.filter(child => child.text().trim());
364
372
  if (length === 0) {
package/dist/util/diff.js CHANGED
@@ -31,7 +31,7 @@ const cmd = (command, args) => new Promise(resolve => {
31
31
  shell = (0, child_process_1.spawn)(command, args);
32
32
  timer = setTimeout(() => {
33
33
  shell.kill('SIGINT');
34
- }, 60 * 1000);
34
+ }, 60 * 1e3);
35
35
  let buf = '';
36
36
  shell.stdout.on('data', data => {
37
37
  buf += String(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.18.3",
3
+ "version": "2.19.0",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -65,13 +65,14 @@
65
65
  ]
66
66
  },
67
67
  "dependencies": {
68
- "@bhsd/common": "^0.9.0",
68
+ "@bhsd/common": "^0.9.2",
69
69
  "vscode-languageserver-types": "^3.17.5"
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "chalk": "^4.1.2",
73
73
  "color-name": "^2.0.0",
74
74
  "entities": "^6.0.0",
75
+ "mathjax": "^3.2.2",
75
76
  "minimatch": "^10.0.1",
76
77
  "stylelint": "^16.14.1",
77
78
  "vscode-css-languageservice": "^6.3.2",
@@ -85,10 +86,10 @@
85
86
  "@types/color-rgba": "^2.1.3",
86
87
  "@types/mocha": "^10.0.10",
87
88
  "@types/node": "^22.13.1",
88
- "@typescript-eslint/eslint-plugin": "^8.23.0",
89
- "@typescript-eslint/parser": "^8.23.0",
89
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
90
+ "@typescript-eslint/parser": "^8.29.0",
90
91
  "color-rgba": "^3.0.0",
91
- "esbuild": "^0.25.0",
92
+ "esbuild": "^0.25.2",
92
93
  "eslint": "^8.57.1",
93
94
  "eslint-plugin-es-x": "^8.4.1",
94
95
  "eslint-plugin-eslint-comments": "^3.2.0",
@@ -103,7 +104,7 @@
103
104
  "mocha": "^11.1.0",
104
105
  "nyc": "^17.1.0",
105
106
  "stylelint-config-recommended": "^15.0.0",
106
- "typescript": "^5.7.3",
107
+ "typescript": "^5.8.2",
107
108
  "v8r": "^4.2.1",
108
109
  "vscode-languageserver-textdocument": "^1.0.12"
109
110
  },