@vue/language-service 3.1.7 → 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");
@@ -7,40 +40,31 @@ const language_core_1 = require("@vue/language-core");
7
40
  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
- const getFormatCodeSettings_js_1 = require("volar-service-typescript/lib/configs/getFormatCodeSettings.js");
11
- const getUserPreferences_js_1 = require("volar-service-typescript/lib/configs/getUserPreferences.js");
12
43
  const lspConverters_js_1 = require("volar-service-typescript/lib/utils/lspConverters.js");
13
- const html = require("vscode-html-languageservice");
44
+ const html = __importStar(require("vscode-html-languageservice"));
14
45
  const vscode_uri_1 = require("vscode-uri");
15
46
  const data_1 = require("../data");
16
47
  const htmlFormatter_1 = require("../htmlFormatter");
17
48
  const nameCasing_1 = require("../nameCasing");
18
49
  const utils_1 = require("../utils");
19
- const specialTags = new Set([
20
- 'slot',
21
- 'component',
22
- 'template',
23
- ]);
24
- const specialProps = new Set([
25
- 'class',
26
- 'data-allow-mismatch',
27
- 'is',
28
- 'key',
29
- 'ref',
30
- 'style',
31
- ]);
32
- const builtInComponents = new Set([
33
- 'Transition',
34
- 'TransitionGroup',
35
- 'KeepAlive',
36
- 'Suspense',
37
- 'Teleport',
38
- ]);
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}"';
39
64
  let builtInData;
40
65
  let modelData;
41
- function create(ts, languageId, { getComponentNames, getComponentProps, getComponentEvents, getComponentDirectives, getComponentSlots, getElementAttrs, resolveModuleName, getAutoImportSuggestions, resolveAutoImportCompletionEntry, }) {
42
- let customData = [];
43
- let extraCustomData = [];
66
+ function create(ts, languageId, tsserver) {
67
+ let htmlData = [];
44
68
  let modulePathCache;
45
69
  const onDidChangeCustomDataListeners = new Set();
46
70
  const onDidChangeCustomData = (listener) => {
@@ -51,62 +75,55 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
51
75
  },
52
76
  };
53
77
  };
78
+ const getDocumentContext = context => ({
79
+ resolveReference(ref, base) {
80
+ let baseUri = vscode_uri_1.URI.parse(base);
81
+ const decoded = context.decodeEmbeddedDocumentUri(baseUri);
82
+ if (decoded) {
83
+ baseUri = decoded[0];
84
+ }
85
+ if (modulePathCache
86
+ && baseUri.scheme === 'file'
87
+ && !ref.startsWith('./')
88
+ && !ref.startsWith('../')) {
89
+ const map = modulePathCache;
90
+ if (!map.has(ref)) {
91
+ const fileName = baseUri.fsPath.replace(/\\/g, '/');
92
+ const promise = tsserver.resolveModuleName(fileName, ref);
93
+ map.set(ref, promise);
94
+ if (promise instanceof Promise) {
95
+ promise.then(res => map.set(ref, res));
96
+ }
97
+ }
98
+ const cached = modulePathCache.get(ref);
99
+ if (cached instanceof Promise) {
100
+ throw cached;
101
+ }
102
+ if (cached) {
103
+ return cached;
104
+ }
105
+ }
106
+ return (0, volar_service_html_1.resolveReference)(ref, baseUri, context.env.workspaceFolders);
107
+ },
108
+ });
54
109
  const baseService = languageId === 'jade'
55
110
  ? (0, volar_service_pug_1.create)({
56
111
  useDefaultDataProvider: false,
112
+ getDocumentContext,
57
113
  getCustomData() {
58
- return [
59
- ...customData,
60
- ...extraCustomData,
61
- ];
114
+ return htmlData;
62
115
  },
63
116
  onDidChangeCustomData,
64
117
  })
