ts-gem-plugin 0.0.5 → 0.0.7

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 +1044 -523
  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,225 @@ 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/constants.ts
162
+ var NAME = "gem-plugin";
163
+
164
+ // src/context.ts
165
+ var import_standard_script_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-script-source-helper"));
166
+ var import_standard_template_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-template-source-helper"));
167
+ var import_vscode_css_languageservice = require("@mantou/vscode-css-languageservice");
168
+ var import_vscode_html_languageservice2 = require("@mantou/vscode-html-languageservice");
169
+
170
+ // ../duoyun-ui/lib/map.js
171
+ var StringWeakMap = class {
172
+ #map = /* @__PURE__ */ new Map();
173
+ #weakMap = /* @__PURE__ */ new WeakMap();
174
+ #registry = new FinalizationRegistry((key) => this.#map.delete(key));
175
+ set(key, val) {
176
+ this.#map.set(key, new WeakRef(val));
177
+ this.#weakMap.set(val, key);
178
+ this.#registry.register(val, key);
179
+ }
180
+ get(key) {
181
+ return this.#map.get(key)?.deref();
182
+ }
183
+ findKey(val) {
184
+ return this.#weakMap.get(val);
185
+ }
186
+ *[Symbol.iterator]() {
187
+ const entries2 = this.#map.entries();
188
+ for (const [tag, ref] of entries2) {
189
+ yield [tag, ref.deref()];
190
+ }
191
+ }
192
+ };
193
+
194
+ // ../duoyun-ui/lib/cache.js
195
+ var Cache = class {
196
+ #max;
197
+ #maxAge;
198
+ #renewal;
199
+ #map = /* @__PURE__ */ new Map();
200
+ #reverseMap = /* @__PURE__ */ new Map();
201
+ #addedLinked = new LinkedList();
202
+ constructor({ max = Infinity, maxAge = Infinity, renewal = false } = {}) {
203
+ this.#max = max;
204
+ this.#maxAge = maxAge;
205
+ this.#renewal = renewal;
206
+ }
207
+ setOptions(options) {
208
+ this.#max = options.max ?? this.#max;
209
+ this.#maxAge = options.maxAge ?? this.#maxAge;
210
+ this.#renewal = options.renewal ?? this.#renewal;
211
+ }
212
+ #trim() {
213
+ for (let i = this.#addedLinked.size - this.#max; i > 0; i--) {
214
+ const value = this.#addedLinked.get();
215
+ const key = this.#reverseMap.get(value);
216
+ this.#reverseMap.delete(value);
217
+ this.#map.delete(key);
218
+ }
219
+ }
220
+ set(key, value) {
221
+ this.#addedLinked.add(value);
222
+ this.#reverseMap.set(value, key);
223
+ this.#map.set(key, { value, timestamp: Date.now() });
224
+ this.#trim();
225
+ return value;
226
+ }
227
+ get(key, init) {
228
+ const cache = this.#map.get(key);
229
+ if (!cache) {
230
+ return init && this.set(key, init(key));
231
+ }
232
+ const { timestamp, value } = cache;
233
+ if (Date.now() - timestamp > this.#maxAge) {
234
+ this.#addedLinked.delete(value);
235
+ this.#reverseMap.delete(value);
236
+ this.#map.delete(key);
237
+ return init && this.set(key, init(key));
238
+ }
239
+ if (this.#renewal) {
240
+ cache.timestamp = Date.now();
241
+ }
242
+ this.#addedLinked.add(value);
243
+ return value;
244
+ }
245
+ };
246
+
247
+ // src/cache.ts
248
+ var LRUCache = class {
249
+ #bucket;
250
+ constructor(args) {
251
+ this.#bucket = new Cache({ max: 25, renewal: true, ...args });
252
+ }
253
+ #genKey(context, position) {
254
+ return [context.fileName, position?.line, position?.character, context.text].join(";");
255
+ }
256
+ get(context, position, init) {
257
+ return this.#bucket.get(this.#genKey(context, position), init);
258
+ }
259
+ };
260
+
261
+ // src/data-provider.ts
262
+ var import_vscode_html_languageservice = require("@mantou/vscode-html-languageservice");
263
+
129
264
  // 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) {
265
+ function isCustomElementTag(tag) {
133
266
  return tag.includes("-");
134
267
  }
135
268
  function isDepElement(node) {
136
269
  return node.getSourceFile().fileName.includes("/node_modules/");
137
270
  }
138
- var openTagReg = /(?<prefix><)(?<tag>\w+-\w+)\s+/g;
139
- function forEachTag(typescript, containerNode, fn) {
140
- const list = [containerNode];
271
+ function bindMemberFunction(o, keys2 = Object.keys(o)) {
272
+ return Object.fromEntries(keys2.map((key) => [key, o[key].bind?.(o)]));
273
+ }
274
+ function forEachNode(roots, fn) {
275
+ const list = [...roots];
141
276
  while (true) {
142
277
  const currentNode = list.pop();
143
278
  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
- });
279
+ fn(currentNode);
280
+ list.push(...currentNode.children);
281
+ }
282
+ }
283
+ function getAstNodeAtPosition(typescript, node, pos) {
284
+ if (node.pos > pos || node.end <= pos) return;
285
+ while (node.kind >= typescript.SyntaxKind.FirstNode) {
286
+ const nested = typescript.forEachChild(node, (child) => child.pos <= pos && child.end > pos ? child : void 0);
287
+ if (nested === void 0) break;
288
+ node = nested;
154
289
  }
290
+ return node;
155
291
  }
