@vue/language-service 3.1.8 → 3.2.0

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.
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.create = create;
4
37
  const language_service_1 = require("@volar/language-service");
@@ -8,37 +41,30 @@ const shared_1 = require("@vue/shared");
8
41
  const volar_service_html_1 = require("volar-service-html");
9
42
  const volar_service_pug_1 = require("volar-service-pug");
10
43
  const lspConverters_js_1 = require("volar-service-typescript/lib/utils/lspConverters.js");
11
- const html = require("vscode-html-languageservice");
44
+ const html = __importStar(require("vscode-html-languageservice"));
12
45
  const vscode_uri_1 = require("vscode-uri");
13
46
  const data_1 = require("../data");
14
47
  const htmlFormatter_1 = require("../htmlFormatter");
15
48
  const nameCasing_1 = require("../nameCasing");
16
49
  const utils_1 = require("../utils");
17
- const specialTags = new Set([
18
- 'slot',
19
- 'component',
20
- 'template',
21
- ]);
22
- const specialProps = new Set([
23
- 'class',
24
- 'data-allow-mismatch',
25
- 'is',
26
- 'key',
27
- 'ref',
28
- 'style',
29
- ]);
30
- const builtInComponents = new Set([
31
- 'Transition',
32
- 'TransitionGroup',
33
- 'KeepAlive',
34
- 'Suspense',
35
- 'Teleport',
36
- ]);
50
+ const EVENT_PROP_REGEX = /^on[A-Z]/;
51
+ // String constants
52
+ const AUTO_IMPORT_PLACEHOLDER = 'AutoImportsPlaceholder';
53
+ const UPDATE_EVENT_PREFIX = 'update:';
54
+ const UPDATE_PROP_PREFIX = 'onUpdate:';
55
+ // Directive prefixes
56
+ const DIRECTIVE_V_ON = 'v-on:';
57
+ const DIRECTIVE_V_BIND = 'v-bind:';
58
+ const DIRECTIVE_V_MODEL = 'v-model:';
59
+ const V_ON_SHORTHAND = '@';
60
+ const V_BIND_SHORTHAND = ':';
61
+ const DIRECTIVE_V_FOR_NAME = 'v-for';
62
+ // Templates
63
+ const V_FOR_SNIPPET = '="${1:value} in ${2:source}"';
37
64
  let builtInData;
38
65
  let modelData;