65
118
  : (0, volar_service_html_1.create)({
66
119
  documentSelector: ['html', 'markdown'],
67
120
  useDefaultDataProvider: false,
68
- getDocumentContext(context) {
69
- return {
70
- resolveReference(ref, base) {
71
- let baseUri = vscode_uri_1.URI.parse(base);
72
- const decoded = context.decodeEmbeddedDocumentUri(baseUri);
73
- if (decoded) {
74
- baseUri = decoded[0];
75
- }
76
- if (modulePathCache
77
- && baseUri.scheme === 'file'
78
- && !ref.startsWith('./')
79
- && !ref.startsWith('../')) {
80
- const map = modulePathCache;
81
- if (!map.has(ref)) {
82
- const fileName = baseUri.fsPath.replace(/\\/g, '/');
83
- const promise = resolveModuleName(fileName, ref);
84
- map.set(ref, promise);
85
- if (promise instanceof Promise) {
86
- promise.then(res => map.set(ref, res));
87
- }
88
- }
89
- const cached = modulePathCache.get(ref);
90
- if (cached instanceof Promise) {
91
- throw cached;
92
- }
93
- if (cached) {
94
- return cached;
95
- }
96
- }
97
- return (0, volar_service_html_1.resolveReference)(ref, baseUri, context.env.workspaceFolders);
98
- },
99
- };
100
- },
121
+ getDocumentContext,
101
122
  getCustomData() {
102
- return [
103
- ...customData,
104
- ...extraCustomData,
105
- ];
123
+ return htmlData;
106
124
  },
107
125
  onDidChangeCustomData,
108
126
  });
