ts-gem-plugin 0.0.5 → 0.0.6

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 +1038 -519
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -25,19 +25,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  // src/index.ts
26
26
  var import_typescript_template_language_service_decorator = require("@mantou/typescript-template-language-service-decorator");
27
27
 
28
- // src/decorate-html.ts
29
- var import_vscode_html_languageservice = require("vscode-html-languageservice");
30
- var import_emmet_helper = require("@vscode/emmet-helper");
31
- var import_vscode_css_languageservice = require("vscode-css-languageservice");
32
-
33
- // ../duoyun-ui/lib/types.js
34
- function isNullish(v) {
35
- return v === null || v === void 0;
36
- }
37
- function isNotNullish(v) {
38
- return !isNullish(v);
39
- }
40
-
41
28
  // ../gem/lib/utils.js
42
29
  var { assign, fromEntries, entries, keys } = Object;
43
30
  var LinkedList = class extends EventTarget {
@@ -86,8 +73,10 @@ var LinkedList = class extends EventTarget {
86
73
  find(value) {
87
74
  return this.#map.get(value);
88
75
  }
89
- // 添加到尾部,已存在时会删除老的项目
90
- // 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
76
+ /**
77
+ * 添加到尾部,已存在时会删除老的项目
78
+ * 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
79
+ */
91
80
  add(value) {
92
81
  if (!this.#lastItem) {
93
82
  this.dispatchEvent(new CustomEvent("start"));
@@ -104,7 +93,7 @@ var LinkedList = class extends EventTarget {
104
93
  }
105
94
  this.#map.set(value, item);
106
95
  }
107
- // 删除这个元素后没有其他元素时立即出发 end 事件
96
+ /** 删除这个元素后没有其他元素时立即出发 end 事件 */
108
97
  delete(value) {
109
98
  const deleteItem = this.#delete(value);
110
99
  if (!this.#firstItem) {
@@ -112,8 +101,7 @@ var LinkedList = class extends EventTarget {
112
101
  }
113
102
  return deleteItem;
114
103
  }
115
- // 获取头部元素
116
- // 会从链表删除
104
+ /** 获取头部元素,会从链表删除 */
117
105
  get() {
118
106
  const firstItem = this.#firstItem;
119
107
  if (!firstItem)
@@ -122,91 +110,152 @@ var LinkedList = class extends EventTarget {
122
110
  return firstItem.value;
123
111
  }
124
112
  };
113
+ function camelToKebabCase(str) {
114
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
115
+ }
125
116
  function kebabToCamelCase(str) {
126
117
  return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
127
118
  }
128
119
 
120
+ // src/configuration.ts
121
+ var defaultConfiguration = {
122
+ emmet: {},
123
+ elementDefineRules: {
124
+ "Duoyun*Element": "dy-*",
125
+ "*Element": "*"
126
+ }
127
+ };
128
+ var Rules = class {
129
+ #map = /* @__PURE__ */ new Map();
130
+ constructor(data) {
131
+ Object.entries(data).forEach(([classNamePattern, tagPattern]) => {
132
+ this.#map.set(new RegExp(classNamePattern.replace("*", "(.*)")), tagPattern.replace("*", "$1"));
133
+ });
134
+ }
135
+ findTag(className) {
136
+ for (const [reg, replaceStr] of this.#map) {
137
+ if (reg.exec(className)) {
138
+ return camelToKebabCase(className.replace(reg, replaceStr));
139
+ }
140
+ }
141
+ }
142
+ };
143
+ var Configuration = class {
144
+ #emmet = defaultConfiguration.emmet;
145
+ #elementDefineRules = new Rules(defaultConfiguration.elementDefineRules);
146
+ update(config) {
147
+ this.#emmet = config.emmet || defaultConfiguration.emmet;
148
+ this.#elementDefineRules = new Rules({
149
+ ...defaultConfiguration.elementDefineRules,
150
+ ...config.elementDefineRules
151
+ });
152
+ }
153
+ get emmet() {
154
+ return this.#emmet;
155
+ }
156
+ get elementDefineRules() {
157
+ return this.#elementDefineRules;
158
+ }
159
+ };
160
+
161
+ // src/context.ts
162
+ var import_vscode_html_languageservice2 = require("@mantou/vscode-html-languageservice");
163
+ var import_standard_template_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-template-source-helper"));
164
+ var import_standard_script_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-script-source-helper"));
165
+ var import_vscode_css_languageservice = require("@mantou/vscode-css-languageservice");
166
+
167
+ // ../duoyun-ui/lib/map.js
168
+ var StringWeakMap = class {
169
+ #map = /* @__PURE__ */ new Map();
170
+ #weakMap = /* @__PURE__ */ new WeakMap();
171
+ #registry = new FinalizationRegistry((key) => this.#map.delete(key));
172
+ set(key, val) {
173
+ this.#map.set(key, new WeakRef(val));
174
+ this.#weakMap.set(val, key);
175
+ this.#registry.register(val, key);
176
+ }
177
+ get(key) {
178
+ return this.#map.get(key)?.deref();
179
+ }
180
+ findKey(val) {
181
+ return this.#weakMap.get(val);
182
+ }
183
+ *[Symbol.iterator]() {
184
+ const entries2 = this.#map.entries();
185
+ for (const [tag, ref] of entries2) {
186
+ yield [tag, ref.deref()];
187
+ }
188
+ }
189
+ };
190
+
129
191
  // src/utils.ts
130
- var vscode = __toESM(require("vscode-languageserver-types"));
131
- var import_vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
132
- function isCustomElement(tag) {
192
+ function isCustomElementTag(tag) {
133
193
  return tag.includes("-");
134
194
  }
135
195
  function isDepElement(node) {
136
196
  return node.getSourceFile().fileName.includes("/node_modules/");
137
197
  }
138
- var openTagReg = /(?<prefix><)(?<tag>\w+-\w+)\s+/g;
139
- function forEachTag(typescript, containerNode, fn) {
140
- const list = [containerNode];
198
+ function bindMemberFunction(o, keys2 = Object.keys(o)) {
199
+ return Object.fromEntries(keys2.map((key) => [key, o[key].bind?.(o)]));
200
+ }
201
+ function forEachNode(roots, fn) {
202
+ const list = [...roots];
141
203
  while (true) {
142
204
  const currentNode = list.pop();
143
205
  if (!currentNode) return;
144
- typescript.forEachChild(currentNode, (node) => {
145
- list.push(node);
146
- if (typescript.isTemplateHead(node) || typescript.isTemplateMiddle(node) || typescript.isTemplateTail(node) || typescript.isNoSubstitutionTemplateLiteral(node)) {
147
- [...node.text.matchAll(openTagReg)].forEach((e) => {
148
- const { prefix = "", tag } = e.groups;
149
- const start = node.getStart() + 1 + prefix.length + e.index;
150
- fn({ tag, length: tag.length, start });
151
- });
152
- }
153
- });
206
+ fn(currentNode);
207
+ list.push(...currentNode.children);
208
+ }
209
+ }
210
+ function getAstNodeAtPosition(typescript, node, pos) {
211
+ if (node.pos > pos || node.end <= pos) return;
212
+ while (node.kind >= typescript.SyntaxKind.FirstNode) {
213
+ const nested = typescript.forEachChild(node, (child) => child.pos <= pos && child.end > pos ? child : void 0);
214
+ if (nested === void 0) break;
215
+ node = nested;
154
216
  }
217
+ return node;
155
218
  }
219
+ var BEFORE_REG = /[^\s</>]+$/;
220
+ var AFTER_REG = /^[^\s</>]+/;
156
221
  function getHTMLTextAtPosition(text, offset) {
157
- const before = text.slice(0, offset).match(/[^ \n<>]+$/)?.at(0) || "";
158
- const after = text.slice(offset).match(/^[^ \n<>]+/)?.at(0) || "";
222
+ const before = text.slice(0, offset).match(BEFORE_REG)?.at(0) || "";
223
+ const after = text.slice(offset).match(AFTER_REG)?.at(0) || "";
159
224
  const str = before + after;
160
225
  return {
226
+ before,
227
+ after,
161
228
  text: str,
162
229
  start: offset - before.length,
163
230
  length: str.length
164
231
  };
165
232
  }
166
- var attrMap = {
167
- ".": "property",
168
- "?": "boolean",
169
- "@": "event"
170
- };
171
233
  function getAttrName(text) {
172
234
  const attr = text.split("=").at(0);
173
- const char = attr.at(0);
174
- if (char in attrMap) {
175
- return { attr: attr.slice(1), type: attrMap[char] };
176
- }
177
- return { attr };
178
- }
179
- function getCustomElementTag(typescript, node, isDep = isDepElement(node)) {
180
- if (!typescript.isClassDeclaration(node)) return;
181
- for (const modifier of node.modifiers || []) {
182
- if (typescript.isDecorator(modifier) && typescript.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === "customElement") {
183
- const arg = modifier.expression.arguments.at(0);
184
- if (arg && typescript.isStringLiteral(arg)) {
185
- return arg.text;
186
- }
187
- }
188
- }
189
- if (isDep && node.name && typescript.isIdentifier(node.name)) {
190
- const name = node.name.text;
191
- if (name.endsWith("Element")) {
192
- return name;
193
- }
194
- }
235
+ const isNotLetter = hasDecoratorAttr(attr);
236
+ const offset = isNotLetter ? 1 : 0;
237
+ return {
238
+ attr: attr.slice(offset),
239
+ offset,
240
+ decorate: isNotLetter ? attr.at(0) : ""
241
+ };
195
242
  }
196
- function getDocComment(typescript, declaration) {
197
- const fullText = declaration.getSourceFile().getFullText();
198
- const commentRanges = typescript.getLeadingCommentRanges(fullText, declaration.getFullStart());
199
- const commentStrings = commentRanges?.filter(({ kind }) => kind === typescript.SyntaxKind.MultiLineCommentTrivia).map(({ pos, end }) => fullText.slice(pos, end));
200
- return commentStrings?.join("\n");
243
+ function hasDecoratorAttr(str) {
244
+ return str.charCodeAt(0) < 65;
201
245
  }
202
- function getAstNodeAtPosition(typescript, node, pos) {
203
- if (node.pos > pos || node.end <= pos) return;
204
- while (node.kind >= typescript.SyntaxKind.FirstNode) {
205
- const nested = typescript.forEachChild(node, (child) => child.pos <= pos && child.end > pos ? child : void 0);
206
- if (nested === void 0) break;
207
- node = nested;
208
- }
209
- return node;
246
+ function getTagInfo(node, offset) {
247
+ const tag = node.tag;
248
+ const openStart = node.start + 1 + offset;
249
+ return {
250
+ node,
251
+ tag,
252
+ offset,
253
+ open: { start: openStart, length: tag.length },
254
+ end: node.endTagStart && {
255
+ start: node.endTagStart + 2 + offset,
256
+ length: node.end - node.endTagStart - 3
257
+ }
258
+ };
210
259
  }
211
260
  var marker = Symbol();
212
261
  function decorate(origin, cb) {
@@ -215,8 +264,317 @@ function decorate(origin, cb) {
215
264
  result[marker] = true;
216
265
  return result;
217
266
  }
267
+
268
+ // src/data-provider.ts
269
+ var import_vscode_html_languageservice = require("@mantou/vscode-html-languageservice");
270
+
271
+ // src/constants.ts
272
+ var NAME = "gem-plugin";
273
+
274
+ // src/data-provider.ts
275
+ var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
276
+ var HTMLDataProvider = class {
277
+ #ts;
278
+ #elements;
279
+ #getProgram;
280
+ constructor(typescript, elements, getProgram) {
281
+ this.#ts = typescript;
282
+ this.#elements = elements;
283
+ this.#getProgram = getProgram;
284
+ }
285
+ getId() {
286
+ return NAME;
287
+ }
288
+ isApplicable() {
289
+ return true;
290
+ }
291
+ provideTags() {
292
+ return [...this.#elements].map(([tag, node]) => ({
293
+ name: tag,
294
+ attributes: [],
295
+ description: getDocComment(this.#ts, node)
296
+ }));
297
+ }
298
+ provideAttributes(tag) {
299
+ const ts = this.#ts;
300
+ const typeChecker = this.#getProgram().getTypeChecker();
301
+ const node = this.#elements.get(tag);
302
+ const result = [
303
+ { name: "v-if", description: "Similar to vue `v-if`" },
304
+ { name: "v-else-if", description: "Similar to vue `v-else-if`" },
305
+ { name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
306
+ ];
307
+ if (!node) return result;
308
+ const isDep = isDepElement(node);
309
+ const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
310
+ props.forEach((e) => {
311
+ const declaration = e.getDeclarations()?.at(0);
312
+ const prop = declaration && ts.isPropertyDeclaration(declaration);
313
+ if (!prop) return;
314
+ const hasPropDecorator = declaration.modifiers?.some((m) => ts.isDecorator(m) && ts.isIdentifier(m.expression));
315
+ if (!hasPropDecorator && !isDep) return;
316
+ const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
317
+ const typeText = declaration.type?.getText();
318
+ const description = getDocComment(ts, declaration);
319
+ switch (type) {
320
+ case typeChecker.getStringType():
321
+ case typeChecker.getNumberType():
322
+ result.push({ name: e.name, description });
323
+ break;
324
+ case typeChecker.getBooleanType():
325
+ result.push({ name: e.name, description, valueSet: "v" });
326
+ result.push({ name: `?${e.name}`, description });
327
+ break;
328
+ }
329
+ if (type && getUnionValues(type)) {
330
+ result.push({ name: e.name, description });
331
+ }
332
+ if (typeText?.startsWith("Emitter")) {
333
+ result.push({ name: `@${e.name}`, description });
334
+ } else {
335
+ result.push({ name: `.${e.name}`, description });
336
+ }
337
+ });
338
+ const oResult = dataProvider.provideAttributes(isCustomElementTag(tag) ? "div" : tag);
339
+ oResult.forEach((data) => {
340
+ const tryEvtName = data.name.replace(/^on/, "@");
341
+ if (tryEvtName !== data.name) {
342
+ result.push({ ...data, name: tryEvtName });
343
+ }
344
+ });
345
+ return result;
346
+ }
347
+ provideValues(tag, attr) {
348
+ const typeChecker = this.#getProgram().getTypeChecker();
349
+ const node = this.#elements.get(tag);
350
+ if (!node) return [];
351
+ const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
352
+ const result = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
353
+ return result?.map((name) => ({ name })) || [];
354
+ }
355
+ };
356
+ function getUnionValues(type) {
357
+ if (!type.isUnion()) return;
358
+ const result = [];
359
+ type.types.forEach((e) => {
360
+ if (!e.isLiteral()) return;
361
+ result.push(String(e.value));
362
+ });
363
+ return result;
364
+ }
365
+ var COMMENT_LINE_CONTENT = /^(\/?[ *\t]*)?(?<str>.*?)(\**\/)?$/gm;
366
+ function getDocComment(typescript, declaration) {
367
+ const fullText = declaration.getSourceFile().getFullText();
368
+ const commentRanges = typescript.getLeadingCommentRanges(fullText, declaration.getFullStart());
369
+ const commentStrings = commentRanges?.filter(({ kind }) => kind === typescript.SyntaxKind.MultiLineCommentTrivia).map(({ pos, end }) => {
370
+ const fullComment = [...fullText.slice(pos, end).matchAll(COMMENT_LINE_CONTENT)];
371
+ return fullComment.map((m) => m.groups.str).join("\n");
372
+ });
373
+ return commentStrings?.join("\n\n");
374
+ }
375
+
376
+ // ../duoyun-ui/lib/cache.js
377
+ var Cache = class {
378
+ #max;
379
+ #maxAge;
380
+ #renewal;
381
+ #map = /* @__PURE__ */ new Map();
382
+ #reverseMap = /* @__PURE__ */ new Map();
383
+ #addedLinked = new LinkedList();
384
+ constructor({ max = Infinity, maxAge = Infinity, renewal = false } = {}) {
385
+ this.#max = max;
386
+ this.#maxAge = maxAge;
387
+ this.#renewal = renewal;
388
+ }
389
+ setOptions(options) {
390
+ this.#max = options.max ?? this.#max;
391
+ this.#maxAge = options.maxAge ?? this.#maxAge;
392
+ this.#renewal = options.renewal ?? this.#renewal;
393
+ }
394
+ #trim() {
395
+ for (let i = this.#addedLinked.size - this.#max; i > 0; i--) {
396
+ const value = this.#addedLinked.get();
397
+ const key = this.#reverseMap.get(value);
398
+ this.#reverseMap.delete(value);
399
+ this.#map.delete(key);
400
+ }
401
+ }
402
+ set(key, value) {
403
+ this.#addedLinked.add(value);
404
+ this.#reverseMap.set(value, key);
405
+ this.#map.set(key, { value, timestamp: Date.now() });
406
+ this.#trim();
407
+ return value;
408
+ }
409
+ get(key, init) {
410
+ const cache = this.#map.get(key);
411
+ if (!cache) {
412
+ return init && this.set(key, init(key));
413
+ }
414
+ const { timestamp, value } = cache;
415
+ if (Date.now() - timestamp > this.#maxAge) {
416
+ this.#addedLinked.delete(value);
417
+ this.#reverseMap.delete(value);
418
+ this.#map.delete(key);
419
+ return init && this.set(key, init(key));
420
+ }
421
+ if (this.#renewal) {
422
+ cache.timestamp = Date.now();
423
+ }
424
+ this.#addedLinked.add(value);
425
+ return value;
426
+ }
427
+ };
428
+
429
+ // src/cache.ts
430
+ var LRUCache = class {
431
+ #bucket;
432
+ constructor(args) {
433
+ this.#bucket = new Cache({ max: 25, renewal: true, ...args });
434
+ }
435
+ #genKey(context, position) {
436
+ return [context.fileName, position?.line, position?.character, context.text].join(";");
437
+ }
438
+ get(context, position, init) {
439
+ return this.#bucket.get(this.#genKey(context, position), init);
440
+ }
441
+ };
442
+
443
+ // src/context.ts
444
+ var Context = class {
445
+ elements;
446
+ builtInElements;
447
+ ts;
448
+ config;
449
+ project;
450
+ logger;
451
+ dataProvider;
452
+ cssLanguageService;
453
+ htmlLanguageService;
454
+ htmlSourceHelper;
455
+ htmlTemplateStringSettings;
456
+ cssTemplateStringSettings;
457
+ getProgram;
458
+ constructor(typescript, config, info, logger) {
459
+ this.ts = typescript;
460
+ this.config = config;
461
+ this.getProgram = () => info.languageService.getProgram();
462
+ this.project = info.project;
463
+ this.logger = logger;
464
+ this.dataProvider = dataProvider;
465
+ this.elements = new StringWeakMap();
466
+ this.builtInElements = new StringWeakMap();
467
+ this.cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({});
468
+ this.htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)({
469
+ customDataProviders: [dataProvider, new HTMLDataProvider(typescript, this.elements, this.getProgram)]
470
+ });
471
+ this.htmlTemplateStringSettings = {
472
+ tags: ["html", "raw", "h"],
473
+ enableForStringWithSubstitutions: true,
474
+ getSubstitution
475
+ };
476
+ this.cssTemplateStringSettings = {
477
+ tags: ["styled", "css"],
478
+ enableForStringWithSubstitutions: true,
479
+ getSubstitution,
480
+ isValidTemplate: (node) => isValidCSSTemplate(typescript, node, "css")
481
+ };
482
+ this.htmlSourceHelper = new import_standard_template_source_helper.default(
483
+ typescript,
484
+ this.htmlTemplateStringSettings,
485
+ new import_standard_script_source_helper.default(typescript, info.project),
486
+ logger
487
+ );
488
+ }
489
+ #virtualHtmlCache = new LRUCache({ max: 1e3 });
490
+ #virtualCssCache = new LRUCache({ max: 1e3 });
491
+ getCssDoc(text) {
492
+ return this.#virtualCssCache.get({ text, fileName: "" }, void 0, () => {
493
+ const vDoc = createVirtualDocument("css", text);
494
+ const vCss = this.cssLanguageService.parseStylesheet(vDoc);
495
+ return { vDoc, vCss };
496
+ });
497
+ }
498
+ getHtmlDoc(text) {
499
+ return this.#virtualHtmlCache.get({ text, fileName: "" }, void 0, () => {
500
+ const vDoc = createVirtualDocument("html", text);
501
+ const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
502
+ vHtml.roots.forEach(function transform(e, index, arr) {
503
+ e.prev = arr[index - 1];
504
+ e.next = arr[index + 1];
505
+ e.children.forEach(transform);
506
+ });
507
+ return { vDoc, vHtml };
508
+ });
509
+ }
510
+ getTagFromNode(node, supportClassName = isDepElement(node)) {
511
+ 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
+ }
520
+ if (supportClassName && node.name && this.ts.isIdentifier(node.name)) {
521
+ return this.config.elementDefineRules.findTag(node.name.text);
522
+ }
523
+ }
524
+ updateElement(file) {
525
+ const isDep = isDepElement(file);
526
+ this.ts.forEachChild(file, (node) => {
527
+ const tag = this.getTagFromNode(node, isDep);
528
+ if (tag && this.ts.isClassDeclaration(node)) {
529
+ this.elements.set(tag, node);
530
+ }
531
+ });
532
+ }
533
+ #initElementsCache = /* @__PURE__ */ new WeakSet();
534
+ /**
535
+ * 当 project 准备好了执行
536
+ */
537
+ initElements() {
538
+ const program = this.getProgram();
539
+ if (this.#initElementsCache.has(program)) return;
540
+ const files = program.getSourceFiles();
541
+ files.forEach((file) => this.updateElement(file));
542
+ const typeChecker = program.getTypeChecker();
543
+ const symbols = typeChecker.getSymbolsInScope(files.at(0), this.ts.SymbolFlags.Interface);
544
+ symbols.forEach((symbol) => {
545
+ const name = symbol.escapedName.toString();
546
+ const match = name.match(/^(SVG|HTML)(\w*)Element$/);
547
+ const declaration = symbol.declarations?.find((e) => this.ts.isInterfaceDeclaration(e));
548
+ if (!match || !declaration) return;
549
+ if (name in partialBuiltInElementMap) {
550
+ partialBuiltInElementMap[name].forEach((e) => this.builtInElements.set(e, declaration));
551
+ } else {
552
+ this.builtInElements.set(match[2].toLowerCase(), declaration);
553
+ }
554
+ });
555
+ }
556
+ };
557
+ var partialBuiltInElementMap = {
558
+ SVGAElement: [],
559
+ HTMLAnchorElement: ["a"],
560
+ SVGImageElement: [],
561
+ HTMLImageElement: ["img"],
562
+ SVGStyleElement: [],
563
+ HTMLStyleElement: ["style"],
564
+ HTMLDListElement: ["dl"],
565
+ HTMLOListElement: ["ol"],
566
+ HTMLUListElement: ["ul"],
567
+ HTMLHeadingElement: ["h1", "h2", "h3", "h4", "h5", "h6"],
568
+ HTMLModElement: ["del", "ins"],
569
+ HTMLQuoteElement: ["blockquote", "q", "cite"],
570
+ HTMLTableCaptionElement: ["caption"],
571
+ HTMLTableCellElement: ["th", "td"],
572
+ HTMLTableColElement: ["col"],
573
+ HTMLTableRowElement: ["tr"],
574
+ HTMLTableSectionElement: ["thead", "tfoot", "tbody"]
575
+ };
218
576
  function createVirtualDocument(languageId, content) {
219
- return import_vscode_languageserver_textdocument.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
577
+ return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
220
578
  }
