wikiparser-node 1.18.0 → 1.18.1

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/lib/lsp.js CHANGED
@@ -3,7 +3,7 @@ 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.LanguageService = exports.tasks = void 0;
6
+ exports.LanguageService = exports.isAttr = exports.tasks = void 0;
7
7
  const common_1 = require("@bhsd/common");
8
8
  const sharable_1 = require("../util/sharable");
9
9
  const lint_1 = require("../util/lint");
@@ -19,6 +19,7 @@ const util_1 = __importDefault(require("util"));
19
19
  const child_process_1 = require("child_process");
20
20
  const crypto_1 = require("crypto");
21
21
  const stylelint_1 = require("@bhsd/common/dist/stylelint");
22
+ const config_1 = __importDefault(require("../bin/config"));
22
23
  const document_1 = require("./document");
23
24
  /** @see https://www.npmjs.com/package/stylelint-config-recommended */
24
25
  const cssRules = {
@@ -42,11 +43,30 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
42
43
  'heading',
43
44
  ...renameTypes,
44
45
  ]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
46
+ /**
47
+ * Check if a token is a plain attribute.
48
+ * @param token
49
+ * @param token.type
50
+ * @param token.parentNode
51
+ * @param token.length
52
+ * @param token.firstChild
53
+ * @param style whether it is a style attribute
54
+ */
55
+ const isAttr = ({ type, parentNode, length, firstChild }, style) => type === 'attr-value' && length === 1 && firstChild.type === 'text'
56
+ && (!style
57
+ || parentNode.name === 'style'
58
+ && Boolean(document_1.cssLSP));
59
+ exports.isAttr = isAttr;
60
+ /**
61
+ * Check if a token is an HTML attribute.
62
+ * @param token
63
+ */
64
+ const isHtmlAttr = (token) => token.type === 'html-attr' || token.type === 'table-attr';
45
65
  /**
46
66
  * Check if all child nodes are plain text or comments.
47
- * @param childNodes child nodes
67
+ * @param token
48
68
  */
49
- const isPlain = (childNodes) => childNodes.every(({ type }) => plainTypes.has(type));
69
+ const isPlain = (token) => token.childNodes.every(({ type }) => plainTypes.has(type));
50
70
  /**
51
71
  * Get the position of a character in the document.
52
72
  * @param root root token
@@ -216,11 +236,12 @@ const getStylelintPos = (rect, bottom, line, column) => {
216
236
  };
217
237
  /**
218
238
  * Convert LilyPond errors to VSCode diagnostics.
239
+ * @param root root token
219
240
  * @param token `<score>` extension token
220
241
  * @param errors LilyPond errors
221
242
  */
