@vue/language-service 3.1.8 → 3.2.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.
@@ -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,29 @@ 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(/(\S)[\S]*$/)?.[1];
229
+ const { result: htmlCompletion, info: { tagNameCasing, components, }, } = await runWithVueDataProvider(info.script.id, info.root, hint, 'completion', () => baseServiceInstance.provideCompletionItems(document, position, completionContext, token));
230
+ const componentSet = new Set(components);
246
231
  if (!htmlCompletion) {
247
232
  return;
248
233
  }
249
- const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === 'AutoImportsPlaceholder');
250
- if (autoImportPlaceholderIndex !== -1) {
234
+ if (!hint) {
235
+ htmlCompletion.isIncomplete = true;
236
+ }
237
+ await resolveAutoImportPlaceholder(htmlCompletion, info);
238
+ resolveComponentItemKinds(htmlCompletion);
239
+ return htmlCompletion;
240
+ async function resolveAutoImportPlaceholder(htmlCompletion, info) {
241
+ const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === AUTO_IMPORT_PLACEHOLDER);
242
+ if (autoImportPlaceholderIndex === -1) {
243
+ return;
244
+ }
251
245
  const offset = document.offsetAt(position);
252
246
  const map = context.language.maps.get(info.code, info.script);
253
247
  let spliced = false;
254
248
  for (const [sourceOffset] of map.toSourceLocation(offset)) {
255
- const autoImport = await getAutoImportSuggestions(info.root.fileName, sourceOffset);
249
+ const autoImport = await tsserver.getAutoImportSuggestions(info.root.fileName, sourceOffset);
256
250
  if (!autoImport) {
257
251
  continue;
258
252
  }
@@ -281,118 +275,42 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
281
275
  htmlCompletion.items.splice(autoImportPlaceholderIndex, 1);
282
276
  }
283
277
  }
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);
278
+ function resolveComponentItemKinds(htmlCompletion) {
279
+ for (const item of htmlCompletion.items) {
280
+ switch (item.kind) {
281
+ case 10:
282
+ if (componentSet.has(item.label)
283
+ || componentSet.has((0, shared_1.capitalize)((0, shared_1.camelize)(item.label)))) {
284
+ item.kind = 6;
285
+ }
286
+ break;
287
+ case 12:
288
+ addDirectiveModifiers(htmlCompletion, item, document);
289
+ if (typeof item.documentation === 'object' && item.documentation.value.includes('*@deprecated*')) {
290
+ item.tags = [1];
291
+ }
292
+ if (item.label.startsWith(DIRECTIVE_V_ON) || item.label.startsWith(V_ON_SHORTHAND)) {
293
+ item.kind = 23;
294
+ }
295
+ else if (item.label.startsWith(DIRECTIVE_V_BIND)
296
+ || item.label.startsWith(V_BIND_SHORTHAND)
297
+ || item.label.startsWith(DIRECTIVE_V_MODEL)) {
298
+ item.kind = 5;
299
+ }
300
+ else if (item.label.startsWith('v-')) {
301
+ item.kind = 14;
302
+ }
303
+ if (item.label === DIRECTIVE_V_FOR_NAME) {
304
+ item.textEdit.newText = item.label + V_FOR_SNIPPET;
305
+ }
322
306
  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
307
  }
373
308
  }
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
309
  }
393
310
  },