109
- const htmlDataProvider = html.getDefaultHTMLDataProvider();
110
127
  return {
111
128
  name: `vue-template (${languageId})`,
112
129
  capabilities: {
@@ -149,7 +166,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
149
166
  const codegen = info && language_core_1.tsCodegen.get(info.root.sfc);
150
167
  if (codegen) {
151
168
  const componentNames = new Set([
152
- ...codegen.getImportComponentNames(),
169
+ ...codegen.getImportedComponents(),
153
170
  ...codegen.getSetupExposed(),
154
171
  ]);
155
172
  // copied from https://github.com/microsoft/vscode-html-languageservice/blob/10daf45dc16b4f4228987cf7cddf3a7dbbdc7570/src/beautify/beautify-html.js#L2746-L2761
@@ -184,66 +201,20 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
184
201
  builtInData ??= (0, data_1.loadTemplateData)(context.env.locale ?? 'en');
185
202
  modelData ??= (0, data_1.loadModelModifiersData)(context.env.locale ?? 'en');
186
203
  // https://vuejs.org/api/built-in-directives.html#v-on
204
+ const vOnModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-on'));
187
205
  // https://vuejs.org/api/built-in-directives.html#v-bind
188
- const vOnModifiers = {};
189
- const vBindModifiers = {};
190
- const vModelModifiers = {};
191
- const vOn = builtInData.globalAttributes?.find(x => x.name === 'v-on');
192
- const vBind = builtInData.globalAttributes?.find(x => x.name === 'v-bind');
193
- const vModel = builtInData.globalAttributes?.find(x => x.name === 'v-model');
194
- if (vOn) {
195
- const markdown = typeof vOn.description === 'object'
196
- ? vOn.description.value
197
- : vOn.description ?? '';
198
- const modifiers = markdown
199
- .split('\n- ')[4]
200
- .split('\n').slice(2, -1);
201
- for (let text of modifiers) {
202
- text = text.slice(' - `.'.length);
203
- const [name, desc] = text.split('` - ');
204
- vOnModifiers[name] = desc;
205
- }
206
- }
207
- if (vBind) {
208
- const markdown = typeof vBind.description === 'object'
209
- ? vBind.description.value
210
- : vBind.description ?? '';
211
- const modifiers = markdown
212
- .split('\n- ')[4]
213
- .split('\n').slice(2, -1);
214
- for (let text of modifiers) {
215
- text = text.slice(' - `.'.length);
216
- const [name, desc] = text.split('` - ');
217
- vBindModifiers[name] = desc;
218
- }
219
- }
220
- if (vModel) {
221
- for (const modifier of modelData.globalAttributes ?? []) {
222
- const description = typeof modifier.description === 'object'
223
- ? modifier.description.value
224
- : modifier.description ?? '';
225
- const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | ');
226
- vModelModifiers[modifier.name] = description + '\n\n' + references;
227
- }
228
- }
229
- 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);
230
208
  const transformedItems = new WeakSet();
231
- let initializing;
232
- let formattingOptions;
209
+ const defaultHtmlTags = new Map();
210
+ for (const tag of html.getDefaultHTMLDataProvider().provideTags()) {
211
+ defaultHtmlTags.set(tag.name, tag);
212
+ }
233
213
  let lastCompletionDocument;
234
214
  return {
235
215
  ...baseServiceInstance,
236
216
  dispose() {
237
217
  baseServiceInstance.dispose?.();
238
- disposable?.dispose();
239
- },
240
- provideDocumentFormattingEdits(document, range, options, ...rest) {
241
- formattingOptions = options;
242
- return baseServiceInstance.provideDocumentFormattingEdits?.(document, range, options, ...rest);
243
- },
244
- provideOnTypeFormattingEdits(document, position, key, options, ...rest) {
245
- formattingOptions = options;
246
- return baseServiceInstance.provideOnTypeFormattingEdits?.(document, position, key, options, ...rest);
247
218
  },
248
219
  async provideCompletionItems(document, position, completionContext, token) {
249
220
  if (document.languageId !== languageId) {
@@ -253,21 +224,35 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
253
224
  if (info?.code.id !== 'template') {
254
225
  return;
255
226
  }
256
- 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);
257
237
  if (!htmlCompletion) {
258
238
  return;
259
239
  }
260
- const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === 'AutoImportsPlaceholder');
261
- 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
+ }
262
251
  const offset = document.offsetAt(position);
263
252
  const map = context.language.maps.get(info.code, info.script);
264
253
  let spliced = false;
265
254
  for (const [sourceOffset] of map.toSourceLocation(offset)) {
266
- const [formatOptions, preferences] = await Promise.all([
267
- (0, getFormatCodeSettings_js_1.getFormatCodeSettings)(context, document, formattingOptions),
268
- (0, getUserPreferences_js_1.getUserPreferences)(context, document),
269
- ]);
270
- const autoImport = await getAutoImportSuggestions(info.root.fileName, sourceOffset, preferences, formatOptions);
255
+ const autoImport = await tsserver.getAutoImportSuggestions(info.root.fileName, sourceOffset);
271
256
  if (!autoImport) {
272
257
  continue;
273
258
  }
@@ -296,118 +281,42 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
296
281
  htmlCompletion.items.splice(autoImportPlaceholderIndex, 1);
297
282
  }
298
283
  }
299
- switch (target) {
300
- case 'tag': {
301
- htmlCompletion.items.forEach(transformTag);
302
- break;
303
- }
304
- case 'attribute': {
305
- addDirectiveModifiers(htmlCompletion, document);
306
- htmlCompletion.items.forEach(transformAttribute);
307
- break;
308
- }
309
- }
310
- updateExtraCustomData([]);
311
- return htmlCompletion;
312
- function transformTag(item) {
313
- const tagName = (0, shared_1.capitalize)((0, shared_1.camelize)(item.label));
314
- if (components?.includes(tagName)) {
315
- item.kind = 6;
316
- item.sortText = '\u0000' + (item.sortText ?? item.label);
317
- }
318
- }
319
- function transformAttribute(item) {
320
- let prop = propMap.get(item.label);
321
- if (prop) {
322
- if (prop.info?.documentation) {
323
- item.documentation = {
324
- kind: 'markdown',
325
- value: prop.info.documentation,
326
- };
327
- }
328
- if (prop.info?.deprecated) {
329
- item.tags = [1];
330
- }
331
- }
332
- else {
333
- let name = item.label;
334
- for (const str of ['v-bind:', ':']) {
335
- if (name.startsWith(str) && name !== str) {
336
- 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
+ }
337
312
  break;
338
- }
339
- }
340
- if (specialProps.has(name)) {
341
- prop = {
342
- name,
343
- kind: 'prop',
344
- };
345
- }
346
- }
347
- const tokens = [];
348
- if (prop) {
349
- const { isEvent, propName } = getPropName(prop.name, prop.kind === 'event');
350
- if (prop.kind === 'prop') {
351
- if (!prop.isGlobal) {
352
- item.kind = 5;
353
- }
354
- }
355
- else if (isEvent) {
356
- item.kind = 23;
357
- if (propName.startsWith('vue:')) {
358
- tokens.push('\u0004');
359
- }
360
- }
361
- if (!prop.isGlobal) {
362
- tokens.push('\u0000');
363
- if (item.label.startsWith(':')) {
364
- tokens.push('\u0001');
365
- }
366
- else if (item.label.startsWith('@')) {
367
- tokens.push('\u0002');
368
- }
369
- else if (item.label.startsWith('v-bind:')) {
370
- tokens.push('\u0003');
371
- }
372
- else if (item.label.startsWith('v-model:')) {
373
- tokens.push('\u0004');
374
- }
375
- else if (item.label.startsWith('v-on:')) {
376
- tokens.push('\u0005');
377
- }
378
- else {
379
- tokens.push('\u0000');
380
- }
381
- if (specialProps.has(propName)) {
382
- tokens.push('\u0001');
383
- }
384
- else {
385
- tokens.push('\u0000');
386
- }
387
313
  }
388
314
  }
389
- else if (item.label === 'v-if'
390
- || item.label === 'v-else-if'
391
- || item.label === 'v-else'
392
- || item.label === 'v-for') {
393
- item.kind = 14;
394
- tokens.push('\u0003');
395
- }
396
- else if (item.label.startsWith('v-')) {
397
- item.kind = 3;
398
- tokens.push('\u0002');
399
- }
400
- else {
401
- tokens.push('\u0001');
402
- }
403
- item.sortText = tokens.join('') + (item.sortText ?? item.label);
404
- if (item.label === 'v-for') {
405
- item.textEdit.newText = item.label + '="${1:value} in ${2:source}"';
406
- }
407
315
  }