221
579
  function getSubstitution(templateString, start, end) {
222
580
  return templateString.slice(start, end).replaceAll(/[^\n]/g, "_");
@@ -240,6 +598,13 @@ function isValidCSSTemplate(typescript, node, callName) {
240
598
  return false;
241
599
  }
242
600
  }
601
+
602
+ // src/decorate-css.ts
603
+ var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
604
+ var import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
605
+
606
+ // src/translates.ts
607
+ var vscode = __toESM(require("vscode-languageserver-types"));
243
608
  function translateCompletionItemsToCompletionInfo(context, items) {
244
609
  return {
245
610
  defaultCommitCharacters: [],
@@ -370,126 +735,160 @@ function toDisplayParts(text, isDoc = false) {
370
735
  }
371
736
  ];
372
737
  }
738
+ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
739
+ const htmlOffset = context.node.pos + 1;
740
+ return {
741
+ textSpan: { start, length },
742
+ definitions: [
743
+ {
744
+ containerName: "Custom Element",
745
+ containerKind: context.typescript.ScriptElementKind.unknown,
746
+ name: definitionNode.name.text,
747
+ kind: context.typescript.ScriptElementKind.classElement,
748
+ fileName: definitionNode.getSourceFile().fileName,
749
+ textSpan: {
750
+ start: definitionNode.name.getStart() - htmlOffset,
751
+ length: definitionNode.name.text.length
752
+ }
753
+ }
754
+ ]
755
+ };
756
+ }
757
+ function genAttrDefinitionInfo(context, { start, length }, propDeclaration) {
758
+ const htmlOffset = context.node.pos + 1;
759
+ return {
760
+ textSpan: { start, length },
761
+ definitions: [
762
+ {
763
+ containerName: "Attribute",
764
+ containerKind: context.typescript.ScriptElementKind.unknown,
765
+ name: propDeclaration.getText(),
766
+ kind: context.typescript.ScriptElementKind.memberVariableElement,
767
+ fileName: propDeclaration.getSourceFile().fileName,
768
+ textSpan: {
769
+ start: propDeclaration.getStart() - htmlOffset,
770
+ length: propDeclaration.getText().length
771
+ }
772
+ }
773
+ ]
774
+ };
775
+ }
776
+ function genCurrentCtxDefinitionInfo(context, { start, length }, definitionTextSpan) {
777
+ return {
778
+ textSpan: { start, length },
779
+ definitions: [
780
+ {
781
+ containerName: "Attribute",
782
+ containerKind: context.typescript.ScriptElementKind.unknown,
783
+ name: context.text.slice(start, start + length),
784
+ kind: context.typescript.ScriptElementKind.memberVariableElement,
785
+ fileName: context.fileName,
786
+ textSpan: definitionTextSpan
787
+ }
788
+ ]
789
+ };
790
+ }
373
791
 