394
311
  async resolveCompletionItem(item) {
395
- if (item.data?.__isAutoImport || item.data?.__isComponentAutoImport) {
312
+ const data = item.data;
313
+ if (data?.__vue__autoImport || data?.__vue__componentAutoImport) {
396
314
  const embeddedUri = vscode_uri_1.URI.parse(lastCompletionDocument.uri);
397
315
  const decoded = context.decodeEmbeddedDocumentUri(embeddedUri);
398
316
  if (!decoded) {
@@ -402,7 +320,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
402
320
  if (!sourceScript) {
403
321
  return item;
404
322
  }
405
- const details = await resolveAutoImportCompletionEntry(item.data);
323
+ const details = await tsserver.resolveAutoImportCompletionEntry(data);
406
324
  if (details) {
407
325
  const virtualCode = sourceScript.generated.embeddedCodes.get(decoded[1]);
408
326
  const sourceDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot);
@@ -420,7 +338,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
420
338
  return item;
421
339
  }
422
340
  },
423
- provideHover(document, position, token) {
341
+ async provideHover(document, position, token) {
424
342
  if (document.languageId !== languageId) {
425
343
  return;
426
344
  }
@@ -428,12 +346,129 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
428
346
  if (info?.code.id !== 'template') {
429
347
  return;
430
348
  }
431
- if (context.decodeEmbeddedDocumentUri(vscode_uri_1.URI.parse(document.uri))) {
432
- updateExtraCustomData([
433
- htmlDataProvider,
434
- ]);
349
+ let { result: htmlHover, } = await runWithVueDataProvider(info.script.id, info.root, undefined, 'hover', () => baseServiceInstance.provideHover(document, position, token));
350
+ const templateAst = info.root.sfc.template?.ast;
351
+ const enabledRichMessage = await context.env.getConfiguration?.('vue.hover.rich');
352
+ if (!templateAst || !enabledRichMessage || (htmlHover && hasContents(htmlHover.contents))) {
353
+ return htmlHover;
354
+ }
355
+ for (const element of (0, language_core_1.forEachElementNode)(templateAst)) {
356
+ const tagStart = element.loc.start.offset + element.loc.source.indexOf(element.tag);
357
+ const tagEnd = tagStart + element.tag.length;
358
+ const offset = document.offsetAt(position);
359
+ if (offset >= tagStart && offset <= tagEnd) {
360
+ const meta = await tsserver.getComponentMeta(info.root.fileName, element.tag);
361
+ const props = meta?.props.filter(p => !p.global);
362
+ const modelProps = new Set();
363
+ let tableContents = [];
364
+ for (const event of meta?.events ?? []) {
365
+ if (event.name.startsWith(UPDATE_EVENT_PREFIX)) {
366
+ const modelName = event.name.slice(UPDATE_EVENT_PREFIX.length);
367
+ const modelProp = props?.find(p => p.name === modelName);
368
+ if (modelProp) {
369
+ modelProps.add(modelProp);
370
+ }
371
+ }
372
+ }
373
+ for (const prop of props ?? []) {
374
+ if (prop.name.startsWith(UPDATE_PROP_PREFIX)) {
375
+ const modelName = prop.name.slice(UPDATE_PROP_PREFIX.length);
376
+ const modelProp = props?.find(p => p.name === modelName);
377
+ if (modelProp) {
378
+ modelProps.add(modelProp);
379
+ }
380
+ }
381
+ }
382
+ if (props?.length) {
383
+ let table = `<tr><th align="left">Prop</th><th align="left">Description</th><th align="left">Default</th></tr>\n`;
384
+ for (const p of props) {
385
+ table += `<tr>
386
+ <td>${printName(p, modelProps.has(p))}</td>
387
+ <td>${printDescription(p)}</td>
388
+ <td>${p.default ? `<code>${p.default}</code>` : ''}</td>
389
+ </tr>\n`;
390
+ }
391
+ tableContents.push(table);
392
+ }
393
+ if (meta?.events?.length) {
394
+ let table = `<tr><th align="left">Event</th><th align="left">Description</th><th></th></tr>\n`;
395
+ for (const e of meta.events) {
396
+ table += `<tr>
397
+ <td>${printName(e)}</td>
398
+ <td colspan="2">${printDescription(e)}</td>
399
+ </tr>\n`;
400
+ }
401
+ tableContents.push(table);
402
+ }
403
+ if (meta?.slots?.length) {
404
+ let table = `<tr><th align="left">Slot</th><th align="left">Description</th><th></th></tr>\n`;
405
+ for (const s of meta.slots) {
406
+ table += `<tr>
407
+ <td>${printName(s)}</td>
408
+ <td colspan="2">${printDescription(s)}</td>
409
+ </tr>\n`;
410
+ }
411
+ tableContents.push(table);
412
+ }
413
+ if (meta?.exposed.length) {
414
+ let table = `<tr><th align="left">Exposed</th><th align="left">Description</th><th></th></tr>\n`;
415
+ for (const e of meta.exposed) {
416
+ table += `<tr>
417
+ <td>${printName(e)}</td>
418
+ <td colspan="2">${printDescription(e)}</td>
419
+ </tr>\n`;
420
+ }
421
+ tableContents.push(table);
422
+ }
423
+ htmlHover ??= {
424
+ range: {
425
+ start: document.positionAt(tagStart),
426
+ end: document.positionAt(tagEnd),
427
+ },
428
+ contents: '',
429
+ };
430
+ // 2px height per <tr>
431
+ const tableGap = `<tr></tr>`.repeat(4);
432
+ htmlHover.contents = {
433
+ kind: 'markdown',
434
+ value: tableContents
435
+ ? `<table>\n${tableContents.join(`\n${tableGap}\n`)}\n</table>`
436
+ : `No type information available.`,
437
+ };
438
+ }
439
+ }
440
+ return htmlHover;
441
+ function printName(meta, model) {
442
+ let name = meta.name;
443
+ if (meta.tags.some(tag => tag.name === 'deprecated')) {
444
+ name = `<del>${name}</del>`;
445
+ }
446
+ if (meta.required) {
447
+ name += ' <sup><em>required</em></sup>';
448
+ }
449
+ if (model) {
450
+ name += ' <sup><em>model</em></sup>';
451
+ }
452
+ return name;
453
+ }
454
+ function printDescription(meta) {
455
+ let desc = `<code>${meta.type}</code>`;
456
+ if (meta.description) {
457
+ // blank line for terminate HTML to support markdown
458
+ // see: https://github.github.com/gfm/#example-118
459
+ desc = `\n\n${meta.description}<br>${desc}`;
460
+ }
461
+ return desc;
462
+ }
463
+ function hasContents(contents) {
464
+ if (typeof contents === 'string') {
465
+ return !!contents;
466
+ }
467
+ if (Array.isArray(contents)) {
468
+ return contents.some(hasContents);
469
+ }
470
+ return !!contents.value;
435
471
  }
436
- return baseServiceInstance.provideHover?.(document, position, token);
437
472
  },
438
473
  async provideDocumentLinks(document, token) {
439
474
  modulePathCache = new Map();
@@ -454,10 +489,10 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
454
489
  }
455
490
  },
456
491
  };
457
- async function runWithVueData(sourceDocumentUri, root, fn) {
492
+ async function runWithVueDataProvider(sourceDocumentUri, root, hint, mode, fn) {
458
493
  // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver
459
494
  await fn();
460
- const { sync } = await provideHtmlData(sourceDocumentUri, root);
495
+ const { sync } = await provideHtmlData(sourceDocumentUri, root, hint, mode);
461
496
  let lastSync = await sync();
462
497
  let result = await fn();
463
498
  while (lastSync.version !== (lastSync = await sync()).version) {
@@ -465,255 +500,210 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
465
500
  }
466
501
  return { result, ...lastSync };
467
502
  }
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
- }
503
+ async function provideHtmlData(sourceDocumentUri, root, hint, mode) {
504
+ const [tagNameCasing, attrNameCasing] = await Promise.all([
505
+ (0, nameCasing_1.getTagNameCasing)(context, sourceDocumentUri),
506
+ (0, nameCasing_1.getAttrNameCasing)(context, sourceDocumentUri),
507
+ ]);
483
508
  let version = 0;
484
- let target;
485
509
  let components;
510
+ let elements;
511
+ let directives;
486
512
  let values;
487
513
  const tasks = [];
488
- const tagMap = new Map();
489
- const propMap = new Map();
514
+ const tagDataMap = new Map();
490
515
  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
516
  {
517
517
  getId: () => 'vue-template',
518
518
  isApplicable: () => true,
519
519
  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
- }
520
+ const { components, elements } = getComponentsAndElements();
528
521
  const codegen = language_core_1.tsCodegen.get(root.sfc);
529
522
  const names = new Set();
530
523
  const tags = [];
524
+ for (const tag of builtInData?.tags ?? []) {
525
+ tags.push({
526
+ ...tag,
527
+ name: tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag.name) : tag.name,
528
+ });
529
+ }
531
530
  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
- }
531
+ names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag) : tag);
532
+ }
533
+ for (const tag of elements) {
534
+ names.add(tag);
538
535
  }
