wikilint 2.16.6 → 2.18.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.
Files changed (96) hide show
  1. package/README.md +25 -2
  2. package/bin/config.js +3 -0
  3. package/config/.schema.json +7 -3
  4. package/config/default.json +86 -23
  5. package/config/enwiki.json +20 -13
  6. package/config/llwiki.json +99 -7
  7. package/config/minimum.json +6 -3
  8. package/config/moegirl.json +12 -12
  9. package/config/zhwiki.json +52 -21
  10. package/coverage/badge.svg +1 -1
  11. package/data/.schema.json +7 -0
  12. package/data/ext/mapframe.json +4 -0
  13. package/data/ext/maplink.json +4 -0
  14. package/data/ext/score.json +1033 -0
  15. package/data/ext/templatedata.json +184 -0
  16. package/dist/base.d.mts +4 -2
  17. package/dist/base.d.ts +4 -2
  18. package/dist/base.js +2 -0
  19. package/dist/base.mjs +3 -1
  20. package/dist/bin/cli.js +224 -47
  21. package/dist/bin/config.js +112 -0
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +12 -9
  24. package/dist/lib/document.d.ts +54 -0
  25. package/dist/lib/document.js +122 -0
  26. package/dist/lib/element.d.ts +6 -0
  27. package/dist/lib/element.js +5 -1
  28. package/dist/lib/lsp.d.ts +7 -7
  29. package/dist/lib/lsp.js +313 -47
  30. package/dist/lib/node.js +4 -1
  31. package/dist/lib/text.js +4 -1
  32. package/dist/lib/title.js +4 -4
  33. package/dist/parser/braces.js +0 -1
  34. package/dist/parser/commentAndExt.js +0 -1
  35. package/dist/parser/converter.js +0 -1
  36. package/dist/parser/externalLinks.js +0 -1
  37. package/dist/parser/hrAndDoubleUnderscore.js +0 -1
  38. package/dist/parser/html.js +0 -1
  39. package/dist/parser/links.js +4 -2
  40. package/dist/parser/list.js +0 -1
  41. package/dist/parser/magicLinks.js +0 -1
  42. package/dist/parser/quotes.js +0 -1
  43. package/dist/parser/redirect.js +4 -2
  44. package/dist/parser/table.js +0 -1
  45. package/dist/src/arg.d.ts +2 -3
  46. package/dist/src/arg.js +10 -7
  47. package/dist/src/attribute.js +4 -1
  48. package/dist/src/attributes.d.ts +2 -3
  49. package/dist/src/attributes.js +5 -2
  50. package/dist/src/converter.d.ts +2 -2
  51. package/dist/src/converter.js +3 -4
  52. package/dist/src/converterFlags.d.ts +2 -3
  53. package/dist/src/converterFlags.js +5 -6
  54. package/dist/src/converterRule.js +4 -1
  55. package/dist/src/extLink.js +4 -1
  56. package/dist/src/gallery.js +7 -3
  57. package/dist/src/heading.d.ts +2 -3
  58. package/dist/src/heading.js +10 -5
  59. package/dist/src/html.d.ts +0 -3
  60. package/dist/src/html.js +82 -91
  61. package/dist/src/imageParameter.d.ts +2 -3
  62. package/dist/src/imageParameter.js +9 -3
  63. package/dist/src/imagemap.js +4 -1
  64. package/dist/src/imagemapLink.d.ts +2 -2
  65. package/dist/src/imagemapLink.js +5 -6
  66. package/dist/src/index.js +25 -1
  67. package/dist/src/link/base.js +4 -1
  68. package/dist/src/link/file.js +4 -1
  69. package/dist/src/link/galleryImage.js +4 -1
  70. package/dist/src/magicLink.js +4 -1
  71. package/dist/src/nested.d.ts +2 -3
  72. package/dist/src/nested.js +5 -2
  73. package/dist/src/nowiki/comment.js +4 -1
  74. package/dist/src/nowiki/doubleUnderscore.d.ts +2 -2
  75. package/dist/src/nowiki/doubleUnderscore.js +1 -2
  76. package/dist/src/nowiki/index.js +4 -1
  77. package/dist/src/nowiki/quote.js +4 -1
  78. package/dist/src/paramTag/index.js +5 -1
  79. package/dist/src/paramTag/inputbox.js +4 -1
  80. package/dist/src/parameter.js +4 -1
  81. package/dist/src/pre.js +4 -1
  82. package/dist/src/redirect.d.ts +2 -3
  83. package/dist/src/redirect.js +6 -5
  84. package/dist/src/table/base.js +4 -1
  85. package/dist/src/table/index.d.ts +8 -0
  86. package/dist/src/table/index.js +13 -3
  87. package/dist/src/table/td.js +4 -1
  88. package/dist/src/tagPair/ext.js +9 -2
  89. package/dist/src/tagPair/include.js +4 -1
  90. package/dist/src/transclude.d.ts +2 -3
  91. package/dist/src/transclude.js +8 -8
  92. package/dist/util/diff.js +20 -6
  93. package/dist/util/lint.js +4 -1
  94. package/dist/util/string.js +5 -2
  95. package/package.json +20 -14
  96. package/dist/util/sharable.d.ts +0 -1