374
- // ../duoyun-ui/lib/cache.js
375
- var Cache = class {
376
- #max;
377
- #maxAge;
378
- #renewal;
379
- #map = /* @__PURE__ */ new Map();
380
- #reverseMap = /* @__PURE__ */ new Map();
381
- #linkedList = new LinkedList();
382
- constructor({ max = Infinity, maxAge = Infinity, renewal = false } = {}) {
383
- this.#max = max;
384
- this.#maxAge = maxAge;
385
- this.#renewal = renewal;
792
+ // src/decorate-css.ts
793
+ var CSSLanguageService = class {
794
+ #completionsCache = new LRUCache({ max: 1 });
795
+ #diagnosticsCache = new LRUCache();
796
+ #ctx;
797
+ constructor(ctx) {
798
+ this.#ctx = ctx;
386
799
  }
387
- #trim() {
388
- for (let i = this.#linkedList.size - this.#max; i > 0; i--) {
389
- const value = this.#linkedList.get();
390
- const key = this.#reverseMap.get(value);
391
- this.#reverseMap.delete(value);
392
- this.#map.delete(key);
393
- }
800
+ #normalize(context, position) {
801
+ const parent = context.node.parent;
802
+ const tag = context.typescript.isTaggedTemplateExpression(parent) && parent.tag.getText();
803
+ const isStyle = context.typescript.isPropertyAssignment(parent) || tag === "styled";
804
+ if (!isStyle) return { offset: 0, text: context.text, pos: { ...position } };
805
+ const appendBefore = ".parent { ";
806
+ const appendAfter = " }";
807
+ const character = position.line === 0 ? position.character + appendBefore.length : position.character;
808
+ return {
809
+ offset: appendBefore.length,
810
+ text: `${appendBefore}${context.text}${appendAfter}`,
811
+ pos: { line: position.line, character }
812
+ };
394
813
  }
395
- set(key, value) {
396
- this.#linkedList.add(value);
397
- this.#reverseMap.set(value, key);
398
- this.#map.set(key, { value, timestamp: Date.now() });
399
- this.#trim();
400
- return value;
814
+ #getCompletionsAtPosition(context, position) {
815
+ return this.#completionsCache.get(context, position, () => {
816
+ const { text, pos } = this.#normalize(context, position);
817
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
818
+ let emmetResults;
819
+ const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
820
+ this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
821
+ (0, import_vscode_css_languageservice2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
822
+ const completions = this.#ctx.cssLanguageService.doComplete(vDoc, pos, vCss);
823
+ completions.items.push(...emmetResults?.items || []);
824
+ return completions;
825
+ });
401
826
  }
402
- get(key, init) {
403
- const cache = this.#map.get(key);
404
- if (!cache) {
405
- return init && this.set(key, init(key));
406
- }
407
- const { timestamp, value } = cache;
408
- if (Date.now() - timestamp > this.#maxAge) {
409
- this.#linkedList.delete(value);
410
- this.#reverseMap.delete(value);
411
- this.#map.delete(key);
412
- return init && this.set(key, init(key));
413
- }
414
- if (this.#renewal) {
415
- cache.timestamp = Date.now();
416
- }
417
- this.#linkedList.get();
418
- this.#linkedList.add(value);
419
- return value;
827
+ getCompletionsAtPosition(context, position) {
828
+ return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
420
829
  }
421
- };
422
-
423
- // src/cache.ts
424
- var LRUCache = class {
425
- #bucket = new Cache({ max: 100, renewal: true });
426
- #genKey(context, position) {
427
- return [context.fileName, position?.line, position?.character, context.text].join(";");
830
+ getCompletionEntryDetails(context, position, name) {
831
+ const completions = this.#getCompletionsAtPosition(context, position);
832
+ const item = completions.items.find((e) => e.label === name);
833
+ if (!item) return genDefaultCompletionEntryDetails(context, name);
834
+ return translateCompletionItemsToCompletionEntryDetails(context, item);
428
835
  }
429
- getCached(context, position) {
430
- return this.#bucket.get(this.#genKey(context, position));
836
+ getQuickInfoAtPosition(context, position) {
837
+ const { text, pos } = this.#normalize(context, position);
838
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
839
+ const hover = this.#ctx.cssLanguageService.doHover(vDoc, pos, vCss, {
840
+ documentation: true,
841
+ references: true
842
+ });
843
+ if (!hover) return;
844
+ return translateHover(context, hover, position, pos.character - position.character);
431
845
  }
432
- updateCached(context, posOrContent, contentOrUndefined) {
433
- let position;
434
- let content;
435
- if ("line" in posOrContent && "character" in posOrContent) {
436
- position = posOrContent;
437
- content = contentOrUndefined;
438
- } else {
439
- position = void 0;
440
- content = posOrContent;
441
- }
442
- return this.#bucket.set(this.#genKey(context, position), content);
846
+ #getSyntacticDiagnostics(context) {
847
+ const { text, offset } = this.#normalize(context, { line: 0, character: 0 });
848
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
849
+ const oDiagnostics = this.#ctx.cssLanguageService.doValidation(vDoc, vCss);
850
+ const file = this.#ctx.getProgram().getSourceFile(context.fileName);
851
+ return oDiagnostics.map(({ message, range }) => {
852
+ const start = context.toOffset(range.start);
853
+ return {
854
+ category: context.typescript.DiagnosticCategory.Warning,
855
+ code: 0,
856
+ file,
857
+ start: range.start.line === 0 ? start - offset : start,
858
+ length: context.toOffset(range.end) - start,
859
+ messageText: message,
860
+ source: NAME
861
+ };
862
+ });
863
+ }
864
+ getSyntacticDiagnostics(context) {
865
+ this.#ctx.initElements();
866
+ return this.#diagnosticsCache.get(context, void 0, () => this.#getSyntacticDiagnostics(context));
867
+ }
868
+ getDefinitionAndBoundSpan(context, position) {
869
+ const { text, offset } = this.#normalize(context, position);
870
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
871
+ const empty = { textSpan: { start: 0, length: 0 } };
872
+ const node = vCss.findChildAtOffset(context.toOffset(position), true);
873
+ if (!node) return empty;
874
+ const ident = vDoc.getText().slice(node.offset, node.end);
875
+ const definitionNode = this.#ctx.elements.get(ident);
876
+ if (!definitionNode) return empty;
877
+ return genElementDefinitionInfo(context, { start: node.offset - offset, length: node.length }, definitionNode);
443
878
  }
444
879
  };