539
536
  if (codegen) {
540
537
  for (const name of [
541
- ...codegen.getImportComponentNames(),
538
+ ...codegen.getImportedComponents(),
542
539
  ...codegen.getSetupExposed(),
543
540
  ]) {
544
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
545
- names.add((0, language_core_1.hyphenateTag)(name));
546
- }
547
- else {
548
- names.add(name);
549
- }
541
+ names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(name) : name);
550
542
  }
551
543
  }
544
+ const added = new Set(tags.map(t => t.name));
552
545
  for (const name of names) {
553
- tags.push({
554
- name: name,
555
- attributes: [],
556
- });
546
+ if (!added.has(name)) {
547
+ const defaultTag = defaultHtmlTags.get(name);
548
+ tags.push({
549
+ ...defaultTag,
550
+ name,
551
+ attributes: [],
552
+ });
553
+ }
557
554
  }
558
555
  return tags;
559
556
  },
560
557
  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
- })());
558
+ const directives = getDirectives();
559
+ const { attrs, meta } = getTagData(tag);
560
+ const attributes = [];
561
+ let addPlainAttrs = false;
562
+ let addVBinds = false;
563
+ let addVBindShorthands = false;
564
+ let addVOns = false;
565
+ let addVOnShorthands = false;
566
+ if (!hint) {
567
+ addVBindShorthands = true;
568
+ addVOnShorthands = true;
579
569
  }
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);
570
+ else if (hint === ':') {
571
+ addVBindShorthands = true;
572
+ }
573
+ else if (hint === '@') {
574
+ addVOnShorthands = true;
575
+ }
576
+ else {
577
+ addPlainAttrs = true;
578
+ addVBinds = true;
579
+ addVOns = true;
580
+ addVBindShorthands = true;
581
+ addVOnShorthands = true;
582
+ }
583
+ for (const attr of builtInData?.globalAttributes ?? []) {
584
+ if (attr.name === 'is' && tag.toLowerCase() !== 'component') {
585
+ continue;
586
+ }
587
+ if (attr.name === 'ref' || attr.name.startsWith('v-')) {
588
+ attributes.push(attr);
585
589
  continue;
586
590
  }
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);
591
+ if (addPlainAttrs) {
592
+ attributes.push({ ...attr, name: attr.name });
593
+ }
594
+ if (addVBindShorthands) {
595
+ attributes.push({ ...attr, name: V_BIND_SHORTHAND + attr.name });
596
+ }
597
+ if (addVBinds) {
598
+ attributes.push({ ...attr, name: DIRECTIVE_V_BIND + attr.name });
590
599
  }