292
+ var BEFORE_REG = /[^\s</>]+$/;
293
+ var AFTER_REG = /^[^\s</>]+/;
156
294
  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) || "";
295
+ const before = text.slice(0, offset).match(BEFORE_REG)?.at(0) || "";
296
+ const after = text.slice(offset).match(AFTER_REG)?.at(0) || "";
159
297
  const str = before + after;
160
298
  return {
299
+ before,
300
+ after,
161
301
  text: str,
162
302
  start: offset - before.length,
163
303
  length: str.length
164
304
  };
165
305
  }
166
- var attrMap = {
167
- ".": "property",
168
- "?": "boolean",
169
- "@": "event"
170
- };
171
306
  function getAttrName(text) {
172
307
  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
- }
308
+ const isNotLetter = hasDecoratorAttr(attr);
309
+ const offset = isNotLetter ? 1 : 0;
310
+ return {
311
+ attr: attr.slice(offset),
312
+ offset,
313
+ decorate: isNotLetter ? attr.at(0) : ""
314
+ };
195
315
  }
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");
316
+ function hasDecoratorAttr(str) {
317
+ return str.charCodeAt(0) < 65;
201
318
  }
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;
319
+ function getTagInfo(node, offset) {
320
+ const tag = node.tag;
321
+ const openStart = node.start + 1 + offset;
322
+ return {
323
+ node,
324
+ tag,
325
+ offset,
326
+ open: { start: openStart, length: tag.length },
327
+ end: node.endTagStart && {
328
+ start: node.endTagStart + 2 + offset,
329
+ length: node.end - node.endTagStart - 3
330
+ }
331
+ };
210
332
  }
211
333
  var marker = Symbol();