package/dist/lib/lsp.js CHANGED
@@ -1,12 +1,27 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.LanguageService = exports.tasks = void 0;
4
- const path = require("path");
5
7
  const common_1 = require("@bhsd/common");
6
8
  const sharable_1 = require("../util/sharable");
7
9
  const lint_1 = require("../util/lint");
8
10
  const string_1 = require("../util/string");
9
- const index_1 = require("../index");
11
+ const index_1 = __importDefault(require("../index"));
12
+ /* NOT FOR BROWSER ONLY */
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const util_1 = __importDefault(require("util"));
16
+ const child_process_1 = require("child_process");
17
+ const crypto_1 = require("crypto");
18
+ const stylelint_1 = require("@bhsd/common/dist/stylelint");
19
+ const document_1 = require("./document");
20
+ /** @see https://www.npmjs.com/package/stylelint-config-recommended */
21
+ const cssRules = {
22
+ 'block-no-empty': null,
23
+ 'property-no-unknown': null,
24
+ }, jsonSelector = document_1.jsonTags.map(s => `ext-inner#${s}`).join(), scores = new Map();
10
25
  /* NOT FOR BROWSER ONLY END */
11
26
  exports.tasks = new WeakMap();
12
27
  const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group']), renameTypes = new Set([
@@ -23,7 +38,7 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
23
38
  'heading-title',
24
39
  'heading',
25
40
  ...renameTypes,
26
- ]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']);
41
+ ]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
27
42
  /**
28
43
  * Check if all child nodes are plain text or comments.
29
44
  * @param childNodes child nodes
@@ -68,28 +83,39 @@ const createNodeRange = (token) => {
68
83
  * @param pos.line line number
69
84
  * @param pos.character character number
70
85
  * @param extra extra text
86
+ * @param getDoc documentation method
71
87
  */
