ts-gem-plugin 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +888 -185
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -160,11 +160,31 @@ var Configuration = class {
|
|
|
160
160
|
|
|
161
161
|
// src/constants.ts
|
|
162
162
|
var NAME = "gem-plugin";
|
|
163
|
+
var Decorators = {
|
|
164
|
+
Attr: "attribute",
|
|
165
|
+
NumAttr: "numattribute",
|
|
166
|
+
BoolAttr: "boolattribute",
|
|
167
|
+
Prop: "property",
|
|
168
|
+
Emitter: "emitter",
|
|
169
|
+
GlobalEmitter: "globalemitter",
|
|
170
|
+
AdoptedStyle: "adoptedStyle",
|
|
171
|
+
CustomElement: "customElement",
|
|
172
|
+
Part: "part",
|
|
173
|
+
Slot: "slot"
|
|
174
|
+
};
|
|
175
|
+
var Utils = {
|
|
176
|
+
ClassMap: "classMap",
|
|
177
|
+
CreateDecoratorTheme: "createDecoratorTheme"
|
|
178
|
+
};
|
|
179
|
+
var Types = {
|
|
180
|
+
Emitter: "Emitter"
|
|
181
|
+
};
|
|
163
182
|
|
|
164
183
|
// src/context.ts
|
|
165
184
|
var import_standard_script_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-script-source-helper"));
|
|
166
185
|
var import_standard_template_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-template-source-helper"));
|
|
167
186
|
var import_vscode_css_languageservice = require("@mantou/vscode-css-languageservice");
|
|
187
|
+
var import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
|
|
168
188
|
var import_vscode_html_languageservice2 = require("@mantou/vscode-html-languageservice");
|
|
169
189
|
|
|
170
190
|
// ../duoyun-ui/lib/map.js
|
|
@@ -173,9 +193,10 @@ var StringWeakMap = class {
|
|
|
173
193
|
#weakMap = /* @__PURE__ */ new WeakMap();
|
|
174
194
|
#registry = new FinalizationRegistry((key) => this.#map.delete(key));
|
|
175
195
|
set(key, val) {
|
|
196
|
+
this.delete(key);
|
|
176
197
|
this.#map.set(key, new WeakRef(val));
|
|
177
198
|
this.#weakMap.set(val, key);
|
|
178
|
-
this.#registry.register(val, key);
|
|
199
|
+
this.#registry.register(val, key, val);
|
|
179
200
|
}
|
|
180
201
|
get(key) {
|
|
181
202
|
return this.#map.get(key)?.deref();
|
|
@@ -183,6 +204,14 @@ var StringWeakMap = class {
|
|
|
183
204
|
findKey(val) {
|
|
184
205
|
return this.#weakMap.get(val);
|
|
185
206
|
}
|
|
207
|
+
delete(key) {
|
|
208
|
+
const val = this.get(key);
|
|
209
|
+
if (val) {
|
|
210
|
+
this.#map.delete(key);
|
|
211
|
+
this.#weakMap.delete(val);
|
|
212
|
+
this.#registry.unregister(val);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
186
215
|
*[Symbol.iterator]() {
|
|
187
216
|
const entries2 = this.#map.entries();
|
|
188
217
|
for (const [tag, ref] of entries2) {
|
|
@@ -191,6 +220,14 @@ var StringWeakMap = class {
|
|
|
191
220
|
}
|
|
192
221
|
};
|
|
193
222
|
|
|
223
|
+
// ../duoyun-ui/lib/types.js
|
|
224
|
+
function isNullish(v) {
|
|
225
|
+
return v === null || v === void 0;
|
|
226
|
+
}
|
|
227
|
+
function isNotNullish(v) {
|
|
228
|
+
return !isNullish(v);
|
|
229
|
+
}
|
|
230
|
+
|
|
194
231
|
// ../duoyun-ui/lib/cache.js
|
|
195
232
|
var Cache = class {
|
|
196
233
|
#max;
|
|
@@ -278,31 +315,85 @@ function forEachNode(roots, fn) {
|
|
|
278
315
|
const currentNode = list.pop();
|
|
279
316
|
if (!currentNode) return;
|
|
280
317
|
fn(currentNode);
|
|
281
|
-
list.push(...currentNode.children);
|
|
318
|
+
list.push(..."getChildren" in currentNode ? currentNode.getChildren() : currentNode.children);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function getTemplateNode(typescript, node) {
|
|
322
|
+
if (typescript.isTaggedTemplateExpression(node)) return node.template;
|
|
323
|
+
if (typescript.isTemplateExpression(node)) return node;
|
|
324
|
+
if (typescript.isNoSubstitutionTemplateLiteral(node)) return node;
|
|
325
|
+
}
|
|
326
|
+
function isClassMapKey(typescript, node) {
|
|
327
|
+
if (!node.parent?.parent?.parent) return false;
|
|
328
|
+
const assignment = node.parent;
|
|
329
|
+
const obj = assignment.parent;
|
|
330
|
+
const callExp = obj.parent;
|
|
331
|
+
const key = typescript.isStringLiteral(node) || typescript.isIdentifier(node);
|
|
332
|
+
return key && (typescript.isPropertyAssignment(assignment) && assignment.initializer !== node || typescript.isShorthandPropertyAssignment(assignment)) && typescript.isObjectLiteralExpression(obj) && typescript.isCallExpression(callExp) && typescript.isIdentifier(callExp.expression) && callExp.expression.text === Utils.ClassMap;
|
|
333
|
+
}
|
|
334
|
+
function getAllStyleNode(typescript, typeChecker, node) {
|
|
335
|
+
const getArgNode = (arg) => {
|
|
336
|
+
if (typescript.isIdentifier(arg)) {
|
|
337
|
+
const decl = typeChecker.getSymbolAtLocation(arg)?.valueDeclaration;
|
|
338
|
+
if (!decl || !typescript.isVariableDeclaration(decl) || !decl.initializer) return;
|
|
339
|
+
return getArgNode(decl.initializer);
|
|
340
|
+
}
|
|
341
|
+
const styleNode = getTemplateNode(typescript, arg);
|
|
342
|
+
if (styleNode) return styleNode;
|
|
343
|
+
const argArg = typescript.isCallExpression(arg) && arg.arguments.at(0);
|
|
344
|
+
if (argArg && typescript.isObjectLiteralExpression(argArg)) {
|
|
345
|
+
return argArg.properties.map((p) => {
|
|
346
|
+
const initializer = typescript.isPropertyAssignment(p) && p.initializer;
|
|
347
|
+
return initializer ? getTemplateNode(typescript, initializer) : void 0;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
return (node.modifiers || []).flatMap((m) => {
|
|
352
|
+
const arg = typescript.isDecorator(m) && typescript.isCallExpression(m.expression) && typescript.isIdentifier(m.expression.expression) && m.expression.expression.escapedText === Decorators.AdoptedStyle ? m.expression.arguments.at(0) : void 0;
|
|
353
|
+
if (!arg) return null;
|
|
354
|
+
return getArgNode(arg);
|
|
355
|
+
}).filter(isNotNullish);
|
|
356
|
+
}
|
|
357
|
+
function getTagFromNodeWithDecorator(typescript, node) {
|
|
358
|
+
if (!typescript.isClassDeclaration(node)) return;
|
|
359
|
+
for (const modifier of node.modifiers || []) {
|
|
360
|
+
if (typescript.isDecorator(modifier) && typescript.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === Decorators.CustomElement) {
|
|
361
|
+
const arg = modifier.expression.arguments.at(0);
|
|
362
|
+
if (arg && typescript.isStringLiteral(arg)) {
|
|
363
|
+
return arg.text;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function getCurrentElementDecl(typescript, node) {
|
|
369
|
+
while (!getTagFromNodeWithDecorator(typescript, node)) {
|
|
370
|
+
node = node.parent;
|
|
371
|
+
if (!node) return;
|
|
282
372
|
}
|
|
373
|
+
return node;
|
|
283
374
|
}
|
|
284
375
|
function getAstNodeAtPosition(typescript, node, pos) {
|
|
285
376
|
if (node.pos > pos || node.end <= pos) return;
|
|
286
377
|
while (node.kind >= typescript.SyntaxKind.FirstNode) {
|
|
287
|
-
const nested = typescript.forEachChild(node, (child) => child.pos
|
|
378
|
+
const nested = typescript.forEachChild(node, (child) => child.pos < pos && child.end >= pos ? child : void 0);
|
|
288
379
|
if (nested === void 0) break;
|
|
289
380
|
node = nested;
|
|
290
381
|
}
|
|
291
382
|
return node;
|
|
292
383
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
384
|
+
function getAllIdent(text) {
|
|
385
|
+
return [...text.matchAll(/[a-zA-Z0-9-_]+/g)].map(({ 0: v, index }) => ({
|
|
386
|
+
start: index,
|
|
387
|
+
length: v.length,
|
|
388
|
+
value: v
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
function getIdentAtPosition(text, offset) {
|
|
392
|
+
for (const { value, start } of getAllIdent(text)) {
|
|
393
|
+
if (offset >= start && offset <= start + value.length) {
|
|
394
|
+
return { text: value, start, length: value.length };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
306
397
|
}
|
|
307
398
|
function getAttrName(text) {
|
|
308
399
|
const attr = text.split("=").at(0);
|
|
@@ -343,12 +434,10 @@ function decorate(origin, cb) {
|
|
|
343
434
|
var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
|
|
344
435
|
var HTMLDataProvider = class {
|
|
345
436
|
#ts;
|
|
346
|
-
#
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this.#
|
|
350
|
-
this.#elements = elements;
|
|
351
|
-
this.#getProgram = getProgram;
|
|
437
|
+
#ctx;
|
|
438
|
+
constructor(ctx) {
|
|
439
|
+
this.#ts = ctx.ts;
|
|
440
|
+
this.#ctx = ctx;
|
|
352
441
|
}
|
|
353
442
|
getId() {
|
|
354
443
|
return NAME;
|
|
@@ -357,7 +446,7 @@ var HTMLDataProvider = class {
|
|
|
357
446
|
return true;
|
|
358
447
|
}
|
|
359
448
|
provideTags() {
|
|
360
|
-
return [...this.#elements].map(([tag, node]) => ({
|
|
449
|
+
return [...this.#ctx.elements].map(([tag, node]) => ({
|
|
361
450
|
name: tag,
|
|
362
451
|
attributes: [],
|
|
363
452
|
description: getDocComment(this.#ts, node)
|
|
@@ -365,14 +454,15 @@ var HTMLDataProvider = class {
|
|
|
365
454
|
}
|
|
366
455
|
provideAttributes(tag) {
|
|
367
456
|
const ts = this.#ts;
|
|
368
|
-
const typeChecker = this.#getProgram().getTypeChecker();
|
|
369
|
-
const node = this.#elements.get(tag);
|
|
457
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
458
|
+
const node = this.#ctx.elements.get(tag);
|
|
370
459
|
const result = [
|
|
371
460
|
{ name: "v-if", description: "Similar to vue `v-if`" },
|
|
372
461
|
{ name: "v-else-if", description: "Similar to vue `v-else-if`" },
|
|
373
462
|
{ name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
|
|
374
463
|
];
|
|
375
|
-
|
|
464
|
+
const builtInAttrsAndEvents = dataProvider.provideAttributes("div").map((e) => ({ ...e, name: e.name.replace(/^on/, "@") }));
|
|
465
|
+
if (!node) return [...result, ...builtInAttrsAndEvents];
|
|
376
466
|
const isDep = isDepElement(node);
|
|
377
467
|
const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
|
|
378
468
|
props.forEach((e) => {
|
|
@@ -385,40 +475,49 @@ var HTMLDataProvider = class {
|
|
|
385
475
|
const typeText = declaration.type?.getText();
|
|
386
476
|
const description = getDocComment(ts, declaration);
|
|
387
477
|
switch (type) {
|
|
478
|
+
// 一般是 attribute
|
|
388
479
|
case typeChecker.getStringType():
|
|
389
480
|
case typeChecker.getNumberType():
|
|
390
481
|
result.push({ name: e.name, description });
|
|
391
482
|
break;
|
|
483
|
+
// 一般是 boolean attribute
|
|
392
484
|
case typeChecker.getBooleanType():
|
|
393
485
|
result.push({ name: e.name, description, valueSet: "v" });
|
|
394
486
|
result.push({ name: `?${e.name}`, description });
|
|
395
487
|
break;
|
|
488
|
+
default: {
|
|
489
|
+
if (type && getUnionValues(type)) {
|
|
490
|
+
result.push({ name: e.name, description });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
396
493
|
}
|
|
397
|
-
if (
|
|
398
|
-
result.push({ name: e.name
|
|
399
|
-
}
|
|
400
|
-
if (typeText?.startsWith("Emitter")) {
|
|
401
|
-
result.push({ name: `@${e.name}`, description });
|
|
494
|
+
if (typeText?.startsWith(Types.Emitter)) {
|
|
495
|
+
result.push({ name: `@${camelToKebabCase(e.name)}`, description });
|
|
402
496
|
} else {
|
|
403
497
|
result.push({ name: `.${e.name}`, description });
|
|
404
498
|
}
|
|
405
499
|
});
|
|
406
|
-
|
|
407
|
-
oResult.forEach((data) => {
|
|
408
|
-
const tryEvtName = data.name.replace(/^on/, "@");
|
|
409
|
-
if (tryEvtName !== data.name) {
|
|
410
|
-
result.push({ ...data, name: tryEvtName });
|
|
411
|
-
}
|
|
412
|
-
});
|
|
500
|
+
result.push(...builtInAttrsAndEvents.filter((e) => e.name.startsWith("@")));
|
|
413
501
|
return result;
|
|
414
502
|
}
|
|
415
503
|
provideValues(tag, attr) {
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
|
|
420
|
-
const
|
|
421
|
-
|
|
504
|
+
const result = [];
|
|
505
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
506
|
+
const node = this.#ctx.elements.get(tag);
|
|
507
|
+
const prop = node && typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
|
|
508
|
+
const values = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
|
|
509
|
+
values?.forEach((name) => result.push({ name }));
|
|
510
|
+
if (attr === "class" || attr === "id") {
|
|
511
|
+
const currentElementDecl = getCurrentElementDecl(this.#ts, this.#ctx.currentNode);
|
|
512
|
+
if (currentElementDecl) {
|
|
513
|
+
this.#ctx.getAllCss(currentElementDecl).forEach(({ classIdNodeMap }) => {
|
|
514
|
+
classIdNodeMap.entries().filter(([key]) => !(+(attr === "id") ^ +key.startsWith("#"))).forEach(([classOrId]) => {
|
|
515
|
+
result.push({ name: classOrId.slice(attr === "id" ? 1 : 0) });
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return result;
|
|
422
521
|
}
|
|
423
522
|
};
|
|
424
523
|
function getUnionValues(type) {
|
|
@@ -442,17 +541,33 @@ function getDocComment(typescript, declaration) {
|
|
|
442
541
|
}
|
|
443
542
|
|
|
444
543
|
// src/context.ts
|
|
544
|
+
var NodeMap = class {
|
|
545
|
+
#map = {};
|
|
546
|
+
get(key) {
|
|
547
|
+
return this.#map[key];
|
|
548
|
+
}
|
|
549
|
+
add(key, value) {
|
|
550
|
+
if (!this.#map[key]) this.#map[key] = [];
|
|
551
|
+
this.#map[key].push(value);
|
|
552
|
+
}
|
|
553
|
+
entries() {
|
|
554
|
+
return Object.entries(this.#map);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
445
557
|
var Context = class {
|
|
558
|
+
// 用于 data-provider 自动完成
|
|
559
|
+
currentNode;
|
|
560
|
+
// TODO: 支持同名元素,查找时使用最近的声明
|
|
446
561
|
elements;
|
|
447
562
|
builtInElements;
|
|
448
563
|
ts;
|
|
449
564
|
config;
|
|
450
565
|
project;
|
|
451
566
|
logger;
|
|
452
|
-
dataProvider;
|
|
453
567
|
cssLanguageService;
|
|
454
568
|
htmlLanguageService;
|
|
455
569
|
htmlSourceHelper;
|
|
570
|
+
cssSourceHelper;
|
|
456
571
|
htmlTemplateStringSettings;
|
|
457
572
|
cssTemplateStringSettings;
|
|
458
573
|
getProgram;
|
|
@@ -462,12 +577,11 @@ var Context = class {
|
|
|
462
577
|
this.getProgram = () => info.languageService.getProgram();
|
|
463
578
|
this.project = info.project;
|
|
464
579
|
this.logger = logger;
|
|
465
|
-
this.dataProvider = dataProvider;
|
|
466
580
|
this.elements = new StringWeakMap();
|
|
467
581
|
this.builtInElements = new StringWeakMap();
|
|
468
582
|
this.cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({});
|
|
469
583
|
this.htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)({
|
|
470
|
-
customDataProviders: [
|
|
584
|
+
customDataProviders: [new HTMLDataProvider(this)]
|
|
471
585
|
});
|
|
472
586
|
this.htmlTemplateStringSettings = {
|
|
473
587
|
tags: ["html", "raw", "h"],
|
|
@@ -486,43 +600,98 @@ var Context = class {
|
|
|
486
600
|
new import_standard_script_source_helper.default(typescript, info.project),
|
|
487
601
|
logger
|
|
488
602
|
);
|
|
603
|
+
this.cssSourceHelper = new import_standard_template_source_helper.default(
|
|
604
|
+
typescript,
|
|
605
|
+
this.cssTemplateStringSettings,
|
|
606
|
+
new import_standard_script_source_helper.default(typescript, info.project),
|
|
607
|
+
logger
|
|
608
|
+
);
|
|
489
609
|
}
|
|
490
|
-
#virtualHtmlCache = new LRUCache({ max: 1e3 });
|
|
491
610
|
#virtualCssCache = new LRUCache({ max: 1e3 });
|
|
492
611
|
getCssDoc(text) {
|
|
493
612
|
return this.#virtualCssCache.get({ text, fileName: "" }, void 0, () => {
|
|
494
613
|
const vDoc = createVirtualDocument("css", text);
|
|
495
614
|
const vCss = this.cssLanguageService.parseStylesheet(vDoc);
|
|
496
|
-
|
|
615
|
+
const tagNodeMap = new NodeMap();
|
|
616
|
+
const classIdNodeMap = new NodeMap();
|
|
617
|
+
const propNodeMap = new NodeMap();
|
|
618
|
+
forEachNode(vCss.getChildren(), (node) => {
|
|
619
|
+
if (node.type === import_vscode_css_languageservice.NodeType.ElementNameSelector) {
|
|
620
|
+
tagNodeMap.add(node.getText(), node);
|
|
621
|
+
}
|
|
622
|
+
if (node.type === import_vscode_css_languageservice.NodeType.IdentifierSelector || node.parent?.type === import_vscode_css_languageservice.NodeType.ClassSelector) {
|
|
623
|
+
classIdNodeMap.add(node.getText(), node);
|
|
624
|
+
}
|
|
625
|
+
if (node.parent?.type === import_vscode_css_languageservice.NodeType.CustomPropertyDeclaration) {
|
|
626
|
+
propNodeMap.add(node.getText(), node);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
return { vDoc, vCss, tagNodeMap, classIdNodeMap, customPropNodeMap: propNodeMap };
|
|
497
630
|
});
|
|
498
631
|
}
|
|
632
|
+
getAllCss(node) {
|
|
633
|
+
return getAllStyleNode(this.ts, this.getProgram().getTypeChecker(), node).map((templateNode) => {
|
|
634
|
+
const { fileName } = templateNode.getSourceFile();
|
|
635
|
+
const templateContext = this.cssSourceHelper.getTemplate(fileName, templateNode.pos + 1);
|
|
636
|
+
if (!templateContext) return;
|
|
637
|
+
return { ...this.getCssDoc(templateContext.text), templateContext, templateNode };
|
|
638
|
+
}).filter(isNotNullish);
|
|
639
|
+
}
|
|
640
|
+
#virtualHtmlCache = new LRUCache({ max: 1e3 });
|
|
499
641
|
getHtmlDoc(text) {
|
|
500
642
|
return this.#virtualHtmlCache.get({ text, fileName: "" }, void 0, () => {
|
|
501
643
|
const vDoc = createVirtualDocument("html", text);
|
|
502
644
|
const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
|
|
503
|
-
|
|
645
|
+
const tagNodeMap = new NodeMap();
|
|
646
|
+
const classIdNodeMap = new NodeMap();
|
|
647
|
+
vHtml.roots.forEach(function process2(e, index, arr) {
|
|
504
648
|
e.prev = arr[index - 1];
|
|
505
649
|
e.next = arr[index + 1];
|
|
506
|
-
e.
|
|
650
|
+
e.tag && tagNodeMap.add(e.tag, e);
|
|
651
|
+
const idAttr = e.attributesMap.get("id");
|
|
652
|
+
const classAttr = e.attributesMap.get("class");
|
|
653
|
+
if (idAttr?.value) {
|
|
654
|
+
const text2 = getIdentAtPosition(idAttr.value, 1);
|
|
655
|
+
if (text2) {
|
|
656
|
+
classIdNodeMap.add(`#${text2.text}`, {
|
|
657
|
+
start: idAttr.end + 1 + text2.start,
|
|
658
|
+
length: text2.length
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (classAttr?.value) {
|
|
663
|
+
getAllIdent(classAttr.value).forEach((text2) => {
|
|
664
|
+
classIdNodeMap.add(text2.value, {
|
|
665
|
+
start: classAttr.end + 1 + text2.start,
|
|
666
|
+
length: text2.length
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
e.children.forEach(process2);
|
|
507
671
|
});
|
|
508
|
-
return { vDoc, vHtml };
|
|
672
|
+
return { vDoc, vHtml, tagNodeMap, classIdNodeMap };
|
|
509
673
|
});
|
|
510
674
|
}
|
|
675
|
+
prepareComplete(node) {
|
|
676
|
+
const tags = [...this.elements].map(([tag]) => tag);
|
|
677
|
+
(0, import_vscode_emmet_helper.updateTags)(tags);
|
|
678
|
+
(0, import_vscode_css_languageservice.updateTags)(tags);
|
|
679
|
+
this.currentNode = node;
|
|
680
|
+
}
|
|
511
681
|
getTagFromNode(node, supportClassName = isDepElement(node)) {
|
|
512
682
|
if (!this.ts.isClassDeclaration(node)) return;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const arg = modifier.expression.arguments.at(0);
|
|
516
|
-
if (arg && this.ts.isStringLiteral(arg)) {
|
|
517
|
-
return arg.text;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
683
|
+
const tag = getTagFromNodeWithDecorator(this.ts, node);
|
|
684
|
+
if (tag) return tag;
|
|
521
685
|
if (supportClassName && node.name && this.ts.isIdentifier(node.name)) {
|
|
522
686
|
return this.config.elementDefineRules.findTag(node.name.text);
|
|
523
687
|
}
|
|
524
688
|
}
|
|
525
689
|
updateElement(file) {
|
|
690
|
+
for (const [tag, decl] of this.elements) {
|
|
691
|
+
if (decl.getSourceFile().fileName === file.fileName) {
|
|
692
|
+
this.elements.delete?.(tag);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
526
695
|
const isDep = isDepElement(file);
|
|
527
696
|
this.ts.forEachChild(file, (node) => {
|
|
528
697
|
const tag = this.getTagFromNode(node, isDep);
|
|
@@ -537,7 +706,8 @@ var Context = class {
|
|
|
537
706
|
*/
|
|
538
707
|
initElements() {
|
|
539
708
|
const program = this.getProgram();
|
|
540
|
-
if (this.#initElementsCache.has(
|
|
709
|
+
if (this.#initElementsCache.has(this.project)) return;
|
|
710
|
+
this.#initElementsCache.add(this.project);
|
|
541
711
|
const files = program.getSourceFiles();
|
|
542
712
|
files.forEach((file) => this.updateElement(file));
|
|
543
713
|
const typeChecker = program.getTypeChecker();
|
|
@@ -556,12 +726,54 @@ var Context = class {
|
|
|
556
726
|
}
|
|
557
727
|
};
|
|
558
728
|
var partialBuiltInElementMap = {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
729
|
+
HTMLElement: [
|
|
730
|
+
"abbr",
|
|
731
|
+
"address",
|
|
732
|
+
"article",
|
|
733
|
+
"aside",
|
|
734
|
+
"b",
|
|
735
|
+
"bid",
|
|
736
|
+
"bdo",
|
|
737
|
+
"cite",
|
|
738
|
+
"code",
|
|
739
|
+
"dd",
|
|
740
|
+
"dfn",
|
|
741
|
+
"dt",
|
|
742
|
+
"em",
|
|
743
|
+
"figcaption",
|
|
744
|
+
"figure",
|
|
745
|
+
"footer",
|
|
746
|
+
"header",
|
|
747
|
+
"hgroup",
|
|
748
|
+
"i",
|
|
749
|
+
"kbd",
|
|
750
|
+
"main",
|
|
751
|
+
"mark",
|
|
752
|
+
"nav",
|
|
753
|
+
"noscript",
|
|
754
|
+
"rp",
|
|
755
|
+
"rt",
|
|
756
|
+
"ruby",
|
|
757
|
+
"s",
|
|
758
|
+
"samp",
|
|
759
|
+
"search",
|
|
760
|
+
"section",
|
|
761
|
+
"small",
|
|
762
|
+
"strong",
|
|
763
|
+
"sub",
|
|
764
|
+
"summary",
|
|
765
|
+
"sup",
|
|
766
|
+
"u",
|
|
767
|
+
"var",
|
|
768
|
+
"wbr"
|
|
769
|
+
],
|
|
770
|
+
HTMLIFrameElement: ["iframe"],
|
|
771
|
+
HTMLFieldSetElement: ["fieldset"],
|
|
772
|
+
HTMLFencedFrameElement: ["fencedframe"],
|
|
773
|
+
HTMLSelectedContentElement: ["selectedcontent"],
|
|
774
|
+
HTMLParagraphElement: ["p"],
|
|
775
|
+
HTMLOptGroupElement: ["optgroup"],
|
|
776
|
+
HTMLTextAreaElement: ["textarea"],
|
|
565
777
|
HTMLDListElement: ["dl"],
|
|
566
778
|
HTMLOListElement: ["ol"],
|
|
567
779
|
HTMLUListElement: ["ul"],
|
|
@@ -572,7 +784,18 @@ var partialBuiltInElementMap = {
|
|
|
572
784
|
HTMLTableCellElement: ["th", "td"],
|
|
573
785
|
HTMLTableColElement: ["col"],
|
|
574
786
|
HTMLTableRowElement: ["tr"],
|
|
575
|
-
HTMLTableSectionElement: ["thead", "tfoot", "tbody"]
|
|
787
|
+
HTMLTableSectionElement: ["thead", "tfoot", "tbody"],
|
|
788
|
+
// 和 svg 同名元素使用 html 元素对待
|
|
789
|
+
SVGAElement: [],
|
|
790
|
+
HTMLAnchorElement: ["a"],
|
|
791
|
+
SVGImageElement: [],
|
|
792
|
+
HTMLImageElement: ["img"],
|
|
793
|
+
SVGStyleElement: [],
|
|
794
|
+
HTMLStyleElement: ["style"],
|
|
795
|
+
SVGScriptElement: [],
|
|
796
|
+
HTMLScriptElement: ["script"],
|
|
797
|
+
SVGTitleElement: [],
|
|
798
|
+
HTMLTitleElement: ["title"]
|
|
576
799
|
};
|
|
577
800
|
function createVirtualDocument(languageId, content) {
|
|
578
801
|
return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
|
|
@@ -603,7 +826,7 @@ function isValidCSSTemplate(typescript, node, callName) {
|
|
|
603
826
|
|
|
604
827
|
// src/decorate-css.ts
|
|
605
828
|
var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
|
|
606
|
-
var
|
|
829
|
+
var import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
|
|
607
830
|
|
|
608
831
|
// src/translates.ts
|
|
609
832
|
var vscode = __toESM(require("vscode-languageserver-types"));
|
|
@@ -705,6 +928,36 @@ function translateHover(context, hover, position, offset = 0) {
|
|
|
705
928
|
tags: []
|
|
706
929
|
};
|
|
707
930
|
}
|
|
931
|
+
function translateFoldingRangeKind(context, kind) {
|
|
932
|
+
const typescript = context.typescript;
|
|
933
|
+
switch (kind) {
|
|
934
|
+
case vscode.FoldingRangeKind.Comment:
|
|
935
|
+
return typescript.OutliningSpanKind.Comment;
|
|
936
|
+
case vscode.FoldingRangeKind.Imports:
|
|
937
|
+
return typescript.OutliningSpanKind.Imports;
|
|
938
|
+
case vscode.FoldingRangeKind.Region:
|
|
939
|
+
return typescript.OutliningSpanKind.Region;
|
|
940
|
+
default:
|
|
941
|
+
return typescript.OutliningSpanKind.Code;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function translateFoldingRange(context, range) {
|
|
945
|
+
const start = context.toOffset({ line: range.startLine, character: range.startCharacter || 0 });
|
|
946
|
+
const end = context.toOffset({ line: range.endLine, character: range.endCharacter || 0 });
|
|
947
|
+
return {
|
|
948
|
+
kind: translateFoldingRangeKind(context, range.kind),
|
|
949
|
+
autoCollapse: true,
|
|
950
|
+
bannerText: range.collapsedText || "",
|
|
951
|
+
textSpan: {
|
|
952
|
+
start,
|
|
953
|
+
length: end - start
|
|
954
|
+
},
|
|
955
|
+
hintSpan: {
|
|
956
|
+
start,
|
|
957
|
+
length: end - start
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
708
961
|
function translateCompletionItemsToCompletionEntryDetails(context, item) {
|
|
709
962
|
return {
|
|
710
963
|
name: item.label,
|
|
@@ -743,8 +996,8 @@ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
|
|
|
743
996
|
{
|
|
744
997
|
containerName: "Custom Element",
|
|
745
998
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
746
|
-
name: definitionNode.name.text,
|
|
747
999
|
kind: context.typescript.ScriptElementKind.classElement,
|
|
1000
|
+
name: definitionNode.name.text,
|
|
748
1001
|
fileName: definitionNode.getSourceFile().fileName,
|
|
749
1002
|
textSpan: {
|
|
750
1003
|
start: definitionNode.name.getStart() - htmlOffset,
|
|
@@ -756,18 +1009,19 @@ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
|
|
|
756
1009
|
}
|
|
757
1010
|
function genAttrDefinitionInfo(context, { start, length }, propDeclaration) {
|
|
758
1011
|
const htmlOffset = context.node.pos + 1;
|
|
1012
|
+
const propName = propDeclaration.getText();
|
|
759
1013
|
return {
|
|
760
1014
|
textSpan: { start, length },
|
|
761
1015
|
definitions: [
|
|
762
1016
|
{
|
|
763
1017
|
containerName: "Attribute",
|
|
764
1018
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
765
|
-
name: propDeclaration.getText(),
|
|
766
1019
|
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1020
|
+
name: propName,
|
|
767
1021
|
fileName: propDeclaration.getSourceFile().fileName,
|
|
768
1022
|
textSpan: {
|
|
769
1023
|
start: propDeclaration.getStart() - htmlOffset,
|
|
770
|
-
length:
|
|
1024
|
+
length: propName.length
|
|
771
1025
|
}
|
|
772
1026
|
}
|
|
773
1027
|
]
|
|
@@ -780,14 +1034,31 @@ function genCurrentCtxDefinitionInfo(context, { start, length }, definitionTextS
|
|
|
780
1034
|
{
|
|
781
1035
|
containerName: "Attribute",
|
|
782
1036
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
783
|
-
name: context.text.slice(start, start + length),
|
|
784
1037
|
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1038
|
+
name: context.text.slice(start, start + length),
|
|
785
1039
|
fileName: context.fileName,
|
|
786
1040
|
textSpan: definitionTextSpan
|
|
787
1041
|
}
|
|
788
1042
|
]
|
|
789
1043
|
};
|
|
790
1044
|
}
|
|
1045
|
+
function genCurrentCtxCssDefinitionInfo(context, value, start, definitions) {
|
|
1046
|
+
const htmlOffset = context.node.getStart();
|
|
1047
|
+
const length = value.length;
|
|
1048
|
+
return {
|
|
1049
|
+
textSpan: { start, length },
|
|
1050
|
+
definitions: definitions.flatMap(
|
|
1051
|
+
({ ctx, nodes, offset = 0 }) => nodes.map((node) => ({
|
|
1052
|
+
containerName: "AttributeValue",
|
|
1053
|
+
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
1054
|
+
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1055
|
+
name: value,
|
|
1056
|
+
fileName: context.fileName,
|
|
1057
|
+
textSpan: { start: node.offset + ctx.node.pos - offset - htmlOffset, length: node.length }
|
|
1058
|
+
}))
|
|
1059
|
+
)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
791
1062
|
|
|
792
1063
|
// src/decorate-css.ts
|
|
793
1064
|
var CSSLanguageService = class {
|
|
@@ -816,9 +1087,9 @@ var CSSLanguageService = class {
|
|
|
816
1087
|
const { text, pos } = this.#normalize(context, position);
|
|
817
1088
|
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
818
1089
|
let emmetResults;
|
|
819
|
-
const onCssProperty = () => emmetResults = (0,
|
|
1090
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper2.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
|
|
820
1091
|
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
821
|
-
|
|
1092
|
+
this.#ctx.prepareComplete(context.node);
|
|
822
1093
|
const completions = this.#ctx.cssLanguageService.doComplete(vDoc, pos, vCss);
|
|
823
1094
|
completions.items.push(...emmetResults?.items || []);
|
|
824
1095
|
return completions;
|
|
@@ -865,22 +1136,95 @@ var CSSLanguageService = class {
|
|
|
865
1136
|
this.#ctx.initElements();
|
|
866
1137
|
return this.#diagnosticsCache.get(context, void 0, () => this.#getSyntacticDiagnostics(context));
|
|
867
1138
|
}
|
|
1139
|
+
getReferencesAtPosition(context, position) {
|
|
1140
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1141
|
+
const { text, offset } = this.#normalize(context, position);
|
|
1142
|
+
const { vCss } = this.#ctx.getCssDoc(text);
|
|
1143
|
+
const node = vCss.findChildAtOffset(context.toOffset(position) + offset, true);
|
|
1144
|
+
const result = [];
|
|
1145
|
+
if (node?.type === import_vscode_css_languageservice2.NodeType.IdentifierSelector || node?.parent?.type === import_vscode_css_languageservice2.NodeType.ClassSelector) {
|
|
1146
|
+
const classOrId = node.getText();
|
|
1147
|
+
for (const [, decl] of this.#ctx.elements) {
|
|
1148
|
+
const { fileName } = decl.getSourceFile();
|
|
1149
|
+
const styles = getAllStyleNode(context.typescript, typeChecker, decl);
|
|
1150
|
+
if (!styles.includes(context.node)) continue;
|
|
1151
|
+
forEachNode(decl.getChildren(), (node2) => {
|
|
1152
|
+
const templateNode = getTemplateNode(context.typescript, node2);
|
|
1153
|
+
if (templateNode) {
|
|
1154
|
+
const templateContext = this.#ctx.htmlSourceHelper.getTemplate(fileName, templateNode.pos + 1);
|
|
1155
|
+
if (!templateContext) return;
|
|
1156
|
+
const { classIdNodeMap } = this.#ctx.getHtmlDoc(templateContext.text);
|
|
1157
|
+
classIdNodeMap.get(classOrId)?.forEach((n) => {
|
|
1158
|
+
result.push({
|
|
1159
|
+
fileName,
|
|
1160
|
+
textSpan: { start: n.start + templateNode.pos - context.node.pos, length: n.length },
|
|
1161
|
+
isWriteAccess: true
|
|
1162
|
+
});
|
|
1163
|
+
});
|
|
1164
|
+
} else if (isClassMapKey(context.typescript, node2) && node2.text === classOrId) {
|
|
1165
|
+
const isString = context.typescript.isStringLiteral(node2);
|
|
1166
|
+
result.push({
|
|
1167
|
+
fileName,
|
|
1168
|
+
textSpan: {
|
|
1169
|
+
start: node2.getStart() - context.node.pos - 1 + (isString ? 1 : 0),
|
|
1170
|
+
length: node2.text.length
|
|
1171
|
+
},
|
|
1172
|
+
isWriteAccess: true
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (node?.parent?.type === import_vscode_css_languageservice2.NodeType.Property) {
|
|
1179
|
+
forEachIdent(vCss, node.getText(), (cssNode) => {
|
|
1180
|
+
result.push({
|
|
1181
|
+
fileName: context.fileName,
|
|
1182
|
+
textSpan: { start: cssNode.offset - offset, length: cssNode.end - cssNode.offset },
|
|
1183
|
+
isWriteAccess: true
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
return result;
|
|
1188
|
+
}
|
|
868
1189
|
getDefinitionAndBoundSpan(context, position) {
|
|
869
1190
|
const { text, offset } = this.#normalize(context, position);
|
|
870
|
-
const {
|
|
1191
|
+
const { vCss, customPropNodeMap } = this.#ctx.getCssDoc(text);
|
|
871
1192
|
const empty = { textSpan: { start: 0, length: 0 } };
|
|
872
|
-
const node = vCss.findChildAtOffset(context.toOffset(position), true);
|
|
1193
|
+
const node = vCss.findChildAtOffset(context.toOffset(position) + offset, true);
|
|
873
1194
|
if (!node) return empty;
|
|
874
|
-
const
|
|
875
|
-
|
|
1195
|
+
const textSpan = { start: node.offset - offset, length: node.length };
|
|
1196
|
+
if (node.type === import_vscode_css_languageservice2.NodeType.IdentifierSelector || node.parent?.type === import_vscode_css_languageservice2.NodeType.ClassSelector || node.parent?.parent?.type === import_vscode_css_languageservice2.NodeType.CustomPropertyDeclaration) {
|
|
1197
|
+
return genCurrentCtxDefinitionInfo(context, textSpan, textSpan);
|
|
1198
|
+
}
|
|
1199
|
+
const propDefinitions = customPropNodeMap.get(node.getText());
|
|
1200
|
+
if (node.type === import_vscode_css_languageservice2.NodeType.Identifier && propDefinitions) {
|
|
1201
|
+
return genCurrentCtxCssDefinitionInfo(context, node.getText(), node.offset - offset, [
|
|
1202
|
+
{ ctx: context, nodes: propDefinitions, offset }
|
|
1203
|
+
]);
|
|
1204
|
+
}
|
|
1205
|
+
if (node.parent?.type !== import_vscode_css_languageservice2.NodeType.ElementNameSelector) return empty;
|
|
1206
|
+
const definitionNode = this.#ctx.elements.get(node.getText());
|
|
876
1207
|
if (!definitionNode) return empty;
|
|
877
|
-
return genElementDefinitionInfo(context,
|
|
1208
|
+
return genElementDefinitionInfo(context, textSpan, definitionNode);
|
|
1209
|
+
}
|
|
1210
|
+
// 不知道如何起作用的,没有被调用,但不加就没有 css 折叠了
|
|
1211
|
+
getOutliningSpans(context) {
|
|
1212
|
+
const { vDoc } = this.#ctx.getHtmlDoc(context.text);
|
|
1213
|
+
const ranges = this.#ctx.cssLanguageService.getFoldingRanges(vDoc);
|
|
1214
|
+
return ranges.map((range) => translateFoldingRange(context, range));
|
|
878
1215
|
}
|
|
879
1216
|
};
|
|
1217
|
+
function forEachIdent(vCss, customPropName, fn) {
|
|
1218
|
+
forEachNode(vCss.getChildren(), (ident) => {
|
|
1219
|
+
if (ident.type !== import_vscode_css_languageservice2.NodeType.Identifier || ident.getText() !== customPropName) return;
|
|
1220
|
+
if (ident.parent?.type !== import_vscode_css_languageservice2.NodeType.Term && ident?.parent?.type !== import_vscode_css_languageservice2.NodeType.Property) return;
|
|
1221
|
+
fn(ident);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
880
1224
|
|
|
881
1225
|
// src/decorate-html.ts
|
|
882
1226
|
var import_vscode_css_languageservice3 = require("@mantou/vscode-css-languageservice");
|
|
883
|
-
var
|
|
1227
|
+
var import_vscode_emmet_helper3 = require("@mantou/vscode-emmet-helper");
|
|
884
1228
|
var HTMLLanguageService = class {
|
|
885
1229
|
#completionsCache = new LRUCache({ max: 1 });
|
|
886
1230
|
#diagnosticsCache = new LRUCache();
|
|
@@ -920,7 +1264,7 @@ var HTMLLanguageService = class {
|
|
|
920
1264
|
const css = this.#getEmbeddedCss(context, position, doc);
|
|
921
1265
|
if (!css) return [];
|
|
922
1266
|
let emmetResults;
|
|
923
|
-
const onCssProperty = () => emmetResults = (0,
|
|
1267
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
|
|
924
1268
|
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
925
1269
|
(0, import_vscode_css_languageservice3.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
|
|
926
1270
|
const completions = this.#ctx.cssLanguageService.doComplete(css.vDoc, css.position, css.style);
|
|
@@ -935,11 +1279,9 @@ var HTMLLanguageService = class {
|
|
|
935
1279
|
return this.#completionsCache.get(context, position, () => {
|
|
936
1280
|
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
937
1281
|
let emmetResults;
|
|
938
|
-
const onHtmlContent = () =>
|
|
939
|
-
(0, import_vscode_emmet_helper2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
|
|
940
|
-
emmetResults = (0, import_vscode_emmet_helper2.doComplete)(vDoc, position, "html", this.#ctx.config.emmet);
|
|
941
|
-
};
|
|
1282
|
+
const onHtmlContent = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(vDoc, position, "html", this.#ctx.config.emmet);
|
|
942
1283
|
this.#ctx.htmlLanguageService.setCompletionParticipants([{ onHtmlContent }]);
|
|
1284
|
+
this.#ctx.prepareComplete(context.node);
|
|
943
1285
|
const completions = this.#ctx.htmlLanguageService.doComplete(vDoc, position, vHtml);
|
|
944
1286
|
completions.items.push(...emmetResults?.items || []);
|
|
945
1287
|
completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
|
|
@@ -1001,7 +1343,7 @@ var HTMLLanguageService = class {
|
|
|
1001
1343
|
return diagnostics;
|
|
1002
1344
|
});
|
|
1003
1345
|
}
|
|
1004
|
-
#
|
|
1346
|
+
#getHtmlSemanticDiagnostics(context) {
|
|
1005
1347
|
const offset = context.node.getStart() + 1;
|
|
1006
1348
|
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1007
1349
|
const program = this.#ctx.getProgram();
|
|
@@ -1011,6 +1353,7 @@ var HTMLLanguageService = class {
|
|
|
1011
1353
|
forEachNode(vHtml.roots, (node) => {
|
|
1012
1354
|
if (!node.tag) return;
|
|
1013
1355
|
const customElementTagDecl = this.#ctx.elements.get(node.tag);
|
|
1356
|
+
const builtInElementTagDecl = this.#ctx.builtInElements.get(node.tag);
|
|
1014
1357
|
if (isCustomElementTag(node.tag) && !customElementTagDecl) {
|
|
1015
1358
|
diagnostics.push({
|
|
1016
1359
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
@@ -1022,13 +1365,38 @@ var HTMLLanguageService = class {
|
|
|
1022
1365
|
source: NAME
|
|
1023
1366
|
});
|
|
1024
1367
|
}
|
|
1025
|
-
const tagDeclaration = customElementTagDecl ||
|
|
1368
|
+
const tagDeclaration = customElementTagDecl || builtInElementTagDecl;
|
|
1026
1369
|
if (!tagDeclaration) return;
|
|
1370
|
+
if (tagDeclaration.name && isDeprecate(typeChecker.getSymbolAtLocation(tagDeclaration.name))) {
|
|
1371
|
+
[node.start + 1, node.endTagStart && node.endTagStart + 2].forEach((tagStart) => {
|
|
1372
|
+
tagStart && diagnostics.push({
|
|
1373
|
+
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1374
|
+
file,
|
|
1375
|
+
start: tagStart,
|
|
1376
|
+
length: node.tag.length,
|
|
1377
|
+
source: NAME,
|
|
1378
|
+
code: 105 /* Deprecated */,
|
|
1379
|
+
messageText: `Deprecated tag '${node.tag}'`,
|
|
1380
|
+
reportsDeprecated: "true"
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1027
1384
|
for (const [attributeName, { value, start, end }] of node.attributesMap) {
|
|
1028
1385
|
if (attributeName.startsWith("_")) continue;
|
|
1029
1386
|
const hasValueSpan = value?.startsWith("_");
|
|
1030
1387
|
const attrInfo = getAttrName(attributeName);
|
|
1031
|
-
const propType = getPropType(typeChecker, tagDeclaration, !customElementTagDecl, attrInfo)
|
|
1388
|
+
const propType = getPropType(typeChecker, tagDeclaration, !customElementTagDecl, attrInfo, () => {
|
|
1389
|
+
diagnostics.push({
|
|
1390
|
+
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1391
|
+
file,
|
|
1392
|
+
start,
|
|
1393
|
+
length: end - start,
|
|
1394
|
+
source: NAME,
|
|
1395
|
+
code: 105 /* Deprecated */,
|
|
1396
|
+
messageText: `Deprecated prop '${attrInfo.attr}'`,
|
|
1397
|
+
reportsDeprecated: "true"
|
|
1398
|
+
});
|
|
1399
|
+
});
|
|
1032
1400
|
const diagnostic = {
|
|
1033
1401
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
1034
1402
|
file,
|
|
@@ -1044,6 +1412,7 @@ var HTMLLanguageService = class {
|
|
|
1044
1412
|
code: 104 /* PropSyntaxError */,
|
|
1045
1413
|
messageText: `'${attrInfo.attr}' syntax error`
|
|
1046
1414
|
});
|
|
1415
|
+
continue;
|
|
1047
1416
|
}
|
|
1048
1417
|
if (attributeName === "v-if" || attributeName === "v-else-if") {
|
|
1049
1418
|
const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
|
|
@@ -1058,8 +1427,17 @@ var HTMLLanguageService = class {
|
|
|
1058
1427
|
}
|
|
1059
1428
|
continue;
|
|
1060
1429
|
}
|
|
1430
|
+
if (attrInfo.decorate === "" && attributeName !== camelToKebabCase(attrInfo.attr)) {
|
|
1431
|
+
diagnostics.push({
|
|
1432
|
+
...diagnostic,
|
|
1433
|
+
code: 2552 /* AttrFormatError */,
|
|
1434
|
+
messageText: `Consider using '${customElementTagDecl ? camelToKebabCase(attrInfo.attr) : attrInfo.attr.toLowerCase()}'`
|
|
1435
|
+
});
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1061
1438
|
if (!propType) {
|
|
1062
|
-
if (attrInfo.decorate !== "@"
|
|
1439
|
+
if (attrInfo.decorate !== "@" && // SVG 元素有很多 css 属性,所以不检查
|
|
1440
|
+
!builtInElementTagDecl?.name.getText().startsWith("SVG")) {
|
|
1063
1441
|
diagnostics.push({
|
|
1064
1442
|
...diagnostic,
|
|
1065
1443
|
code: 102 /* UnknownProp */,
|
|
@@ -1068,6 +1446,16 @@ var HTMLLanguageService = class {
|
|
|
1068
1446
|
}
|
|
1069
1447
|
continue;
|
|
1070
1448
|
}
|
|
1449
|
+
if (globalEnumeratedBooleanAttr.has(attrInfo.attr) && // <div ?draggable=${xx}>
|
|
1450
|
+
(attrInfo.decorate === "?" || // <div draggable>
|
|
1451
|
+
value === null)) {
|
|
1452
|
+
diagnostics.push({
|
|
1453
|
+
...diagnostic,
|
|
1454
|
+
code: 104 /* PropSyntaxError */,
|
|
1455
|
+
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}', must has value`
|
|
1456
|
+
});
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1071
1459
|
if (value === null) {
|
|
1072
1460
|
if (attrInfo.decorate) {
|
|
1073
1461
|
diagnostics.push({
|
|
@@ -1100,11 +1488,13 @@ var HTMLLanguageService = class {
|
|
|
1100
1488
|
diagnostics.push(diagnostic);
|
|
1101
1489
|
}
|
|
1102
1490
|
continue;
|
|
1103
|
-
default:
|
|
1104
|
-
|
|
1491
|
+
default: {
|
|
1492
|
+
const nullablePropType = getUnionType(typeChecker, [propType, typeChecker.getNullType()]);
|
|
1493
|
+
if (!typeChecker.isTypeAssignableTo(spanType, nullablePropType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
|
|
1105
1494
|
diagnostics.push(diagnostic);
|
|
1106
1495
|
}
|
|
1107
1496
|
continue;
|
|
1497
|
+
}
|
|
1108
1498
|
}
|
|
1109
1499
|
} else {
|
|
1110
1500
|
const types = [typeChecker.getStringType(), typeChecker.getNumberType()];
|
|
@@ -1120,6 +1510,15 @@ var HTMLLanguageService = class {
|
|
|
1120
1510
|
code: 104 /* PropSyntaxError */,
|
|
1121
1511
|
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
|
|
1122
1512
|
});
|
|
1513
|
+
} else if (globalEnumeratedBooleanAttr.has(attrInfo.attr)) {
|
|
1514
|
+
const values = ["true", "false", ...globalEnumeratedBooleanAttr.get(attrInfo.attr)];
|
|
1515
|
+
if (!values.includes(valueLetter)) {
|
|
1516
|
+
diagnostics.push({
|
|
1517
|
+
...diagnostic,
|
|
1518
|
+
code: 103 /* PropTypeError */,
|
|
1519
|
+
messageText: `Must be ${values.join(", ")}`
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1123
1522
|
} else if (types.every((t) => !typeChecker.isTypeAssignableTo(t, propType))) {
|
|
1124
1523
|
diagnostics.push(diagnostic);
|
|
1125
1524
|
}
|
|
@@ -1130,20 +1529,25 @@ var HTMLLanguageService = class {
|
|
|
1130
1529
|
}
|
|
1131
1530
|
getSyntacticDiagnostics(context) {
|
|
1132
1531
|
this.#ctx.initElements();
|
|
1133
|
-
return
|
|
1532
|
+
return this.#getCssSyntacticDiagnostics(context);
|
|
1533
|
+
}
|
|
1534
|
+
getSemanticDiagnostics(context) {
|
|
1535
|
+
return this.#getHtmlSemanticDiagnostics(context);
|
|
1134
1536
|
}
|
|
1135
1537
|
getDefinitionAndBoundSpan(context, position) {
|
|
1538
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1136
1539
|
const currentOffset = context.toOffset(position);
|
|
1137
1540
|
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1138
|
-
const node = vHtml
|
|
1139
|
-
const {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1541
|
+
const { node, attrStart, attrEnd, attrName, attrValue } = getHTMLNodeAtPosition(vHtml, currentOffset);
|
|
1542
|
+
const empty = { textSpan: { start: 0, length: 0 } };
|
|
1543
|
+
const notInStartTag = currentOffset > node.startTagEnd;
|
|
1544
|
+
const startTagNameStart = node.start + 1;
|
|
1545
|
+
if (!node.tag) return empty;
|
|
1546
|
+
if (node.tag === "style" && notInStartTag) {
|
|
1142
1547
|
const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
|
|
1143
1548
|
const cssNode = style.findChildAtOffset(vDoc.offsetAt(pos), true);
|
|
1144
|
-
if (!cssNode) return empty;
|
|
1145
|
-
const
|
|
1146
|
-
const definitionNode2 = this.#ctx.elements.get(ident);
|
|
1549
|
+
if (!cssNode || cssNode.parent?.type !== import_vscode_css_languageservice3.NodeType.ElementNameSelector) return empty;
|
|
1550
|
+
const definitionNode2 = this.#ctx.elements.get(cssNode.getText());
|
|
1147
1551
|
if (!definitionNode2) return empty;
|
|
1148
1552
|
return genElementDefinitionInfo(
|
|
1149
1553
|
context,
|
|
@@ -1152,28 +1556,104 @@ var HTMLLanguageService = class {
|
|
|
1152
1556
|
);
|
|
1153
1557
|
}
|
|
1154
1558
|
const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
|
|
1155
|
-
if (!definitionNode ||
|
|
1156
|
-
if (
|
|
1157
|
-
return genElementDefinitionInfo(context, { start, length }, definitionNode);
|
|
1559
|
+
if (!definitionNode || notInStartTag) return empty;
|
|
1560
|
+
if (currentOffset < startTagNameStart + node.tag.length) {
|
|
1561
|
+
return genElementDefinitionInfo(context, { start: startTagNameStart, length: node.tag.length }, definitionNode);
|
|
1158
1562
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1563
|
+
if (!attrName) return empty;
|
|
1564
|
+
const { attr, offset } = getAttrName(attrName);
|
|
1565
|
+
if (attr === "class" || attr === "id") {
|
|
1566
|
+
if (!attrValue) return empty;
|
|
1567
|
+
const currentAttrValue = getIdentAtPosition(attrValue, currentOffset - (attrEnd + 1));
|
|
1568
|
+
const currentElementDecl = getCurrentElementDecl(context.typescript, context.node);
|
|
1569
|
+
if (!currentAttrValue || !currentElementDecl) return empty;
|
|
1570
|
+
return genCurrentCtxCssDefinitionInfo(
|
|
1571
|
+
context,
|
|
1572
|
+
currentAttrValue.text,
|
|
1573
|
+
attrEnd + 1 + currentAttrValue.start,
|
|
1574
|
+
this.#ctx.getAllCss(currentElementDecl).flatMap(({ classIdNodeMap, templateContext }) => {
|
|
1575
|
+
const nodes = classIdNodeMap.get(attr === "id" ? `#${currentAttrValue.text}` : currentAttrValue.text);
|
|
1576
|
+
return { ctx: templateContext, nodes: nodes || [], offset: 0 };
|
|
1577
|
+
}).filter(isNotNullish)
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
if (currentOffset > attrEnd) return empty;
|
|
1161
1581
|
if (attr === "v-else" || attr === "v-else-if") {
|
|
1162
1582
|
const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
|
|
1163
1583
|
if (!ifAttr) return empty;
|
|
1164
1584
|
return genCurrentCtxDefinitionInfo(
|
|
1165
1585
|
context,
|
|
1166
|
-
{ start:
|
|
1586
|
+
{ start: attrStart + offset, length: attr.length },
|
|
1167
1587
|
{ start: ifAttr.start, length: ifAttr.end - ifAttr.start }
|
|
1168
1588
|
);
|
|
1169
1589
|
}
|
|
1170
|
-
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1171
1590
|
const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
|
|
1172
1591
|
const propDeclaration = propSymbol?.getDeclarations()?.at(0);
|
|
1173
1592
|
if (!propDeclaration) return empty;
|
|
1174
|
-
return genAttrDefinitionInfo(context, { start:
|
|
1593
|
+
return genAttrDefinitionInfo(context, { start: attrStart + offset, length: attr.length }, propDeclaration);
|
|
1594
|
+
}
|
|
1595
|
+
// 不知道如何起作用的,没有被调用,但不加就没有 HTML 折叠了
|
|
1596
|
+
// 所以也忽略了 HTML 里面的内联样式折叠
|
|
1597
|
+
getOutliningSpans(context) {
|
|
1598
|
+
const { vDoc } = this.#ctx.getHtmlDoc(context.text);
|
|
1599
|
+
const ranges = this.#ctx.htmlLanguageService.getFoldingRanges(vDoc);
|
|
1600
|
+
return ranges.map((range) => translateFoldingRange(context, range));
|
|
1601
|
+
}
|
|
1602
|
+
getJsxClosingTagAtPosition(context, position) {
|
|
1603
|
+
const currentOffset = context.toOffset(position);
|
|
1604
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1605
|
+
const node = vHtml.findNodeAt(currentOffset);
|
|
1606
|
+
if (!node.tag || !node.startTagEnd || voidElementTags.has(node.tag)) return;
|
|
1607
|
+
if (!node.endTagStart) return { newText: `</${node.tag}>` };
|
|
1608
|
+
}
|
|
1609
|
+
getDocumentHighlights(context, position) {
|
|
1610
|
+
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1611
|
+
const docHighlights = this.#ctx.htmlLanguageService.findDocumentHighlights(vDoc, position, vHtml);
|
|
1612
|
+
return [
|
|
1613
|
+
{
|
|
1614
|
+
fileName: context.fileName,
|
|
1615
|
+
highlightSpans: docHighlights.map(({ range }) => {
|
|
1616
|
+
const start = vDoc.offsetAt(range.start);
|
|
1617
|
+
const end = vDoc.offsetAt(range.end);
|
|
1618
|
+
return { textSpan: { start, length: end - start }, kind: this.#ctx.ts.HighlightSpanKind.definition };
|
|
1619
|
+
})
|
|
1620
|
+
}
|
|
1621
|
+
];
|
|
1622
|
+
}
|
|
1623
|
+
getCodeFixesAtPosition(context, start, end, errorCodes) {
|
|
1624
|
+
const result = [];
|
|
1625
|
+
if (errorCodes.includes(2552 /* AttrFormatError */)) {
|
|
1626
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1627
|
+
const node = vHtml.findNodeAt(start);
|
|
1628
|
+
const isBuiltInTag = !!this.#ctx.builtInElements.get(node.tag);
|
|
1629
|
+
const attr = context.text.slice(start, end);
|
|
1630
|
+
const targetAttr = isBuiltInTag ? attr.toLowerCase() : camelToKebabCase(attr);
|
|
1631
|
+
const textChanges = [{ span: { start, length: end - start }, newText: targetAttr }];
|
|
1632
|
+
result.push({
|
|
1633
|
+
fixName: context.fileName,
|
|
1634
|
+
description: `Convert attribute to '${targetAttr}'`,
|
|
1635
|
+
changes: [{ fileName: context.fileName, textChanges }]
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
return result;
|
|
1175
1639
|
}
|
|
1176
1640
|
};
|
|
1641
|
+
var voidElementTags = /* @__PURE__ */ new Set([
|
|
1642
|
+
"area",
|
|
1643
|
+
"base",
|
|
1644
|
+
"br",
|
|
1645
|
+
"col",
|
|
1646
|
+
"embed",
|
|
1647
|
+
"hr",
|
|
1648
|
+
"img",
|
|
1649
|
+
"input",
|
|
1650
|
+
"link",
|
|
1651
|
+
"meta",
|
|
1652
|
+
"param",
|
|
1653
|
+
"source",
|
|
1654
|
+
"track",
|
|
1655
|
+
"wbr"
|
|
1656
|
+
]);
|
|
1177
1657
|
function getSpanExpression(typescript, file, pos) {
|
|
1178
1658
|
let node = getAstNodeAtPosition(typescript, file, pos);
|
|
1179
1659
|
while (!typescript.isTemplateSpan(node)) {
|
|
@@ -1183,7 +1663,7 @@ function getSpanExpression(typescript, file, pos) {
|
|
|
1183
1663
|
return node.expression;
|
|
1184
1664
|
}
|
|
1185
1665
|
function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
|
|
1186
|
-
const valueOffset = attrNameEnd + htmlOffset +
|
|
1666
|
+
const valueOffset = attrNameEnd + htmlOffset + 4;
|
|
1187
1667
|
const spanExp = getSpanExpression(typescript, file, valueOffset);
|
|
1188
1668
|
return typeChecker.getTypeAtLocation(spanExp);
|
|
1189
1669
|
}
|
|
@@ -1194,17 +1674,24 @@ var buildInElementNoGlobalAttrPropMap = /* @__PURE__ */ new Map([
|
|
|
1194
1674
|
// <input> list: string
|
|
1195
1675
|
["list", "ariaLabelledby"]
|
|
1196
1676
|
]);
|
|
1197
|
-
|
|
1677
|
+
var globalAttrPropMap = /* @__PURE__ */ new Map([["contenteditable", "contentEditable"]]);
|
|
1678
|
+
var globalEnumeratedBooleanAttr = /* @__PURE__ */ new Map([
|
|
1679
|
+
["draggable", []],
|
|
1680
|
+
["spellcheck", []],
|
|
1681
|
+
["contenteditable", ["plaintext-only"]]
|
|
1682
|
+
]);
|
|
1683
|
+
function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo, reportDeprecate) {
|
|
1198
1684
|
const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
|
|
1199
1685
|
if (attrInfo.attr.startsWith("data-")) {
|
|
1200
1686
|
return typeChecker.getStringType();
|
|
1201
1687
|
}
|
|
1202
|
-
const propName = isBuiltInTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr);
|
|
1688
|
+
const propName = globalAttrPropMap.get(attrInfo.attr) || (isBuiltInTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr));
|
|
1203
1689
|
switch (propName) {
|
|
1204
1690
|
case "class":
|
|
1205
1691
|
case "style":
|
|
1206
1692
|
case "part":
|
|
1207
1693
|
case "exportparts":
|
|
1694
|
+
case "accesskey":
|
|
1208
1695
|
case "xmlns":
|
|
1209
1696
|
case "viewBox":
|
|
1210
1697
|
case "ariaLabelledby":
|
|
@@ -1233,6 +1720,7 @@ function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo) {
|
|
|
1233
1720
|
default: {
|
|
1234
1721
|
const isEvent = attrInfo.decorate === "@";
|
|
1235
1722
|
const propSymbol = classType.getProperty(propName);
|
|
1723
|
+
if (isDeprecate(propSymbol)) reportDeprecate();
|
|
1236
1724
|
const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
|
|
1237
1725
|
if (!isEvent) return propType;
|
|
1238
1726
|
const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
|
|
@@ -1240,6 +1728,11 @@ function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo) {
|
|
|
1240
1728
|
}
|
|
1241
1729
|
}
|
|
1242
1730
|
}
|
|
1731
|
+
function isDeprecate(symbol) {
|
|
1732
|
+
if (!symbol) return false;
|
|
1733
|
+
const tags = symbol.getJsDocTags();
|
|
1734
|
+
return tags.some(({ name }) => name === "deprecated");
|
|
1735
|
+
}
|
|
1243
1736
|
function getEmitterHandleType(typeChecker, classType, propType) {
|
|
1244
1737
|
const handleSymbol = propType?.getProperty("handler");
|
|
1245
1738
|
if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
|
|
@@ -1257,6 +1750,19 @@ function getUnionType(typeChecker, types) {
|
|
|
1257
1750
|
}
|
|
1258
1751
|
return types.at(0);
|
|
1259
1752
|
}
|
|
1753
|
+
function getHTMLNodeAtPosition(vHtml, offset) {
|
|
1754
|
+
const node = vHtml.findNodeAt(offset);
|
|
1755
|
+
const attr = node.attributesMap.entries().find(([_, n]) => {
|
|
1756
|
+
return offset > n.start && offset < n.end + 1 + (n.value?.length || 0);
|
|
1757
|
+
});
|
|
1758
|
+
return {
|
|
1759
|
+
node,
|
|
1760
|
+
attrName: attr?.[0] ?? "",
|
|
1761
|
+
attrValue: attr?.[1].value ?? "",
|
|
1762
|
+
attrStart: attr?.[1].start ?? 0,
|
|
1763
|
+
attrEnd: attr?.[1].end ?? 0
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1260
1766
|
|
|
1261
1767
|
// src/decorate-ts.ts
|
|
1262
1768
|
function decorateLanguageService(ctx, languageService) {
|
|
@@ -1265,14 +1771,58 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1265
1771
|
languageService.getCompletionsAtPosition = (...args) => {
|
|
1266
1772
|
const program = getProgram();
|
|
1267
1773
|
const typeChecker = program.getTypeChecker();
|
|
1268
|
-
decorate(typeChecker, () => decorateTypeChecker(
|
|
1774
|
+
decorate(typeChecker, () => decorateTypeChecker(typeChecker));
|
|
1775
|
+
const classKeys = getClassKeys(ctx, args[0], args[1]);
|
|
1776
|
+
if (classKeys) {
|
|
1777
|
+
return {
|
|
1778
|
+
isGlobalCompletion: true,
|
|
1779
|
+
isMemberCompletion: true,
|
|
1780
|
+
isNewIdentifierLocation: true,
|
|
1781
|
+
entries: classKeys.map((name) => ({ kind: ts.ScriptElementKind.enumMemberElement, sortText: "", name }))
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
const themeKeys = getThemeKeys(ctx, args[0], args[1]);
|
|
1785
|
+
if (themeKeys) {
|
|
1786
|
+
return {
|
|
1787
|
+
isGlobalCompletion: true,
|
|
1788
|
+
isMemberCompletion: true,
|
|
1789
|
+
isNewIdentifierLocation: true,
|
|
1790
|
+
entries: themeKeys.map((name) => ({ kind: ts.ScriptElementKind.enumMemberElement, sortText: "", name }))
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1269
1793
|
return ls.getCompletionsAtPosition(...args);
|
|
1270
1794
|
};
|
|
1795
|
+
languageService.getSyntacticDiagnostics = (...args) => {
|
|
1796
|
+
const program = getProgram();
|
|
1797
|
+
const file = program.getSourceFile(args[0]);
|
|
1798
|
+
ctx.updateElement(file);
|
|
1799
|
+
return ls.getSyntacticDiagnostics(...args);
|
|
1800
|
+
};
|
|
1271
1801
|
languageService.getSuggestionDiagnostics = (...args) => {
|
|
1272
1802
|
const program = getProgram();
|
|
1273
1803
|
const file = program.getSourceFile(args[0]);
|
|
1274
1804
|
const result = ls.getSuggestionDiagnostics(...args);
|
|
1275
|
-
|
|
1805
|
+
ts.forEachChild(file, (node) => {
|
|
1806
|
+
const tag = ctx.getTagFromNode(node);
|
|
1807
|
+
if (tag && ts.isClassDeclaration(node)) {
|
|
1808
|
+
node.members.forEach((member) => {
|
|
1809
|
+
if (!ts.isPropertyDeclaration(member) || !member.modifiers) return;
|
|
1810
|
+
const shouldIsStatic = member.modifiers.some(
|
|
1811
|
+
(modifier) => ts.isDecorator(modifier) && ts.isIdentifier(modifier.expression) && [Decorators.Slot, Decorators.Part].includes(modifier.expression.text)
|
|
1812
|
+
);
|
|
1813
|
+
if (shouldIsStatic && member.modifiers.every((e) => e.kind !== ts.SyntaxKind.StaticKeyword)) {
|
|
1814
|
+
result.push({
|
|
1815
|
+
file,
|
|
1816
|
+
start: member.getStart(),
|
|
1817
|
+
length: member.getEnd() - member.getStart(),
|
|
1818
|
+
category: ts.DiagnosticCategory.Warning,
|
|
1819
|
+
code: 2552 /* DecoratorSyntaxError */,
|
|
1820
|
+
messageText: "Use static field for `@part` and `@slot`"
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1276
1826
|
return result.filter(({ start, reportsUnnecessary, category }) => {
|
|
1277
1827
|
if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
|
|
1278
1828
|
const node = getAstNodeAtPosition(ts, file, start);
|
|
@@ -1283,52 +1833,84 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1283
1833
|
});
|
|
1284
1834
|
};
|
|
1285
1835
|
languageService.findReferences = (...args) => {
|
|
1836
|
+
const map = /* @__PURE__ */ new Map();
|
|
1837
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, ...args);
|
|
1838
|
+
if (tagDefinedInfo) {
|
|
1839
|
+
forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (file, tagInfo) => {
|
|
1840
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
1841
|
+
map.set(file.fileName, symbol);
|
|
1842
|
+
symbol.references.push({
|
|
1843
|
+
fileName: file.fileName,
|
|
1844
|
+
isWriteAccess: true,
|
|
1845
|
+
textSpan: tagInfo.open
|
|
1846
|
+
});
|
|
1847
|
+
});
|
|
1848
|
+
forEachAllCssTemplateNode(ctx, tagDefinedInfo.tag, (file, textSpan) => {
|
|
1849
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
1850
|
+
map.set(file.fileName, symbol);
|
|
1851
|
+
symbol.references.push({
|
|
1852
|
+
fileName: file.fileName,
|
|
1853
|
+
isWriteAccess: true,
|
|
1854
|
+
textSpan
|
|
1855
|
+
});
|
|
1856
|
+
});
|
|
1857
|
+
return [...map.values()];
|
|
1858
|
+
}
|
|
1286
1859
|
const oResult = ls.findReferences(...args) || [];
|
|
1287
1860
|
const program = getProgram();
|
|
1288
1861
|
const currentNode = getAstNodeAtPosition(ts, program.getSourceFile(args[0]), args[1]);
|
|
1289
|
-
if (!currentNode) return oResult;
|
|
1290
|
-
const isIdent = ts.isIdentifier(currentNode);
|
|
1291
|
-
if (!isIdent) return oResult;
|
|
1862
|
+
if (!currentNode || !ts.isIdentifier(currentNode)) return oResult;
|
|
1292
1863
|
const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
|
|
1293
1864
|
const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
|
|
1294
1865
|
if (!currentTag) return oResult;
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
map.set(file.fileName, symbol);
|
|
1310
|
-
if (prop) {
|
|
1311
|
-
const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
|
|
1312
|
-
const kebabCaseName = camelToKebabCase(prop.text);
|
|
1313
|
-
["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
|
|
1314
|
-
for (const propName of propNames) {
|
|
1315
|
-
const info = tagInfo.node.attributesMap.get(propName);
|
|
1316
|
-
if (!info) continue;
|
|
1317
|
-
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1318
|
-
symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
|
|
1319
|
-
}
|
|
1320
|
-
} else {
|
|
1321
|
-
symbol.references.push({
|
|
1322
|
-
fileName: file.fileName,
|
|
1323
|
-
isWriteAccess: true,
|
|
1324
|
-
textSpan: tagInfo.open
|
|
1866
|
+
if (prop) {
|
|
1867
|
+
getAllTagFromProp(ctx, currentNode).forEach((tag) => {
|
|
1868
|
+
forEachAllHtmlTemplateNode(ctx, tag, (file, tagInfo) => {
|
|
1869
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
1870
|
+
map.set(file.fileName, symbol);
|
|
1871
|
+
const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
|
|
1872
|
+
const kebabCaseName = camelToKebabCase(prop.text);
|
|
1873
|
+
["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
|
|
1874
|
+
for (const propName of propNames) {
|
|
1875
|
+
const info = tagInfo.node.attributesMap.get(propName);
|
|
1876
|
+
if (!info) continue;
|
|
1877
|
+
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1878
|
+
symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
|
|
1879
|
+
}
|
|
1325
1880
|
});
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1328
1883
|
return [...map.values(), ...oResult];
|
|
1329
1884
|
};
|
|
1885
|
+
languageService.getDefinitionAndBoundSpan = (...args) => {
|
|
1886
|
+
const classMapKeyInfo = getClassMapKeyInfo(ctx, ...args);
|
|
1887
|
+
const kind = ts.ScriptElementKind.classElement;
|
|
1888
|
+
const containerKind = ts.ScriptElementKind.unknown;
|
|
1889
|
+
const fileName = args[0];
|
|
1890
|
+
if (classMapKeyInfo) {
|
|
1891
|
+
return {
|
|
1892
|
+
textSpan: classMapKeyInfo.textSpan,
|
|
1893
|
+
definitions: classMapKeyInfo.styles.flatMap(({ classIdNodeMap, templateNode }) => {
|
|
1894
|
+
return classIdNodeMap.get(classMapKeyInfo.text)?.map((node) => ({
|
|
1895
|
+
kind,
|
|
1896
|
+
containerKind,
|
|
1897
|
+
fileName,
|
|
1898
|
+
containerName: "",
|
|
1899
|
+
name: "",
|
|
1900
|
+
textSpan: { start: node.offset + templateNode.pos + 1, length: node.getText().length }
|
|
1901
|
+
}));
|
|
1902
|
+
}).filter(isNotNullish)
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, ...args);
|
|
1906
|
+
if (!tagDefinedInfo) return ls.getDefinitionAndBoundSpan(...args);
|
|
1907
|
+
const textSpan = tagDefinedInfo.textSpan;
|
|
1908
|
+
return {
|
|
1909
|
+
textSpan,
|
|
1910
|
+
definitions: [{ kind, containerKind, fileName, containerName: "", name: "", textSpan }]
|
|
1911
|
+
};
|
|
1912
|
+
};
|
|
1330
1913
|
languageService.getRenameInfo = (fileName, position, ...args) => {
|
|
1331
|
-
const result = ls.getRenameInfo(fileName, position, ...args);
|
|
1332
1914
|
const tagInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
1333
1915
|
if (tagInfo) {
|
|
1334
1916
|
return {
|
|
@@ -1351,7 +1933,7 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1351
1933
|
triggerSpan: tagDefinedInfo.textSpan
|
|
1352
1934
|
};
|
|
1353
1935
|
}
|
|
1354
|
-
return
|
|
1936
|
+
return ls.getRenameInfo(fileName, position, ...args);
|
|
1355
1937
|
};
|
|
1356
1938
|
languageService.findRenameLocations = (fileName, position, ...args) => {
|
|
1357
1939
|
const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
@@ -1367,31 +1949,42 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1367
1949
|
result.push({ fileName: f.fileName, textSpan: info.open });
|
|
1368
1950
|
if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
|
|
1369
1951
|
});
|
|
1952
|
+
forEachAllCssTemplateNode(ctx, tagDefinedInfo.tag, (f, textSpan) => {
|
|
1953
|
+
result.push({ fileName: f.fileName, textSpan });
|
|
1954
|
+
});
|
|
1370
1955
|
return result;
|
|
1371
1956
|
}
|
|
1372
1957
|
const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
|
|
1373
1958
|
const file = ctx.getProgram().getSourceFile(fileName);
|
|
1374
|
-
const node = getAstNodeAtPosition(
|
|
1959
|
+
const node = getAstNodeAtPosition(ts, file, position);
|
|
1375
1960
|
const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
|
|
1376
|
-
if (!tag) return oResult;
|
|
1961
|
+
if (!tag || !ts.isIdentifier(node)) return oResult;
|
|
1377
1962
|
const propText = node.getText();
|
|
1378
1963
|
const kebabCaseName = camelToKebabCase(propText);
|
|
1379
|
-
if (isPropType(
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1384
|
-
oResult.push({ fileName: f.fileName, prefixText: "@", textSpan });
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
if (isPropType(ctx.ts, node.parent, ["attribute", "numattribute", "boolattribute", "property"])) {
|
|
1388
|
-
forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
|
|
1389
|
-
const propNames = ["", ".", "?"].map((c) => `${c}${kebabCaseName}`);
|
|
1390
|
-
propNames.map((propName) => {
|
|
1391
|
-
const info = tagInfo.node.attributesMap.get(propName);
|
|
1964
|
+
if (isPropType(ts, node.parent, [Decorators.Emitter, Decorators.GlobalEmitter])) {
|
|
1965
|
+
getAllTagFromProp(ctx, node).forEach((tag2) => {
|
|
1966
|
+
forEachAllHtmlTemplateNode(ctx, tag2, (f, tagInfo) => {
|
|
1967
|
+
const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
|
|
1392
1968
|
if (!info) return;
|
|
1393
1969
|
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1394
|
-
oResult.push({ fileName: f.fileName, prefixText: "
|
|
1970
|
+
oResult.push({ textSpan, fileName: f.fileName, prefixText: "@" });
|
|
1971
|
+
});
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
if (isPropType(ts, node.parent, [Decorators.Attr, Decorators.NumAttr, Decorators.BoolAttr, Decorators.Prop])) {
|
|
1975
|
+
getAllTagFromProp(ctx, node).forEach((tag2) => {
|
|
1976
|
+
forEachAllHtmlTemplateNode(ctx, tag2, (f, tagInfo) => {
|
|
1977
|
+
const propNames = [
|
|
1978
|
+
["", kebabCaseName],
|
|
1979
|
+
["?", kebabCaseName],
|
|
1980
|
+
[".", propText]
|
|
1981
|
+
];
|
|
1982
|
+
propNames.map(([decorate2, propName]) => {
|
|
1983
|
+
const info = tagInfo.node.attributesMap.get(decorate2 + propName);
|
|
1984
|
+
if (!info) return;
|
|
1985
|
+
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1986
|
+
oResult.push({ textSpan, fileName: f.fileName, prefixText: decorate2 });
|
|
1987
|
+
});
|
|
1395
1988
|
});
|
|
1396
1989
|
});
|
|
1397
1990
|
}
|
|
@@ -1399,15 +1992,53 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1399
1992
|
};
|
|
1400
1993
|
return languageService;
|
|
1401
1994
|
}
|
|
1995
|
+
function getAllTagFromProp(ctx, prop) {
|
|
1996
|
+
let originTagDecl = prop;
|
|
1997
|
+
while (!ctx.ts.isClassDeclaration(originTagDecl)) {
|
|
1998
|
+
originTagDecl = originTagDecl.parent;
|
|
1999
|
+
}
|
|
2000
|
+
const typeChecker = ctx.getProgram().getTypeChecker();
|
|
2001
|
+
const originTagType = typeChecker.getTypeAtLocation(originTagDecl);
|
|
2002
|
+
const result = [];
|
|
2003
|
+
[...ctx.elements].forEach(([tag, decl]) => {
|
|
2004
|
+
const tagType = typeChecker.getTypeAtLocation(decl);
|
|
2005
|
+
if (!tagType.isClassOrInterface()) return;
|
|
2006
|
+
if (typeChecker.isTypeAssignableTo(tagType, originTagType)) {
|
|
2007
|
+
result.push(tag);
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
return result;
|
|
2011
|
+
}
|
|
2012
|
+
function getReferencedSymbol(ctx, file) {
|
|
2013
|
+
return {
|
|
2014
|
+
references: [],
|
|
2015
|
+
definition: {
|
|
2016
|
+
containerKind: ctx.ts.ScriptElementKind.unknown,
|
|
2017
|
+
containerName: "",
|
|
2018
|
+
displayParts: [],
|
|
2019
|
+
fileName: file.fileName,
|
|
2020
|
+
textSpan: { start: 0, length: 0 },
|
|
2021
|
+
name: "test",
|
|
2022
|
+
kind: ctx.ts.ScriptElementKind.unknown
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
1402
2026
|
function forEachAllHtmlTemplateNode(ctx, tag, fn) {
|
|
1403
2027
|
for (const file of ctx.getProgram().getSourceFiles()) {
|
|
1404
2028
|
if (file.fileName.endsWith(".d.ts")) continue;
|
|
1405
2029
|
for (const templateContext of ctx.htmlSourceHelper.getAllTemplates(file.fileName)) {
|
|
1406
|
-
const {
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
2030
|
+
const { tagNodeMap } = ctx.getHtmlDoc(templateContext.text);
|
|
2031
|
+
tagNodeMap.get(tag)?.forEach((node) => fn(file, getTagInfo(node, templateContext.node.getStart() + 1)));
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
function forEachAllCssTemplateNode(ctx, tag, fn) {
|
|
2036
|
+
for (const file of ctx.getProgram().getSourceFiles()) {
|
|
2037
|
+
if (file.fileName.endsWith(".d.ts")) continue;
|
|
2038
|
+
for (const templateContext of ctx.cssSourceHelper.getAllTemplates(file.fileName)) {
|
|
2039
|
+
const { tagNodeMap } = ctx.getCssDoc(templateContext.text);
|
|
2040
|
+
const offset = templateContext.node.getStart() + 1;
|
|
2041
|
+
tagNodeMap.get(tag)?.forEach((node) => fn(file, { start: offset + node.offset, length: node.end - node.offset }));
|
|
1411
2042
|
}
|
|
1412
2043
|
}
|
|
1413
2044
|
}
|
|
@@ -1419,20 +2050,97 @@ function findCurrentTagInfo(ctx, fileName, position) {
|
|
|
1419
2050
|
const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
|
|
1420
2051
|
const offset = templateContext.toOffset(relativePosition);
|
|
1421
2052
|
const node = vHtml.findNodeAt(offset);
|
|
1422
|
-
|
|
1423
|
-
const onTag = offset < node.startTagEnd && text === node.tag;
|
|
1424
|
-
if (!onTag || !node.tag) return;
|
|
1425
|
-
return getTagInfo(node, htmlOffset);
|
|
2053
|
+
if (node.tag && offset < node.start + 1 + node.tag.length) return getTagInfo(node, htmlOffset);
|
|
1426
2054
|
}
|
|
1427
2055
|
function findDefinedTagInfo(ctx, fileName, position) {
|
|
1428
2056
|
const file = ctx.getProgram().getSourceFile(fileName);
|
|
1429
2057
|
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
1430
|
-
if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !==
|
|
2058
|
+
if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !== Decorators.CustomElement) {
|
|
1431
2059
|
return;
|
|
1432
2060
|
}
|
|
1433
2061
|
const tag = node.text;
|
|
1434
2062
|
return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
|
|
1435
2063
|
}
|
|
2064
|
+
function getClassMapKeyInfo(ctx, fileName, position) {
|
|
2065
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2066
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2067
|
+
const decl = node && getCurrentElementDecl(ctx.ts, node);
|
|
2068
|
+
if (decl && isClassMapKey(ctx.ts, node)) {
|
|
2069
|
+
const isString = ctx.ts.isStringLiteral(node);
|
|
2070
|
+
return {
|
|
2071
|
+
text: node.text,
|
|
2072
|
+
styles: ctx.getAllCss(decl),
|
|
2073
|
+
textSpan: { start: node.getStart() + (isString ? 1 : 0), length: node.text.length }
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
function isKey(typescript, node) {
|
|
2078
|
+
if (!node.parent?.parent) return false;
|
|
2079
|
+
const assignment = node.parent;
|
|
2080
|
+
const obj = assignment.parent;
|
|
2081
|
+
const key = typescript.isStringLiteral(node) || typescript.isIdentifier(node);
|
|
2082
|
+
return key && (typescript.isPropertyAssignment(assignment) && assignment.initializer !== node || typescript.isShorthandPropertyAssignment(assignment)) && typescript.isObjectLiteralExpression(obj);
|
|
2083
|
+
}
|
|
2084
|
+
function inReturn(typescript, node) {
|
|
2085
|
+
if (!node?.parent?.parent) return false;
|
|
2086
|
+
const isReturnObject = (n) => typescript.isObjectLiteralExpression(n) && (typescript.isParenthesizedExpression(n.parent) && typescript.isArrowFunction(n.parent.parent) && n.parent.parent.body === n.parent || typescript.isReturnStatement(n.parent));
|
|
2087
|
+
return (
|
|
2088
|
+
// 空对象
|
|
2089
|
+
isReturnObject(node) || // 在返回对象的 key 上
|
|
2090
|
+
isReturnObject(node.parent.parent) && isKey(typescript, node)
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
function getThemeKeys(ctx, fileName, position) {
|
|
2094
|
+
const program = ctx.getProgram();
|
|
2095
|
+
const typeChecker = program.getTypeChecker();
|
|
2096
|
+
const file = program.getSourceFile(fileName);
|
|
2097
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2098
|
+
if (!inReturn(ctx.ts, node)) return;
|
|
2099
|
+
let desc = node;
|
|
2100
|
+
while (desc) {
|
|
2101
|
+
if ((ctx.ts.isArrowFunction(desc) || ctx.ts.isFunctionExpression(desc)) && !ctx.ts.isPropertyDeclaration(desc.parent) || ctx.ts.isFunctionDeclaration(desc) || ctx.ts.isClassDeclaration(desc) || ctx.ts.isClassExpression(desc)) {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (ctx.ts.isPropertyDeclaration(desc) || ctx.ts.isMethodDeclaration(desc)) {
|
|
2105
|
+
for (const modifier of desc.modifiers || []) {
|
|
2106
|
+
if (!ctx.ts.isDecorator(modifier) || !ctx.ts.isCallExpression(modifier.expression)) continue;
|
|
2107
|
+
const themeDescSymbol = typeChecker.getSymbolAtLocation(modifier.expression.expression);
|
|
2108
|
+
const themeDesc = themeDescSymbol?.valueDeclaration;
|
|
2109
|
+
const param = themeDesc && ctx.ts.isVariableDeclaration(themeDesc) && themeDesc.initializer && ctx.ts.isCallExpression(themeDesc.initializer) && themeDesc.initializer.expression.getText() === Utils.CreateDecoratorTheme && themeDesc.initializer.arguments.at(0);
|
|
2110
|
+
if (!param) continue;
|
|
2111
|
+
const type = typeChecker.getTypeAtLocation(param);
|
|
2112
|
+
return type.getApparentProperties().map((e) => e.name);
|
|
2113
|
+
}
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
desc = desc.parent;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function isClassMap(typescript, node) {
|
|
2120
|
+
if (!node.parent?.parent) return false;
|
|
2121
|
+
const callExp = node.parent;
|
|
2122
|
+
const isEmptyClassMap = typescript.isObjectLiteralExpression(node) && typescript.isCallExpression(callExp) && typescript.isIdentifier(callExp.expression) && callExp.expression.text === Utils.ClassMap;
|
|
2123
|
+
return isEmptyClassMap || isClassMapKey(typescript, node);
|
|
2124
|
+
}
|
|
2125
|
+
function getCurrentClassMap(typescript, node) {
|
|
2126
|
+
while (!typescript.isObjectLiteralExpression(node)) {
|
|
2127
|
+
node = node.parent;
|
|
2128
|
+
if (!node) return;
|
|
2129
|
+
}
|
|
2130
|
+
return node;
|
|
2131
|
+
}
|
|
2132
|
+
function getClassKeys(ctx, fileName, position) {
|
|
2133
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2134
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2135
|
+
const decl = node && getCurrentElementDecl(ctx.ts, node);
|
|
2136
|
+
if (decl && isClassMap(ctx.ts, node)) {
|
|
2137
|
+
const obj = getCurrentClassMap(ctx.ts, node);
|
|
2138
|
+
const keys2 = new Set(obj.properties.map((e) => e.name?.getText()));
|
|
2139
|
+
return ctx.getAllCss(decl).flatMap(({ classIdNodeMap }) => {
|
|
2140
|
+
return [...classIdNodeMap.entries()].filter(([k]) => !keys2.has(k) && !k.startsWith("#")).map(([k]) => k);
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
1436
2144
|
function isPropType(typescript, node, types) {
|
|
1437
2145
|
if (!typescript.isPropertyDeclaration(node)) return;
|
|
1438
2146
|
for (const modifier of node.modifiers || []) {
|
|
@@ -1443,21 +2151,16 @@ function isPropType(typescript, node, types) {
|
|
|
1443
2151
|
}
|
|
1444
2152
|
}
|
|
1445
2153
|
}
|
|
1446
|
-
function decorateTypeChecker(
|
|
2154
|
+
function decorateTypeChecker(typeChecker) {
|
|
2155
|
+
const neverType = typeChecker.getNeverType();
|
|
1447
2156
|
const internal = typeChecker;
|
|
1448
2157
|
const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
|
|
1449
2158
|
internal.isValidPropertyAccessForCompletions = (...args) => {
|
|
1450
2159
|
const result = checker.isValidPropertyAccessForCompletions(...args);
|
|
1451
|
-
if (!result) return false;
|
|
1452
2160
|
try {
|
|
1453
|
-
|
|
1454
|
-
if (!declarations) return true;
|
|
1455
|
-
const isNever = declarations.every(
|
|
1456
|
-
(node) => ctx.ts.isPropertySignature(node) && node.type?.getText() === "never"
|
|
1457
|
-
);
|
|
1458
|
-
return !isNever;
|
|
2161
|
+
return result && typeChecker.getTypeOfSymbol(args.at(2)) !== neverType;
|
|
1459
2162
|
} catch {
|
|
1460
|
-
return
|
|
2163
|
+
return result;
|
|
1461
2164
|
}
|
|
1462
2165
|
};
|
|
1463
2166
|
return typeChecker;
|