212
334
  function decorate(origin, cb) {
@@ -215,8 +337,244 @@ function decorate(origin, cb) {
215
337
  result[marker] = true;
216
338
  return result;
217
339
  }
340
+
341
+ // src/data-provider.ts
342
+ var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
343
+ var HTMLDataProvider = class {
344
+ #ts;
345
+ #elements;
346
+ #getProgram;
347
+ constructor(typescript, elements, getProgram) {
348
+ this.#ts = typescript;
349
+ this.#elements = elements;
350
+ this.#getProgram = getProgram;
351
+ }
352
+ getId() {
353
+ return NAME;
354
+ }
355
+ isApplicable() {
356
+ return true;
357
+ }
358
+ provideTags() {
359
+ return [...this.#elements].map(([tag, node]) => ({
360
+ name: tag,
361
+ attributes: [],
362
+ description: getDocComment(this.#ts, node)
363
+ }));
364
+ }
365
+ provideAttributes(tag) {
366
+ const ts = this.#ts;
367
+ const typeChecker = this.#getProgram().getTypeChecker();
368
+ const node = this.#elements.get(tag);
369
+ const result = [
370
+ { name: "v-if", description: "Similar to vue `v-if`" },
371
+ { name: "v-else-if", description: "Similar to vue `v-else-if`" },
372
+ { name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
373
+ ];
374
+ if (!node) return result;
375
+ const isDep = isDepElement(node);
376
+ const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
377
+ props.forEach((e) => {
378
+ const declaration = e.getDeclarations()?.at(0);
379
+ const prop = declaration && ts.isPropertyDeclaration(declaration);
380
+ if (!prop) return;
381
+ const hasPropDecorator = declaration.modifiers?.some((m) => ts.isDecorator(m) && ts.isIdentifier(m.expression));
382
+ if (!hasPropDecorator && !isDep) return;
383
+ const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
384
+ const typeText = declaration.type?.getText();
385
+ const description = getDocComment(ts, declaration);
386
+ switch (type) {
387
+ case typeChecker.getStringType():
388
+ case typeChecker.getNumberType():
389
+ result.push({ name: e.name, description });
390
+ break;
391
+ case typeChecker.getBooleanType():
392
+ result.push({ name: e.name, description, valueSet: "v" });
393
+ result.push({ name: `?${e.name}`, description });
394
+ break;
395
+ }
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 });
401
+ } else {
402
+ result.push({ name: `.${e.name}`, description });
403
+ }
404
+ });
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
+ });
412
+ return result;
413
+ }
414
+ 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 })) || [];
421
+ }
422
+ };
423
+ function getUnionValues(type) {
424
+ if (!type.isUnion()) return;
425
+ const result = [];
426
+ type.types.forEach((e) => {
427
+ if (!e.isLiteral()) return;
428
+ result.push(String(e.value));
429
+ });
430
+ return result;
431
+ }
432
+ var COMMENT_LINE_CONTENT = /^(\/?[ *\t]*)?(?<str>.*?)(\**\/)?$/gm;
433
+ function getDocComment(typescript, declaration) {
434
+ const fullText = declaration.getSourceFile().getFullText();
435
+ const commentRanges = typescript.getLeadingCommentRanges(fullText, declaration.getFullStart());
436
+ const commentStrings = commentRanges?.filter(({ kind }) => kind === typescript.SyntaxKind.MultiLineCommentTrivia).map(({ pos, end }) => {
437
+ const fullComment = [...fullText.slice(pos, end).matchAll(COMMENT_LINE_CONTENT)];
438
+ return fullComment.map((m) => m.groups.str).join("\n");
439
+ });
440
+ return commentStrings?.join("\n\n");
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, "_");
@@ -224,7 +582,7 @@ function getSubstitution(templateString, start, end) {
224
582
  function isValidCSSTemplate(typescript, node, callName) {
225
583
  switch (node.kind) {
226
584
  case typescript.SyntaxKind.NoSubstitutionTemplateLiteral:
227
- case typescript.SyntaxKind.TemplateExpression:
585
+ case typescript.SyntaxKind.TemplateExpression: {
228
586
  const parent = node.parent;
229
587
  if (typescript.isCallExpression(parent) && parent.expression.getText() === callName) {
230
588
  return true;
@@ -236,10 +594,18 @@ function isValidCSSTemplate(typescript, node, callName) {
236
594
  }
237
595
  }
238
596
  return false;
597
+ }
239
598
  default:
240
599
  return false;
241
600
  }
242
601
  }
602
+
603
+ // src/decorate-css.ts
604
+ var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
605
+ var import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
606
+
607
+ // src/translates.ts
608
+ var vscode = __toESM(require("vscode-languageserver-types"));
243
609
  function translateCompletionItemsToCompletionInfo(context, items) {
244
610
  return {
245
611
  defaultCommitCharacters: [],
@@ -297,8 +663,6 @@ function translationCompletionItemKind(context, kind) {
297
663
  return typescript.ScriptElementKind.alias;
298
664
  case vscode.CompletionItemKind.File:
299
665
  return typescript.ScriptElementKind.moduleElement;
300
- case vscode.CompletionItemKind.Snippet:
301
- case vscode.CompletionItemKind.Text:
302
666
  default:
303
667
  return typescript.ScriptElementKind.unknown;
304
668
  }
@@ -362,134 +726,168 @@ function genDefaultCompletionEntryDetails(context, name) {
362
726
  }
363
727
  function toDisplayParts(text, isDoc = false) {
364
728
  if (!text) return [];
365
- const escape = (unsafe) => unsafe.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll(" ", "&nbsp;").replaceAll("\n", " \n").replaceAll(" ", "&emsp;");
729
+ const escapeText = (unsafe) => unsafe.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll(" ", "&nbsp;").replaceAll("\n", " \n").replaceAll(" ", "&emsp;");
366
730
  return [
367
731
  {
368
732
  kind: "unknown",
369
- text: typeof text !== "string" ? text.value : isDoc ? escape(text) : text
733
+ text: typeof text !== "string" ? text.value : isDoc ? escapeText(text) : text
370
734
  }
371
735
  ];
372
736
  }
737
+ function genElementDefinitionInfo(context, { start, length }, definitionNode) {
738
+ const htmlOffset = context.node.pos + 1;
739
+ return {
740
+ textSpan: { start, length },
741
+ definitions: [
742
+ {
743
+ containerName: "Custom Element",
744
+ containerKind: context.typescript.ScriptElementKind.unknown,
745
+ name: definitionNode.name.text,
746
+ kind: context.typescript.ScriptElementKind.classElement,
747
+ fileName: definitionNode.getSourceFile().fileName,
748
+ textSpan: {
749
+ start: definitionNode.name.getStart() - htmlOffset,
750
+ length: definitionNode.name.text.length
751
+ }
752
+ }
753
+ ]
754
+ };
755
+ }
756
+ function genAttrDefinitionInfo(context, { start, length }, propDeclaration) {
757
+ const htmlOffset = context.node.pos + 1;
758
+ return {
759
+ textSpan: { start, length },
760
+ definitions: [
761
+ {
762
+ containerName: "Attribute",
763
+ containerKind: context.typescript.ScriptElementKind.unknown,
764
+ name: propDeclaration.getText(),
765
+ kind: context.typescript.ScriptElementKind.memberVariableElement,
766
+ fileName: propDeclaration.getSourceFile().fileName,
767
+ textSpan: {
768
+ start: propDeclaration.getStart() - htmlOffset,
769
+ length: propDeclaration.getText().length
770
+ }
771
+ }
772
+ ]
773
+ };
774
+ }
775
+ function genCurrentCtxDefinitionInfo(context, { start, length }, definitionTextSpan) {
776
+ return {
777
+ textSpan: { start, length },
778
+ definitions: [
779
+ {
780
+ containerName: "Attribute",
781
+ containerKind: context.typescript.ScriptElementKind.unknown,
782
+ name: context.text.slice(start, start + length),
783
+ kind: context.typescript.ScriptElementKind.memberVariableElement,
784
+ fileName: context.fileName,
785
+ textSpan: definitionTextSpan
786
+ }
787
+ ]
788
+ };
789
+ }
373
790
 
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;
791
+ // src/decorate-css.ts
792
+ var CSSLanguageService = class {
793
+ #completionsCache = new LRUCache({ max: 1 });
794
+ #diagnosticsCache = new LRUCache();
795
+ #ctx;
796
+ constructor(ctx) {
797
+ this.#ctx = ctx;
386
798
  }
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
- }
799
+ #normalize(context, position) {
800
+ const parent = context.node.parent;
801
+ const tag = context.typescript.isTaggedTemplateExpression(parent) && parent.tag.getText();
802
+ const isStyle = context.typescript.isPropertyAssignment(parent) || tag === "styled";
803
+ if (!isStyle) return { offset: 0, text: context.text, pos: { ...position } };
804
+ const appendBefore = ".parent { ";
805
+ const appendAfter = " }";
806
+ const character = position.line === 0 ? position.character + appendBefore.length : position.character;
807
+ return {
808
+ offset: appendBefore.length,
809
+ text: `${appendBefore}${context.text}${appendAfter}`,
810
+ pos: { line: position.line, character }
811
+ };
394
812
  }
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;
813
+ #getCompletionsAtPosition(context, position) {
814
+ return this.#completionsCache.get(context, position, () => {
815
+ const { text, pos } = this.#normalize(context, position);
816
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
817
+ let emmetResults;
818
+ const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
819
+ this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
820
+ (0, import_vscode_css_languageservice2.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
821
+ const completions = this.#ctx.cssLanguageService.doComplete(vDoc, pos, vCss);
822
+ completions.items.push(...emmetResults?.items || []);
823
+ return completions;
824
+ });
401
825
  }
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;
826
+ getCompletionsAtPosition(context, position) {
827
+ return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
420
828
  }
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(";");
829
+ getCompletionEntryDetails(context, position, name) {
830
+ const completions = this.#getCompletionsAtPosition(context, position);
831
+ const item = completions.items.find((e) => e.label === name);
832
+ if (!item) return genDefaultCompletionEntryDetails(context, name);
833
+ return translateCompletionItemsToCompletionEntryDetails(context, item);
428
834
  }
