ts-gem-plugin 0.0.8 → 0.0.11

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/package.json +6 -6
  2. package/dist/index.js +0 -1512
package/dist/index.js DELETED
@@ -1,1512 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
-
25
- // src/index.ts
26
- var import_typescript_template_language_service_decorator = require("@mantou/typescript-template-language-service-decorator");
27
-
28
- // ../gem/lib/utils.js
29
- var { assign, fromEntries, entries, keys } = Object;
30
- var LinkedList = class extends EventTarget {
31
- #map = /* @__PURE__ */ new Map();
32
- #firstItem;
33
- #lastItem;
34
- #delete(value) {
35
- const existItem = this.#map.get(value);
36
- if (existItem) {
37
- if (existItem.prev) {
38
- existItem.prev.next = existItem.next;
39
- } else {
40
- this.#firstItem = existItem.next;
41
- }
42
- if (existItem.next) {
43
- existItem.next.prev = existItem.prev;
44
- } else {
45
- this.#lastItem = existItem.prev;
46
- }
47
- this.#map.delete(value);
48
- }
49
- return existItem;
50
- }
51
- get size() {
52
- return this.#map.size;
53
- }
54
- get first() {
55
- return this.#firstItem;
56
- }
57
- get last() {
58
- return this.#lastItem;
59
- }
60
- isSuperLinkOf(subLink) {
61
- let subItem = subLink.first;
62
- if (!subItem)
63
- return true;
64
- let item = this.find(subItem.value);
65
- while (item && item.value === subItem.value) {
66
- subItem = subItem.next;
67
- if (!subItem)
68
- return true;
69
- item = item.next;
70
- }
71
- return false;
72
- }
73
- find(value) {
74
- return this.#map.get(value);
75
- }
76
- /**
77
- * 添加到尾部,已存在时会删除老的项目
78
- * 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
79
- */
80
- add(value) {
81
- if (!this.#lastItem) {
82
- this.dispatchEvent(new CustomEvent("start"));
83
- }
84
- const item = this.#delete(value) || { value };
85
- item.prev = this.#lastItem;
86
- if (item.prev) {
87
- item.prev.next = item;
88
- }
89
- item.next = void 0;
90
- this.#lastItem = item;
91
- if (!this.#firstItem) {
92
- this.#firstItem = item;
93
- }
94
- this.#map.set(value, item);
95
- }
96
- /** 删除这个元素后没有其他元素时立即出发 end 事件 */
97
- delete(value) {
98
- const deleteItem = this.#delete(value);
99
- if (!this.#firstItem) {
100
- this.dispatchEvent(new CustomEvent("end"));
101
- }
102
- return deleteItem;
103
- }
104
- /** 获取头部元素,会从链表删除 */
105
- get() {
106
- const firstItem = this.#firstItem;
107
- if (!firstItem)
108
- return;
109
- this.delete(firstItem.value);
110
- return firstItem.value;
111
- }
112
- };
113
- function camelToKebabCase(str) {
114
- return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
115
- }
116
- function kebabToCamelCase(str) {
117
- return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
118
- }
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
-
264
- // src/utils.ts
265
- function isCustomElementTag(tag) {
266
- return tag.includes("-");
267
- }
268
- function isDepElement(node) {
269
- const { fileName } = node.getSourceFile();
270
- return ["/node_modules/", "/dist/", ".d.ts"].some((s) => fileName.includes(s));
271
- }
272
- function bindMemberFunction(o, keys2 = Object.keys(o)) {
273
- return Object.fromEntries(keys2.map((key) => [key, o[key].bind?.(o)]));
274
- }
275
- function forEachNode(roots, fn) {
276
- const list = [...roots];
277
- while (true) {
278
- const currentNode = list.pop();
279
- if (!currentNode) return;
280
- fn(currentNode);
281
- list.push(...currentNode.children);
282
- }
283
- }
284
- function getAstNodeAtPosition(typescript, node, pos) {
285
- if (node.pos > pos || node.end <= pos) return;
286
- while (node.kind >= typescript.SyntaxKind.FirstNode) {
287
- const nested = typescript.forEachChild(node, (child) => child.pos <= pos && child.end > pos ? child : void 0);
288
- if (nested === void 0) break;
289
- node = nested;
290
- }
291
- return node;
292
- }
293
- var BEFORE_REG = /[^\s</>]+$/;
294
- var AFTER_REG = /^[^\s</>]+/;
295
- function getHTMLTextAtPosition(text, offset) {
296
- const before = text.slice(0, offset).match(BEFORE_REG)?.at(0) || "";
297
- const after = text.slice(offset).match(AFTER_REG)?.at(0) || "";
298
- const str = before + after;
299
- return {
300
- before,
301
- after,
302
- text: str,
303
- start: offset - before.length,
304
- length: str.length
305
- };
306
- }
307
- function getAttrName(text) {
308
- const attr = text.split("=").at(0);
309
- const isNotLetter = hasDecoratorAttr(attr);
310
- const offset = isNotLetter ? 1 : 0;
311
- return {
312
- attr: attr.slice(offset),
313
- offset,
314
- decorate: isNotLetter ? attr.at(0) : ""
315
- };
316
- }
317
- function hasDecoratorAttr(str) {
318
- return str.charCodeAt(0) < 65;
319
- }
320
- function getTagInfo(node, offset) {
321
- const tag = node.tag;
322
- const openStart = node.start + 1 + offset;
323
- return {
324
- node,
325
- tag,
326
- offset,
327
- open: { start: openStart, length: tag.length },
328
- end: node.endTagStart && {
329
- start: node.endTagStart + 2 + offset,
330
- length: node.end - node.endTagStart - 3
331
- }
332
- };
333
- }
334
- var marker = Symbol();
335
- function decorate(origin, cb) {
336
- if (origin[marker]) return origin;
337
- const result = cb(origin);
338
- result[marker] = true;
339
- return result;
340
- }
341
-
342
- // src/data-provider.ts
343
- var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
344
- var HTMLDataProvider = class {
345
- #ts;
346
- #elements;
347
- #getProgram;
348
- constructor(typescript, elements, getProgram) {
349
- this.#ts = typescript;
350
- this.#elements = elements;
351
- this.#getProgram = getProgram;
352
- }
353
- getId() {
354
- return NAME;
355
- }
356
- isApplicable() {
357
- return true;
358
- }
359
- provideTags() {
360
- return [...this.#elements].map(([tag, node]) => ({
361
- name: tag,
362
- attributes: [],
363
- description: getDocComment(this.#ts, node)
364
- }));
365
- }
366
- provideAttributes(tag) {
367
- const ts = this.#ts;
368
- const typeChecker = this.#getProgram().getTypeChecker();
369
- const node = this.#elements.get(tag);
370
- const result = [
371
- { name: "v-if", description: "Similar to vue `v-if`" },
372
- { name: "v-else-if", description: "Similar to vue `v-else-if`" },
373
- { name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
374
- ];
375
- if (!node) return result;
376
- const isDep = isDepElement(node);
377
- const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
378
- props.forEach((e) => {
379
- const declaration = e.getDeclarations()?.at(0);
380
- const prop = declaration && ts.isPropertyDeclaration(declaration);
381
- if (!prop) return;
382
- const hasPropDecorator = declaration.modifiers?.some((m) => ts.isDecorator(m) && ts.isIdentifier(m.expression));
383
- if (!hasPropDecorator && !isDep) return;
384
- const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
385
- const typeText = declaration.type?.getText();
386
- const description = getDocComment(ts, declaration);
387
- switch (type) {
388
- case typeChecker.getStringType():
389
- case typeChecker.getNumberType():
390
- result.push({ name: e.name, description });
391
- break;
392
- case typeChecker.getBooleanType():
393
- result.push({ name: e.name, description, valueSet: "v" });
394
- result.push({ name: `?${e.name}`, description });
395
- break;
396
- }
397
- if (type && getUnionValues(type)) {
398
- result.push({ name: e.name, description });
399
- }
400
- if (typeText?.startsWith("Emitter")) {
401
- result.push({ name: `@${e.name}`, description });
402
- } else {
403
- result.push({ name: `.${e.name}`, description });
404
- }
405
- });
406
- const oResult = dataProvider.provideAttributes(isCustomElementTag(tag) ? "div" : tag);
407
- oResult.forEach((data) => {
408
- const tryEvtName = data.name.replace(/^on/, "@");
409
- if (tryEvtName !== data.name) {
410
- result.push({ ...data, name: tryEvtName });
411
- }
412
- });
413
- return result;
414
- }
415
- provideValues(tag, attr) {
416
- const typeChecker = this.#getProgram().getTypeChecker();
417
- const node = this.#elements.get(tag);
418
- if (!node) return [];
419
- const prop = typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
420
- const result = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
421
- return result?.map((name) => ({ name })) || [];
422
- }
423
- };
424
- function getUnionValues(type) {
425
- if (!type.isUnion()) return;
426
- const result = [];
427
- type.types.forEach((e) => {
428
- if (!e.isLiteral()) return;
429
- result.push(String(e.value));
430
- });
431
- return result;
432
- }
433
- var COMMENT_LINE_CONTENT = /^(\/?[ *\t]*)?(?<str>.*?)(\**\/)?$/gm;
434
- function getDocComment(typescript, declaration) {
435
- const fullText = declaration.getSourceFile().getFullText();
436
- const commentRanges = typescript.getLeadingCommentRanges(fullText, declaration.getFullStart());
437
- const commentStrings = commentRanges?.filter(({ kind }) => kind === typescript.SyntaxKind.MultiLineCommentTrivia).map(({ pos, end }) => {
438
- const fullComment = [...fullText.slice(pos, end).matchAll(COMMENT_LINE_CONTENT)];
439
- return fullComment.map((m) => m.groups.str).join("\n");
440
- });
441
- return commentStrings?.join("\n\n");
442
- }
443
-
444
- // src/context.ts
445
- var Context = class {
446
- elements;
447
- builtInElements;
448
- ts;
449
- config;
450
- project;
451
- logger;
452
- dataProvider;
453
- cssLanguageService;
454
- htmlLanguageService;
455
- htmlSourceHelper;
456
- htmlTemplateStringSettings;
457
- cssTemplateStringSettings;
458
- getProgram;
459
- constructor(typescript, config, info, logger) {
460
- this.ts = typescript;
461
- this.config = config;
462
- this.getProgram = () => info.languageService.getProgram();
463
- this.project = info.project;
464
- this.logger = logger;
465
- this.dataProvider = dataProvider;
466
- this.elements = new StringWeakMap();
467
- this.builtInElements = new StringWeakMap();
468
- this.cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({});
469
- this.htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)({
470
- customDataProviders: [dataProvider, new HTMLDataProvider(typescript, this.elements, this.getProgram)]
471
- });
472
- this.htmlTemplateStringSettings = {
473
- tags: ["html", "raw", "h"],
474
- enableForStringWithSubstitutions: true,
475
- getSubstitution
476
- };
477
- this.cssTemplateStringSettings = {
478
- tags: ["styled", "css"],
479
- enableForStringWithSubstitutions: true,
480
- getSubstitution,
481
- isValidTemplate: (node) => isValidCSSTemplate(typescript, node, "css")
482
- };
483
- this.htmlSourceHelper = new import_standard_template_source_helper.default(
484
- typescript,
485
- this.htmlTemplateStringSettings,
486
- new import_standard_script_source_helper.default(typescript, info.project),
487
- logger
488
- );
489
- }
490
- #virtualHtmlCache = new LRUCache({ max: 1e3 });
491
- #virtualCssCache = new LRUCache({ max: 1e3 });
492
- getCssDoc(text) {
493
- return this.#virtualCssCache.get({ text, fileName: "" }, void 0, () => {
494
- const vDoc = createVirtualDocument("css", text);
495
- const vCss = this.cssLanguageService.parseStylesheet(vDoc);
496
- return { vDoc, vCss };
497
- });
498
- }
499
- getHtmlDoc(text) {
500
- return this.#virtualHtmlCache.get({ text, fileName: "" }, void 0, () => {
501
- const vDoc = createVirtualDocument("html", text);
502
- const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
503
- vHtml.roots.forEach(function transform(e, index, arr) {
504
- e.prev = arr[index - 1];
505
- e.next = arr[index + 1];
506
- e.children.forEach(transform);
507
- });
508
- return { vDoc, vHtml };
509
- });
510
- }
511
- getTagFromNode(node, supportClassName = isDepElement(node)) {
512
- if (!this.ts.isClassDeclaration(node)) return;
513
- for (const modifier of node.modifiers || []) {
514
- if (this.ts.isDecorator(modifier) && this.ts.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === "customElement") {
515
- const arg = modifier.expression.arguments.at(0);
516
- if (arg && this.ts.isStringLiteral(arg)) {
517
- return arg.text;
518
- }
519
- }
520
- }
521
- if (supportClassName && node.name && this.ts.isIdentifier(node.name)) {
522
- return this.config.elementDefineRules.findTag(node.name.text);
523
- }
524
- }
525
- updateElement(file) {
526
- const isDep = isDepElement(file);
527
- this.ts.forEachChild(file, (node) => {
528
- const tag = this.getTagFromNode(node, isDep);
529
- if (tag && this.ts.isClassDeclaration(node)) {
530
- this.elements.set(tag, node);
531
- }
532
- });
533
- }
534
- #initElementsCache = /* @__PURE__ */ new WeakSet();
535
- /**
536
- * 当 project 准备好了执行
537
- */
538
- initElements() {
539
- const program = this.getProgram();
540
- if (this.#initElementsCache.has(program)) return;
541
- const files = program.getSourceFiles();
542
- files.forEach((file) => this.updateElement(file));
543
- const typeChecker = program.getTypeChecker();
544
- const symbols = typeChecker.getSymbolsInScope(files.at(0), this.ts.SymbolFlags.Interface);
545
- symbols.forEach((symbol) => {
546
- const name = symbol.escapedName.toString();
547
- const match = name.match(/^(SVG|HTML)(\w*)Element$/);
548
- const declaration = symbol.declarations?.find((e) => this.ts.isInterfaceDeclaration(e));
549
- if (!match || !declaration) return;
550
- if (name in partialBuiltInElementMap) {
551
- partialBuiltInElementMap[name].forEach((e) => this.builtInElements.set(e, declaration));
552
- } else {
553
- this.builtInElements.set(match[2].toLowerCase(), declaration);
554
- }
555
- });
556
- }
557
- };
558
- var partialBuiltInElementMap = {
559
- SVGAElement: [],
560
- HTMLAnchorElement: ["a"],
561
- SVGImageElement: [],
562
- HTMLImageElement: ["img"],
563
- SVGStyleElement: [],
564
- HTMLStyleElement: ["style"],
565
- HTMLDListElement: ["dl"],
566
- HTMLOListElement: ["ol"],
567
- HTMLUListElement: ["ul"],
568
- HTMLHeadingElement: ["h1", "h2", "h3", "h4", "h5", "h6"],
569
- HTMLModElement: ["del", "ins"],
570
- HTMLQuoteElement: ["blockquote", "q", "cite"],
571
- HTMLTableCaptionElement: ["caption"],
572
- HTMLTableCellElement: ["th", "td"],
573
- HTMLTableColElement: ["col"],
574
- HTMLTableRowElement: ["tr"],
575
- HTMLTableSectionElement: ["thead", "tfoot", "tbody"]
576
- };
577
- function createVirtualDocument(languageId, content) {
578
- return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
579
- }
580
- function getSubstitution(templateString, start, end) {
581
- return templateString.slice(start, end).replaceAll(/[^\n]/g, "_");
582
- }
583
- function isValidCSSTemplate(typescript, node, callName) {
584
- switch (node.kind) {
585
- case typescript.SyntaxKind.NoSubstitutionTemplateLiteral:
586
- case typescript.SyntaxKind.TemplateExpression: {
587
- const parent = node.parent;
588
- if (typescript.isCallExpression(parent) && parent.expression.getText() === callName) {
589
- return true;
590
- }
591
- if (typescript.isPropertyAssignment(parent)) {
592
- const call = parent.parent.parent;
593
- if (typescript.isCallExpression(call) && call.expression.getText() === callName) {
594
- return true;
595
- }
596
- }
597
- return false;
598
- }
599
- default:
600
- return false;
601
- }
602
- }
603
-
604
- // src/decorate-css.ts
605
- var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
606
- var import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
607
-
608
- // src/translates.ts
609
- var vscode = __toESM(require("vscode-languageserver-types"));
610
- function translateCompletionItemsToCompletionInfo(context, items) {
611
- return {
612
- defaultCommitCharacters: [],
613
- isGlobalCompletion: false,
614
- isMemberCompletion: false,
615
- isNewIdentifierLocation: false,
616
- entries: items.items.map((x) => translateCompletionEntry(context, x))
617
- };
618
- }
619
- function translateCompletionEntry(context, vsItem) {
620
- const entry = {
621
- name: vsItem.label,
622
- kind: translationCompletionItemKind(context, vsItem.kind),
623
- sortText: "0",
624
- filterText: vsItem.label,
625
- labelDetails: { description: vsItem.detail }
626
- };
627
- if (vsItem.textEdit) {
628
- entry.isSnippet = vsItem.insertTextFormat === vscode.InsertTextFormat.Snippet || void 0;
629
- entry.insertText = vsItem.textEdit.newText;
630
- entry.replacementSpan = "range" in vsItem.textEdit ? toTsSpan(context, vsItem.textEdit.range) : void 0;
631
- }
632
- return entry;
633
- }
634
- function translationCompletionItemKind(context, kind) {
635
- const typescript = context.typescript;
636
- switch (kind) {
637
- case vscode.CompletionItemKind.Method:
638
- return typescript.ScriptElementKind.memberFunctionElement;
639
- case vscode.CompletionItemKind.Function:
640
- return typescript.ScriptElementKind.functionElement;
641
- case vscode.CompletionItemKind.Constructor:
642
- return typescript.ScriptElementKind.constructorImplementationElement;
643
- case vscode.CompletionItemKind.Field:
644
- case vscode.CompletionItemKind.Variable:
645
- return typescript.ScriptElementKind.variableElement;
646
- case vscode.CompletionItemKind.Class:
647
- return typescript.ScriptElementKind.classElement;
648
- case vscode.CompletionItemKind.Interface:
649
- return typescript.ScriptElementKind.interfaceElement;
650
- case vscode.CompletionItemKind.Module:
651
- return typescript.ScriptElementKind.moduleElement;
652
- case vscode.CompletionItemKind.Property:
653
- return typescript.ScriptElementKind.memberVariableElement;
654
- case vscode.CompletionItemKind.Unit:
655
- case vscode.CompletionItemKind.Value:
656
- return typescript.ScriptElementKind.constElement;
657
- case vscode.CompletionItemKind.Enum:
658
- return typescript.ScriptElementKind.enumElement;
659
- case vscode.CompletionItemKind.Keyword:
660
- return typescript.ScriptElementKind.keyword;
661
- case vscode.CompletionItemKind.Color:
662
- return typescript.ScriptElementKind.constElement;
663
- case vscode.CompletionItemKind.Reference:
664
- return typescript.ScriptElementKind.alias;
665
- case vscode.CompletionItemKind.File:
666
- return typescript.ScriptElementKind.moduleElement;
667
- default:
668
- return typescript.ScriptElementKind.unknown;
669
- }
670
- }
671
- function toTsSpan(context, range) {
672
- const editStart = context.toOffset(range.start);
673
- const editEnd = context.toOffset(range.end);
674
- return {
675
- start: editStart,
676
- length: editEnd - editStart
677
- };
678
- }
679
- function translateHover(context, hover, position, offset = 0) {
680
- const typescript = context.typescript;
681
- const header = [];
682
- const docs = [];
683
- const convertPart = (hoverContents) => {
684
- if (typeof hoverContents === "string") {
685
- docs.push({ kind: "unknown", text: hoverContents });
686
- } else if (Array.isArray(hoverContents)) {
687
- hoverContents.forEach(convertPart);
688
- } else if ("language" in hoverContents && hoverContents.language === "html") {
689
- header.push({ kind: "unknown", text: hoverContents.value });
690
- } else {
691
- docs.push({ kind: "unknown", text: hoverContents.value });
692
- }
693
- };
694
- convertPart(hover.contents);
695
- const start = context.toOffset(hover.range ? hover.range.start : position);
696
- return {
697
- kind: typescript.ScriptElementKind.string,
698
- kindModifiers: "",
699
- textSpan: {
700
- start: start - offset,
701
- length: hover.range ? context.toOffset(hover.range.end) - start : 1
702
- },
703
- displayParts: header,
704
- documentation: docs,
705
- tags: []
706
- };
707
- }
708
- function translateCompletionItemsToCompletionEntryDetails(context, item) {
709
- return {
710
- name: item.label,
711
- kindModifiers: "declare",
712
- kind: item.kind ? translationCompletionItemKind(context, item.kind) : context.typescript.ScriptElementKind.unknown,
713
- displayParts: toDisplayParts(item.detail),
714
- documentation: toDisplayParts(item.documentation, true),
715
- tags: []
716
- };
717
- }
718
- function genDefaultCompletionEntryDetails(context, name) {
719
- return {
720
- name,
721
- kindModifiers: "",
722
- kind: context.typescript.ScriptElementKind.unknown,
723
- displayParts: toDisplayParts(name),
724
- documentation: [],
725
- tags: []
726
- };
727
- }
728
- function toDisplayParts(text, isDoc = false) {
729
- if (!text) return [];
730
- const escapeText = (unsafe) => unsafe.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll(" ", "&nbsp;").replaceAll("\n", " \n").replaceAll(" ", "&emsp;");
731
- return [
732
- {
733
- kind: "unknown",
734
- text: typeof text !== "string" ? text.value : isDoc ? escapeText(text) : text
735
- }
736
- ];
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
- }
791
-
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;
799
- }
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
- };
813
- }
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
- });
826
- }
827
- getCompletionsAtPosition(context, position) {
828
- return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
829
- }
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);
835
- }
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);
845
- }
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);
878
- }
879
- };
880
-
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");
884
- var HTMLLanguageService = class {
885
- #completionsCache = new LRUCache({ max: 1 });
886
- #diagnosticsCache = new LRUCache();
887
- #ctx;
888
- constructor(ctx) {
889
- this.#ctx = ctx;
890
- }
891
- #getAllStyleSheet(doc) {
892
- const styles = [];
893
- const nodes = [...doc.roots];
894
- while (true) {
895
- const node = nodes.pop();
896
- if (!node) break;
897
- if (node.tag === "style") styles.push(node);
898
- nodes.push(...node.children);
899
- }
900
- return styles;
901
- }
902
- #getEmbeddedCss(context, position, doc) {
903
- const node = doc.findNodeAt(context.toOffset(position));
904
- if (node.tag !== "style") return;
905
- const text = context.text.slice(node.startTagEnd, node.endTagStart);
906
- const { vDoc, vCss } = this.#ctx.getCssDoc(text);
907
- const offset = context.toOffset(position) - node.startTagEnd;
908
- const toPosition = (pos) => context.toPosition(vDoc.offsetAt(pos) + node.startTagEnd);
909
- return {
910
- style: vCss,
911
- vDoc,
912
- position: vDoc.positionAt(offset),
913
- updateRange: (range) => ({
914
- start: toPosition(range.start),
915
- end: toPosition(range.end)
916
- })
917
- };
918
- }
919
- #getCSSCompletionsAtPosition(context, position, doc) {
920
- const css = this.#getEmbeddedCss(context, position, doc);
921
- if (!css) return [];
922
- let emmetResults;
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);
927
- completions.items.push(...emmetResults?.items || []);
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
- });
933
- }
934
- #getCompletionsAtPosition(context, position) {
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
- });
948
- }
949
- getCompletionsAtPosition(context, position) {
950
- return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
951
- }
952
- getCompletionEntryDetails(context, position, name) {
953
- const completions = this.#getCompletionsAtPosition(context, position);
954
- const item = completions.items.find((e) => e.label === name);
955
- if (!item) return genDefaultCompletionEntryDetails(context, name);
956
- return translateCompletionItemsToCompletionEntryDetails(context, item);
957
- }
958
- #getCSSQuickInfoAtPosition(context, position, doc) {
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, {
962
- documentation: true,
963
- references: true
964
- });
965
- if (!hover) return;
966
- return { ...hover, range: hover.range && css.updateRange(hover.range) };
967
- }
968
- getQuickInfoAtPosition(context, position) {
969
- const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
970
- const htmlHover = this.#ctx.htmlLanguageService.doHover(vDoc, position, vHtml, {
971
- documentation: true,
972
- references: true
973
- });
974
- const hover = htmlHover || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
975
- if (!hover) return;
976
- return translateHover(context, hover, position);
977
- }
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({
991
- category: context.typescript.DiagnosticCategory.Warning,
992
- code: 0,
993
- file,
994
- start,
995
- length: end - start,
996
- messageText: message,
997
- source: NAME
998
- });
999
- });
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
- const customElementTagDecl = this.#ctx.elements.get(node.tag);
1014
- if (isCustomElementTag(node.tag) && !customElementTagDecl) {
1015
- diagnostics.push({
1016
- category: context.typescript.DiagnosticCategory.Warning,
1017
- code: 101 /* UnknownTag */,
1018
- file,
1019
- start: node.start + 1,
1020
- length: node.tag.length,
1021
- messageText: `Unknown element tag '${node.tag}'`,
1022
- source: NAME
1023
- });
1024
- }
1025
- const tagDeclaration = customElementTagDecl || this.#ctx.builtInElements.get(node.tag);
1026
- if (!tagDeclaration) return;
1027
- for (const [attributeName, { value, start, end }] of node.attributesMap) {
1028
- if (attributeName.startsWith("_")) continue;
1029
- const hasValueSpan = value?.startsWith("_");
1030
- const attrInfo = getAttrName(attributeName);
1031
- const propType = getPropType(typeChecker, tagDeclaration, !customElementTagDecl, attrInfo);
1032
- const diagnostic = {
1033
- category: context.typescript.DiagnosticCategory.Warning,
1034
- file,
1035
- start,
1036
- length: end - start,
1037
- source: NAME,
1038
- code: 103 /* PropTypeError */,
1039
- messageText: !propType ? `'${attributeName}' type error` : `'${attributeName}' not satisfied '${typeChecker.typeToString(propType)}'`
1040
- };
1041
- if ((attributeName === "v-else-if" || attributeName === "v-else") && !node.prev?.attributesMap.has("v-if") && !node.prev?.attributesMap.has("v-else-if")) {
1042
- diagnostics.push({
1043
- ...diagnostic,
1044
- code: 104 /* PropSyntaxError */,
1045
- messageText: `'${attrInfo.attr}' syntax error`
1046
- });
1047
- }
1048
- if (attributeName === "v-if" || attributeName === "v-else-if") {
1049
- const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1050
- if (!hasValueSpan || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getBooleanType())) {
1051
- diagnostics.push(diagnostic);
1052
- }
1053
- continue;
1054
- }
1055
- if (attributeName === "v-else") {
1056
- if (value !== null) {
1057
- diagnostics.push(diagnostic);
1058
- }
1059
- continue;
1060
- }
1061
- if (!propType) {
1062
- if (attrInfo.decorate !== "@") {
1063
- diagnostics.push({
1064
- ...diagnostic,
1065
- code: 102 /* UnknownProp */,
1066
- messageText: `Unknown property '${attrInfo.attr}'`
1067
- });
1068
- }
1069
- continue;
1070
- }
1071
- if (value === null) {
1072
- if (attrInfo.decorate) {
1073
- diagnostics.push({
1074
- ...diagnostic,
1075
- code: 104 /* PropSyntaxError */,
1076
- messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
1077
- });
1078
- } else if (propType !== typeChecker.getBooleanType()) {
1079
- diagnostics.push(diagnostic);
1080
- }
1081
- continue;
1082
- }
1083
- if (hasValueSpan) {
1084
- const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
1085
- switch (attrInfo.decorate) {
1086
- case "?": {
1087
- const boolType = getUnionType(typeChecker, [
1088
- typeChecker.getBooleanType(),
1089
- typeChecker.getUndefinedType(),
1090
- typeChecker.getNullType()
1091
- ]);
1092
- if (!typeChecker.isTypeAssignableTo(spanType, boolType)) {
1093
- diagnostics.push(diagnostic);
1094
- }
1095
- continue;
1096
- }
1097
- case ".":
1098
- case "@":
1099
- if (!typeChecker.isTypeAssignableTo(spanType, propType)) {
1100
- diagnostics.push(diagnostic);
1101
- }
1102
- continue;
1103
- default:
1104
- if (!typeChecker.isTypeAssignableTo(spanType, propType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
1105
- diagnostics.push(diagnostic);
1106
- }
1107
- continue;
1108
- }
1109
- } else {
1110
- const types = [typeChecker.getStringType(), typeChecker.getNumberType()];
1111
- const valueLetter = value.startsWith('"') ? value.slice(1, -1) : value;
1112
- types.push(typeChecker.getStringLiteralType(valueLetter));
1113
- const numberValue = Number(valueLetter);
1114
- if (!Number.isNaN(numberValue)) {
1115
- types.push(typeChecker.getNumberLiteralType(numberValue));
1116
- }
1117
- if (attrInfo.decorate) {
1118
- diagnostics.push({
1119
- ...diagnostic,
1120
- code: 104 /* PropSyntaxError */,
1121
- messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
1122
- });
1123
- } else if (types.every((t) => !typeChecker.isTypeAssignableTo(t, propType))) {
1124
- diagnostics.push(diagnostic);
1125
- }
1126
- }
1127
- }
1128
- });
1129
- return diagnostics;
1130
- }
1131
- getSyntacticDiagnostics(context) {
1132
- this.#ctx.initElements();
1133
- return [...this.#getCssSyntacticDiagnostics(context), ...this.#getHtmlSyntacticDiagnostics(context)];
1134
- }
1135
- getDefinitionAndBoundSpan(context, position) {
1136
- const currentOffset = context.toOffset(position);
1137
- const { vHtml } = this.#ctx.getHtmlDoc(context.text);
1138
- const node = vHtml.findNodeAt(currentOffset);
1139
- const { text, start, length, before } = getHTMLTextAtPosition(context.text, currentOffset);
1140
- const empty = { textSpan: { start, length } };
1141
- if (node.tag === "style" && currentOffset > node.startTagEnd) {
1142
- const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
1143
- const cssNode = style.findChildAtOffset(vDoc.offsetAt(pos), true);
1144
- if (!cssNode) return empty;
1145
- const ident = vDoc.getText().slice(cssNode.offset, cssNode.end);
1146
- const definitionNode2 = this.#ctx.elements.get(ident);
1147
- if (!definitionNode2) return empty;
1148
- return genElementDefinitionInfo(
1149
- context,
1150
- { start: cssNode.offset + node.startTagEnd, length: cssNode.length },
1151
- definitionNode2
1152
- );
1153
- }
1154
- const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
1155
- if (!definitionNode || currentOffset > node.startTagEnd || !text) return empty;
1156
- if (text === node.tag) {
1157
- return genElementDefinitionInfo(context, { start, length }, definitionNode);
1158
- }
1159
- const { attr, offset } = getAttrName(text);
1160
- if (before.length > attr.length) return empty;
1161
- if (attr === "v-else" || attr === "v-else-if") {
1162
- const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
1163
- if (!ifAttr) return empty;
1164
- return genCurrentCtxDefinitionInfo(
1165
- context,
1166
- { start: start + offset, length: attr.length },
1167
- { start: ifAttr.start, length: ifAttr.end - ifAttr.start }
1168
- );
1169
- }
1170
- const typeChecker = this.#ctx.getProgram().getTypeChecker();
1171
- const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
1172
- const propDeclaration = propSymbol?.getDeclarations()?.at(0);
1173
- if (!propDeclaration) return empty;
1174
- return genAttrDefinitionInfo(context, { start: start + offset, length: attr.length }, propDeclaration);
1175
- }
1176
- };
1177
- function getSpanExpression(typescript, file, pos) {
1178
- let node = getAstNodeAtPosition(typescript, file, pos);
1179
- while (!typescript.isTemplateSpan(node)) {
1180
- node = node.parent;
1181
- if (!node) return;
1182
- }
1183
- return node.expression;
1184
- }
1185
- function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
1186
- const valueOffset = attrNameEnd + htmlOffset + 3;
1187
- const spanExp = getSpanExpression(typescript, file, valueOffset);
1188
- return typeChecker.getTypeAtLocation(spanExp);
1189
- }
1190
- var buildInElementNoGlobalAttrPropMap = /* @__PURE__ */ new Map([
1191
- ["crossorigin", "crossOrigin"],
1192
- ["rowspan", "rowSpan"],
1193
- ["colspan", "colSpan"],
1194
- // <input> list: string
1195
- ["list", "ariaLabelledby"]
1196
- ]);
1197
- function getPropType(typeChecker, tagClassDeclaration, isBuiltInTag, attrInfo) {
1198
- const classType = typeChecker.getTypeAtLocation(tagClassDeclaration);
1199
- if (attrInfo.attr.startsWith("data-")) {
1200
- return typeChecker.getStringType();
1201
- }
1202
- const propName = isBuiltInTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr);
1203
- switch (propName) {
1204
- case "class":
1205
- case "style":
1206
- case "part":
1207
- case "exportparts":
1208
- case "xmlns":
1209
- case "viewBox":
1210
- case "ariaLabelledby":
1211
- return typeChecker.getStringType();
1212
- case "tabindex":
1213
- return typeChecker.getNumberType();
1214
- case "ariaAtomic":
1215
- case "ariaBusy":
1216
- case "ariaChecked":
1217
- case "ariaDisabled":
1218
- case "ariaExpanded":
1219
- case "ariaGrabbed":
1220
- case "ariaHidden":
1221
- case "ariaModal":
1222
- case "ariaMultiline":
1223
- case "ariaMultiselectable":
1224
- case "ariaReadonly":
1225
- case "ariaRequired":
1226
- case "ariaPressed":
1227
- case "ariaSelected":
1228
- return getUnionType(typeChecker, [
1229
- typeChecker.getStringType(),
1230
- typeChecker.getBooleanType(),
1231
- typeChecker.getUndefinedType()
1232
- ]);
1233
- default: {
1234
- const isEvent = attrInfo.decorate === "@";
1235
- const propSymbol = classType.getProperty(propName);
1236
- const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
1237
- if (!isEvent) return propType;
1238
- const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
1239
- return getUnionType(typeChecker, [eventHandleType, typeChecker.getUndefinedType()]);
1240
- }
1241
- }
1242
- }
1243
- function getEmitterHandleType(typeChecker, classType, propType) {
1244
- const handleSymbol = propType?.getProperty("handler");
1245
- if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
1246
- const addEventListenerSymbol = classType.getProperty("addEventListener");
1247
- if (!addEventListenerSymbol) return typeChecker.getAnyType();
1248
- const addEventListenerDecls = addEventListenerSymbol.declarations;
1249
- const addEventListenerDecl = addEventListenerDecls.find((e) => !e.typeParameters);
1250
- const listenerDecl = addEventListenerDecl.parameters.at(1);
1251
- if (!listenerDecl) return typeChecker.getAnyType();
1252
- return typeChecker.getTypeAtLocation(listenerDecl);
1253
- }
1254
- function getUnionType(typeChecker, types) {
1255
- if ("getUnionType" in typeChecker) {
1256
- return typeChecker.getUnionType(types);
1257
- }
1258
- return types.at(0);
1259
- }
1260
-
1261
- // src/decorate-ts.ts
1262
- function decorateLanguageService(ctx, languageService) {
1263
- const { ts, getProgram } = ctx;
1264
- const ls = bindMemberFunction(languageService);
1265
- languageService.getCompletionsAtPosition = (...args) => {
1266
- const program = getProgram();
1267
- const typeChecker = program.getTypeChecker();
1268
- decorate(typeChecker, () => decorateTypeChecker(ctx, typeChecker));
1269
- return ls.getCompletionsAtPosition(...args);
1270
- };
1271
- languageService.getSuggestionDiagnostics = (...args) => {
1272
- const program = getProgram();
1273
- const file = program.getSourceFile(args[0]);
1274
- const result = ls.getSuggestionDiagnostics(...args);
1275
- ctx.updateElement(file);
1276
- return result.filter(({ start, reportsUnnecessary, category }) => {
1277
- if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
1278
- const node = getAstNodeAtPosition(ts, file, start);
1279
- if (!node || !ts.isPrivateIdentifier(node)) return true;
1280
- const declaration = node.parent;
1281
- if (!ts.isMethodDeclaration(declaration) && !ts.isPropertyDeclaration(declaration)) return true;
1282
- return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
1283
- });
1284
- };
1285
- languageService.findReferences = (...args) => {
1286
- const oResult = ls.findReferences(...args) || [];
1287
- const program = getProgram();
1288
- const currentNode = getAstNodeAtPosition(ts, program.getSourceFile(args[0]), args[1]);
1289
- if (!currentNode) return oResult;
1290
- const isIdent = ts.isIdentifier(currentNode);
1291
- if (!isIdent) return oResult;
1292
- const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
1293
- const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
1294
- if (!currentTag) return oResult;
1295
- const map = /* @__PURE__ */ new Map();
1296
- forEachAllHtmlTemplateNode(ctx, currentTag, (file, tagInfo) => {
1297
- const symbol = map.get(file.fileName) || {
1298
- references: [],
1299
- definition: {
1300
- containerKind: ctx.ts.ScriptElementKind.unknown,
1301
- containerName: "",
1302
- displayParts: [],
1303
- fileName: file.fileName,
1304
- textSpan: { start: 0, length: 0 },
1305
- name: "test",
1306
- kind: ctx.ts.ScriptElementKind.unknown
1307
- }
1308
- };
1309
- map.set(file.fileName, symbol);
1310
- if (prop) {
1311
- const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
1312
- const kebabCaseName = camelToKebabCase(prop.text);
1313
- ["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
1314
- for (const propName of propNames) {
1315
- const info = tagInfo.node.attributesMap.get(propName);
1316
- if (!info) continue;
1317
- const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1318
- symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
1319
- }
1320
- } else {
1321
- symbol.references.push({
1322
- fileName: file.fileName,
1323
- isWriteAccess: true,
1324
- textSpan: tagInfo.open
1325
- });
1326
- }
1327
- });
1328
- return [...map.values(), ...oResult];
1329
- };
1330
- languageService.getRenameInfo = (fileName, position, ...args) => {
1331
- const result = ls.getRenameInfo(fileName, position, ...args);
1332
- const tagInfo = findCurrentTagInfo(ctx, fileName, position);
1333
- if (tagInfo) {
1334
- return {
1335
- canRename: true,
1336
- displayName: tagInfo.tag,
1337
- fullDisplayName: tagInfo.tag,
1338
- kind: ts.ScriptElementKind.alias,
1339
- kindModifiers: "tag",
1340
- triggerSpan: tagInfo.open
1341
- };
1342
- }
1343
- const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1344
- if (tagDefinedInfo) {
1345
- return {
1346
- canRename: true,
1347
- displayName: tagDefinedInfo.tag,
1348
- fullDisplayName: tagDefinedInfo.tag,
1349
- kind: ts.ScriptElementKind.alias,
1350
- kindModifiers: "tag",
1351
- triggerSpan: tagDefinedInfo.textSpan
1352
- };
1353
- }
1354
- return result;
1355
- };
1356
- languageService.findRenameLocations = (fileName, position, ...args) => {
1357
- const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
1358
- if (tagPairInfo) {
1359
- const result = [{ fileName, textSpan: tagPairInfo.open }];
1360
- if (tagPairInfo.end) result.push({ fileName, textSpan: tagPairInfo.end });
1361
- return result;
1362
- }
1363
- const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
1364
- if (tagDefinedInfo) {
1365
- const result = [{ fileName, textSpan: tagDefinedInfo.textSpan }];
1366
- forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (f, info) => {
1367
- result.push({ fileName: f.fileName, textSpan: info.open });
1368
- if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
1369
- });
1370
- return result;
1371
- }
1372
- const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
1373
- const file = ctx.getProgram().getSourceFile(fileName);
1374
- const node = getAstNodeAtPosition(ctx.ts, file, position);
1375
- const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
1376
- if (!tag) return oResult;
1377
- const propText = node.getText();
1378
- const kebabCaseName = camelToKebabCase(propText);
1379
- if (isPropType(ctx.ts, node.parent, ["emitter", "globalemitter"])) {
1380
- forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1381
- const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
1382
- if (!info) return;
1383
- const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1384
- oResult.push({ fileName: f.fileName, prefixText: "@", textSpan });
1385
- });
1386
- }
1387
- if (isPropType(ctx.ts, node.parent, ["attribute", "numattribute", "boolattribute", "property"])) {
1388
- forEachAllHtmlTemplateNode(ctx, tag, (f, tagInfo) => {
1389
- const propNames = ["", ".", "?"].map((c) => `${c}${kebabCaseName}`);
1390
- propNames.map((propName) => {
1391
- const info = tagInfo.node.attributesMap.get(propName);
1392
- if (!info) return;
1393
- const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
1394
- oResult.push({ fileName: f.fileName, prefixText: ".", textSpan });
1395
- });
1396
- });
1397
- }
1398
- return oResult;
1399
- };
1400
- return languageService;
1401
- }
1402
- function forEachAllHtmlTemplateNode(ctx, tag, fn) {
1403
- for (const file of ctx.getProgram().getSourceFiles()) {
1404
- if (file.fileName.endsWith(".d.ts")) continue;
1405
- for (const templateContext of ctx.htmlSourceHelper.getAllTemplates(file.fileName)) {
1406
- const { vHtml } = ctx.getHtmlDoc(templateContext.text);
1407
- forEachNode(vHtml.roots, (node) => {
1408
- if (node.tag !== tag) return;
1409
- fn(file, getTagInfo(node, templateContext.node.getStart() + 1));
1410
- });
1411
- }
1412
- }
1413
- }
1414
- function findCurrentTagInfo(ctx, fileName, position) {
1415
- const templateContext = ctx.htmlSourceHelper.getTemplate(fileName, position);
1416
- if (!templateContext) return;
1417
- const htmlOffset = templateContext.node.pos + 1;
1418
- const { vHtml } = ctx.getHtmlDoc(templateContext.text);
1419
- const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
1420
- const offset = templateContext.toOffset(relativePosition);
1421
- const node = vHtml.findNodeAt(offset);
1422
- const { text } = getHTMLTextAtPosition(templateContext.text, offset);
1423
- const onTag = offset < node.startTagEnd && text === node.tag;
1424
- if (!onTag || !node.tag) return;
1425
- return getTagInfo(node, htmlOffset);
1426
- }
1427
- function findDefinedTagInfo(ctx, fileName, position) {
1428
- const file = ctx.getProgram().getSourceFile(fileName);
1429
- const node = getAstNodeAtPosition(ctx.ts, file, position);
1430
- if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !== "customElement") {
1431
- return;
1432
- }
1433
- const tag = node.text;
1434
- return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
1435
- }
1436
- function isPropType(typescript, node, types) {
1437
- if (!typescript.isPropertyDeclaration(node)) return;
1438
- for (const modifier of node.modifiers || []) {
1439
- if (!typescript.isDecorator(modifier)) continue;
1440
- const { expression } = modifier;
1441
- if (typescript.isIdentifier(expression) && types.includes(expression.text)) {
1442
- return true;
1443
- }
1444
- }
1445
- }
1446
- function decorateTypeChecker(ctx, typeChecker) {
1447
- const internal = typeChecker;
1448
- const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
1449
- internal.isValidPropertyAccessForCompletions = (...args) => {
1450
- const result = checker.isValidPropertyAccessForCompletions(...args);
1451
- if (!result) return false;
1452
- try {
1453
- const { declarations } = args.at(2);
1454
- if (!declarations) return true;
1455
- const isNever = declarations.every(
1456
- (node) => ctx.ts.isPropertySignature(node) && node.type?.getText() === "never"
1457
- );
1458
- return !isNever;
1459
- } catch {
1460
- return true;
1461
- }
1462
- };
1463
- return typeChecker;
1464
- }
1465
-
1466
- // src/index.ts
1467
- var LanguageServiceLogger = class {
1468
- #info;
1469
- constructor(info) {
1470
- this.#info = info;
1471
- }
1472
- log(msg) {
1473
- this.#info.project.projectService.logger.info(`[${NAME}] ${msg}`);
1474
- }
1475
- };
1476
- var HtmlPlugin = class {
1477
- #ts;
1478
- #config = new Configuration();
1479
- constructor(typescript) {
1480
- this.#ts = typescript;
1481
- }
1482
- create(info) {
1483
- return decorate(info.languageService, () => {
1484
- const logger = new LanguageServiceLogger(info);
1485
- logger.log("Starting...");
1486
- this.#config.update(info.config);
1487
- const context = new Context(this.#ts, this.#config, info, logger);
1488
- const decoratedService = decorateLanguageService(context, info.languageService);
1489
- const decoratedService1 = (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
1490
- this.#ts,
1491
- decoratedService,
1492
- info.project,
1493
- new CSSLanguageService(context),
1494
- context.cssTemplateStringSettings,
1495
- { logger }
1496
- );
1497
- return (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
1498
- this.#ts,
1499
- decoratedService1,
1500
- info.project,
1501
- new HTMLLanguageService(context),
1502
- context.htmlTemplateStringSettings,
1503
- { logger }
1504
- );
1505
- });
1506
- }
1507
- onConfigurationChanged(config) {
1508
- this.#config.update(config);
1509
- }
1510
- };
1511
- module.exports = (mod) => new HtmlPlugin(mod.typescript);
1512
- //# sourceMappingURL=index.js.map