222
- const getLilyPondDiagnostics = (token, errors) => {
223
- const { top, left } = token.lastChild.getBoundingClientRect();
243
+ const getLilyPondDiagnostics = (root, token, errors) => {
244
+ const { top, left } = root.posFromIndex(token.lastChild.getAbsoluteIndex());
224
245
  return errors.map(({ line, col, message }) => {
225
246
  const pos = (0, lint_1.getEndPos)(top, left, line, col);
226
247
  return {
@@ -257,16 +278,20 @@ class LanguageService {
257
278
  #completionConfig;
258
279
  include = true;
259
280
  /** @private */
281
+ config;
282
+ /** @private */
260
283
  data;
261
284
  /* NOT FOR BROWSER ONLY */
262
285
  lilypond;
286
+ /** @private */
263
287
  lilypondData;
264
288
  /* NOT FOR BROWSER ONLY END */
265
289
  /** @param uri 任务标识 */
266
290
  constructor(uri) {
267
291
  exports.tasks.set(uri, this);
268
- /* NOT FOR BROWSER ONLY */
269
292
  Object.defineProperties(this, {
293
+ config: { enumerable: false },
294
+ /* NOT FOR BROWSER ONLY */
270
295
  data: {
271
296
  value: require(path_1.default.join('..', '..', 'data', 'signatures')),
272
297
  enumerable: false,
@@ -284,16 +309,18 @@ class LanguageService {
284
309
  const dir = path_1.default.join(__dirname, 'lilypond');
285
310
  if (fs_1.default.existsSync(dir)) {
286
311
  for (const file of fs_1.default.readdirSync(dir)) {
287
- try {
288
- void fs_1.default.promises.unlink(path_1.default.join(dir, file));
289
- }
290
- catch { }
312
+ (async () => {
313
+ try {
314
+ await fs_1.default.promises.unlink(path_1.default.join(dir, file));
315
+ }
316
+ catch { }
317
+ })();
291
318
  }
292
319
  }
293
320
  }
294
321
  /** 检查解析设置有无更新 */
295
322
  #checkConfig() {
296
- return this.#config === index_1.default.config && this.#include === this.include;
323
+ return this.#config === this.config && this.#include === this.include;
297
324
  }
298
325
  /**
299
326
  * 提交解析任务
@@ -319,10 +346,10 @@ class LanguageService {
319
346
  * - 总是返回最新的解析结果
320
347
  */
321
348
  async #parse() {
322
- const config = index_1.default.getConfig();
323
- this.#config = index_1.default.config;
349
+ this.config ??= index_1.default.getConfig();
350
+ this.#config = this.config;
324
351
  this.#include = this.include;
325
- const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, config);
352
+ const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, this.config);
326
353
  if (this.#checkConfig() && this.#text === text) {
327
354
  this.#done = root;
328
355
  this.#running = undefined;
@@ -357,10 +384,10 @@ class LanguageService {
357
384
  * - 总是返回最新的解析结果
358
385
  */
359
386
  async #parseSignature() {
360
- const config = index_1.default.getConfig();
361
- this.#config = index_1.default.config;
387
+ this.config ??= index_1.default.getConfig();
388
+ this.#config = this.config;
362
389
  this.#include = this.include;
363
- const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, config);
390
+ const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, this.config);
364
391
  if (this.#checkConfig() && this.#text2 === text) {
365
392
  this.#done2 = root;
366
393
  this.#running2 = undefined;
@@ -381,14 +408,38 @@ class LanguageService {
381
408
  */
382
409
  async provideDocumentColors(rgba, text, hsl = true) {
383
410
  const root = await this.#queue(text);
411
+ /* NOT FOR BROWSER ONLY */
412
+ let colors;
413
+ try {
414
+ colors = new RegExp(String.raw `\b${Object.keys((await import('color-name')).default).join('|')}\b`, 'giu');
415
+ }
416
+ catch { }
417
+ /* NOT FOR BROWSER ONLY END */
384
418
  return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse()
385
- .flatMap(({ type, childNodes }) => {
386
- if (type !== 'attr-value' && !isPlain(childNodes)) {
419
+ .flatMap(token => {
420
+ const { type, childNodes,
421
+ /* NOT FOR BROWSER ONLY */
422
+ parentNode, } = token;
423
+ if (type !== 'attr-value' && !isPlain(token)) {
387
424
  return [];
425
+ /* NOT FOR BROWSER ONLY */
426
+ }
427
+ else if ((0, exports.isAttr)(token, true)) {
428
+ const textDoc = new document_1.EmbeddedCSSDocument(root, token);
429
+ return document_1.cssLSP.findDocumentColors(textDoc, textDoc.styleSheet);
430
+ /* NOT FOR BROWSER ONLY END */
388
431
  }
432
+ /* NOT FOR BROWSER ONLY */
433
+ const isStyle = colors && type === 'attr-value' && parentNode.name === 'style';
434
+ /* NOT FOR BROWSER ONLY END */
389
435
  return childNodes.filter((child) => child.type === 'text').reverse()
390
436
  .flatMap(child => {
391
- const parts = (0, common_1.splitColors)(child.data, hsl).filter(([, , , isColor]) => isColor);
437
+ const { data } = child, parts = (0, common_1.splitColors)(data, hsl).filter(([, , , isColor]) => isColor);
438
+ /* NOT FOR BROWSER ONLY */
439
+ if (isStyle) {
440
+ parts.push(...[...data.matchAll(colors)].map(({ index, 0: s }) => [s, index, index + s.length, true]));
441
+ }
442
+ /* NOT FOR BROWSER ONLY END */
392
443
  if (parts.length === 0) {
393
444
  return [];
394
445
  }
@@ -426,8 +477,9 @@ class LanguageService {
426
477
  }
427
478
  /** 准备自动补全设置 */
428
479
  #prepareCompletionConfig() {
429
- if (!this.#completionConfig) {
430
- const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = index_1.default.getConfig(), tags = new Set([ext, html].flat(2));
480
+ if (!this.#completionConfig || this.#completionConfig[1] !== this.config) {
481
+ this.config ??= index_1.default.getConfig();
482
+ const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = this.config, tags = new Set([ext, html].flat(2));
431
483
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions, es-x/no-regexp-unicode-property-escapes
432
484
  /(?:<\/?(\w*)|(\{{2,4}|\[\[)\s*([^|{}<>[\]\s][^|{}<>[\]#]*)?|(__(?:(?!__)[\p{L}\d_])*)|(?<!\[)\[([a-z:/]*)|\[\[\s*(?:file|image)\s*:[^[\]{}<>]+\|([^[\]{}<>|=]*)|<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*))$/iu;
433
485
  const re = new RegExp('(?:' // eslint-disable-line prefer-template
@@ -444,23 +496,27 @@ class LanguageService {
444
496
  // attribute key
445
497
  + String.raw `<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*)`
446
498
  + ')$', 'iu');
447
- this.#completionConfig = {
448
- re,
449
- ext,
450
- tags,
451
- allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
452
- functions: [
453
- Object.keys(insensitive),
454
- Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
455
- other,
456
- ].flat(2),
457
- switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
458
- protocols: protocol.split('|'),
459
- params: Object.keys(img).filter(k => k.endsWith('$1') || !k.includes('$1'))
460
- .map(k => k.replace(/\$1$/u, '')),
461
- };
499
+ this.#completionConfig = [
500
+ {
501
+ re,
502
+ ext,
503
+ tags,
504
+ allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
505
+ functions: [
506
+ Object.keys(insensitive),
507
+ Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
508
+ other,
509
+ ].flat(2),
510
+ switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
511
+ protocols: protocol.split('|'),
512
+ params: Object.keys(img)
513
+ .filter(k => k.endsWith('$1') || !k.includes('$1'))
514
+ .map(k => k.replace(/\$1$/u, '')),
515
+ },
516
+ this.config,
517
+ ];
462
518
  }
463
- return this.#completionConfig;
519
+ return this.#completionConfig[0];
464
520
  }
465
521
  /**
466
522
  * Provide auto-completion
@@ -470,13 +526,25 @@ class LanguageService {
470
526
  * @param position 位置
471
527
  */
472
528
  async provideCompletionItems(text, position) {
473
- const { re, allTags, functions, switches, 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) ?? '');
529
+ const { re, allTags, functions, switches, 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;
474
530
  if (mt?.[1] !== undefined) { // tag
475
531
  const closing = mt[1].startsWith('/');
476
532
  return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
477
533
  }
478
534
  else if (mt?.[4]) { // behavior switch
479
- return getCompletion(switches, 'Constant', mt[4], position, '', name => this.data && this.#getBehaviorSwitch(name.slice(2, -2)));
535
+ return getCompletion(switches, 'Constant', mt[4], position, '', name => {
536
+ if (!this.data) {
537
+ return undefined;
538
+ }
539
+ name = name.slice(2, -2);
540
+ if (name in iAlias) {
541
+ name = iAlias[name];
542
+ }
543
+ else if (name in sAlias) {
544
+ name = sAlias[name];
545
+ }
546
+ return this.#getBehaviorSwitch(name.toLowerCase());
547
+ });
480
548
  }
481
549
  else if (mt?.[5] !== undefined) { // protocol
482
550
  return getCompletion(protocols, 'Reference', mt[5], position);
@@ -490,12 +558,23 @@ class LanguageService {
490
558
  return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
491
559
  .map(({ name }) => name), 'Variable', match, position);
492
560
  }
493
- const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
561
+ const [insensitive, sensitive] = this.config.parserFunction, isOld = Array.isArray(sensitive), colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
494
562
  return mt[2] === '[['
495
563
  ? getCompletion(// link
496
564
  root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
497
565
  : [
498
- ...getCompletion(functions, 'Function', match, position, '', name => this.data && this.#getParserFunction(name.replace(/^#/u, '').toLowerCase())),
566
+ ...getCompletion(functions, 'Function', match, position, '', name => {
567
+ if (!this.data) {
568
+ return undefined;
569
+ }
570
+ else if (name in insensitive) {
571
+ name = insensitive[name];
572
+ }
573
+ else if (!isOld && name in sensitive) {
574
+ name = sensitive[name];
575
+ }
576
+ return this.#getParserFunction(name.toLowerCase());
577
+ }),
499
578
  ...match.startsWith('#')
500
579
  ? []
501
580
  : getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
@@ -555,7 +634,7 @@ class LanguageService {
555
634
  if (t === 'magic-word' && n !== 'invoke') {
556
635
  return undefined;
557
636
  }
558
- const index = root.indexFromPos(line, character), key = this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
637
+ const key = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
559
638
  return key
560
639
  ? getCompletion(root.querySelectorAll('parameter').filter(token => {
561
640
  if (token === parentNode
@@ -573,7 +652,7 @@ class LanguageService {
573
652
  : undefined;
574
653
  /* NOT FOR BROWSER ONLY */
575
654
  }
576
- else if (document_1.cssLSP && type === 'attr-value' && parentNode.name === 'style' && cur.length === 1) {
655
+ else if ((0, exports.isAttr)(cur, true)) {
577
656
  const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
578
657
  return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
579
658
  ...item,
@@ -592,7 +671,7 @@ class LanguageService {
592
671
  if (lang !== undefined && lang !== 'lilypond') {
593
672
  return undefined;
594
673
  }
595
- const j = root.indexFromPos(line, character), i = cur.getAbsoluteIndex(), before = this.#text.slice(i, j), comment = before.lastIndexOf('%');
674
+ const before = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)), comment = before.lastIndexOf('%');
596
675
  if (comment !== -1
597
676
  && (before.charAt(comment + 1) === '{' || !before.slice(comment).includes('\n'))) {
598
677
  return undefined;
@@ -609,6 +688,14 @@ class LanguageService {
609
688
  }
610
689
  /* NOT FOR BROWSER ONLY END */
611
690
  }
691
+ else if ((0, exports.isAttr)(cur) && isHtmlAttr(parentNode)) {
692
+ const data = lint_1.htmlData.provideValues(parentNode.tag, parentNode.name);
693
+ if (data.length === 0) {
694
+ return undefined;
695
+ }
696
+ const val = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart();
697
+ return getCompletion(data.map(({ name }) => name), 'Value', val, position);
698
+ }
612
699
  return undefined;
613
700
  }
614
701
  /**
@@ -700,7 +787,7 @@ class LanguageService {
700
787
  lilypondDiagnostics = await Promise.all(tokens.map(async (token) => {
701
788
  const { innerText } = token, score = `showLastLength = R1${token.getAttr('raw') === undefined ? ` \\score {\n${innerText}\n}` : `\n${innerText}`}`;
702
789
  if (scores.has(score)) {
703
- return getLilyPondDiagnostics(token, scores.get(score));
790
+ return getLilyPondDiagnostics(root, token, scores.get(score));
704
791
  }
705
792
  const hash = (0, crypto_1.createHash)('sha256');
706
793
  hash.update(score);
@@ -722,7 +809,7 @@ class LanguageService {
722
809
  };
723
810
  });
724
811
  scores.set(score, lilypondErrors);
725
- return getLilyPondDiagnostics(token, lilypondErrors);
812
+ return getLilyPondDiagnostics(root, token, lilypondErrors);
726
813
  }
727
814
  }
728
815
  return [];
@@ -802,8 +889,9 @@ class LanguageService {
802
889
  * @param text source Wikitext / 源代码
803
890
  */
804
891
  async provideLinks(text) {
892
+ this.config ??= index_1.default.getConfig();
805
893
  /^(?:http:\/\/|\/\/)/iu; // eslint-disable-line @typescript-eslint/no-unused-expressions
806
- const { articlePath, protocol } = index_1.default.getConfig(), absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
894
+ const { articlePath, protocol } = this.config, absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
807
895
  return (await this.#queue(text))
808
896
  .querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`)
809
897
  .reverse()
@@ -812,7 +900,7 @@ class LanguageService {
812
900
  if (!(type !== 'attr-value'
813
901
  || name === 'src' && ['templatestyles', 'img'].includes(tag)
814
902
  || name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
815
- || !isPlain(childNodes)) {
903
+ || !isPlain(token)) {
816
904
  return false;
817
905
  }
818
906
  let target = childNodes.filter((node) => node.type === 'text')
@@ -852,8 +940,7 @@ class LanguageService {
852
940
  else if (type === 'invoke-module') {
853
941
  ns = 828;
854
942
  }
855
- const title = index_1.default
856
- .normalizeTitle(target, ns, false, undefined, true);
943
+ const title = index_1.default.normalizeTitle(target, ns, false, this.config, true);
857
944
  /* istanbul ignore if */
858
945
  if (!title.valid) {
859
946
  return false;
@@ -997,40 +1084,74 @@ class LanguageService {
997
1084
  if (!this.data) {
998
1085
  return undefined;
999
1086
  }
1000
- const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, this.#text, position), token = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, { type, parentNode, length, name } = token;
1087
+ const root = await this.#queue(text);
1088
+ let { offsetNode, offset } = caretPositionFromWord(root, this.#text, position);
1089
+ if (offsetNode.type === 'text') {
1090
+ offset += offsetNode.getRelativeIndex();
1091
+ offsetNode = offsetNode.parentNode;
1092
+ }
1093
+ const { type, parentNode, length, name } = offsetNode;
1001
1094
  let info, f, range;
1002
- if (token.is('double-underscore') && offset > 0) {
1003
- info = this.#getBehaviorSwitch(token.innerText.toLowerCase());
1095
+ if (offsetNode.is('double-underscore') && offset > 0) {
1096
+ info = this.#getBehaviorSwitch(offsetNode.name);
1004
1097
  }
1005
1098
  else if (type === 'magic-word-name') {
1006
1099
  info = this.#getParserFunction(parentNode.name);
1007
- f = token.toString(true).trim();
1100
+ f = offsetNode.toString(true).trim();
1008
1101
  }
1009
- else if (token.is('magic-word') && !token.modifier && length === 1
1010
- && (offset > 0 || root.posFromIndex(token.getAbsoluteIndex()).left === position.character)) {
1102
+ else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
1103
+ && (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
1011
1104
  info = this.#getParserFunction(name);
1012
- f = token.firstChild.toString(true).trim();
1105
+ f = offsetNode.firstChild.toString(true).trim();
1013
1106
  }
1014
- else if ((token.is('magic-word') || token.is('template'))
1015
- && token.modifier && offset >= 2 && token.getRelativeIndex(0) > offset) {
1016
- f = token.modifier.trim().slice(0, -1);
1107
+ else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
1108
+ && offsetNode.modifier && offset >= 2 && offsetNode.getRelativeIndex(0) > offset) {
1109
+ f = offsetNode.modifier.trim().slice(0, -1);
1017
1110
  info = this.#getParserFunction(f.toLowerCase());
1018
1111
  if (info) {
1019
- const aIndex = token.getAbsoluteIndex();
1112
+ const aIndex = offsetNode.getAbsoluteIndex();
1020
1113
  range = {
1021
1114
  start: positionAt(root, aIndex + 2),
1022
- end: positionAt(root, aIndex + token.modifier.trimEnd().length + 1),
1115
+ end: positionAt(root, aIndex + offsetNode.modifier.trimEnd().length + 1),
1023
1116
  };
1024
1117
  }
1025
1118
  /* NOT FOR BROWSER ONLY */
1026
1119
  }
1027
- else if (document_1.cssLSP && type === 'attr-value' && length === 1 && parentNode.name === 'style') {
1028
- const textDoc = new document_1.EmbeddedCSSDocument(root, token);
1120
+ else if ((0, exports.isAttr)(offsetNode, true)) {
1121
+ const textDoc = new document_1.EmbeddedCSSDocument(root, offsetNode);
1029
1122
  return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
1030
1123
  }
1031
1124
  else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(name)) {
1032
- const textDoc = new document_1.EmbeddedJSONDocument(root, token);
1125
+ const textDoc = new document_1.EmbeddedJSONDocument(root, offsetNode);
1033
1126
  return await document_1.jsonLSP.doHover(textDoc, position, textDoc.jsonDoc) ?? undefined;
1127
+ }
1128
+ else if (lint_1.htmlData.provideTags && lint_1.htmlData.provideAttributes) {
1129
+ if (type === 'html' && offset <= offsetNode.getRelativeIndex(0)
1130
+ || type === 'html-attr-dirty' && offset === 0 && parentNode.firstChild === offsetNode) {
1131
+ const token = type === 'html' ? offsetNode : parentNode.parentNode, data = lint_1.htmlData.provideTags().find(({ name: n }) => n === token.name);
1132
+ if (data?.description) {
1133
+ const start = positionAt(root, token.getAbsoluteIndex());
1134
+ return {
1135
+ contents: data.description,
1136
+ range: {
1137
+ start,
1138
+ end: {
1139
+ line: start.line,
1140
+ character: start.character + token.getRelativeIndex(0),
1141
+ },
1142
+ },
1143
+ };
1144
+ }
1145
+ }
1146
+ else if (type === 'attr-key' && isHtmlAttr(parentNode)) {
1147
+ const data = lint_1.htmlData.provideAttributes(parentNode.tag).find(({ name: n }) => n === parentNode.name);
1148
+ if (data?.description) {
1149
+ return {
1150
+ contents: data.description,
1151
+ range: createNodeRange(offsetNode),
1152
+ };
1153
+ }
1154
+ }
1034
1155
  /* NOT FOR BROWSER ONLY END */
1035
1156
  }
1036
1157
  return info && {
@@ -1041,7 +1162,7 @@ class LanguageService {
1041
1162
  : '')
1042
1163
  + info.description,
1043
1164
  },
1044
- range: range ?? createNodeRange(token),
1165
+ range: range ?? createNodeRange(offsetNode),
1045
1166
  };
1046
1167
  }
1047
1168
  /**
@@ -1114,8 +1235,7 @@ class LanguageService {
1114
1235
  }
1115
1236
  /** @private */
1116
1237
  findStyleTokens() {
1117
- return this.#done.querySelectorAll(cssSelector)
1118
- .filter(({ lastChild: { length, firstChild } }) => length === 1 && firstChild.type === 'text');
1238
+ return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => (0, exports.isAttr)(lastChild));
1119
1239
  }
1120
1240
  /* NOT FOR BROWSER ONLY */
1121
1241
  /**
@@ -1177,6 +1297,28 @@ class LanguageService {
1177
1297
  }
1178
1298
  return symbols;
1179
1299
  }
1300
+ /**
1301
+ * Set the target Wikipedia
1302
+ *
1303
+ * 设置目标维基百科
1304
+ * @param wiki Wikipedia URL / 维基百科网址
1305
+ * @throws `RangeError` 不是有效的维基百科网址
1306
+ */
1307
+ async setTargetWikipedia(wiki) {
1308
+ const mt = /^https?:\/\/([^./]+)\.wikipedia\.org/iu.exec(wiki);
1309
+ if (!mt) {
1310
+ throw new RangeError('Invalid Wikipedia URL!');
1311
+ }
1312
+ const site = `${mt[1].toLowerCase()}wiki`;
1313
+ try {
1314
+ const config = require(path_1.default.join('..', '..', 'config', site));
1315
+ this.config = index_1.default.getConfig(config);
1316
+ }
1317
+ catch {
1318
+ this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`));
1319
+ }
1320
+ Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
1321
+ }
1180
1322
  }
1181
1323
  exports.LanguageService = LanguageService;
1182
1324
  constants_1.classes['LanguageService'] = __filename;
@@ -51,7 +51,7 @@ const debug_1 = require("../util/debug");
51
51
  const document_1 = require("../lib/document");
52
52
  const fixed_1 = require("../mixin/fixed");
53
53
  const stages = { 'ext-attr': 0, 'html-attr': 2, 'table-attr': 3 };
54
- const insecureStyle = /expression|(?:accelerator|-o-link(?:-source)?|-o-replace)\s*:|(?:url|image(?:-set)?)\s*\(|attr\s*\([^)]+[\s,]url/u;
54
+ const insecureStyle = /expression|(?:accelerator|-o-link(?:-source)?|-o-replace)\s*:|(?:url|image(?:-set)?)\s*\(|attr\s*\([^)]+[\s,]url/u, complexTypes = new Set(['ext', 'arg', 'magic-word', 'template']);
55
55
  /**
56
56
  * attribute of extension and HTML tags
57
57
  *
@@ -201,9 +201,6 @@ let AttributeToken = (() => {
201
201
  e.suggestions = [{ desc: 'remove', range: [start, start + length], text: '' }];
202
202
  errors.push(e);
203
203
  }
204
- else if (sharable_1.obsoleteAttrs[tag]?.has(name)) {
205
- errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
206
- }
207
204
  else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
208
205
  errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure-style', 'insecure style'));
209
206
  }
@@ -215,6 +212,15 @@ let AttributeToken = (() => {
215
212
  ];
216
213
  errors.push(e);
217
214
  }
215
+ else if (type !== 'ext-attr' && !lastChild.childNodes.some(({ type: t }) => complexTypes.has(t))) {
216
+ const data = lint_1.htmlData.provideValues(tag, name), v = String(value).toLowerCase();
217
+ if (data.length > 0 && data.every(({ name: n }) => n !== v)) {
218
+ errors.push((0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'illegal attribute value', 'warning'));
219
+ }
220
+ }
221
+ if (sharable_1.obsoleteAttrs[tag]?.has(name)) {
222
+ errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
223
+ }
218
224
  return errors;
219
225
  }
220
226
  /**
package/dist/src/index.js CHANGED
@@ -61,6 +61,7 @@ const range_1 = require("../lib/range");
61
61
  /* NOT FOR BROWSER END */
62
62
  /* NOT FOR BROWSER ONLY */
63
63
  const document_1 = require("../lib/document");
64
+ const lsp_1 = require("../lib/lsp");
64
65
  /* NOT FOR BROWSER */
65
66
  /**
66
67
  * 可接受的Token类型
@@ -524,8 +525,7 @@ class Token extends element_1.AstElement {
524
525
  });
525
526
  /* NOT FOR BROWSER ONLY */
526
527
  }
527
- else if (document_1.cssLSP && this.type === 'attr-value' && this.length === 1 && this.firstChild.type === 'text'
528
- && this.parentNode.name === 'style') {
528
+ else if ((0, lsp_1.isAttr)(this, true)) {
529
529
  const root = this.getRootNode(), textDoc = new document_1.EmbeddedCSSDocument(root, this);
530
530
  errors.push(...document_1.cssLSP.doValidation(textDoc, textDoc.styleSheet)
531
531
  .filter(({ code }) => code !== 'css-ruleorselectorexpected')
@@ -62,6 +62,7 @@ let DoubleUnderscoreToken = (() => {
62
62
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
63
63
  __runInitializers(_classThis, _classExtraInitializers);
64
64
  }
65
+ /* NOT FOR BROWSER */
65
66
  #sensitive;
66
67
  /* NOT FOR BROWSER END */
67
68
  get type() {
@@ -73,10 +74,10 @@ let DoubleUnderscoreToken = (() => {
73
74
  */
74
75
  constructor(word, sensitive, config, accum) {
75
76
  super(word, config, accum);
77
+ const lc = word.toLowerCase(), { doubleUnderscore: [, , iAlias, sAlias] } = config;
78
+ this.setAttribute('name', (sensitive ? sAlias?.[word]?.toLowerCase() : iAlias?.[lc]) ?? lc);
76
79
  /* NOT FOR BROWSER */
77
- const lc = word.toLowerCase();
78
80
  this.#sensitive = sensitive;
79
- this.setAttribute('name', sensitive ? lc : config.doubleUnderscore[2]?.[lc] ?? lc);
80
81
  this.setAttribute('pattern', new RegExp(`^${word}$`, sensitive ? 'u' : 'iu'));
81
82
  }
82
83
  /** @private */
package/dist/util/diff.js CHANGED
@@ -73,7 +73,7 @@ const diff = async (oldStr, newStr, uid) => {
73
73
  newFile,
74
74
  ]);
75
75
  console.log(stdout?.split('\n').slice(4).join('\n'));
76
- await Promise.all([promises_1.default.unlink(oldFile), promises_1.default.unlink(newFile)]);
76
+ await Promise.allSettled([promises_1.default.unlink(oldFile), promises_1.default.unlink(newFile)]);
77
77
  };
78
78
  exports.diff = diff;
79
79
  /* istanbul ignore next */
package/dist/util/lint.js CHANGED
@@ -3,7 +3,7 @@ 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.cache = exports.generateForSelf = exports.generateForChild = exports.getEndPos = void 0;
6
+ exports.htmlData = exports.cache = exports.generateForSelf = exports.generateForChild = exports.getEndPos = void 0;
7
7
  const debug_1 = require("./debug");
8
8
  const rect_1 = require("../lib/rect");
9
9
  const index_1 = __importDefault(require("../index"));
@@ -65,3 +65,33 @@ const cache = (store, compute, update) => {
65
65
  return result;
66
66
  };
67
67
  exports.cache = cache;
68
+ let htmlData;
69
+ try {
70
+ exports.htmlData = htmlData = require('vscode-html-languageservice')
71
+ .getDefaultHTMLDataProvider();
72
+ }
73
+ catch {
74
+ /**
75
+ * 获取HTML属性值可选列表
76
+ * @param tag 标签名
77
+ * @param attribute 属性名
78
+ */
79
+ const provideValues = (tag, attribute) => {
80
+ if (tag === 'ol' && attribute === 'type') {
81
+ return ['1', 'a', 'A', 'i', 'I'];
82
+ }
83
+ else if (tag === 'th' && attribute === 'scope') {
84
+ return ['row', 'col', 'rowgroup', 'colgroup'];
85
+ }
86
+ else if (attribute === 'dir') {
87
+ return ['ltr', 'rtl', 'auto'];
88
+ }
89
+ return attribute === 'aria-hidden' ? ['true', 'false'] : [];
90
+ };
91
+ exports.htmlData = htmlData = {
92
+ /** @implements */
93
+ provideValues(tag, attribute) {
94
+ return provideValues(tag, attribute).map(value => ({ name: value }));
95
+ },
96
+ };
97
+ }
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  var _a;
3
- const version = '1.18.0', src = (_a = document.currentScript) === null || _a === void 0 ? void 0 : _a.src, file = /\/extensions\/dist\/base\.(?:min\.)?js$/u, CDN = src && file.test(src)
3
+ const version = '1.18.1', src = (_a = document.currentScript) === null || _a === void 0 ? void 0 : _a.src, file = /\/extensions\/dist\/base\.(?:min\.)?js$/u, CDN = src && file.test(src)
4
4
  ? src.replace(file, '')
5
5
  : `https://testingcf.jsdelivr.net/npm/wikiparser-node@${version}`;
6
6
  const workerJS = () => {