429
- getCached(context, position) {
430
- return this.#bucket.get(this.#genKey(context, position));
835
+ getQuickInfoAtPosition(context, position) {
836
+ const { text, pos } = this.#normalize(context, position);
837
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
838
+ const hover = this.#ctx.cssLanguageService.doHover(vDoc, pos, vCss, {
839
+ documentation: true,
840
+ references: true
841
+ });
842
+ if (!hover) return;
843
+ return translateHover(context, hover, position, pos.character - position.character);
431
844
  }
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);
845
+ #getSyntacticDiagnostics(context) {
846
+ const { text, offset } = this.#normalize(context, { line: 0, character: 0 });
847
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
848
+ const oDiagnostics = this.#ctx.cssLanguageService.doValidation(vDoc, vCss);
849
+ const file = this.#ctx.getProgram().getSourceFile(context.fileName);
850
+ return oDiagnostics.map(({ message, range }) => {
851
+ const start = context.toOffset(range.start);
852
+ return {
853
+ category: context.typescript.DiagnosticCategory.Warning,
854
+ code: 0,
855
+ file,
856
+ start: range.start.line === 0 ? start - offset : start,
857
+ length: context.toOffset(range.end) - start,
858
+ messageText: message,
859
+ source: NAME
860
+ };
861
+ });
862
+ }
863
+ getSyntacticDiagnostics(context) {
864
+ this.#ctx.initElements();
865
+ return this.#diagnosticsCache.get(context, void 0, () => this.#getSyntacticDiagnostics(context));
866
+ }
867
+ getDefinitionAndBoundSpan(context, position) {
868
+ const { text, offset } = this.#normalize(context, position);
869
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
870
+ const empty = { textSpan: { start: 0, length: 0 } };
871
+ const node = vCss.findChildAtOffset(context.toOffset(position), true);
872
+ if (!node) return empty;
873
+ const ident = vDoc.getText().slice(node.offset, node.end);
874
+ const definitionNode = this.#ctx.elements.get(ident);
875
+ if (!definitionNode) return empty;
876
+ return genElementDefinitionInfo(context, { start: node.offset - offset, length: node.length }, definitionNode);
443
877
  }
444
878
  };
445
879
 
446
880
  // src/decorate-html.ts
881
+ var import_vscode_css_languageservice3 = require("@mantou/vscode-css-languageservice");
882
+ var import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
447
883
  var HTMLLanguageService = class {
448
- #completionsCache = new LRUCache();
884
+ #completionsCache = new LRUCache({ max: 1 });
449
885
  #diagnosticsCache = new LRUCache();
450
- #cssLanguageService;
451
- #htmlLanguageService;
452
886
  #ctx;
453
887
  constructor(ctx) {
454
888
  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
889
  }
492
- #getAllCssDoc(doc) {
890
+ #getAllStyleSheet(doc) {
493
891
  const styles = [];
494
892
  const nodes = [...doc.roots];
495
893
  while (true) {
@@ -500,17 +898,17 @@ var HTMLLanguageService = class {
500
898
  }
501
899
  return styles;
502
900
  }
503
- #getCssDoc(context, position, doc) {
901
+ #getEmbeddedCss(context, position, doc) {
504
902
  const node = doc.findNodeAt(context.toOffset(position));
505
903
  if (node.tag !== "style") return;
506
- const virtualDocument = createVirtualDocument("css", context.text.slice(node.startTagEnd, node.endTagStart));
507
- const style = this.#cssLanguageService.parseStylesheet(virtualDocument);
904
+ const text = context.text.slice(node.startTagEnd, node.endTagStart);
905
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
508
906
  const offset = context.toOffset(position) - node.startTagEnd;
509
- const toPosition = (pos) => context.toPosition(virtualDocument.offsetAt(pos) + node.startTagEnd);
907
+ const toPosition = (pos) => context.toPosition(vDoc.offsetAt(pos) + node.startTagEnd);
510
908
  return {
511
- style,
512
- virtualDocument,
513
- position: virtualDocument.positionAt(offset),
909
+ style: vCss,
910
+ vDoc,
911
+ position: vDoc.positionAt(offset),
514
912
  updateRange: (range) => ({
515
913
  start: toPosition(range.start),
516
914
  end: toPosition(range.end)
@@ -518,43 +916,34 @@ var HTMLLanguageService = class {
518
916
  };
519
917
  }
520
918
  #getCSSCompletionsAtPosition(context, position, doc) {
521
- const css = this.#getCssDoc(context, position, doc);
919
+ const css = this.#getEmbeddedCss(context, position, doc);
522
920
  if (!css) return [];
523
921
  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);
922
+ const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper2.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
923
+ this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
924
+ (0, import_vscode_css_languageservice3.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
925
+ const completions = this.#ctx.cssLanguageService.doComplete(css.vDoc, css.position, css.style);
532
926
  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
- }));
927
+ return completions.items.map((e) => {
928
+ const textEdit = e.textEdit && "range" in e.textEdit && e.textEdit;
929
+ const newTextEdit = textEdit && { newText: textEdit.newText, range: css.updateRange(textEdit.range) };
930
+ return { ...e, textEdit: newTextEdit || e.textEdit };
931
+ });
540
932
  }
