wikilint 2.18.0 → 2.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/bin/config.js +4 -2
- package/config/.schema.json +1 -1
- package/data/signatures.json +141 -353
- package/dist/base.d.mts +9 -1
- package/dist/base.d.ts +9 -1
- package/dist/bin/config.js +62 -56
- package/dist/index.js +4 -4
- package/dist/lib/lsp.d.ts +20 -3
- package/dist/lib/lsp.js +211 -69
- package/dist/src/attribute.js +10 -4
- package/dist/src/index.js +3 -2
- package/dist/src/nowiki/doubleUnderscore.d.ts +1 -0
- package/dist/src/nowiki/doubleUnderscore.js +2 -0
- package/dist/util/diff.js +1 -1
- package/dist/util/lint.js +31 -1
- package/i18n/zh-hans.json +1 -0
- package/i18n/zh-hant.json +1 -0
- package/package.json +6 -2
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");
|
|
@@ -16,6 +16,7 @@ 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"));
|
|
19
20
|
const document_1 = require("./document");
|
|
20
21
|
/** @see https://www.npmjs.com/package/stylelint-config-recommended */
|
|
21
22
|
const cssRules = {
|
|
@@ -39,11 +40,30 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
|
|
|
39
40
|
'heading',
|
|
40
41
|
...renameTypes,
|
|
41
42
|
]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
|
|
43
|
+
/**
|
|
44
|
+
* Check if a token is a plain attribute.
|
|
45
|
+
* @param token
|
|
46
|
+
* @param token.type
|
|
47
|
+
* @param token.parentNode
|
|
48
|
+
* @param token.length
|
|
49
|
+
* @param token.firstChild
|
|
50
|
+
* @param style whether it is a style attribute
|
|
51
|
+
*/
|
|
52
|
+
const isAttr = ({ type, parentNode, length, firstChild }, style) => type === 'attr-value' && length === 1 && firstChild.type === 'text'
|
|
53
|
+
&& (!style
|
|
54
|
+
|| parentNode.name === 'style'
|
|
55
|
+
&& Boolean(document_1.cssLSP));
|
|
56
|
+
exports.isAttr = isAttr;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a token is an HTML attribute.
|
|
59
|
+
* @param token
|
|
60
|
+
*/
|
|
61
|
+
const isHtmlAttr = (token) => token.type === 'html-attr' || token.type === 'table-attr';
|
|
42
62
|
/**
|
|
43
63
|
* Check if all child nodes are plain text or comments.
|
|
44
|
-
* @param
|
|
64
|
+
* @param token
|
|
45
65
|
*/
|
|
46
|
-
const isPlain = (
|
|
66
|
+
const isPlain = (token) => token.childNodes.every(({ type }) => plainTypes.has(type));
|
|
47
67
|
/**
|
|
48
68
|
* Get the position of a character in the document.
|
|
49
69
|
* @param root root token
|
|
@@ -213,11 +233,12 @@ const getStylelintPos = (rect, bottom, line, column) => {
|
|
|
213
233
|
};
|
|
214
234
|
/**
|
|
215
235
|
* Convert LilyPond errors to VSCode diagnostics.
|
|
236
|
+
* @param root root token
|
|
216
237
|
* @param token `<score>` extension token
|
|
217
238
|
* @param errors LilyPond errors
|
|
218
239
|
*/
|
|
219
|
-
const getLilyPondDiagnostics = (token, errors) => {
|
|
220
|
-
const { top, left } = token.lastChild.
|
|
240
|
+
const getLilyPondDiagnostics = (root, token, errors) => {
|
|
241
|
+
const { top, left } = root.posFromIndex(token.lastChild.getAbsoluteIndex());
|
|
221
242
|
return errors.map(({ line, col, message }) => {
|
|
222
243
|
const pos = (0, lint_1.getEndPos)(top, left, line, col);
|
|
223
244
|
return {
|
|
@@ -254,16 +275,20 @@ class LanguageService {
|
|
|
254
275
|
#completionConfig;
|
|
255
276
|
include = true;
|
|
256
277
|
/** @private */
|
|
278
|
+
config;
|
|
279
|
+
/** @private */
|
|
257
280
|
data;
|
|
258
281
|
/* NOT FOR BROWSER ONLY */
|
|
259
282
|
lilypond;
|
|
283
|
+
/** @private */
|
|
260
284
|
lilypondData;
|
|
261
285
|
/* NOT FOR BROWSER ONLY END */
|
|
262
286
|
/** @param uri 任务标识 */
|
|
263
287
|
constructor(uri) {
|
|
264
288
|
exports.tasks.set(uri, this);
|
|
265
|
-
/* NOT FOR BROWSER ONLY */
|
|
266
289
|
Object.defineProperties(this, {
|
|
290
|
+
config: { enumerable: false },
|
|
291
|
+
/* NOT FOR BROWSER ONLY */
|
|
267
292
|
data: {
|
|
268
293
|
value: require(path_1.default.join('..', '..', 'data', 'signatures')),
|
|
269
294
|
enumerable: false,
|
|
@@ -281,16 +306,18 @@ class LanguageService {
|
|
|
281
306
|
const dir = path_1.default.join(__dirname, 'lilypond');
|
|
282
307
|
if (fs_1.default.existsSync(dir)) {
|
|
283
308
|
for (const file of fs_1.default.readdirSync(dir)) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
309
|
+
(async () => {
|
|
310
|
+
try {
|
|
311
|
+
await fs_1.default.promises.unlink(path_1.default.join(dir, file));
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
})();
|
|
288
315
|
}
|
|
289
316
|
}
|
|
290
317
|
}
|
|
291
318
|
/** 检查解析设置有无更新 */
|
|
292
319
|
#checkConfig() {
|
|
293
|
-
return this.#config ===
|
|
320
|
+
return this.#config === this.config && this.#include === this.include;
|
|
294
321
|
}
|
|
295
322
|
/**
|
|
296
323
|
* 提交解析任务
|
|
@@ -316,10 +343,10 @@ class LanguageService {
|
|
|
316
343
|
* - 总是返回最新的解析结果
|
|
317
344
|
*/
|
|
318
345
|
async #parse() {
|
|
319
|
-
|
|
320
|
-
this.#config =
|
|
346
|
+
this.config ??= index_1.default.getConfig();
|
|
347
|
+
this.#config = this.config;
|
|
321
348
|
this.#include = this.include;
|
|
322
|
-
const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, config);
|
|
349
|
+
const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, this.config);
|
|
323
350
|
if (this.#checkConfig() && this.#text === text) {
|
|
324
351
|
this.#done = root;
|
|
325
352
|
this.#running = undefined;
|
|
@@ -354,10 +381,10 @@ class LanguageService {
|
|
|
354
381
|
* - 总是返回最新的解析结果
|
|
355
382
|
*/
|
|
356
383
|
async #parseSignature() {
|
|
357
|
-
|
|
358
|
-
this.#config =
|
|
384
|
+
this.config ??= index_1.default.getConfig();
|
|
385
|
+
this.#config = this.config;
|
|
359
386
|
this.#include = this.include;
|
|
360
|
-
const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, config);
|
|
387
|
+
const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, this.config);
|
|
361
388
|
if (this.#checkConfig() && this.#text2 === text) {
|
|
362
389
|
this.#done2 = root;
|
|
363
390
|
this.#running2 = undefined;
|
|
@@ -378,14 +405,38 @@ class LanguageService {
|
|
|
378
405
|
*/
|
|
379
406
|
async provideDocumentColors(rgba, text, hsl = true) {
|
|
380
407
|
const root = await this.#queue(text);
|
|
408
|
+
/* NOT FOR BROWSER ONLY */
|
|
409
|
+
let colors;
|
|
410
|
+
try {
|
|
411
|
+
colors = new RegExp(String.raw `\b${Object.keys((await import('color-name')).default).join('|')}\b`, 'giu');
|
|
412
|
+
}
|
|
413
|
+
catch { }
|
|
414
|
+
/* NOT FOR BROWSER ONLY END */
|
|
381
415
|
return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse()
|
|
382
|
-
.flatMap(
|
|
383
|
-
|
|
416
|
+
.flatMap(token => {
|
|
417
|
+
const { type, childNodes,
|
|
418
|
+
/* NOT FOR BROWSER ONLY */
|
|
419
|
+
parentNode, } = token;
|
|
420
|
+
if (type !== 'attr-value' && !isPlain(token)) {
|
|
384
421
|
return [];
|
|
422
|
+
/* NOT FOR BROWSER ONLY */
|
|
423
|
+
}
|
|
424
|
+
else if ((0, exports.isAttr)(token, true)) {
|
|
425
|
+
const textDoc = new document_1.EmbeddedCSSDocument(root, token);
|
|
426
|
+
return document_1.cssLSP.findDocumentColors(textDoc, textDoc.styleSheet);
|
|
427
|
+
/* NOT FOR BROWSER ONLY END */
|
|
385
428
|
}
|
|
429
|
+
/* NOT FOR BROWSER ONLY */
|
|
430
|
+
const isStyle = colors && type === 'attr-value' && parentNode.name === 'style';
|
|
431
|
+
/* NOT FOR BROWSER ONLY END */
|
|
386
432
|
return childNodes.filter((child) => child.type === 'text').reverse()
|
|
387
433
|
.flatMap(child => {
|
|
388
|
-
const parts = (0, common_1.splitColors)(
|
|
434
|
+
const { data } = child, parts = (0, common_1.splitColors)(data, hsl).filter(([, , , isColor]) => isColor);
|
|
435
|
+
/* NOT FOR BROWSER ONLY */
|
|
436
|
+
if (isStyle) {
|
|
437
|
+
parts.push(...[...data.matchAll(colors)].map(({ index, 0: s }) => [s, index, index + s.length, true]));
|
|
438
|
+
}
|
|
439
|
+
/* NOT FOR BROWSER ONLY END */
|
|
389
440
|
if (parts.length === 0) {
|
|
390
441
|
return [];
|
|
391
442
|
}
|
|
@@ -423,8 +474,9 @@ class LanguageService {
|
|
|
423
474
|
}
|
|
424
475
|
/** 准备自动补全设置 */
|
|
425
476
|
#prepareCompletionConfig() {
|
|
426
|
-
if (!this.#completionConfig) {
|
|
427
|
-
|
|
477
|
+
if (!this.#completionConfig || this.#completionConfig[1] !== this.config) {
|
|
478
|
+
this.config ??= index_1.default.getConfig();
|
|
479
|
+
const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = this.config, tags = new Set([ext, html].flat(2));
|
|
428
480
|
const re = new RegExp('(?:' // eslint-disable-line prefer-template
|
|
429
481
|
+ String.raw `<(\/?\w*)` // tag
|
|
430
482
|
+ '|'
|
|
@@ -439,23 +491,27 @@ class LanguageService {
|
|
|
439
491
|
// attribute key
|
|
440
492
|
+ String.raw `<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*)`
|
|
441
493
|
+ ')$', 'iu');
|
|
442
|
-
this.#completionConfig =
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
494
|
+
this.#completionConfig = [
|
|
495
|
+
{
|
|
496
|
+
re,
|
|
497
|
+
ext,
|
|
498
|
+
tags,
|
|
499
|
+
allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
|
|
500
|
+
functions: [
|
|
501
|
+
Object.keys(insensitive),
|
|
502
|
+
Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
|
|
503
|
+
other,
|
|
504
|
+
].flat(2),
|
|
505
|
+
switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
|
|
506
|
+
protocols: protocol.split('|'),
|
|
507
|
+
params: Object.keys(img)
|
|
508
|
+
.filter(k => k.endsWith('$1') || !k.includes('$1'))
|
|
509
|
+
.map(k => k.replace(/\$1$/u, '')),
|
|
510
|
+
},
|
|
511
|
+
this.config,
|
|
512
|
+
];
|
|
457
513
|
}
|
|
458
|
-
return this.#completionConfig;
|
|
514
|
+
return this.#completionConfig[0];
|
|
459
515
|
}
|
|
460
516
|
/**
|
|
461
517
|
* Provide auto-completion
|
|
@@ -465,13 +521,25 @@ class LanguageService {
|
|
|
465
521
|
* @param position 位置
|
|
466
522
|
*/
|
|
467
523
|
async provideCompletionItems(text, position) {
|
|
468
|
-
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) ?? '');
|
|
524
|
+
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;
|
|
469
525
|
if (mt?.[1] !== undefined) { // tag
|
|
470
526
|
const closing = mt[1].startsWith('/');
|
|
471
527
|
return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
|
|
472
528
|
}
|
|
473
529
|
else if (mt?.[4]) { // behavior switch
|
|
474
|
-
return getCompletion(switches, 'Constant', mt[4], position, '', name =>
|
|
530
|
+
return getCompletion(switches, 'Constant', mt[4], position, '', name => {
|
|
531
|
+
if (!this.data) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
name = name.slice(2, -2);
|
|
535
|
+
if (name in iAlias) {
|
|
536
|
+
name = iAlias[name];
|
|
537
|
+
}
|
|
538
|
+
else if (name in sAlias) {
|
|
539
|
+
name = sAlias[name];
|
|
540
|
+
}
|
|
541
|
+
return this.#getBehaviorSwitch(name.toLowerCase());
|
|
542
|
+
});
|
|
475
543
|
}
|
|
476
544
|
else if (mt?.[5] !== undefined) { // protocol
|
|
477
545
|
return getCompletion(protocols, 'Reference', mt[5], position);
|
|
@@ -485,12 +553,23 @@ class LanguageService {
|
|
|
485
553
|
return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
|
|
486
554
|
.map(({ name }) => name), 'Variable', match, position);
|
|
487
555
|
}
|
|
488
|
-
const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
556
|
+
const [insensitive, sensitive] = this.config.parserFunction, isOld = Array.isArray(sensitive), colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
489
557
|
return mt[2] === '[['
|
|
490
558
|
? getCompletion(// link
|
|
491
559
|
root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
|
|
492
560
|
: [
|
|
493
|
-
...getCompletion(functions, 'Function', match, position, '', name =>
|
|
561
|
+
...getCompletion(functions, 'Function', match, position, '', name => {
|
|
562
|
+
if (!this.data) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
else if (name in insensitive) {
|
|
566
|
+
name = insensitive[name];
|
|
567
|
+
}
|
|
568
|
+
else if (!isOld && name in sensitive) {
|
|
569
|
+
name = sensitive[name];
|
|
570
|
+
}
|
|
571
|
+
return this.#getParserFunction(name.toLowerCase());
|
|
572
|
+
}),
|
|
494
573
|
...match.startsWith('#')
|
|
495
574
|
? []
|
|
496
575
|
: getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
|
|
@@ -550,7 +629,7 @@ class LanguageService {
|
|
|
550
629
|
if (t === 'magic-word' && n !== 'invoke') {
|
|
551
630
|
return undefined;
|
|
552
631
|
}
|
|
553
|
-
const
|
|
632
|
+
const key = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
|
|
554
633
|
return key
|
|
555
634
|
? getCompletion(root.querySelectorAll('parameter').filter(token => {
|
|
556
635
|
if (token === parentNode
|
|
@@ -568,7 +647,7 @@ class LanguageService {
|
|
|
568
647
|
: undefined;
|
|
569
648
|
/* NOT FOR BROWSER ONLY */
|
|
570
649
|
}
|
|
571
|
-
else if (
|
|
650
|
+
else if ((0, exports.isAttr)(cur, true)) {
|
|
572
651
|
const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
|
|
573
652
|
return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
|
|
574
653
|
...item,
|
|
@@ -587,7 +666,7 @@ class LanguageService {
|
|
|
587
666
|
if (lang !== undefined && lang !== 'lilypond') {
|
|
588
667
|
return undefined;
|
|
589
668
|
}
|
|
590
|
-
const
|
|
669
|
+
const before = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)), comment = before.lastIndexOf('%');
|
|
591
670
|
if (comment !== -1
|
|
592
671
|
&& (before.charAt(comment + 1) === '{' || !before.slice(comment).includes('\n'))) {
|
|
593
672
|
return undefined;
|
|
@@ -604,6 +683,14 @@ class LanguageService {
|
|
|
604
683
|
}
|
|
605
684
|
/* NOT FOR BROWSER ONLY END */
|
|
606
685
|
}
|
|
686
|
+
else if ((0, exports.isAttr)(cur) && isHtmlAttr(parentNode)) {
|
|
687
|
+
const data = lint_1.htmlData.provideValues(parentNode.tag, parentNode.name);
|
|
688
|
+
if (data.length === 0) {
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
const val = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart();
|
|
692
|
+
return getCompletion(data.map(({ name }) => name), 'Value', val, position);
|
|
693
|
+
}
|
|
607
694
|
return undefined;
|
|
608
695
|
}
|
|
609
696
|
/**
|
|
@@ -695,7 +782,7 @@ class LanguageService {
|
|
|
695
782
|
lilypondDiagnostics = await Promise.all(tokens.map(async (token) => {
|
|
696
783
|
const { innerText } = token, score = `showLastLength = R1${token.getAttr('raw') === undefined ? ` \\score {\n${innerText}\n}` : `\n${innerText}`}`;
|
|
697
784
|
if (scores.has(score)) {
|
|
698
|
-
return getLilyPondDiagnostics(token, scores.get(score));
|
|
785
|
+
return getLilyPondDiagnostics(root, token, scores.get(score));
|
|
699
786
|
}
|
|
700
787
|
const hash = (0, crypto_1.createHash)('sha256');
|
|
701
788
|
hash.update(score);
|
|
@@ -717,7 +804,7 @@ class LanguageService {
|
|
|
717
804
|
};
|
|
718
805
|
});
|
|
719
806
|
scores.set(score, lilypondErrors);
|
|
720
|
-
return getLilyPondDiagnostics(token, lilypondErrors);
|
|
807
|
+
return getLilyPondDiagnostics(root, token, lilypondErrors);
|
|
721
808
|
}
|
|
722
809
|
}
|
|
723
810
|
return [];
|
|
@@ -797,7 +884,8 @@ class LanguageService {
|
|
|
797
884
|
* @param text source Wikitext / 源代码
|
|
798
885
|
*/
|
|
799
886
|
async provideLinks(text) {
|
|
800
|
-
|
|
887
|
+
this.config ??= index_1.default.getConfig();
|
|
888
|
+
const { articlePath, protocol } = this.config, absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
|
|
801
889
|
return (await this.#queue(text))
|
|
802
890
|
.querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`)
|
|
803
891
|
.reverse()
|
|
@@ -806,7 +894,7 @@ class LanguageService {
|
|
|
806
894
|
if (!(type !== 'attr-value'
|
|
807
895
|
|| name === 'src' && ['templatestyles', 'img'].includes(tag)
|
|
808
896
|
|| name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
|
|
809
|
-
|| !isPlain(
|
|
897
|
+
|| !isPlain(token)) {
|
|
810
898
|
return false;
|
|
811
899
|
}
|
|
812
900
|
let target = childNodes.filter((node) => node.type === 'text')
|
|
@@ -846,8 +934,7 @@ class LanguageService {
|
|
|
846
934
|
else if (type === 'invoke-module') {
|
|
847
935
|
ns = 828;
|
|
848
936
|
}
|
|
849
|
-
const title = index_1.default
|
|
850
|
-
.normalizeTitle(target, ns, false, undefined, true);
|
|
937
|
+
const title = index_1.default.normalizeTitle(target, ns, false, this.config, true);
|
|
851
938
|
/* istanbul ignore if */
|
|
852
939
|
if (!title.valid) {
|
|
853
940
|
return false;
|
|
@@ -991,40 +1078,74 @@ class LanguageService {
|
|
|
991
1078
|
if (!this.data) {
|
|
992
1079
|
return undefined;
|
|
993
1080
|
}
|
|
994
|
-
const root = await this.#queue(text)
|
|
1081
|
+
const root = await this.#queue(text);
|
|
1082
|
+
let { offsetNode, offset } = caretPositionFromWord(root, this.#text, position);
|
|
1083
|
+
if (offsetNode.type === 'text') {
|
|
1084
|
+
offset += offsetNode.getRelativeIndex();
|
|
1085
|
+
offsetNode = offsetNode.parentNode;
|
|
1086
|
+
}
|
|
1087
|
+
const { type, parentNode, length, name } = offsetNode;
|
|
995
1088
|
let info, f, range;
|
|
996
|
-
if (
|
|
997
|
-
info = this.#getBehaviorSwitch(
|
|
1089
|
+
if (offsetNode.is('double-underscore') && offset > 0) {
|
|
1090
|
+
info = this.#getBehaviorSwitch(offsetNode.name);
|
|
998
1091
|
}
|
|
999
1092
|
else if (type === 'magic-word-name') {
|
|
1000
1093
|
info = this.#getParserFunction(parentNode.name);
|
|
1001
|
-
f =
|
|
1094
|
+
f = offsetNode.toString(true).trim();
|
|
1002
1095
|
}
|
|
1003
|
-
else if (
|
|
1004
|
-
&& (offset > 0 || root.posFromIndex(
|
|
1096
|
+
else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
|
|
1097
|
+
&& (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
|
|
1005
1098
|
info = this.#getParserFunction(name);
|
|
1006
|
-
f =
|
|
1099
|
+
f = offsetNode.firstChild.toString(true).trim();
|
|
1007
1100
|
}
|
|
1008
|
-
else if ((
|
|
1009
|
-
&&
|
|
1010
|
-
f =
|
|
1101
|
+
else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
|
|
1102
|
+
&& offsetNode.modifier && offset >= 2 && offsetNode.getRelativeIndex(0) > offset) {
|
|
1103
|
+
f = offsetNode.modifier.trim().slice(0, -1);
|
|
1011
1104
|
info = this.#getParserFunction(f.toLowerCase());
|
|
1012
1105
|
if (info) {
|
|
1013
|
-
const aIndex =
|
|
1106
|
+
const aIndex = offsetNode.getAbsoluteIndex();
|
|
1014
1107
|
range = {
|
|
1015
1108
|
start: positionAt(root, aIndex + 2),
|
|
1016
|
-
end: positionAt(root, aIndex +
|
|
1109
|
+
end: positionAt(root, aIndex + offsetNode.modifier.trimEnd().length + 1),
|
|
1017
1110
|
};
|
|
1018
1111
|
}
|
|
1019
1112
|
/* NOT FOR BROWSER ONLY */
|
|
1020
1113
|
}
|
|
1021
|
-
else if (
|
|
1022
|
-
const textDoc = new document_1.EmbeddedCSSDocument(root,
|
|
1114
|
+
else if ((0, exports.isAttr)(offsetNode, true)) {
|
|
1115
|
+
const textDoc = new document_1.EmbeddedCSSDocument(root, offsetNode);
|
|
1023
1116
|
return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
|
|
1024
1117
|
}
|
|
1025
1118
|
else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(name)) {
|
|
1026
|
-
const textDoc = new document_1.EmbeddedJSONDocument(root,
|
|
1119
|
+
const textDoc = new document_1.EmbeddedJSONDocument(root, offsetNode);
|
|
1027
1120
|
return await document_1.jsonLSP.doHover(textDoc, position, textDoc.jsonDoc) ?? undefined;
|
|
1121
|
+
}
|
|
1122
|
+
else if (lint_1.htmlData.provideTags && lint_1.htmlData.provideAttributes) {
|
|
1123
|
+
if (type === 'html' && offset <= offsetNode.getRelativeIndex(0)
|
|
1124
|
+
|| type === 'html-attr-dirty' && offset === 0 && parentNode.firstChild === offsetNode) {
|
|
1125
|
+
const token = type === 'html' ? offsetNode : parentNode.parentNode, data = lint_1.htmlData.provideTags().find(({ name: n }) => n === token.name);
|
|
1126
|
+
if (data?.description) {
|
|
1127
|
+
const start = positionAt(root, token.getAbsoluteIndex());
|
|
1128
|
+
return {
|
|
1129
|
+
contents: data.description,
|
|
1130
|
+
range: {
|
|
1131
|
+
start,
|
|
1132
|
+
end: {
|
|
1133
|
+
line: start.line,
|
|
1134
|
+
character: start.character + token.getRelativeIndex(0),
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else if (type === 'attr-key' && isHtmlAttr(parentNode)) {
|
|
1141
|
+
const data = lint_1.htmlData.provideAttributes(parentNode.tag).find(({ name: n }) => n === parentNode.name);
|
|
1142
|
+
if (data?.description) {
|
|
1143
|
+
return {
|
|
1144
|
+
contents: data.description,
|
|
1145
|
+
range: createNodeRange(offsetNode),
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1028
1149
|
/* NOT FOR BROWSER ONLY END */
|
|
1029
1150
|
}
|
|
1030
1151
|
return info && {
|
|
@@ -1035,7 +1156,7 @@ class LanguageService {
|
|
|
1035
1156
|
: '')
|
|
1036
1157
|
+ info.description,
|
|
1037
1158
|
},
|
|
1038
|
-
range: range ?? createNodeRange(
|
|
1159
|
+
range: range ?? createNodeRange(offsetNode),
|
|
1039
1160
|
};
|
|
1040
1161
|
}
|
|
1041
1162
|
/**
|
|
@@ -1108,8 +1229,7 @@ class LanguageService {
|
|
|
1108
1229
|
}
|
|
1109
1230
|
/** @private */
|
|
1110
1231
|
findStyleTokens() {
|
|
1111
|
-
return this.#done.querySelectorAll(cssSelector)
|
|
1112
|
-
.filter(({ lastChild: { length, firstChild } }) => length === 1 && firstChild.type === 'text');
|
|
1232
|
+
return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => (0, exports.isAttr)(lastChild));
|
|
1113
1233
|
}
|
|
1114
1234
|
/* NOT FOR BROWSER ONLY */
|
|
1115
1235
|
/**
|
|
@@ -1171,5 +1291,27 @@ class LanguageService {
|
|
|
1171
1291
|
}
|
|
1172
1292
|
return symbols;
|
|
1173
1293
|
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Set the target Wikipedia
|
|
1296
|
+
*
|
|
1297
|
+
* 设置目标维基百科
|
|
1298
|
+
* @param wiki Wikipedia URL / 维基百科网址
|
|
1299
|
+
* @throws `RangeError` 不是有效的维基百科网址
|
|
1300
|
+
*/
|
|
1301
|
+
async setTargetWikipedia(wiki) {
|
|
1302
|
+
const mt = /^https?:\/\/([^./]+)\.wikipedia\.org/iu.exec(wiki);
|
|
1303
|
+
if (!mt) {
|
|
1304
|
+
throw new RangeError('Invalid Wikipedia URL!');
|
|
1305
|
+
}
|
|
1306
|
+
const site = `${mt[1].toLowerCase()}wiki`;
|
|
1307
|
+
try {
|
|
1308
|
+
const config = require(path_1.default.join('..', '..', 'config', site));
|
|
1309
|
+
this.config = index_1.default.getConfig(config);
|
|
1310
|
+
}
|
|
1311
|
+
catch {
|
|
1312
|
+
this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`));
|
|
1313
|
+
}
|
|
1314
|
+
Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
|
|
1315
|
+
}
|
|
1174
1316
|
}
|
|
1175
1317
|
exports.LanguageService = LanguageService;
|
package/dist/src/attribute.js
CHANGED
|
@@ -12,7 +12,7 @@ const rect_1 = require("../lib/rect");
|
|
|
12
12
|
const index_1 = __importDefault(require("../index"));
|
|
13
13
|
const index_2 = require("./index");
|
|
14
14
|
const atom_1 = require("./atom");
|
|
15
|
-
const insecureStyle = /expression|(?:accelerator|-o-link(?:-source)?|-o-replace)\s*:|(?:url|image(?:-set)?)\s*\(|attr\s*\([^)]+[\s,]url/u;
|
|
15
|
+
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']);
|
|
16
16
|
/**
|
|
17
17
|
* attribute of extension and HTML tags
|
|
18
18
|
*
|
|
@@ -118,9 +118,6 @@ class AttributeToken extends index_2.Token {
|
|
|
118
118
|
e.suggestions = [{ desc: 'remove', range: [start, start + length], text: '' }];
|
|
119
119
|
errors.push(e);
|
|
120
120
|
}
|
|
121
|
-
else if (sharable_1.obsoleteAttrs[tag]?.has(name)) {
|
|
122
|
-
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
|
|
123
|
-
}
|
|
124
121
|
else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
|
|
125
122
|
errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure-style', 'insecure style'));
|
|
126
123
|
}
|
|
@@ -132,6 +129,15 @@ class AttributeToken extends index_2.Token {
|
|
|
132
129
|
];
|
|
133
130
|
errors.push(e);
|
|
134
131
|
}
|
|
132
|
+
else if (type !== 'ext-attr' && !lastChild.childNodes.some(({ type: t }) => complexTypes.has(t))) {
|
|
133
|
+
const data = lint_1.htmlData.provideValues(tag, name), v = String(value).toLowerCase();
|
|
134
|
+
if (data.length > 0 && data.every(({ name: n }) => n !== v)) {
|
|
135
|
+
errors.push((0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'illegal attribute value', 'warning'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (sharable_1.obsoleteAttrs[tag]?.has(name)) {
|
|
139
|
+
errors.push((0, lint_1.generateForChild)(firstChild, rect, 'obsolete-attr', 'obsolete attribute', 'warning'));
|
|
140
|
+
}
|
|
135
141
|
return errors;
|
|
136
142
|
}
|
|
137
143
|
/**
|
package/dist/src/index.js
CHANGED
|
@@ -54,6 +54,7 @@ const element_1 = require("../lib/element");
|
|
|
54
54
|
const text_1 = require("../lib/text");
|
|
55
55
|
/* NOT FOR BROWSER ONLY */
|
|
56
56
|
const document_1 = require("../lib/document");
|
|
57
|
+
const lsp_1 = require("../lib/lsp");
|
|
57
58
|
/**
|
|
58
59
|
* base class for all tokens
|
|
59
60
|
*
|
|
@@ -422,10 +423,10 @@ class Token extends element_1.AstElement {
|
|
|
422
423
|
});
|
|
423
424
|
/* NOT FOR BROWSER ONLY */
|
|
424
425
|
}
|
|
425
|
-
else if (
|
|
426
|
-
&& this.parentNode.name === 'style') {
|
|
426
|
+
else if ((0, lsp_1.isAttr)(this, true)) {
|
|
427
427
|
const root = this.getRootNode(), textDoc = new document_1.EmbeddedCSSDocument(root, this);
|
|
428
428
|
errors.push(...document_1.cssLSP.doValidation(textDoc, textDoc.styleSheet)
|
|
429
|
+
.filter(({ code }) => code !== 'css-ruleorselectorexpected')
|
|
429
430
|
.map(({ range: { start: { line, character }, end }, message, severity, code }) => ({
|
|
430
431
|
code: code,
|
|
431
432
|
rule: 'invalid-css',
|
|
@@ -66,6 +66,8 @@ let DoubleUnderscoreToken = (() => {
|
|
|
66
66
|
*/
|
|
67
67
|
constructor(word, sensitive, config, accum) {
|
|
68
68
|
super(word, config, accum);
|
|
69
|
+
const lc = word.toLowerCase(), { doubleUnderscore: [, , iAlias, sAlias] } = config;
|
|
70
|
+
this.setAttribute('name', (sensitive ? sAlias?.[word]?.toLowerCase() : iAlias?.[lc]) ?? lc);
|
|
69
71
|
}
|
|
70
72
|
/** @private */
|
|
71
73
|
getAttribute(key) {
|
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.
|
|
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"));
|
|
@@ -61,3 +61,33 @@ const cache = (store, compute, update) => {
|
|
|
61
61
|
return result;
|
|
62
62
|
};
|
|
63
63
|
exports.cache = cache;
|
|
64
|
+
let htmlData;
|
|
65
|
+
try {
|
|
66
|
+
exports.htmlData = htmlData = require('vscode-html-languageservice')
|
|
67
|
+
.getDefaultHTMLDataProvider();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/**
|
|
71
|
+
* 获取HTML属性值可选列表
|
|
72
|
+
* @param tag 标签名
|
|
73
|
+
* @param attribute 属性名
|
|
74
|
+
*/
|
|
75
|
+
const provideValues = (tag, attribute) => {
|
|
76
|
+
if (tag === 'ol' && attribute === 'type') {
|
|
77
|
+
return ['1', 'a', 'A', 'i', 'I'];
|
|
78
|
+
}
|
|
79
|
+
else if (tag === 'th' && attribute === 'scope') {
|
|
80
|
+
return ['row', 'col', 'rowgroup', 'colgroup'];
|
|
81
|
+
}
|
|
82
|
+
else if (attribute === 'dir') {
|
|
83
|
+
return ['ltr', 'rtl', 'auto'];
|
|
84
|
+
}
|
|
85
|
+
return attribute === 'aria-hidden' ? ['true', 'false'] : [];
|
|
86
|
+
};
|
|
87
|
+
exports.htmlData = htmlData = {
|
|
88
|
+
/** @implements */
|
|
89
|
+
provideValues(tag, attribute) {
|
|
90
|
+
return provideValues(tag, attribute).map(value => ({ name: value }));
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
package/i18n/zh-hans.json
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"HTML comment": "HTML注释",
|
|
22
22
|
"HTML tag in table attributes": "表格属性中的HTML标签",
|
|
23
23
|
"illegal attribute name": "非法的属性名",
|
|
24
|
+
"illegal attribute value": "非法的属性值",
|
|
24
25
|
"illegal module name": "非法的模块名称",
|
|
25
26
|
"inconsistent table layout": "不一致的表格布局",
|
|
26
27
|
"insecure style": "不安全的样式",
|
package/i18n/zh-hant.json
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"HTML comment": "HTML註釋",
|
|
22
22
|
"HTML tag in table attributes": "表格屬性中的HTML標籤",
|
|
23
23
|
"illegal attribute name": "非法的屬性名",
|
|
24
|
+
"illegal attribute value": "非法的屬性值",
|
|
24
25
|
"illegal module name": "非法的模組名稱",
|
|
25
26
|
"inconsistent table layout": "不一致的表格佈局",
|
|
26
27
|
"insecure style": "不安全的樣式",
|