wikilint 2.18.3 → 2.18.4

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/index.d.ts CHANGED
@@ -23,6 +23,15 @@ 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
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 },
package/dist/lib/lsp.js CHANGED
@@ -16,7 +16,6 @@ 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 = {
@@ -1116,13 +1115,13 @@ class LanguageService {
1116
1115
  }
1117
1116
  else if (type === 'magic-word-name') {
1118
1117
  info = this.#getParserFunction(parentNode.name);
1119
- f = offsetNode.toString(true).trim();
1118
+ f = offsetNode.text().trim();
1120
1119
  colon = parentNode.getAttribute('colon');
1121
1120
  }
1122
1121
  else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
1123
1122
  && (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
1124
1123
  info = this.#getParserFunction(name);
1125
- f = offsetNode.firstChild.toString(true).trim();
1124
+ f = offsetNode.firstChild.text().trim();
1126
1125
  colon = offsetNode.getAttribute('colon');
1127
1126
  }
1128
1127
  else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
@@ -1209,7 +1208,7 @@ class LanguageService {
1209
1208
  }
1210
1209
  const n = childNodes.length - 1, candidates = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
1211
1210
  && params.every(({ label, const: c }, i) => {
1212
- const p = c && i < n && childNodes[i + 1]?.toString(true).trim();
1211
+ const p = c && i < n && childNodes[i + 1]?.text().trim();
1213
1212
  return !p || label.toLowerCase().includes(p.toLowerCase());
1214
1213
  }));
1215
1214
  if (candidates.length === 0) {
@@ -1222,7 +1221,7 @@ class LanguageService {
1222
1221
  break;
1223
1222
  }
1224
1223
  }
1225
- const f = firstChild.toString(true).trim(), colon = lastChild.getAttribute('colon');
1224
+ const f = firstChild.text().trim(), colon = lastChild.getAttribute('colon');
1226
1225
  return {
1227
1226
  signatures: candidates.map((params) => ({
1228
1227
  label: `{{${f}${params.length === 0 ? '' : colon}${params.map(({ label }) => label).join('|')}}}`,
@@ -1337,7 +1336,7 @@ class LanguageService {
1337
1336
  this.config = index_1.default.getConfig(config);
1338
1337
  }
1339
1338
  catch {
1340
- this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`, false, true));
1339
+ this.config = await index_1.default.fetchConfig(site, `${mt[0]}/w`);
1341
1340
  }
1342
1341
  Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
1343
1342
  }
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 模板或魔术字名
@@ -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 */
@@ -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;
@@ -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;
@@ -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.18.4",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -65,7 +65,7 @@
65
65
  ]
66
66
  },
67
67
  "dependencies": {
68
- "@bhsd/common": "^0.9.0",
68
+ "@bhsd/common": "^0.9.1",
69
69
  "vscode-languageserver-types": "^3.17.5"
70
70
  },
71
71
  "optionalDependencies": {