541
933
  #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);
934
+ return this.#completionsCache.get(context, position, () => {
935
+ const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
936
+ 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
+ };
941
+ this.#ctx.htmlLanguageService.setCompletionParticipants([{ onHtmlContent }]);
942
+ const completions = this.#ctx.htmlLanguageService.doComplete(vDoc, position, vHtml);
943
+ completions.items.push(...emmetResults?.items || []);
944
+ completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
945
+ return completions;
946
+ });
558
947
  }
559
948
  getCompletionsAtPosition(context, position) {
560
949
  return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
@@ -566,163 +955,326 @@ var HTMLLanguageService = class {
566
955
  return translateCompletionItemsToCompletionEntryDetails(context, item);
567
956
  }
568
957
  #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, {
958
+ const css = this.#getEmbeddedCss(context, position, doc);
959
+ if (!css) return;
960
+ const hover = this.#ctx.cssLanguageService.doHover(css.vDoc, css.position, css.style, {
571
961
  documentation: true,
572
962
  references: true
573
963
  });
574
- return hover && {
575
- ...hover,
576
- range: hover.range && css.updateRange(hover.range)
577
- };
964
+ if (!hover) return;
965
+ return { ...hover, range: hover.range && css.updateRange(hover.range) };
578
966
  }
579
967
  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, {
968
+ const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
969
+ const htmlHover = this.#ctx.htmlLanguageService.doHover(vDoc, position, vHtml, {
583
970
  documentation: true,
584
971
  references: true
585
- }) || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
972
+ });
973
+ const hover = htmlHover || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
586
974
  if (!hover) return;
587
975
  return translateHover(context, hover, position);
588
976
  }
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 {
977
+ #getCssSyntacticDiagnostics(context) {
978
+ return this.#diagnosticsCache.get(context, void 0, () => {
979
+ const { vHtml } = this.#ctx.getHtmlDoc(context.text);
980
+ const file = this.#ctx.getProgram().getSourceFile(context.fileName);
981
+ const styles = this.#getAllStyleSheet(vHtml);
982
+ const diagnostics = [];
983
+ styles.forEach((node) => {
984
+ const text = context.text.slice(node.startTagEnd, node.endTagStart);
985
+ const { vDoc, vCss } = this.#ctx.getCssDoc(text);
986
+ this.#ctx.cssLanguageService.doValidation(vDoc, vCss).forEach(({ message, range }) => {
987
+ const start = node.startTagEnd + vDoc.offsetAt(range.start);
988
+ const end = node.startTagEnd + vDoc.offsetAt(range.end);
989
+ diagnostics.push({
606
990
  category: context.typescript.DiagnosticCategory.Warning,
607
991
  code: 0,
608
992
  file,
609
993
  start,
610
994
  length: end - start,
611
- messageText: message
612
- };
995
+ messageText: message,
996
+ source: NAME
997
+ });
613
998
  });
