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.
Files changed (2) hide show
  1. package/dist/index.js +912 -188
  2. 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
- return node.getSourceFile().fileName.includes("/node_modules/");
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 <= pos && child.end > pos ? child : void 0);
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
- var BEFORE_REG = /[^\s</>]+$/;
293
- var AFTER_REG = /^[^\s</>]+/;
294
- function getHTMLTextAtPosition(text, offset) {
295
- const before = text.slice(0, offset).match(BEFORE_REG)?.at(0) || "";
296
- const after = text.slice(offset).match(AFTER_REG)?.at(0) || "";
297
- const str = before + after;
298
- return {
299
- before,
300
- after,
301
- text: str,
302
- start: offset - before.length,
303
- length: str.length
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
- #elements;
346
- #getProgram;
347
- constructor(typescript, elements, getProgram) {
348
- this.#ts = typescript;
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
- if (!node) return result;
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 (type && getUnionValues(type)) {
397
- result.push({ name: e.name, description });
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
- const oResult = dataProvider.provideAttributes(isCustomElementTag(tag) ? "div" : tag);
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 typeChecker = this.#getProgram().getTypeChecker();
416
- const node = this.#elements.get(tag);
417
- if (!node) return [];
418
- const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
419
- const result = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
420
- 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;
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: [dataProvider, new HTMLDataProvider(typescript, this.elements, this.getProgram)]
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
- 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 };
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
- 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) {
503
648
  e.prev = arr[index - 1];
504
649
  e.next = arr[index + 1];
505
- 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);
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
- for (const modifier of node.modifiers || []) {
513
- if (this.ts.isDecorator(modifier) && this.ts.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === "customElement") {
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(program)) return;
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
- SVGAElement: [],
559
- HTMLAnchorElement: ["a"],
560
- SVGImageElement: [],
561
- HTMLImageElement: ["img"],
562
- SVGStyleElement: [],
563
- 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"],
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 import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
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: propDeclaration.getText().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, 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);
819
1091
  this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
820
- (0, import_vscode_css_languageservice2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
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 { vDoc, vCss } = this.#ctx.getCssDoc(text);
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 ident = vDoc.getText().slice(node.offset, node.end);
874
- 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());
875
1207
  if (!definitionNode) return empty;
876
- 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));
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 import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
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, 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);
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
- #getHtmlSyntacticDiagnostics(context) {
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
- if (isCustomElementTag(node.tag) && !this.#ctx.elements.get(node.tag)) {
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 = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
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
- 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()))) {
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 [...this.#getCssSyntacticDiagnostics(context), ...this.#getHtmlSyntacticDiagnostics(context)];
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.findNodeAt(currentOffset);
1137
- const { text, start, length, before } = getHTMLTextAtPosition(context.text, currentOffset);
1138
- const empty = { textSpan: { start, length } };
1139
- 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) {
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 ident = vDoc.getText().slice(cssNode.offset, cssNode.end);
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 || currentOffset > node.startTagEnd || !text) return empty;
1154
- if (text === node.tag) {
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
- const { attr, offset } = getAttrName(text);
1158
- 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;
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: start + offset, length: attr.length },
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: 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;
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 + 3;
1666
+ const valueOffset = attrNameEnd + htmlOffset + 4;
1185
1667
  const spanExp = getSpanExpression(typescript, file, valueOffset);
1186
1668
  return typeChecker.getTypeAtLocation(spanExp);
1187
1669
  }
1188
- function getPropType(typeChecker, tagClassDeclaration, attrInfo) {
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 "ariaDisabled":
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(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
+ }
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
- 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
+ });
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
- const map = /* @__PURE__ */ new Map();
1275
- forEachAllHtmlTemplateNode(ctx, currentTag, (file, tagInfo) => {
1276
- const symbol = map.get(file.fileName) || {
1277
- references: [],
1278
- definition: {
1279
- containerKind: ctx.ts.ScriptElementKind.unknown,
1280
- containerName: "",
1281
- displayParts: [],
1282
- fileName: file.fileName,
1283
- textSpan: { start: 0, length: 0 },
1284
- name: "test",
1285
- kind: ctx.ts.ScriptElementKind.unknown
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 result;
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(ctx.ts, file, position);
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(ctx.ts, node.parent, ["emitter", "globalemitter"])) {
1359
- forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1360
- const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
1361
- if (!info) return;
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: ".", 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
+ });
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 { vHtml } = ctx.getHtmlDoc(templateContext.text);
1386
- forEachNode(vHtml.roots, (node) => {
1387
- if (node.tag !== tag) return;
1388
- fn(file, getTagInfo(node, templateContext.node.getStart() + 1));
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
- const { text } = getHTMLTextAtPosition(templateContext.text, offset);
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() !== "customElement") {
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(ctx, typeChecker) {
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
- const { declarations } = args.at(2);
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 true;
2163
+ return result;
1440
2164
  }
1441
2165
  };
1442
2166
  return typeChecker;