445
880
 
446
881
  // src/decorate-html.ts
882
+ var import_vscode_css_languageservice3 = require("@mantou/vscode-css-languageservice");
883
+ var import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
447
884
  var HTMLLanguageService = class {
448
- #completionsCache = new LRUCache();
885
+ #completionsCache = new LRUCache({ max: 1 });
449
886
  #diagnosticsCache = new LRUCache();
450
- #cssLanguageService;
451
- #htmlLanguageService;
452
887
  #ctx;
453
888
  constructor(ctx) {
454
889
  this.#ctx = ctx;
455
- const ts = ctx.ts;
456
- const htmlDataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
457
- const provideTags = htmlDataProvider.provideTags.bind(htmlDataProvider);
458
- htmlDataProvider.provideTags = () => {
459
- const result = [...ctx.elements].map(([tag, node]) => ({
460
- name: tag,
461
- attributes: [],
462
- description: getDocComment(ts, node)
463
- })).filter(isNotNullish);
464
- return [...result, ...provideTags()];
465
- };
466
- const provideAttributes = htmlDataProvider.provideAttributes.bind(htmlDataProvider);
467
- htmlDataProvider.provideAttributes = (tag) => {
468
- const typeChecker = ctx.getProgram().getTypeChecker();
469
- const node = ctx.elements.get(tag);
470
- const isDep = node && isDepElement(node);
471
- const props = node && typeChecker.getTypeAtLocation(node).getApparentProperties();
472
- const result = (props || []).map((e) => {
473
- const declaration = e.getDeclarations()?.at(0);
474
- const prop = declaration && ts.isPropertyDeclaration(declaration);
475
- if (!prop) return;
476
- const hasPropDecorator = declaration.modifiers?.some(
477
- (m) => ts.isDecorator(m) && ts.isIdentifier(m.expression)
478
- );
479
- if (!hasPropDecorator && !isDep) return;
480
- const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
481
- return {
482
- name: type === typeChecker.getBooleanType() ? e.name : `.${e.name}`,
483
- description: getDocComment(ts, declaration),
484
- valueSet: "v"
485
- };
486
- }).filter(isNotNullish);
487
- return [...result, ...provideAttributes(isCustomElement(tag) ? "div" : tag)];
488
- };
489
- this.#htmlLanguageService = (0, import_vscode_html_languageservice.getLanguageService)({ customDataProviders: [htmlDataProvider] });
490
- this.#cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({ useDefaultDataProvider: true });
491
890
  }
492
- #getAllCssDoc(doc) {
891
+ #getAllStyleSheet(doc) {
493
892
  const styles = [];
494
893
  const nodes = [...doc.roots];
495
894
  while (true) {
@@ -500,17 +899,17 @@ var HTMLLanguageService = class {
500
899
  }
501
900
  return styles;
502
901
  }
503
- #getCssDoc(context, position, doc) {
902
+ #getEmbeddedCss(context, position, doc) {
504
903
  const node = doc.findNodeAt(context.toOffset(position));
505
904
  if (node.tag !== "style") return;
506
- const virtualDocument = createVirtualDocument("css", context.text.slice(node.startTagEnd, node.endTagStart));
507
- const style = this.#cssLanguageService.parseStylesheet(virtualDocument);
905
+ const text = context.text.slice(node.startTagEnd, node.endTagStart);
906
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
508
907
  const offset = context.toOffset(position) - node.startTagEnd;
509
- const toPosition = (pos) => context.toPosition(virtualDocument.offsetAt(pos) + node.startTagEnd);
908
+ const toPosition = (pos) => context.toPosition(vDoc.offsetAt(pos) + node.startTagEnd);
510
909
  return {
511
- style,
512
- virtualDocument,
513
- position: virtualDocument.positionAt(offset),
910
+ style: vCss,
911
+ vDoc,
912
+ position: vDoc.positionAt(offset),
514
913
  updateRange: (range) => ({
515
914
  start: toPosition(range.start),
516
915
  end: toPosition(range.end)
@@ -518,43 +917,34 @@ var HTMLLanguageService = class {
518
917
  };
519
918
  }
520
919
  #getCSSCompletionsAtPosition(context, position, doc) {
521
- const css = this.#getCssDoc(context, position, doc);
920
+ const css = this.#getEmbeddedCss(context, position, doc);
522
921
  if (!css) return [];
523
922
  let emmetResults;
524
- this.#cssLanguageService.setCompletionParticipants([
525
- {
526
- onCssProperty: () => {
527
- emmetResults = (0, import_emmet_helper.doComplete)(css.virtualDocument, css.position, "css", this.#ctx.config.emmet);
528
- }
529
- }
530
- ]);
531
- const completions = this.#cssLanguageService.doComplete(css.virtualDocument, css.position, css.style);
923
+ const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper2.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
924
+ this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
925
+ (0, import_vscode_css_languageservice3.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
926
+ const completions = this.#ctx.cssLanguageService.doComplete(css.vDoc, css.position, css.style);
532
927
  completions.items.push(...emmetResults?.items || []);
533
- return completions.items.map((e) => ({
534
- ...e,
535
- textEdit: e.textEdit && "range" in e.textEdit ? {
536
- newText: e.textEdit.newText,
537
- range: css.updateRange(e.textEdit.range)
538
- } : e.textEdit
539
- }));
928
+ return completions.items.map((e) => {
929
+ const textEdit = e.textEdit && "range" in e.textEdit && e.textEdit;
930
+ const newTextEdit = textEdit && { newText: textEdit.newText, range: css.updateRange(textEdit.range) };
931
+ return { ...e, textEdit: newTextEdit || e.textEdit };
932
+ });
540
933
  }
541
934
  #getCompletionsAtPosition(context, position) {
542
- const cached = this.#completionsCache.getCached(context, position);
543
- if (cached) return cached;
544
- const virtualDocument = createVirtualDocument("html", context.text);
545
- const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
546
- let emmetResults;
547
- this.#htmlLanguageService.setCompletionParticipants([
548
- {
549
- onHtmlContent: () => {
550
- emmetResults = (0, import_emmet_helper.doComplete)(virtualDocument, position, "html", this.#ctx.config.emmet);
551
- }
552
- }
553
- ]);
554
- const completions = this.#htmlLanguageService.doComplete(virtualDocument, position, vHtml);
555
- completions.items.push(...emmetResults?.items || []);
556
- completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
557
- return this.#completionsCache.updateCached(context, position, completions);
935
+ return this.#completionsCache.get(context, position, () => {
936
+ const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
937
+ 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
+ };
942
+ this.#ctx.htmlLanguageService.setCompletionParticipants([{ onHtmlContent }]);
943
+ const completions = this.#ctx.htmlLanguageService.doComplete(vDoc, position, vHtml);
944
+ completions.items.push(...emmetResults?.items || []);
945
+ completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
946
+ return completions;
947
+ });
558
948
  }