614
- }).flat()
615
- );
999
+ });
1000
+ return diagnostics;
1001
+ });
1002
+ }
1003
+ #getHtmlSyntacticDiagnostics(context) {
1004
+ const offset = context.node.getStart() + 1;
1005
+ const { vHtml } = this.#ctx.getHtmlDoc(context.text);
1006
+ const program = this.#ctx.getProgram();
1007
+ const file = program.getSourceFile(context.fileName);
1008
+ const typeChecker = program.getTypeChecker();
1009
+ const diagnostics = [];
1010
+ forEachNode(vHtml.roots, (node) => {
1011
+ if (!node.tag) return;
1012
+ if (isCustomElementTag(node.tag) && !this.#ctx.elements.get(node.tag)) {
1013
+ diagnostics.push({
1014
+ category: context.typescript.DiagnosticCategory.Warning,
1015
+ code: 101 /* UnknownTag */,
1016
+ file,
1017
+ start: node.start + 1,
1018
+ length: node.tag.length,
1019
+ messageText: `Unknown element tag '${node.tag}'`,
1020
+ source: NAME
1021
+ });
1022
+ }
1023
+ const tagDeclaration = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
1024
+ if (!tagDeclaration) return;
1025
+ for (const [attributeName, { value, start, end }] of node.attributesMap) {
1026
+ if (attributeName.startsWith("_")) continue;
1027
+ const hasValueSpan = value?.startsWith("_");
1028
+ const attrInfo = getAttrName(attributeName);
1029
+ const propType = getPropType(typeChecker, tagDeclaration, attrInfo);
1030
+ const diagnostic = {
1031
+ category: context.typescript.DiagnosticCategory.Warning,
1032
+ file,
1033
+ start,
1034
+ length: end - start,
1035
+ source: NAME,
1036
+ code: 103 /* PropTypeError */,
1037
+ messageText: !propType ? `'${attributeName}' type error` : `'${attributeName}' not satisfied '${typeChecker.typeToString(propType)}'`
1038
+ };
1039
+ if ((attributeName === "v-else-if" || attributeName === "v-else") && !node.prev?.attributesMap.has("v-if") && !node.prev?.attributesMap.has("v-else-if")) {
1040
+ diagnostics.push({
1041
+ ...diagnostic,
1042
+ code: 104 /* PropSyntaxError */,
1043
+ messageText: `'${attrInfo.attr}' syntax error`
1044
+ });
1045
+ }
1046
+ if (attributeName === "v-if" || attributeName === "v-else-if") {
1047
+ const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1048
+ if (!hasValueSpan || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getBooleanType())) {
1049
+ diagnostics.push(diagnostic);
1050
+ }
1051
+ continue;
1052
+ }
1053
+ if (attributeName === "v-else") {
1054
+ if (value !== null) {
1055
+ diagnostics.push(diagnostic);
1056
+ }
1057
+ continue;
1058
+ }
1059
+ if (!propType) {
1060
+ if (attrInfo.decorate !== "@") {
1061
+ diagnostics.push({
1062
+ ...diagnostic,
1063
+ code: 102 /* UnknownProp */,
1064
+ messageText: `Unknown property '${attrInfo.attr}'`
1065
+ });
1066
+ }
1067
+ continue;
1068
+ }
1069
+ if (value === null) {
1070
+ if (attrInfo.decorate) {
1071
+ diagnostics.push({
1072
+ ...diagnostic,
1073
+ code: 104 /* PropSyntaxError */,
1074
+ messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
1075
+ });
1076
+ } else if (propType !== typeChecker.getBooleanType()) {
1077
+ diagnostics.push(diagnostic);
1078
+ }
1079
+ continue;
1080
+ }
1081
+ if (hasValueSpan) {
1082
+ const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1083
+ switch (attrInfo.decorate) {
1084
+ case "?": {
1085
+ const boolType = getUnionType(typeChecker, [
1086
+ typeChecker.getBooleanType(),
1087
+ typeChecker.getUndefinedType(),
1088
+ typeChecker.getNullType()
1089
+ ]);
1090
+ if (!typeChecker.isTypeAssignableTo(spanType, boolType)) {
1091
+ diagnostics.push(diagnostic);
1092
+ }
1093
+ continue;
1094
+ }
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
+ }
1125
+ }
1126
+ });
1127
+ return diagnostics;
1128
+ }
1129
+ getSyntacticDiagnostics(context) {
1130
+ this.#ctx.initElements();
1131
+ return [...this.#getCssSyntacticDiagnostics(context), ...this.#getHtmlSyntacticDiagnostics(context)];
616
1132
  }
617
1133
  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 } };
1134
+ const currentOffset = context.toOffset(position);
1135
+ 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) {
1140
+ const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
1141
+ 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);
1145
+ if (!definitionNode2) return empty;
1146
+ return genElementDefinitionInfo(
1147
+ context,
1148
+ { start: cssNode.offset + node.startTagEnd, length: cssNode.length },
1149
+ definitionNode2
1150
+ );
627
1151
  }
1152
+ const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
1153
+ if (!definitionNode || currentOffset > node.startTagEnd || !text) return empty;
628
1154
  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
- };
1155
+ return genElementDefinitionInfo(context, { start, length }, definitionNode);
1156
+ }
1157
+ const { attr, offset } = getAttrName(text);
1158
+ if (before.length > attr.length) return empty;
1159
+ if (attr === "v-else" || attr === "v-else-if") {
1160
+ const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
1161
+ if (!ifAttr) return empty;
1162
+ return genCurrentCtxDefinitionInfo(
1163
+ context,
1164
+ { start: start + offset, length: attr.length },
1165
+ { start: ifAttr.start, length: ifAttr.end - ifAttr.start }
1166
+ );
645
1167
  }
646
- const { attr, type } = getAttrName(text);
647
1168
  const typeChecker = this.#ctx.getProgram().getTypeChecker();
648
1169
  const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
649
1170
  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
- };
1171
+ if (!propDeclaration) return empty;
1172
+ return genAttrDefinitionInfo(context, { start: start + offset, length: attr.length }, propDeclaration);
666
1173
  }