408
316
  },
409
317
  async resolveCompletionItem(item) {
410
- if (item.data?.__isAutoImport || item.data?.__isComponentAutoImport) {
318
+ const data = item.data;
319
+ if (data?.__vue__autoImport || data?.__vue__componentAutoImport) {
411
320
  const embeddedUri = vscode_uri_1.URI.parse(lastCompletionDocument.uri);
412
321
  const decoded = context.decodeEmbeddedDocumentUri(embeddedUri);
413
322
  if (!decoded) {
@@ -417,11 +326,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
417
326
  if (!sourceScript) {
418
327
  return item;
419
328
  }
420
- const [formatOptions, preferences] = await Promise.all([
421
- (0, getFormatCodeSettings_js_1.getFormatCodeSettings)(context, lastCompletionDocument, formattingOptions),
422
- (0, getUserPreferences_js_1.getUserPreferences)(context, lastCompletionDocument),
423
- ]);
424
- const details = await resolveAutoImportCompletionEntry(item.data, preferences, formatOptions);
329
+ const details = await tsserver.resolveAutoImportCompletionEntry(data);
425
330
  if (details) {
426
331
  const virtualCode = sourceScript.generated.embeddedCodes.get(decoded[1]);
427
332
  const sourceDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot);
@@ -439,7 +344,7 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
439
344
  return item;
440
345
  }
441
346
  },
442
- provideHover(document, position, token) {
347
+ async provideHover(document, position, token) {
443
348
  if (document.languageId !== languageId) {
444
349
  return;
445
350
  }
@@ -447,12 +352,129 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
447
352
  if (info?.code.id !== 'template') {
448
353
  return;
449
354
  }
450
- if (context.decodeEmbeddedDocumentUri(vscode_uri_1.URI.parse(document.uri))) {
451
- updateExtraCustomData([
452
- htmlDataProvider,
453
- ]);
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;
454
477
  }
455
- return baseServiceInstance.provideHover?.(document, position, token);
456
478
  },
457
479
  async provideDocumentLinks(document, token) {
458
480
  modulePathCache = new Map();
@@ -473,10 +495,10 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
473
495
  }
474
496
  },
475
497
  };
476
- async function runWithVueData(sourceDocumentUri, root, fn) {
498
+ async function runWithVueDataProvider(sourceDocumentUri, root, hint, mode, fn) {
477
499
  // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver
478
500
  await fn();
479
- const { sync } = await provideHtmlData(sourceDocumentUri, root);
501
+ const { sync } = await provideHtmlData(sourceDocumentUri, root, hint, mode);
480
502
  let lastSync = await sync();
481
503
  let result = await fn();
482
504
  while (lastSync.version !== (lastSync = await sync()).version) {
@@ -484,255 +506,195 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
484
506
  }
485
507
  return { result, ...lastSync };
486
508
  }
487
- async function provideHtmlData(sourceDocumentUri, root) {
488
- await (initializing ??= initialize());
489
- const tagNameCasing = await (0, nameCasing_1.getTagNameCasing)(context, sourceDocumentUri);
490
- const attrNameCasing = await (0, nameCasing_1.getAttrNameCasing)(context, sourceDocumentUri);
491
- for (const tag of builtInData.tags ?? []) {
492
- if (specialTags.has(tag.name)) {
493
- continue;
494
- }
495
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
496
- tag.name = (0, language_core_1.hyphenateTag)(tag.name);
497
- }
498
- else {
499
- tag.name = (0, shared_1.camelize)((0, shared_1.capitalize)(tag.name));
500
- }
501
- }
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
+ ]);
502
514
  let version = 0;
