@vue/language-service 2.0.0 → 2.0.1
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/index.d.ts +7 -0
- package/index.js +64 -0
- package/lib/ideFeatures/nameCasing.d.ts +13 -0
- package/lib/ideFeatures/nameCasing.js +211 -0
- package/lib/plugins/css.d.ts +2 -0
- package/lib/plugins/css.js +27 -0
- package/lib/plugins/data.d.ts +4 -0
- package/lib/plugins/data.js +91 -0
- package/lib/plugins/vue-autoinsert-dotvalue.d.ts +10 -0
- package/lib/plugins/vue-autoinsert-dotvalue.js +177 -0
- package/lib/plugins/vue-autoinsert-parentheses.d.ts +2 -0
- package/lib/plugins/vue-autoinsert-parentheses.js +60 -0
- package/lib/plugins/vue-autoinsert-space.d.ts +2 -0
- package/lib/plugins/vue-autoinsert-space.js +34 -0
- package/lib/plugins/vue-codelens-references.d.ts +2 -0
- package/lib/plugins/vue-codelens-references.js +38 -0
- package/lib/plugins/vue-directive-comments.d.ts +2 -0
- package/lib/plugins/vue-directive-comments.js +61 -0
- package/lib/plugins/vue-document-drop.d.ts +2 -0
- package/lib/plugins/vue-document-drop.js +81 -0
- package/lib/plugins/vue-extract-file.d.ts +8 -0
- package/lib/plugins/vue-extract-file.js +258 -0
- package/lib/plugins/vue-sfc.d.ts +7 -0
- package/lib/plugins/vue-sfc.js +163 -0
- package/lib/plugins/vue-template.d.ts +3 -0
- package/lib/plugins/vue-template.js +594 -0
- package/lib/plugins/vue-toggle-v-bind-codeaction.d.ts +2 -0
- package/lib/plugins/vue-toggle-v-bind-codeaction.js +126 -0
- package/lib/plugins/vue-twoslash-queries.d.ts +2 -0
- package/lib/plugins/vue-twoslash-queries.js +50 -0
- package/lib/plugins/vue-visualize-hidden-callback-param.d.ts +2 -0
- package/lib/plugins/vue-visualize-hidden-callback-param.js +45 -0
- package/lib/types.d.ts +10 -0
- package/lib/types.js +31 -0
- package/package.json +6 -6
- package/scripts/update-html-data.js +426 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.create = void 0;
|
|
4
|
+
const language_core_1 = require("@vue/language-core");
|
|
5
|
+
const shared_1 = require("@vue/shared");
|
|
6
|
+
const namedPipeClient = require("@vue/typescript-plugin/lib/client");
|
|
7
|
+
const volar_service_html_1 = require("volar-service-html");
|
|
8
|
+
const volar_service_pug_1 = require("volar-service-pug");
|
|
9
|
+
const html = require("vscode-html-languageservice");
|
|
10
|
+
const nameCasing_1 = require("../ideFeatures/nameCasing");
|
|
11
|
+
const types_1 = require("../types");
|
|
12
|
+
const data_1 = require("./data");
|
|
13
|
+
let builtInData;
|
|
14
|
+
let modelData;
|
|
15
|
+
function create(mode, ts, getVueOptions) {
|
|
16
|
+
let customData = [];
|
|
17
|
+
const onDidChangeCustomDataListeners = new Set();
|
|
18
|
+
const onDidChangeCustomData = (listener) => {
|
|
19
|
+
onDidChangeCustomDataListeners.add(listener);
|
|
20
|
+
return {
|
|
21
|
+
dispose() {
|
|
22
|
+
onDidChangeCustomDataListeners.delete(listener);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const baseServicePlugin = mode === 'pug' ? volar_service_pug_1.create : volar_service_html_1.create;
|
|
27
|
+
const baseService = baseServicePlugin({
|
|
28
|
+
getCustomData() {
|
|
29
|
+
return customData;
|
|
30
|
+
},
|
|
31
|
+
onDidChangeCustomData,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
name: `vue-template (${mode})`,
|
|
35
|
+
triggerCharacters: [
|
|
36
|
+
...baseService.triggerCharacters ?? [],
|
|
37
|
+
'@', // vue event shorthand
|
|
38
|
+
],
|
|
39
|
+
create(context) {
|
|
40
|
+
const baseServiceInstance = baseService.create(context);
|
|
41
|
+
const vueCompilerOptions = getVueOptions(context.env);
|
|
42
|
+
builtInData ??= (0, data_1.loadTemplateData)(context.env.locale ?? 'en');
|
|
43
|
+
modelData ??= (0, data_1.loadModelModifiersData)(context.env.locale ?? 'en');
|
|
44
|
+
// https://vuejs.org/api/built-in-directives.html#v-on
|
|
45
|
+
// https://vuejs.org/api/built-in-directives.html#v-bind
|
|
46
|
+
const eventModifiers = {};
|
|
47
|
+
const propModifiers = {};
|
|
48
|
+
const vOn = builtInData.globalAttributes?.find(x => x.name === 'v-on');
|
|
49
|
+
const vBind = builtInData.globalAttributes?.find(x => x.name === 'v-bind');
|
|
50
|
+
if (vOn) {
|
|
51
|
+
const markdown = (typeof vOn.description === 'string' ? vOn.description : vOn.description?.value) ?? '';
|
|
52
|
+
const modifiers = markdown
|
|
53
|
+
.split('\n- ')[4]
|
|
54
|
+
.split('\n').slice(2, -1);
|
|
55
|
+
for (let text of modifiers) {
|
|
56
|
+
text = text.substring(' - `.'.length);
|
|
57
|
+
const [name, disc] = text.split('` - ');
|
|
58
|
+
eventModifiers[name] = disc;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (vBind) {
|
|
62
|
+
const markdown = (typeof vBind.description === 'string' ? vBind.description : vBind.description?.value) ?? '';
|
|
63
|
+
const modifiers = markdown
|
|
64
|
+
.split('\n- ')[4]
|
|
65
|
+
.split('\n').slice(2, -1);
|
|
66
|
+
for (let text of modifiers) {
|
|
67
|
+
text = text.substring(' - `.'.length);
|
|
68
|
+
const [name, disc] = text.split('` - ');
|
|
69
|
+
propModifiers[name] = disc;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...baseServiceInstance,
|
|
74
|
+
async provideCompletionItems(document, position, completionContext, token) {
|
|
75
|
+
if (!isSupportedDocument(document))
|
|
76
|
+
return;
|
|
77
|
+
let sync;
|
|
78
|
+
let currentVersion;
|
|
79
|
+
const [_, sourceFile] = context.documents.getVirtualCodeByUri(document.uri);
|
|
80
|
+
if (sourceFile?.generated?.code instanceof language_core_1.VueGeneratedCode) {
|
|
81
|
+
sync = (await provideHtmlData(sourceFile.id, sourceFile.generated.code)).sync;
|
|
82
|
+
currentVersion = await sync();
|
|
83
|
+
}
|
|
84
|
+
let htmlComplete = await baseServiceInstance.provideCompletionItems?.(document, position, completionContext, token);
|
|
85
|
+
while (currentVersion !== (currentVersion = await sync?.())) {
|
|
86
|
+
htmlComplete = await baseServiceInstance.provideCompletionItems?.(document, position, completionContext, token);
|
|
87
|
+
}
|
|
88
|
+
if (!htmlComplete)
|
|
89
|
+
return;
|
|
90
|
+
if (sourceFile?.generated?.code instanceof language_core_1.VueGeneratedCode) {
|
|
91
|
+
await afterHtmlCompletion(htmlComplete, context.documents.get(sourceFile.id, sourceFile.languageId, sourceFile.snapshot), sourceFile.generated.code);
|
|
92
|
+
}
|
|
93
|
+
return htmlComplete;
|
|
94
|
+
},
|
|
95
|
+
async provideInlayHints(document) {
|
|
96
|
+
if (!isSupportedDocument(document))
|
|
97
|
+
return;
|
|
98
|
+
const enabled = await context.env.getConfiguration?.('vue.inlayHints.missingProps') ?? false;
|
|
99
|
+
if (!enabled)
|
|
100
|
+
return;
|
|
101
|
+
const result = [];
|
|
102
|
+
const [virtualCode] = context.documents.getVirtualCodeByUri(document.uri);
|
|
103
|
+
if (!virtualCode)
|
|
104
|
+
return;
|
|
105
|
+
for (const map of context.documents.getMaps(virtualCode)) {
|
|
106
|
+
const code = context.language.files.get(map.sourceDocument.uri)?.generated?.code;
|
|
107
|
+
const scanner = getScanner(baseServiceInstance, document);
|
|
108
|
+
if (code instanceof language_core_1.VueGeneratedCode && scanner) {
|
|
109
|
+
// visualize missing required props
|
|
110
|
+
const casing = await (0, nameCasing_1.getNameCasing)(context, map.sourceDocument.uri);
|
|
111
|
+
const components = await namedPipeClient.getComponentNames(code.fileName) ?? [];
|
|
112
|
+
const componentProps = {};
|
|
113
|
+
let token;
|
|
114
|
+
let current;
|
|
115
|
+
while ((token = scanner.scan()) !== html.TokenType.EOS) {
|
|
116
|
+
if (token === html.TokenType.StartTag) {
|
|
117
|
+
const tagName = scanner.getTokenText();
|
|
118
|
+
const component = tagName.indexOf('.') >= 0
|
|
119
|
+
? components.find(component => component === tagName.split('.')[0])
|
|
120
|
+
: components.find(component => component === tagName || (0, language_core_1.hyphenateTag)(component) === tagName);
|
|
121
|
+
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
|
|
122
|
+
if (checkTag) {
|
|
123
|
+
componentProps[checkTag] ??= await namedPipeClient.getComponentProps(code.fileName, checkTag, true) ?? [];
|
|
124
|
+
current = {
|
|
125
|
+
unburnedRequiredProps: [...componentProps[checkTag]],
|
|
126
|
+
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
|
|
127
|
+
insertOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (token === html.TokenType.AttributeName) {
|
|
132
|
+
if (current) {
|
|
133
|
+
let attrText = scanner.getTokenText();
|
|
134
|
+
if (attrText === 'v-bind') {
|
|
135
|
+
current.unburnedRequiredProps = [];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// remove modifiers
|
|
139
|
+
if (attrText.indexOf('.') >= 0) {
|
|
140
|
+
attrText = attrText.split('.')[0];
|
|
141
|
+
}
|
|
142
|
+
// normalize
|
|
143
|
+
if (attrText.startsWith('v-bind:')) {
|
|
144
|
+
attrText = attrText.substring('v-bind:'.length);
|
|
145
|
+
}
|
|
146
|
+
else if (attrText.startsWith(':')) {
|
|
147
|
+
attrText = attrText.substring(':'.length);
|
|
148
|
+
}
|
|
149
|
+
else if (attrText.startsWith('v-model:')) {
|
|
150
|
+
attrText = attrText.substring('v-model:'.length);
|
|
151
|
+
}
|
|
152
|
+
else if (attrText === 'v-model') {
|
|
153
|
+
attrText = vueCompilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName?
|
|
154
|
+
}
|
|
155
|
+
else if (attrText.startsWith('@')) {
|
|
156
|
+
attrText = 'on-' + (0, language_core_1.hyphenateAttr)(attrText.substring('@'.length));
|
|
157
|
+
}
|
|
158
|
+
current.unburnedRequiredProps = current.unburnedRequiredProps.filter(propName => {
|
|
159
|
+
return attrText !== propName
|
|
160
|
+
&& attrText !== (0, language_core_1.hyphenateAttr)(propName);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (token === html.TokenType.StartTagSelfClose || token === html.TokenType.StartTagClose) {
|
|
166
|
+
if (current) {
|
|
167
|
+
for (const requiredProp of current.unburnedRequiredProps) {
|
|
168
|
+
result.push({
|
|
169
|
+
label: `${requiredProp}!`,
|
|
170
|
+
paddingLeft: true,
|
|
171
|
+
position: document.positionAt(current.labelOffset),
|
|
172
|
+
kind: 2,
|
|
173
|
+
textEdits: [{
|
|
174
|
+
range: {
|
|
175
|
+
start: document.positionAt(current.insertOffset),
|
|
176
|
+
end: document.positionAt(current.insertOffset),
|
|
177
|
+
},
|
|
178
|
+
newText: ` :${casing.attr === types_1.AttrNameCasing.Kebab ? (0, language_core_1.hyphenateAttr)(requiredProp) : requiredProp}=`,
|
|
179
|
+
}],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
current = undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (token === html.TokenType.AttributeName || token === html.TokenType.AttributeValue) {
|
|
186
|
+
if (current) {
|
|
187
|
+
current.insertOffset = scanner.getTokenOffset() + scanner.getTokenLength();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
},
|
|
195
|
+
provideHover(document, position, token) {
|
|
196
|
+
if (!isSupportedDocument(document))
|
|
197
|
+
return;
|
|
198
|
+
if (context.documents.getVirtualCodeByUri(document.uri)[0])
|
|
199
|
+
updateCustomData([]);
|
|
200
|
+
return baseServiceInstance.provideHover?.(document, position, token);
|
|
201
|
+
},
|
|
202
|
+
async provideDiagnostics(document, token) {
|
|
203
|
+
if (!isSupportedDocument(document))
|
|
204
|
+
return;
|
|
205
|
+
const originalResult = await baseServiceInstance.provideDiagnostics?.(document, token);
|
|
206
|
+
const [virtualCode] = context.documents.getVirtualCodeByUri(document.uri);
|
|
207
|
+
if (!virtualCode)
|
|
208
|
+
return;
|
|
209
|
+
for (const map of context.documents.getMaps(virtualCode)) {
|
|
210
|
+
const code = context.language.files.get(map.sourceDocument.uri)?.generated?.code;
|
|
211
|
+
if (!(code instanceof language_core_1.VueGeneratedCode))
|
|
212
|
+
continue;
|
|
213
|
+
const templateErrors = [];
|
|
214
|
+
const { template } = code.sfc;
|
|
215
|
+
if (template) {
|
|
216
|
+
for (const error of template.errors) {
|
|
217
|
+
onCompilerError(error, 1);
|
|
218
|
+
}
|
|
219
|
+
for (const warning of template.warnings) {
|
|
220
|
+
onCompilerError(warning, 2);
|
|
221
|
+
}
|
|
222
|
+
function onCompilerError(error, severity) {
|
|
223
|
+
const templateHtmlRange = {
|
|
224
|
+
start: error.loc?.start.offset ?? 0,
|
|
225
|
+
end: error.loc?.end.offset ?? 0,
|
|
226
|
+
};
|
|
227
|
+
let errorMessage = error.message;
|
|
228
|
+
templateErrors.push({
|
|
229
|
+
range: {
|
|
230
|
+
start: document.positionAt(templateHtmlRange.start),
|
|
231
|
+
end: document.positionAt(templateHtmlRange.end),
|
|
232
|
+
},
|
|
233
|
+
severity,
|
|
234
|
+
code: error.code,
|
|
235
|
+
source: 'vue',
|
|
236
|
+
message: errorMessage,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return [
|
|
241
|
+
...originalResult ?? [],
|
|
242
|
+
...templateErrors,
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
async function provideHtmlData(sourceDocumentUri, vueCode) {
|
|
248
|
+
const casing = await (0, nameCasing_1.getNameCasing)(context, sourceDocumentUri);
|
|
249
|
+
if (builtInData.tags) {
|
|
250
|
+
for (const tag of builtInData.tags) {
|
|
251
|
+
if (tag.name === 'slot')
|
|
252
|
+
continue;
|
|
253
|
+
if (tag.name === 'component')
|
|
254
|
+
continue;
|
|
255
|
+
if (tag.name === 'template')
|
|
256
|
+
continue;
|
|
257
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
258
|
+
tag.name = (0, language_core_1.hyphenateTag)(tag.name);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
tag.name = (0, shared_1.camelize)((0, shared_1.capitalize)(tag.name));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const promises = [];
|
|
266
|
+
const tagInfos = new Map();
|
|
267
|
+
let version = 0;
|
|
268
|
+
let components;
|
|
269
|
+
let templateContextProps;
|
|
270
|
+
updateCustomData([
|
|
271
|
+
html.newHTMLDataProvider('vue-template-built-in', builtInData),
|
|
272
|
+
{
|
|
273
|
+
getId: () => 'vue-template',
|
|
274
|
+
isApplicable: () => true,
|
|
275
|
+
provideTags: () => {
|
|
276
|
+
if (!components) {
|
|
277
|
+
promises.push((async () => {
|
|
278
|
+
components = (await namedPipeClient.getComponentNames(vueCode.fileName) ?? [])
|
|
279
|
+
.filter(name => name !== 'Transition'
|
|
280
|
+
&& name !== 'TransitionGroup'
|
|
281
|
+
&& name !== 'KeepAlive'
|
|
282
|
+
&& name !== 'Suspense'
|
|
283
|
+
&& name !== 'Teleport');
|
|
284
|
+
version++;
|
|
285
|
+
})());
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
const scriptSetupRanges = vueCode.sfc.scriptSetup ? (0, language_core_1.parseScriptSetupRanges)(ts, vueCode.sfc.scriptSetup.ast, vueCompilerOptions) : undefined;
|
|
289
|
+
const names = new Set();
|
|
290
|
+
const tags = [];
|
|
291
|
+
for (const tag of components) {
|
|
292
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
293
|
+
names.add((0, language_core_1.hyphenateTag)(tag));
|
|
294
|
+
}
|
|
295
|
+
else if (casing.tag === types_1.TagNameCasing.Pascal) {
|
|
296
|
+
names.add(tag);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const binding of scriptSetupRanges?.bindings ?? []) {
|
|
300
|
+
const name = vueCode.sfc.scriptSetup.content.substring(binding.start, binding.end);
|
|
301
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
302
|
+
names.add((0, language_core_1.hyphenateTag)(name));
|
|
303
|
+
}
|
|
304
|
+
else if (casing.tag === types_1.TagNameCasing.Pascal) {
|
|
305
|
+
names.add(name);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (const name of names) {
|
|
309
|
+
tags.push({
|
|
310
|
+
name: name,
|
|
311
|
+
attributes: [],
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return tags;
|
|
315
|
+
},
|
|
316
|
+
provideAttributes: (tag) => {
|
|
317
|
+
namedPipeClient.getTemplateContextProps;
|
|
318
|
+
let failed = false;
|
|
319
|
+
let tagInfo = tagInfos.get(tag);
|
|
320
|
+
if (!tagInfo) {
|
|
321
|
+
promises.push((async () => {
|
|
322
|
+
const attrs = await namedPipeClient.getElementAttrs(vueCode.fileName, tag) ?? [];
|
|
323
|
+
const props = await namedPipeClient.getComponentProps(vueCode.fileName, tag) ?? [];
|
|
324
|
+
const events = await namedPipeClient.getComponentEvents(vueCode.fileName, tag) ?? [];
|
|
325
|
+
tagInfos.set(tag, {
|
|
326
|
+
attrs,
|
|
327
|
+
props,
|
|
328
|
+
events,
|
|
329
|
+
});
|
|
330
|
+
version++;
|
|
331
|
+
})());
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
if (failed) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
const { attrs, props, events } = tagInfo;
|
|
338
|
+
const attributes = [];
|
|
339
|
+
const _tsCodegen = language_core_1.tsCodegen.get(vueCode.sfc);
|
|
340
|
+
if (_tsCodegen) {
|
|
341
|
+
if (!templateContextProps) {
|
|
342
|
+
promises.push((async () => {
|
|
343
|
+
templateContextProps = await namedPipeClient.getTemplateContextProps(vueCode.fileName) ?? [];
|
|
344
|
+
version++;
|
|
345
|
+
})());
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
let ctxVars = [
|
|
349
|
+
..._tsCodegen.scriptRanges()?.bindings.map(binding => vueCode.sfc.script.content.substring(binding.start, binding.end)) ?? [],
|
|
350
|
+
..._tsCodegen.scriptSetupRanges()?.bindings.map(binding => vueCode.sfc.scriptSetup.content.substring(binding.start, binding.end)) ?? [],
|
|
351
|
+
...templateContextProps,
|
|
352
|
+
];
|
|
353
|
+
ctxVars = [...new Set(ctxVars)];
|
|
354
|
+
const dirs = ctxVars.map(language_core_1.hyphenateAttr).filter(v => v.startsWith('v-'));
|
|
355
|
+
for (const dir of dirs) {
|
|
356
|
+
attributes.push({
|
|
357
|
+
name: dir,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const propsSet = new Set(props);
|
|
362
|
+
for (const prop of [...props, ...attrs]) {
|
|
363
|
+
const isGlobal = !propsSet.has(prop);
|
|
364
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? prop : (0, language_core_1.hyphenateAttr)(prop);
|
|
365
|
+
if ((0, language_core_1.hyphenateAttr)(name).startsWith('on-')) {
|
|
366
|
+
const propNameBase = name.startsWith('on-')
|
|
367
|
+
? name.slice('on-'.length)
|
|
368
|
+
: (name['on'.length].toLowerCase() + name.slice('onX'.length));
|
|
369
|
+
const propKey = createInternalItemId('componentEvent', [isGlobal ? '*' : tag, propNameBase]);
|
|
370
|
+
attributes.push({
|
|
371
|
+
name: 'v-on:' + propNameBase,
|
|
372
|
+
description: propKey,
|
|
373
|
+
}, {
|
|
374
|
+
name: '@' + propNameBase,
|
|
375
|
+
description: propKey,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
{
|
|
379
|
+
const propName = name;
|
|
380
|
+
const propKey = createInternalItemId('componentProp', [isGlobal ? '*' : tag, propName]);
|
|
381
|
+
attributes.push({
|
|
382
|
+
name: propName,
|
|
383
|
+
description: propKey,
|
|
384
|
+
}, {
|
|
385
|
+
name: ':' + propName,
|
|
386
|
+
description: propKey,
|
|
387
|
+
}, {
|
|
388
|
+
name: 'v-bind:' + propName,
|
|
389
|
+
description: propKey,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const event of events) {
|
|
394
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? event : (0, language_core_1.hyphenateAttr)(event);
|
|
395
|
+
const propKey = createInternalItemId('componentEvent', [tag, name]);
|
|
396
|
+
attributes.push({
|
|
397
|
+
name: 'v-on:' + name,
|
|
398
|
+
description: propKey,
|
|
399
|
+
});
|
|
400
|
+
attributes.push({
|
|
401
|
+
name: '@' + name,
|
|
402
|
+
description: propKey,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const models = [];
|
|
406
|
+
for (const prop of [...props, ...attrs]) {
|
|
407
|
+
if (prop.startsWith('onUpdate:')) {
|
|
408
|
+
const isGlobal = !propsSet.has(prop);
|
|
409
|
+
models.push([isGlobal, prop.substring('onUpdate:'.length)]);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const event of events) {
|
|
413
|
+
if (event.startsWith('update:')) {
|
|
414
|
+
models.push([false, event.substring('update:'.length)]);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const [isGlobal, model] of models) {
|
|
418
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? model : (0, language_core_1.hyphenateAttr)(model);
|
|
419
|
+
const propKey = createInternalItemId('componentProp', [isGlobal ? '*' : tag, name]);
|
|
420
|
+
attributes.push({
|
|
421
|
+
name: 'v-model:' + name,
|
|
422
|
+
description: propKey,
|
|
423
|
+
});
|
|
424
|
+
if (model === 'modelValue') {
|
|
425
|
+
attributes.push({
|
|
426
|
+
name: 'v-model',
|
|
427
|
+
description: propKey,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return attributes;
|
|
432
|
+
},
|
|
433
|
+
provideValues: () => [],
|
|
434
|
+
},
|
|
435
|
+
]);
|
|
436
|
+
return {
|
|
437
|
+
async sync() {
|
|
438
|
+
await Promise.all(promises);
|
|
439
|
+
return version;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function afterHtmlCompletion(completionList, sourceDocument, code) {
|
|
444
|
+
const replacement = getReplacement(completionList, sourceDocument);
|
|
445
|
+
const componentNames = new Set((await namedPipeClient.getComponentNames(code.fileName) ?? [])
|
|
446
|
+
.map(language_core_1.hyphenateTag));
|
|
447
|
+
if (replacement) {
|
|
448
|
+
const isEvent = replacement.text.startsWith('v-on:') || replacement.text.startsWith('@');
|
|
449
|
+
const isProp = replacement.text.startsWith('v-bind:') || replacement.text.startsWith(':');
|
|
450
|
+
const isModel = replacement.text.startsWith('v-model:') || replacement.text.split('.')[0] === 'v-model';
|
|
451
|
+
const hasModifier = replacement.text.includes('.');
|
|
452
|
+
const validModifiers = isEvent ? eventModifiers
|
|
453
|
+
: isProp ? propModifiers
|
|
454
|
+
: undefined;
|
|
455
|
+
const modifiers = replacement.text.split('.').slice(1);
|
|
456
|
+
const textWithoutModifier = replacement.text.split('.')[0];
|
|
457
|
+
if (validModifiers && hasModifier) {
|
|
458
|
+
for (const modifier in validModifiers) {
|
|
459
|
+
if (modifiers.includes(modifier))
|
|
460
|
+
continue;
|
|
461
|
+
const modifierDes = validModifiers[modifier];
|
|
462
|
+
const insertText = textWithoutModifier + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier;
|
|
463
|
+
const newItem = {
|
|
464
|
+
label: modifier,
|
|
465
|
+
filterText: insertText,
|
|
466
|
+
documentation: {
|
|
467
|
+
kind: 'markdown',
|
|
468
|
+
value: modifierDes,
|
|
469
|
+
},
|
|
470
|
+
textEdit: {
|
|
471
|
+
range: replacement.textEdit.range,
|
|
472
|
+
newText: insertText,
|
|
473
|
+
},
|
|
474
|
+
kind: 20,
|
|
475
|
+
};
|
|
476
|
+
completionList.items.push(newItem);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else if (hasModifier && isModel) {
|
|
480
|
+
for (const modifier of modelData.globalAttributes ?? []) {
|
|
481
|
+
if (modifiers.includes(modifier.name))
|
|
482
|
+
continue;
|
|
483
|
+
const insertText = textWithoutModifier + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier.name;
|
|
484
|
+
const newItem = {
|
|
485
|
+
label: modifier.name,
|
|
486
|
+
filterText: insertText,
|
|
487
|
+
documentation: {
|
|
488
|
+
kind: 'markdown',
|
|
489
|
+
value: (typeof modifier.description === 'object' ? modifier.description.value : modifier.description)
|
|
490
|
+
+ '\n\n' + modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | '),
|
|
491
|
+
},
|
|
492
|
+
textEdit: {
|
|
493
|
+
range: replacement.textEdit.range,
|
|
494
|
+
newText: insertText,
|
|
495
|
+
},
|
|
496
|
+
kind: 20,
|
|
497
|
+
};
|
|
498
|
+
completionList.items.push(newItem);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
for (const item of completionList.items) {
|
|
503
|
+
const itemIdKey = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value;
|
|
504
|
+
const itemId = itemIdKey ? readInternalItemId(itemIdKey) : undefined;
|
|
505
|
+
if (itemId) {
|
|
506
|
+
item.documentation = undefined;
|
|
507
|
+
}
|
|
508
|
+
if (item.kind === 10 && componentNames.has((0, language_core_1.hyphenateTag)(item.label))) {
|
|
509
|
+
item.kind = 6;
|
|
510
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
511
|
+
}
|
|
512
|
+
else if (itemId && (itemId.type === 'componentProp' || itemId.type === 'componentEvent')) {
|
|
513
|
+
const [componentName] = itemId.args;
|
|
514
|
+
if (componentName !== '*') {
|
|
515
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
516
|
+
}
|
|
517
|
+
if (itemId.type === 'componentProp') {
|
|
518
|
+
if (componentName !== '*') {
|
|
519
|
+
item.kind = 5;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
item.kind = componentName !== '*' ? 3 : 23;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else if (item.label === 'v-if'
|
|
527
|
+
|| item.label === 'v-else-if'
|
|
528
|
+
|| item.label === 'v-else'
|
|
529
|
+
|| item.label === 'v-for') {
|
|
530
|
+
item.kind = 14;
|
|
531
|
+
item.sortText = '\u0003' + (item.sortText ?? item.label);
|
|
532
|
+
}
|
|
533
|
+
else if (item.label.startsWith('v-')) {
|
|
534
|
+
item.kind = 3;
|
|
535
|
+
item.sortText = '\u0002' + (item.sortText ?? item.label);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
item.sortText = '\u0001' + (item.sortText ?? item.label);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
updateCustomData([]);
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
function getScanner(service, document) {
|
|
546
|
+
if (mode === 'html') {
|
|
547
|
+
return service.provide['html/languageService']().createScanner(document.getText());
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const pugDocument = service.provide['pug/pugDocument'](document);
|
|
551
|
+
if (pugDocument) {
|
|
552
|
+
return service.provide['pug/languageService']().createScanner(pugDocument);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function updateCustomData(extraData) {
|
|
557
|
+
customData = extraData;
|
|
558
|
+
onDidChangeCustomDataListeners.forEach(l => l());
|
|
559
|
+
}
|
|
560
|
+
function isSupportedDocument(document) {
|
|
561
|
+
if (mode === 'pug') {
|
|
562
|
+
return document.languageId === 'jade';
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
return document.languageId === 'html';
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
exports.create = create;
|
|
570
|
+
;
|
|
571
|
+
function createInternalItemId(type, args) {
|
|
572
|
+
return '__VLS_::' + type + '::' + args.join(',');
|
|
573
|
+
}
|
|
574
|
+
function readInternalItemId(key) {
|
|
575
|
+
if (key.startsWith('__VLS_::')) {
|
|
576
|
+
const strs = key.split('::');
|
|
577
|
+
return {
|
|
578
|
+
type: strs[1],
|
|
579
|
+
args: strs[2].split(','),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function getReplacement(list, doc) {
|
|
584
|
+
for (const item of list.items) {
|
|
585
|
+
if (item.textEdit && 'range' in item.textEdit) {
|
|
586
|
+
return {
|
|
587
|
+
item: item,
|
|
588
|
+
textEdit: item.textEdit,
|
|
589
|
+
text: doc.getText(item.textEdit.range)
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
//# sourceMappingURL=vue-template.js.map
|