72
- const getCompletion = (words, kind, mt, { line, character }, extra) => [...new Set(words)].map((w) => ({
73
- label: w,
74
- kind,
75
- textEdit: {
76
- range: {
77
- start: { line, character: character - mt.length },
78
- end: { line, character },
88
+ const getCompletion = (words, kind, mt, { line, character }, extra, getDoc) => [...new Set(words)].map((w) => {
89
+ const doc = getDoc?.(w)?.description;
90
+ return {
91
+ label: w,
92
+ kind,
93
+ textEdit: {
94
+ range: {
95
+ start: { line, character: character - mt.length },
96
+ end: { line, character },
97
+ },
98
+ newText: w + (extra ?? ''),
79
99
  },
80
- newText: w + (extra ?? ''),
81
- },
82
- }));
100
+ ...doc && {
101
+ documentation: {
102
+ kind: 'markdown',
103
+ value: doc,
104
+ },
105
+ },
106
+ };
107
+ });
83
108
  /**
84
109
  * Get the caret position at the position from a word.
85
110
  * @param root root token
111
+ * @param text source code
86
112
  * @param pos position
87
113
  * @param pos.line line number
88
114
  * @param pos.character character number
89
115
  */
90
- const caretPositionFromWord = (root, { line, character }) => {
116
+ const caretPositionFromWord = (root, text, { line, character }) => {
91
117
  const index = root.indexFromPos(line, character);
92
- return root.caretPositionFromIndex(index + Number(/\w/u.test(root.toString().charAt(index))));
118
+ return root.caretPositionFromIndex(index + Number(/\w/u.test(text.charAt(index))));
93
119
  };
94
120
  /**
95
121
  * Get the attribute of a `<ref>` tag.
@@ -141,7 +167,6 @@ const getName = (token) => {
141
167
  return parentNode.name;
142
168
  }
143
169
  };
144
- /* NOT FOR BROWSER ONLY */
145
170
  /**
146
171
  * Get the quick fix data.
147
172
  * @param root root token
@@ -154,6 +179,55 @@ const getQuickFix = (root, fix, preferred = false) => ({
154
179
  title: `${preferred ? 'Fix' : 'Suggestion'}: ${fix.desc}`,
155
180
  fix: preferred,
156
181
  });
182
+ /* NOT FOR BROWSER ONLY */
183
+ /**
184
+ * Correct the position of an error.
185
+ * @param height
186
+ * @param width
187
+ * @param line 0-based line number
188
+ * @param column 0-based column number
189
+ */
190
+ const adjustPos = (height, width, line, column) => {
191
+ if (line === 0) {
192
+ line = 1;
193
+ column = 0;
194
+ }
195
+ else if (line === height + 1) {
196
+ line = height;
197
+ column = width;
198
+ }
199
+ return [line, column];
200
+ };
201
+ /**
202
+ * Get the position of a Stylelint error.
203
+ * @param rect bounding client rect of the token
204
+ * @param bottom bottom of the style block
205
+ * @param line line number
206
+ * @param column column number
207
+ */
208
+ const getStylelintPos = (rect, bottom, line, column) => {
209
+ const { top, left, height, width } = rect, start = bottom - height - 1;
210
+ line -= start;
211
+ [line, column] = adjustPos(height, width, line, column);
212
+ return (0, lint_1.getEndPos)(top, left, line, column);
213
+ };
214
+ /**
215
+ * Convert LilyPond errors to VSCode diagnostics.
216
+ * @param token `<score>` extension token
217
+ * @param errors LilyPond errors
218
+ */
219
+ const getLilyPondDiagnostics = (token, errors) => {
220
+ const { top, left } = token.lastChild.getBoundingClientRect();
221
+ return errors.map(({ line, col, message }) => {
222
+ const pos = (0, lint_1.getEndPos)(top, left, line, col);
223
+ return {
224
+ range: { start: pos, end: pos },
225
+ severity: 1,
226
+ source: 'LilyPond',
227
+ message,
228
+ };
229
+ });
230
+ };
157
231
  /**
158
232
  * Get the end position of a section.
159
233
  * @param section section
@@ -176,22 +250,47 @@ class LanguageService {
176
250
  #done;
177
251
  #done2;
178
252
  #config;
179
- #config2;
253
+ #include;
180
254
  #completionConfig;
255
+ include = true;
181
256
  /** @private */
182
257
  data;
258
+ /* NOT FOR BROWSER ONLY */
259
+ lilypond;
260
+ lilypondData;
261
+ /* NOT FOR BROWSER ONLY END */
183
262
  /** @param uri 任务标识 */
184
263
  constructor(uri) {
185
264
  exports.tasks.set(uri, this);
186
265
  /* NOT FOR BROWSER ONLY */
187
- Object.defineProperty(this, 'data', {
188
- value: require(path.join('..', '..', 'data', 'signatures')),
189
- enumerable: false,
266
+ Object.defineProperties(this, {
267
+ data: {
268
+ value: require(path_1.default.join('..', '..', 'data', 'signatures')),
269
+ enumerable: false,
270
+ },
271
+ lilypondData: {
272
+ value: require(path_1.default.join('..', '..', 'data', 'ext', 'score')),
273
+ enumerable: false,
274
+ },
190
275
  });
191
276
  }
192
277
  /** @implements */
193
278
  destroy() {
194
279
  Object.setPrototypeOf(this, null);
280
+ /* NOT FOR BROWSER ONLY */
281
+ const dir = path_1.default.join(__dirname, 'lilypond');
282
+ if (fs_1.default.existsSync(dir)) {
283
+ for (const file of fs_1.default.readdirSync(dir)) {
284
+ try {
285
+ void fs_1.default.promises.unlink(path_1.default.join(dir, file));
286
+ }
287
+ catch { }
288
+ }
289
+ }
290
+ }
291
+ /** 检查解析设置有无更新 */
292
+ #checkConfig() {
293
+ return this.#config === index_1.default.config && this.#include === this.include;
195
294
  }
196
295
  /**
197
296
  * 提交解析任务
@@ -203,7 +302,7 @@ class LanguageService {
203
302
  */
204
303
  async #queue(text) {
205
304
  text = (0, string_1.tidy)(text);
206
- if (this.#text === text && this.#config === index_1.default.config && !this.#running) {
305
+ if (!this.#running && this.#checkConfig() && this.#text === text) {
207
306
  return this.#done;
208
307
  }
209
308
  this.#text = text;
@@ -219,8 +318,9 @@ class LanguageService {
219
318
  async #parse() {
220
319
  const config = index_1.default.getConfig();
221
320
  this.#config = index_1.default.config;
222
- const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, true, config);
223
- if (this.#text === text && this.#config === index_1.default.config) {
321
+ this.#include = this.include;
322
+ const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, config);
323
+ if (this.#checkConfig() && this.#text === text) {
224
324
  this.#done = root;
225
325
  this.#running = undefined;
226
326
  return root;
@@ -240,7 +340,7 @@ class LanguageService {
240
340
  */
241
341
  async #queueSignature(text) {
242
342
  text = (0, string_1.tidy)(text);
243
- if (this.#text2 === text && this.#config2 === index_1.default.config && !this.#running2) {
343
+ if (!this.#running2 && this.#checkConfig() && this.#text2 === text) {
244
344
  return this.#done2;
245
345
  }
246
346
  this.#text2 = text;
@@ -255,9 +355,10 @@ class LanguageService {
255
355
  */
256
356
  async #parseSignature() {
257
357
  const config = index_1.default.getConfig();
258
- this.#config2 = index_1.default.config;
259
- const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, true, config);
260
- if (this.#text2 === text && this.#config2 === index_1.default.config) {
358
+ this.#config = index_1.default.config;
359
+ this.#include = this.include;
360
+ const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, config);
361
+ if (this.#checkConfig() && this.#text2 === text) {
261
362
  this.#done2 = root;
262
363
  this.#running2 = undefined;
263
364
  return root;
@@ -370,7 +471,7 @@ class LanguageService {
370
471
  return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
371
472
  }
372
473
  else if (mt?.[4]) { // behavior switch
373
- return getCompletion(switches, 'Constant', mt[4], position);
474
+ return getCompletion(switches, 'Constant', mt[4], position, '', name => this.data && this.#getBehaviorSwitch(name.slice(2, -2)));
374
475
  }
375
476
  else if (mt?.[5] !== undefined) { // protocol
376
477
  return getCompletion(protocols, 'Reference', mt[5], position);
@@ -389,7 +490,7 @@ class LanguageService {
389
490
  ? getCompletion(// link
390
491
  root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
391
492
  : [
392
- ...getCompletion(functions, 'Function', match, position),
493
+ ...getCompletion(functions, 'Function', match, position, '', name => this.data && this.#getParserFunction(name.replace(/^#/u, '').toLowerCase())),
393
494
  ...match.startsWith('#')
394
495
  ? []
395
496
  : getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
@@ -423,7 +524,8 @@ class LanguageService {
423
524
  ];
424
525
  }
425
526
  else if (mt?.[7] !== undefined || type === 'attr-key') { // attribute key
426
- const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9] ?? cur.toString();
527
+ const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9]
528
+ ?? cur.toString().slice(0, character - root.posFromIndex(cur.getAbsoluteIndex()).left);
427
529
  if (!tags.has(tag)) {
428
530
  return undefined;
429
531
  }
@@ -448,7 +550,7 @@ class LanguageService {
448
550
  if (t === 'magic-word' && n !== 'invoke') {
449
551
  return undefined;
450
552
  }
451
- const key = cur.toString().trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
553
+ const index = root.indexFromPos(line, character), key = this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
452
554
  return key
453
555
  ? getCompletion(root.querySelectorAll('parameter').filter(token => {
454
556
  if (token === parentNode
@@ -464,6 +566,43 @@ class LanguageService {
464
566
  return m === module && f === func;
465
567
  }).map(({ name }) => name), 'Variable', key, position, type === 'parameter-value' ? '=' : '')
466
568
  : undefined;
569
+ /* NOT FOR BROWSER ONLY */
570
+ }
571
+ else if (document_1.cssLSP && type === 'attr-value' && parentNode.name === 'style' && cur.length === 1) {
572
+ const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
573
+ return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
574
+ ...item,
575
+ textEdit: {
576
+ range: item.textEdit.range,
577
+ newText: item.textEdit.newText.replace(/\s/gu, ''),
578
+ },
579
+ }));
580
+ }
581
+ else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(cur.name)) {
582
+ const textDoc = new document_1.EmbeddedJSONDocument(root, cur);
583
+ return (await document_1.jsonLSP.doComplete(textDoc, position, textDoc.jsonDoc))?.items;
584
+ }
585
+ else if (type === 'ext-inner' && cur.name === 'score') {
586
+ const lang = parentNode.getAttr('lang');
587
+ if (lang !== undefined && lang !== 'lilypond') {
588
+ return undefined;
589
+ }
590
+ const j = root.indexFromPos(line, character), i = cur.getAbsoluteIndex(), before = this.#text.slice(i, j), comment = before.lastIndexOf('%');
591
+ if (comment !== -1
592
+ && (before.charAt(comment + 1) === '{' || !before.slice(comment).includes('\n'))) {
593
+ return undefined;
594
+ }
595
+ const word = /\\?\b(?:\w|\b(?:->?|\.)|\bly:)+$/u.exec(curLine.slice(0, character))?.[0];
596
+ if (word) {
597
+ const { lilypondData } = this;
598
+ return word.startsWith('\\')
599
+ ? getCompletion(lilypondData.filter(w => w.startsWith('\\')), 'Function', word, position)
600
+ : [
601
+ ...getCompletion(lilypondData.filter(w => /^[a-z]/u.test(w)), 'Variable', word, position),
602
+ ...getCompletion(lilypondData.filter(w => /^[A-Z]/u.test(w)), 'Class', word, position),
603
+ ];
604
+ }
605
+ /* NOT FOR BROWSER ONLY END */
467
606
  }
468
607
  return undefined;
469
608
  }
@@ -475,23 +614,124 @@ class LanguageService {
475
614
  * @param warning whether to include warnings / 是否包含警告
476
615
  */
477
616
  async provideDiagnostics(text, warning = true) {
478
- const root = await this.#queue(text), errors = root.lint();
479
- return (warning ? errors : errors.filter(({ severity }) => severity === 'error'))
480
- .map(({ startLine, startCol, endLine, endCol, severity, rule, message, fix, suggestions }) => ({
617
+ const root = await this.#queue(text), errors = root.lint(), diagnostics = (warning ? errors : errors.filter(({ severity }) => severity === 'error')).map(({ startLine, startCol, endLine, endCol, severity, rule, message, fix, suggestions,
618
+ /* NOT FOR BROWSER ONLY */
619
+ code, }) => ({
481
620
  range: {
482
621
  start: { line: startLine, character: startCol },
483
622
  end: { line: endLine, character: endCol },
484
623
  },
485
624
  severity: severity === 'error' ? 1 : 2,
486
- source: 'WikiLint',
487
- code: rule,
625
+ source:
626
+ /* eslint-disable @stylistic/operator-linebreak */
627
+ rule === 'invalid-css' ?
628
+ 'css' :
629
+ 'WikiLint',
630
+ code: code ??
631
+ /* eslint-enable @stylistic/operator-linebreak */
632
+ rule,
488
633
  message,
489
- /* NOT FOR BROWSER ONLY */
490
634
  data: [
491
635
  ...fix ? [getQuickFix(root, fix, true)] : [],
492
636
  ...suggestions ? suggestions.map(suggestion => getQuickFix(root, suggestion)) : [],
493
637
  ],
494
- }));
638
+ })),
639
+ /* eslint-disable @stylistic/operator-linebreak */
640
+ cssDiagnostics = await document_1.stylelint ?
641
+ await (async () => {
642
+ const tokens = this.findStyleTokens();
643
+ if (tokens.length === 0) {
644
+ return [];
645
+ }
646
+ const cssErrors = await (0, stylelint_1.styleLint)((await document_1.stylelint), tokens.map(({ type, tag, lastChild }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n${(0, common_1.sanitizeInlineStyle)(lastChild.toString())}\n}`).join('\n'), cssRules);
647
+ if (cssErrors.length === 0) {
648
+ return [];
649
+ }
650
+ const rects = tokens.map(({ lastChild }) => lastChild.getBoundingClientRect());
651
+ let acc = 0;
652
+ const bottoms = rects.map(({ height }) => {
653
+ acc += height + 2;
654
+ return acc;
655
+ });
656
+ return cssErrors.map(({ rule, text: msg, severity, line, column, endLine = line, endColumn = column, }) => {
657
+ const i = bottoms.findIndex(bottom => bottom >= line);
658
+ return {
659
+ range: {
660
+ start: getStylelintPos(rects[i], bottoms[i], line, column - 1),
661
+ end: getStylelintPos(rects[i], bottoms[i], endLine, endColumn - 1),
662
+ },
663
+ severity: severity === 'error' ? 1 : 2,
664
+ source: 'Stylelint',
665
+ code: rule,
666
+ message: msg.slice(0, msg.lastIndexOf('(') - 1),
667
+ };
668
+ });
669
+ })() :
670
+ [], jsonDiagnostics = document_1.jsonLSP ?
671
+ await Promise.all(root.querySelectorAll(jsonSelector).map(async (token) => {
672
+ const textDoc = new document_1.EmbeddedJSONDocument(root, token), severityLevel = token.name === 'templatedata' ? 'error' : 'ignore', e = (await document_1.jsonLSP.doValidation(textDoc, textDoc.jsonDoc, {
673
+ comments: severityLevel,
674
+ trailingCommas: severityLevel,
675
+ })).map((error) => ({
676
+ ...error,
677
+ source: 'json',
678
+ }));
679
+ return warning ? e : e.filter(({ severity }) => severity === 1);
680
+ })) :
681
+ [];
682
+ /* eslint-enable @stylistic/operator-linebreak */
683
+ /* NOT FOR BROWSER ONLY */
684
+ let lilypondDiagnostics = [];
685
+ if (this.lilypond) {
686
+ const tokens = root.querySelectorAll('ext#score').filter(token => {
687
+ const lang = token.getAttr('lang');
688
+ return (lang === undefined || lang === 'lilypond') && token.innerText;
689
+ });
690
+ if (tokens.length > 0) {
691
+ const dir = path_1.default.join(__dirname, 'lilypond');
692
+ if (!fs_1.default.existsSync(dir)) {
693
+ fs_1.default.mkdirSync(dir);
694
+ }
695
+ lilypondDiagnostics = await Promise.all(tokens.map(async (token) => {
696
+ const { innerText } = token, score = `showLastLength = R1${token.getAttr('raw') === undefined ? ` \\score {\n${innerText}\n}` : `\n${innerText}`}`;
697
+ if (scores.has(score)) {
698
+ return getLilyPondDiagnostics(token, scores.get(score));
699
+ }
700
+ const hash = (0, crypto_1.createHash)('sha256');
701
+ hash.update(score);
702
+ const file = path_1.default.join(dir, `${hash.digest('hex')}.ly`);
703
+ fs_1.default.writeFileSync(file, score);
704
+ try {
705
+ await util_1.default.promisify(child_process_1.execFile)(this.lilypond, ['-s', '-o', dir, file]);
706
+ scores.set(score, []);
707
+ }
708
+ catch (e) {
709
+ const { stderr } = e;
710
+ if (stderr) {
711
+ const re = new RegExp(String.raw `^${file}:(\d+):(\d+): error: (.+)$`, 'gmu'), lilypondErrors = [...stderr.matchAll(re)].map(([, line, col, msg]) => {
712
+ const { offsetHeight, offsetWidth } = token.lastChild, pos = adjustPos(offsetHeight, offsetWidth, Number(line) - 1, Number(col) - 1);
713
+ return {
714
+ line: pos[0],
715
+ col: pos[1],
716
+ message: msg,
717
+ };
718
+ });
719
+ scores.set(score, lilypondErrors);
720
+ return getLilyPondDiagnostics(token, lilypondErrors);
721
+ }
722
+ }
723
+ return [];
724
+ }));
725
+ }
726
+ }
727
+ /* NOT FOR BROWSER ONLY END */
728
+ return [
729
+ diagnostics,
730
+ cssDiagnostics,
731
+ jsonDiagnostics,
732
+ /* NOT FOR BROWSER ONLY */
733
+ lilypondDiagnostics,
734
+ ].flat(2);
495
735
  }
496
736
  /**
497
737
  * Provide folding ranges
@@ -507,7 +747,7 @@ class LanguageService {
507
747
  for (const token of tokens) {
508
748
  const { offsetHeight } = token;
509
749
  if (token.type === 'heading-title' || offsetHeight > 2) {
510
- const { top } = token.getBoundingClientRect();
750
+ const { top } = root.posFromIndex(token.getAbsoluteIndex());
511
751
  if (token.type === 'heading-title') {
512
752
  const { level } = token.parentNode;
513
753
  for (let i = level - 1; i < 6; i++) {
@@ -541,6 +781,13 @@ class LanguageService {
541
781
  });
542
782
  }
543
783
  }
784
+ /* NOT FOR BROWSER ONLY */
785
+ if (document_1.jsonLSP) {
786
+ for (const token of root.querySelectorAll(jsonSelector)) {
787
+ ranges.push(...document_1.jsonLSP.getFoldingRanges(new document_1.EmbeddedJSONDocument(root, token)));
788
+ }
789
+ }
790
+ /* NOT FOR BROWSER ONLY END */
544
791
  return ranges;
545
792
  }
546
793
  /**
@@ -637,7 +884,7 @@ class LanguageService {
637
884
  * @param position 位置
638
885
  */
639
886
  async provideReferences(text, position) {
640
- const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, position), element = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, node = offset === 0 && (element.type === 'ext-attr-dirty' || element.type === 'html-attr-dirty')
887
+ const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, this.#text, position), element = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, node = offset === 0 && (element.type === 'ext-attr-dirty' || element.type === 'html-attr-dirty')
641
888
  ? element.parentNode.parentNode
642
889
  : element, { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
643
890
  if (!refName && !refGroup && !referenceTypes.has(type)) {
@@ -717,6 +964,13 @@ class LanguageService {
717
964
  },
718
965
  };
719
966
  }
967
+ /**
968
+ * 检索状态开关
969
+ * @param name 魔术字名
970
+ */
971
+ #getBehaviorSwitch(name) {
972
+ return this.data.behaviorSwitches.find(({ aliases }) => aliases.includes(name));
973
+ }
720
974
  /**
721
975
  * 检索解析器函数
722
976
  * @param name 函数名
@@ -737,17 +991,17 @@ class LanguageService {
737
991
  if (!this.data) {
738
992
  return undefined;
739
993
  }
740
- const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, position), token = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, { type, parentNode, length, name } = token;
994
+ 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;
741
995
  let info, f, range;
742
996
  if (token.is('double-underscore') && offset > 0) {
743
- info = this.data.behaviorSwitches.find(({ aliases }) => aliases.includes(token.innerText.toLowerCase()));
997
+ info = this.#getBehaviorSwitch(token.innerText.toLowerCase());
744
998
  }
745
999
  else if (type === 'magic-word-name') {
746
1000
  info = this.#getParserFunction(parentNode.name);
747
1001
  f = token.toString(true).trim();
748
1002
  }
749
1003
  else if (token.is('magic-word') && !token.modifier && length === 1
750
- && (offset > 0 || token.getBoundingClientRect().left === position.character)) {
1004
+ && (offset > 0 || root.posFromIndex(token.getAbsoluteIndex()).left === position.character)) {
751
1005
  info = this.#getParserFunction(name);
752
1006
  f = token.firstChild.toString(true).trim();
753
1007
  }
@@ -762,6 +1016,16 @@ class LanguageService {
762
1016
  end: positionAt(root, aIndex + token.modifier.trimEnd().length + 1),
763
1017
  };
764
1018
  }
1019
+ /* NOT FOR BROWSER ONLY */
1020
+ }
1021
+ else if (document_1.cssLSP && type === 'attr-value' && length === 1 && parentNode.name === 'style') {
1022
+ const textDoc = new document_1.EmbeddedCSSDocument(root, token);
1023
+ return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
1024
+ }
1025
+ else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(name)) {
1026
+ const textDoc = new document_1.EmbeddedJSONDocument(root, token);
1027
+ return await document_1.jsonLSP.doHover(textDoc, position, textDoc.jsonDoc) ?? undefined;
1028
+ /* NOT FOR BROWSER ONLY END */
765
1029
  }
766
1030
  return info && {
767
1031
  contents: {
@@ -842,6 +1106,11 @@ class LanguageService {
842
1106
  }
843
1107
  return hints;
844
1108
  }
1109
+ /** @private */
1110
+ findStyleTokens() {
1111
+ return this.#done.querySelectorAll(cssSelector)
1112
+ .filter(({ lastChild: { length, firstChild } }) => length === 1 && firstChild.type === 'text');
1113
+ }
845
1114
  /* NOT FOR BROWSER ONLY */
846
1115
  /**
847
1116
  * Provide quick fixes
@@ -851,7 +1120,7 @@ class LanguageService {
851
1120
  */
852
1121
  // eslint-disable-next-line @typescript-eslint/class-methods-use-this
853
1122
  provideCodeAction(diagnostics) {
854
- return diagnostics.flatMap(diagnostic => diagnostic.data.map((data) => ({
1123
+ return diagnostics.filter((diagnostic) => diagnostic.data).flatMap(diagnostic => diagnostic.data.map((data) => ({
855
1124
  title: data.title,
856
1125
  kind: 'quickfix',
857
1126
  diagnostics: [diagnostic],
@@ -869,9 +1138,6 @@ class LanguageService {
869
1138
  */
870
1139
  async provideDocumentSymbols(text) {
871
1140
  const root = await this.#queue(text), lines = root.getLines(), { length } = lines, symbols = [], names = new Set(), sections = new Array(6), tokens = root.querySelectorAll('heading-title');
872
- for (const token of [...tokens].reverse()) { // 提高 getBoundingClientRect 的性能
873
- token.getRelativeIndex();
874
- }
875
1141
  for (const token of tokens) {
876
1142
  const { top, height, left, width } = token.getBoundingClientRect(), { level } = token.parentNode;
877
1143
  for (let i = level - 1; i < 6; i++) {
package/dist/lib/node.js CHANGED
@@ -180,7 +180,10 @@ class AstNode {
180
180
  */
181
181
  getBoundingClientRect() {
182
182
  // eslint-disable-next-line no-unused-labels
183
- LSP: return { ...this.#getDimension(), ...this.getRootNode().posFromIndex(this.getAbsoluteIndex()) };
183
+ LSP: return {
184
+ ...this.#getDimension(),
185
+ ...this.getRootNode().posFromIndex(this.getAbsoluteIndex()),
186
+ };
184
187
  }
185
188
  /** @private */
186
189
  seal(key, permanent) {
package/dist/lib/text.js CHANGED
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.AstText = void 0;
4
7
  const string_1 = require("../util/string");
5
8
  const lint_1 = require("../util/lint");
6
- const index_1 = require("../index");
9
+ const index_1 = __importDefault(require("../index"));
7
10
  const node_1 = require("./node");
8
11
  const sp = String.raw `[${string_1.zs}\t]*`, source = String.raw `<\s*(?:/\s*)?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*?\])|((?:^|\])[^[]*?)\]+|(?:rfc|pmid)(?=[-::]?${sp}\d)|isbn(?=[-::]?${sp}(?:\d(?:${sp}|-)){6})`;
9
12
  const errorSyntax = new RegExp(String.raw `${source}|https?[:/]/+`, 'giu');
package/dist/lib/title.js CHANGED
@@ -145,11 +145,11 @@ class Title {
145
145
  const { title, fragment } = this;
146
146
  if (title) {
147
147
  return this.#path.replace('$1', encodeURIComponent(title)
148
- + (fragment === undefined
149
- ? ''
150
- : `#${encodeURIComponent(
148
+ + (fragment
149
+ ? `#${encodeURIComponent(
151
150
  // eslint-disable-next-line @stylistic/comma-dangle
152
- fragment)}`));
151
+ fragment)}`
152
+ : ''));
153
153
  }
154
154
  return fragment === undefined ? '' : `#${encodeURIComponent(fragment)}`;
155
155
  }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBraces = void 0;
4
- /* istanbul ignore file */
5
4
  const string_1 = require("../util/string");
6
5
  const heading_1 = require("../src/heading");
7
6
  const transclude_1 = require("../src/transclude");
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCommentAndExt = void 0;
4
- /* istanbul ignore file */
5
4
  const onlyinclude_1 = require("../src/onlyinclude");
6
5
  const noinclude_1 = require("../src/nowiki/noinclude");
7
6
  const include_1 = require("../src/tagPair/include");
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseConverter = void 0;
4
- /* istanbul ignore file */
5
4
  const converter_1 = require("../src/converter");
6
5
  /**
7
6
  * 解析语言变体转换
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseExternalLinks = void 0;
4
- /* istanbul ignore file */
5
4
  const string_1 = require("../util/string");
6
5
  const extLink_1 = require("../src/extLink");
7
6
  const magicLink_1 = require("../src/magicLink");
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseHrAndDoubleUnderscore = void 0;
4
- /* istanbul ignore file */
5
4
  const hr_1 = require("../src/nowiki/hr");
6
5
  const doubleUnderscore_1 = require("../src/nowiki/doubleUnderscore");
7
6
  const heading_1 = require("../src/heading");