591
600
  }
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 })),
601
+ for (const [propName, propMeta] of [
602
+ ...meta?.props.map(prop => [prop.name, prop]) ?? [],
603
+ ...attrs.map(attr => [attr.name, undefined]),
597
604
  ]) {
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,
605
+ if (propName.match(EVENT_PROP_REGEX)) {
606
+ let labelName = propName.slice(2);
607
+ labelName = labelName.charAt(0).toLowerCase() + labelName.slice(1);
608
+ if (attrNameCasing === 0 /* AttrNameCasing.Kebab */) {
609
+ labelName = (0, language_core_1.hyphenateAttr)(labelName);
610
+ }
611
+ if (addVOnShorthands) {
612
+ attributes.push({
613
+ name: V_ON_SHORTHAND + labelName,
614
+ description: propMeta && createDescription(propMeta),
615
+ });
616
+ }
617
+ if (addVOns) {
618
+ attributes.push({
619
+ name: DIRECTIVE_V_ON + labelName,
620
+ description: propMeta && createDescription(propMeta),
615
621
  });
616
622
  }
617
623
  }
618
624
  else {
619
- const propInfo = propInfos.find(prop => {
625
+ const labelName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? propName : (0, language_core_1.hyphenateAttr)(propName);
626
+ const propMeta2 = meta?.props.find(prop => {
620
627
  const name = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? prop.name : (0, language_core_1.hyphenateAttr)(prop.name);
621
- return name === propName;
628
+ return name === labelName;
622
629
  });
623
- for (const name of [
624
- propName,
625
- ':' + propName,
626
- 'v-bind:' + propName,
627
- ]) {
630
+ if (addPlainAttrs) {
628
631
  attributes.push({
629
- name,
630
- valueSet: prop.values?.some(value => typeof value === 'string') ? '__deferred__' : undefined,
632
+ name: labelName,
633
+ description: propMeta2 && createDescription(propMeta2),
631
634
  });
632
- propMap.set(name, {
633
- name: propName,
634
- kind: 'prop',
635
- isGlobal,
636
- info: propInfo,
635
+ }
636
+ if (addVBindShorthands) {
637
+ attributes.push({
638
+ name: V_BIND_SHORTHAND + labelName,
639
+ description: propMeta2 && createDescription(propMeta2),
640
+ });
641
+ }
642
+ if (addVBinds) {
643
+ attributes.push({
644
+ name: DIRECTIVE_V_BIND + labelName,
645
+ description: propMeta2 && createDescription(propMeta2),
637
646
  });
638
647
  }
639
648
  }
640
649
  }
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',
650
+ for (const event of meta?.events ?? []) {
651
+ const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? event.name : (0, language_core_1.hyphenateAttr)(event.name);
652
+ if (addVOnShorthands) {
653
+ attributes.push({
654
+ name: V_ON_SHORTHAND + eventName,
655
+ description: event && createDescription(event),
656
+ });
657
+ }
658
+ if (addVOns) {
659
+ attributes.push({
660
+ name: DIRECTIVE_V_ON + eventName,
661
+ description: event && createDescription(event),
651
662
  });
652
663
  }
653
664
  }
654
665
  for (const directive of directives) {
655
- const name = (0, language_core_1.hyphenateAttr)(directive);
656
666
  attributes.push({
657
- name,
667
+ name: (0, language_core_1.hyphenateAttr)(directive),
658
668
  });
659
669
  }
660
- const models = [];
661
- for (const prop of [
662
- ...propInfos,
663
- ...attrs.map(attr => ({ name: attr })),
670
+ for (const [propName, propMeta] of [
671
+ ...meta?.props.map(prop => [prop.name, prop]) ?? [],
672
+ ...attrs.map(attr => [attr.name, undefined]),
664
673
  ]) {
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));
674
+ if (propName.startsWith(UPDATE_PROP_PREFIX)) {
675
+ const model = propName.slice(UPDATE_PROP_PREFIX.length);
676
+ const label = DIRECTIVE_V_MODEL
677
+ + (attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model));
678
+ attributes.push({
679
+ name: label,
680
+ description: propMeta && createDescription(propMeta),
681
+ });
672
682
  }
673
683
  }
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',
684
+ for (const event of meta?.events ?? []) {
685
+ if (event.name.startsWith(UPDATE_EVENT_PREFIX)) {
686
+ const model = event.name.slice(UPDATE_EVENT_PREFIX.length);
687
+ const label = DIRECTIVE_V_MODEL
688
+ + (attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model));
689
+ attributes.push({
690
+ name: label,
691
+ description: createDescription(event),
685
692
  });
686
693
  }
687
694
  }
688
695
  return attributes;
689
696
  },
690
697
  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
- }));
698
+ return getAttrValues(tag, attr).map(value => ({ name: value }));
703
699
  },