503
- let target;
504
515
  let components;
516
+ let elements;
517
+ let directives;
505
518
  let values;
506
519
  const tasks = [];
507
- const tagMap = new Map();
508
- const propMap = new Map();
520
+ const tagDataMap = new Map();
509
521
  updateExtraCustomData([
510
- {
511
- getId: () => htmlDataProvider.getId(),
512
- isApplicable: () => true,
513
- provideTags() {
514
- target = 'tag';
515
- return htmlDataProvider.provideTags()
516
- .filter(tag => !specialTags.has(tag.name));
517
- },
518
- provideAttributes(tag) {
519
- target = 'attribute';
520
- const attrs = htmlDataProvider.provideAttributes(tag);
521
- if (tag === 'slot') {
522
- const nameAttr = attrs.find(attr => attr.name === 'name');
523
- if (nameAttr) {
524
- nameAttr.valueSet = 'slot';
525
- }
526
- }
527
- return attrs;
528
- },
529
- provideValues(tag, attr) {
530
- target = 'value';
531
- return htmlDataProvider.provideValues(tag, attr);
532
- },
533
- },
534
- html.newHTMLDataProvider('vue-template-built-in', builtInData),
535
522
  {
536
523
  getId: () => 'vue-template',
537
524
  isApplicable: () => true,
538
525
  provideTags: () => {
539
- if (!components) {
540
- components = [];
541
- tasks.push((async () => {
542
- components = (await getComponentNames(root.fileName) ?? [])
543
- .filter(name => !builtInComponents.has(name));
544
- version++;
545
- })());
546
- }
526
+ const { components, elements } = getComponentsAndElements();
547
527
  const codegen = language_core_1.tsCodegen.get(root.sfc);
548
528
  const names = new Set();
549
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
+ }
550
536
  for (const tag of components) {
551
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
552
- names.add((0, language_core_1.hyphenateTag)(tag));
553
- }
554
- else {
555
- names.add(tag);
556
- }
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);
557
541
  }
558
542
  if (codegen) {
559
543
  for (const name of [
560
- ...codegen.getImportComponentNames(),
544
+ ...codegen.getImportedComponents(),
561
545
  ...codegen.getSetupExposed(),
562
546
  ]) {
563
- if (tagNameCasing === 0 /* TagNameCasing.Kebab */) {
564
- names.add((0, language_core_1.hyphenateTag)(name));
565
- }
566
- else {
567
- names.add(name);
568
- }
547
+ names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(name) : name);
569
548
  }
570
549
  }
550
+ const added = new Set(tags.map(t => t.name));
571
551
  for (const name of names) {
572
- tags.push({
573
- name: name,
574
- attributes: [],
575
- });
552
+ if (!added.has(name)) {
553
+ const defaultTag = defaultHtmlTags.get(name);
554
+ tags.push({
555
+ ...defaultTag,
556
+ name,
557
+ attributes: [],
558
+ });
559
+ }
576
560
  }
577
561
  return tags;
578
562
  },
579
563
  provideAttributes: tag => {
580
- let tagInfo = tagMap.get(tag);
581
- if (!tagInfo) {
582
- tagInfo = {
583
- attrs: [],
584
- propInfos: [],
585
- events: [],
586
- directives: [],
587
- };
588
- tagMap.set(tag, tagInfo);
589
- tasks.push((async () => {
590
- tagMap.set(tag, {
591
- attrs: await getElementAttrs(root.fileName, tag) ?? [],
592
- propInfos: await getComponentProps(root.fileName, tag) ?? [],
593
- events: await getComponentEvents(root.fileName, tag) ?? [],
594
- directives: await getComponentDirectives(root.fileName) ?? [],
595
- });
596
- version++;
597
- })());
598
- }
599
- const { attrs, propInfos, events, directives } = tagInfo;
600
- for (let i = 0; i < propInfos.length; i++) {
601
- const prop = propInfos[i];
602
- if (prop.name.startsWith('ref_')) {
603
- 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') {
604
569
  continue;
605
570
  }
606
- if ((0, language_core_1.hyphenateTag)(prop.name).startsWith('on-vnode-')) {
607
- prop.name = 'onVue:' + prop.name['onVnode'.length].toLowerCase()
608
- + 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
+ });
609
590
  }
