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.
Files changed (2) hide show
  1. package/dist/index.js +888 -185
  2. 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 <= pos && child.end > pos ? child : void 0);
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
- var BEFORE_REG = /[^\s</>]+$/;
294
- var AFTER_REG = /^[^\s</>]+/;
295
- function getHTMLTextAtPosition(text, offset) {
296
- const before = text.slice(0, offset).match(BEFORE_REG)?.at(0) || "";
297
- const after = text.slice(offset).match(AFTER_REG)?.at(0) || "";
298
- const str = before + after;
299
- return {
300
- before,
301
- after,
302
- text: str,
303
- start: offset - before.length,
304
- length: str.length
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
- #elements;
347
- #getProgram;
348
- constructor(typescript, elements, getProgram) {
349
- this.#ts = typescript;
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
- if (!node) return result;
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 (type && getUnionValues(type)) {
398
- result.push({ name: e.name, description });
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
- const oResult = dataProvider.provideAttributes(isCustomElementTag(tag) ? "div" : tag);
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 typeChecker = this.#getProgram().getTypeChecker();
417
- const node = this.#elements.get(tag);
418
- if (!node) return [];
419
- const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
420
- const result = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
421
- return result?.map((name) => ({ name })) || [];
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: [dataProvider, new HTMLDataProvider(typescript, this.elements, this.getProgram)]
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
- return { vDoc, vCss };
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
- vHtml.roots.forEach(function transform(e, index, arr) {
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.children.forEach(transform);
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
- for (const modifier of node.modifiers || []) {
514
- if (this.ts.isDecorator(modifier) && this.ts.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === "customElement") {
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(program)) return;
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
- SVGAElement: [],
560
- HTMLAnchorElement: ["a"],
561
- SVGImageElement: [],
562
- HTMLImageElement: ["img"],
563
- SVGStyleElement: [],
564
- HTMLStyleElement: ["style"],
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 import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
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: propDeclaration.getText().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, import_vscode_emmet_helper.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
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
- (0, import_vscode_css_languageservice2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
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 { vDoc, vCss } = this.#ctx.getCssDoc(text);
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 ident = vDoc.getText().slice(node.offset, node.end);
875
- const definitionNode = this.#ctx.elements.get(ident);
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, { start: node.offset - offset, length: node.length }, definitionNode);
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 import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
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, import_vscode_emmet_helper2.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
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
- #getHtmlSyntacticDiagnostics(context) {
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 || this.#ctx.builtInElements.get(node.tag);
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
- if (!typeChecker.isTypeAssignableTo(spanType, propType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
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 [...this.#getCssSyntacticDiagnostics(context), ...this.#getHtmlSyntacticDiagnostics(context)];
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.findNodeAt(currentOffset);
1139
- const { text, start, length, before } = getHTMLTextAtPosition(context.text, currentOffset);
1140
- const empty = { textSpan: { start, length } };
1141
- if (node.tag === "style" && currentOffset > node.startTagEnd) {
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 ident = vDoc.getText().slice(cssNode.offset, cssNode.end);
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 || currentOffset > node.startTagEnd || !text) return empty;
1156
- if (text === node.tag) {
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
- const { attr, offset } = getAttrName(text);
1160
- if (before.length > attr.length) return empty;
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: start + offset, length: attr.length },
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: start + offset, length: attr.length }, propDeclaration);
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 + 3;
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
- function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo) {
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(ctx, typeChecker));
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
- ctx.updateElement(file);
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
- const map = /* @__PURE__ */ new Map();
1296
- forEachAllHtmlTemplateNode(ctx, currentTag, (file, tagInfo) => {
1297
- const symbol = map.get(file.fileName) || {
1298
- references: [],
1299
- definition: {
1300
- containerKind: ctx.ts.ScriptElementKind.unknown,
1301
- containerName: "",
1302
- displayParts: [],
1303
- fileName: file.fileName,
1304
- textSpan: { start: 0, length: 0 },
1305
- name: "test",
1306
- kind: ctx.ts.ScriptElementKind.unknown
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 result;
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(ctx.ts, file, position);
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(ctx.ts, node.parent, ["emitter", "globalemitter"])) {
1380
- forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1381
- const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
1382
- if (!info) return;
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: ".", textSpan });
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 { vHtml } = ctx.getHtmlDoc(templateContext.text);
1407
- forEachNode(vHtml.roots, (node) => {
1408
- if (node.tag !== tag) return;
1409
- fn(file, getTagInfo(node, templateContext.node.getStart() + 1));
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
- const { text } = getHTMLTextAtPosition(templateContext.text, offset);
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() !== "customElement") {
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(ctx, typeChecker) {
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
- const { declarations } = args.at(2);
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 true;
2163
+ return result;
1461
2164
  }
1462
2165
  };
1463
2166
  return typeChecker;