559
949
  getCompletionsAtPosition(context, position) {
560
950
  return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
@@ -566,163 +956,323 @@ var HTMLLanguageService = class {
566
956
  return translateCompletionItemsToCompletionEntryDetails(context, item);
567
957
  }
568
958
  #getCSSQuickInfoAtPosition(context, position, doc) {
569
- const css = this.#getCssDoc(context, position, doc);
570
- const hover = css && this.#cssLanguageService.doHover(css.virtualDocument, css.position, css.style, {
959
+ const css = this.#getEmbeddedCss(context, position, doc);
960
+ if (!css) return;
961
+ const hover = this.#ctx.cssLanguageService.doHover(css.vDoc, css.position, css.style, {
571
962
  documentation: true,
572
963
  references: true
573
964
  });
574
- return hover && {
575
- ...hover,
576
- range: hover.range && css.updateRange(hover.range)
577
- };
965
+ if (!hover) return;
966
+ return { ...hover, range: hover.range && css.updateRange(hover.range) };
578
967
  }
579
968
  getQuickInfoAtPosition(context, position) {
580
- const virtualDocument = createVirtualDocument("html", context.text);
581
- const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
582
- const hover = this.#htmlLanguageService.doHover(virtualDocument, position, vHtml, {
969
+ const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
970
+ const htmlHover = this.#ctx.htmlLanguageService.doHover(vDoc, position, vHtml, {
583
971
  documentation: true,
584
972
  references: true
585
- }) || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
973
+ });
974
+ const hover = htmlHover || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
586
975
  if (!hover) return;
587
976
  return translateHover(context, hover, position);
588
977
  }
