ts-gem-plugin 0.0.11 → 0.0.12
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.
- package/dist/index.js +2362 -0
- package/package.json +2 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,2362 @@
|
|
|
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
|
+
function queueMicrotask(cb) {
|
|
31
|
+
Promise.resolve().then(cb);
|
|
32
|
+
}
|
|
33
|
+
var microtaskSet = /* @__PURE__ */ new Set();
|
|
34
|
+
function addMicrotask(func, method = queueMicrotask) {
|
|
35
|
+
if (microtaskSet.has(func))
|
|
36
|
+
return;
|
|
37
|
+
method(() => {
|
|
38
|
+
microtaskSet.delete(func);
|
|
39
|
+
func();
|
|
40
|
+
});
|
|
41
|
+
microtaskSet.add(func);
|
|
42
|
+
}
|
|
43
|
+
function createUpdater(initState, fn) {
|
|
44
|
+
const state = fn;
|
|
45
|
+
delete state.name;
|
|
46
|
+
delete state.length;
|
|
47
|
+
assign(state, initState);
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
var LinkedList = class extends EventTarget {
|
|
51
|
+
#map = /* @__PURE__ */ new Map();
|
|
52
|
+
#firstItem;
|
|
53
|
+
#lastItem;
|
|
54
|
+
#delete(value) {
|
|
55
|
+
const existItem = this.#map.get(value);
|
|
56
|
+
if (existItem) {
|
|
57
|
+
if (existItem.prev) {
|
|
58
|
+
existItem.prev.next = existItem.next;
|
|
59
|
+
} else {
|
|
60
|
+
this.#firstItem = existItem.next;
|
|
61
|
+
}
|
|
62
|
+
if (existItem.next) {
|
|
63
|
+
existItem.next.prev = existItem.prev;
|
|
64
|
+
} else {
|
|
65
|
+
this.#lastItem = existItem.prev;
|
|
66
|
+
}
|
|
67
|
+
this.#map.delete(value);
|
|
68
|
+
}
|
|
69
|
+
return existItem;
|
|
70
|
+
}
|
|
71
|
+
get size() {
|
|
72
|
+
return this.#map.size;
|
|
73
|
+
}
|
|
74
|
+
get first() {
|
|
75
|
+
return this.#firstItem;
|
|
76
|
+
}
|
|
77
|
+
get last() {
|
|
78
|
+
return this.#lastItem;
|
|
79
|
+
}
|
|
80
|
+
isSuperLinkOf(subLink) {
|
|
81
|
+
let subItem = subLink.first;
|
|
82
|
+
if (!subItem)
|
|
83
|
+
return true;
|
|
84
|
+
let item = this.find(subItem.value);
|
|
85
|
+
while (item && item.value === subItem.value) {
|
|
86
|
+
subItem = subItem.next;
|
|
87
|
+
if (!subItem)
|
|
88
|
+
return true;
|
|
89
|
+
item = item.next;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
find(value) {
|
|
94
|
+
return this.#map.get(value);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 添加到尾部,已存在时会删除老的项目
|
|
98
|
+
* 如果是添加第一个,start 事件会在添加前触发,避免处理事件重复的逻辑
|
|
99
|
+
*/
|
|
100
|
+
add(value) {
|
|
101
|
+
if (!this.#lastItem) {
|
|
102
|
+
this.dispatchEvent(new CustomEvent("start"));
|
|
103
|
+
}
|
|
104
|
+
const item = this.#delete(value) || { value };
|
|
105
|
+
item.prev = this.#lastItem;
|
|
106
|
+
if (item.prev) {
|
|
107
|
+
item.prev.next = item;
|
|
108
|
+
}
|
|
109
|
+
item.next = void 0;
|
|
110
|
+
this.#lastItem = item;
|
|
111
|
+
if (!this.#firstItem) {
|
|
112
|
+
this.#firstItem = item;
|
|
113
|
+
}
|
|
114
|
+
this.#map.set(value, item);
|
|
115
|
+
}
|
|
116
|
+
/** 删除这个元素后没有其他元素时立即出发 end 事件 */
|
|
117
|
+
delete(value) {
|
|
118
|
+
const deleteItem = this.#delete(value);
|
|
119
|
+
if (!this.#firstItem) {
|
|
120
|
+
this.dispatchEvent(new CustomEvent("end"));
|
|
121
|
+
}
|
|
122
|
+
return deleteItem;
|
|
123
|
+
}
|
|
124
|
+
clear() {
|
|
125
|
+
this.#map.clear();
|
|
126
|
+
this.#firstItem = this.#lastItem = void 0;
|
|
127
|
+
}
|
|
128
|
+
/** 获取头部元素,会从链表删除 */
|
|
129
|
+
get() {
|
|
130
|
+
const firstItem = this.#firstItem;
|
|
131
|
+
if (!firstItem)
|
|
132
|
+
return;
|
|
133
|
+
this.delete(firstItem.value);
|
|
134
|
+
return firstItem.value;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
function camelToKebabCase(str) {
|
|
138
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
139
|
+
}
|
|
140
|
+
function kebabToCamelCase(str) {
|
|
141
|
+
return str.replace(/-(.)/g, (_substr, $1) => $1.toUpperCase());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ../gem/lib/store.js
|
|
145
|
+
var _StoreListenerMap = /* @__PURE__ */ new WeakMap();
|
|
146
|
+
function createStore(originStore) {
|
|
147
|
+
const store = createUpdater(originStore, (value) => {
|
|
148
|
+
Object.assign(store, value);
|
|
149
|
+
_StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
|
|
150
|
+
});
|
|
151
|
+
_StoreListenerMap.set(store, /* @__PURE__ */ new Set());
|
|
152
|
+
return store;
|
|
153
|
+
}
|
|
154
|
+
function connect(store, func) {
|
|
155
|
+
const listeners = _StoreListenerMap.get(store);
|
|
156
|
+
listeners?.add(func);
|
|
157
|
+
return () => listeners?.delete(func);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/configuration.ts
|
|
161
|
+
var defaultConfiguration = {
|
|
162
|
+
strict: false,
|
|
163
|
+
emmet: {},
|
|
164
|
+
elementDefineRules: {
|
|
165
|
+
"Duoyun*Element": "dy-*",
|
|
166
|
+
"*Element": "*"
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var Rules = class {
|
|
170
|
+
#map = /* @__PURE__ */ new Map();
|
|
171
|
+
constructor(data) {
|
|
172
|
+
Object.entries(data).forEach(([classNamePattern, tagPattern]) => {
|
|
173
|
+
this.#map.set(new RegExp(classNamePattern.replace("*", "(.*)")), tagPattern.replace("*", "$1"));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
findTag(className) {
|
|
177
|
+
for (const [reg, replaceStr] of this.#map) {
|
|
178
|
+
if (reg.exec(className)) {
|
|
179
|
+
return camelToKebabCase(className.replace(reg, replaceStr));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var configurationStore = createStore({});
|
|
185
|
+
function onConfigurationUpdate(fn) {
|
|
186
|
+
return connect(configurationStore, fn);
|
|
187
|
+
}
|
|
188
|
+
var Configuration = class {
|
|
189
|
+
strict = defaultConfiguration.strict;
|
|
190
|
+
emmet = defaultConfiguration.emmet;
|
|
191
|
+
elementDefineRules = new Rules(defaultConfiguration.elementDefineRules);
|
|
192
|
+
update({ update, ...config }) {
|
|
193
|
+
Object.assign(this, defaultConfiguration, config);
|
|
194
|
+
this.elementDefineRules = new Rules({
|
|
195
|
+
...defaultConfiguration.elementDefineRules,
|
|
196
|
+
...config.elementDefineRules
|
|
197
|
+
});
|
|
198
|
+
configurationStore();
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/constants.ts
|
|
203
|
+
var NAME = "gem-plugin";
|
|
204
|
+
var HTML_SUBSTITUTION_CHAR = "_";
|
|
205
|
+
var Decorators = {
|
|
206
|
+
Attr: "attribute",
|
|
207
|
+
NumAttr: "numattribute",
|
|
208
|
+
BoolAttr: "boolattribute",
|
|
209
|
+
Prop: "property",
|
|
210
|
+
Emitter: "emitter",
|
|
211
|
+
GlobalEmitter: "globalemitter",
|
|
212
|
+
AdoptedStyle: "adoptedStyle",
|
|
213
|
+
Shadow: "shadow",
|
|
214
|
+
CustomElement: "customElement",
|
|
215
|
+
Part: "part",
|
|
216
|
+
Slot: "slot"
|
|
217
|
+
};
|
|
218
|
+
var Utils = {
|
|
219
|
+
ClassMap: "classMap",
|
|
220
|
+
CreateDecoratorTheme: "createDecoratorTheme"
|
|
221
|
+
};
|
|
222
|
+
var Types = {
|
|
223
|
+
Emitter: "Emitter"
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/context.ts
|
|
227
|
+
var import_standard_script_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-script-source-helper"));
|
|
228
|
+
var import_standard_template_source_helper = __toESM(require("@mantou/typescript-template-language-service-decorator/lib/standard-template-source-helper"));
|
|
229
|
+
var import_vscode_css_languageservice = require("@mantou/vscode-css-languageservice");
|
|
230
|
+
var import_vscode_emmet_helper = require("@mantou/vscode-emmet-helper");
|
|
231
|
+
var import_vscode_html_languageservice2 = require("@mantou/vscode-html-languageservice");
|
|
232
|
+
|
|
233
|
+
// ../duoyun-ui/lib/map.js
|
|
234
|
+
var StringWeakMap = class {
|
|
235
|
+
#map = /* @__PURE__ */ new Map();
|
|
236
|
+
#weakMap = /* @__PURE__ */ new WeakMap();
|
|
237
|
+
#registry = new FinalizationRegistry((key) => this.#map.delete(key));
|
|
238
|
+
set(key, val) {
|
|
239
|
+
this.delete(key);
|
|
240
|
+
this.#map.set(key, new WeakRef(val));
|
|
241
|
+
this.#weakMap.set(val, key);
|
|
242
|
+
this.#registry.register(val, key, val);
|
|
243
|
+
}
|
|
244
|
+
get(key) {
|
|
245
|
+
return this.#map.get(key)?.deref();
|
|
246
|
+
}
|
|
247
|
+
findKey(val) {
|
|
248
|
+
return this.#weakMap.get(val);
|
|
249
|
+
}
|
|
250
|
+
delete(key) {
|
|
251
|
+
const val = this.get(key);
|
|
252
|
+
if (val) {
|
|
253
|
+
this.#map.delete(key);
|
|
254
|
+
this.#weakMap.delete(val);
|
|
255
|
+
this.#registry.unregister(val);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
*[Symbol.iterator]() {
|
|
259
|
+
const entries2 = this.#map.entries();
|
|
260
|
+
for (const [tag, ref] of entries2) {
|
|
261
|
+
yield [tag, ref.deref()];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// ../duoyun-ui/lib/types.js
|
|
267
|
+
function isNullish(v) {
|
|
268
|
+
return v === null || v === void 0;
|
|
269
|
+
}
|
|
270
|
+
function isNotNullish(v) {
|
|
271
|
+
return !isNullish(v);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ../duoyun-ui/lib/cache.js
|
|
275
|
+
var Cache = class {
|
|
276
|
+
#max;
|
|
277
|
+
#maxAge;
|
|
278
|
+
#renewal;
|
|
279
|
+
#map = /* @__PURE__ */ new Map();
|
|
280
|
+
#reverseMap = /* @__PURE__ */ new Map();
|
|
281
|
+
#addedLinked = new LinkedList();
|
|
282
|
+
constructor({ max = Infinity, maxAge = Infinity, renewal = false } = {}) {
|
|
283
|
+
this.#max = max;
|
|
284
|
+
this.#maxAge = maxAge;
|
|
285
|
+
this.#renewal = renewal;
|
|
286
|
+
}
|
|
287
|
+
setOptions(options) {
|
|
288
|
+
this.#max = options.max ?? this.#max;
|
|
289
|
+
this.#maxAge = options.maxAge ?? this.#maxAge;
|
|
290
|
+
this.#renewal = options.renewal ?? this.#renewal;
|
|
291
|
+
}
|
|
292
|
+
#trim() {
|
|
293
|
+
for (let i = this.#addedLinked.size - this.#max; i > 0; i--) {
|
|
294
|
+
const value = this.#addedLinked.get();
|
|
295
|
+
const key = this.#reverseMap.get(value);
|
|
296
|
+
this.#reverseMap.delete(value);
|
|
297
|
+
this.#map.delete(key);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
set(key, value) {
|
|
301
|
+
this.#addedLinked.add(value);
|
|
302
|
+
this.#reverseMap.set(value, key);
|
|
303
|
+
this.#map.set(key, { value, timestamp: Date.now() });
|
|
304
|
+
this.#trim();
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
get(key, init) {
|
|
308
|
+
const cache = this.#map.get(key);
|
|
309
|
+
if (!cache) {
|
|
310
|
+
return init && this.set(key, init(key));
|
|
311
|
+
}
|
|
312
|
+
const { timestamp, value } = cache;
|
|
313
|
+
if (Date.now() - timestamp > this.#maxAge) {
|
|
314
|
+
this.#addedLinked.delete(value);
|
|
315
|
+
this.#reverseMap.delete(value);
|
|
316
|
+
this.#map.delete(key);
|
|
317
|
+
return init && this.set(key, init(key));
|
|
318
|
+
}
|
|
319
|
+
if (this.#renewal) {
|
|
320
|
+
cache.timestamp = Date.now();
|
|
321
|
+
}
|
|
322
|
+
this.#addedLinked.add(value);
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
clear() {
|
|
326
|
+
this.#map.clear();
|
|
327
|
+
this.#reverseMap.clear();
|
|
328
|
+
this.#addedLinked.clear();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// src/cache.ts
|
|
333
|
+
var LRUCache = class {
|
|
334
|
+
#bucket;
|
|
335
|
+
constructor(args) {
|
|
336
|
+
this.#bucket = new Cache({ max: 25, renewal: true, ...args });
|
|
337
|
+
}
|
|
338
|
+
#genKey(context, position) {
|
|
339
|
+
return [context.fileName, position?.line, position?.character, context.text].join(";");
|
|
340
|
+
}
|
|
341
|
+
get(context, position, init) {
|
|
342
|
+
return this.#bucket.get(this.#genKey(context, position), init);
|
|
343
|
+
}
|
|
344
|
+
clear() {
|
|
345
|
+
this.#bucket.clear?.();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// src/data-provider.ts
|
|
350
|
+
var import_vscode_html_languageservice = require("@mantou/vscode-html-languageservice");
|
|
351
|
+
|
|
352
|
+
// src/utils.ts
|
|
353
|
+
function isCustomElementTag(tag) {
|
|
354
|
+
return tag.includes("-");
|
|
355
|
+
}
|
|
356
|
+
function isDepElement(node) {
|
|
357
|
+
const { fileName } = node.getSourceFile();
|
|
358
|
+
return ["/node_modules/", "/dist/", ".d.ts"].some((s) => fileName.includes(s));
|
|
359
|
+
}
|
|
360
|
+
function bindMemberFunction(o, keys2 = Object.keys(o)) {
|
|
361
|
+
return Object.fromEntries(keys2.map((key) => [key, o[key].bind?.(o)]));
|
|
362
|
+
}
|
|
363
|
+
function forEachNode(roots, fn) {
|
|
364
|
+
const list = [...roots];
|
|
365
|
+
while (true) {
|
|
366
|
+
const currentNode = list.pop();
|
|
367
|
+
if (!currentNode) return;
|
|
368
|
+
fn(currentNode);
|
|
369
|
+
list.push(..."getChildren" in currentNode ? currentNode.getChildren() : currentNode.children);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function hasDecorator(typescript, node, decorates) {
|
|
373
|
+
return node.modifiers?.some(
|
|
374
|
+
(modifier) => typescript.isDecorator(modifier) && typescript.isIdentifier(modifier.expression) && (!decorates || decorates.includes(modifier.expression.text))
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function hasCallDecorator(typescript, node, decorates) {
|
|
378
|
+
return node.modifiers?.some(
|
|
379
|
+
(modifier) => typescript.isDecorator(modifier) && typescript.isCallExpression(modifier.expression) && (!decorates || decorates.includes(modifier.expression.expression.getText()))
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
function getTemplateNode(typescript, node) {
|
|
383
|
+
if (typescript.isTaggedTemplateExpression(node)) return node.template;
|
|
384
|
+
if (typescript.isTemplateExpression(node)) return node;
|
|
385
|
+
if (typescript.isNoSubstitutionTemplateLiteral(node)) return node;
|
|
386
|
+
}
|
|
387
|
+
function isClassMapKey(typescript, node) {
|
|
388
|
+
if (!node.parent?.parent?.parent) return false;
|
|
389
|
+
const assignment = node.parent;
|
|
390
|
+
const obj = assignment.parent;
|
|
391
|
+
const callExp = obj.parent;
|
|
392
|
+
const key = typescript.isStringLiteral(node) || typescript.isIdentifier(node);
|
|
393
|
+
return key && (typescript.isPropertyAssignment(assignment) && assignment.initializer !== node || typescript.isShorthandPropertyAssignment(assignment)) && typescript.isObjectLiteralExpression(obj) && typescript.isCallExpression(callExp) && typescript.isIdentifier(callExp.expression) && callExp.expression.text === Utils.ClassMap;
|
|
394
|
+
}
|
|
395
|
+
function getAllStyleNode(typescript, typeChecker, node) {
|
|
396
|
+
const getArgNode = (arg) => {
|
|
397
|
+
if (typescript.isIdentifier(arg)) {
|
|
398
|
+
const decl = typeChecker.getSymbolAtLocation(arg)?.valueDeclaration;
|
|
399
|
+
if (!decl || !typescript.isVariableDeclaration(decl) || !decl.initializer) return;
|
|
400
|
+
return getArgNode(decl.initializer);
|
|
401
|
+
}
|
|
402
|
+
const styleNode = getTemplateNode(typescript, arg);
|
|
403
|
+
if (styleNode) return styleNode;
|
|
404
|
+
const argArg = typescript.isCallExpression(arg) && arg.arguments.at(0);
|
|
405
|
+
if (argArg && typescript.isObjectLiteralExpression(argArg)) {
|
|
406
|
+
return argArg.properties.map((p) => {
|
|
407
|
+
const initializer = typescript.isPropertyAssignment(p) && p.initializer;
|
|
408
|
+
return initializer ? getTemplateNode(typescript, initializer) : void 0;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
return (node.modifiers || []).flatMap((m) => {
|
|
413
|
+
const arg = typescript.isDecorator(m) && typescript.isCallExpression(m.expression) && typescript.isIdentifier(m.expression.expression) && m.expression.expression.escapedText === Decorators.AdoptedStyle ? m.expression.arguments.at(0) : void 0;
|
|
414
|
+
if (!arg) return null;
|
|
415
|
+
return getArgNode(arg);
|
|
416
|
+
}).filter(isNotNullish);
|
|
417
|
+
}
|
|
418
|
+
function getTagFromNodeWithDecorator(typescript, node) {
|
|
419
|
+
if (!typescript.isClassDeclaration(node)) return;
|
|
420
|
+
for (const modifier of node.modifiers || []) {
|
|
421
|
+
if (typescript.isDecorator(modifier) && typescript.isCallExpression(modifier.expression) && modifier.expression.expression.getText() === Decorators.CustomElement) {
|
|
422
|
+
const arg = modifier.expression.arguments.at(0);
|
|
423
|
+
if (arg && typescript.isStringLiteral(arg)) {
|
|
424
|
+
return arg.text;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function getCurrentElementDecl(typescript, node) {
|
|
430
|
+
while (!getTagFromNodeWithDecorator(typescript, node)) {
|
|
431
|
+
node = node.parent;
|
|
432
|
+
if (!node) return;
|
|
433
|
+
}
|
|
434
|
+
return node;
|
|
435
|
+
}
|
|
436
|
+
function getAstNodeAtPosition(typescript, node, pos) {
|
|
437
|
+
if (node.pos > pos || node.end <= pos) return;
|
|
438
|
+
while (node.kind >= typescript.SyntaxKind.FirstNode) {
|
|
439
|
+
const nested = typescript.forEachChild(node, (child) => child.pos < pos && child.end >= pos ? child : void 0);
|
|
440
|
+
if (nested === void 0) break;
|
|
441
|
+
node = nested;
|
|
442
|
+
}
|
|
443
|
+
return node;
|
|
444
|
+
}
|
|
445
|
+
function getAllIdent(text) {
|
|
446
|
+
return [...text.matchAll(/[a-zA-Z0-9-_]+/g)].map(({ 0: v, index }) => ({
|
|
447
|
+
start: index,
|
|
448
|
+
length: v.length,
|
|
449
|
+
value: v
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
function getIdentAtPosition(text, offset) {
|
|
453
|
+
for (const { value, start } of getAllIdent(text)) {
|
|
454
|
+
if (offset >= start && offset <= start + value.length) {
|
|
455
|
+
return { text: value, start, length: value.length };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function getAttrName(text) {
|
|
460
|
+
const attr = text.split("=").at(0);
|
|
461
|
+
const isNotLetter = hasDecoratorAttr(attr);
|
|
462
|
+
const offset = isNotLetter ? 1 : 0;
|
|
463
|
+
return {
|
|
464
|
+
attr: attr.slice(offset),
|
|
465
|
+
offset,
|
|
466
|
+
decorate: isNotLetter ? attr.at(0) : ""
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function hasDecoratorAttr(str) {
|
|
470
|
+
return str.charCodeAt(0) < 65;
|
|
471
|
+
}
|
|
472
|
+
function getTagInfo(node, offset) {
|
|
473
|
+
const tag = node.tag;
|
|
474
|
+
const openStart = node.start + 1 + offset;
|
|
475
|
+
return {
|
|
476
|
+
node,
|
|
477
|
+
tag,
|
|
478
|
+
offset,
|
|
479
|
+
open: { start: openStart, length: tag.length },
|
|
480
|
+
end: node.endTagStart && {
|
|
481
|
+
start: node.endTagStart + 2 + offset,
|
|
482
|
+
length: node.end - node.endTagStart - 3
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
var marker = Symbol();
|
|
487
|
+
function decorate(origin, cb) {
|
|
488
|
+
if (origin[marker]) return origin;
|
|
489
|
+
const result = cb(origin);
|
|
490
|
+
result[marker] = true;
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/data-provider.ts
|
|
495
|
+
var dataProvider = (0, import_vscode_html_languageservice.getDefaultHTMLDataProvider)();
|
|
496
|
+
var HTMLDataProvider = class {
|
|
497
|
+
#ts;
|
|
498
|
+
#ctx;
|
|
499
|
+
constructor(ctx) {
|
|
500
|
+
this.#ts = ctx.ts;
|
|
501
|
+
this.#ctx = ctx;
|
|
502
|
+
}
|
|
503
|
+
getId() {
|
|
504
|
+
return NAME;
|
|
505
|
+
}
|
|
506
|
+
isApplicable() {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
provideTags() {
|
|
510
|
+
return [...this.#ctx.elements].map(([tag, node]) => ({
|
|
511
|
+
name: tag,
|
|
512
|
+
attributes: [],
|
|
513
|
+
description: getDocComment(this.#ts, node)
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
provideAttributes(tag) {
|
|
517
|
+
const ts = this.#ts;
|
|
518
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
519
|
+
const node = this.#ctx.elements.get(tag);
|
|
520
|
+
const result = [
|
|
521
|
+
{ name: "v-if", description: "Similar to vue `v-if`" },
|
|
522
|
+
{ name: "v-else-if", description: "Similar to vue `v-else-if`" },
|
|
523
|
+
{ name: "v-else", description: "Similar to vue `v-else`", valueSet: "v" }
|
|
524
|
+
];
|
|
525
|
+
const builtInAttrsAndEvents = dataProvider.provideAttributes("div").map((e) => ({ ...e, name: e.name.replace(/^on/, "@") }));
|
|
526
|
+
if (!node) return [...result, ...builtInAttrsAndEvents];
|
|
527
|
+
const isDep = isDepElement(node);
|
|
528
|
+
const props = typeChecker.getTypeAtLocation(node).getApparentProperties();
|
|
529
|
+
props.forEach((e) => {
|
|
530
|
+
const declaration = e.getDeclarations()?.at(0);
|
|
531
|
+
const prop = declaration && ts.isPropertyDeclaration(declaration);
|
|
532
|
+
if (!prop) return;
|
|
533
|
+
const hasPropDecorator = hasDecorator(ts, declaration);
|
|
534
|
+
if (!hasPropDecorator && !isDep) return;
|
|
535
|
+
const type = declaration.type && typeChecker.getTypeFromTypeNode(declaration.type);
|
|
536
|
+
const typeText = declaration.type?.getText();
|
|
537
|
+
const description = getDocComment(ts, declaration);
|
|
538
|
+
switch (type) {
|
|
539
|
+
// 一般是 attribute
|
|
540
|
+
case typeChecker.getStringType():
|
|
541
|
+
case typeChecker.getNumberType():
|
|
542
|
+
result.push({ name: e.name, description });
|
|
543
|
+
break;
|
|
544
|
+
// 一般是 boolean attribute
|
|
545
|
+
case typeChecker.getBooleanType():
|
|
546
|
+
result.push({ name: e.name, description, valueSet: "v" });
|
|
547
|
+
result.push({ name: `?${e.name}`, description });
|
|
548
|
+
break;
|
|
549
|
+
default: {
|
|
550
|
+
if (type && getUnionValues(type)) {
|
|
551
|
+
result.push({ name: e.name, description });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (typeText?.startsWith(Types.Emitter)) {
|
|
556
|
+
result.push({ name: `@${camelToKebabCase(e.name)}`, description });
|
|
557
|
+
} else {
|
|
558
|
+
result.push({ name: `.${e.name}`, description });
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
result.push(...builtInAttrsAndEvents.filter((e) => e.name.startsWith("@")));
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
provideValues(tag, attr) {
|
|
565
|
+
const result = [];
|
|
566
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
567
|
+
const node = this.#ctx.elements.get(tag);
|
|
568
|
+
const prop = node && typeChecker.getTypeAtLocation(node).getProperty(getAttrName(attr).attr);
|
|
569
|
+
const values = prop && getUnionValues(typeChecker.getTypeOfSymbol(prop));
|
|
570
|
+
values?.forEach((name) => result.push({ name }));
|
|
571
|
+
if (attr === "class" || attr === "id") {
|
|
572
|
+
const currentElementDecl = getCurrentElementDecl(this.#ts, this.#ctx.currentNode);
|
|
573
|
+
if (currentElementDecl) {
|
|
574
|
+
this.#ctx.getAllCss(currentElementDecl).forEach(({ classIdNodeMap }) => {
|
|
575
|
+
classIdNodeMap.entries().filter(([key]) => !(+(attr === "id") ^ +key.startsWith("#"))).forEach(([classOrId]) => {
|
|
576
|
+
result.push({ name: classOrId.slice(attr === "id" ? 1 : 0) });
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
function getUnionValues(type) {
|
|
585
|
+
if (!type.isUnion()) return;
|
|
586
|
+
const result = [];
|
|
587
|
+
type.types.forEach((e) => {
|
|
588
|
+
if (!e.isLiteral()) return;
|
|
589
|
+
result.push(String(e.value));
|
|
590
|
+
});
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
var COMMENT_LINE_CONTENT = /^(\/?[ *\t]*)?(?<str>.*?)(\**\/)?$/gm;
|
|
594
|
+
function getDocComment(typescript, declaration) {
|
|
595
|
+
const fullText = declaration.getSourceFile().getFullText();
|
|
596
|
+
const commentRanges = typescript.getLeadingCommentRanges(fullText, declaration.getFullStart());
|
|
597
|
+
const commentStrings = commentRanges?.filter(({ kind }) => kind === typescript.SyntaxKind.MultiLineCommentTrivia).map(({ pos, end }) => {
|
|
598
|
+
const fullComment = [...fullText.slice(pos, end).matchAll(COMMENT_LINE_CONTENT)];
|
|
599
|
+
return fullComment.map((m) => m.groups.str).join("\n");
|
|
600
|
+
});
|
|
601
|
+
return commentStrings?.join("\n\n");
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/context.ts
|
|
605
|
+
var NodeMap = class {
|
|
606
|
+
#map = {};
|
|
607
|
+
get(key) {
|
|
608
|
+
return this.#map[key];
|
|
609
|
+
}
|
|
610
|
+
add(key, value) {
|
|
611
|
+
if (!this.#map[key]) this.#map[key] = [];
|
|
612
|
+
this.#map[key].push(value);
|
|
613
|
+
}
|
|
614
|
+
entries() {
|
|
615
|
+
return Object.entries(this.#map);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
var Context = class {
|
|
619
|
+
// 用于 data-provider 自动完成
|
|
620
|
+
currentNode;
|
|
621
|
+
// TODO: 支持同名元素,查找时使用最近的声明
|
|
622
|
+
elements;
|
|
623
|
+
builtInElements;
|
|
624
|
+
ts;
|
|
625
|
+
config;
|
|
626
|
+
project;
|
|
627
|
+
logger;
|
|
628
|
+
cssLanguageService;
|
|
629
|
+
htmlLanguageService;
|
|
630
|
+
htmlSourceHelper;
|
|
631
|
+
cssSourceHelper;
|
|
632
|
+
htmlTemplateStringSettings;
|
|
633
|
+
cssTemplateStringSettings;
|
|
634
|
+
getProgram;
|
|
635
|
+
constructor(typescript, config, info, logger) {
|
|
636
|
+
this.ts = typescript;
|
|
637
|
+
this.config = config;
|
|
638
|
+
this.getProgram = () => info.languageService.getProgram();
|
|
639
|
+
this.project = info.project;
|
|
640
|
+
this.logger = logger;
|
|
641
|
+
this.elements = new StringWeakMap();
|
|
642
|
+
this.builtInElements = new StringWeakMap();
|
|
643
|
+
this.cssLanguageService = (0, import_vscode_css_languageservice.getCSSLanguageService)({});
|
|
644
|
+
this.htmlLanguageService = (0, import_vscode_html_languageservice2.getLanguageService)({
|
|
645
|
+
customDataProviders: [new HTMLDataProvider(this)]
|
|
646
|
+
});
|
|
647
|
+
this.htmlTemplateStringSettings = {
|
|
648
|
+
tags: ["html", "raw", "h"],
|
|
649
|
+
enableForStringWithSubstitutions: true,
|
|
650
|
+
getSubstitution
|
|
651
|
+
};
|
|
652
|
+
this.cssTemplateStringSettings = {
|
|
653
|
+
tags: ["styled", "css"],
|
|
654
|
+
enableForStringWithSubstitutions: true,
|
|
655
|
+
getSubstitution,
|
|
656
|
+
isValidTemplate: (node) => isValidCSSTemplate(typescript, node, "css")
|
|
657
|
+
};
|
|
658
|
+
this.htmlSourceHelper = new import_standard_template_source_helper.default(
|
|
659
|
+
typescript,
|
|
660
|
+
this.htmlTemplateStringSettings,
|
|
661
|
+
new import_standard_script_source_helper.default(typescript, info.project),
|
|
662
|
+
logger
|
|
663
|
+
);
|
|
664
|
+
this.cssSourceHelper = new import_standard_template_source_helper.default(
|
|
665
|
+
typescript,
|
|
666
|
+
this.cssTemplateStringSettings,
|
|
667
|
+
new import_standard_script_source_helper.default(typescript, info.project),
|
|
668
|
+
logger
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
#virtualCssCache = new LRUCache({ max: 1e3 });
|
|
672
|
+
getCssDoc(text) {
|
|
673
|
+
return this.#virtualCssCache.get({ text, fileName: "" }, void 0, () => {
|
|
674
|
+
const vDoc = createVirtualDocument("css", text);
|
|
675
|
+
const vCss = this.cssLanguageService.parseStylesheet(vDoc);
|
|
676
|
+
const tagNodeMap = new NodeMap();
|
|
677
|
+
const classIdNodeMap = new NodeMap();
|
|
678
|
+
const propNodeMap = new NodeMap();
|
|
679
|
+
forEachNode(vCss.getChildren(), (node) => {
|
|
680
|
+
if (node.type === import_vscode_css_languageservice.NodeType.ElementNameSelector) {
|
|
681
|
+
tagNodeMap.add(node.getText(), node);
|
|
682
|
+
}
|
|
683
|
+
if (node.type === import_vscode_css_languageservice.NodeType.IdentifierSelector || node.parent?.type === import_vscode_css_languageservice.NodeType.ClassSelector) {
|
|
684
|
+
classIdNodeMap.add(node.getText(), node);
|
|
685
|
+
}
|
|
686
|
+
if (node.parent?.type === import_vscode_css_languageservice.NodeType.CustomPropertyDeclaration) {
|
|
687
|
+
propNodeMap.add(node.getText(), node);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return { vDoc, vCss, tagNodeMap, classIdNodeMap, customPropNodeMap: propNodeMap };
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
getAllCss(node) {
|
|
694
|
+
return getAllStyleNode(this.ts, this.getProgram().getTypeChecker(), node).map((templateNode) => {
|
|
695
|
+
const { fileName } = templateNode.getSourceFile();
|
|
696
|
+
const templateContext = this.cssSourceHelper.getTemplate(fileName, templateNode.pos + 1);
|
|
697
|
+
if (!templateContext) return;
|
|
698
|
+
return { ...this.getCssDoc(templateContext.text), templateContext, templateNode };
|
|
699
|
+
}).filter(isNotNullish);
|
|
700
|
+
}
|
|
701
|
+
#virtualHtmlCache = new LRUCache({ max: 1e3 });
|
|
702
|
+
getHtmlDoc(text) {
|
|
703
|
+
return this.#virtualHtmlCache.get({ text, fileName: "" }, void 0, () => {
|
|
704
|
+
const vDoc = createVirtualDocument("html", text);
|
|
705
|
+
const vHtml = this.htmlLanguageService.parseHTMLDocument(vDoc);
|
|
706
|
+
const tagNodeMap = new NodeMap();
|
|
707
|
+
const classIdNodeMap = new NodeMap();
|
|
708
|
+
vHtml.roots.forEach(function process(e, index, arr) {
|
|
709
|
+
e.prev = arr[index - 1];
|
|
710
|
+
e.next = arr[index + 1];
|
|
711
|
+
e.tag && tagNodeMap.add(e.tag, e);
|
|
712
|
+
const idAttr = e.attributesMap.get("id");
|
|
713
|
+
const classAttr = e.attributesMap.get("class");
|
|
714
|
+
if (idAttr?.value) {
|
|
715
|
+
const text2 = getIdentAtPosition(idAttr.value, 1);
|
|
716
|
+
if (text2) {
|
|
717
|
+
classIdNodeMap.add(`#${text2.text}`, {
|
|
718
|
+
start: idAttr.end + 1 + text2.start,
|
|
719
|
+
length: text2.length
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (classAttr?.value) {
|
|
724
|
+
getAllIdent(classAttr.value).forEach((text2) => {
|
|
725
|
+
classIdNodeMap.add(text2.value, {
|
|
726
|
+
start: classAttr.end + 1 + text2.start,
|
|
727
|
+
length: text2.length
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
e.children.forEach(process);
|
|
732
|
+
});
|
|
733
|
+
return { vDoc, vHtml, tagNodeMap, classIdNodeMap };
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
prepareComplete(node) {
|
|
737
|
+
const tags = [...this.elements].map(([tag]) => tag);
|
|
738
|
+
(0, import_vscode_emmet_helper.updateTags)(tags);
|
|
739
|
+
(0, import_vscode_css_languageservice.updateTags)(tags);
|
|
740
|
+
this.currentNode = node;
|
|
741
|
+
}
|
|
742
|
+
getTagFromNode(node, supportClassName = isDepElement(node)) {
|
|
743
|
+
if (!this.ts.isClassDeclaration(node)) return;
|
|
744
|
+
const tag = getTagFromNodeWithDecorator(this.ts, node);
|
|
745
|
+
if (tag) return tag;
|
|
746
|
+
if (supportClassName && node.name && this.ts.isIdentifier(node.name)) {
|
|
747
|
+
return this.config.elementDefineRules.findTag(node.name.text);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
updateElement(file) {
|
|
751
|
+
for (const [tag, decl] of this.elements) {
|
|
752
|
+
if (decl.getSourceFile().fileName === file.fileName) {
|
|
753
|
+
this.elements.delete?.(tag);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const isDep = isDepElement(file);
|
|
757
|
+
this.ts.forEachChild(file, (node) => {
|
|
758
|
+
const tag = this.getTagFromNode(node, isDep);
|
|
759
|
+
if (tag && this.ts.isClassDeclaration(node)) {
|
|
760
|
+
this.elements.set(tag, node);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
#initElementsCache = /* @__PURE__ */ new WeakSet();
|
|
765
|
+
/**
|
|
766
|
+
* 当 project 准备好了执行
|
|
767
|
+
*/
|
|
768
|
+
initElements() {
|
|
769
|
+
const program = this.getProgram();
|
|
770
|
+
if (this.#initElementsCache.has(this.project)) return;
|
|
771
|
+
this.#initElementsCache.add(this.project);
|
|
772
|
+
const files = program.getSourceFiles();
|
|
773
|
+
files.forEach((file) => this.updateElement(file));
|
|
774
|
+
const typeChecker = program.getTypeChecker();
|
|
775
|
+
const symbols = typeChecker.getSymbolsInScope(files.at(0), this.ts.SymbolFlags.Interface);
|
|
776
|
+
symbols.forEach((symbol) => {
|
|
777
|
+
const name = symbol.escapedName.toString();
|
|
778
|
+
const match = name.match(/^(SVG|HTML)(\w*)Element$/);
|
|
779
|
+
const declaration = symbol.declarations?.find((e) => this.ts.isInterfaceDeclaration(e));
|
|
780
|
+
if (!match || !declaration) return;
|
|
781
|
+
if (name in partialBuiltInElementMap) {
|
|
782
|
+
partialBuiltInElementMap[name].forEach((e) => this.builtInElements.set(e, declaration));
|
|
783
|
+
} else {
|
|
784
|
+
this.builtInElements.set(match[2].toLowerCase(), declaration);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
var partialBuiltInElementMap = {
|
|
790
|
+
HTMLElement: [
|
|
791
|
+
"abbr",
|
|
792
|
+
"address",
|
|
793
|
+
"article",
|
|
794
|
+
"aside",
|
|
795
|
+
"b",
|
|
796
|
+
"bid",
|
|
797
|
+
"bdo",
|
|
798
|
+
"cite",
|
|
799
|
+
"code",
|
|
800
|
+
"dd",
|
|
801
|
+
"dfn",
|
|
802
|
+
"dt",
|
|
803
|
+
"em",
|
|
804
|
+
"figcaption",
|
|
805
|
+
"figure",
|
|
806
|
+
"footer",
|
|
807
|
+
"header",
|
|
808
|
+
"hgroup",
|
|
809
|
+
"i",
|
|
810
|
+
"kbd",
|
|
811
|
+
"main",
|
|
812
|
+
"mark",
|
|
813
|
+
"nav",
|
|
814
|
+
"noscript",
|
|
815
|
+
"rp",
|
|
816
|
+
"rt",
|
|
817
|
+
"ruby",
|
|
818
|
+
"s",
|
|
819
|
+
"samp",
|
|
820
|
+
"search",
|
|
821
|
+
"section",
|
|
822
|
+
"small",
|
|
823
|
+
"strong",
|
|
824
|
+
"sub",
|
|
825
|
+
"summary",
|
|
826
|
+
"sup",
|
|
827
|
+
"u",
|
|
828
|
+
"var",
|
|
829
|
+
"wbr"
|
|
830
|
+
],
|
|
831
|
+
HTMLIFrameElement: ["iframe"],
|
|
832
|
+
HTMLFieldSetElement: ["fieldset"],
|
|
833
|
+
HTMLFencedFrameElement: ["fencedframe"],
|
|
834
|
+
HTMLSelectedContentElement: ["selectedcontent"],
|
|
835
|
+
HTMLParagraphElement: ["p"],
|
|
836
|
+
HTMLOptGroupElement: ["optgroup"],
|
|
837
|
+
HTMLTextAreaElement: ["textarea"],
|
|
838
|
+
HTMLDListElement: ["dl"],
|
|
839
|
+
HTMLOListElement: ["ol"],
|
|
840
|
+
HTMLUListElement: ["ul"],
|
|
841
|
+
HTMLHeadingElement: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
|
842
|
+
HTMLModElement: ["del", "ins"],
|
|
843
|
+
HTMLQuoteElement: ["blockquote", "q", "cite"],
|
|
844
|
+
HTMLTableCaptionElement: ["caption"],
|
|
845
|
+
HTMLTableCellElement: ["th", "td"],
|
|
846
|
+
HTMLTableColElement: ["col"],
|
|
847
|
+
HTMLTableRowElement: ["tr"],
|
|
848
|
+
HTMLTableSectionElement: ["thead", "tfoot", "tbody"],
|
|
849
|
+
// 和 svg 同名元素使用 html 元素对待
|
|
850
|
+
SVGAElement: [],
|
|
851
|
+
HTMLAnchorElement: ["a"],
|
|
852
|
+
SVGImageElement: [],
|
|
853
|
+
HTMLImageElement: ["img"],
|
|
854
|
+
SVGStyleElement: [],
|
|
855
|
+
HTMLStyleElement: ["style"],
|
|
856
|
+
SVGScriptElement: [],
|
|
857
|
+
HTMLScriptElement: ["script"],
|
|
858
|
+
SVGTitleElement: [],
|
|
859
|
+
HTMLTitleElement: ["title"]
|
|
860
|
+
};
|
|
861
|
+
function createVirtualDocument(languageId, content) {
|
|
862
|
+
return import_vscode_html_languageservice2.TextDocument.create(`embedded://document.${languageId}`, languageId, 1, content);
|
|
863
|
+
}
|
|
864
|
+
function getSubstitution(templateString, start, end) {
|
|
865
|
+
return templateString.slice(start, end).replaceAll(/[^\n]/g, HTML_SUBSTITUTION_CHAR);
|
|
866
|
+
}
|
|
867
|
+
function isValidCSSTemplate(typescript, node, callName) {
|
|
868
|
+
switch (node.kind) {
|
|
869
|
+
case typescript.SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
870
|
+
case typescript.SyntaxKind.TemplateExpression: {
|
|
871
|
+
const parent = node.parent;
|
|
872
|
+
if (typescript.isCallExpression(parent) && parent.expression.getText() === callName) {
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
if (typescript.isPropertyAssignment(parent)) {
|
|
876
|
+
const call = parent.parent.parent;
|
|
877
|
+
if (typescript.isCallExpression(call) && call.expression.getText() === callName) {
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
default:
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/decorate-css.ts
|
|
889
|
+
var import_vscode_css_languageservice2 = require("@mantou/vscode-css-languageservice");
|
|
890
|
+
var import_vscode_emmet_helper2 = require("@mantou/vscode-emmet-helper");
|
|
891
|
+
|
|
892
|
+
// src/translates.ts
|
|
893
|
+
var vscode = __toESM(require("vscode-languageserver-types"));
|
|
894
|
+
function translateCompletionItemsToCompletionInfo(context, items) {
|
|
895
|
+
return {
|
|
896
|
+
defaultCommitCharacters: [],
|
|
897
|
+
isGlobalCompletion: false,
|
|
898
|
+
isMemberCompletion: false,
|
|
899
|
+
isNewIdentifierLocation: false,
|
|
900
|
+
entries: items.items.map((x) => translateCompletionEntry(context, x))
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function translateCompletionEntry(context, vsItem) {
|
|
904
|
+
const entry = {
|
|
905
|
+
name: vsItem.label,
|
|
906
|
+
kind: translationCompletionItemKind(context, vsItem.kind),
|
|
907
|
+
sortText: "0",
|
|
908
|
+
filterText: vsItem.label,
|
|
909
|
+
labelDetails: { description: vsItem.detail }
|
|
910
|
+
};
|
|
911
|
+
if (vsItem.textEdit) {
|
|
912
|
+
entry.isSnippet = vsItem.insertTextFormat === vscode.InsertTextFormat.Snippet || void 0;
|
|
913
|
+
entry.insertText = vsItem.textEdit.newText;
|
|
914
|
+
entry.replacementSpan = "range" in vsItem.textEdit ? toTsSpan(context, vsItem.textEdit.range) : void 0;
|
|
915
|
+
}
|
|
916
|
+
return entry;
|
|
917
|
+
}
|
|
918
|
+
function translationCompletionItemKind(context, kind) {
|
|
919
|
+
const typescript = context.typescript;
|
|
920
|
+
switch (kind) {
|
|
921
|
+
case vscode.CompletionItemKind.Method:
|
|
922
|
+
return typescript.ScriptElementKind.memberFunctionElement;
|
|
923
|
+
case vscode.CompletionItemKind.Function:
|
|
924
|
+
return typescript.ScriptElementKind.functionElement;
|
|
925
|
+
case vscode.CompletionItemKind.Constructor:
|
|
926
|
+
return typescript.ScriptElementKind.constructorImplementationElement;
|
|
927
|
+
case vscode.CompletionItemKind.Field:
|
|
928
|
+
case vscode.CompletionItemKind.Variable:
|
|
929
|
+
return typescript.ScriptElementKind.variableElement;
|
|
930
|
+
case vscode.CompletionItemKind.Class:
|
|
931
|
+
return typescript.ScriptElementKind.classElement;
|
|
932
|
+
case vscode.CompletionItemKind.Interface:
|
|
933
|
+
return typescript.ScriptElementKind.interfaceElement;
|
|
934
|
+
case vscode.CompletionItemKind.Module:
|
|
935
|
+
return typescript.ScriptElementKind.moduleElement;
|
|
936
|
+
case vscode.CompletionItemKind.Property:
|
|
937
|
+
return typescript.ScriptElementKind.memberVariableElement;
|
|
938
|
+
case vscode.CompletionItemKind.Unit:
|
|
939
|
+
case vscode.CompletionItemKind.Value:
|
|
940
|
+
return typescript.ScriptElementKind.constElement;
|
|
941
|
+
case vscode.CompletionItemKind.Enum:
|
|
942
|
+
return typescript.ScriptElementKind.enumElement;
|
|
943
|
+
case vscode.CompletionItemKind.Keyword:
|
|
944
|
+
return typescript.ScriptElementKind.keyword;
|
|
945
|
+
case vscode.CompletionItemKind.Color:
|
|
946
|
+
return typescript.ScriptElementKind.constElement;
|
|
947
|
+
case vscode.CompletionItemKind.Reference:
|
|
948
|
+
return typescript.ScriptElementKind.alias;
|
|
949
|
+
case vscode.CompletionItemKind.File:
|
|
950
|
+
return typescript.ScriptElementKind.moduleElement;
|
|
951
|
+
default:
|
|
952
|
+
return typescript.ScriptElementKind.unknown;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function toTsSpan(context, range) {
|
|
956
|
+
const editStart = context.toOffset(range.start);
|
|
957
|
+
const editEnd = context.toOffset(range.end);
|
|
958
|
+
return {
|
|
959
|
+
start: editStart,
|
|
960
|
+
length: editEnd - editStart
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function translateHover(context, hover, position, offset = 0) {
|
|
964
|
+
const typescript = context.typescript;
|
|
965
|
+
const header = [];
|
|
966
|
+
const docs = [];
|
|
967
|
+
const convertPart = (hoverContents) => {
|
|
968
|
+
if (typeof hoverContents === "string") {
|
|
969
|
+
docs.push({ kind: "unknown", text: hoverContents });
|
|
970
|
+
} else if (Array.isArray(hoverContents)) {
|
|
971
|
+
hoverContents.forEach(convertPart);
|
|
972
|
+
} else if ("language" in hoverContents && hoverContents.language === "html") {
|
|
973
|
+
header.push({ kind: "unknown", text: hoverContents.value });
|
|
974
|
+
} else {
|
|
975
|
+
docs.push({ kind: "unknown", text: hoverContents.value });
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
convertPart(hover.contents);
|
|
979
|
+
const start = context.toOffset(hover.range ? hover.range.start : position);
|
|
980
|
+
return {
|
|
981
|
+
kind: typescript.ScriptElementKind.string,
|
|
982
|
+
kindModifiers: "",
|
|
983
|
+
textSpan: {
|
|
984
|
+
start: start - offset,
|
|
985
|
+
length: hover.range ? context.toOffset(hover.range.end) - start : 1
|
|
986
|
+
},
|
|
987
|
+
displayParts: header,
|
|
988
|
+
documentation: docs,
|
|
989
|
+
tags: []
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function translateFoldingRangeKind(context, kind) {
|
|
993
|
+
const typescript = context.typescript;
|
|
994
|
+
switch (kind) {
|
|
995
|
+
case vscode.FoldingRangeKind.Comment:
|
|
996
|
+
return typescript.OutliningSpanKind.Comment;
|
|
997
|
+
case vscode.FoldingRangeKind.Imports:
|
|
998
|
+
return typescript.OutliningSpanKind.Imports;
|
|
999
|
+
case vscode.FoldingRangeKind.Region:
|
|
1000
|
+
return typescript.OutliningSpanKind.Region;
|
|
1001
|
+
default:
|
|
1002
|
+
return typescript.OutliningSpanKind.Code;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function translateFoldingRange(context, range) {
|
|
1006
|
+
const start = context.toOffset({ line: range.startLine, character: range.startCharacter || 0 });
|
|
1007
|
+
const end = context.toOffset({ line: range.endLine, character: range.endCharacter || 0 });
|
|
1008
|
+
return {
|
|
1009
|
+
kind: translateFoldingRangeKind(context, range.kind),
|
|
1010
|
+
autoCollapse: true,
|
|
1011
|
+
bannerText: range.collapsedText || "",
|
|
1012
|
+
textSpan: {
|
|
1013
|
+
start,
|
|
1014
|
+
length: end - start
|
|
1015
|
+
},
|
|
1016
|
+
hintSpan: {
|
|
1017
|
+
start,
|
|
1018
|
+
length: end - start
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function translateCompletionItemsToCompletionEntryDetails(context, item) {
|
|
1023
|
+
return {
|
|
1024
|
+
name: item.label,
|
|
1025
|
+
kindModifiers: "declare",
|
|
1026
|
+
kind: item.kind ? translationCompletionItemKind(context, item.kind) : context.typescript.ScriptElementKind.unknown,
|
|
1027
|
+
displayParts: toDisplayParts(item.detail),
|
|
1028
|
+
documentation: toDisplayParts(item.documentation, true),
|
|
1029
|
+
tags: []
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function genDefaultCompletionEntryDetails(context, name) {
|
|
1033
|
+
return {
|
|
1034
|
+
name,
|
|
1035
|
+
kindModifiers: "",
|
|
1036
|
+
kind: context.typescript.ScriptElementKind.unknown,
|
|
1037
|
+
displayParts: toDisplayParts(name),
|
|
1038
|
+
documentation: [],
|
|
1039
|
+
tags: []
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function toDisplayParts(text, isDoc = false) {
|
|
1043
|
+
if (!text) return [];
|
|
1044
|
+
const escapeText = (unsafe) => unsafe.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'").replaceAll(" ", " ").replaceAll("\n", " \n").replaceAll(" ", " ");
|
|
1045
|
+
return [
|
|
1046
|
+
{
|
|
1047
|
+
kind: "unknown",
|
|
1048
|
+
text: typeof text !== "string" ? text.value : isDoc ? escapeText(text) : text
|
|
1049
|
+
}
|
|
1050
|
+
];
|
|
1051
|
+
}
|
|
1052
|
+
function genElementDefinitionInfo(context, { start, length }, definitionNode) {
|
|
1053
|
+
const htmlOffset = context.node.pos + 1;
|
|
1054
|
+
return {
|
|
1055
|
+
textSpan: { start, length },
|
|
1056
|
+
definitions: [
|
|
1057
|
+
{
|
|
1058
|
+
containerName: "Custom Element",
|
|
1059
|
+
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
1060
|
+
kind: context.typescript.ScriptElementKind.classElement,
|
|
1061
|
+
name: definitionNode.name.text,
|
|
1062
|
+
fileName: definitionNode.getSourceFile().fileName,
|
|
1063
|
+
textSpan: {
|
|
1064
|
+
start: definitionNode.name.getStart() - htmlOffset,
|
|
1065
|
+
length: definitionNode.name.text.length
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
]
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function genAttrDefinitionInfo(context, { start, length }, propDeclaration) {
|
|
1072
|
+
const htmlOffset = context.node.pos + 1;
|
|
1073
|
+
const propName = propDeclaration.getText();
|
|
1074
|
+
return {
|
|
1075
|
+
textSpan: { start, length },
|
|
1076
|
+
definitions: [
|
|
1077
|
+
{
|
|
1078
|
+
containerName: "Attribute",
|
|
1079
|
+
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
1080
|
+
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1081
|
+
name: propName,
|
|
1082
|
+
fileName: propDeclaration.getSourceFile().fileName,
|
|
1083
|
+
textSpan: {
|
|
1084
|
+
start: propDeclaration.getStart() - htmlOffset,
|
|
1085
|
+
length: propName.length
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function genCurrentCtxDefinitionInfo(context, { start, length }, definitionTextSpan) {
|
|
1092
|
+
return {
|
|
1093
|
+
textSpan: { start, length },
|
|
1094
|
+
definitions: [
|
|
1095
|
+
{
|
|
1096
|
+
containerName: "Attribute",
|
|
1097
|
+
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
1098
|
+
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1099
|
+
name: context.text.slice(start, start + length),
|
|
1100
|
+
fileName: context.fileName,
|
|
1101
|
+
textSpan: definitionTextSpan
|
|
1102
|
+
}
|
|
1103
|
+
]
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function genCurrentCtxCssDefinitionInfo(context, value, start, definitions) {
|
|
1107
|
+
const htmlOffset = context.node.getStart();
|
|
1108
|
+
const length = value.length;
|
|
1109
|
+
return {
|
|
1110
|
+
textSpan: { start, length },
|
|
1111
|
+
definitions: definitions.flatMap(
|
|
1112
|
+
({ ctx, nodes, offset = 0 }) => nodes.map((node) => ({
|
|
1113
|
+
containerName: "AttributeValue",
|
|
1114
|
+
containerKind: context.typescript.ScriptElementKind.unknown,
|
|
1115
|
+
kind: context.typescript.ScriptElementKind.memberVariableElement,
|
|
1116
|
+
name: value,
|
|
1117
|
+
fileName: context.fileName,
|
|
1118
|
+
textSpan: { start: node.offset + ctx.node.pos - offset - htmlOffset, length: node.length }
|
|
1119
|
+
}))
|
|
1120
|
+
)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/decorate-css.ts
|
|
1125
|
+
var CSSLanguageService = class {
|
|
1126
|
+
#completionsCache = new LRUCache({ max: 1 });
|
|
1127
|
+
#diagnosticsCache = new LRUCache();
|
|
1128
|
+
#ctx;
|
|
1129
|
+
constructor(ctx) {
|
|
1130
|
+
this.#ctx = ctx;
|
|
1131
|
+
onConfigurationUpdate(() => this.#diagnosticsCache.clear());
|
|
1132
|
+
}
|
|
1133
|
+
#normalize(context, position) {
|
|
1134
|
+
const parent = context.node.parent;
|
|
1135
|
+
const tag = context.typescript.isTaggedTemplateExpression(parent) && parent.tag.getText();
|
|
1136
|
+
const isStyle = context.typescript.isPropertyAssignment(parent) || tag === "styled";
|
|
1137
|
+
if (!isStyle) return { offset: 0, text: context.text, pos: { ...position } };
|
|
1138
|
+
const appendBefore = ".parent { ";
|
|
1139
|
+
const appendAfter = " }";
|
|
1140
|
+
const character = position.line === 0 ? position.character + appendBefore.length : position.character;
|
|
1141
|
+
return {
|
|
1142
|
+
offset: appendBefore.length,
|
|
1143
|
+
text: `${appendBefore}${context.text}${appendAfter}`,
|
|
1144
|
+
pos: { line: position.line, character }
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
#getCompletionsAtPosition(context, position) {
|
|
1148
|
+
return this.#completionsCache.get(context, position, () => {
|
|
1149
|
+
const { text, pos } = this.#normalize(context, position);
|
|
1150
|
+
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1151
|
+
let emmetResults;
|
|
1152
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper2.doComplete)(vDoc, pos, "css", this.#ctx.config.emmet);
|
|
1153
|
+
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
1154
|
+
this.#ctx.prepareComplete(context.node);
|
|
1155
|
+
const completions = this.#ctx.cssLanguageService.doComplete(vDoc, pos, vCss);
|
|
1156
|
+
completions.items.push(...emmetResults?.items || []);
|
|
1157
|
+
return completions;
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
getCompletionsAtPosition(context, position) {
|
|
1161
|
+
return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
|
|
1162
|
+
}
|
|
1163
|
+
getCompletionEntryDetails(context, position, name) {
|
|
1164
|
+
const completions = this.#getCompletionsAtPosition(context, position);
|
|
1165
|
+
const item = completions.items.find((e) => e.label === name);
|
|
1166
|
+
if (!item) return genDefaultCompletionEntryDetails(context, name);
|
|
1167
|
+
return translateCompletionItemsToCompletionEntryDetails(context, item);
|
|
1168
|
+
}
|
|
1169
|
+
getQuickInfoAtPosition(context, position) {
|
|
1170
|
+
const { text, pos } = this.#normalize(context, position);
|
|
1171
|
+
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1172
|
+
const hover = this.#ctx.cssLanguageService.doHover(vDoc, pos, vCss, {
|
|
1173
|
+
documentation: true,
|
|
1174
|
+
references: true
|
|
1175
|
+
});
|
|
1176
|
+
if (!hover) return;
|
|
1177
|
+
return translateHover(context, hover, position, pos.character - position.character);
|
|
1178
|
+
}
|
|
1179
|
+
#getSyntacticDiagnostics(context) {
|
|
1180
|
+
const { text, offset } = this.#normalize(context, { line: 0, character: 0 });
|
|
1181
|
+
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1182
|
+
const result = [];
|
|
1183
|
+
const oDiagnostics = this.#ctx.cssLanguageService.doValidation(vDoc, vCss);
|
|
1184
|
+
const file = this.#ctx.getProgram().getSourceFile(context.fileName);
|
|
1185
|
+
const baseDiagnostic = { code: 0, file, source: NAME, category: context.typescript.DiagnosticCategory.Warning };
|
|
1186
|
+
if (this.#ctx.config.strict) {
|
|
1187
|
+
const scopeSelectors = /* @__PURE__ */ new Set([":host", ":scope", "&"]);
|
|
1188
|
+
vCss.getChildren().forEach((rule) => {
|
|
1189
|
+
if (rule.type !== import_vscode_css_languageservice2.NodeType.Ruleset) return;
|
|
1190
|
+
const [selectors, declarations] = rule.getChildren();
|
|
1191
|
+
const isScopeSelector = selectors?.getChildren().every((s) => scopeSelectors.has(s.getText()));
|
|
1192
|
+
if (!isScopeSelector) return;
|
|
1193
|
+
declarations.getChildren().forEach((decl) => {
|
|
1194
|
+
const [property] = decl.getChildren();
|
|
1195
|
+
if (property?.getText() === "display") {
|
|
1196
|
+
result.push({
|
|
1197
|
+
...baseDiagnostic,
|
|
1198
|
+
start: property.offset,
|
|
1199
|
+
length: property.length,
|
|
1200
|
+
messageText: `Do not set 'display' directly in '${selectors.getText()}', which will cause the 'hidden' attribute to be unavailable`
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
oDiagnostics.forEach(({ message, range }) => {
|
|
1207
|
+
const start = context.toOffset(range.start);
|
|
1208
|
+
result.push({
|
|
1209
|
+
...baseDiagnostic,
|
|
1210
|
+
start: range.start.line === 0 ? start - offset : start,
|
|
1211
|
+
length: context.toOffset(range.end) - start,
|
|
1212
|
+
messageText: message
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
getSyntacticDiagnostics(context) {
|
|
1218
|
+
this.#ctx.initElements();
|
|
1219
|
+
return this.#diagnosticsCache.get(context, void 0, () => this.#getSyntacticDiagnostics(context));
|
|
1220
|
+
}
|
|
1221
|
+
getReferencesAtPosition(context, position) {
|
|
1222
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1223
|
+
const { text, offset } = this.#normalize(context, position);
|
|
1224
|
+
const { vCss } = this.#ctx.getCssDoc(text);
|
|
1225
|
+
const node = vCss.findChildAtOffset(context.toOffset(position) + offset, true);
|
|
1226
|
+
const result = [];
|
|
1227
|
+
if (node?.type === import_vscode_css_languageservice2.NodeType.IdentifierSelector || node?.parent?.type === import_vscode_css_languageservice2.NodeType.ClassSelector) {
|
|
1228
|
+
const classOrId = node.getText();
|
|
1229
|
+
for (const [, decl] of this.#ctx.elements) {
|
|
1230
|
+
const { fileName } = decl.getSourceFile();
|
|
1231
|
+
const styles = getAllStyleNode(context.typescript, typeChecker, decl);
|
|
1232
|
+
if (!styles.includes(context.node)) continue;
|
|
1233
|
+
forEachNode(decl.getChildren(), (node2) => {
|
|
1234
|
+
const templateNode = getTemplateNode(context.typescript, node2);
|
|
1235
|
+
if (templateNode) {
|
|
1236
|
+
const templateContext = this.#ctx.htmlSourceHelper.getTemplate(fileName, templateNode.pos + 1);
|
|
1237
|
+
if (!templateContext) return;
|
|
1238
|
+
const { classIdNodeMap } = this.#ctx.getHtmlDoc(templateContext.text);
|
|
1239
|
+
classIdNodeMap.get(classOrId)?.forEach((n) => {
|
|
1240
|
+
result.push({
|
|
1241
|
+
fileName,
|
|
1242
|
+
textSpan: { start: n.start + templateNode.pos - context.node.pos, length: n.length },
|
|
1243
|
+
isWriteAccess: true
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
} else if (isClassMapKey(context.typescript, node2) && node2.text === classOrId) {
|
|
1247
|
+
const isString = context.typescript.isStringLiteral(node2);
|
|
1248
|
+
result.push({
|
|
1249
|
+
fileName,
|
|
1250
|
+
textSpan: {
|
|
1251
|
+
start: node2.getStart() - context.node.pos - 1 + (isString ? 1 : 0),
|
|
1252
|
+
length: node2.text.length
|
|
1253
|
+
},
|
|
1254
|
+
isWriteAccess: true
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (node?.parent?.type === import_vscode_css_languageservice2.NodeType.Property) {
|
|
1261
|
+
forEachIdent(vCss, node.getText(), (cssNode) => {
|
|
1262
|
+
result.push({
|
|
1263
|
+
fileName: context.fileName,
|
|
1264
|
+
textSpan: { start: cssNode.offset - offset, length: cssNode.end - cssNode.offset },
|
|
1265
|
+
isWriteAccess: true
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
return result;
|
|
1270
|
+
}
|
|
1271
|
+
getDefinitionAndBoundSpan(context, position) {
|
|
1272
|
+
const { text, offset } = this.#normalize(context, position);
|
|
1273
|
+
const { vCss, customPropNodeMap } = this.#ctx.getCssDoc(text);
|
|
1274
|
+
const empty = { textSpan: { start: 0, length: 0 } };
|
|
1275
|
+
const node = vCss.findChildAtOffset(context.toOffset(position) + offset, true);
|
|
1276
|
+
if (!node) return empty;
|
|
1277
|
+
const textSpan = { start: node.offset - offset, length: node.length };
|
|
1278
|
+
if (node.type === import_vscode_css_languageservice2.NodeType.IdentifierSelector || node.parent?.type === import_vscode_css_languageservice2.NodeType.ClassSelector || node.parent?.parent?.type === import_vscode_css_languageservice2.NodeType.CustomPropertyDeclaration) {
|
|
1279
|
+
return genCurrentCtxDefinitionInfo(context, textSpan, textSpan);
|
|
1280
|
+
}
|
|
1281
|
+
const propDefinitions = customPropNodeMap.get(node.getText());
|
|
1282
|
+
if (node.type === import_vscode_css_languageservice2.NodeType.Identifier && propDefinitions) {
|
|
1283
|
+
return genCurrentCtxCssDefinitionInfo(context, node.getText(), node.offset - offset, [
|
|
1284
|
+
{ ctx: context, nodes: propDefinitions, offset }
|
|
1285
|
+
]);
|
|
1286
|
+
}
|
|
1287
|
+
if (node.parent?.type !== import_vscode_css_languageservice2.NodeType.ElementNameSelector) return empty;
|
|
1288
|
+
const definitionNode = this.#ctx.elements.get(node.getText());
|
|
1289
|
+
if (!definitionNode) return empty;
|
|
1290
|
+
return genElementDefinitionInfo(context, textSpan, definitionNode);
|
|
1291
|
+
}
|
|
1292
|
+
// 不知道如何起作用的,没有被调用,但不加就没有 css 折叠了
|
|
1293
|
+
getOutliningSpans(context) {
|
|
1294
|
+
const { vDoc } = this.#ctx.getHtmlDoc(context.text);
|
|
1295
|
+
const ranges = this.#ctx.cssLanguageService.getFoldingRanges(vDoc);
|
|
1296
|
+
return ranges.map((range) => translateFoldingRange(context, range));
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
function forEachIdent(vCss, customPropName, fn) {
|
|
1300
|
+
forEachNode(vCss.getChildren(), (ident) => {
|
|
1301
|
+
if (ident.type !== import_vscode_css_languageservice2.NodeType.Identifier || ident.getText() !== customPropName) return;
|
|
1302
|
+
if (ident.parent?.type !== import_vscode_css_languageservice2.NodeType.Term && ident?.parent?.type !== import_vscode_css_languageservice2.NodeType.Property) return;
|
|
1303
|
+
fn(ident);
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/decorate-html.ts
|
|
1308
|
+
var import_vscode_css_languageservice3 = require("@mantou/vscode-css-languageservice");
|
|
1309
|
+
var import_vscode_emmet_helper3 = require("@mantou/vscode-emmet-helper");
|
|
1310
|
+
var HTMLLanguageService = class {
|
|
1311
|
+
#completionsCache = new LRUCache({ max: 1 });
|
|
1312
|
+
#diagnosticsCache = new LRUCache();
|
|
1313
|
+
#ctx;
|
|
1314
|
+
constructor(ctx) {
|
|
1315
|
+
this.#ctx = ctx;
|
|
1316
|
+
}
|
|
1317
|
+
#getAllStyleSheet(doc) {
|
|
1318
|
+
const styles = [];
|
|
1319
|
+
const nodes = [...doc.roots];
|
|
1320
|
+
while (true) {
|
|
1321
|
+
const node = nodes.pop();
|
|
1322
|
+
if (!node) break;
|
|
1323
|
+
if (node.tag === "style") styles.push(node);
|
|
1324
|
+
nodes.push(...node.children);
|
|
1325
|
+
}
|
|
1326
|
+
return styles;
|
|
1327
|
+
}
|
|
1328
|
+
#getEmbeddedCss(context, position, doc) {
|
|
1329
|
+
const node = doc.findNodeAt(context.toOffset(position));
|
|
1330
|
+
if (node.tag !== "style") return;
|
|
1331
|
+
const text = context.text.slice(node.startTagEnd, node.endTagStart);
|
|
1332
|
+
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1333
|
+
const offset = context.toOffset(position) - node.startTagEnd;
|
|
1334
|
+
const toPosition = (pos) => context.toPosition(vDoc.offsetAt(pos) + node.startTagEnd);
|
|
1335
|
+
return {
|
|
1336
|
+
style: vCss,
|
|
1337
|
+
vDoc,
|
|
1338
|
+
position: vDoc.positionAt(offset),
|
|
1339
|
+
updateRange: (range) => ({
|
|
1340
|
+
start: toPosition(range.start),
|
|
1341
|
+
end: toPosition(range.end)
|
|
1342
|
+
})
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
#getCSSCompletionsAtPosition(context, position, doc) {
|
|
1346
|
+
const css = this.#getEmbeddedCss(context, position, doc);
|
|
1347
|
+
if (!css) return [];
|
|
1348
|
+
let emmetResults;
|
|
1349
|
+
const onCssProperty = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(css.vDoc, css.position, "css", this.#ctx.config.emmet);
|
|
1350
|
+
this.#ctx.cssLanguageService.setCompletionParticipants([{ onCssProperty }]);
|
|
1351
|
+
(0, import_vscode_css_languageservice3.updateTags)([...this.#ctx.elements].map(([tag]) => tag));
|
|
1352
|
+
const completions = this.#ctx.cssLanguageService.doComplete(css.vDoc, css.position, css.style);
|
|
1353
|
+
completions.items.push(...emmetResults?.items || []);
|
|
1354
|
+
return completions.items.map((e) => {
|
|
1355
|
+
const textEdit = e.textEdit && "range" in e.textEdit && e.textEdit;
|
|
1356
|
+
const newTextEdit = textEdit && { newText: textEdit.newText, range: css.updateRange(textEdit.range) };
|
|
1357
|
+
return { ...e, textEdit: newTextEdit || e.textEdit };
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
#getCompletionsAtPosition(context, position) {
|
|
1361
|
+
return this.#completionsCache.get(context, position, () => {
|
|
1362
|
+
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1363
|
+
let emmetResults;
|
|
1364
|
+
const onHtmlContent = () => emmetResults = (0, import_vscode_emmet_helper3.doComplete)(vDoc, position, "html", this.#ctx.config.emmet);
|
|
1365
|
+
this.#ctx.htmlLanguageService.setCompletionParticipants([{ onHtmlContent }]);
|
|
1366
|
+
this.#ctx.prepareComplete(context.node);
|
|
1367
|
+
const completions = this.#ctx.htmlLanguageService.doComplete(vDoc, position, vHtml);
|
|
1368
|
+
completions.items.push(...emmetResults?.items || []);
|
|
1369
|
+
completions.items.push(...this.#getCSSCompletionsAtPosition(context, position, vHtml));
|
|
1370
|
+
return completions;
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
getCompletionsAtPosition(context, position) {
|
|
1374
|
+
return translateCompletionItemsToCompletionInfo(context, this.#getCompletionsAtPosition(context, position));
|
|
1375
|
+
}
|
|
1376
|
+
getCompletionEntryDetails(context, position, name) {
|
|
1377
|
+
const completions = this.#getCompletionsAtPosition(context, position);
|
|
1378
|
+
const item = completions.items.find((e) => e.label === name);
|
|
1379
|
+
if (!item) return genDefaultCompletionEntryDetails(context, name);
|
|
1380
|
+
return translateCompletionItemsToCompletionEntryDetails(context, item);
|
|
1381
|
+
}
|
|
1382
|
+
#getCSSQuickInfoAtPosition(context, position, doc) {
|
|
1383
|
+
const css = this.#getEmbeddedCss(context, position, doc);
|
|
1384
|
+
if (!css) return;
|
|
1385
|
+
const hover = this.#ctx.cssLanguageService.doHover(css.vDoc, css.position, css.style, {
|
|
1386
|
+
documentation: true,
|
|
1387
|
+
references: true
|
|
1388
|
+
});
|
|
1389
|
+
if (!hover) return;
|
|
1390
|
+
return { ...hover, range: hover.range && css.updateRange(hover.range) };
|
|
1391
|
+
}
|
|
1392
|
+
getQuickInfoAtPosition(context, position) {
|
|
1393
|
+
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1394
|
+
const htmlHover = this.#ctx.htmlLanguageService.doHover(vDoc, position, vHtml, {
|
|
1395
|
+
documentation: true,
|
|
1396
|
+
references: true
|
|
1397
|
+
});
|
|
1398
|
+
const hover = htmlHover || this.#getCSSQuickInfoAtPosition(context, position, vHtml);
|
|
1399
|
+
if (!hover) return;
|
|
1400
|
+
return translateHover(context, hover, position);
|
|
1401
|
+
}
|
|
1402
|
+
#getCssSyntacticDiagnostics(context) {
|
|
1403
|
+
return this.#diagnosticsCache.get(context, void 0, () => {
|
|
1404
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1405
|
+
const file = this.#ctx.getProgram().getSourceFile(context.fileName);
|
|
1406
|
+
const styles = this.#getAllStyleSheet(vHtml);
|
|
1407
|
+
const diagnostics = [];
|
|
1408
|
+
styles.forEach((node) => {
|
|
1409
|
+
const text = context.text.slice(node.startTagEnd, node.endTagStart);
|
|
1410
|
+
const { vDoc, vCss } = this.#ctx.getCssDoc(text);
|
|
1411
|
+
this.#ctx.cssLanguageService.doValidation(vDoc, vCss).forEach(({ message, range }) => {
|
|
1412
|
+
const start = node.startTagEnd + vDoc.offsetAt(range.start);
|
|
1413
|
+
const end = node.startTagEnd + vDoc.offsetAt(range.end);
|
|
1414
|
+
diagnostics.push({
|
|
1415
|
+
category: context.typescript.DiagnosticCategory.Warning,
|
|
1416
|
+
code: 0,
|
|
1417
|
+
file,
|
|
1418
|
+
start,
|
|
1419
|
+
length: end - start,
|
|
1420
|
+
messageText: message,
|
|
1421
|
+
source: NAME
|
|
1422
|
+
});
|
|
1423
|
+
});
|
|
1424
|
+
});
|
|
1425
|
+
return diagnostics;
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
#getHtmlSemanticDiagnostics(context) {
|
|
1429
|
+
const offset = context.node.getStart() + 1;
|
|
1430
|
+
const templateTag = context.node.parent.tag?.getText();
|
|
1431
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1432
|
+
const program = this.#ctx.getProgram();
|
|
1433
|
+
const file = program.getSourceFile(context.fileName);
|
|
1434
|
+
const typeChecker = program.getTypeChecker();
|
|
1435
|
+
const diagnostics = [];
|
|
1436
|
+
const baseDiagnostic = { file, source: NAME };
|
|
1437
|
+
forEachNode(vHtml.roots, (node) => {
|
|
1438
|
+
if (!node.tag) return;
|
|
1439
|
+
const customElementTagDecl = this.#ctx.elements.get(node.tag);
|
|
1440
|
+
const builtInElementTagDecl = this.#ctx.builtInElements.get(node.tag);
|
|
1441
|
+
const baseTagDiagnostic = { ...baseDiagnostic, start: node.start + 1, length: node.tag.length };
|
|
1442
|
+
if (this.#ctx.config.strict && templateTag !== "raw" && node.tag === "style") {
|
|
1443
|
+
diagnostics.push({
|
|
1444
|
+
...baseTagDiagnostic,
|
|
1445
|
+
category: context.typescript.DiagnosticCategory.Warning,
|
|
1446
|
+
code: 106 /* NoStyleTag */,
|
|
1447
|
+
messageText: `Use 'adoptedStyle' or 'createDecoratorTheme' instead of the style tag`
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
if (isCustomElementTag(node.tag) && !customElementTagDecl) {
|
|
1451
|
+
diagnostics.push({
|
|
1452
|
+
...baseTagDiagnostic,
|
|
1453
|
+
category: context.typescript.DiagnosticCategory.Warning,
|
|
1454
|
+
code: 101 /* UnknownTag */,
|
|
1455
|
+
messageText: `Unknown element tag '${node.tag}'`
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
const tagDeclaration = customElementTagDecl || builtInElementTagDecl;
|
|
1459
|
+
if (!tagDeclaration) return;
|
|
1460
|
+
const classType = typeChecker.getTypeAtLocation(tagDeclaration);
|
|
1461
|
+
const isSVG = builtInElementTagDecl?.name.getText().startsWith("SVG");
|
|
1462
|
+
if (tagDeclaration.name && isDeprecate(typeChecker.getSymbolAtLocation(tagDeclaration.name))) {
|
|
1463
|
+
[node.start + 1, node.endTagStart && node.endTagStart + 2].filter(isNotNullish).forEach((tagStart) => {
|
|
1464
|
+
diagnostics.push({
|
|
1465
|
+
...baseTagDiagnostic,
|
|
1466
|
+
start: tagStart,
|
|
1467
|
+
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1468
|
+
code: 105 /* Deprecated */,
|
|
1469
|
+
messageText: `Deprecated tag '${node.tag}'`,
|
|
1470
|
+
reportsDeprecated: "true"
|
|
1471
|
+
});
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
for (const [attributeName, { value, start, end }] of node.attributesMap) {
|
|
1475
|
+
if (attributeName.startsWith(HTML_SUBSTITUTION_CHAR)) continue;
|
|
1476
|
+
const hasValueSpan = value?.startsWith(HTML_SUBSTITUTION_CHAR);
|
|
1477
|
+
const attrInfo = getAttrName(attributeName);
|
|
1478
|
+
const propName = getPropName(attrInfo, !!builtInElementTagDecl);
|
|
1479
|
+
const propType = getPropType(typeChecker, classType, propName, attrInfo.decorate === "@");
|
|
1480
|
+
const attrBaseDiagnostic = { ...baseDiagnostic, start, length: end - start };
|
|
1481
|
+
if (isDeprecate(classType.getProperty(propName))) {
|
|
1482
|
+
diagnostics.push({
|
|
1483
|
+
...attrBaseDiagnostic,
|
|
1484
|
+
category: context.typescript.DiagnosticCategory.Suggestion,
|
|
1485
|
+
code: 105 /* Deprecated */,
|
|
1486
|
+
messageText: `Deprecated prop '${attrInfo.attr}'`,
|
|
1487
|
+
reportsDeprecated: "true"
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
const diagnostic = {
|
|
1491
|
+
...attrBaseDiagnostic,
|
|
1492
|
+
category: context.typescript.DiagnosticCategory.Warning,
|
|
1493
|
+
code: 103 /* PropTypeError */,
|
|
1494
|
+
messageText: !propType ? `'${attributeName}' type error` : `'${attributeName}' not satisfied '${typeChecker.typeToString(propType)}'`
|
|
1495
|
+
};
|
|
1496
|
+
if (templateTag === "raw") {
|
|
1497
|
+
if (attrInfo.decorate) {
|
|
1498
|
+
diagnostics.push({
|
|
1499
|
+
...diagnostic,
|
|
1500
|
+
code: 104 /* PropSyntaxError */,
|
|
1501
|
+
messageText: `Raw HTML templates only support attributes`
|
|
1502
|
+
});
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (hasValueSpan) {
|
|
1506
|
+
diagnostics.push({
|
|
1507
|
+
...diagnostic,
|
|
1508
|
+
code: 104 /* PropSyntaxError */,
|
|
1509
|
+
messageText: `Please wrap the raw html template attribute value with "" to avoid parsing errors when the value is an empty string`
|
|
1510
|
+
});
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if ((attributeName === "v-else-if" || attributeName === "v-else") && !node.prev?.attributesMap.has("v-if") && !node.prev?.attributesMap.has("v-else-if")) {
|
|
1515
|
+
diagnostics.push({
|
|
1516
|
+
...diagnostic,
|
|
1517
|
+
code: 104 /* PropSyntaxError */,
|
|
1518
|
+
messageText: `'${attrInfo.attr}' syntax error`
|
|
1519
|
+
});
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (attributeName === "v-if" || attributeName === "v-else-if") {
|
|
1523
|
+
const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
|
|
1524
|
+
if (!hasValueSpan || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getBooleanType())) {
|
|
1525
|
+
diagnostics.push(diagnostic);
|
|
1526
|
+
}
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
if (attributeName === "v-else") {
|
|
1530
|
+
if (value !== null) {
|
|
1531
|
+
diagnostics.push(diagnostic);
|
|
1532
|
+
}
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (
|
|
1536
|
+
// SVG 大小写敏感
|
|
1537
|
+
!isSVG && attrInfo.decorate === "" && attributeName !== camelToKebabCase(attrInfo.attr)
|
|
1538
|
+
) {
|
|
1539
|
+
diagnostics.push({
|
|
1540
|
+
...diagnostic,
|
|
1541
|
+
code: 2552 /* AttrFormatError */,
|
|
1542
|
+
messageText: `Consider using '${customElementTagDecl ? camelToKebabCase(attrInfo.attr) : attrInfo.attr.toLowerCase()}'`
|
|
1543
|
+
});
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
if (!propType) {
|
|
1547
|
+
if (
|
|
1548
|
+
// SVG 元素有很多 css 属性,所以不检查
|
|
1549
|
+
!isSVG && attrInfo.decorate !== "@"
|
|
1550
|
+
) {
|
|
1551
|
+
diagnostics.push({
|
|
1552
|
+
...diagnostic,
|
|
1553
|
+
code: 102 /* UnknownProp */,
|
|
1554
|
+
messageText: `Unknown property '${attrInfo.attr}'`
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
if (globalEnumeratedBooleanAttr.has(attrInfo.attr) && // <div ?draggable=${xx}>
|
|
1560
|
+
(attrInfo.decorate === "?" || // <div draggable>
|
|
1561
|
+
value === null)) {
|
|
1562
|
+
diagnostics.push({
|
|
1563
|
+
...diagnostic,
|
|
1564
|
+
code: 104 /* PropSyntaxError */,
|
|
1565
|
+
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}', must has value`
|
|
1566
|
+
});
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (value === null) {
|
|
1570
|
+
if (attrInfo.decorate) {
|
|
1571
|
+
diagnostics.push({
|
|
1572
|
+
...diagnostic,
|
|
1573
|
+
code: 104 /* PropSyntaxError */,
|
|
1574
|
+
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
|
|
1575
|
+
});
|
|
1576
|
+
} else if (propType !== typeChecker.getBooleanType()) {
|
|
1577
|
+
diagnostics.push(diagnostic);
|
|
1578
|
+
}
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (hasValueSpan) {
|
|
1582
|
+
const spanType = getSpanType(this.#ctx.ts, typeChecker, file, offset, end);
|
|
1583
|
+
switch (attrInfo.decorate) {
|
|
1584
|
+
case "?": {
|
|
1585
|
+
const boolType = getUnionType(typeChecker, [
|
|
1586
|
+
typeChecker.getBooleanType(),
|
|
1587
|
+
typeChecker.getUndefinedType(),
|
|
1588
|
+
typeChecker.getNullType()
|
|
1589
|
+
]);
|
|
1590
|
+
if (!typeChecker.isTypeAssignableTo(spanType, boolType)) {
|
|
1591
|
+
diagnostics.push(diagnostic);
|
|
1592
|
+
}
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
case ".":
|
|
1596
|
+
case "@":
|
|
1597
|
+
if (!typeChecker.isTypeAssignableTo(spanType, propType)) {
|
|
1598
|
+
diagnostics.push(diagnostic);
|
|
1599
|
+
}
|
|
1600
|
+
continue;
|
|
1601
|
+
default: {
|
|
1602
|
+
const nullablePropType = getUnionType(typeChecker, [
|
|
1603
|
+
propType,
|
|
1604
|
+
typeChecker.getNullType(),
|
|
1605
|
+
typeChecker.getUndefinedType()
|
|
1606
|
+
]);
|
|
1607
|
+
if (!typeChecker.isTypeAssignableTo(spanType, nullablePropType) && (!typeChecker.isTypeAssignableTo(propType, typeChecker.getStringType()) || !typeChecker.isTypeAssignableTo(spanType, typeChecker.getStringType()))) {
|
|
1608
|
+
diagnostics.push(diagnostic);
|
|
1609
|
+
}
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
} else {
|
|
1614
|
+
const types = [typeChecker.getStringType(), typeChecker.getNumberType()];
|
|
1615
|
+
const valueLetter = value.startsWith('"') ? value.slice(1, -1) : value;
|
|
1616
|
+
types.push(typeChecker.getStringLiteralType(valueLetter));
|
|
1617
|
+
const numberValue = Number(valueLetter);
|
|
1618
|
+
if (!Number.isNaN(numberValue)) {
|
|
1619
|
+
types.push(typeChecker.getNumberLiteralType(numberValue));
|
|
1620
|
+
}
|
|
1621
|
+
if (attrInfo.decorate) {
|
|
1622
|
+
diagnostics.push({
|
|
1623
|
+
...diagnostic,
|
|
1624
|
+
code: 104 /* PropSyntaxError */,
|
|
1625
|
+
messageText: `Consider using '${camelToKebabCase(attrInfo.attr)}'`
|
|
1626
|
+
});
|
|
1627
|
+
} else if (globalEnumeratedBooleanAttr.has(attrInfo.attr)) {
|
|
1628
|
+
const values = ["true", "false", ...globalEnumeratedBooleanAttr.get(attrInfo.attr)];
|
|
1629
|
+
if (!values.includes(valueLetter)) {
|
|
1630
|
+
diagnostics.push({
|
|
1631
|
+
...diagnostic,
|
|
1632
|
+
code: 103 /* PropTypeError */,
|
|
1633
|
+
messageText: `Must be ${values.join(", ")}`
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
} else if (types.every((t) => !typeChecker.isTypeAssignableTo(t, propType))) {
|
|
1637
|
+
diagnostics.push(diagnostic);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
return diagnostics;
|
|
1643
|
+
}
|
|
1644
|
+
getSyntacticDiagnostics(context) {
|
|
1645
|
+
this.#ctx.initElements();
|
|
1646
|
+
return this.#getCssSyntacticDiagnostics(context);
|
|
1647
|
+
}
|
|
1648
|
+
getSemanticDiagnostics(context) {
|
|
1649
|
+
return this.#getHtmlSemanticDiagnostics(context);
|
|
1650
|
+
}
|
|
1651
|
+
getDefinitionAndBoundSpan(context, position) {
|
|
1652
|
+
const typeChecker = this.#ctx.getProgram().getTypeChecker();
|
|
1653
|
+
const currentOffset = context.toOffset(position);
|
|
1654
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1655
|
+
const { node, attrStart, attrEnd, attrName, attrValue } = getHTMLNodeAtPosition(vHtml, currentOffset);
|
|
1656
|
+
const empty = { textSpan: { start: 0, length: 0 } };
|
|
1657
|
+
const notInStartTag = currentOffset > node.startTagEnd;
|
|
1658
|
+
const startTagNameStart = node.start + 1;
|
|
1659
|
+
if (!node.tag) return empty;
|
|
1660
|
+
if (node.tag === "style" && notInStartTag) {
|
|
1661
|
+
const { style, vDoc, position: pos } = this.#getEmbeddedCss(context, position, vHtml);
|
|
1662
|
+
const cssNode = style.findChildAtOffset(vDoc.offsetAt(pos), true);
|
|
1663
|
+
if (!cssNode || cssNode.parent?.type !== import_vscode_css_languageservice3.NodeType.ElementNameSelector) return empty;
|
|
1664
|
+
const definitionNode2 = this.#ctx.elements.get(cssNode.getText());
|
|
1665
|
+
if (!definitionNode2) return empty;
|
|
1666
|
+
return genElementDefinitionInfo(
|
|
1667
|
+
context,
|
|
1668
|
+
{ start: cssNode.offset + node.startTagEnd, length: cssNode.length },
|
|
1669
|
+
definitionNode2
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
const definitionNode = this.#ctx.elements.get(node.tag) || this.#ctx.builtInElements.get(node.tag);
|
|
1673
|
+
if (!definitionNode || notInStartTag) return empty;
|
|
1674
|
+
if (currentOffset < startTagNameStart + node.tag.length) {
|
|
1675
|
+
return genElementDefinitionInfo(context, { start: startTagNameStart, length: node.tag.length }, definitionNode);
|
|
1676
|
+
}
|
|
1677
|
+
if (!attrName) return empty;
|
|
1678
|
+
const { attr, offset } = getAttrName(attrName);
|
|
1679
|
+
if (attr === "class" || attr === "id") {
|
|
1680
|
+
if (!attrValue) return empty;
|
|
1681
|
+
const currentAttrValue = getIdentAtPosition(attrValue, currentOffset - (attrEnd + 1));
|
|
1682
|
+
const currentElementDecl = getCurrentElementDecl(context.typescript, context.node);
|
|
1683
|
+
if (!currentAttrValue || !currentElementDecl) return empty;
|
|
1684
|
+
return genCurrentCtxCssDefinitionInfo(
|
|
1685
|
+
context,
|
|
1686
|
+
currentAttrValue.text,
|
|
1687
|
+
attrEnd + 1 + currentAttrValue.start,
|
|
1688
|
+
this.#ctx.getAllCss(currentElementDecl).flatMap(({ classIdNodeMap, templateContext }) => {
|
|
1689
|
+
const nodes = classIdNodeMap.get(attr === "id" ? `#${currentAttrValue.text}` : currentAttrValue.text);
|
|
1690
|
+
return { ctx: templateContext, nodes: nodes || [], offset: 0 };
|
|
1691
|
+
}).filter(isNotNullish)
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
if (currentOffset > attrEnd) return empty;
|
|
1695
|
+
if (attr === "v-else" || attr === "v-else-if") {
|
|
1696
|
+
const ifAttr = node.prev?.attributesMap.get("v-if") || node.prev?.attributesMap.get("v-else-if");
|
|
1697
|
+
if (!ifAttr) return empty;
|
|
1698
|
+
return genCurrentCtxDefinitionInfo(
|
|
1699
|
+
context,
|
|
1700
|
+
{ start: attrStart + offset, length: attr.length },
|
|
1701
|
+
{ start: ifAttr.start, length: ifAttr.end - ifAttr.start }
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
const propSymbol = typeChecker.getTypeAtLocation(definitionNode).getProperty(kebabToCamelCase(attr));
|
|
1705
|
+
const propDeclaration = propSymbol?.getDeclarations()?.at(0);
|
|
1706
|
+
if (!propDeclaration) return empty;
|
|
1707
|
+
return genAttrDefinitionInfo(context, { start: attrStart + offset, length: attr.length }, propDeclaration);
|
|
1708
|
+
}
|
|
1709
|
+
// 不知道如何起作用的,没有被调用,但不加就没有 HTML 折叠了
|
|
1710
|
+
// 所以也忽略了 HTML 里面的内联样式折叠
|
|
1711
|
+
getOutliningSpans(context) {
|
|
1712
|
+
const { vDoc } = this.#ctx.getHtmlDoc(context.text);
|
|
1713
|
+
const ranges = this.#ctx.htmlLanguageService.getFoldingRanges(vDoc);
|
|
1714
|
+
return ranges.map((range) => translateFoldingRange(context, range));
|
|
1715
|
+
}
|
|
1716
|
+
getJsxClosingTagAtPosition(context, position) {
|
|
1717
|
+
const currentOffset = context.toOffset(position);
|
|
1718
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1719
|
+
const node = vHtml.findNodeAt(currentOffset);
|
|
1720
|
+
if (!node.tag || !node.startTagEnd || voidElementTags.has(node.tag)) return;
|
|
1721
|
+
if (!node.endTagStart) return { newText: `</${node.tag}>` };
|
|
1722
|
+
}
|
|
1723
|
+
getDocumentHighlights(context, position) {
|
|
1724
|
+
const { vDoc, vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1725
|
+
const docHighlights = this.#ctx.htmlLanguageService.findDocumentHighlights(vDoc, position, vHtml);
|
|
1726
|
+
return [
|
|
1727
|
+
{
|
|
1728
|
+
fileName: context.fileName,
|
|
1729
|
+
highlightSpans: docHighlights.map(({ range }) => {
|
|
1730
|
+
const start = vDoc.offsetAt(range.start);
|
|
1731
|
+
const end = vDoc.offsetAt(range.end);
|
|
1732
|
+
return { textSpan: { start, length: end - start }, kind: this.#ctx.ts.HighlightSpanKind.definition };
|
|
1733
|
+
})
|
|
1734
|
+
}
|
|
1735
|
+
];
|
|
1736
|
+
}
|
|
1737
|
+
getCodeFixesAtPosition(context, start, end, errorCodes) {
|
|
1738
|
+
const result = [];
|
|
1739
|
+
if (errorCodes.includes(2552 /* AttrFormatError */)) {
|
|
1740
|
+
const { vHtml } = this.#ctx.getHtmlDoc(context.text);
|
|
1741
|
+
const node = vHtml.findNodeAt(start);
|
|
1742
|
+
const isBuiltInTag = !!this.#ctx.builtInElements.get(node.tag);
|
|
1743
|
+
const attr = context.text.slice(start, end);
|
|
1744
|
+
const targetAttr = isBuiltInTag ? attr.toLowerCase() : camelToKebabCase(attr);
|
|
1745
|
+
const textChanges = [{ span: { start, length: end - start }, newText: targetAttr }];
|
|
1746
|
+
result.push({
|
|
1747
|
+
fixName: context.fileName,
|
|
1748
|
+
description: `Convert attribute to '${targetAttr}'`,
|
|
1749
|
+
changes: [{ fileName: context.fileName, textChanges }]
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
return result;
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
var voidElementTags = /* @__PURE__ */ new Set([
|
|
1756
|
+
"area",
|
|
1757
|
+
"base",
|
|
1758
|
+
"br",
|
|
1759
|
+
"col",
|
|
1760
|
+
"embed",
|
|
1761
|
+
"hr",
|
|
1762
|
+
"img",
|
|
1763
|
+
"input",
|
|
1764
|
+
"link",
|
|
1765
|
+
"meta",
|
|
1766
|
+
"param",
|
|
1767
|
+
"source",
|
|
1768
|
+
"track",
|
|
1769
|
+
"wbr"
|
|
1770
|
+
]);
|
|
1771
|
+
function getSpanExpression(typescript, file, pos) {
|
|
1772
|
+
let node = getAstNodeAtPosition(typescript, file, pos);
|
|
1773
|
+
while (!typescript.isTemplateSpan(node)) {
|
|
1774
|
+
node = node.parent;
|
|
1775
|
+
if (!node) return;
|
|
1776
|
+
}
|
|
1777
|
+
return node.expression;
|
|
1778
|
+
}
|
|
1779
|
+
function getSpanType(typescript, typeChecker, file, htmlOffset, attrNameEnd) {
|
|
1780
|
+
const valueOffset = attrNameEnd + htmlOffset + 4;
|
|
1781
|
+
const spanExp = getSpanExpression(typescript, file, valueOffset);
|
|
1782
|
+
return typeChecker.getTypeAtLocation(spanExp);
|
|
1783
|
+
}
|
|
1784
|
+
var buildInElementNoGlobalAttrPropMap = /* @__PURE__ */ new Map([
|
|
1785
|
+
["crossorigin", "crossOrigin"],
|
|
1786
|
+
["rowspan", "rowSpan"],
|
|
1787
|
+
["colspan", "colSpan"],
|
|
1788
|
+
// <input> list: string
|
|
1789
|
+
["list", "ariaLabelledby"]
|
|
1790
|
+
]);
|
|
1791
|
+
var globalAttrPropMap = /* @__PURE__ */ new Map([["contenteditable", "contentEditable"]]);
|
|
1792
|
+
var globalEnumeratedBooleanAttr = /* @__PURE__ */ new Map([
|
|
1793
|
+
["draggable", []],
|
|
1794
|
+
["spellcheck", []],
|
|
1795
|
+
["contenteditable", ["plaintext-only"]]
|
|
1796
|
+
]);
|
|
1797
|
+
function getPropName(attrInfo, isNativeTag) {
|
|
1798
|
+
if (attrInfo.attr.startsWith("data-")) {
|
|
1799
|
+
return attrInfo.attr;
|
|
1800
|
+
}
|
|
1801
|
+
return globalAttrPropMap.get(attrInfo.attr) || (isNativeTag ? buildInElementNoGlobalAttrPropMap.get(attrInfo.attr) || kebabToCamelCase(attrInfo.attr) : kebabToCamelCase(attrInfo.attr));
|
|
1802
|
+
}
|
|
1803
|
+
function getPropType(typeChecker, classType, propName, isEvent) {
|
|
1804
|
+
if (propName.startsWith("data-")) {
|
|
1805
|
+
return typeChecker.getStringType();
|
|
1806
|
+
}
|
|
1807
|
+
switch (propName) {
|
|
1808
|
+
case "class":
|
|
1809
|
+
case "style":
|
|
1810
|
+
case "part":
|
|
1811
|
+
case "exportparts":
|
|
1812
|
+
case "accesskey":
|
|
1813
|
+
case "xmlns":
|
|
1814
|
+
case "viewBox":
|
|
1815
|
+
case "ariaLabelledby":
|
|
1816
|
+
return typeChecker.getStringType();
|
|
1817
|
+
case "tabindex":
|
|
1818
|
+
return typeChecker.getNumberType();
|
|
1819
|
+
case "ariaAtomic":
|
|
1820
|
+
case "ariaBusy":
|
|
1821
|
+
case "ariaChecked":
|
|
1822
|
+
case "ariaDisabled":
|
|
1823
|
+
case "ariaExpanded":
|
|
1824
|
+
case "ariaGrabbed":
|
|
1825
|
+
case "ariaHidden":
|
|
1826
|
+
case "ariaModal":
|
|
1827
|
+
case "ariaMultiline":
|
|
1828
|
+
case "ariaMultiselectable":
|
|
1829
|
+
case "ariaReadonly":
|
|
1830
|
+
case "ariaRequired":
|
|
1831
|
+
case "ariaPressed":
|
|
1832
|
+
case "ariaSelected":
|
|
1833
|
+
return getUnionType(typeChecker, [
|
|
1834
|
+
typeChecker.getStringType(),
|
|
1835
|
+
typeChecker.getBooleanType(),
|
|
1836
|
+
typeChecker.getUndefinedType()
|
|
1837
|
+
]);
|
|
1838
|
+
default: {
|
|
1839
|
+
const propSymbol = classType.getProperty(propName);
|
|
1840
|
+
const propType = propSymbol && typeChecker.getTypeOfSymbol(propSymbol);
|
|
1841
|
+
if (!isEvent) return propType;
|
|
1842
|
+
const eventHandleType = getEmitterHandleType(typeChecker, classType, propType);
|
|
1843
|
+
return getUnionType(typeChecker, [eventHandleType, typeChecker.getUndefinedType()]);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
function isDeprecate(symbol) {
|
|
1848
|
+
if (!symbol) return false;
|
|
1849
|
+
const tags = symbol.getJsDocTags();
|
|
1850
|
+
return tags.some(({ name }) => name === "deprecated");
|
|
1851
|
+
}
|
|
1852
|
+
function getEmitterHandleType(typeChecker, classType, propType) {
|
|
1853
|
+
const handleSymbol = propType?.getProperty("handler");
|
|
1854
|
+
if (handleSymbol) return typeChecker.getTypeOfSymbol(handleSymbol);
|
|
1855
|
+
const addEventListenerSymbol = classType.getProperty("addEventListener");
|
|
1856
|
+
if (!addEventListenerSymbol) return typeChecker.getAnyType();
|
|
1857
|
+
const addEventListenerDecls = addEventListenerSymbol.declarations;
|
|
1858
|
+
const addEventListenerDecl = addEventListenerDecls.find((e) => !e.typeParameters);
|
|
1859
|
+
const listenerDecl = addEventListenerDecl.parameters.at(1);
|
|
1860
|
+
if (!listenerDecl) return typeChecker.getAnyType();
|
|
1861
|
+
return typeChecker.getTypeAtLocation(listenerDecl);
|
|
1862
|
+
}
|
|
1863
|
+
function getUnionType(typeChecker, types) {
|
|
1864
|
+
if ("getUnionType" in typeChecker) {
|
|
1865
|
+
return typeChecker.getUnionType(types);
|
|
1866
|
+
}
|
|
1867
|
+
return types.at(0);
|
|
1868
|
+
}
|
|
1869
|
+
function getHTMLNodeAtPosition(vHtml, offset) {
|
|
1870
|
+
const node = vHtml.findNodeAt(offset);
|
|
1871
|
+
const attr = node.attributesMap.entries().find(([_, n]) => {
|
|
1872
|
+
return offset > n.start && offset < n.end + 1 + (n.value?.length || 0);
|
|
1873
|
+
});
|
|
1874
|
+
return {
|
|
1875
|
+
node,
|
|
1876
|
+
attrName: attr?.[0] ?? "",
|
|
1877
|
+
attrValue: attr?.[1].value ?? "",
|
|
1878
|
+
attrStart: attr?.[1].start ?? 0,
|
|
1879
|
+
attrEnd: attr?.[1].end ?? 0
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/decorate-ts.ts
|
|
1884
|
+
function decorateLanguageService(ctx, languageService) {
|
|
1885
|
+
const { ts, getProgram } = ctx;
|
|
1886
|
+
const ls = bindMemberFunction(languageService);
|
|
1887
|
+
languageService.getCompletionsAtPosition = (...args) => {
|
|
1888
|
+
const program = getProgram();
|
|
1889
|
+
const typeChecker = program.getTypeChecker();
|
|
1890
|
+
decorate(typeChecker, () => decorateTypeChecker(typeChecker));
|
|
1891
|
+
const classKeys = getClassKeys(ctx, args[0], args[1]);
|
|
1892
|
+
if (classKeys) {
|
|
1893
|
+
return {
|
|
1894
|
+
isGlobalCompletion: true,
|
|
1895
|
+
isMemberCompletion: true,
|
|
1896
|
+
isNewIdentifierLocation: true,
|
|
1897
|
+
entries: classKeys.map((name) => ({ kind: ts.ScriptElementKind.enumMemberElement, sortText: "", name }))
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
const themeKeys = getThemeKeys(ctx, args[0], args[1]);
|
|
1901
|
+
if (themeKeys) {
|
|
1902
|
+
return {
|
|
1903
|
+
isGlobalCompletion: true,
|
|
1904
|
+
isMemberCompletion: true,
|
|
1905
|
+
isNewIdentifierLocation: true,
|
|
1906
|
+
entries: themeKeys.map((name) => ({ kind: ts.ScriptElementKind.enumMemberElement, sortText: "", name }))
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
return ls.getCompletionsAtPosition(...args);
|
|
1910
|
+
};
|
|
1911
|
+
languageService.getSyntacticDiagnostics = (...args) => {
|
|
1912
|
+
const program = getProgram();
|
|
1913
|
+
const file = program.getSourceFile(args[0]);
|
|
1914
|
+
ctx.updateElement(file);
|
|
1915
|
+
return ls.getSyntacticDiagnostics(...args);
|
|
1916
|
+
};
|
|
1917
|
+
languageService.getSuggestionDiagnostics = (...args) => {
|
|
1918
|
+
const program = getProgram();
|
|
1919
|
+
const file = program.getSourceFile(args[0]);
|
|
1920
|
+
const result = ls.getSuggestionDiagnostics(...args);
|
|
1921
|
+
ts.forEachChild(file, (node) => {
|
|
1922
|
+
const tag = ctx.getTagFromNode(node);
|
|
1923
|
+
if (tag && ts.isClassDeclaration(node)) {
|
|
1924
|
+
if (node.name && !node.name.text.endsWith("Element")) {
|
|
1925
|
+
result.push({
|
|
1926
|
+
file,
|
|
1927
|
+
start: node.name.getStart(),
|
|
1928
|
+
length: node.name.getEnd() - node.name.getStart(),
|
|
1929
|
+
category: ctx.config.strict ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Suggestion,
|
|
1930
|
+
code: 108 /* SuggestionClassName */,
|
|
1931
|
+
messageText: "Element definition class suggests the suffix to use `Element`"
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
const isShadowDom = hasCallDecorator(ts, node, [Decorators.Shadow]);
|
|
1935
|
+
node.members.forEach((member) => {
|
|
1936
|
+
const memberStart = member.getStart();
|
|
1937
|
+
const baseMemberDiagnostic = { file, start: memberStart, length: member.getEnd() - memberStart };
|
|
1938
|
+
if (ctx.config.strict && member.name && ts.isIdentifier(member.name) && member.name.text.length > 3 && member.name.text === member.name.text.toLowerCase() && member.name.text.startsWith("on")) {
|
|
1939
|
+
result.push({
|
|
1940
|
+
...baseMemberDiagnostic,
|
|
1941
|
+
category: ts.DiagnosticCategory.Warning,
|
|
1942
|
+
code: 110 /* SuggestionPropName */,
|
|
1943
|
+
messageText: "Consider changing the name, it looks too much like the html event handler"
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
if (!ts.isPropertyDeclaration(member) || !member.modifiers) return;
|
|
1947
|
+
if (hasDecorator(ts, member, [Decorators.Prop]) && !member.questionToken && !member.initializer) {
|
|
1948
|
+
result.push({
|
|
1949
|
+
...baseMemberDiagnostic,
|
|
1950
|
+
category: ctx.config.strict ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Suggestion,
|
|
1951
|
+
code: 109 /* SuggestionPropOptional */,
|
|
1952
|
+
messageText: "Custom element property should be optional"
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
if (!hasDecorator(ts, member, [Decorators.Slot, Decorators.Part])) return;
|
|
1956
|
+
const missStaticKeyword = member.modifiers.every((e) => e.kind !== ts.SyntaxKind.StaticKeyword);
|
|
1957
|
+
if (missStaticKeyword || !isShadowDom) {
|
|
1958
|
+
result.push({
|
|
1959
|
+
...baseMemberDiagnostic,
|
|
1960
|
+
category: ts.DiagnosticCategory.Warning,
|
|
1961
|
+
code: 107 /* DecoratorSyntaxError */,
|
|
1962
|
+
messageText: missStaticKeyword ? "Use static field for `@part` and `@slot`" : "Not available on light dom `@part` and `@slot`"
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
return result.filter(({ start, reportsUnnecessary, category }) => {
|
|
1969
|
+
if (!reportsUnnecessary || category !== ts.DiagnosticCategory.Suggestion) return true;
|
|
1970
|
+
const node = getAstNodeAtPosition(ts, file, start);
|
|
1971
|
+
const declaration = node?.parent;
|
|
1972
|
+
if (!declaration) return true;
|
|
1973
|
+
if (ts.isClassDeclaration(declaration)) {
|
|
1974
|
+
return !hasCallDecorator(ts, declaration, [Decorators.CustomElement]);
|
|
1975
|
+
}
|
|
1976
|
+
if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
|
|
1977
|
+
return !declaration.modifiers?.some((e) => e?.kind === ts.SyntaxKind.Decorator);
|
|
1978
|
+
}
|
|
1979
|
+
return true;
|
|
1980
|
+
});
|
|
1981
|
+
};
|
|
1982
|
+
languageService.findReferences = (...args) => {
|
|
1983
|
+
const map = /* @__PURE__ */ new Map();
|
|
1984
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, ...args);
|
|
1985
|
+
if (tagDefinedInfo) {
|
|
1986
|
+
forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (file, tagInfo) => {
|
|
1987
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
1988
|
+
map.set(file.fileName, symbol);
|
|
1989
|
+
symbol.references.push({
|
|
1990
|
+
fileName: file.fileName,
|
|
1991
|
+
isWriteAccess: true,
|
|
1992
|
+
textSpan: tagInfo.open
|
|
1993
|
+
});
|
|
1994
|
+
});
|
|
1995
|
+
forEachAllCssTemplateNode(ctx, tagDefinedInfo.tag, (file, textSpan) => {
|
|
1996
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
1997
|
+
map.set(file.fileName, symbol);
|
|
1998
|
+
symbol.references.push({
|
|
1999
|
+
fileName: file.fileName,
|
|
2000
|
+
isWriteAccess: true,
|
|
2001
|
+
textSpan
|
|
2002
|
+
});
|
|
2003
|
+
});
|
|
2004
|
+
return [...map.values()];
|
|
2005
|
+
}
|
|
2006
|
+
const oResult = ls.findReferences(...args) || [];
|
|
2007
|
+
const program = getProgram();
|
|
2008
|
+
const currentNode = getAstNodeAtPosition(ts, program.getSourceFile(args[0]), args[1]);
|
|
2009
|
+
if (!currentNode || !ts.isIdentifier(currentNode)) return oResult;
|
|
2010
|
+
const currentTag = ctx.getTagFromNode(currentNode.parent) || ctx.getTagFromNode(currentNode.parent.parent);
|
|
2011
|
+
const prop = ts.isClassDeclaration(currentNode.parent.parent) && currentNode;
|
|
2012
|
+
if (!currentTag) return oResult;
|
|
2013
|
+
if (prop) {
|
|
2014
|
+
getAllTagFromProp(ctx, currentNode).forEach((tag) => {
|
|
2015
|
+
forEachAllHtmlTemplateNode(ctx, tag, (file, tagInfo) => {
|
|
2016
|
+
const symbol = map.get(file.fileName) || getReferencedSymbol(ctx, file);
|
|
2017
|
+
map.set(file.fileName, symbol);
|
|
2018
|
+
const propNames = /* @__PURE__ */ new Set([`.${prop.text}`]);
|
|
2019
|
+
const kebabCaseName = camelToKebabCase(prop.text);
|
|
2020
|
+
["", "?", "@"].forEach((c) => propNames.add(`${c}${kebabCaseName}`));
|
|
2021
|
+
for (const propName of propNames) {
|
|
2022
|
+
const info = tagInfo.node.attributesMap.get(propName);
|
|
2023
|
+
if (!info) continue;
|
|
2024
|
+
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
2025
|
+
symbol.references.push({ fileName: file.fileName, isWriteAccess: true, textSpan });
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
return [...map.values(), ...oResult];
|
|
2031
|
+
};
|
|
2032
|
+
languageService.getDefinitionAndBoundSpan = (...args) => {
|
|
2033
|
+
const classMapKeyInfo = getClassMapKeyInfo(ctx, ...args);
|
|
2034
|
+
const kind = ts.ScriptElementKind.classElement;
|
|
2035
|
+
const containerKind = ts.ScriptElementKind.unknown;
|
|
2036
|
+
const fileName = args[0];
|
|
2037
|
+
if (classMapKeyInfo) {
|
|
2038
|
+
return {
|
|
2039
|
+
textSpan: classMapKeyInfo.textSpan,
|
|
2040
|
+
definitions: classMapKeyInfo.styles.flatMap(({ classIdNodeMap, templateNode }) => {
|
|
2041
|
+
return classIdNodeMap.get(classMapKeyInfo.text)?.map((node) => ({
|
|
2042
|
+
kind,
|
|
2043
|
+
containerKind,
|
|
2044
|
+
fileName,
|
|
2045
|
+
containerName: "",
|
|
2046
|
+
name: "",
|
|
2047
|
+
textSpan: { start: node.offset + templateNode.pos + 1, length: node.getText().length }
|
|
2048
|
+
}));
|
|
2049
|
+
}).filter(isNotNullish)
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, ...args);
|
|
2053
|
+
if (!tagDefinedInfo) return ls.getDefinitionAndBoundSpan(...args);
|
|
2054
|
+
const textSpan = tagDefinedInfo.textSpan;
|
|
2055
|
+
return {
|
|
2056
|
+
textSpan,
|
|
2057
|
+
definitions: [{ kind, containerKind, fileName, containerName: "", name: "", textSpan }]
|
|
2058
|
+
};
|
|
2059
|
+
};
|
|
2060
|
+
languageService.getRenameInfo = (fileName, position, ...args) => {
|
|
2061
|
+
const tagInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
2062
|
+
if (tagInfo) {
|
|
2063
|
+
return {
|
|
2064
|
+
canRename: true,
|
|
2065
|
+
displayName: tagInfo.tag,
|
|
2066
|
+
fullDisplayName: tagInfo.tag,
|
|
2067
|
+
kind: ts.ScriptElementKind.alias,
|
|
2068
|
+
kindModifiers: "tag",
|
|
2069
|
+
triggerSpan: tagInfo.open
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
|
|
2073
|
+
if (tagDefinedInfo) {
|
|
2074
|
+
return {
|
|
2075
|
+
canRename: true,
|
|
2076
|
+
displayName: tagDefinedInfo.tag,
|
|
2077
|
+
fullDisplayName: tagDefinedInfo.tag,
|
|
2078
|
+
kind: ts.ScriptElementKind.alias,
|
|
2079
|
+
kindModifiers: "tag",
|
|
2080
|
+
triggerSpan: tagDefinedInfo.textSpan
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
return ls.getRenameInfo(fileName, position, ...args);
|
|
2084
|
+
};
|
|
2085
|
+
languageService.findRenameLocations = (fileName, position, ...args) => {
|
|
2086
|
+
const tagPairInfo = findCurrentTagInfo(ctx, fileName, position);
|
|
2087
|
+
if (tagPairInfo) {
|
|
2088
|
+
const result = [{ fileName, textSpan: tagPairInfo.open }];
|
|
2089
|
+
if (tagPairInfo.end) result.push({ fileName, textSpan: tagPairInfo.end });
|
|
2090
|
+
return result;
|
|
2091
|
+
}
|
|
2092
|
+
const tagDefinedInfo = findDefinedTagInfo(ctx, fileName, position);
|
|
2093
|
+
if (tagDefinedInfo) {
|
|
2094
|
+
const result = [{ fileName, textSpan: tagDefinedInfo.textSpan }];
|
|
2095
|
+
forEachAllHtmlTemplateNode(ctx, tagDefinedInfo.tag, (f, info) => {
|
|
2096
|
+
result.push({ fileName: f.fileName, textSpan: info.open });
|
|
2097
|
+
if (info.end) result.push({ fileName: f.fileName, textSpan: info.end });
|
|
2098
|
+
});
|
|
2099
|
+
forEachAllCssTemplateNode(ctx, tagDefinedInfo.tag, (f, textSpan) => {
|
|
2100
|
+
result.push({ fileName: f.fileName, textSpan });
|
|
2101
|
+
});
|
|
2102
|
+
return result;
|
|
2103
|
+
}
|
|
2104
|
+
const oResult = [...ls.findRenameLocations(fileName, position, ...args) || []];
|
|
2105
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2106
|
+
const node = getAstNodeAtPosition(ts, file, position);
|
|
2107
|
+
const tag = node && ts.isPropertyDeclaration(node.parent) && ctx.getTagFromNode(node.parent.parent);
|
|
2108
|
+
if (!tag || !ts.isIdentifier(node)) return oResult;
|
|
2109
|
+
const propText = node.getText();
|
|
2110
|
+
const kebabCaseName = camelToKebabCase(propText);
|
|
2111
|
+
if (isPropType(ts, node.parent, [Decorators.Emitter, Decorators.GlobalEmitter])) {
|
|
2112
|
+
getAllTagFromProp(ctx, node).forEach((tag2) => {
|
|
2113
|
+
forEachAllHtmlTemplateNode(ctx, tag2, (f, tagInfo) => {
|
|
2114
|
+
const info = tagInfo.node.attributesMap.get(`@${kebabCaseName}`);
|
|
2115
|
+
if (!info) return;
|
|
2116
|
+
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
2117
|
+
oResult.push({ textSpan, fileName: f.fileName, prefixText: "@" });
|
|
2118
|
+
});
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
if (isPropType(ts, node.parent, [Decorators.Attr, Decorators.NumAttr, Decorators.BoolAttr, Decorators.Prop])) {
|
|
2122
|
+
getAllTagFromProp(ctx, node).forEach((tag2) => {
|
|
2123
|
+
forEachAllHtmlTemplateNode(ctx, tag2, (f, tagInfo) => {
|
|
2124
|
+
const propNames = [
|
|
2125
|
+
["", kebabCaseName],
|
|
2126
|
+
["?", kebabCaseName],
|
|
2127
|
+
[".", propText]
|
|
2128
|
+
];
|
|
2129
|
+
propNames.map(([decorate2, propName]) => {
|
|
2130
|
+
const info = tagInfo.node.attributesMap.get(decorate2 + propName);
|
|
2131
|
+
if (!info) return;
|
|
2132
|
+
const textSpan = { start: info.start + tagInfo.offset, length: info.end - info.start };
|
|
2133
|
+
oResult.push({ textSpan, fileName: f.fileName, prefixText: decorate2 });
|
|
2134
|
+
});
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
return oResult;
|
|
2139
|
+
};
|
|
2140
|
+
return languageService;
|
|
2141
|
+
}
|
|
2142
|
+
function getAllTagFromProp(ctx, prop) {
|
|
2143
|
+
let originTagDecl = prop;
|
|
2144
|
+
while (!ctx.ts.isClassDeclaration(originTagDecl)) {
|
|
2145
|
+
originTagDecl = originTagDecl.parent;
|
|
2146
|
+
}
|
|
2147
|
+
const typeChecker = ctx.getProgram().getTypeChecker();
|
|
2148
|
+
const originTagType = typeChecker.getTypeAtLocation(originTagDecl);
|
|
2149
|
+
const result = [];
|
|
2150
|
+
[...ctx.elements].forEach(([tag, decl]) => {
|
|
2151
|
+
const tagType = typeChecker.getTypeAtLocation(decl);
|
|
2152
|
+
if (!tagType.isClassOrInterface()) return;
|
|
2153
|
+
if (typeChecker.isTypeAssignableTo(tagType, originTagType)) {
|
|
2154
|
+
result.push(tag);
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
return result;
|
|
2158
|
+
}
|
|
2159
|
+
function getReferencedSymbol(ctx, file) {
|
|
2160
|
+
return {
|
|
2161
|
+
references: [],
|
|
2162
|
+
definition: {
|
|
2163
|
+
containerKind: ctx.ts.ScriptElementKind.unknown,
|
|
2164
|
+
containerName: "",
|
|
2165
|
+
displayParts: [],
|
|
2166
|
+
fileName: file.fileName,
|
|
2167
|
+
textSpan: { start: 0, length: 0 },
|
|
2168
|
+
name: "test",
|
|
2169
|
+
kind: ctx.ts.ScriptElementKind.unknown
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
function forEachAllHtmlTemplateNode(ctx, tag, fn) {
|
|
2174
|
+
for (const file of ctx.getProgram().getSourceFiles()) {
|
|
2175
|
+
if (file.fileName.endsWith(".d.ts")) continue;
|
|
2176
|
+
for (const templateContext of ctx.htmlSourceHelper.getAllTemplates(file.fileName)) {
|
|
2177
|
+
const { tagNodeMap } = ctx.getHtmlDoc(templateContext.text);
|
|
2178
|
+
tagNodeMap.get(tag)?.forEach((node) => fn(file, getTagInfo(node, templateContext.node.getStart() + 1)));
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function forEachAllCssTemplateNode(ctx, tag, fn) {
|
|
2183
|
+
for (const file of ctx.getProgram().getSourceFiles()) {
|
|
2184
|
+
if (file.fileName.endsWith(".d.ts")) continue;
|
|
2185
|
+
for (const templateContext of ctx.cssSourceHelper.getAllTemplates(file.fileName)) {
|
|
2186
|
+
const { tagNodeMap } = ctx.getCssDoc(templateContext.text);
|
|
2187
|
+
const offset = templateContext.node.getStart() + 1;
|
|
2188
|
+
tagNodeMap.get(tag)?.forEach((node) => fn(file, { start: offset + node.offset, length: node.end - node.offset }));
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
function findCurrentTagInfo(ctx, fileName, position) {
|
|
2193
|
+
const templateContext = ctx.htmlSourceHelper.getTemplate(fileName, position);
|
|
2194
|
+
if (!templateContext) return;
|
|
2195
|
+
const htmlOffset = templateContext.node.pos + 1;
|
|
2196
|
+
const { vHtml } = ctx.getHtmlDoc(templateContext.text);
|
|
2197
|
+
const relativePosition = ctx.htmlSourceHelper.getRelativePosition(templateContext, position);
|
|
2198
|
+
const offset = templateContext.toOffset(relativePosition);
|
|
2199
|
+
const node = vHtml.findNodeAt(offset);
|
|
2200
|
+
if (node.tag && offset < node.start + 1 + node.tag.length) return getTagInfo(node, htmlOffset);
|
|
2201
|
+
}
|
|
2202
|
+
function findDefinedTagInfo(ctx, fileName, position) {
|
|
2203
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2204
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2205
|
+
if (!node || !ctx.ts.isStringLiteral(node) || !ctx.ts.isCallExpression(node.parent) || node.parent.expression.getText() !== Decorators.CustomElement) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const tag = node.text;
|
|
2209
|
+
return { tag, textSpan: { start: node.getStart() + 1, length: tag.length } };
|
|
2210
|
+
}
|
|
2211
|
+
function getClassMapKeyInfo(ctx, fileName, position) {
|
|
2212
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2213
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2214
|
+
const decl = node && getCurrentElementDecl(ctx.ts, node);
|
|
2215
|
+
if (decl && isClassMapKey(ctx.ts, node)) {
|
|
2216
|
+
const isString = ctx.ts.isStringLiteral(node);
|
|
2217
|
+
return {
|
|
2218
|
+
text: node.text,
|
|
2219
|
+
styles: ctx.getAllCss(decl),
|
|
2220
|
+
textSpan: { start: node.getStart() + (isString ? 1 : 0), length: node.text.length }
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
function isKey(typescript, node) {
|
|
2225
|
+
if (!node.parent?.parent) return false;
|
|
2226
|
+
const assignment = node.parent;
|
|
2227
|
+
const obj = assignment.parent;
|
|
2228
|
+
const key = typescript.isStringLiteral(node) || typescript.isIdentifier(node);
|
|
2229
|
+
return key && (typescript.isPropertyAssignment(assignment) && assignment.initializer !== node || typescript.isShorthandPropertyAssignment(assignment)) && typescript.isObjectLiteralExpression(obj);
|
|
2230
|
+
}
|
|
2231
|
+
function inReturn(typescript, node) {
|
|
2232
|
+
if (!node?.parent?.parent) return false;
|
|
2233
|
+
const isReturnObject = (n) => typescript.isObjectLiteralExpression(n) && (typescript.isParenthesizedExpression(n.parent) && typescript.isArrowFunction(n.parent.parent) && n.parent.parent.body === n.parent || typescript.isReturnStatement(n.parent));
|
|
2234
|
+
return (
|
|
2235
|
+
// 空对象
|
|
2236
|
+
isReturnObject(node) || // 在返回对象的 key 上
|
|
2237
|
+
isReturnObject(node.parent.parent) && isKey(typescript, node)
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
function getThemeKeys(ctx, fileName, position) {
|
|
2241
|
+
const program = ctx.getProgram();
|
|
2242
|
+
const typeChecker = program.getTypeChecker();
|
|
2243
|
+
const file = program.getSourceFile(fileName);
|
|
2244
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2245
|
+
if (!inReturn(ctx.ts, node)) return;
|
|
2246
|
+
let desc = node;
|
|
2247
|
+
while (desc) {
|
|
2248
|
+
if ((ctx.ts.isArrowFunction(desc) || ctx.ts.isFunctionExpression(desc)) && !ctx.ts.isPropertyDeclaration(desc.parent) || ctx.ts.isFunctionDeclaration(desc) || ctx.ts.isClassDeclaration(desc) || ctx.ts.isClassExpression(desc)) {
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
if (ctx.ts.isPropertyDeclaration(desc) || ctx.ts.isMethodDeclaration(desc)) {
|
|
2252
|
+
for (const modifier of desc.modifiers || []) {
|
|
2253
|
+
if (!ctx.ts.isDecorator(modifier) || !ctx.ts.isCallExpression(modifier.expression)) continue;
|
|
2254
|
+
const themeDescSymbol = typeChecker.getSymbolAtLocation(modifier.expression.expression);
|
|
2255
|
+
const themeDesc = themeDescSymbol?.valueDeclaration;
|
|
2256
|
+
const param = themeDesc && ctx.ts.isVariableDeclaration(themeDesc) && themeDesc.initializer && ctx.ts.isCallExpression(themeDesc.initializer) && themeDesc.initializer.expression.getText() === Utils.CreateDecoratorTheme && themeDesc.initializer.arguments.at(0);
|
|
2257
|
+
if (!param) continue;
|
|
2258
|
+
const type = typeChecker.getTypeAtLocation(param);
|
|
2259
|
+
return type.getApparentProperties().map((e) => e.name);
|
|
2260
|
+
}
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
desc = desc.parent;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
function isClassMap(typescript, node) {
|
|
2267
|
+
if (!node.parent?.parent) return false;
|
|
2268
|
+
const callExp = node.parent;
|
|
2269
|
+
const isEmptyClassMap = typescript.isObjectLiteralExpression(node) && typescript.isCallExpression(callExp) && typescript.isIdentifier(callExp.expression) && callExp.expression.text === Utils.ClassMap;
|
|
2270
|
+
return isEmptyClassMap || isClassMapKey(typescript, node);
|
|
2271
|
+
}
|
|
2272
|
+
function getCurrentClassMap(typescript, node) {
|
|
2273
|
+
while (!typescript.isObjectLiteralExpression(node)) {
|
|
2274
|
+
node = node.parent;
|
|
2275
|
+
if (!node) return;
|
|
2276
|
+
}
|
|
2277
|
+
return node;
|
|
2278
|
+
}
|
|
2279
|
+
function getClassKeys(ctx, fileName, position) {
|
|
2280
|
+
const file = ctx.getProgram().getSourceFile(fileName);
|
|
2281
|
+
const node = getAstNodeAtPosition(ctx.ts, file, position);
|
|
2282
|
+
const decl = node && getCurrentElementDecl(ctx.ts, node);
|
|
2283
|
+
if (decl && isClassMap(ctx.ts, node)) {
|
|
2284
|
+
const obj = getCurrentClassMap(ctx.ts, node);
|
|
2285
|
+
const keys2 = new Set(obj.properties.map((e) => e.name?.getText()));
|
|
2286
|
+
return ctx.getAllCss(decl).flatMap(({ classIdNodeMap }) => {
|
|
2287
|
+
return [...classIdNodeMap.entries()].filter(([k]) => !keys2.has(k) && !k.startsWith("#")).map(([k]) => k);
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
function isPropType(typescript, node, types) {
|
|
2292
|
+
if (!typescript.isPropertyDeclaration(node)) return;
|
|
2293
|
+
for (const modifier of node.modifiers || []) {
|
|
2294
|
+
if (!typescript.isDecorator(modifier)) continue;
|
|
2295
|
+
const { expression } = modifier;
|
|
2296
|
+
if (typescript.isIdentifier(expression) && types.includes(expression.text)) {
|
|
2297
|
+
return true;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function decorateTypeChecker(typeChecker) {
|
|
2302
|
+
const neverType = typeChecker.getNeverType();
|
|
2303
|
+
const internal = typeChecker;
|
|
2304
|
+
const checker = bindMemberFunction(internal, ["isValidPropertyAccessForCompletions"]);
|
|
2305
|
+
internal.isValidPropertyAccessForCompletions = (...args) => {
|
|
2306
|
+
const result = checker.isValidPropertyAccessForCompletions(...args);
|
|
2307
|
+
try {
|
|
2308
|
+
return result && typeChecker.getTypeOfSymbol(args.at(2)) !== neverType;
|
|
2309
|
+
} catch {
|
|
2310
|
+
return result;
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
return typeChecker;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// src/index.ts
|
|
2317
|
+
var LanguageServiceLogger = class {
|
|
2318
|
+
#info;
|
|
2319
|
+
constructor(info) {
|
|
2320
|
+
this.#info = info;
|
|
2321
|
+
}
|
|
2322
|
+
log(msg) {
|
|
2323
|
+
this.#info.project.projectService.logger.info(`[${NAME}] ${msg}`);
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
var HtmlPlugin = class {
|
|
2327
|
+
#ts;
|
|
2328
|
+
#config = new Configuration();
|
|
2329
|
+
constructor(typescript) {
|
|
2330
|
+
this.#ts = typescript;
|
|
2331
|
+
}
|
|
2332
|
+
create(info) {
|
|
2333
|
+
return decorate(info.languageService, () => {
|
|
2334
|
+
const logger = new LanguageServiceLogger(info);
|
|
2335
|
+
logger.log("Starting...");
|
|
2336
|
+
this.#config.update(info.config);
|
|
2337
|
+
const context = new Context(this.#ts, this.#config, info, logger);
|
|
2338
|
+
const decoratedService = decorateLanguageService(context, info.languageService);
|
|
2339
|
+
const decoratedService1 = (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
|
|
2340
|
+
this.#ts,
|
|
2341
|
+
decoratedService,
|
|
2342
|
+
info.project,
|
|
2343
|
+
new CSSLanguageService(context),
|
|
2344
|
+
context.cssTemplateStringSettings,
|
|
2345
|
+
{ logger }
|
|
2346
|
+
);
|
|
2347
|
+
return (0, import_typescript_template_language_service_decorator.decorateWithTemplateLanguageService)(
|
|
2348
|
+
this.#ts,
|
|
2349
|
+
decoratedService1,
|
|
2350
|
+
info.project,
|
|
2351
|
+
new HTMLLanguageService(context),
|
|
2352
|
+
context.htmlTemplateStringSettings,
|
|
2353
|
+
{ logger }
|
|
2354
|
+
);
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
onConfigurationChanged(config) {
|
|
2358
|
+
this.#config.update(config);
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
module.exports = (mod) => new HtmlPlugin(mod.typescript);
|
|
2362
|
+
//# sourceMappingURL=index.js.map
|