ts-gem-plugin 0.0.7 → 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 +912 -188
- package/package.json +6 -6
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;
|
|
@@ -266,7 +303,8 @@ function isCustomElementTag(tag) {
|
|
|
266
303
|
return tag.includes("-");
|
|
267
304
|
}
|
|
268
305
|
function isDepElement(node) {
|
|
269
|
-
|
|
306
|
+
const { fileName } = node.getSourceFile();
|
|
307
|
+
return ["/node_modules/", "/dist/", ".d.ts"].some((s) => fileName.includes(s));
|
|
270
308
|
}
|
|
271
309
|
function bindMemberFunction(o, keys2 = Object.keys(o)) {
|
|
272
310
|
return Object.fromEntries(keys2.map((key) => [key, o[key].bind?.(o)]));
|
|
@@ -277,31 +315,85 @@ function forEachNode(roots, fn) {
|
|
|
277
315
|
const currentNode = list.pop();
|
|
278
316
|
if (!currentNode) return;
|
|
279
317
|
fn(currentNode);
|
|
280
|
-
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;
|
|
281
372
|
}
|
|
373
|
+
return node;
|
|
282
374
|
}
|
|
283
375
|
function getAstNodeAtPosition(typescript, node, pos) {
|
|
284
376
|
if (node.pos > pos || node.end <= pos) return;
|
|
285
377
|
while (node.kind >= typescript.SyntaxKind.FirstNode) {
|
|
286
|
-
const nested = typescript.forEachChild(node, (child) => child.pos
|
|
378
|
+
const nested = typescript.forEachChild(node, (child) => child.pos < pos && child.end >= pos ? child : void 0);
|
|
287
379
|
if (nested === void 0) break;
|
|
288
380
|
node = nested;
|
|
289
381
|
}
|
|
290
382
|
return node;
|
|
291
383
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
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
|
+
}
|
|
305
397
|
}
|
|
306
398
|
function getAttrName(text) {
|
|
307
399
|
const attr = text.split("=").at(0);
|
|
@@ -342,12 +434,10 @@ function decorate(origin, cb) {
|
|
|
342
434
|
var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
|
|
343
435
|
var HTMLDataProvider = class {
|
|
344
436
|
#ts;
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
this.#
|
|
349
|
-
this.#elements = elements;
|
|
350
|
-
this.#getProgram = getProgram;
|
|
437
|
+
#ctx;
|
|
438
|
+
constructor(ctx) {
|
|
439
|
+
this.#ts = ctx.ts;
|
|
440
|
+
this.#ctx = ctx;
|
|
351
441
|
}
|
|
352
442
|
getId() {
|
|
353
443
|
return NAME;
|
|
@@ -356,7 +446,7 @@ var HTMLDataProvider = class {
|
|
|
356
446
|
return true;
|
|
357
447
|
}
|
|
358
448
|
provideTags() {
|
|
359
|
-
return [...this.#elements].map(([tag, node]) => ({
|
|
449
|
+
return [...this.#ctx.elements].map(([tag, node]) => ({
|
|
360
450
|
name: tag,
|
|
361
451
|
attributes: [],
|
|
362
452
|
description: getDocComment(this.#ts, node)
|
|
@@ -364,14 +454,15 @@ var HTMLDataProvider = class {
|
|
|
364
454
|
}
|
|
365
455
|
provideAttributes(tag) {
|
|
366
456
|
const ts = this.#ts;
|
|
367
|
-
const typeChecker = this.#getProgram().getTypeChecker();
|
|
368
|
-
const node = this.#elements.get(tag);
|
|
457
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
458
|
+
const node = this.#ctx.elements.get(tag);
|
|
369
459
|
const result = [
|
|
370
460
|
{ name: "v-if", description: "Similar to vue `v-if`" },
|
|
371
461
|
{ name: "v-else-if", description: "Similar to vue `v-else-if`" },
|
|
372
462
|
{ name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
|
|
373
463
|
];
|
|
374
|
-
|
|
464
|
+
const builtInAttrsAndEvents = dataProvider.provideAttributes("div").map((e) => ({ ...e, name: e.name.replace(/^on/, "@") }));
|
|
465
|
+
if (!node) return [...result, ...builtInAttrsAndEvents];
|
|
375
466
|
const isDep = isDepElement(node);
|
|
376
467
|
const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
|
|
377
468
|
props.forEach((e) => {
|
|
@@ -384,40 +475,49 @@ var HTMLDataProvider = class {
|
|
|
384
475
|
const typeText = declaration.type?.getText();
|
|
385
476
|
const description = getDocComment(ts, declaration);
|
|
386
477
|
switch (type) {
|
|
478
|
+
// 一般是 attribute
|
|
387
479
|
case typeChecker.getStringType():
|
|
388
480
|
case typeChecker.getNumberType():
|
|
389
481
|
result.push({ name: e.name, description });
|
|
390
482
|
break;
|
|
483
|
+
// 一般是 boolean attribute
|
|
391
484
|
case typeChecker.getBooleanType():
|
|
392
485
|
result.push({ name: e.name, description, valueSet: "v" });
|
|
393
486
|
result.push({ name: `?${e.name}`, description });
|
|
394
487
|
break;
|
|
488
|
+
default: {
|
|
489
|
+
if (type && getUnionValues(type)) {
|
|
490
|
+
result.push({ name: e.name, description });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
395
493
|
}
|
|
396
|
-
if (
|
|
397
|
-
result.push({ name: e.name
|
|
398
|
-
}
|
|
399
|
-
if (typeText?.startsWith("Emitter")) {
|
|
400
|
-
result.push({ name: `@${e.name}`, description });
|
|
494
|
+
if (typeText?.startsWith(Types.Emitter)) {
|
|
495
|
+
result.push({ name: `@${camelToKebabCase(e.name)}`, description });
|
|
401
496
|
} else {
|
|
402
497
|
result.push({ name: `.${e.name}`, description });
|
|
403
498
|
}
|
|
404
499
|
});
|
|
405
|
-
|
|
406
|
-
oResult.forEach((data) => {
|
|
407
|
-
const tryEvtName = data.name.replace(/^on/, "@");
|
|
408
|
-
if (tryEvtName !== data.name) {
|
|
409
|
-
result.push({ ...data, name: tryEvtName });
|
|
410
|
-
}
|
|
411
|
-
});
|
|
500
|
+
result.push(...builtInAttrsAndEvents.filter((e) => e.name.startsWith("@")));
|
|
412
501
|
return result;
|
|
413
502
|
}
|
|
414
503
|
provideValues(tag, attr) {
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
|
|
419
|
-
const
|
|
420
|
-
|
|
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;
|
|
421
521
|
}
|
|
422
522
|
};
|
|
423
523
|
function getUnionValues(type) {
|
|
@@ -441,17 +541,33 @@ function getDocComment(typescript, declaration) {
|
|
|
441
541
|
}
|
|
442
542
|
|
|
443
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
|
+
};
|
|
444
557
|
var Context = class {
|
|
558
|
+
// 用于 data-provider 自动完成
|
|
559
|
+
currentNode;
|
|
560
|
+
// TODO: 支持同名元素,查找时使用最近的声明
|
|
445
561
|
elements;
|
|
446
562
|
builtInElements;
|
|
447
563
|
ts;
|
|
448
564
|
config;
|
|
449
565
|
project;
|
|
450
566
|
logger;
|
|
451
|
-
dataProvider;
|
|
452
567
|
cssLanguageService;
|
|
453
568
|
htmlLanguageService;
|
|
454
569
|
htmlSourceHelper;
|
|
570
|
+
cssSourceHelper;
|
|
455
571
|
htmlTemplateStringSettings;
|
|
456
572
|
cssTemplateStringSettings;
|
|
457
573
|
getProgram;
|
|
@@ -461,12 +577,11 @@ var Context = class {
|
|
|
461
577
|
this.getProgram = () => info.languageService.getProgram();
|
|
462
578
|
this.project = info.project;
|
|
463
579
|
this.logger = logger;
|
|
464
|
-
this.dataProvider = dataProvider;
|
|
465
580
|
this.elements = new StringWeakMap();
|
|
466
581
|
this.builtInElements = new StringWeakMap();
|
|
467
582
|
this.cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({});
|
|
468
583
|
this.htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)({
|
|
469
|
-
customDataProviders: [
|
|
584
|
+
customDataProviders: [new HTMLDataProvider(this)]
|
|
470
585
|
});
|
|
471
586
|
this.htmlTemplateStringSettings = {
|
|
472
587
|
tags: ["html", "raw", "h"],
|
|
@@ -485,43 +600,98 @@ var Context = class {
|
|
|
485
600
|
new import_standard_script_source_helper.default(typescript, info.project),
|
|
486
601
|
logger
|
|
487
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
|
+
);
|
|
488
609
|
}
|
|
489
|
-
#virtualHtmlCache = new LRUCache({ max: 1e3 });
|
|
490
610
|
#virtualCssCache = new LRUCache({ max: 1e3 });
|
|
491
611
|
getCssDoc(text) {
|
|
492
612
|
return this.#virtualCssCache.get({ text, fileName: "" }, void 0, () => {
|
|
493
613
|
const vDoc = createVirtualDocument("css", text);
|
|
494
614
|
const vCss = this.cssLanguageService.parseStylesheet(vDoc);
|
|
495
|
-
|
|
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 };
|
|
496
630
|
});
|
|
497
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 });
|
|
498
641
|
getHtmlDoc(text) {
|
|
499
642
|
return this.#virtualHtmlCache.get({ text, fileName: "" }, void 0, () => {
|
|
500
643
|
const vDoc = createVirtualDocument("html", text);
|
|
501
644
|
const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
|
|
502
|
-
|
|
645
|
+
const tagNodeMap = new NodeMap();
|
|
646
|
+
const classIdNodeMap = new NodeMap();
|
|
647
|
+
vHtml.roots.forEach(function process2(e, index, arr) {
|
|
503
648
|
e.prev = arr[index - 1];
|
|
504
649
|
e.next = arr[index + 1];
|
|
505
|
-
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);
|
|
506
671
|
});
|
|
507
|
-
return { vDoc, vHtml };
|
|
672
|
+
return { vDoc, vHtml, tagNodeMap, classIdNodeMap };
|
|
508
673
|
});
|
|
509
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
|
+
}
|
|
510
681
|
getTagFromNode(node, supportClassName = isDepElement(node)) {
|
|
511
682
|
if (!this.ts.isClassDeclaration(node)) return;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const arg = modifier.expression.arguments.at(0);
|
|
515
|
-
if (arg && this.ts.isStringLiteral(arg)) {
|
|
516
|
-
return arg.text;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
683
|
+
const tag = getTagFromNodeWithDecorator(this.ts, node);
|
|
684
|
+
if (tag) return tag;
|
|
520
685
|
if (supportClassName && node.name && this.ts.isIdentifier(node.name)) {
|
|
521
686
|
return this.config.elementDefineRules.findTag(node.name.text);
|
|
522
687
|
}
|
|
523
688
|
}
|
|
524
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
|
+
}
|
|
525
695
|
const isDep = isDepElement(file);
|
|
526
696
|
this.ts.forEachChild(file, (node) => {
|
|
527
697
|
const tag = this.getTagFromNode(node, isDep);
|
|
@@ -536,7 +706,8 @@ var Context = class {
|
|
|
536
706
|
*/
|
|
537
707
|
initElements() {
|
|
538
708
|
const program = this.getProgram();
|
|
539
|
-
if (this.#initElementsCache.has(
|
|
709
|
+
if (this.#initElementsCache.has(this.project)) return;
|
|
710
|
+
this.#initElementsCache.add(this.project);
|
|
540
711
|
const files = program.getSourceFiles();
|
|
541
712
|
files.forEach((file) => this.updateElement(file));
|
|
542
713
|
const typeChecker = program.getTypeChecker();
|
|
@@ -555,12 +726,54 @@ var Context = class {
|
|
|
555
726
|
}
|
|
556
727
|
};
|
|
557
728
|
var partialBuiltInElementMap = {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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"],
|
|
564
777
|
HTMLDListElement: ["dl"],
|
|
565
778
|
HTMLOListElement: ["ol"],
|
|
566
779
|
HTMLUListElement: ["ul"],
|
|
@@ -571,7 +784,18 @@ var partialBuiltInElementMap = {
|
|
|
571
784
|
HTMLTableCellElement: ["th", "td"],
|
|
572
785
|
HTMLTableColElement: ["col"],
|
|
573
786
|
HTMLTableRowElement: ["tr"],
|
|
574
|
-
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"]
|
|
575
799
|
};
|
|
576
800
|
function createVirtualDocument(languageId, content) {
|
|
577
801
|
return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
|
|
@@ -602,7 +826,7 @@ function isValidCSSTemplate(typescript, node, callName) {
|
|
|
602
826
|
|
|
603
827
|
// src/decorate-css.ts
|
|
604
828
|
var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
|
|
605
|
-
var
|
|
829
|
+
var import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
|
|
606
830
|
|
|
607
831
|
// src/translates.ts
|
|
608
832
|
var vscode = __toESM(require("vscode-languageserver-types"));
|
|
@@ -704,6 +928,36 @@ function translateHover(context, hover, position, offset = 0) {
|
|
|
704
928
|
tags: []
|
|
705
929
|
};
|
|
706
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
|
+
}
|
|
707
961
|
function translateCompletionItemsToCompletionEntryDetails(context, item) {
|
|
708
962
|
return {
|
|
709
963
|
name: item.label,
|
|
@@ -742,8 +996,8 @@ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
|
|
|
742
996
|
{
|
|
743
997
|
containerName: "Custom Element",
|
|
744
998
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
745
|
-
name: definitionNode.name.text,
|
|
746
999
|
kind: context.typescript.ScriptElementKind.classElement,
|
|
1000
|
+
name: definitionNode.name.text,
|
|
747
1001
|
fileName: definitionNode.getSourceFile().fileName,
|
|
748
1002
|
textSpan: {
|
|
749
1003
|
start: definitionNode.name.getStart() - htmlOffset,
|
|
@@ -755,18 +1009,19 @@ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
|
|
|
755
1009
|
}
|
|
756
1010
|
function genAttrDefinitionInfo(context, { start, length }, propDeclaration) {
|
|
757
1011
|
const htmlOffset = context.node.pos + 1;
|
|
1012
|
+
const propName = propDeclaration.getText();
|
|
758
1013
|
return {
|
|
759
1014
|
textSpan: { start, length },
|
|
760
1015
|
definitions: [
|
|
761
1016
|
{
|
|
762
1017
|
containerName: "Attribute",
|
|
763
1018
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
764
|
-
name: propDeclaration.getText(),
|
|
765
1019
|
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1020
|
+
name: propName,
|
|
766
1021
|
fileName: propDeclaration.getSourceFile().fileName,
|
|
767
1022
|
textSpan: {
|
|
768
1023
|
start: propDeclaration.getStart() - htmlOffset,
|
|
769
|
-
length:
|
|
1024
|
+
length: propName.length
|
|
770
1025
|
}
|
|
771
1026
|
}
|
|
772
1027
|
]
|
|
@@ -779,14 +1034,31 @@ function genCurrentCtxDefinitionInfo(context, { start, length }, definitionTextS
|
|
|
779
1034
|
{
|
|
780
1035
|
containerName: "Attribute",
|
|
781
1036
|
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
782
|
-
name: context.text.slice(start, start + length),
|
|
783
1037
|
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1038
|
+
name: context.text.slice(start, start + length),
|
|
784
1039
|
fileName: context.fileName,
|
|
785
1040
|
textSpan: definitionTextSpan
|
|
786
1041
|
}
|
|
787
1042
|
]
|
|
788
1043
|
};
|
|
789
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
|
+
}
|
|
790
1062
|
|
|
791
1063
|
// src/decorate-css.ts
|
|
792
1064
|
var CSSLanguageService = class {
|
|
@@ -815,9 +1087,9 @@ var CSSLanguageService = class {
|
|
|
815
1087
|
const { text, pos } = this.#normalize(context, position);
|
|
816
1088
|
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
817
1089
|
let emmetResults;
|
|
818
|
-
const onCssProperty = () => emmetResults = (0,
|
|
1090
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper2.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
|
|
819
1091
|
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
820
|
-
|
|
1092
|
+
this.#ctx.prepareComplete(context.node);
|
|
821
1093
|
const completions = this.#ctx.cssLanguageService.doComplete(vDoc, pos, vCss);
|
|
822
1094
|
completions.items.push(...emmetResults?.items || []);
|
|
823
1095
|
return completions;
|
|
@@ -864,22 +1136,95 @@ var CSSLanguageService = class {
|
|
|
864
1136
|
this.#ctx.initElements();
|
|
865
1137
|
return this.#diagnosticsCache.get(context, void 0, () => this.#getSyntacticDiagnostics(context));
|
|
866
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
|
+
}
|
|
867
1189
|
getDefinitionAndBoundSpan(context, position) {
|
|
868
1190
|
const { text, offset } = this.#normalize(context, position);
|
|
869
|
-
const {
|
|
1191
|
+
const { vCss, customPropNodeMap } = this.#ctx.getCssDoc(text);
|
|
870
1192
|
const empty = { textSpan: { start: 0, length: 0 } };
|
|
871
|
-
const node = vCss.findChildAtOffset(context.toOffset(position), true);
|
|
1193
|
+
const node = vCss.findChildAtOffset(context.toOffset(position) + offset, true);
|
|
872
1194
|
if (!node) return empty;
|
|
873
|
-
const
|
|
874
|
-
|
|
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());
|
|
875
1207
|
if (!definitionNode) return empty;
|
|
876
|
-
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));
|
|
877
1215
|
}
|
|
878
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
|
+
}
|
|
879
1224
|
|
|
880
1225
|
// src/decorate-html.ts
|
|
881
1226
|
var import_vscode_css_languageservice3 = require("@mantou/vscode-css-languageservice");
|
|
882
|
-
var
|
|
1227
|
+
var import_vscode_emmet_helper3 = require("@mantou/vscode-emmet-helper");
|
|
883
1228
|
var HTMLLanguageService = class {
|
|
884
1229
|
#completionsCache = new LRUCache({ max: 1 });
|
|
885
1230
|
#diagnosticsCache = new LRUCache();
|
|
@@ -919,7 +1264,7 @@ var HTMLLanguageService = class {
|
|
|
919
1264
|
const css = this.#getEmbeddedCss(context, position, doc);
|
|
920
1265
|
if (!css) return [];
|
|
921
1266
|
let emmetResults;
|
|
922
|
-
const onCssProperty = () => emmetResults = (0,
|
|
1267
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
|
|
923
1268
|
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
924
1269
|
(0, import_vscode_css_languageservice3.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
|
|
925
1270
|
const completions = this.#ctx.cssLanguageService.doComplete(css.vDoc, css.position, css.style);
|
|
@@ -934,11 +1279,9 @@ var HTMLLanguageService = class {
|
|
|
934
1279
|
return this.#completionsCache.get(context, position, () => {
|
|
935
1280
|
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
936
1281
|
let emmetResults;
|
|
937
|
-
const onHtmlContent = () =>
|
|
938
|
-
(0, import_vscode_emmet_helper2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
|
|
939
|
-
emmetResults = (0, import_vscode_emmet_helper2.doComplete)(vDoc, position, "html", this.#ctx.config.emmet);
|
|
940
|
-
};
|
|
1282
|
+
const onHtmlContent = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(vDoc, position, "html", this.#ctx.config.emmet);
|
|
941
1283
|
this.#ctx.htmlLanguageService.setCompletionParticipants([{ onHtmlContent }]);
|
|
1284
|
+
this.#ctx.prepareComplete(context.node);
|
|
942
1285
|
const completions = this.#ctx.htmlLanguageService.doComplete(vDoc, position, vHtml);
|
|
943
1286
|
completions.items.push(...emmetResults?.items || []);
|
|
944
1287
|
completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
|
|
@@ -1000,7 +1343,7 @@ var HTMLLanguageService = class {
|
|
|
1000
1343
|
return diagnostics;
|
|
1001
1344
|
});
|
|
1002
1345
|
}
|
|
1003
|
-
#
|
|
1346
|
+
#getHtmlSemanticDiagnostics(context) {
|
|
1004
1347
|
const offset = context.node.getStart() + 1;
|
|
1005
1348
|
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1006
1349
|
const program = this.#ctx.getProgram();
|
|
@@ -1009,7 +1352,9 @@ var HTMLLanguageService = class {
|
|
|
1009
1352
|
const diagnostics = [];
|
|
1010
1353
|
forEachNode(vHtml.roots, (node) => {
|
|
1011
1354
|
if (!node.tag) return;
|
|
1012
|
-
|
|
1355
|
+
const customElementTagDecl = this.#ctx.elements.get(node.tag);
|
|
1356
|
+
const builtInElementTagDecl = this.#ctx.builtInElements.get(node.tag);
|
|
1357
|
+
if (isCustomElementTag(node.tag) && !customElementTagDecl) {
|
|
1013
1358
|
diagnostics.push({
|
|
1014
1359
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
1015
1360
|
code: 101 /* UnknownTag */,
|
|
@@ -1020,13 +1365,38 @@ var HTMLLanguageService = class {
|
|
|
1020
1365
|
source: NAME
|
|
1021
1366
|
});
|
|
1022
1367
|
}
|
|
1023
|
-
const tagDeclaration =
|
|
1368
|
+
const tagDeclaration = customElementTagDecl || builtInElementTagDecl;
|
|
1024
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
|
+
}
|
|
1025
1384
|
for (const [attributeName, { value, start, end }] of node.attributesMap) {
|
|
1026
1385
|
if (attributeName.startsWith("_")) continue;
|
|
1027
1386
|
const hasValueSpan = value?.startsWith("_");
|
|
1028
1387
|
const attrInfo = getAttrName(attributeName);
|
|
1029
|
-
const propType = getPropType(typeChecker, tagDeclaration, 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
|
+
});
|
|
1030
1400
|
const diagnostic = {
|
|
1031
1401
|
category: context.typescript.DiagnosticCategory.Warning,
|
|
1032
1402
|
file,
|
|
@@ -1042,6 +1412,7 @@ var HTMLLanguageService = class {
|
|
|
1042
1412
|
code: 104 /* PropSyntaxError */,
|
|
1043
1413
|
messageText: `'${attrInfo.attr}' syntax error`
|
|
1044
1414
|
});
|
|
1415
|
+
continue;
|
|
1045
1416
|
}
|
|
1046
1417
|
if (attributeName === "v-if" || attributeName === "v-else-if") {
|
|
1047
1418
|
const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
|
|
@@ -1056,8 +1427,17 @@ var HTMLLanguageService = class {
|
|
|
1056
1427
|
}
|
|
1057
1428
|
continue;
|
|
1058
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
|
+
}
|
|
1059
1438
|
if (!propType) {
|
|
1060
|
-
if (attrInfo.decorate !== "@"
|
|
1439
|
+
if (attrInfo.decorate !== "@" && // SVG 元素有很多 css 属性,所以不检查
|
|
1440
|
+
!builtInElementTagDecl?.name.getText().startsWith("SVG")) {
|
|
1061
1441
|
diagnostics.push({
|
|
1062
1442
|
...diagnostic,
|
|
1063
1443
|
code: 102 /* UnknownProp */,
|
|
@@ -1066,6 +1446,16 @@ var HTMLLanguageService = class {
|
|
|
1066
1446
|
}
|
|
1067
1447
|
continue;
|
|
1068
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
|
+
}
|
|
1069
1459
|
if (value === null) {
|
|
1070
1460
|
if (attrInfo.decorate) {
|
|
1071
1461
|
diagnostics.push({
|
|
@@ -1098,11 +1488,13 @@ var HTMLLanguageService = class {
|
|
|
1098
1488
|
diagnostics.push(diagnostic);
|
|
1099
1489
|
}
|
|
1100
1490
|
continue;
|
|
1101
|
-
default:
|
|
1102
|
-
|
|
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()))) {
|
|
1103
1494
|
diagnostics.push(diagnostic);
|
|
1104
1495
|
}
|
|
1105
1496
|
continue;
|
|
1497
|
+
}
|
|
1106
1498
|
}
|
|
1107
1499
|
} else {
|
|
1108
1500
|
const types = [typeChecker.getStringType(), typeChecker.getNumberType()];
|
|
@@ -1118,6 +1510,15 @@ var HTMLLanguageService = class {
|
|
|
1118
1510
|
code: 104 /* PropSyntaxError */,
|
|
1119
1511
|
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
|
|
1120
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
|
+
}
|
|
1121
1522
|
} else if (types.every((t) => !typeChecker.isTypeAssignableTo(t, propType))) {
|
|
1122
1523
|
diagnostics.push(diagnostic);
|
|
1123
1524
|
}
|
|
@@ -1128,20 +1529,25 @@ var HTMLLanguageService = class {
|
|
|
1128
1529
|
}
|
|
1129
1530
|
getSyntacticDiagnostics(context) {
|
|
1130
1531
|
this.#ctx.initElements();
|
|
1131
|
-
return
|
|
1532
|
+
return this.#getCssSyntacticDiagnostics(context);
|
|
1533
|
+
}
|
|
1534
|
+
getSemanticDiagnostics(context) {
|
|
1535
|
+
return this.#getHtmlSemanticDiagnostics(context);
|
|
1132
1536
|
}
|
|
1133
1537
|
getDefinitionAndBoundSpan(context, position) {
|
|
1538
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1134
1539
|
const currentOffset = context.toOffset(position);
|
|
1135
1540
|
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1136
|
-
const node = vHtml
|
|
1137
|
-
const {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
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) {
|
|
1140
1547
|
const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
|
|
1141
1548
|
const cssNode = style.findChildAtOffset(vDoc.offsetAt(pos), true);
|
|
1142
|
-
if (!cssNode) return empty;
|
|
1143
|
-
const
|
|
1144
|
-
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());
|
|
1145
1551
|
if (!definitionNode2) return empty;
|
|
1146
1552
|
return genElementDefinitionInfo(
|
|
1147
1553
|
context,
|
|
@@ -1150,28 +1556,104 @@ var HTMLLanguageService = class {
|
|
|
1150
1556
|
);
|
|
1151
1557
|
}
|
|
1152
1558
|
const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
|
|
1153
|
-
if (!definitionNode ||
|
|
1154
|
-
if (
|
|
1155
|
-
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);
|
|
1156
1562
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
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;
|
|
1159
1581
|
if (attr === "v-else" || attr === "v-else-if") {
|
|
1160
1582
|
const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
|
|
1161
1583
|
if (!ifAttr) return empty;
|
|
1162
1584
|
return genCurrentCtxDefinitionInfo(
|
|
1163
1585
|
context,
|
|
1164
|
-
{ start:
|
|
1586
|
+
{ start: attrStart + offset, length: attr.length },
|
|
1165
1587
|
{ start: ifAttr.start, length: ifAttr.end - ifAttr.start }
|
|
1166
1588
|
);
|
|
1167
1589
|
}
|
|
1168
|
-
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1169
1590
|
const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
|
|
1170
1591
|
const propDeclaration = propSymbol?.getDeclarations()?.at(0);
|
|
1171
1592
|
if (!propDeclaration) return empty;
|
|
1172
|
-
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;
|
|
1173
1639
|
}
|
|
1174
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
|
+
]);
|
|
1175
1657
|
function getSpanExpression(typescript, file, pos) {
|
|
1176
1658
|
let node = getAstNodeAtPosition(typescript, file, pos);
|
|
1177
1659
|
while (!typescript.isTemplateSpan(node)) {
|
|
@@ -1181,29 +1663,55 @@ function getSpanExpression(typescript, file, pos) {
|
|
|
1181
1663
|
return node.expression;
|
|
1182
1664
|
}
|
|
1183
1665
|
function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
|
|
1184
|
-
const valueOffset = attrNameEnd + htmlOffset +
|
|
1666
|
+
const valueOffset = attrNameEnd + htmlOffset + 4;
|
|
1185
1667
|
const spanExp = getSpanExpression(typescript, file, valueOffset);
|
|
1186
1668
|
return typeChecker.getTypeAtLocation(spanExp);
|
|
1187
1669
|
}
|
|
1188
|
-
|
|
1670
|
+
var buildInElementNoGlobalAttrPropMap = /* @__PURE__ */ new Map([
|
|
1671
|
+
["crossorigin", "crossOrigin"],
|
|
1672
|
+
["rowspan", "rowSpan"],
|
|
1673
|
+
["colspan", "colSpan"],
|
|
1674
|
+
// <input> list: string
|
|
1675
|
+
["list", "ariaLabelledby"]
|
|
1676
|
+
]);
|
|
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) {
|
|
1189
1684
|
const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
|
|
1190
1685
|
if (attrInfo.attr.startsWith("data-")) {
|
|
1191
1686
|
return typeChecker.getStringType();
|
|
1192
1687
|
}
|
|
1193
|
-
const propName = kebabToCamelCase(attrInfo.attr);
|
|
1688
|
+
const propName = globalAttrPropMap.get(attrInfo.attr) || (isBuiltInTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr));
|
|
1194
1689
|
switch (propName) {
|
|
1195
1690
|
case "class":
|
|
1196
1691
|
case "style":
|
|
1197
1692
|
case "part":
|
|
1198
1693
|
case "exportparts":
|
|
1694
|
+
case "accesskey":
|
|
1199
1695
|
case "xmlns":
|
|
1200
1696
|
case "viewBox":
|
|
1697
|
+
case "ariaLabelledby":
|
|
1201
1698
|
return typeChecker.getStringType();
|
|
1202
1699
|
case "tabindex":
|
|
1203
1700
|
return typeChecker.getNumberType();
|
|
1204
|
-
case "
|
|
1701
|
+
case "ariaAtomic":
|
|
1702
|
+
case "ariaBusy":
|
|
1205
1703
|
case "ariaChecked":
|
|
1704
|
+
case "ariaDisabled":
|
|
1705
|
+
case "ariaExpanded":
|
|
1706
|
+
case "ariaGrabbed":
|
|
1206
1707
|
case "ariaHidden":
|
|
1708
|
+
case "ariaModal":
|
|
1709
|
+
case "ariaMultiline":
|
|
1710
|
+
case "ariaMultiselectable":
|
|
1711
|
+
case "ariaReadonly":
|
|
1712
|
+
case "ariaRequired":
|
|
1713
|
+
case "ariaPressed":
|
|
1714
|
+
case "ariaSelected":
|
|
1207
1715
|
return getUnionType(typeChecker, [
|
|
1208
1716
|
typeChecker.getStringType(),
|
|
1209
1717
|
typeChecker.getBooleanType(),
|
|
@@ -1212,6 +1720,7 @@ function getPropType(typeChecker, tagClassDeclaration, attrInfo) {
|
|
|
1212
1720
|
default: {
|
|
1213
1721
|
const isEvent = attrInfo.decorate === "@";
|
|
1214
1722
|
const propSymbol = classType.getProperty(propName);
|
|
1723
|
+
if (isDeprecate(propSymbol)) reportDeprecate();
|
|
1215
1724
|
const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
|
|
1216
1725
|
if (!isEvent) return propType;
|
|
1217
1726
|
const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
|
|
@@ -1219,6 +1728,11 @@ function getPropType(typeChecker, tagClassDeclaration, attrInfo) {
|
|
|
1219
1728
|
}
|
|
1220
1729
|
}
|
|
1221
1730
|
}
|
|
1731
|
+
function isDeprecate(symbol) {
|
|
1732
|
+
if (!symbol) return false;
|
|
1733
|
+
const tags = symbol.getJsDocTags();
|
|
1734
|
+
return tags.some(({ name }) => name === "deprecated");
|
|
1735
|
+
}
|
|
1222
1736
|
function getEmitterHandleType(typeChecker, classType, propType) {
|
|
1223
1737
|
const handleSymbol = propType?.getProperty("handler");
|
|
1224
1738
|
if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
|
|
@@ -1236,6 +1750,19 @@ function getUnionType(typeChecker, types) {
|
|
|
1236
1750
|
}
|
|
1237
1751
|
return types.at(0);
|
|
1238
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
|
+
}
|
|
1239
1766
|
|
|
1240
1767
|
// src/decorate-ts.ts
|
|
1241
1768
|
function decorateLanguageService(ctx, languageService) {
|
|
@@ -1244,14 +1771,58 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1244
1771
|
languageService.getCompletionsAtPosition = (...args) => {
|
|
1245
1772
|
const program = getProgram();
|
|
1246
1773
|
const typeChecker = program.getTypeChecker();
|
|
1247
|
-
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
|
+
}
|
|
1248
1793
|
return ls.getCompletionsAtPosition(...args);
|
|
1249
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
|
+
};
|
|
1250
1801
|
languageService.getSuggestionDiagnostics = (...args) => {
|
|
1251
1802
|
const program = getProgram();
|
|
1252
1803
|
const file = program.getSourceFile(args[0]);
|
|
1253
1804
|
const result = ls.getSuggestionDiagnostics(...args);
|
|
1254
|
-
|
|
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
|
+
});
|
|
1255
1826
|
return result.filter(({ start, reportsUnnecessary, category }) => {
|
|
1256
1827
|
if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
|
|
1257
1828
|
const node = getAstNodeAtPosition(ts, file, start);
|
|
@@ -1262,52 +1833,84 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1262
1833
|
});
|
|
1263
1834
|
};
|
|
1264
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
|
+
}
|
|
1265
1859
|
const oResult = ls.findReferences(...args) || [];
|
|
1266
1860
|
const program = getProgram();
|
|
1267
1861
|
const currentNode = getAstNodeAtPosition(ts, program.getSourceFile(args[0]), args[1]);
|
|
1268
|
-
if (!currentNode) return oResult;
|
|
1269
|
-
const isIdent = ts.isIdentifier(currentNode);
|
|
1270
|
-
if (!isIdent) return oResult;
|
|
1862
|
+
if (!currentNode || !ts.isIdentifier(currentNode)) return oResult;
|
|
1271
1863
|
const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
|
|
1272
1864
|
const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
|
|
1273
1865
|
if (!currentTag) return oResult;
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
map.set(file.fileName, symbol);
|
|
1289
|
-
if (prop) {
|
|
1290
|
-
const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
|
|
1291
|
-
const kebabCaseName = camelToKebabCase(prop.text);
|
|
1292
|
-
["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
|
|
1293
|
-
for (const propName of propNames) {
|
|
1294
|
-
const info = tagInfo.node.attributesMap.get(propName);
|
|
1295
|
-
if (!info) continue;
|
|
1296
|
-
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1297
|
-
symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
|
|
1298
|
-
}
|
|
1299
|
-
} else {
|
|
1300
|
-
symbol.references.push({
|
|
1301
|
-
fileName: file.fileName,
|
|
1302
|
-
isWriteAccess: true,
|
|
1303
|
-
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
|
+
}
|
|
1304
1880
|
});
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1307
1883
|
return [...map.values(), ...oResult];
|
|
1308
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
|
+
};
|
|
1309
1913
|
languageService.getRenameInfo = (fileName, position, ...args) => {
|
|
1310
|
-
const result = ls.getRenameInfo(fileName, position, ...args);
|
|
1311
1914
|
const tagInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
1312
1915
|
if (tagInfo) {
|
|
1313
1916
|
return {
|
|
@@ -1330,7 +1933,7 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1330
1933
|
triggerSpan: tagDefinedInfo.textSpan
|
|
1331
1934
|
};
|
|
1332
1935
|
}
|
|
1333
|
-
return
|
|
1936
|
+
return ls.getRenameInfo(fileName, position, ...args);
|
|
1334
1937
|
};
|
|
1335
1938
|
languageService.findRenameLocations = (fileName, position, ...args) => {
|
|
1336
1939
|
const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
@@ -1346,31 +1949,42 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1346
1949
|
result.push({ fileName: f.fileName, textSpan: info.open });
|
|
1347
1950
|
if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
|
|
1348
1951
|
});
|
|
1952
|
+
forEachAllCssTemplateNode(ctx, tagDefinedInfo.tag, (f, textSpan) => {
|
|
1953
|
+
result.push({ fileName: f.fileName, textSpan });
|
|
1954
|
+
});
|
|
1349
1955
|
return result;
|
|
1350
1956
|
}
|
|
1351
1957
|
const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
|
|
1352
1958
|
const file = ctx.getProgram().getSourceFile(fileName);
|
|
1353
|
-
const node = getAstNodeAtPosition(
|
|
1959
|
+
const node = getAstNodeAtPosition(ts, file, position);
|
|
1354
1960
|
const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
|
|
1355
|
-
if (!tag) return oResult;
|
|
1961
|
+
if (!tag || !ts.isIdentifier(node)) return oResult;
|
|
1356
1962
|
const propText = node.getText();
|
|
1357
1963
|
const kebabCaseName = camelToKebabCase(propText);
|
|
1358
|
-
if (isPropType(
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1363
|
-
oResult.push({ fileName: f.fileName, prefixText: "@", textSpan });
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
if (isPropType(ctx.ts, node.parent, ["attribute", "numattribute", "boolattribute", "property"])) {
|
|
1367
|
-
forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
|
|
1368
|
-
const propNames = ["", ".", "?"].map((c) => `${c}${kebabCaseName}`);
|
|
1369
|
-
propNames.map((propName) => {
|
|
1370
|
-
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}`);
|
|
1371
1968
|
if (!info) return;
|
|
1372
1969
|
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
1373
|
-
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
|
+
});
|
|
1374
1988
|
});
|
|
1375
1989
|
});
|
|
1376
1990
|
}
|
|
@@ -1378,15 +1992,53 @@ function decorateLanguageService(ctx, languageService) {
|
|
|
1378
1992
|
};
|
|
1379
1993
|
return languageService;
|
|
1380
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
|
+
}
|
|
1381
2026
|
function forEachAllHtmlTemplateNode(ctx, tag, fn) {
|
|
1382
2027
|
for (const file of ctx.getProgram().getSourceFiles()) {
|
|
1383
2028
|
if (file.fileName.endsWith(".d.ts")) continue;
|
|
1384
2029
|
for (const templateContext of ctx.htmlSourceHelper.getAllTemplates(file.fileName)) {
|
|
1385
|
-
const {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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 }));
|
|
1390
2042
|
}
|
|
1391
2043
|
}
|
|
1392
2044
|
}
|
|
@@ -1398,20 +2050,97 @@ function findCurrentTagInfo(ctx, fileName, position) {
|
|
|
1398
2050
|
const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
|
|
1399
2051
|
const offset = templateContext.toOffset(relativePosition);
|
|
1400
2052
|
const node = vHtml.findNodeAt(offset);
|
|
1401
|
-
|
|
1402
|
-
const onTag = offset < node.startTagEnd && text === node.tag;
|
|
1403
|
-
if (!onTag || !node.tag) return;
|
|
1404
|
-
return getTagInfo(node, htmlOffset);
|
|
2053
|
+
if (node.tag && offset < node.start + 1 + node.tag.length) return getTagInfo(node, htmlOffset);
|
|
1405
2054
|
}
|
|
1406
2055
|
function findDefinedTagInfo(ctx, fileName, position) {
|
|
1407
2056
|
const file = ctx.getProgram().getSourceFile(fileName);
|
|
1408
2057
|
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
1409
|
-
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) {
|
|
1410
2059
|
return;
|
|
1411
2060
|
}
|
|
1412
2061
|
const tag = node.text;
|
|
1413
2062
|
return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
|
|
1414
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
|
+
}
|
|
1415
2144
|
function isPropType(typescript, node, types) {
|
|
1416
2145
|
if (!typescript.isPropertyDeclaration(node)) return;
|
|
1417
2146
|
for (const modifier of node.modifiers || []) {
|
|
@@ -1422,21 +2151,16 @@ function isPropType(typescript, node, types) {
|
|
|
1422
2151
|
}
|
|
1423
2152
|
}
|
|
1424
2153
|
}
|
|
1425
|
-
function decorateTypeChecker(
|
|
2154
|
+
function decorateTypeChecker(typeChecker) {
|
|
2155
|
+
const neverType = typeChecker.getNeverType();
|
|
1426
2156
|
const internal = typeChecker;
|
|
1427
2157
|
const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
|
|
1428
2158
|
internal.isValidPropertyAccessForCompletions = (...args) => {
|
|
1429
2159
|
const result = checker.isValidPropertyAccessForCompletions(...args);
|
|
1430
|
-
if (!result) return false;
|
|
1431
2160
|
try {
|
|
1432
|
-
|
|
1433
|
-
if (!declarations) return true;
|
|
1434
|
-
const isNever = declarations.every(
|
|
1435
|
-
(node) => ctx.ts.isPropertySignature(node) && node.type?.getText() === "never"
|
|
1436
|
-
);
|
|
1437
|
-
return !isNever;
|
|
2161
|
+
return result && typeChecker.getTypeOfSymbol(args.at(2)) !== neverType;
|
|
1438
2162
|
} catch {
|
|
1439
|
-
return
|
|
2163
|
+
return result;
|
|
1440
2164
|
}
|
|
1441
2165
|
};
|
|
1442
2166
|
return typeChecker;
|