610
591
  }
611
- const attributes = [];
612
- const propNameSet = new Set(propInfos.map(prop => prop.name));
613
- for (const prop of [
614
- ...propInfos,
615
- ...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]),
616
595
  ]) {
617
- const isGlobal = prop.isAttribute || !propNameSet.has(prop.name);
618
- const propName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? prop.name : (0, language_core_1.hyphenateAttr)(prop.name);
619
- const isEvent = (0, language_core_1.hyphenateAttr)(propName).startsWith('on-');
620
- if (isEvent) {
621
- const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */
622
- ? propName['on'.length].toLowerCase() + propName.slice('onX'.length)
623
- : propName.slice('on-'.length);
624
- for (const name of [
625
- 'v-on:' + eventName,
626
- '@' + eventName,
627
- ]) {
628
- attributes.push({ name });
629
- propMap.set(name, {
630
- name: propName,
631
- kind: 'event',
632
- isGlobal,
633
- 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),
634
612
  });
635
613
  }
636
614
  }
637
615
  else {
638
- 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 => {
639
618
  const name = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? prop.name : (0, language_core_1.hyphenateAttr)(prop.name);
640
- return name === propName;
619
+ return name === labelName;
641
620
  });
642
- for (const name of [
643
- propName,
644
- ':' + propName,
645
- 'v-bind:' + propName,
646
- ]) {
621
+ if (!hint || hint === ':') {
647
622
  attributes.push({
648
- name,
649
- valueSet: prop.values?.some(value => typeof value === 'string') ? '__deferred__' : undefined,
623
+ name: V_BIND_SHORTHAND + labelName,
624
+ description: propMeta2 && createDescription(propMeta2),
650
625
  });
651
- propMap.set(name, {
652
- name: propName,
653
- kind: 'prop',
654
- isGlobal,
655
- info: propInfo,
626
+ }
627
+ if (!hint || hint === 'v') {
628
+ attributes.push({
629
+ name: DIRECTIVE_V_BIND + labelName,
630
+ description: propMeta2 && createDescription(propMeta2),
631
+ });
632
+ attributes.push({
633
+ name: labelName,
634
+ description: propMeta2 && createDescription(propMeta2),
656
635
  });
657
636
  }
658
637
  }
659
638
  }
660
- for (const event of events) {
661
- const eventName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? event : (0, language_core_1.hyphenateAttr)(event);
662
- for (const name of [
663
- 'v-on:' + eventName,
664
- '@' + eventName,
665
- ]) {
666
- attributes.push({ name });
667
- propMap.set(name, {
668
- name: eventName,
669
- 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),
670
651
  });
671
652
  }
672
653
  }
673
654
  for (const directive of directives) {
674
- const name = (0, language_core_1.hyphenateAttr)(directive);
675
655
  attributes.push({
676
- name,
656
+ name: (0, language_core_1.hyphenateAttr)(directive),
677
657
  });
678
658
  }
679
- const models = [];
680
- for (const prop of [
681
- ...propInfos,
682
- ...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]),
683
662
  ]) {
684
- if (prop.name.startsWith('onUpdate:')) {
685
- models.push(prop.name.slice('onUpdate:'.length));
686
- }
687
- }
688
- for (const event of events) {
689
- if (event.startsWith('update:')) {
690
- 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
+ });
691
671
  }
692
672
  }
693
- for (const model of models) {
694
- const name = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model);
695
- attributes.push({ name: 'v-model:' + name });
696
- propMap.set('v-model:' + name, {
697
- name,
698
- kind: 'prop',
699
- });
700
- if (model === 'modelValue') {
701
- propMap.set('v-model', {
702
- name,
703
- kind: 'prop',
704
- });
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
+ }
705
684
  }
706
685
  }
707
686
  return attributes;
708
687
  },