667
1174
  };
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;
1175
+ function getSpanExpression(typescript, file, pos) {
1176
+ let node = getAstNodeAtPosition(typescript, file, pos);
1177
+ while (!typescript.isTemplateSpan(node)) {
1178
+ node = node.parent;
1179
+ if (!node) return;
1180
+ }
1181
+ return node.expression;
687
1182
  }
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);
1183
+ function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
1184
+ const valueOffset = attrNameEnd + htmlOffset + 3;
1185
+ const spanExp = getSpanExpression(typescript, file, valueOffset);
1186
+ return typeChecker.getTypeAtLocation(spanExp);
1187
+ }
1188
+ function getPropType(typeChecker, tagClassDeclaration, attrInfo) {
1189
+ const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
1190
+ if (attrInfo.attr.startsWith("data-")) {
1191
+ return typeChecker.getStringType();
1192
+ }
1193
+ const propName = kebabToCamelCase(attrInfo.attr);
1194
+ switch (propName) {
1195
+ case "class":
1196
+ case "style":
1197
+ case "part":
1198
+ case "exportparts":
1199
+ case "xmlns":
1200
+ case "viewBox":
1201
+ return typeChecker.getStringType();
1202
+ case "tabindex":
1203
+ return typeChecker.getNumberType();
1204
+ case "ariaDisabled":
1205
+ case "ariaChecked":
1206
+ case "ariaHidden":
1207
+ return getUnionType(typeChecker, [
1208
+ typeChecker.getStringType(),
1209
+ typeChecker.getBooleanType(),
1210
+ typeChecker.getUndefinedType()
1211
+ ]);
1212
+ default: {
1213
+ const isEvent = attrInfo.decorate === "@";
1214
+ const propSymbol = classType.getProperty(propName);
1215
+ const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
1216
+ if (!isEvent) return propType;
1217
+ const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
1218
+ return getUnionType(typeChecker, [eventHandleType, typeChecker.getUndefinedType()]);
694
1219
  }
695
- });
1220
+ }
1221
+ }
1222
+ function getEmitterHandleType(typeChecker, classType, propType) {
1223
+ const handleSymbol = propType?.getProperty("handler");
1224
+ if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
1225
+ const addEventListenerSymbol = classType.getProperty("addEventListener");
1226
+ if (!addEventListenerSymbol) return typeChecker.getAnyType();
1227
+ const addEventListenerDecls = addEventListenerSymbol.declarations;
1228
+ const addEventListenerDecl = addEventListenerDecls.find((e) => !e.typeParameters);
1229
+ const listenerDecl = addEventListenerDecl.parameters.at(1);
1230
+ if (!listenerDecl) return typeChecker.getAnyType();
1231
+ return typeChecker.getTypeAtLocation(listenerDecl);
1232
+ }
1233
+ function getUnionType(typeChecker, types) {
1234
+ if ("getUnionType" in typeChecker) {
1235
+ return typeChecker.getUnionType(types);
1236
+ }
1237
+ return types.at(0);
696
1238
  }
1239
+
1240
+ // src/decorate-ts.ts
697
1241
  function decorateLanguageService(ctx, languageService) {
698
1242
  const { ts, getProgram } = ctx;
699
- const ls = Object.fromEntries(
700
- Object.entries(languageService).map(([key, value]) => [key, value.bind(languageService)])
701
- );
1243
+ const ls = bindMemberFunction(languageService);
702
1244
  languageService.getCompletionsAtPosition = (...args) => {
703
1245
  const program = getProgram();
704
1246
  const typeChecker = program.getTypeChecker();
705
1247
  decorate(typeChecker, () => decorateTypeChecker(ctx, typeChecker));
706
1248
  return ls.getCompletionsAtPosition(...args);
707
1249
  };
1250
+ languageService.getSuggestionDiagnostics = (...args) => {
1251
+ const program = getProgram();
1252
+ const file = program.getSourceFile(args[0]);
1253
+ const result = ls.getSuggestionDiagnostics(...args);
1254
+ ctx.updateElement(file);
1255
+ return result.filter(({ start, reportsUnnecessary, category }) => {
1256
+ if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
1257
+ const node = getAstNodeAtPosition(ts, file, start);
1258
+ if (!node || !ts.isPrivateIdentifier(node)) return true;
1259
+ const declaration = node.parent;
1260
+ if (!ts.isMethodDeclaration(declaration) && !ts.isPropertyDeclaration(declaration)) return true;
1261
+ return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
1262
+ });
1263
+ };
708
1264
  languageService.findReferences = (...args) => {
1265
+ const oResult = ls.findReferences(...args) || [];
709
1266
  const program = getProgram();
710
- const result = [];
711
1267
  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,
1268
+ if (!currentNode) return oResult;
1269
+ const isIdent = ts.isIdentifier(currentNode);
1270
+ if (!isIdent) return oResult;
1271
+ const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
1272
+ const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
1273
+ 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: [],
726
1278
  definition: {
727
1279
  containerKind: ctx.ts.ScriptElementKind.unknown,
728
1280
  containerName: "",
@@ -732,174 +1284,163 @@ function decorateLanguageService(ctx, languageService) {
732
1284
  name: "test",
733
1285
  kind: ctx.ts.ScriptElementKind.unknown
734
1286
  }
735
- });
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
1304
+ });
1305
+ }
1306
+ });
1307
+ return [...map.values(), ...oResult];
1308
+ };
1309
+ languageService.getRenameInfo = (fileName, position, ...args) => {
1310
+ const result = ls.getRenameInfo(fileName, position, ...args);
1311
+ const tagInfo = findCurrentTagInfo(ctx, fileName, position);
1312
+ if (tagInfo) {
1313
+ return {
1314
+ canRename: true,
1315
+ displayName: tagInfo.tag,
1316
+ fullDisplayName: tagInfo.tag,
1317
+ kind: ts.ScriptElementKind.alias,
1318
+ kindModifiers: "tag",
1319
+ triggerSpan: tagInfo.open
1320
+ };
736
1321
  }
737
- return [...result, ...ls.findReferences(...args) || []];
1322
+ const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1323
+ if (tagDefinedInfo) {
1324
+ return {
1325
+ canRename: true,
1326
+ displayName: tagDefinedInfo.tag,
1327
+ fullDisplayName: tagDefinedInfo.tag,
1328
+ kind: ts.ScriptElementKind.alias,
1329
+ kindModifiers: "tag",
1330
+ triggerSpan: tagDefinedInfo.textSpan
1331
+ };
1332
+ }
1333
+ return result;
738
1334
  };
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
- });
1335
+ languageService.findRenameLocations = (fileName, position, ...args) => {
1336
+ const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
1337
+ if (tagPairInfo) {
1338
+ const result = [{ fileName, textSpan: tagPairInfo.open }];
1339
+ if (tagPairInfo.end) result.push({ fileName, textSpan: tagPairInfo.end });
1340
+ return result;
1341
+ }
1342
+ const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1343
+ if (tagDefinedInfo) {
1344
+ const result = [{ fileName, textSpan: tagDefinedInfo.textSpan }];
1345
+ forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (f, info) => {
1346
+ result.push({ fileName: f.fileName, textSpan: info.open });
1347
+ if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
1348
+ });
1349
+ return result;
1350
+ }
1351
+ const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
1352
+ const file = ctx.getProgram().getSourceFile(fileName);
1353
+ const node = getAstNodeAtPosition(ctx.ts, file, position);
1354
+ const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
1355
+ if (!tag) return oResult;
1356
+ const propText = node.getText();
1357
+ 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);
1371
+ if (!info) return;
1372
+ const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1373
+ oResult.push({ fileName: f.fileName, prefixText: ".", textSpan });
1374
+ });
1375
+ });
1376
+ }
1377
+ return oResult;
756
1378
  };