39
- function create(ts, languageId, { getComponentNames, getComponentProps, getComponentEvents, getComponentDirectives, getComponentSlots, getElementAttrs, resolveModuleName, getAutoImportSuggestions, resolveAutoImportCompletionEntry, }) {
40
- let customData = [];
41
- let extraCustomData = [];
66
+ function create(ts, languageId, tsserver) {
67
+ let htmlData = [];
42
68
  let modulePathCache;
43
69
  const onDidChangeCustomDataListeners = new Set();
44
70
  const onDidChangeCustomData = (listener) => {
@@ -63,7 +89,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
63
89
  const map = modulePathCache;
64
90
  if (!map.has(ref)) {
65
91
  const fileName = baseUri.fsPath.replace(/\\/g, '/');
66
- const promise = resolveModuleName(fileName, ref);
92
+ const promise = tsserver.resolveModuleName(fileName, ref);
67
93
  map.set(ref, promise);
68
94
  if (promise instanceof Promise) {
69
95
  promise.then(res => map.set(ref, res));
@@ -85,10 +111,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
85
111
  useDefaultDataProvider: false,
86
112
  getDocumentContext,
87
113
  getCustomData() {
88
- return [
89
- ...customData,
90
- ...extraCustomData,
91
- ];
114
+ return htmlData;
92
115
  },
93
116
  onDidChangeCustomData,
94
117
  })
@@ -97,14 +120,10 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
97
120
  useDefaultDataProvider: false,
98
121
  getDocumentContext,
99
122
  getCustomData() {
100
- return [
101
- ...customData,
102
- ...extraCustomData,
103
- ];
123
+ return htmlData;
104
124
  },
105
125
  onDidChangeCustomData,
106
126
  });
107
- const htmlDataProvider = html.getDefaultHTMLDataProvider();
108
127
  return {
109
128
  name: `vue-template (${languageId})`,
110
129
  capabilities: {
@@ -147,7 +166,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
147
166
  const codegen = info && language_core_1.tsCodegen.get(info.root.sfc);
148
167
  if (codegen) {
149
168
  const componentNames = new Set([
150
- ...codegen.getImportComponentNames(),
169
+ ...codegen.getImportedComponents(),
151
170
  ...codegen.getSetupExposed(),
152
171
  ]);
153
172
  // copied from https://github.com/microsoft/vscode-html-languageservice/blob/10daf45dc16b4f4228987cf7cddf3a7dbbdc7570/src/beautify/beautify-html.js#L2746-L2761
@@ -182,57 +201,20 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
182
201
  builtInData ??= (0, data_1.loadTemplateData)(context.env.locale ?? 'en');
183
202
  modelData ??= (0, data_1.loadModelModifiersData)(context.env.locale ?? 'en');
184
203
  // https://vuejs.org/api/built-in-directives.html#v-on
204
+ const vOnModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-on'));
185
205
  // https://vuejs.org/api/built-in-directives.html#v-bind
186
- const vOnModifiers = {};
187
- const vBindModifiers = {};
188
- const vModelModifiers = {};
189
- const vOn = builtInData.globalAttributes?.find(x => x.name === 'v-on');
190
- const vBind = builtInData.globalAttributes?.find(x => x.name === 'v-bind');
191
- const vModel = builtInData.globalAttributes?.find(x => x.name === 'v-model');
192
- if (vOn) {
193
- const markdown = typeof vOn.description === 'object'
194
- ? vOn.description.value
195
- : vOn.description ?? '';
196
- const modifiers = markdown
197
- .split('\n- ')[4]
198
- .split('\n').slice(2, -1);
199
- for (let text of modifiers) {
200
- text = text.slice(' - `.'.length);
201
- const [name, desc] = text.split('` - ');
202
- vOnModifiers[name] = desc;
203
- }
204
- }
205
- if (vBind) {
206
- const markdown = typeof vBind.description === 'object'
207
- ? vBind.description.value
208
- : vBind.description ?? '';
209
- const modifiers = markdown
210
- .split('\n- ')[4]
211
- .split('\n').slice(2, -1);
212
- for (let text of modifiers) {
213
- text = text.slice(' - `.'.length);
214
- const [name, desc] = text.split('` - ');
215
- vBindModifiers[name] = desc;
216
- }
217
- }
218
- if (vModel) {
219
- for (const modifier of modelData.globalAttributes ?? []) {
220
- const description = typeof modifier.description === 'object'
221
- ? modifier.description.value
222
- : modifier.description ?? '';
223
- const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | ');
224
- vModelModifiers[modifier.name] = description + '\n\n' + references;
225
- }
226
- }
227
- const disposable = context.env.onDidChangeConfiguration?.(() => initializing = undefined);
206
+ const vBindModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-bind'));
207
+ const vModelModifiers = extractModelModifiers(modelData.globalAttributes);
228
208
  const transformedItems = new WeakSet();
229
- let initializing;
209
+ const defaultHtmlTags = new Map();
210
+ for (const tag of html.getDefaultHTMLDataProvider().provideTags()) {
211
+ defaultHtmlTags.set(tag.name, tag);
212
+ }
230
213
  let lastCompletionDocument;
231
214
  return {
232
215
  ...baseServiceInstance,
233
216
  dispose() {
234
217
  baseServiceInstance.dispose?.();
235
- disposable?.dispose();
236
218
  },
237
219
  async provideCompletionItems(document, position, completionContext, token) {
238
220
  if (document.languageId !== languageId) {
@@ -242,17 +224,35 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
242
224
  if (info?.code.id !== 'template') {
243
225
  return;
244
226
  }
245
- const { result: htmlCompletion, target, info: { tagNameCasing, components, propMap, }, } = await runWithVueData(info.script.id, info.root, () => baseServiceInstance.provideCompletionItems(document, position, completionContext, token));
227
+ const prevText = document.getText({ start: { line: 0, character: 0 }, end: position });
228
+ const hint = prevText.match(/\bv[\S]*$/)
229
+ ? 'v'
230
+ : prevText.match(/[:][\S]*$/)
231
+ ? ':'
232
+ : prevText.match(/[@][\S]*$/)
233
+ ? '@'
234
+ : undefined;
235
+ const { result: htmlCompletion, info: { tagNameCasing, components, }, } = await runWithVueDataProvider(info.script.id, info.root, hint, 'completion', () => baseServiceInstance.provideCompletionItems(document, position, completionContext, token));
236
+ const componentSet = new Set(components);
246
237
  if (!htmlCompletion) {
247
238
  return;
248
239
  }
249
- const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === 'AutoImportsPlaceholder');
250
- if (autoImportPlaceholderIndex !== -1) {
240
+ if (!prevText.match(/[\S]+$/)) {
241
+ htmlCompletion.isIncomplete = true;
242
+ }
243
+ await resolveAutoImportPlaceholder(htmlCompletion, info);
244
+ resolveComponentItemKinds(htmlCompletion);
245
+ return htmlCompletion;
246
+ async function resolveAutoImportPlaceholder(htmlCompletion, info) {
247
+ const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === AUTO_IMPORT_PLACEHOLDER);
248
+ if (autoImportPlaceholderIndex === -1) {
249
+ return;
250
+ }
251
251
  const offset = document.offsetAt(position);
252
252
  const map = context.language.maps.get(info.code, info.script);
253
253
  let spliced = false;
254
254
  for (const [sourceOffset] of map.toSourceLocation(offset)) {
255
- const autoImport = await getAutoImportSuggestions(info.root.fileName, sourceOffset);
255
+ const autoImport = await tsserver.getAutoImportSuggestions(info.root.fileName, sourceOffset);
256
256
  if (!autoImport) {
257
257
  continue;
258
258
  }
@@ -281,118 +281,42 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
281
281
  htmlCompletion.items.splice(autoImportPlaceholderIndex, 1);
282
282
  }
283
283
  }
284
- switch (target) {
285
- case 'tag': {
286
- htmlCompletion.items.forEach(transformTag);
287
- break;
288
- }
289
- case 'attribute': {
290
- addDirectiveModifiers(htmlCompletion, document);
291
- htmlCompletion.items.forEach(transformAttribute);
292
- break;
293
- }
294
- }
295
- updateExtraCustomData([]);
296
- return htmlCompletion;
297
- function transformTag(item) {
298
- const tagName = (0, shared_1.capitalize)((0, shared_1.camelize)(item.label));
299
- if (components?.includes(tagName)) {
300
- item.kind = 6;
301
- item.sortText = '\u0000' + (item.sortText ?? item.label);
302
- }
303
- }
304
- function transformAttribute(item) {
305
- let prop = propMap.get(item.label);
306
- if (prop) {
307
- if (prop.info?.documentation) {
308
- item.documentation = {
309
- kind: 'markdown',
310
- value: prop.info.documentation,
311
- };
312
- }
313
- if (prop.info?.deprecated) {
314
- item.tags = [1];
315
- }
316
- }
317
- else {
318
- let name = item.label;
319
- for (const str of ['v-bind:', ':']) {
320
- if (name.startsWith(str) && name !== str) {
321
- name = name.slice(str.length);
284
+ function resolveComponentItemKinds(htmlCompletion) {
285
+ for (const item of htmlCompletion.items) {
286
+ switch (item.kind) {
287
+ case 10:
288
+ if (componentSet.has(item.label)
289
+ || componentSet.has((0, shared_1.capitalize)((0, shared_1.camelize)(item.label)))) {
290
+ item.kind = 6;
291
+ }
292
+ break;
293
+ case 12:
294
+ addDirectiveModifiers(htmlCompletion, item, document);
295
+ if (typeof item.documentation === 'object' && item.documentation.value.includes('*@deprecated*')) {
296
+ item.tags = [1];
297
+ }
298
+ if (item.label.startsWith(DIRECTIVE_V_ON) || item.label.startsWith(V_ON_SHORTHAND)) {
299
+ item.kind = 23;
300
+ }
301
+ else if (item.label.startsWith(DIRECTIVE_V_BIND)
302
+ || item.label.startsWith(V_BIND_SHORTHAND)
303
+ || item.label.startsWith(DIRECTIVE_V_MODEL)) {
304
+ item.kind = 5;
305
+ }
306
+ else if (item.label.startsWith('v-')) {
307
+ item.kind = 14;
308
+ }
309
+ if (item.label === DIRECTIVE_V_FOR_NAME) {
310
+ item.textEdit.newText = item.label + V_FOR_SNIPPET;
311
+ }
322
312
  break;
323
- }
324
- }
325
- if (specialProps.has(name)) {
326
- prop = {
327
- name,
328
- kind: 'prop',
329
- };
330
- }
331
- }
332
- const tokens = [];
333
- if (prop) {
334
- const { isEvent, propName } = getPropName(prop.name, prop.kind === 'event');
335
- if (prop.kind === 'prop') {
336
- if (!prop.isGlobal) {
337
- item.kind = 5;
338
- }
339
- }
340
- else if (isEvent) {
341
- item.kind = 23;
342
- if (propName.startsWith('vue:')) {
343
- tokens.push('\u0004');
344
- }
345
- }
346
- if (!prop.isGlobal) {
347
- tokens.push('\u0000');
348
- if (item.label.startsWith(':')) {
349
- tokens.push('\u0001');
350
- }
351
- else if (item.label.startsWith('@')) {
352
- tokens.push('\u0002');
353
- }
354
- else if (item.label.startsWith('v-bind:')) {
355
- tokens.push('\u0003');
356
- }
357
- else if (item.label.startsWith('v-model:')) {
358
- tokens.push('\u0004');
359
- }
360
- else if (item.label.startsWith('v-on:')) {
361
- tokens.push('\u0005');
362
- }
363
- else {
364
- tokens.push('\u0000');
365
- }
366
- if (specialProps.has(propName)) {
367
- tokens.push('\u0001');
368
- }
369
- else {
370
- tokens.push('\u0000');
371
- }
372
313
  }
373
314
  }
374
- else if (item.label === 'v-if'
375
- || item.label === 'v-else-if'
376
- || item.label === 'v-else'
377
- || item.label === 'v-for') {
378
- item.kind = 14;
379
- tokens.push('\u0003');
380
- }
381
- else if (item.label.startsWith('v-')) {
382
- item.kind = 3;
383
- tokens.push('\u0002');
384
- }
385
- else {
386
- tokens.push('\u0001');
387
- }
388
- item.sortText = tokens.join('') + (item.sortText ?? item.label);
389
- if (item.label === 'v-for') {
390
- item.textEdit.newText = item.label + '="${1:value} in ${2:source}"';
391
- }
392
315
  }
393
316
  },
394
317
  async resolveCompletionItem(item) {
395
- if (item.data?.__isAutoImport || item.data?.__isComponentAutoImport) {
318
+ const data = item.data;
319
+ if (data?.__vue__autoImport || data?.__vue__componentAutoImport) {
396
320
  const embeddedUri = vscode_uri_1.URI.parse(lastCompletionDocument.uri);
397
321
  const decoded = context.decodeEmbeddedDocumentUri(embeddedUri);
398
322
  if (!decoded) {
@@ -402,7 +326,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
402
326
  if (!sourceScript) {
403
327
  return item;
404
328
  }
405
- const details = await resolveAutoImportCompletionEntry(item.data);
329
+ const details = await tsserver.resolveAutoImportCompletionEntry(data);
406
330
  if (details) {
407
331
  const virtualCode = sourceScript.generated.embeddedCodes.get(decoded[1]);
408
332
  const sourceDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot);
@@ -420,7 +344,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
420
344
  return item;
421
345
  }
422
346
  },
423
- provideHover(document, position, token) {
347
+ async provideHover(document, position, token) {
424
348
  if (document.languageId !== languageId) {
425
349
  return;
426
350
  }
@@ -428,12 +352,129 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
428
352
  if (info?.code.id !== 'template') {
429
353
  return;
430
354
  }
431
- if (context.decodeEmbeddedDocumentUri(vscode_uri_1.URI.parse(document.uri))) {
432
- updateExtraCustomData([
433
- htmlDataProvider,
434
- ]);
355
+ let { result: htmlHover, } = await runWithVueDataProvider(info.script.id, info.root, undefined, 'hover', () => baseServiceInstance.provideHover(document, position, token));
356
+ const templateAst = info.root.sfc.template?.ast;
357
+ const enabledRichMessage = await context.env.getConfiguration?.('vue.hover.rich');
358
+ if (!templateAst || !enabledRichMessage || (htmlHover && hasContents(htmlHover.contents))) {
359
+ return htmlHover;
360
+ }
361
+ for (const element of (0, language_core_1.forEachElementNode)(templateAst)) {
362
+ const tagStart = element.loc.start.offset + element.loc.source.indexOf(element.tag);
363
+ const tagEnd = tagStart + element.tag.length;
364
+ const offset = document.offsetAt(position);
365
+ if (offset >= tagStart && offset <= tagEnd) {
366
+ const meta = await tsserver.getComponentMeta(info.root.fileName, element.tag);
367
+ const props = meta?.props.filter(p => !p.global);
368
+ const modelProps = new Set();
369
+ let tableContents = [];
370
+ for (const event of meta?.events ?? []) {
371
+ if (event.name.startsWith(UPDATE_EVENT_PREFIX)) {
372
+ const modelName = event.name.slice(UPDATE_EVENT_PREFIX.length);
373
+ const modelProp = props?.find(p => p.name === modelName);
374
+ if (modelProp) {
375
+ modelProps.add(modelProp);
376
+ }
377
+ }
378
+ }
379
+ for (const prop of props ?? []) {
380
+ if (prop.name.startsWith(UPDATE_PROP_PREFIX)) {
381
+ const modelName = prop.name.slice(UPDATE_PROP_PREFIX.length);
382
+ const modelProp = props?.find(p => p.name === modelName);
383
+ if (modelProp) {
384
+ modelProps.add(modelProp);
385
+ }
386
+ }
387
+ }
388
+ if (props?.length) {
389
+ let table = `<tr><th align="left">Prop</th><th align="left">Description</th><th align="left">Default</th></tr>\n`;
390
+ for (const p of props) {
391
+ table += `<tr>
392
+ <td>${printName(p, modelProps.has(p))}</td>
393
+ <td>${printDescription(p)}</td>
394
+ <td>${p.default ? `<code>${p.default}</code>` : ''}</td>
395
+ </tr>\n`;
396
+ }
397
+ tableContents.push(table);
398
+ }
399
+ if (meta?.events?.length) {
400
+ let table = `<tr><th align="left">Event</th><th align="left">Description</th><th></th></tr>\n`;
401
+ for (const e of meta.events) {
402
+ table += `<tr>
403
+ <td>${printName(e)}</td>
404
+ <td colspan="2">${printDescription(e)}</td>
405
+ </tr>\n`;
406
+ }
407
+ tableContents.push(table);
408
+ }
409
+ if (meta?.slots?.length) {
410
+ let table = `<tr><th align="left">Slot</th><th align="left">Description</th><th></th></tr>\n`;
411
+ for (const s of meta.slots) {
412
+ table += `<tr>
413
+ <td>${printName(s)}</td>
414
+ <td colspan="2">${printDescription(s)}</td>
415
+ </tr>\n`;
416
+ }
417
+ tableContents.push(table);
418
+ }
419
+ if (meta?.exposed.length) {
420
+ let table = `<tr><th align="left">Exposed</th><th align="left">Description</th><th></th></tr>\n`;
421
+ for (const e of meta.exposed) {
422
+ table += `<tr>
423
+ <td>${printName(e)}</td>
424
+ <td colspan="2">${printDescription(e)}</td>
425
+ </tr>\n`;
426
+ }
427
+ tableContents.push(table);
428
+ }
429
+ htmlHover ??= {
430
+ range: {
431
+ start: document.positionAt(tagStart),
432
+ end: document.positionAt(tagEnd),
433
+ },
434
+ contents: '',
435
+ };
436
+ // 2px height per <tr>
437
+ const tableGap = `<tr></tr>`.repeat(4);
438
+ htmlHover.contents = {
439
+ kind: 'markdown',
440
+ value: tableContents
441
+ ? `<table>\n${tableContents.join(`\n${tableGap}\n`)}\n</table>`
442
+ : `No type information available.`,
443
+ };
444
+ }
445
+ }
446
+ return htmlHover;
447
+ function printName(meta, model) {
448
+ let name = meta.name;
449
+ if (meta.tags.some(tag => tag.name === 'deprecated')) {
450
+ name = `<del>${name}</del>`;
451
+ }
452
+ if (meta.required) {
453
+ name += ' <sup><em>required</em></sup>';
454
+ }
455
+ if (model) {
456
+ name += ' <sup><em>model</em></sup>';
457
+ }
458
+ return name;
459
+ }
460
+ function printDescription(meta) {
461
+ let desc = `<code>${meta.type}</code>`;
462
+ if (meta.description) {
463
+ // blank line for terminate HTML to support markdown
464
+ // see: https://github.github.com/gfm/#example-118
465
+ desc = `\n\n${meta.description}<br>${desc}`;
466
+ }
467
+ return desc;
468
+ }
469
+ function hasContents(contents) {
470
+ if (typeof contents === 'string') {
471
+ return !!contents;
472
+ }
473
+ if (Array.isArray(contents)) {
474
+ return contents.some(hasContents);
475
+ }
476
+ return !!contents.value;
435
477
  }
436
- return baseServiceInstance.provideHover?.(document, position, token);
437
478
  },
438
479
  async provideDocumentLinks(document, token) {
439
480
  modulePathCache = new Map();
@@ -454,10 +495,10 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
454
495
  }
455
496
  },
456
497
  };
457
- async function runWithVueData(sourceDocumentUri, root, fn) {
498
+ async function runWithVueDataProvider(sourceDocumentUri, root, hint, mode, fn) {
458
499
  // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver
459
500
  await fn();
460
- const { sync } = await provideHtmlData(sourceDocumentUri, root);
501
+ const { sync } = await provideHtmlData(sourceDocumentUri, root, hint, mode);
461
502
  let lastSync = await sync();
462
503
  let result = await fn();
463
504
  while (lastSync.version !== (lastSync = await sync()).version) {
@@ -465,255 +506,195 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
465
506
  }
466
507
  return { result, ...lastSync };
467
508
  }
468
- async function provideHtmlData(sourceDocumentUri, root) {
469
- await (initializing ??= initialize());
470
- const tagNameCasing = await (0, nameCasing_1.getTagNameCasing)(context, sourceDocumentUri);
471
- const attrNameCasing = await (0, nameCasing_1.getAttrNameCasing)(context, sourceDocumentUri);
472
- for (const tag of builtInData.tags ?? []) {
473
- if (specialTags.has(tag.name)) {
474
- continue;
475
- }
476
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
477
- tag.name = (0, language_core_1.hyphenateTag)(tag.name);
478
- }
479
- else {
480
- tag.name = (0, shared_1.camelize)((0, shared_1.capitalize)(tag.name));
481
- }
482
- }
509
+ async function provideHtmlData(sourceDocumentUri, root, hint, mode) {
510
+ const [tagNameCasing, attrNameCasing] = await Promise.all([
511
+ (0, nameCasing_1.getTagNameCasing)(context, sourceDocumentUri),
512
+ (0, nameCasing_1.getAttrNameCasing)(context, sourceDocumentUri),
513
+ ]);
483
514
  let version = 0;
484
- let target;
485
515
  let components;
516
+ let elements;
517
+ let directives;
486
518
  let values;
487
519
  const tasks = [];
488
- const tagMap = new Map();
489
- const propMap = new Map();
520
+ const tagDataMap = new Map();
490
521
  updateExtraCustomData([
491
- {
492
- getId: () => htmlDataProvider.getId(),
493
- isApplicable: () => true,
494
- provideTags() {
495
- target = 'tag';
496
- return htmlDataProvider.provideTags()
497
- .filter(tag => !specialTags.has(tag.name));
498
- },
499
- provideAttributes(tag) {
500
- target = 'attribute';
501
- const attrs = htmlDataProvider.provideAttributes(tag);
502
- if (tag === 'slot') {
503
- const nameAttr = attrs.find(attr => attr.name === 'name');
504
- if (nameAttr) {
505
- nameAttr.valueSet = 'slot';
506
- }
507
- }
508
- return attrs;
509
- },
510
- provideValues(tag, attr) {
511
- target = 'value';
512
- return htmlDataProvider.provideValues(tag, attr);
513
- },
514
- },
515
- html.newHTMLDataProvider('vue-template-built-in', builtInData),
516
522
  {
517
523
  getId: () => 'vue-template',
518
524
  isApplicable: () => true,
519
525
  provideTags: () => {
520
- if (!components) {
521
- components = [];
522
- tasks.push((async () => {
523
- components = (await getComponentNames(root.fileName) ?? [])
524
- .filter(name => !builtInComponents.has(name));
525
- version++;
526
- })());
527
- }
526
+ const { components, elements } = getComponentsAndElements();
528
527
  const codegen = language_core_1.tsCodegen.get(root.sfc);
529
528
  const names = new Set();
530
529
  const tags = [];
530
+ for (const tag of builtInData?.tags ?? []) {
531
+ tags.push({
532
+ ...tag,
533
+ name: tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag.name) : tag.name,
534
+ });
535
+ }
531
536
  for (const tag of components) {
532
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
533
- names.add((0, language_core_1.hyphenateTag)(tag));
534
- }
535
- else {
536
- names.add(tag);
537
- }
537
+ names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag) : tag);
538
+ }
539
+ for (const tag of elements) {
540
+ names.add(tag);
538
541
  }
539
542
  if (codegen) {
540
543
  for (const name of [
541
- ...codegen.getImportComponentNames(),
544
+ ...codegen.getImportedComponents(),
542
545
  ...codegen.getSetupExposed(),
543
546
  ]) {
544
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
545
- names.add((0, language_core_1.hyphenateTag)(name));
546
- }
547
- else {
548
- names.add(name);
549
- }
547
+ names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(name) : name);
550
548
  }
551
549
  }
550
+ const added = new Set(tags.map(t => t.name));
552
551
  for (const name of names) {
553
- tags.push({
554
- name: name,
555
- attributes: [],
556
- });
552
+ if (!added.has(name)) {
553
+ const defaultTag = defaultHtmlTags.get(name);
554
+ tags.push({
555
+ ...defaultTag,
556
+ name,
557
+ attributes: [],
558
+ });
559
+ }
557
560
  }
558
561
  return tags;
559
562
  },
560
563
  provideAttributes: tag => {
561
- let tagInfo = tagMap.get(tag);
562
- if (!tagInfo) {
563
- tagInfo = {
564
- attrs: [],
565
- propInfos: [],
566
- events: [],
567
- directives: [],
568
- };
569
- tagMap.set(tag, tagInfo);
570
- tasks.push((async () => {
571
- tagMap.set(tag, {
572
- attrs: await getElementAttrs(root.fileName, tag) ?? [],
573
- propInfos: await getComponentProps(root.fileName, tag) ?? [],
574
- events: await getComponentEvents(root.fileName, tag) ?? [],
575
- directives: await getComponentDirectives(root.fileName) ?? [],
576
- });
577
- version++;
578
- })());
579
- }
580
- const { attrs, propInfos, events, directives } = tagInfo;
581
- for (let i = 0; i < propInfos.length; i++) {
582
- const prop = propInfos[i];
583
- if (prop.name.startsWith('ref_')) {
584
- propInfos.splice(i--, 1);
564
+ const directives = getDirectives();
565
+ const { attrs, meta } = getTagData(tag);
566
+ const attributes = [];
567
+ for (const attr of builtInData?.globalAttributes ?? []) {
568
+ if (attr.name === 'is' && tag.toLowerCase() !== 'component') {
585
569
  continue;
586
570
  }
587
- if ((0, language_core_1.hyphenateTag)(prop.name).startsWith('on-vnode-')) {
588
- prop.name = 'onVue:' + prop.name['onVnode'.length].toLowerCase()
589
- + prop.name.slice('onVnodeX'.length);
571
+ if (attr.name === 'ref' || attr.name.startsWith('v-')) {
572
+ attributes.push(attr);
573
+ continue;
574
+ }
575
+ if (!hint || hint === ':') {
576
+ attributes.push({
577
+ ...attr,
578
+ name: V_BIND_SHORTHAND + attr.name,
579
+ });
580
+ }
581
+ if (!hint || hint === 'v') {
582
+ attributes.push({
583
+ ...attr,
584
+ name: DIRECTIVE_V_BIND + attr.name,
585
+ });
586
+ attributes.push({
587
+ ...attr,
588
+ name: attr.name,
589
+ });
590
590
  }
591
591
  }
592
- const attributes = [];
593
- const propNameSet = new Set(propInfos.map(prop => prop.name));
594
- for (const prop of [
595
- ...propInfos,
596
- ...attrs.map(attr => ({ name: attr })),
592
+ for (const [propName, propMeta] of [
593
+ ...meta?.props.map(prop => [prop.name, prop]) ?? [],
594
+ ...attrs.map(attr => [attr.name, undefined]),
597
595
  ]) {
598
- const isGlobal = prop.isAttribute || !propNameSet.has(prop.name);
599
- const propName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? prop.name : (0, language_core_1.hyphenateAttr)(prop.name);
600
- const isEvent = (0, language_core_1.hyphenateAttr)(propName).startsWith('on-');
601
- if (isEvent) {
602
- const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */
603
- ? propName['on'.length].toLowerCase() + propName.slice('onX'.length)
604
- : propName.slice('on-'.length);
605
- for (const name of [
606
- 'v-on:' + eventName,
607
- '@' + eventName,
608
- ]) {
609
- attributes.push({ name });
610
- propMap.set(name, {
611
- name: propName,
612
- kind: 'event',
613
- isGlobal,
614
- info: prop,
596
+ if (propName.match(EVENT_PROP_REGEX)) {
597
+ let labelName = propName.slice(2);
598
+ labelName = labelName.charAt(0).toLowerCase() + labelName.slice(1);
599
+ if (attrNameCasing === 0 /* AttrNameCasing.Kebab */) {
600
+ labelName = (0, language_core_1.hyphenateAttr)(labelName);
601
+ }
602
+ if (!hint || hint === '@') {
603
+ attributes.push({
604
+ name: V_ON_SHORTHAND + labelName,
605
+ description: propMeta && createDescription(propMeta),
606
+ });
607
+ }
608
+ if (!hint || hint === 'v') {
609
+ attributes.push({
610
+ name: DIRECTIVE_V_ON + labelName,
611
+ description: propMeta && createDescription(propMeta),
615
612
  });
616
613
  }
617
614
  }
618
615
  else {
619
- const propInfo = propInfos.find(prop => {
616
+ const labelName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? propName : (0, language_core_1.hyphenateAttr)(propName);
617
+ const propMeta2 = meta?.props.find(prop => {
620
618
  const name = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? prop.name : (0, language_core_1.hyphenateAttr)(prop.name);
621
- return name === propName;
619
+ return name === labelName;
622
620
  });
623
- for (const name of [
624
- propName,
625
- ':' + propName,
626
- 'v-bind:' + propName,
627
- ]) {
621
+ if (!hint || hint === ':') {
622
+ attributes.push({
623
+ name: V_BIND_SHORTHAND + labelName,
624
+ description: propMeta2 && createDescription(propMeta2),
625
+ });
626
+ }
627
+ if (!hint || hint === 'v') {
628
628
  attributes.push({
629
- name,
630
- valueSet: prop.values?.some(value => typeof value === 'string') ? '__deferred__' : undefined,
629
+ name: DIRECTIVE_V_BIND + labelName,
630
+ description: propMeta2 && createDescription(propMeta2),
631
631
  });
632
- propMap.set(name, {
633
- name: propName,
634
- kind: 'prop',
635
- isGlobal,
636
- info: propInfo,
632
+ attributes.push({
633
+ name: labelName,
634
+ description: propMeta2 && createDescription(propMeta2),
637
635
  });
638
636
  }
639
637
  }
640
638
  }
641
- for (const event of events) {
642
- const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? event : (0, language_core_1.hyphenateAttr)(event);
643
- for (const name of [
644
- 'v-on:' + eventName,
645
- '@' + eventName,
646
- ]) {
647
- attributes.push({ name });
648
- propMap.set(name, {
649
- name: eventName,
650
- kind: 'event',
639
+ for (const event of meta?.events ?? []) {
640
+ const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? event.name : (0, language_core_1.hyphenateAttr)(event.name);
641
+ if (!hint || hint === '@') {
642
+ attributes.push({
643
+ name: V_ON_SHORTHAND + eventName,
644
+ description: event && createDescription(event),
645
+ });
646
+ }
647
+ if (!hint || hint === 'v') {
648
+ attributes.push({
649
+ name: DIRECTIVE_V_ON + eventName,
650
+ description: event && createDescription(event),
651
651
  });
652
652
  }
653
653
  }
654
654
  for (const directive of directives) {
655
- const name = (0, language_core_1.hyphenateAttr)(directive);
656
655
  attributes.push({
657
- name,
656
+ name: (0, language_core_1.hyphenateAttr)(directive),
658
657
  });
659
658
  }
660
- const models = [];
661
- for (const prop of [
662
- ...propInfos,
663
- ...attrs.map(attr => ({ name: attr })),
659
+ for (const [propName, propMeta] of [
660
+ ...meta?.props.map(prop => [prop.name, prop]) ?? [],
661
+ ...attrs.map(attr => [attr.name, undefined]),
664
662
  ]) {
665
- if (prop.name.startsWith('onUpdate:')) {
666
- models.push(prop.name.slice('onUpdate:'.length));
667
- }
668
- }
669
- for (const event of events) {
670
- if (event.startsWith('update:')) {
671
- models.push(event.slice('update:'.length));
663
+ if (propName.startsWith(UPDATE_PROP_PREFIX)) {
664
+ const model = propName.slice(UPDATE_PROP_PREFIX.length);
665
+ const label = DIRECTIVE_V_MODEL
666
+ + (attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model));
667
+ attributes.push({
668
+ name: label,
669
+ description: propMeta && createDescription(propMeta),
670
+ });
672
671
  }
673
672
  }
674
- for (const model of models) {
675
- const name = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model);
676
- attributes.push({ name: 'v-model:' + name });
677
- propMap.set('v-model:' + name, {
678
- name,
679
- kind: 'prop',
680
- });
681
- if (model === 'modelValue') {
682
- propMap.set('v-model', {
683
- name,
684
- kind: 'prop',
685
- });
673
+ if (!hint || hint === 'v') {
674
+ for (const event of meta?.events ?? []) {
675
+ if (event.name.startsWith(UPDATE_EVENT_PREFIX)) {
676
+ const model = event.name.slice(UPDATE_EVENT_PREFIX.length);
677
+ const label = DIRECTIVE_V_MODEL
678
+ + (attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model));
679
+ attributes.push({
680
+ name: label,
681
+ description: createDescription(event),
682
+ });
683
+ }
686
684
  }
687
685
  }
688
686
  return attributes;
689
687
  },
690
688
  provideValues: (tag, attr) => {
691
- if (!values) {
692
- values = [];
693
- tasks.push((async () => {
694
- if (tag === 'slot' && attr === 'name') {
695
- values = await getComponentSlots(root.fileName) ?? [];
696
- }
697
- version++;
698
- })());
699
- }
700
- return values.map(value => ({
701
- name: value,
702
- }));
689
+ return getAttrValues(tag, attr).map(value => ({ name: value }));
703
690
  },
704
691
  },
705
692
  {
706
693
  getId: () => 'vue-auto-imports',
707
694
  isApplicable: () => true,
708
- provideTags() {
709
- return [{ name: 'AutoImportsPlaceholder', attributes: [] }];
710
- },
711
- provideAttributes() {
712
- return [];
713
- },
714
- provideValues() {
715
- return [];
716
- },
695
+ provideTags: () => [{ name: AUTO_IMPORT_PLACEHOLDER, attributes: [] }],
696
+ provideAttributes: () => [],
697
+ provideValues: () => [],
717
698
  },
718
699
  ]);
719
700
  return {
@@ -721,25 +702,96 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
721
702
  await Promise.all(tasks);
722
703
  return {
723
704
  version,
724
- target,
725
705
  info: {
726
706
  tagNameCasing,
727
707
  components,
728
- propMap,
729
708
  },
730
709
  };
731
710
  },
732
711
  };
712
+ function createDescription(meta) {
713
+ if (mode === 'hover') {
714
+ // dedupe from TS hover
715
+ return;
716
+ }
717
+ let description = meta?.description ?? '';
718
+ for (const tag of meta.tags) {
719
+ description += `\n\n*@${tag.name}* ${tag.text ?? ''}`;
720
+ }
721
+ if (!description) {
722
+ return;
723
+ }
724
+ return {
725
+ kind: 'markdown',
726
+ value: description,
727
+ };
728
+ }
729
+ function getAttrValues(tag, attr) {
730
+ if (!values) {
731
+ values = [];
732
+ tasks.push((async () => {
733
+ if (tag === 'slot' && attr === 'name') {
734
+ values = await tsserver.getComponentSlots(root.fileName) ?? [];
735
+ }
736
+ version++;
737
+ })());
738
+ }
739
+ return values;
740
+ }
741
+ function getTagData(tag) {
742
+ let data = tagDataMap.get(tag);
743
+ if (!data) {
744
+ data = { attrs: [], meta: undefined };
745
+ tagDataMap.set(tag, data);
746
+ tasks.push((async () => {
747
+ tagDataMap.set(tag, {
748
+ attrs: await tsserver.getElementAttrs(root.fileName, tag) ?? [],
749
+ meta: await tsserver.getComponentMeta(root.fileName, tag),
750
+ });
751
+ version++;
752
+ })());
753
+ }
754
+ return data;
755
+ }
756
+ function getDirectives() {
757
+ if (!directives) {
758
+ directives = [];
759
+ tasks.push((async () => {
760
+ directives = await tsserver.getComponentDirectives(root.fileName) ?? [];
761
+ version++;
762
+ })());
763
+ }
764
+ return directives;
765
+ }
766
+ function getComponentsAndElements() {
767
+ if (!components || !elements) {
768
+ components = [];
769
+ elements = [];
770
+ tasks.push((async () => {
771
+ const res = await Promise.all([
772
+ tsserver.getComponentNames(root.fileName),
773
+ tsserver.getElementNames(root.fileName),
774
+ ]);
775
+ components = res[0] ?? [];
776
+ elements = res[1] ?? [];
777
+ version++;
778
+ })());
779
+ }
780
+ return {
781
+ components,
782
+ elements,
783
+ };
784
+ }
733
785
  }
734
- function addDirectiveModifiers(completionList, document) {
735
- const replacement = getReplacement(completionList, document);
786
+ function addDirectiveModifiers(list, item, document) {
787
+ const replacement = getReplacement(item, document);
736
788
  if (!replacement?.text.includes('.')) {
737
789
  return;
738
790
  }
739
791
  const [text, ...modifiers] = replacement.text.split('.');
740
- const isVOn = text.startsWith('v-on:') || text.startsWith('@') && text.length > 1;
741
- const isVBind = text.startsWith('v-bind:') || text.startsWith(':') && text.length > 1;
742
- const isVModel = text.startsWith('v-model:') || text === 'v-model';
792
+ const isVOn = text.startsWith(DIRECTIVE_V_ON) || text.startsWith(V_ON_SHORTHAND) && text.length > 1;
793
+ const isVBind = text.startsWith(DIRECTIVE_V_BIND) || text.startsWith(V_BIND_SHORTHAND) && text.length > 1;
794
+ const isVModel = text.startsWith(DIRECTIVE_V_MODEL) || text === 'v-model';
743
795
  const currentModifiers = isVOn
744
796
  ? vOnModifiers
745
797
  : isVBind
@@ -769,55 +821,55 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
769
821
  },
770
822
  kind: 20,
771
823
  };
772
- completionList.items.push(newItem);
773
- }
774
- }
775
- async function initialize() {
776
- customData = await getHtmlCustomData();
777
- }
778
- async function getHtmlCustomData() {
779
- const customData = await context.env.getConfiguration?.('html.customData') ?? [];
780
- const newData = [];
781
- for (const customDataPath of customData) {
782
- for (const workspaceFolder of context.env.workspaceFolders) {
783
- const uri = vscode_uri_1.Utils.resolvePath(workspaceFolder, customDataPath);
784
- const json = await context.env.fs?.readFile(uri);
785
- if (json) {
786
- try {
787
- const data = JSON.parse(json);
788
- newData.push(html.newHTMLDataProvider(customDataPath, data));
789
- }
790
- catch (error) {
791
- console.error(error);
792
- }
793
- }
794
- }
824
+ list.items.push(newItem);
795
825
  }
796
- return newData;
797
826
  }
798
827
  },
799
828
  };
800
- function updateExtraCustomData(extraData) {
801
- extraCustomData = extraData;
829
+ function updateExtraCustomData(newData) {
830
+ htmlData = newData;
802
831
  onDidChangeCustomDataListeners.forEach(l => l());
803
832
  }
804
833
  }
805
- function getReplacement(list, doc) {
806
- for (const item of list.items) {
807
- if (item.textEdit && 'range' in item.textEdit) {
808
- return {
809
- item: item,
810
- textEdit: item.textEdit,
811
- text: doc.getText(item.textEdit.range),
812
- };
813
- }
834
+ function getReplacement(item, doc) {
835
+ if (item.textEdit && 'range' in item.textEdit) {
836
+ return {
837
+ item: item,
838
+ textEdit: item.textEdit,
839
+ text: doc.getText(item.textEdit.range),
840
+ };
814
841
  }
815
842
  }
816
- function getPropName(prop, isEvent) {
817
- const name = (0, language_core_1.hyphenateAttr)(prop);
818
- if (name.startsWith('on-')) {
819
- return { isEvent: true, propName: name.slice('on-'.length) };
843
+ function extractDirectiveModifiers(directive) {
844
+ const modifiers = {};
845
+ if (!directive) {
846
+ return modifiers;
847
+ }
848
+ const markdown = typeof directive.description === 'object'
849
+ ? directive.description.value
850
+ : directive.description ?? '';
851
+ const modifierLines = markdown
852
+ .split('\n- ')[4]
853
+ ?.split('\n').slice(2, -1) ?? [];
854
+ for (let text of modifierLines) {
855
+ text = text.slice(' - `.'.length);
856
+ const [name, desc] = text.split('` - ');
857
+ modifiers[name] = desc;
858
+ }
859
+ return modifiers;
860
+ }
861
+ function extractModelModifiers(attributes) {
862
+ const modifiers = {};
863
+ if (!attributes) {
864
+ return modifiers;
865
+ }
866
+ for (const modifier of attributes) {
867
+ const description = typeof modifier.description === 'object'
868
+ ? modifier.description.value
869
+ : modifier.description ?? '';
870
+ const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | ');
871
+ modifiers[modifier.name] = description + '\n\n' + references;
820
872
  }
821
- return { isEvent, propName: name };
873
+ return modifiers;
822
874
  }
823
875
  //# sourceMappingURL=vue-template.js.map