589
- getSyntacticDiagnostics(context) {
590
- const cached = this.#diagnosticsCache.getCached(context);
591
- if (cached) return cached;
592
- const virtualDocument = createVirtualDocument("html", context.text);
593
- const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
594
- const styles = this.#getAllCssDoc(vHtml);
595
- const file = this.#ctx.getProgram().getSourceFile(context.fileName);
596
- return this.#diagnosticsCache.updateCached(
597
- context,
598
- styles.map((node) => {
599
- const textDocument = createVirtualDocument("css", context.text.slice(node.startTagEnd, node.endTagStart));
600
- const vCss = this.#cssLanguageService.parseStylesheet(textDocument);
601
- const oDiagnostics = this.#cssLanguageService.doValidation(textDocument, vCss);
602
- return oDiagnostics.map(({ message, range }) => {
603
- const start = node.startTagEnd + textDocument.offsetAt(range.start);
604
- const end = node.startTagEnd + textDocument.offsetAt(range.end);
605
- return {
978
+ #getCssSyntacticDiagnostics(context) {
979
+ return this.#diagnosticsCache.get(context, void 0, () => {
980
+ const { vHtml } = this.#ctx.getHtmlDoc(context.text);
981
+ const file = this.#ctx.getProgram().getSourceFile(context.fileName);
982
+ const styles = this.#getAllStyleSheet(vHtml);
983
+ const diagnostics = [];
984
+ styles.forEach((node) => {
985
+ const text = context.text.slice(node.startTagEnd, node.endTagStart);
986
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
987
+ this.#ctx.cssLanguageService.doValidation(vDoc, vCss).forEach(({ message, range }) => {
988
+ const start = node.startTagEnd + vDoc.offsetAt(range.start);
989
+ const end = node.startTagEnd + vDoc.offsetAt(range.end);
990
+ diagnostics.push({
606
991
  category: context.typescript.DiagnosticCategory.Warning,
607
992
  code: 0,
608
993
  file,
609
994
  start,
610
995
  length: end - start,
611
- messageText: message
612
- };
996
+ messageText: message,
997
+ source: NAME
998
+ });
613
999
  });
614
- }).flat()
615
- );
1000
+ });
1001
+ return diagnostics;
1002
+ });
1003
+ }
1004
+ #getHtmlSyntacticDiagnostics(context) {
1005
+ const offset = context.node.getStart() + 1;
1006
+ const { vHtml } = this.#ctx.getHtmlDoc(context.text);
1007
+ const program = this.#ctx.getProgram();
1008
+ const file = program.getSourceFile(context.fileName);
1009
+ const typeChecker = program.getTypeChecker();
1010
+ const diagnostics = [];
1011
+ forEachNode(vHtml.roots, (node) => {
1012
+ if (!node.tag) return;
1013
+ if (isCustomElementTag(node.tag) && !this.#ctx.elements.get(node.tag)) {
1014
+ diagnostics.push({
1015
+ category: context.typescript.DiagnosticCategory.Warning,
1016
+ code: 101 /* UnknownTag */,
1017
+ file,
1018
+ start: node.start + 1,
1019
+ length: node.tag.length,
1020
+ messageText: `Unknown element tag '${node.tag}'`,
1021
+ source: NAME
1022
+ });
1023
+ }
1024
+ const tagDeclaration = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
1025
+ if (!tagDeclaration) return;
1026
+ for (const [attributeName, { value, start, end }] of node.attributesMap) {
1027
+ if (attributeName.startsWith("_")) continue;
1028
+ const hasValueSpan = value?.startsWith("_");
1029
+ const attrInfo = getAttrName(attributeName);
1030
+ const propType = getPropType(typeChecker, tagDeclaration, attrInfo);
1031
+ const diagnostic = {
1032
+ category: context.typescript.DiagnosticCategory.Warning,
1033
+ file,
1034
+ start,
1035
+ length: end - start,
1036
+ source: NAME,
1037
+ code: 103 /* PropTypeError */,
1038
+ messageText: !propType ? `'${attributeName}' type error` : `'${attributeName}' not satisfied '${typeChecker.typeToString(propType)}'`
1039
+ };
1040
+ if ((attributeName === "v-else-if" || attributeName === "v-else") && !node.prev?.attributesMap.has("v-if") && !node.prev?.attributesMap.has("v-else-if")) {
1041
+ diagnostics.push({
1042
+ ...diagnostic,
1043
+ code: 104 /* PropSyntaxError */,
1044
+ messageText: `'${attrInfo.attr}' syntax error`
1045
+ });
1046
+ }
1047
+ if (attributeName === "v-if" || attributeName === "v-else-if") {
1048
+ const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1049
+ if (!hasValueSpan || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getBooleanType())) {
1050
+ diagnostics.push(diagnostic);
1051
+ }
1052
+ continue;
1053
+ }
1054
+ if (attributeName === "v-else") {
1055
+ if (value !== null) {
1056
+ diagnostics.push(diagnostic);
1057
+ }
1058
+ continue;
1059
+ }
1060
+ if (!propType) {
1061
+ if (attrInfo.decorate !== "@") {
1062
+ diagnostics.push({
1063
+ ...diagnostic,
1064
+ code: 102 /* UnknownProp */,
1065
+ messageText: `Unknown property '${attrInfo.attr}'`
1066
+ });
1067
+ }
1068
+ continue;
1069
+ }
1070
+ if (value === null) {
1071
+ if (attrInfo.decorate) {
1072
+ diagnostics.push({
1073
+ ...diagnostic,
1074
+ code: 104 /* PropSyntaxError */,
1075
+ messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
1076
+ });
1077
+ } else if (propType !== typeChecker.getBooleanType()) {
1078
+ diagnostics.push(diagnostic);
1079
+ }
1080
+ continue;
1081
+ }
1082
+ if (hasValueSpan) {
1083
+ const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1084
+ switch (attrInfo.decorate) {
1085
+ case "?":
1086
+ const boolType = getUnionType(typeChecker, [
1087
+ typeChecker.getBooleanType(),
1088
+ typeChecker.getUndefinedType(),
1089
+ typeChecker.getNullType()
1090
+ ]);
1091
+ if (!typeChecker.isTypeAssignableTo(spanType, boolType)) {
1092
+ diagnostics.push(diagnostic);
1093
+ }
1094
+ continue;
1095
+ case ".":
1096
+ case "@":
1097
+ if (!typeChecker.isTypeAssignableTo(spanType, propType)) {
1098
+ diagnostics.push(diagnostic);
1099
+ }
1100
+ continue;
1101
+ default:
1102
+ if (!typeChecker.isTypeAssignableTo(spanType, propType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
1103
+ diagnostics.push(diagnostic);
1104
+ }
1105
+ continue;
1106
+ }
1107
+ } else {
1108
+ const types = [typeChecker.getStringType(), typeChecker.getNumberType()];
1109
+ const valueLetter = value.startsWith('"') ? value.slice(1, -1) : value;
1110
+ types.push(typeChecker.getStringLiteralType(valueLetter));
1111
+ const numberValue = Number(valueLetter);
1112
+ if (!Number.isNaN(numberValue)) {
1113
+ types.push(typeChecker.getNumberLiteralType(numberValue));
1114
+ }
1115
+ if (attrInfo.decorate) {
1116
+ diagnostics.push({
1117
+ ...diagnostic,
1118
+ code: 104 /* PropSyntaxError */,
1119
+ messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
1120
+ });
1121
+ } else if (types.every((t) => !typeChecker.isTypeAssignableTo(t, propType))) {
1122
+ diagnostics.push(diagnostic);
1123
+ }
1124
+ continue;
1125
+ }
1126
+ }
1127
+ });
1128
+ return diagnostics;
1129
+ }
1130
+ getSyntacticDiagnostics(context) {
1131
+ this.#ctx.initElements();
1132
+ return [...this.#getCssSyntacticDiagnostics(context), ...this.#getHtmlSyntacticDiagnostics(context)];
616
1133
  }
617
1134
  getDefinitionAndBoundSpan(context, position) {
618
- const htmlOffset = context.node.pos + 1;
619
- const virtualDocument = createVirtualDocument("html", context.text);
620
- const vHtml = this.#htmlLanguageService.parseHTMLDocument(virtualDocument);
621
- const offset = context.toOffset(position);
622
- const node = vHtml.findNodeAt(offset);
623
- const { text, start, length } = getHTMLTextAtPosition(context.text, offset);
624
- const definitionNode = this.#ctx.elements.get(node.tag);
625
- if (!definitionNode || node.tag === "style" || offset > node.startTagEnd || !text) {
626
- return { textSpan: { start, length } };
1135
+ const currentOffset = context.toOffset(position);
1136
+ const { vHtml } = this.#ctx.getHtmlDoc(context.text);
1137
+ const node = vHtml.findNodeAt(currentOffset);
1138
+ const { text, start, length, before } = getHTMLTextAtPosition(context.text, currentOffset);
1139
+ const empty = { textSpan: { start, length } };
1140
+ if (node.tag === "style" && currentOffset > node.startTagEnd) {
1141
+ const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
1142
+ const cssNode = style.findChildAtOffset(vDoc.offsetAt(pos), true);
1143
+ if (!cssNode) return empty;
1144
+ const ident = vDoc.getText().slice(cssNode.offset, cssNode.end);
1145
+ const definitionNode2 = this.#ctx.elements.get(ident);
1146
+ if (!definitionNode2) return empty;
1147
+ return genElementDefinitionInfo(
1148
+ context,
1149
+ { start: cssNode.offset + node.startTagEnd, length: cssNode.length },
1150
+ definitionNode2
1151
+ );
627
1152
  }
1153
+ const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
1154
+ if (!definitionNode || currentOffset > node.startTagEnd || !text) return empty;
628
1155
  if (text === node.tag) {
629
- return {
630
- textSpan: { start, length },
631
- definitions: [
632
- {
633
- containerName: "Custom Element",
634
- containerKind: context.typescript.ScriptElementKind.unknown,
635
- name: definitionNode.name.text,
636
- kind: context.typescript.ScriptElementKind.classElement,
637
- fileName: definitionNode.getSourceFile().fileName,
638
- textSpan: {
639
- start: definitionNode.name.getStart() - htmlOffset,
640
- length: definitionNode.name.text.length
641
- }
642
- }
643
- ]
644
- };
1156
+ return genElementDefinitionInfo(context, { start, length }, definitionNode);
1157
+ }
1158
+ const { attr, offset } = getAttrName(text);
1159
+ if (before.length > attr.length) return empty;
1160
+ if (attr === "v-else" || attr === "v-else-if") {
1161
+ const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
1162
+ if (!ifAttr) return empty;
1163
+ return genCurrentCtxDefinitionInfo(
1164
+ context,
1165
+ { start: start + offset, length: attr.length },
1166
+ { start: ifAttr.start, length: ifAttr.end - ifAttr.start }
1167
+ );
645
1168
  }
646
- const { attr, type } = getAttrName(text);
647
1169
  const typeChecker = this.#ctx.getProgram().getTypeChecker();
648
1170
  const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
649
1171
  const propDeclaration = propSymbol?.getDeclarations()?.at(0);
650
- return {
651
- textSpan: { start: type ? start + 1 : start, length: attr.length },
652
- definitions: !propDeclaration ? void 0 : [
653
- {
654
- containerName: "Attribute",
655
- containerKind: context.typescript.ScriptElementKind.unknown,
656
- name: propDeclaration.getText(),
657
- kind: context.typescript.ScriptElementKind.memberVariableElement,
658
- fileName: propDeclaration.getSourceFile().fileName,
659
- textSpan: {
660
- start: propDeclaration.getStart() - htmlOffset,
661
- length: propDeclaration.getText().length
662
- }
663
- }
664
- ]
665
- };
1172
+ if (!propDeclaration) return empty;
1173
+ return genAttrDefinitionInfo(context, { start: start + offset, length: attr.length }, propDeclaration);
666
1174
  }
667
1175
  };
668
-
669
- // src/decorate-ts.ts
670
- function decorateTypeChecker(ctx, typeChecker) {
671
- const fn = typeChecker.isValidPropertyAccessForCompletions.bind(typeChecker);
672
- typeChecker.isValidPropertyAccessForCompletions = (...args) => {
673
- const result = fn(...args);
674
- if (!result) return false;
675
- try {
676
- const { declarations } = args.at(2);
677
- if (!declarations) return true;
678
- const isNever = declarations.every(
679
- (node) => ctx.ts.isPropertySignature(node) && node.type?.getText() === "never"
680
- );
681
- return !isNever;
682
- } catch {
683
- return true;
684
- }
685
- };
686
- return typeChecker;
1176
+ function getSpanExpression(typescript, file, pos) {
1177
+ let node = getAstNodeAtPosition(typescript, file, pos);
1178
+ while (!typescript.isTemplateSpan(node)) {
1179
+ node = node.parent;
1180
+ if (!node) return;
1181
+ }
1182
+ return node.expression;
687
1183
  }
688
- function updateElement(ctx, file) {
689
- const isDep = isDepElement(file);
690
- ctx.ts.forEachChild(file, (node) => {
691
- const tag = getCustomElementTag(ctx.ts, node, isDep);
692
- if (tag && ctx.ts.isClassDeclaration(node)) {
693
- ctx.elements.set(tag, node);
694
- }
695
- });
1184
+ function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
1185
+ const valueOffset = attrNameEnd + htmlOffset + 3;
1186
+ const spanExp = getSpanExpression(typescript, file, valueOffset);
1187
+ return typeChecker.getTypeAtLocation(spanExp);
1188
+ }
1189
+ function getPropType(typeChecker, tagClassDeclaration, attrInfo) {
1190
+ const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
1191
+ if (attrInfo.attr.startsWith("data-")) {
1192
+ return typeChecker.getStringType();
1193
+ }
1194
+ const propName = kebabToCamelCase(attrInfo.attr);
1195
+ switch (propName) {
1196
+ case "class":
1197
+ case "style":
1198
+ case "part":
1199
+ case "exportparts":
1200
+ return typeChecker.getStringType();
1201
+ case "tabindex":
1202
+ return typeChecker.getNumberType();
1203
+ case "ariaDisabled":
1204
+ case "ariaChecked":
1205
+ case "ariaHidden":
1206
+ return getUnionType(typeChecker, [
1207
+ typeChecker.getStringType(),
1208
+ typeChecker.getBooleanType(),
1209
+ typeChecker.getUndefinedType()
1210
+ ]);
1211
+ default:
1212
+ const isEvent = attrInfo.decorate === "@";
1213
+ const propSymbol = classType.getProperty(propName);
1214
+ const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
1215
+ if (!isEvent) return propType;
1216
+ const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
1217
+ return getUnionType(typeChecker, [eventHandleType, typeChecker.getUndefinedType()]);
1218
+ }
1219
+ }
1220
+ function getEmitterHandleType(typeChecker, classType, propType) {
1221
+ const handleSymbol = propType?.getProperty("handler");
1222
+ if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
1223
+ const addEventListenerSymbol = classType.getProperty("addEventListener");
1224
+ if (!addEventListenerSymbol) return typeChecker.getAnyType();
1225
+ const addEventListenerDecls = addEventListenerSymbol.declarations;
1226
+ const addEventListenerDecl = addEventListenerDecls.find((e) => !e.typeParameters);
1227
+ const listenerDecl = addEventListenerDecl.parameters.at(1);
1228
+ if (!listenerDecl) return typeChecker.getAnyType();
1229
+ return typeChecker.getTypeAtLocation(listenerDecl);
1230
+ }
1231
+ function getUnionType(typeChecker, types) {
1232
+ if ("getUnionType" in typeChecker) {
1233
+ return typeChecker.getUnionType(types);
1234
+ }
1235
+ return types.at(0);
696
1236
  }
1237
+
1238
+ // src/decorate-ts.ts
697
1239
  function decorateLanguageService(ctx, languageService) {
698
1240
  const { ts, getProgram } = ctx;
699
- const ls = Object.fromEntries(
700
- Object.entries(languageService).map(([key, value]) => [key, value.bind(languageService)])
701
- );
1241
+ const ls = bindMemberFunction(languageService);
702
1242
  languageService.getCompletionsAtPosition = (...args) => {
703
1243
  const program = getProgram();
704
1244
  const typeChecker = program.getTypeChecker();
705
1245
  decorate(typeChecker, () => decorateTypeChecker(ctx, typeChecker));
706
1246
  return ls.getCompletionsAtPosition(...args);
707
1247
  };
1248
+ languageService.getSuggestionDiagnostics = (...args) => {
1249
+ const program = getProgram();
1250
+ const file = program.getSourceFile(args[0]);
1251
+ const result = ls.getSuggestionDiagnostics(...args);
1252
+ ctx.updateElement(file);
1253
+ return result.filter(({ start, reportsUnnecessary, category }) => {
1254
+ if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
1255
+ const node = getAstNodeAtPosition(ts, file, start);
1256
+ if (!node || !ts.isPrivateIdentifier(node)) return true;
1257
+ const declaration = node.parent;
1258
+ if (!ts.isMethodDeclaration(declaration) && !ts.isPropertyDeclaration(declaration)) return true;
1259
+ return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
1260
+ });
1261
+ };
708
1262
  languageService.findReferences = (...args) => {
1263
+ const oResult = ls.findReferences(...args) || [];
709
1264
  const program = getProgram();
710
- const result = [];
711
1265
  const currentNode = getAstNodeAtPosition(ts, program.getSourceFile(args[0]), args[1]);
712
- const currentTag = currentNode && getCustomElementTag(ts, currentNode.parent);
713
- for (const file of program.getSourceFiles()) {
714
- const references = [];
715
- forEachTag(ts, file, ({ tag, length, start }) => {
716
- if (tag !== currentTag) return;
717
- references.push({
718
- fileName: file.fileName,
719
- isWriteAccess: true,
720
- textSpan: { start, length }
721
- });
722
- });
723
- if (!references.length) continue;
724
- result.push({
725
- references,
1266
+ if (!currentNode) return oResult;
1267
+ const isIdent = ts.isIdentifier(currentNode);
1268
+ if (!isIdent) return oResult;
1269
+ const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
1270
+ const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
1271
+ if (!currentTag) return oResult;
1272
+ const map = /* @__PURE__ */ new Map();
1273
+ forEachAllHtmlTemplateNode(ctx, currentTag, (file, tagInfo) => {
1274
+ const symbol = map.get(file.fileName) || {
1275
+ references: [],
726
1276
  definition: {
727
1277
  containerKind: ctx.ts.ScriptElementKind.unknown,
728
1278
  containerName: "",
@@ -732,174 +1282,163 @@ function decorateLanguageService(ctx, languageService) {
732
1282
  name: "test",
733
1283
  kind: ctx.ts.ScriptElementKind.unknown
734
1284
  }
735
- });
1285
+ };
1286
+ map.set(file.fileName, symbol);
1287
+ if (prop) {
1288
+ const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
1289
+ const kebabCaseName = camelToKebabCase(prop.text);
1290
+ ["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
1291
+ for (const propName of propNames) {
1292
+ const info = tagInfo.node.attributesMap.get(propName);
1293
+ if (!info) continue;
1294
+ const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1295
+ symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
1296
+ }
1297
+ } else {
1298
+ symbol.references.push({
1299
+ fileName: file.fileName,
1300
+ isWriteAccess: true,
1301
+ textSpan: tagInfo.open
1302
+ });
1303
+ }
1304
+ });
1305
+ return [...map.values(), ...oResult];
1306
+ };
1307
+ languageService.getRenameInfo = (fileName, position, ...args) => {
1308
+ const result = ls.getRenameInfo(fileName, position, ...args);
1309
+ const tagInfo = findCurrentTagInfo(ctx, fileName, position);
1310
+ if (tagInfo) {
1311
+ return {
1312
+ canRename: true,
1313
+ displayName: tagInfo.tag,
1314
+ fullDisplayName: tagInfo.tag,
1315
+ kind: ts.ScriptElementKind.alias,
1316
+ kindModifiers: "tag",
1317
+ triggerSpan: tagInfo.open
1318
+ };
736
1319
  }
737
- return [...result, ...ls.findReferences(...args) || []];
1320
+ const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1321
+ if (tagDefinedInfo) {
1322
+ return {
1323
+ canRename: true,
1324
+ displayName: tagDefinedInfo.tag,
1325
+ fullDisplayName: tagDefinedInfo.tag,
1326
+ kind: ts.ScriptElementKind.alias,
1327
+ kindModifiers: "tag",
1328
+ triggerSpan: tagDefinedInfo.textSpan
1329
+ };
1330
+ }
1331
+ return result;
738
1332
  };
739
- languageService.getSuggestionDiagnostics = (...args) => {
740
- const program = getProgram();
741
- decorate(program, () => {
742
- program.getSourceFiles().forEach((file2) => updateElement(ctx, file2));
743
- return program;
744
- });
745
- const file = program.getSourceFile(args[0]);
746
- const result = ls.getSuggestionDiagnostics(...args);
747
- updateElement(ctx, file);
748
- return result.filter(({ start, reportsUnnecessary, category }) => {
749
- if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
750
- const node = getAstNodeAtPosition(ts, file, start);
751
- if (!node || !ts.isPrivateIdentifier(node)) return true;
752
- const declaration = node.parent;
753
- if (!ts.isMethodDeclaration(declaration) && !ts.isPropertyDeclaration(declaration)) return true;
754
- return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
755
- });
1333
+ languageService.findRenameLocations = (fileName, position, ...args) => {
1334
+ const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
1335
+ if (tagPairInfo) {
1336
+ const result = [{ fileName, textSpan: tagPairInfo.open }];
1337
+ if (tagPairInfo.end) result.push({ fileName, textSpan: tagPairInfo.end });
1338
+ return result;
1339
+ }
1340
+ const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1341
+ if (tagDefinedInfo) {
1342
+ const result = [{ fileName, textSpan: tagDefinedInfo.textSpan }];
1343
+ forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (f, info) => {
1344
+ result.push({ fileName: f.fileName, textSpan: info.open });
1345
+ if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
1346
+ });
1347
+ return result;
1348
+ }
1349
+ const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
1350
+ const file = ctx.getProgram().getSourceFile(fileName);
1351
+ const node = getAstNodeAtPosition(ctx.ts, file, position);
1352
+ const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
1353
+ if (!tag) return oResult;
1354
+ const propText = node.getText();
1355
+ const kebabCaseName = camelToKebabCase(propText);
1356
+ if (isPropType(ctx.ts, node.parent, ["emitter", "globalemitter"])) {
1357
+ forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1358
+ const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
1359
+ if (!info) return;
1360
+ const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1361
+ oResult.push({ fileName: f.fileName, prefixText: "@", textSpan });
1362
+ });
1363
+ }
1364
+ if (isPropType(ctx.ts, node.parent, ["attribute", "numattribute", "boolattribute", "property"])) {
1365
+ forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1366
+ const propNames = ["", ".", "?"].map((c) => `${c}${kebabCaseName}`);
1367
+ propNames.map((propName) => {
1368
+ const info = tagInfo.node.attributesMap.get(propName);
1369
+ if (!info) return;
1370
+ const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1371
+ oResult.push({ fileName: f.fileName, prefixText: ".", textSpan });
1372
+ });
1373
+ });
1374
+ }
1375
+ return oResult;
756
1376
  };
757
1377
  return languageService;
758
1378
  }
759
-
760
- // src/configuration.ts
761
- var defaultConfiguration = {
762
- emmet: {},
763
- tags: ["html", "raw"],
764
- format: {
765
- enabled: true
766
- }
767
- };
768
- var Configuration = class {
769
- #emmet = defaultConfiguration.emmet;
770
- #format = defaultConfiguration.format;
771
- #tags = defaultConfiguration.tags;
772
- update(config) {
773
- this.#format = Object.assign({}, defaultConfiguration.format, config.format || {});
774
- this.#tags = config.tags || defaultConfiguration.tags;
775
- }
776
- get emmet() {
777
- return this.#emmet;
778
- }
779
- get format() {
780
- return this.#format;
781
- }
782
- get tags() {
783
- return this.#tags;
784
- }
785
- };
786
- var Store = class {
787
- #map = /* @__PURE__ */ new Map();
788
- #weakMap = /* @__PURE__ */ new WeakMap();
789
- #registry = new FinalizationRegistry((key) => this.#map.delete(key));
790
- set(key, val) {
791
- this.#map.set(key, new WeakRef(val));
792
- this.#weakMap.set(val, key);
793
- this.#registry.register(val, key);
794
- }
795
- get(key) {
796
- return this.#map.get(key)?.deref();
1379
+ function forEachAllHtmlTemplateNode(ctx, tag, fn) {
1380
+ for (const file of ctx.getProgram().getSourceFiles()) {
1381
+ if (file.fileName.endsWith(".d.ts")) continue;
1382
+ for (const templateContext of ctx.htmlSourceHelper.getAllTemplates(file.fileName)) {
1383
+ const { vHtml } = ctx.getHtmlDoc(templateContext.text);
1384
+ forEachNode(vHtml.roots, (node) => {
1385
+ if (node.tag !== tag) return;
1386
+ fn(file, getTagInfo(node, templateContext.node.getStart() + 1));
1387
+ });
1388
+ }
797
1389
  }
798
- findKey(val) {
799
- return this.#weakMap.get(val);
1390
+ }
1391
+ function findCurrentTagInfo(ctx, fileName, position) {
1392
+ const templateContext = ctx.htmlSourceHelper.getTemplate(fileName, position);
1393
+ if (!templateContext) return;
1394
+ const htmlOffset = templateContext.node.pos + 1;
1395
+ const { vHtml } = ctx.getHtmlDoc(templateContext.text);
1396
+ const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
1397
+ const offset = templateContext.toOffset(relativePosition);
1398
+ const node = vHtml.findNodeAt(offset);
1399
+ const { text } = getHTMLTextAtPosition(templateContext.text, offset);
1400
+ const onTag = offset < node.startTagEnd && text === node.tag;
1401
+ if (!onTag || !node.tag) return;
1402
+ return getTagInfo(node, htmlOffset);
1403
+ }
1404
+ function findDefinedTagInfo(ctx, fileName, position) {
1405
+ const file = ctx.getProgram().getSourceFile(fileName);
1406
+ const node = getAstNodeAtPosition(ctx.ts, file, position);
1407
+ if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !== "customElement") {
1408
+ return;
800
1409
  }
801
- *[Symbol.iterator]() {
802
- const entries2 = this.#map.entries();
803
- for (const [tag, ref] of entries2) {
804
- yield [tag, ref.deref()];
1410
+ const tag = node.text;
1411
+ return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
1412
+ }
1413
+ function isPropType(typescript, node, types) {
1414
+ if (!typescript.isPropertyDeclaration(node)) return;
1415
+ for (const modifier of node.modifiers || []) {
1416
+ if (!typescript.isDecorator(modifier)) continue;
1417
+ const { expression } = modifier;
1418
+ if (typescript.isIdentifier(expression) && types.includes(expression.text)) {
1419
+ return true;
805
1420
  }
806
1421
  }
807
- };
808
-
809
- // src/decorate-css.ts
810
- var import_vscode_css_languageservice2 = require("vscode-css-languageservice");
811
- var import_emmet_helper2 = require("@vscode/emmet-helper");
812
- var CSSLanguageService = class {
813
- #completionsCache = new LRUCache();
814
- #diagnosticsCache = new LRUCache();
815
- #cssLanguageService = (0, import_vscode_css_languageservice2.getCSSLanguageService)();
816
- #ctx;
817
- constructor(ctx) {
818
- this.#ctx = ctx;
819
- }
820
- #normalize = (context, position) => {
821
- const parent = context.node.parent;
822
- const tag = context.typescript.isTaggedTemplateExpression(parent) && parent.tag.getText();
823
- if (context.typescript.isPropertyAssignment(parent) || tag === "styled") {
824
- const appendBefore = ".parent { ";
825
- const appendAfter = " }";
826
- return {
827
- offset: appendBefore.length,
828
- text: `${appendBefore}${context.text}${appendAfter}`,
829
- pos: {
830
- line: position.line,
831
- character: position.line === 0 ? position.character + appendBefore.length : position.character
832
- }
833
- };
1422
+ }
1423
+ function decorateTypeChecker(ctx, typeChecker) {
1424
+ const internal = typeChecker;
1425
+ const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
1426
+ internal.isValidPropertyAccessForCompletions = (...args) => {
1427
+ const result = checker.isValidPropertyAccessForCompletions(...args);
1428
+ if (!result) return false;
1429
+ try {
1430
+ const { declarations } = args.at(2);
1431
+ if (!declarations) return true;
1432
+ const isNever = declarations.every(
1433
+ (node) => ctx.ts.isPropertySignature(node) && node.type?.getText() === "never"
1434
+ );
1435
+ return !isNever;
1436
+ } catch {
1437
+ return true;
834
1438
  }
835
- return {
836
- offset: 0,
837
- text: context.text,
838
- pos: { ...position }
839
- };
840
1439
  };
841
- #getCompletionsAtPosition(context, position) {
842
- const cached = this.#completionsCache.getCached(context, position);
843
- if (cached) return cached;
844
- const { text, pos } = this.#normalize(context, position);
845
- const virtualDocument = createVirtualDocument("css", text);
846
- const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);
847
- let emmetResults;
848
- this.#cssLanguageService.setCompletionParticipants([
849
- {
850
- onCssProperty: () => {
851
- emmetResults = (0, import_emmet_helper2.doComplete)(virtualDocument, pos, "css", this.#ctx.config.emmet);
852
- }
853
- }
854
- ]);
855
- const completions = this.#cssLanguageService.doComplete(virtualDocument, pos, vCss);
856
- completions.items.push(...emmetResults?.items || []);
857
- return this.#completionsCache.updateCached(context, position, completions);
858
- }
859
- getCompletionsAtPosition(context, position) {
860
- return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
861
- }
862
- getCompletionEntryDetails(context, position, name) {
863
- const completions = this.#getCompletionsAtPosition(context, position);
864
- const item = completions.items.find((e) => e.label === name);
865
- if (!item) return genDefaultCompletionEntryDetails(context, name);
866
- return translateCompletionItemsToCompletionEntryDetails(context, item);
867
- }
868
- getQuickInfoAtPosition(context, position) {
869
- const { text, pos } = this.#normalize(context, position);
870
- const virtualDocument = createVirtualDocument("css", text);
871
- const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);
872
- const hover = this.#cssLanguageService.doHover(virtualDocument, pos, vCss, {
873
- documentation: true,
874
- references: true
875
- });
876
- if (!hover) return;
877
- return translateHover(context, hover, position, pos.character - position.character);
878
- }
879
- getSyntacticDiagnostics(context) {
880
- const cached = this.#diagnosticsCache.getCached(context);
881
- if (cached) return cached;
882
- const { text, offset } = this.#normalize(context, { line: 0, character: 0 });
883
- const virtualDocument = createVirtualDocument("css", text);
884
- const vCss = this.#cssLanguageService.parseStylesheet(virtualDocument);
885
- const oDiagnostics = this.#cssLanguageService.doValidation(virtualDocument, vCss);
886
- const file = this.#ctx.getProgram().getSourceFile(context.fileName);
887
- return this.#diagnosticsCache.updateCached(
888
- context,
889
- oDiagnostics.map(({ message, range }) => {
890
- const start = context.toOffset(range.start);
891
- return {
892
- category: context.typescript.DiagnosticCategory.Warning,
893
- code: 0,
894
- file,
895
- start: range.start.line === 0 ? start - offset : start,
896
- length: context.toOffset(range.end) - start,
897
- messageText: message
898
- };
899
- })
900
- );
901
- }
902
- };
1440
+ return typeChecker;
1441
+ }
903
1442
 