704
700
  },
705
701
  {
706
702
  getId: () => 'vue-auto-imports',
707
703
  isApplicable: () => true,
708
- provideTags() {
709
- return [{ name: 'AutoImportsPlaceholder', attributes: [] }];
710
- },
711
- provideAttributes() {
712
- return [];
713
- },
714
- provideValues() {
715
- return [];
716
- },
704
+ provideTags: () => [{ name: AUTO_IMPORT_PLACEHOLDER, attributes: [] }],
705
+ provideAttributes: () => [],
706
+ provideValues: () => [],
717
707
  },
718
708
  ]);
719
709
  return {
@@ -721,25 +711,96 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
721
711
  await Promise.all(tasks);
722
712
  return {
723
713
  version,
724
- target,
725
714
  info: {
726
715
  tagNameCasing,
727
716
  components,
728
- propMap,
729
717
  },
730
718
  };
731
719
  },
732
720
  };
721
+ function createDescription(meta) {
722
+ if (mode === 'hover') {
723
+ // dedupe from TS hover
724
+ return;
725
+ }
726
+ let description = meta?.description ?? '';
727
+ for (const tag of meta.tags) {
728
+ description += `\n\n*@${tag.name}* ${tag.text ?? ''}`;
729
+ }
730
+ if (!description) {
731
+ return;
732
+ }
733
+ return {
734
+ kind: 'markdown',
735
+ value: description,
736
+ };
737
+ }
738
+ function getAttrValues(tag, attr) {
739
+ if (!values) {
740
+ values = [];
741
+ tasks.push((async () => {
742
+ if (tag === 'slot' && attr === 'name') {
743
+ values = await tsserver.getComponentSlots(root.fileName) ?? [];
744
+ }
745
+ version++;
746
+ })());
747
+ }
748
+ return values;
749
+ }
750
+ function getTagData(tag) {
751
+ let data = tagDataMap.get(tag);
752
+ if (!data) {
753
+ data = { attrs: [], meta: undefined };
754
+ tagDataMap.set(tag, data);
755
+ tasks.push((async () => {
756
+ tagDataMap.set(tag, {
757
+ attrs: await tsserver.getElementAttrs(root.fileName, tag) ?? [],
758
+ meta: await tsserver.getComponentMeta(root.fileName, tag),
759
+ });
760
+ version++;
761
+ })());
762
+ }
763
+ return data;
764
+ }
765
+ function getDirectives() {
766
+ if (!directives) {
767
+ directives = [];
768
+ tasks.push((async () => {
769
+ directives = await tsserver.getComponentDirectives(root.fileName) ?? [];
770
+ version++;
771
+ })());
772
+ }
773
+ return directives;
774
+ }
775
+ function getComponentsAndElements() {
776
+ if (!components || !elements) {
777
+ components = [];
778
+ elements = [];
779
+ tasks.push((async () => {
780
+ const res = await Promise.all([
781
+ tsserver.getComponentNames(root.fileName),
782
+ tsserver.getElementNames(root.fileName),
783
+ ]);
784
+ components = res[0] ?? [];
785
+ elements = res[1] ?? [];
786
+ version++;
787
+ })());
788
+ }
789
+ return {
790
+ components,
791
+ elements,
792
+ };
793
+ }
733
794
  }
