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