904
1443
  // src/index.ts
905
1444
  var LanguageServiceLogger = class {
@@ -908,7 +1447,7 @@ var LanguageServiceLogger = class {
908
1447
  this.#info = info;
909
1448
  }
910
1449
  log(msg) {
911
- this.#info.project.projectService.logger.info(`[gem] ${msg}`);
1450
+ this.#info.project.projectService.logger.info(`[${NAME}] ${msg}`);
912
1451
  }
913
1452
  };
914
1453
  var HtmlPlugin = class {
@@ -920,32 +1459,16 @@ var HtmlPlugin = class {
920
1459
  create(info) {
921
1460
  return decorate(info.languageService, () => {
922
1461
  const logger = new LanguageServiceLogger(info);
923
- logger.log("Starting ts-gem-plugin...");
1462
+ logger.log("Starting...");
924
1463
  this.#config.update(info.config);
925
- const context = {
926
- config: this.#config,
927
- ts: this.#ts,
928
- logger,
929
- elements: new Store(),
930
- getProgram: () => {
931
- return info.languageService.getProgram();
932
- },
933
- getProject: () => {
934
- return info.project;
935
- }
936
- };
1464
+ const context = new Context(this.#ts, this.#config, info, logger);
937
1465
  const decoratedService = decorateLanguageService(context, info.languageService);
938
1466
  const decoratedService1 = (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
939
1467
  this.#ts,
940
1468
  decoratedService,
941
1469
  info.project,
942
1470
  new CSSLanguageService(context),
943
- {
944
- tags: ["styled", "css"],
945
- enableForStringWithSubstitutions: true,
946
- getSubstitution,
947
- isValidTemplate: (node) => isValidCSSTemplate(this.#ts, node, "css")
948
- },
1471
+ context.cssTemplateStringSettings,
949
1472
  { logger }
950
1473
  );
951
1474
  return (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
@@ -953,11 +1476,7 @@ var HtmlPlugin = class {
953
1476
  decoratedService1,
954
1477
  info.project,
955
1478
  new HTMLLanguageService(context),
956
- {
957
- tags: ["html", "raw", "h"],
958
- enableForStringWithSubstitutions: true,
959
- getSubstitution
960
- },
1479
+ context.htmlTemplateStringSettings,
961
1480
  { logger }
962
1481
  );
963
1482
  });