@vue/language-service 1.7.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.
- package/LICENSE +21 -0
- package/data/language-blocks/en.json +626 -0
- package/data/language-blocks/fr.json +626 -0
- package/data/language-blocks/ja.json +626 -0
- package/data/language-blocks/ko.json +626 -0
- package/data/language-blocks/zh-cn.json +626 -0
- package/data/language-blocks/zh-tw.json +626 -0
- package/data/model-modifiers/en.json +104 -0
- package/data/model-modifiers/fr.json +104 -0
- package/data/model-modifiers/ja.json +104 -0
- package/data/model-modifiers/ko.json +104 -0
- package/data/model-modifiers/zh-cn.json +104 -0
- package/data/model-modifiers/zh-tw.json +104 -0
- package/data/template/en.json +866 -0
- package/data/template/fr.json +866 -0
- package/data/template/ja.json +866 -0
- package/data/template/ko.json +866 -0
- package/data/template/zh-cn.json +866 -0
- package/data/template/zh-tw.json +866 -0
- package/out/helpers.d.ts +15 -0
- package/out/helpers.js +246 -0
- package/out/ideFeatures/nameCasing.d.ts +13 -0
- package/out/ideFeatures/nameCasing.js +173 -0
- package/out/index.d.ts +5 -0
- package/out/index.js +24 -0
- package/out/languageService.d.ts +9 -0
- package/out/languageService.js +243 -0
- package/out/plugins/data.d.ts +4 -0
- package/out/plugins/data.js +90 -0
- package/out/plugins/vue-autoinsert-dotvalue.d.ts +7 -0
- package/out/plugins/vue-autoinsert-dotvalue.js +157 -0
- package/out/plugins/vue-autoinsert-parentheses.d.ts +3 -0
- package/out/plugins/vue-autoinsert-parentheses.js +67 -0
- package/out/plugins/vue-autoinsert-space.d.ts +3 -0
- package/out/plugins/vue-autoinsert-space.js +30 -0
- package/out/plugins/vue-codelens-references.d.ts +2 -0
- package/out/plugins/vue-codelens-references.js +53 -0
- package/out/plugins/vue-template.d.ts +10 -0
- package/out/plugins/vue-template.js +538 -0
- package/out/plugins/vue-twoslash-queries.d.ts +3 -0
- package/out/plugins/vue-twoslash-queries.js +59 -0
- package/out/plugins/vue-visualize-hidden-callback-param.d.ts +3 -0
- package/out/plugins/vue-visualize-hidden-callback-param.js +42 -0
- package/out/plugins/vue.d.ts +8 -0
- package/out/plugins/vue.js +137 -0
- package/out/types.d.ts +10 -0
- package/out/types.js +31 -0
- package/package.json +44 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const language_core_1 = require("@vue/language-core");
|
|
4
|
+
function default_1() {
|
|
5
|
+
return (context) => {
|
|
6
|
+
if (!context)
|
|
7
|
+
return {};
|
|
8
|
+
return {
|
|
9
|
+
provideReferencesCodeLensRanges(document) {
|
|
10
|
+
return worker(document.uri, async () => {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const [_, map] of context.documents.getMapsBySourceFileUri(document.uri)?.maps ?? []) {
|
|
13
|
+
for (const mapping of map.map.mappings) {
|
|
14
|
+
if (!mapping.data.referencesCodeLens)
|
|
15
|
+
continue;
|
|
16
|
+
result.push({
|
|
17
|
+
start: document.positionAt(mapping.sourceRange[0]),
|
|
18
|
+
end: document.positionAt(mapping.sourceRange[1]),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
async resolveReferencesCodeLensLocations(document, range, references) {
|
|
26
|
+
await worker(document.uri, async (vueFile) => {
|
|
27
|
+
const document = context.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName);
|
|
28
|
+
const offset = document.offsetAt(range.start);
|
|
29
|
+
const blocks = [
|
|
30
|
+
vueFile.sfc.script,
|
|
31
|
+
vueFile.sfc.scriptSetup,
|
|
32
|
+
vueFile.sfc.template,
|
|
33
|
+
...vueFile.sfc.styles,
|
|
34
|
+
...vueFile.sfc.customBlocks,
|
|
35
|
+
];
|
|
36
|
+
const sourceBlock = blocks.find(block => block && offset >= block.startTagEnd && offset <= block.endTagStart);
|
|
37
|
+
references = references.filter(reference => reference.uri !== document.uri // different file
|
|
38
|
+
|| sourceBlock !== blocks.find(block => block && document.offsetAt(reference.range.start) >= block.startTagEnd && document.offsetAt(reference.range.end) <= block.endTagStart) // different block
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
return references;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function worker(uri, callback) {
|
|
45
|
+
const [virtualFile] = context.documents.getVirtualFileByUri(uri);
|
|
46
|
+
if (!(virtualFile instanceof language_core_1.VueFile))
|
|
47
|
+
return;
|
|
48
|
+
return callback(virtualFile);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
exports.default = default_1;
|
|
53
|
+
//# sourceMappingURL=vue-codelens-references.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import createHtmlPlugin from 'volar-service-html';
|
|
2
|
+
import * as html from 'vscode-html-languageservice';
|
|
3
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
4
|
+
import { VueCompilerOptions } from '../types';
|
|
5
|
+
export default function useVueTemplateLanguagePlugin<T extends ReturnType<typeof createHtmlPlugin>>(options: {
|
|
6
|
+
getScanner(document: TextDocument, t: ReturnType<T>): html.Scanner | undefined;
|
|
7
|
+
templateLanguagePlugin: T;
|
|
8
|
+
isSupportedDocument: (document: TextDocument) => boolean;
|
|
9
|
+
vueCompilerOptions: VueCompilerOptions;
|
|
10
|
+
}): T;
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vue = require("@vue/language-core");
|
|
4
|
+
const shared_1 = require("@vue/shared");
|
|
5
|
+
const html = require("vscode-html-languageservice");
|
|
6
|
+
const vscode = require("vscode-languageserver-protocol");
|
|
7
|
+
const helpers_1 = require("../helpers");
|
|
8
|
+
const nameCasing_1 = require("../ideFeatures/nameCasing");
|
|
9
|
+
const types_1 = require("../types");
|
|
10
|
+
const data_1 = require("./data");
|
|
11
|
+
let builtInData;
|
|
12
|
+
let modelData;
|
|
13
|
+
function useVueTemplateLanguagePlugin(options) {
|
|
14
|
+
const plugin = (_context, modules) => {
|
|
15
|
+
const templatePlugin = options.templateLanguagePlugin(_context);
|
|
16
|
+
const triggerCharacters = [
|
|
17
|
+
...templatePlugin.triggerCharacters ?? [],
|
|
18
|
+
'@', // vue event shorthand
|
|
19
|
+
];
|
|
20
|
+
if (!_context?.typescript || !modules?.typescript)
|
|
21
|
+
return { triggerCharacters };
|
|
22
|
+
builtInData ??= (0, data_1.loadTemplateData)(_context.env.locale ?? 'en');
|
|
23
|
+
modelData ??= (0, data_1.loadModelModifiersData)(_context.env.locale ?? 'en');
|
|
24
|
+
// https://vuejs.org/api/built-in-directives.html#v-on
|
|
25
|
+
// https://vuejs.org/api/built-in-directives.html#v-bind
|
|
26
|
+
const eventModifiers = {};
|
|
27
|
+
const propModifiers = {};
|
|
28
|
+
const vOn = builtInData.globalAttributes?.find(x => x.name === 'v-on');
|
|
29
|
+
const vBind = builtInData.globalAttributes?.find(x => x.name === 'v-bind');
|
|
30
|
+
if (vOn) {
|
|
31
|
+
const markdown = (typeof vOn.description === 'string' ? vOn.description : vOn.description?.value) ?? '';
|
|
32
|
+
const modifiers = markdown
|
|
33
|
+
.split('\n- ')[4]
|
|
34
|
+
.split('\n').slice(2, -1);
|
|
35
|
+
for (let text of modifiers) {
|
|
36
|
+
text = text.substring(' - `.'.length);
|
|
37
|
+
const [name, disc] = text.split('` - ');
|
|
38
|
+
eventModifiers[name] = disc;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (vBind) {
|
|
42
|
+
const markdown = (typeof vBind.description === 'string' ? vBind.description : vBind.description?.value) ?? '';
|
|
43
|
+
const modifiers = markdown
|
|
44
|
+
.split('\n- ')[4]
|
|
45
|
+
.split('\n').slice(2, -1);
|
|
46
|
+
for (let text of modifiers) {
|
|
47
|
+
text = text.substring(' - `.'.length);
|
|
48
|
+
const [name, disc] = text.split('` - ');
|
|
49
|
+
propModifiers[name] = disc;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const ts = modules.typescript;
|
|
53
|
+
const _ts = _context.typescript;
|
|
54
|
+
return {
|
|
55
|
+
...templatePlugin,
|
|
56
|
+
triggerCharacters,
|
|
57
|
+
async provideCompletionItems(document, position, context, token) {
|
|
58
|
+
if (!options.isSupportedDocument(document))
|
|
59
|
+
return;
|
|
60
|
+
for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
61
|
+
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
62
|
+
if (virtualFile && virtualFile instanceof vue.VueFile) {
|
|
63
|
+
await provideHtmlData(map, virtualFile);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const htmlComplete = await templatePlugin.provideCompletionItems?.(document, position, context, token);
|
|
67
|
+
if (!htmlComplete)
|
|
68
|
+
return;
|
|
69
|
+
for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
70
|
+
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
71
|
+
if (virtualFile && virtualFile instanceof vue.VueFile) {
|
|
72
|
+
afterHtmlCompletion(htmlComplete, map, virtualFile);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return htmlComplete;
|
|
76
|
+
},
|
|
77
|
+
async provideInlayHints(document) {
|
|
78
|
+
if (!options.isSupportedDocument(document))
|
|
79
|
+
return;
|
|
80
|
+
const enabled = await _context.env.getConfiguration?.('vue.inlayHints.missingProps') ?? false;
|
|
81
|
+
if (!enabled)
|
|
82
|
+
return;
|
|
83
|
+
const result = [];
|
|
84
|
+
for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
85
|
+
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
86
|
+
const scanner = options.getScanner(document, templatePlugin);
|
|
87
|
+
if (virtualFile && virtualFile instanceof vue.VueFile && scanner) {
|
|
88
|
+
// visualize missing required props
|
|
89
|
+
const casing = await (0, nameCasing_1.getNameCasing)(ts, _context, map.sourceFileDocument.uri);
|
|
90
|
+
const nativeTags = (0, helpers_1.checkNativeTags)(ts, _ts.languageService, virtualFile.fileName);
|
|
91
|
+
const components = (0, helpers_1.checkComponentNames)(ts, _ts.languageService, virtualFile, nativeTags);
|
|
92
|
+
const componentProps = {};
|
|
93
|
+
let token;
|
|
94
|
+
let current;
|
|
95
|
+
while ((token = scanner.scan()) !== html.TokenType.EOS) {
|
|
96
|
+
if (token === html.TokenType.StartTag) {
|
|
97
|
+
const tagName = scanner.getTokenText();
|
|
98
|
+
const component = tagName.indexOf('.') >= 0
|
|
99
|
+
? components.find(component => component === tagName.split('.')[0])
|
|
100
|
+
: components.find(component => component === tagName || (0, shared_1.hyphenate)(component) === tagName);
|
|
101
|
+
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
|
|
102
|
+
if (checkTag) {
|
|
103
|
+
componentProps[checkTag] ??= (0, helpers_1.checkPropsOfTag)(ts, _ts.languageService, virtualFile, checkTag, nativeTags, true);
|
|
104
|
+
current = {
|
|
105
|
+
unburnedRequiredProps: [...componentProps[checkTag]],
|
|
106
|
+
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
|
|
107
|
+
insertOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (token === html.TokenType.AttributeName) {
|
|
112
|
+
if (current) {
|
|
113
|
+
let attrText = scanner.getTokenText();
|
|
114
|
+
if (attrText === 'v-bind') {
|
|
115
|
+
current.unburnedRequiredProps = [];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// remove modifiers
|
|
119
|
+
if (attrText.indexOf('.') >= 0) {
|
|
120
|
+
attrText = attrText.split('.')[0];
|
|
121
|
+
}
|
|
122
|
+
// normalize
|
|
123
|
+
if (attrText.startsWith('v-bind:')) {
|
|
124
|
+
attrText = attrText.substring('v-bind:'.length);
|
|
125
|
+
}
|
|
126
|
+
else if (attrText.startsWith(':')) {
|
|
127
|
+
attrText = attrText.substring(':'.length);
|
|
128
|
+
}
|
|
129
|
+
else if (attrText.startsWith('v-model:')) {
|
|
130
|
+
attrText = attrText.substring('v-model:'.length);
|
|
131
|
+
}
|
|
132
|
+
else if (attrText === 'v-model') {
|
|
133
|
+
attrText = options.vueCompilerOptions.target >= 3 ? 'modelValue' : 'value'; // TODO: support for experimentalModelPropName?
|
|
134
|
+
}
|
|
135
|
+
else if (attrText.startsWith('@')) {
|
|
136
|
+
attrText = 'on-' + (0, shared_1.hyphenate)(attrText.substring('@'.length));
|
|
137
|
+
}
|
|
138
|
+
current.unburnedRequiredProps = current.unburnedRequiredProps.filter(propName => {
|
|
139
|
+
return attrText !== propName
|
|
140
|
+
&& attrText !== (0, shared_1.hyphenate)(propName);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (token === html.TokenType.StartTagSelfClose || token === html.TokenType.StartTagClose) {
|
|
146
|
+
if (current) {
|
|
147
|
+
for (const requiredProp of current.unburnedRequiredProps) {
|
|
148
|
+
result.push({
|
|
149
|
+
label: `${requiredProp}!`,
|
|
150
|
+
paddingLeft: true,
|
|
151
|
+
position: document.positionAt(current.labelOffset),
|
|
152
|
+
kind: vscode.InlayHintKind.Parameter,
|
|
153
|
+
textEdits: [{
|
|
154
|
+
range: {
|
|
155
|
+
start: document.positionAt(current.insertOffset),
|
|
156
|
+
end: document.positionAt(current.insertOffset),
|
|
157
|
+
},
|
|
158
|
+
newText: ` :${casing.attr === types_1.AttrNameCasing.Kebab ? (0, shared_1.hyphenate)(requiredProp) : requiredProp}=`,
|
|
159
|
+
}],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
current = undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (token === html.TokenType.AttributeName || token === html.TokenType.AttributeValue) {
|
|
166
|
+
if (current) {
|
|
167
|
+
current.insertOffset = scanner.getTokenOffset() + scanner.getTokenLength();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
},
|
|
175
|
+
provideHover(document, position, token) {
|
|
176
|
+
if (!options.isSupportedDocument(document))
|
|
177
|
+
return;
|
|
178
|
+
if (_context.documents.isVirtualFileUri(document.uri))
|
|
179
|
+
templatePlugin.updateCustomData([]);
|
|
180
|
+
return templatePlugin.provideHover?.(document, position, token);
|
|
181
|
+
},
|
|
182
|
+
async provideDiagnostics(document, token) {
|
|
183
|
+
if (!options.isSupportedDocument(document))
|
|
184
|
+
return;
|
|
185
|
+
const originalResult = await templatePlugin.provideDiagnostics?.(document, token);
|
|
186
|
+
for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
187
|
+
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
188
|
+
if (!virtualFile || !(virtualFile instanceof vue.VueFile))
|
|
189
|
+
continue;
|
|
190
|
+
const templateErrors = [];
|
|
191
|
+
const sfcVueTemplateCompiled = virtualFile.compiledSFCTemplate;
|
|
192
|
+
if (sfcVueTemplateCompiled) {
|
|
193
|
+
for (const error of sfcVueTemplateCompiled.errors) {
|
|
194
|
+
onCompilerError(error, vscode.DiagnosticSeverity.Error);
|
|
195
|
+
}
|
|
196
|
+
for (const warning of sfcVueTemplateCompiled.warnings) {
|
|
197
|
+
onCompilerError(warning, vscode.DiagnosticSeverity.Warning);
|
|
198
|
+
}
|
|
199
|
+
function onCompilerError(error, severity) {
|
|
200
|
+
const templateHtmlRange = {
|
|
201
|
+
start: error.loc?.start.offset ?? 0,
|
|
202
|
+
end: error.loc?.end.offset ?? 0,
|
|
203
|
+
};
|
|
204
|
+
let errorMessage = error.message;
|
|
205
|
+
templateErrors.push({
|
|
206
|
+
range: {
|
|
207
|
+
start: document.positionAt(templateHtmlRange.start),
|
|
208
|
+
end: document.positionAt(templateHtmlRange.end),
|
|
209
|
+
},
|
|
210
|
+
severity,
|
|
211
|
+
code: error.code,
|
|
212
|
+
source: 'vue',
|
|
213
|
+
message: errorMessage,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [
|
|
218
|
+
...originalResult ?? [],
|
|
219
|
+
...templateErrors,
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
async provideDocumentSemanticTokens(document, range, legend, token) {
|
|
224
|
+
if (!options.isSupportedDocument(document))
|
|
225
|
+
return;
|
|
226
|
+
const result = await templatePlugin.provideDocumentSemanticTokens?.(document, range, legend, token) ?? [];
|
|
227
|
+
const scanner = options.getScanner(document, templatePlugin);
|
|
228
|
+
if (!scanner)
|
|
229
|
+
return;
|
|
230
|
+
for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
231
|
+
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
232
|
+
if (!virtualFile || !(virtualFile instanceof vue.VueFile))
|
|
233
|
+
continue;
|
|
234
|
+
const nativeTags = (0, helpers_1.checkNativeTags)(ts, _ts.languageService, virtualFile.fileName);
|
|
235
|
+
const templateScriptData = (0, helpers_1.checkComponentNames)(ts, _ts.languageService, virtualFile, nativeTags);
|
|
236
|
+
const components = new Set([
|
|
237
|
+
...templateScriptData,
|
|
238
|
+
...templateScriptData.map(shared_1.hyphenate),
|
|
239
|
+
]);
|
|
240
|
+
const offsetRange = {
|
|
241
|
+
start: document.offsetAt(range.start),
|
|
242
|
+
end: document.offsetAt(range.end),
|
|
243
|
+
};
|
|
244
|
+
let token = scanner.scan();
|
|
245
|
+
while (token !== html.TokenType.EOS) {
|
|
246
|
+
const tokenOffset = scanner.getTokenOffset();
|
|
247
|
+
// TODO: fix source map perf and break in while condition
|
|
248
|
+
if (tokenOffset > offsetRange.end)
|
|
249
|
+
break;
|
|
250
|
+
if (tokenOffset >= offsetRange.start && (token === html.TokenType.StartTag || token === html.TokenType.EndTag)) {
|
|
251
|
+
const tokenText = scanner.getTokenText();
|
|
252
|
+
if (components.has(tokenText) || tokenText.indexOf('.') >= 0) {
|
|
253
|
+
const tokenLength = scanner.getTokenLength();
|
|
254
|
+
const tokenPosition = document.positionAt(tokenOffset);
|
|
255
|
+
if (components.has(tokenText)) {
|
|
256
|
+
let tokenType = legend.tokenTypes.indexOf('component');
|
|
257
|
+
if (tokenType === -1) {
|
|
258
|
+
tokenType = legend.tokenTypes.indexOf('class');
|
|
259
|
+
}
|
|
260
|
+
result.push([tokenPosition.line, tokenPosition.character, tokenLength, tokenType, 0]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
token = scanner.scan();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
async function provideHtmlData(map, vueSourceFile) {
|
|
271
|
+
const casing = await (0, nameCasing_1.getNameCasing)(ts, _context, map.sourceFileDocument.uri);
|
|
272
|
+
if (builtInData.tags) {
|
|
273
|
+
for (const tag of builtInData.tags) {
|
|
274
|
+
if (tag.name === 'slot')
|
|
275
|
+
continue;
|
|
276
|
+
if (tag.name === 'component')
|
|
277
|
+
continue;
|
|
278
|
+
if (tag.name === 'template')
|
|
279
|
+
continue;
|
|
280
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
281
|
+
tag.name = (0, shared_1.hyphenate)(tag.name);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
tag.name = (0, shared_1.camelize)((0, shared_1.capitalize)(tag.name));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const nativeTags = (0, helpers_1.checkNativeTags)(ts, _ts.languageService, vueSourceFile.fileName);
|
|
289
|
+
templatePlugin.updateCustomData([
|
|
290
|
+
html.newHTMLDataProvider('vue-template-built-in', builtInData),
|
|
291
|
+
{
|
|
292
|
+
getId: () => 'vue-template',
|
|
293
|
+
isApplicable: () => true,
|
|
294
|
+
provideTags: () => {
|
|
295
|
+
const components = (0, helpers_1.checkComponentNames)(ts, _ts.languageService, vueSourceFile, nativeTags)
|
|
296
|
+
.filter(name => name !== 'Transition'
|
|
297
|
+
&& name !== 'TransitionGroup'
|
|
298
|
+
&& name !== 'KeepAlive'
|
|
299
|
+
&& name !== 'Suspense'
|
|
300
|
+
&& name !== 'Teleport');
|
|
301
|
+
const scriptSetupRanges = vueSourceFile.sfc.scriptSetupAst ? vue.parseScriptSetupRanges(ts, vueSourceFile.sfc.scriptSetupAst, options.vueCompilerOptions) : undefined;
|
|
302
|
+
const names = new Set();
|
|
303
|
+
const tags = [];
|
|
304
|
+
for (const tag of components) {
|
|
305
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
306
|
+
names.add((0, shared_1.hyphenate)(tag));
|
|
307
|
+
}
|
|
308
|
+
else if (casing.tag === types_1.TagNameCasing.Pascal) {
|
|
309
|
+
names.add(tag);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
for (const binding of scriptSetupRanges?.bindings ?? []) {
|
|
313
|
+
const name = vueSourceFile.sfc.scriptSetup.content.substring(binding.start, binding.end);
|
|
314
|
+
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
315
|
+
names.add((0, shared_1.hyphenate)(name));
|
|
316
|
+
}
|
|
317
|
+
else if (casing.tag === types_1.TagNameCasing.Pascal) {
|
|
318
|
+
names.add(name);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const name of names) {
|
|
322
|
+
tags.push({
|
|
323
|
+
name: name,
|
|
324
|
+
attributes: [],
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return tags;
|
|
328
|
+
},
|
|
329
|
+
provideAttributes: (tag) => {
|
|
330
|
+
const attrs = (0, helpers_1.getElementAttrs)(ts, _ts.languageService, vueSourceFile.fileName, tag);
|
|
331
|
+
const props = new Set((0, helpers_1.checkPropsOfTag)(ts, _ts.languageService, vueSourceFile, tag, nativeTags));
|
|
332
|
+
const events = (0, helpers_1.checkEventsOfTag)(ts, _ts.languageService, vueSourceFile, tag, nativeTags);
|
|
333
|
+
const attributes = [];
|
|
334
|
+
for (const prop of [...props, ...attrs]) {
|
|
335
|
+
const isGlobal = !props.has(prop);
|
|
336
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? prop : (0, shared_1.hyphenate)(prop);
|
|
337
|
+
if ((0, shared_1.hyphenate)(name).startsWith('on-')) {
|
|
338
|
+
const propNameBase = name.startsWith('on-')
|
|
339
|
+
? name.slice('on-'.length)
|
|
340
|
+
: (name['on'.length].toLowerCase() + name.slice('onX'.length));
|
|
341
|
+
const propKey = createInternalItemId('componentEvent', [isGlobal ? '*' : tag, propNameBase]);
|
|
342
|
+
attributes.push({
|
|
343
|
+
name: 'v-on:' + propNameBase,
|
|
344
|
+
description: propKey,
|
|
345
|
+
}, {
|
|
346
|
+
name: '@' + propNameBase,
|
|
347
|
+
description: propKey,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
{
|
|
351
|
+
const propName = name;
|
|
352
|
+
const propKey = createInternalItemId('componentProp', [isGlobal ? '*' : tag, propName]);
|
|
353
|
+
attributes.push({
|
|
354
|
+
name: propName,
|
|
355
|
+
description: propKey,
|
|
356
|
+
}, {
|
|
357
|
+
name: ':' + propName,
|
|
358
|
+
description: propKey,
|
|
359
|
+
}, {
|
|
360
|
+
name: 'v-bind:' + propName,
|
|
361
|
+
description: propKey,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const event of events) {
|
|
366
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? event : (0, shared_1.hyphenate)(event);
|
|
367
|
+
const propKey = createInternalItemId('componentEvent', [tag, name]);
|
|
368
|
+
attributes.push({
|
|
369
|
+
name: 'v-on:' + name,
|
|
370
|
+
description: propKey,
|
|
371
|
+
});
|
|
372
|
+
attributes.push({
|
|
373
|
+
name: '@' + name,
|
|
374
|
+
description: propKey,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const models = [];
|
|
378
|
+
for (const prop of [...props, ...attrs]) {
|
|
379
|
+
if (prop.startsWith('onUpdate:')) {
|
|
380
|
+
const isGlobal = !props.has(prop);
|
|
381
|
+
models.push([isGlobal, prop.substring('onUpdate:'.length)]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
for (const event of events) {
|
|
385
|
+
if (event.startsWith('update:')) {
|
|
386
|
+
models.push([false, event.substring('update:'.length)]);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const [isGlobal, model] of models) {
|
|
390
|
+
const name = casing.attr === types_1.AttrNameCasing.Camel ? model : (0, shared_1.hyphenate)(model);
|
|
391
|
+
const propKey = createInternalItemId('componentProp', [isGlobal ? '*' : tag, name]);
|
|
392
|
+
attributes.push({
|
|
393
|
+
name: 'v-model:' + name,
|
|
394
|
+
description: propKey,
|
|
395
|
+
});
|
|
396
|
+
if (model === 'modelValue') {
|
|
397
|
+
attributes.push({
|
|
398
|
+
name: 'v-model',
|
|
399
|
+
description: propKey,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return attributes;
|
|
404
|
+
},
|
|
405
|
+
provideValues: () => [],
|
|
406
|
+
},
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
function afterHtmlCompletion(completionList, map, vueSourceFile) {
|
|
410
|
+
const replacement = getReplacement(completionList, map.sourceFileDocument);
|
|
411
|
+
const nativeTags = (0, helpers_1.checkNativeTags)(ts, _ts.languageService, vueSourceFile.fileName);
|
|
412
|
+
const componentNames = new Set((0, helpers_1.checkComponentNames)(ts, _ts.languageService, vueSourceFile, nativeTags).map(shared_1.hyphenate));
|
|
413
|
+
if (replacement) {
|
|
414
|
+
const isEvent = replacement.text.startsWith('v-on:') || replacement.text.startsWith('@');
|
|
415
|
+
const isProp = replacement.text.startsWith('v-bind:') || replacement.text.startsWith(':');
|
|
416
|
+
const isModel = replacement.text.startsWith('v-model:') || replacement.text.split('.')[0] === 'v-model';
|
|
417
|
+
const hasModifier = replacement.text.includes('.');
|
|
418
|
+
const validModifiers = isEvent ? eventModifiers
|
|
419
|
+
: isProp ? propModifiers
|
|
420
|
+
: undefined;
|
|
421
|
+
const modifiers = replacement.text.split('.').slice(1);
|
|
422
|
+
const textWithoutModifier = replacement.text.split('.')[0];
|
|
423
|
+
if (validModifiers && hasModifier) {
|
|
424
|
+
for (const modifier in validModifiers) {
|
|
425
|
+
if (modifiers.includes(modifier))
|
|
426
|
+
continue;
|
|
427
|
+
const modifierDes = validModifiers[modifier];
|
|
428
|
+
const insertText = textWithoutModifier + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier;
|
|
429
|
+
const newItem = {
|
|
430
|
+
label: modifier,
|
|
431
|
+
filterText: insertText,
|
|
432
|
+
documentation: {
|
|
433
|
+
kind: 'markdown',
|
|
434
|
+
value: modifierDes,
|
|
435
|
+
},
|
|
436
|
+
textEdit: {
|
|
437
|
+
range: replacement.textEdit.range,
|
|
438
|
+
newText: insertText,
|
|
439
|
+
},
|
|
440
|
+
kind: vscode.CompletionItemKind.EnumMember,
|
|
441
|
+
};
|
|
442
|
+
completionList.items.push(newItem);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (hasModifier && isModel) {
|
|
446
|
+
for (const modifier of modelData.globalAttributes ?? []) {
|
|
447
|
+
if (modifiers.includes(modifier.name))
|
|
448
|
+
continue;
|
|
449
|
+
const insertText = textWithoutModifier + modifiers.slice(0, -1).map(m => '.' + m).join('') + '.' + modifier.name;
|
|
450
|
+
const newItem = {
|
|
451
|
+
label: modifier.name,
|
|
452
|
+
filterText: insertText,
|
|
453
|
+
documentation: {
|
|
454
|
+
kind: 'markdown',
|
|
455
|
+
value: (typeof modifier.description === 'object' ? modifier.description.value : modifier.description)
|
|
456
|
+
+ '\n\n' + modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | '),
|
|
457
|
+
},
|
|
458
|
+
textEdit: {
|
|
459
|
+
range: replacement.textEdit.range,
|
|
460
|
+
newText: insertText,
|
|
461
|
+
},
|
|
462
|
+
kind: vscode.CompletionItemKind.EnumMember,
|
|
463
|
+
};
|
|
464
|
+
completionList.items.push(newItem);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (const item of completionList.items) {
|
|
469
|
+
const itemIdKey = typeof item.documentation === 'string' ? item.documentation : item.documentation?.value;
|
|
470
|
+
const itemId = itemIdKey ? readInternalItemId(itemIdKey) : undefined;
|
|
471
|
+
if (itemId) {
|
|
472
|
+
item.documentation = undefined;
|
|
473
|
+
}
|
|
474
|
+
if (itemIdKey && itemId) {
|
|
475
|
+
if (itemId.type === 'componentProp' || itemId.type === 'componentEvent') {
|
|
476
|
+
const [componentName] = itemId.args;
|
|
477
|
+
if (componentName !== '*') {
|
|
478
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
479
|
+
}
|
|
480
|
+
if (itemId.type === 'componentProp') {
|
|
481
|
+
if (componentName !== '*') {
|
|
482
|
+
item.kind = vscode.CompletionItemKind.Field;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
item.kind = componentName !== '*' ? vscode.CompletionItemKind.Function : vscode.CompletionItemKind.Event;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else if (item.label === 'v-if'
|
|
490
|
+
|| item.label === 'v-else-if'
|
|
491
|
+
|| item.label === 'v-else'
|
|
492
|
+
|| item.label === 'v-for') {
|
|
493
|
+
item.kind = vscode.CompletionItemKind.Method;
|
|
494
|
+
item.sortText = '\u0003' + (item.sortText ?? item.label);
|
|
495
|
+
}
|
|
496
|
+
else if (item.label.startsWith('v-')) {
|
|
497
|
+
item.kind = vscode.CompletionItemKind.Function;
|
|
498
|
+
item.sortText = '\u0002' + (item.sortText ?? item.label);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
item.sortText = '\u0001' + (item.sortText ?? item.label);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (item.kind === vscode.CompletionItemKind.Property && componentNames.has((0, shared_1.hyphenate)(item.label))) {
|
|
505
|
+
item.kind = vscode.CompletionItemKind.Variable;
|
|
506
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
templatePlugin.updateCustomData([]);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
return plugin;
|
|
513
|
+
}
|
|
514
|
+
exports.default = useVueTemplateLanguagePlugin;
|
|
515
|
+
function createInternalItemId(type, args) {
|
|
516
|
+
return '__VLS_::' + type + '::' + args.join(',');
|
|
517
|
+
}
|
|
518
|
+
function readInternalItemId(key) {
|
|
519
|
+
if (key.startsWith('__VLS_::')) {
|
|
520
|
+
const strs = key.split('::');
|
|
521
|
+
return {
|
|
522
|
+
type: strs[1],
|
|
523
|
+
args: strs[2].split(','),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function getReplacement(list, doc) {
|
|
528
|
+
for (const item of list.items) {
|
|
529
|
+
if (item.textEdit && 'range' in item.textEdit) {
|
|
530
|
+
return {
|
|
531
|
+
item: item,
|
|
532
|
+
textEdit: item.textEdit,
|
|
533
|
+
text: doc.getText(item.textEdit.range)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
//# sourceMappingURL=vue-template.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const language_service_1 = require("@volar/language-service");
|
|
4
|
+
const vue = require("@vue/language-core");
|
|
5
|
+
const plugin = (context, modules) => {
|
|
6
|
+
if (!modules?.typescript)
|
|
7
|
+
return {};
|
|
8
|
+
if (!context?.typescript)
|
|
9
|
+
return {};
|
|
10
|
+
const ts = modules.typescript;
|
|
11
|
+
const _ts = context.typescript;
|
|
12
|
+
return {
|
|
13
|
+
provideInlayHints(document, range) {
|
|
14
|
+
return worker(document.uri, (vueFile) => {
|
|
15
|
+
const hoverOffsets = [];
|
|
16
|
+
const inlayHints = [];
|
|
17
|
+
for (const pointer of document.getText(range).matchAll(/<!--\s*\^\?\s*-->/g)) {
|
|
18
|
+
const offset = pointer.index + pointer[0].indexOf('^?') + document.offsetAt(range.start);
|
|
19
|
+
const position = document.positionAt(offset);
|
|
20
|
+
hoverOffsets.push([position, document.offsetAt({
|
|
21
|
+
line: position.line - 1,
|
|
22
|
+
character: position.character,
|
|
23
|
+
})]);
|
|
24
|
+
}
|
|
25
|
+
(0, language_service_1.forEachEmbeddedFile)(vueFile, (embedded) => {
|
|
26
|
+
if (embedded.kind === language_service_1.FileKind.TypeScriptHostFile) {
|
|
27
|
+
for (const [_, map] of context.documents.getMapsByVirtualFileName(embedded.fileName)) {
|
|
28
|
+
for (const [pointerPosition, hoverOffset] of hoverOffsets) {
|
|
29
|
+
for (const [tsOffset, mapping] of map.map.toGeneratedOffsets(hoverOffset)) {
|
|
30
|
+
if (mapping.data.hover) {
|
|
31
|
+
const quickInfo = _ts.languageService.getQuickInfoAtPosition(embedded.fileName, tsOffset);
|
|
32
|
+
if (quickInfo) {
|
|
33
|
+
inlayHints.push({
|
|
34
|
+
position: { line: pointerPosition.line, character: pointerPosition.character + 2 },
|
|
35
|
+
label: ts.displayPartsToString(quickInfo.displayParts),
|
|
36
|
+
paddingLeft: true,
|
|
37
|
+
paddingRight: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return inlayHints;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
function worker(uri, callback) {
|
|
52
|
+
const [virtualFile] = context.documents.getVirtualFileByUri(uri);
|
|
53
|
+
if (!(virtualFile instanceof vue.VueFile))
|
|
54
|
+
return;
|
|
55
|
+
return callback(virtualFile);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
exports.default = () => plugin;
|
|
59
|
+
//# sourceMappingURL=vue-twoslash-queries.js.map
|