709
688
  provideValues: (tag, attr) => {
710
- if (!values) {
711
- values = [];
712
- tasks.push((async () => {
713
- if (tag === 'slot' && attr === 'name') {
714
- values = await getComponentSlots(root.fileName) ?? [];
715
- }
716
- version++;
717
- })());
718
- }
719
- return values.map(value => ({
720
- name: value,
721
- }));
689
+ return getAttrValues(tag, attr).map(value => ({ name: value }));
722
690
  },
723
691
  },
724
692
  {
725
693
  getId: () => 'vue-auto-imports',
726
694
  isApplicable: () => true,
727
- provideTags() {
728
- return [{ name: 'AutoImportsPlaceholder', attributes: [] }];
729
- },
730
- provideAttributes() {
731
- return [];
732
- },
733
- provideValues() {
734
- return [];
735
- },
695
+ provideTags: () => [{ name: AUTO_IMPORT_PLACEHOLDER, attributes: [] }],
696
+ provideAttributes: () => [],
697
+ provideValues: () => [],
736
698
  },
737
699
  ]);
738
700
  return {
@@ -740,25 +702,96 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
740
702
  await Promise.all(tasks);
741
703
  return {
742
704
  version,
743
- target,
744
705
  info: {
745
706
  tagNameCasing,
746
707
  components,
747
- propMap,
748
708
  },
749
709
  };
750
710
  },
751
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
+ }
752
785
  }
753
- function addDirectiveModifiers(completionList, document) {
754
- const replacement = getReplacement(completionList, document);
786
+ function addDirectiveModifiers(list, item, document) {
787
+ const replacement = getReplacement(item, document);
755
788
  if (!replacement?.text.includes('.')) {
756
789
  return;
757
790
  }
758
791
  const [text, ...modifiers] = replacement.text.split('.');
759
- const isVOn = text.startsWith('v-on:') || text.startsWith('@') && text.length > 1;
760
- const isVBind = text.startsWith('v-bind:') || text.startsWith(':') && text.length > 1;
761
- 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';
762
795
  const currentModifiers = isVOn
763
796
  ? vOnModifiers
764
797
  : isVBind
@@ -788,55 +821,55 @@ function create(ts, languageId, { getComponentNames, getComponentProps, getCompo
788
821
  },
789
822
  kind: 20,
790
823
  };
791
- completionList.items.push(newItem);
824
+ list.items.push(newItem);
792
825
  }
793
826
  }
794
- async function initialize() {
795
- customData = await getHtmlCustomData();
796
- }
797
- async function getHtmlCustomData() {
798
- const customData = await context.env.getConfiguration?.('html.customData') ?? [];
799
- const newData = [];
800
- for (const customDataPath of customData) {
801
- for (const workspaceFolder of context.env.workspaceFolders) {
802
- const uri = vscode_uri_1.Utils.resolvePath(workspaceFolder, customDataPath);
803
- const json = await context.env.fs?.readFile(uri);
804
- if (json) {
805
- try {
806
- const data = JSON.parse(json);
807
- newData.push(html.newHTMLDataProvider(customDataPath, data));
808
- }
809
- catch (error) {
810
- console.error(error);
811
- }
812
- }
813
- }
814
- }
815
- return newData;
816
- }
817
827
  },
818
828
  };
819
- function updateExtraCustomData(extraData) {
820
- extraCustomData = extraData;
829
+ function updateExtraCustomData(newData) {
830
+ htmlData = newData;
821
831
  onDidChangeCustomDataListeners.forEach(l => l());
822
832
  }
823
833
  }
824
- function getReplacement(list, doc) {
825
- for (const item of list.items) {
826
- if (item.textEdit && 'range' in item.textEdit) {
827
- return {
828
- item: item,
829
- textEdit: item.textEdit,
830
- text: doc.getText(item.textEdit.range),
831
- };
832
- }
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
+ };
841
+ }
842
+ }
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;
833
858
  }
859
+ return modifiers;
834
860
  }
835
- function getPropName(prop, isEvent) {
836
- const name = (0, language_core_1.hyphenateAttr)(prop);
837
- if (name.startsWith('on-')) {
838
- return { isEvent: true, propName: name.slice('on-'.length) };
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;
839
872
  }
840
- return { isEvent, propName: name };
873
+ return modifiers;
841
874
  }
842
875
  //# sourceMappingURL=vue-template.js.map