757
1379
  return languageService;
758
1380
  }
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();
1381
+ function forEachAllHtmlTemplateNode(ctx, tag, fn) {
1382
+ for (const file of ctx.getProgram().getSourceFiles()) {
1383
+ if (file.fileName.endsWith(".d.ts")) continue;
1384
+ 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
+ });
1390
+ }
797
1391
  }
798
- findKey(val) {
799
- return this.#weakMap.get(val);
1392
+ }
1393
+ function findCurrentTagInfo(ctx, fileName, position) {
1394
+ const templateContext = ctx.htmlSourceHelper.getTemplate(fileName, position);
1395
+ if (!templateContext) return;
1396
+ const htmlOffset = templateContext.node.pos + 1;
1397
+ const { vHtml } = ctx.getHtmlDoc(templateContext.text);
1398
+ const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
1399
+ const offset = templateContext.toOffset(relativePosition);
1400
+ 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);
1405
+ }
1406
+ function findDefinedTagInfo(ctx, fileName, position) {
1407
+ const file = ctx.getProgram().getSourceFile(fileName);
1408
+ const node = getAstNodeAtPosition(ctx.ts, file, position);
1409
+ if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !== "customElement") {
1410
+ return;
800
1411
  }
801
- *[Symbol.iterator]() {
802
- const entries2 = this.#map.entries();
803
- for (const [tag, ref] of entries2) {
804
- yield [tag, ref.deref()];
1412
+ const tag = node.text;
1413
+ return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
1414
+ }
1415
+ function isPropType(typescript, node, types) {
1416
+ if (!typescript.isPropertyDeclaration(node)) return;
1417
+ for (const modifier of node.modifiers || []) {
1418
+ if (!typescript.isDecorator(modifier)) continue;
1419
+ const { expression } = modifier;
1420
+ if (typescript.isIdentifier(expression) && types.includes(expression.text)) {
1421
+ return true;
805
1422
  }
806
1423
  }
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
- };
1424
+ }
1425
+ function decorateTypeChecker(ctx, typeChecker) {
1426
+ const internal = typeChecker;
1427
+ const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
1428
+ internal.isValidPropertyAccessForCompletions = (...args) => {
1429
+ const result = checker.isValidPropertyAccessForCompletions(...args);
1430
+ if (!result) return false;
1431
+ 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;
1438
+ } catch {
1439
+ return true;
834
1440
  }
835
- return {
836
- offset: 0,
837
- text: context.text,
838
- pos: { ...position }
839
- };
840
1441
  };
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
- };
1442
+ return typeChecker;
1443
+ }
903
1444
 
904
1445
  // src/index.ts
905
1446
  var LanguageServiceLogger = class {
@@ -908,7 +1449,7 @@ var LanguageServiceLogger = class {
908
1449
  this.#info = info;
909
1450
  }
910
1451
  log(msg) {
911
- this.#info.project.projectService.logger.info(`[gem] ${msg}`);
1452
+ this.#info.project.projectService.logger.info(`[${NAME}] ${msg}`);
912
1453
  }
913
1454
  };
914
1455
  var HtmlPlugin = class {
@@ -920,32 +1461,16 @@ var HtmlPlugin = class {
920
1461
  create(info) {
921
1462
  return decorate(info.languageService, () => {
922
1463
  const logger = new LanguageServiceLogger(info);
923
- logger.log("Starting ts-gem-plugin...");
1464
+ logger.log("Starting...");
924
1465
  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
- };
1466
+ const context = new Context(this.#ts, this.#config, info, logger);
937
1467
  const decoratedService = decorateLanguageService(context, info.languageService);
938
1468
  const decoratedService1 = (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
939
1469
  this.#ts,
940
1470
  decoratedService,
941
1471
  info.project,
942
1472
  new CSSLanguageService(context),
943
- {
944
- tags: ["styled", "css"],
945
- enableForStringWithSubstitutions: true,
946
- getSubstitution,
947
- isValidTemplate: (node) => isValidCSSTemplate(this.#ts, node, "css")
948
- },
1473
+ context.cssTemplateStringSettings,
949
1474
  { logger }
950
1475
  );
951
1476
  return (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
@@ -953,11 +1478,7 @@ var HtmlPlugin = class {
953
1478
  decoratedService1,
954
1479
  info.project,
955
1480
  new HTMLLanguageService(context),
956
- {
957
- tags: ["html", "raw", "h"],
958
- enableForStringWithSubstitutions: true,
959
- getSubstitution
960
- },
1481
+ context.htmlTemplateStringSettings,
961
1482
  { logger }
962
1483
  );
963
1484
  });