734
- function addDirectiveModifiers(completionList, document) {
735
- const replacement = getReplacement(completionList, document);
795
+ function addDirectiveModifiers(list, item, document) {
796
+ const replacement = getReplacement(item, document);
736
797
  if (!replacement?.text.includes('.')) {
737
798
  return;
738
799
  }
739
800
  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';
801
+ const isVOn = text.startsWith(DIRECTIVE_V_ON) || text.startsWith(V_ON_SHORTHAND) && text.length > 1;
802
+ const isVBind = text.startsWith(DIRECTIVE_V_BIND) || text.startsWith(V_BIND_SHORTHAND) && text.length > 1;
803
+ const isVModel = text.startsWith(DIRECTIVE_V_MODEL) || text === 'v-model';
743
804
  const currentModifiers = isVOn
744
805
  ? vOnModifiers
745
806
  : isVBind
@@ -769,55 +830,55 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
769
830
  },
770
831
  kind: 20,
771
832
  };
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
- }
833
+ list.items.push(newItem);
795
834
  }
796
- return newData;
797
835
  }
798
836
  },
799
837
  };
800
- function updateExtraCustomData(extraData) {
801
- extraCustomData = extraData;
838
+ function updateExtraCustomData(newData) {
839
+ htmlData = newData;
802
840
  onDidChangeCustomDataListeners.forEach(l => l());
803
841
  }
804
842
  }
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
- }
843
+ function getReplacement(item, doc) {
844
+ if (item.textEdit && 'range' in item.textEdit) {
845
+ return {
846
+ item: item,
847
+ textEdit: item.textEdit,
848
+ text: doc.getText(item.textEdit.range),
849
+ };
814
850
  }
815
851
  }
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) };
852
+ function extractDirectiveModifiers(directive) {
853
+ const modifiers = {};
854
+ if (!directive) {
855
+ return modifiers;
856
+ }
857
+ const markdown = typeof directive.description === 'object'
858
+ ? directive.description.value
859
+ : directive.description ?? '';
860
+ const modifierLines = markdown
861
+ .split('\n- ')[4]
862
+ ?.split('\n').slice(2, -1) ?? [];
863
+ for (let text of modifierLines) {
864
+ text = text.slice(' - `.'.length);
865
+ const [name, desc] = text.split('` - ');
866
+ modifiers[name] = desc;
867
+ }
868
+ return modifiers;
869
+ }
870
+ function extractModelModifiers(attributes) {
871
+ const modifiers = {};
872
+ if (!attributes) {
873
+ return modifiers;
874
+ }
875
+ for (const modifier of attributes) {
876
+ const description = typeof modifier.description === 'object'
877
+ ? modifier.description.value
878
+ : modifier.description ?? '';
879
+ const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | ');
880
+ modifiers[modifier.name] = description + '\n\n' + references;
820
881
  }
821
- return { isEvent, propName: name };
882
+ return modifiers;
822
883
  }
823
884
  //# sourceMappingURL=vue-template.js.map