@vue/language-service 1.8.5 → 1.8.7
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/data/template/ko.json +15 -15
- package/data/template/zh-cn.json +26 -26
- package/out/helpers.d.ts +4 -3
- package/out/helpers.js +51 -44
- package/out/ideFeatures/nameCasing.js +4 -4
- package/out/languageService.js +32 -74
- package/out/plugins/vue-extract-file.d.ts +7 -0
- package/out/plugins/vue-extract-file.js +294 -0
- package/out/plugins/vue-template.js +47 -34
- package/out/plugins/vue-toggle-v-bind-codeaction.d.ts +2 -0
- package/out/plugins/vue-toggle-v-bind-codeaction.js +125 -0
- package/package.json +15 -15
|
@@ -16,7 +16,7 @@ async function convertTagName(ts, context, uri, casing, vueCompilerOptions) {
|
|
|
16
16
|
const template = desc.template;
|
|
17
17
|
const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName);
|
|
18
18
|
const edits = [];
|
|
19
|
-
const components = (0, helpers_1.
|
|
19
|
+
const components = (0, helpers_1.getComponentNames)(ts, languageService, rootFile, vueCompilerOptions);
|
|
20
20
|
const tags = (0, helpers_1.getTemplateTagsAndAttrs)(rootFile);
|
|
21
21
|
for (const [tagName, { offsets }] of tags) {
|
|
22
22
|
const componentName = components.find(component => component === tagName || (0, shared_1.hyphenate)(component) === tagName);
|
|
@@ -48,12 +48,12 @@ async function convertAttrName(ts, context, uri, casing, vueCompilerOptions) {
|
|
|
48
48
|
const template = desc.template;
|
|
49
49
|
const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName);
|
|
50
50
|
const edits = [];
|
|
51
|
-
const components = (0, helpers_1.
|
|
51
|
+
const components = (0, helpers_1.getComponentNames)(ts, languageService, rootFile, vueCompilerOptions);
|
|
52
52
|
const tags = (0, helpers_1.getTemplateTagsAndAttrs)(rootFile);
|
|
53
53
|
for (const [tagName, { attrs }] of tags) {
|
|
54
54
|
const componentName = components.find(component => component === tagName || (0, shared_1.hyphenate)(component) === tagName);
|
|
55
55
|
if (componentName) {
|
|
56
|
-
const props = (0, helpers_1.
|
|
56
|
+
const props = (0, helpers_1.getPropsByTag)(ts, languageService, rootFile, componentName, vueCompilerOptions);
|
|
57
57
|
for (const [attrName, { offsets }] of attrs) {
|
|
58
58
|
const propName = props.find(prop => prop === attrName || (0, shared_1.hyphenate)(prop) === attrName);
|
|
59
59
|
if (propName) {
|
|
@@ -124,7 +124,7 @@ function detect(ts, context, uri, vueCompilerOptions) {
|
|
|
124
124
|
return result;
|
|
125
125
|
}
|
|
126
126
|
function getTagNameCase(file) {
|
|
127
|
-
const components = (0, helpers_1.
|
|
127
|
+
const components = (0, helpers_1.getComponentNames)(ts, languageService, file, vueCompilerOptions);
|
|
128
128
|
const tagNames = (0, helpers_1.getTemplateTagsAndAttrs)(file);
|
|
129
129
|
const result = [];
|
|
130
130
|
let anyComponentUsed = false;
|
package/out/languageService.js
CHANGED
|
@@ -21,6 +21,8 @@ const vue_template_1 = require("./plugins/vue-template");
|
|
|
21
21
|
const vue_twoslash_queries_1 = require("./plugins/vue-twoslash-queries");
|
|
22
22
|
const vue_visualize_hidden_callback_param_1 = require("./plugins/vue-visualize-hidden-callback-param");
|
|
23
23
|
const vue_directive_comments_1 = require("./plugins/vue-directive-comments");
|
|
24
|
+
const vue_extract_file_1 = require("./plugins/vue-extract-file");
|
|
25
|
+
const vue_toggle_v_bind_codeaction_1 = require("./plugins/vue-toggle-v-bind-codeaction");
|
|
24
26
|
const types_1 = require("./types");
|
|
25
27
|
function resolveConfig(config, compilerOptions = {}, vueCompilerOptions = {}, ts = require('typescript'), codegenStack = false) {
|
|
26
28
|
const resolvedVueCompilerOptions = vue.resolveVueCompilerOptions(vueCompilerOptions);
|
|
@@ -30,23 +32,16 @@ function resolveConfig(config, compilerOptions = {}, vueCompilerOptions = {}, ts
|
|
|
30
32
|
return config;
|
|
31
33
|
}
|
|
32
34
|
exports.resolveConfig = resolveConfig;
|
|
33
|
-
const unicodeReg = /\\u/g;
|
|
34
35
|
function resolvePlugins(services, vueCompilerOptions) {
|
|
35
36
|
const originalTsPlugin = services?.typescript ?? (0, volar_service_typescript_1.default)();
|
|
36
37
|
services ??= {};
|
|
37
|
-
services.typescript = (
|
|
38
|
-
const base = typeof originalTsPlugin === 'function' ? originalTsPlugin(
|
|
39
|
-
if (!
|
|
38
|
+
services.typescript = (ctx, modules) => {
|
|
39
|
+
const base = typeof originalTsPlugin === 'function' ? originalTsPlugin(ctx, modules) : originalTsPlugin;
|
|
40
|
+
if (!ctx || !modules?.typescript)
|
|
40
41
|
return base;
|
|
41
42
|
const ts = modules.typescript;
|
|
42
|
-
const transformedItem = new WeakSet();
|
|
43
43
|
return {
|
|
44
44
|
...base,
|
|
45
|
-
transformCompletionItem(item) {
|
|
46
|
-
if (transformedItem.has(item)) {
|
|
47
|
-
return item;
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
45
|
async provideCompletionItems(document, position, context, item) {
|
|
51
46
|
const result = await base.provideCompletionItems?.(document, position, context, item);
|
|
52
47
|
if (result) {
|
|
@@ -55,18 +50,18 @@ function resolvePlugins(services, vueCompilerOptions) {
|
|
|
55
50
|
&& (!item.labelDetails?.description || item.labelDetails.description.indexOf('__VLS_') === -1));
|
|
56
51
|
// handle component auto-import patch
|
|
57
52
|
let casing;
|
|
58
|
-
for (const [_, map] of
|
|
59
|
-
const virtualFile =
|
|
53
|
+
for (const [_, map] of ctx.documents.getMapsByVirtualFileUri(document.uri)) {
|
|
54
|
+
const virtualFile = ctx.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
60
55
|
if (virtualFile instanceof vue.VueFile) {
|
|
61
56
|
const isAutoImport = !!map.toSourcePosition(position, data => typeof data.completion === 'object' && !!data.completion.autoImportOnly);
|
|
62
57
|
if (isAutoImport) {
|
|
63
|
-
const source =
|
|
58
|
+
const source = ctx.documents.getVirtualFileByUri(document.uri)[1];
|
|
64
59
|
for (const item of result.items) {
|
|
65
60
|
item.data.__isComponentAutoImport = true;
|
|
66
61
|
}
|
|
67
62
|
// fix #2458
|
|
68
63
|
if (source) {
|
|
69
|
-
casing ??= await (0, nameCasing_1.getNameCasing)(ts,
|
|
64
|
+
casing ??= await (0, nameCasing_1.getNameCasing)(ts, ctx, ctx.env.fileNameToUri(source.fileName), vueCompilerOptions);
|
|
70
65
|
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
71
66
|
for (const item of result.items) {
|
|
72
67
|
item.filterText = (0, shared_1.hyphenate)(item.filterText ?? item.label);
|
|
@@ -91,7 +86,7 @@ function resolvePlugins(services, vueCompilerOptions) {
|
|
|
91
86
|
if (itemData?.uri
|
|
92
87
|
&& item.textEdit?.newText.endsWith(suffix)
|
|
93
88
|
&& item.additionalTextEdits?.length === 1 && item.additionalTextEdits[0].newText.indexOf('import ' + item.textEdit.newText + ' from ') >= 0
|
|
94
|
-
&& (await
|
|
89
|
+
&& (await ctx.env.getConfiguration?.('vue.complete.normalizeComponentImportName') ?? true)) {
|
|
95
90
|
newName = item.textEdit.newText.slice(0, -suffix.length);
|
|
96
91
|
newName = newName[0].toUpperCase() + newName.substring(1);
|
|
97
92
|
if (newName === 'Index') {
|
|
@@ -106,9 +101,9 @@ function resolvePlugins(services, vueCompilerOptions) {
|
|
|
106
101
|
}
|
|
107
102
|
item.additionalTextEdits[0].newText = item.additionalTextEdits[0].newText.replace('import ' + item.textEdit.newText + ' from ', 'import ' + newName + ' from ');
|
|
108
103
|
item.textEdit.newText = newName;
|
|
109
|
-
const source =
|
|
104
|
+
const source = ctx.documents.getVirtualFileByUri(itemData.uri)[1];
|
|
110
105
|
if (source) {
|
|
111
|
-
const casing = await (0, nameCasing_1.getNameCasing)(ts,
|
|
106
|
+
const casing = await (0, nameCasing_1.getNameCasing)(ts, ctx, ctx.env.fileNameToUri(source.fileName), vueCompilerOptions);
|
|
112
107
|
if (casing.tag === types_1.TagNameCasing.Kebab) {
|
|
113
108
|
item.textEdit.newText = (0, shared_1.hyphenate)(item.textEdit.newText);
|
|
114
109
|
}
|
|
@@ -120,65 +115,26 @@ function resolvePlugins(services, vueCompilerOptions) {
|
|
|
120
115
|
}
|
|
121
116
|
}
|
|
122
117
|
const data = item.data;
|
|
123
|
-
if (item.data?.__isComponentAutoImport && data && item.additionalTextEdits?.length && item.textEdit) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
...exportDefault.componentsOptionNode.properties,
|
|
142
|
-
ts.factory.createShorthandPropertyAssignment(componentName),
|
|
143
|
-
],
|
|
144
|
-
};
|
|
145
|
-
const printText = printer.printNode(ts.EmitHint.Expression, newNode, sfc.scriptAst);
|
|
146
|
-
const editRange = {
|
|
147
|
-
start: textDoc.positionAt(sfc.script.startTagEnd + exportDefault.componentsOption.start),
|
|
148
|
-
end: textDoc.positionAt(sfc.script.startTagEnd + exportDefault.componentsOption.end),
|
|
149
|
-
};
|
|
150
|
-
transformed = true;
|
|
151
|
-
item.additionalTextEdits.push({
|
|
152
|
-
range: editRange,
|
|
153
|
-
newText: unescape(printText.replace(unicodeReg, '%u')),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else if (exportDefault.args && exportDefault.argsNode) {
|
|
157
|
-
const newNode = {
|
|
158
|
-
...exportDefault.argsNode,
|
|
159
|
-
properties: [
|
|
160
|
-
...exportDefault.argsNode.properties,
|
|
161
|
-
ts.factory.createShorthandPropertyAssignment(`components: { ${componentName} }`),
|
|
162
|
-
],
|
|
163
|
-
};
|
|
164
|
-
const printText = printer.printNode(ts.EmitHint.Expression, newNode, sfc.scriptAst);
|
|
165
|
-
const editRange = {
|
|
166
|
-
start: textDoc.positionAt(sfc.script.startTagEnd + exportDefault.args.start),
|
|
167
|
-
end: textDoc.positionAt(sfc.script.startTagEnd + exportDefault.args.end),
|
|
168
|
-
};
|
|
169
|
-
transformed = true;
|
|
170
|
-
item.additionalTextEdits.push({
|
|
171
|
-
range: editRange,
|
|
172
|
-
newText: unescape(printText.replace(unicodeReg, '%u')),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
118
|
+
if (item.data?.__isComponentAutoImport && data && item.additionalTextEdits?.length && item.textEdit && itemData?.uri) {
|
|
119
|
+
const fileName = ctx.env.uriToFileName(itemData.uri);
|
|
120
|
+
const langaugeService = ctx.inject('typescript/languageService');
|
|
121
|
+
const [virtualFile] = ctx.virtualFiles.getVirtualFile(fileName);
|
|
122
|
+
const ast = langaugeService.getProgram()?.getSourceFile(fileName);
|
|
123
|
+
const exportDefault = ast ? vue.scriptRanges.parseScriptRanges(ts, ast, false, true).exportDefault : undefined;
|
|
124
|
+
if (virtualFile && ast && exportDefault) {
|
|
125
|
+
const componentName = newName ?? item.textEdit.newText;
|
|
126
|
+
const optionEdit = (0, vue_extract_file_1.createAddComponentToOptionEdit)(ts, ast, componentName);
|
|
127
|
+
if (optionEdit) {
|
|
128
|
+
const textDoc = ctx.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName);
|
|
129
|
+
item.additionalTextEdits.push({
|
|
130
|
+
range: {
|
|
131
|
+
start: textDoc.positionAt(optionEdit.range.start),
|
|
132
|
+
end: textDoc.positionAt(optionEdit.range.end),
|
|
133
|
+
},
|
|
134
|
+
newText: optionEdit.newText,
|
|
135
|
+
});
|
|
177
136
|
}
|
|
178
137
|
}
|
|
179
|
-
if (transformed) {
|
|
180
|
-
transformedItem.add(item);
|
|
181
|
-
}
|
|
182
138
|
}
|
|
183
139
|
return item;
|
|
184
140
|
},
|
|
@@ -244,6 +200,8 @@ function resolvePlugins(services, vueCompilerOptions) {
|
|
|
244
200
|
services['vue/autoInsertSpaces'] ??= (0, vue_autoinsert_space_1.default)();
|
|
245
201
|
services['vue/visualizeHiddenCallbackParam'] ??= (0, vue_visualize_hidden_callback_param_1.default)();
|
|
246
202
|
services['vue/directiveComments'] ??= (0, vue_directive_comments_1.default)();
|
|
203
|
+
services['vue/extractComponent'] ??= (0, vue_extract_file_1.default)();
|
|
204
|
+
services['vue/toggleVBind'] ??= (0, vue_toggle_v_bind_codeaction_1.default)();
|
|
247
205
|
services.emmet ??= (0, volar_service_emmet_1.default)();
|
|
248
206
|
return services;
|
|
249
207
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Service } from '@volar/language-service';
|
|
2
|
+
import type * as ts from 'typescript/lib/tsserverlibrary';
|
|
3
|
+
export default function (): Service;
|
|
4
|
+
export declare function createAddComponentToOptionEdit(ts: typeof import('typescript/lib/tsserverlibrary'), ast: ts.SourceFile, componentName: string): {
|
|
5
|
+
range: import("@vue/language-core").TextRange;
|
|
6
|
+
newText: string;
|
|
7
|
+
} | undefined;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAddComponentToOptionEdit = void 0;
|
|
4
|
+
const language_core_1 = require("@vue/language-core");
|
|
5
|
+
const unicodeReg = /\\u/g;
|
|
6
|
+
function default_1() {
|
|
7
|
+
return (ctx, modules) => {
|
|
8
|
+
if (!modules?.typescript)
|
|
9
|
+
return {};
|
|
10
|
+
const ts = modules.typescript;
|
|
11
|
+
return {
|
|
12
|
+
async provideCodeActions(document, range, _context) {
|
|
13
|
+
const startOffset = document.offsetAt(range.start);
|
|
14
|
+
const endOffset = document.offsetAt(range.end);
|
|
15
|
+
if (startOffset === endOffset) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const [vueFile] = ctx.documents.getVirtualFileByUri(document.uri);
|
|
19
|
+
if (!vueFile || !(vueFile instanceof language_core_1.VueFile))
|
|
20
|
+
return;
|
|
21
|
+
const { sfc } = vueFile;
|
|
22
|
+
const script = sfc.scriptSetup ?? sfc.script;
|
|
23
|
+
const scriptAst = sfc.scriptSetupAst ?? sfc.scriptAst;
|
|
24
|
+
if (!sfc.template || !sfc.templateAst || !script || !scriptAst)
|
|
25
|
+
return;
|
|
26
|
+
const templateCodeRange = selectTemplateCode(startOffset, endOffset, sfc.template, sfc.templateAst);
|
|
27
|
+
if (!templateCodeRange)
|
|
28
|
+
return;
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
title: 'Extract into new dumb component',
|
|
32
|
+
kind: 'refactor.move.newFile.dumb',
|
|
33
|
+
data: {
|
|
34
|
+
uri: document.uri,
|
|
35
|
+
range: [startOffset, endOffset],
|
|
36
|
+
newName: 'NewComponent',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
async resolveCodeAction(codeAction) {
|
|
42
|
+
const { uri, range, newName } = codeAction.data;
|
|
43
|
+
const document = ctx.getTextDocument(uri);
|
|
44
|
+
const [startOffset, endOffset] = range;
|
|
45
|
+
const [vueFile] = ctx.documents.getVirtualFileByUri(document.uri);
|
|
46
|
+
const { sfc } = vueFile;
|
|
47
|
+
const script = sfc.scriptSetup ?? sfc.script;
|
|
48
|
+
const scriptAst = sfc.scriptSetupAst ?? sfc.scriptAst;
|
|
49
|
+
if (!sfc.template || !sfc.templateAst || !script || !scriptAst)
|
|
50
|
+
return codeAction;
|
|
51
|
+
const templateCodeRange = selectTemplateCode(startOffset, endOffset, sfc.template, sfc.templateAst);
|
|
52
|
+
if (!templateCodeRange)
|
|
53
|
+
return codeAction;
|
|
54
|
+
const languageService = ctx.inject('typescript/languageService');
|
|
55
|
+
const languageServiceHost = ctx.inject('typescript/languageServiceHost');
|
|
56
|
+
const sourceFile = languageService.getProgram().getSourceFile(vueFile.mainScriptName);
|
|
57
|
+
const sourceFileKind = languageServiceHost.getScriptKind?.(vueFile.mainScriptName);
|
|
58
|
+
const toExtract = collectExtractProps();
|
|
59
|
+
const initialIndentSetting = await ctx.env.getConfiguration('volar.format.initialIndent');
|
|
60
|
+
const newUri = document.uri.substring(0, document.uri.lastIndexOf('/') + 1) + `${newName}.vue`;
|
|
61
|
+
const lastImportNode = getLastImportNode(scriptAst);
|
|
62
|
+
let newFileTags = [];
|
|
63
|
+
newFileTags.push(constructTag('template', [], initialIndentSetting.html, sfc.template.content.substring(templateCodeRange[0], templateCodeRange[1])));
|
|
64
|
+
if (toExtract.length) {
|
|
65
|
+
newFileTags.push(constructTag('script', ['setup', 'lang="ts"'], isInitialIndentNeeded(ts, sourceFileKind, initialIndentSetting), generateNewScriptContents()));
|
|
66
|
+
}
|
|
67
|
+
if (sfc.template.startTagEnd > script.startTagEnd) {
|
|
68
|
+
newFileTags = newFileTags.reverse();
|
|
69
|
+
}
|
|
70
|
+
const currentFileEdits = [
|
|
71
|
+
{
|
|
72
|
+
range: {
|
|
73
|
+
start: document.positionAt(sfc.template.startTagEnd + templateCodeRange[0]),
|
|
74
|
+
end: document.positionAt(sfc.template.startTagEnd + templateCodeRange[1]),
|
|
75
|
+
},
|
|
76
|
+
newText: generateReplaceTemplate(),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
range: lastImportNode ? {
|
|
80
|
+
start: document.positionAt(script.startTagEnd + lastImportNode.end),
|
|
81
|
+
end: document.positionAt(script.startTagEnd + lastImportNode.end),
|
|
82
|
+
} : {
|
|
83
|
+
start: document.positionAt(script.startTagEnd),
|
|
84
|
+
end: document.positionAt(script.startTagEnd),
|
|
85
|
+
},
|
|
86
|
+
newText: `\nimport ${newName} from './${newName}.vue'`,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
if (sfc.script && sfc.scriptAst) {
|
|
90
|
+
const edit = createAddComponentToOptionEdit(ts, sfc.scriptAst, newName);
|
|
91
|
+
if (edit) {
|
|
92
|
+
currentFileEdits.push({
|
|
93
|
+
range: {
|
|
94
|
+
start: document.positionAt(sfc.script.startTagEnd + edit.range.start),
|
|
95
|
+
end: document.positionAt(sfc.script.startTagEnd + edit.range.end),
|
|
96
|
+
},
|
|
97
|
+
newText: edit.newText,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...codeAction,
|
|
103
|
+
edit: {
|
|
104
|
+
documentChanges: [
|
|
105
|
+
// editing current file
|
|
106
|
+
{
|
|
107
|
+
textDocument: {
|
|
108
|
+
uri: document.uri,
|
|
109
|
+
version: null,
|
|
110
|
+
},
|
|
111
|
+
edits: currentFileEdits,
|
|
112
|
+
},
|
|
113
|
+
// creating new file with content
|
|
114
|
+
{
|
|
115
|
+
uri: newUri,
|
|
116
|
+
kind: 'create',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
textDocument: {
|
|
120
|
+
uri: newUri,
|
|
121
|
+
version: null,
|
|
122
|
+
},
|
|
123
|
+
edits: [
|
|
124
|
+
{
|
|
125
|
+
range: {
|
|
126
|
+
start: { line: 0, character: 0 },
|
|
127
|
+
end: { line: 0, character: 0 },
|
|
128
|
+
},
|
|
129
|
+
newText: newFileTags.join('\n'),
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
function getLastImportNode(sourceFile) {
|
|
137
|
+
let lastImportNode;
|
|
138
|
+
for (const statement of sourceFile.statements) {
|
|
139
|
+
if (ts.isImportDeclaration(statement)) {
|
|
140
|
+
lastImportNode = statement;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lastImportNode;
|
|
147
|
+
}
|
|
148
|
+
function collectExtractProps() {
|
|
149
|
+
const result = new Map();
|
|
150
|
+
const checker = languageService.getProgram().getTypeChecker();
|
|
151
|
+
const maps = [...ctx.documents.getMapsByVirtualFileName(vueFile.mainScriptName)];
|
|
152
|
+
sourceFile.forEachChild(function visit(node) {
|
|
153
|
+
if (ts.isPropertyAccessExpression(node)
|
|
154
|
+
&& ts.isIdentifier(node.expression)
|
|
155
|
+
&& node.expression.text === '__VLS_ctx'
|
|
156
|
+
&& ts.isIdentifier(node.name)) {
|
|
157
|
+
const { name } = node;
|
|
158
|
+
for (const [_, map] of maps) {
|
|
159
|
+
const source = map.map.toSourceOffset(name.getEnd());
|
|
160
|
+
if (source && source[0] >= sfc.template.startTagEnd + templateCodeRange[0] && source[0] <= sfc.template.startTagEnd + templateCodeRange[1] && source[1].data.semanticTokens) {
|
|
161
|
+
if (!result.has(name.text)) {
|
|
162
|
+
const type = checker.getTypeAtLocation(node);
|
|
163
|
+
const typeString = checker.typeToString(type, node, ts.TypeFormatFlags.NoTruncation);
|
|
164
|
+
result.set(name.text, {
|
|
165
|
+
name: name.text,
|
|
166
|
+
type: typeString.includes('__VLS_') ? 'any' : typeString,
|
|
167
|
+
model: false,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const isModel = ts.isPostfixUnaryExpression(node.parent) || ts.isBinaryExpression(node.parent);
|
|
171
|
+
if (isModel) {
|
|
172
|
+
result.get(name.text).model = true;
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
node.forEachChild(visit);
|
|
179
|
+
});
|
|
180
|
+
return [...result.values()];
|
|
181
|
+
}
|
|
182
|
+
function generateNewScriptContents() {
|
|
183
|
+
const lines = [];
|
|
184
|
+
const props = [...toExtract.values()].filter(p => !p.model);
|
|
185
|
+
const models = [...toExtract.values()].filter(p => p.model);
|
|
186
|
+
if (props.length) {
|
|
187
|
+
lines.push(`defineProps<{ \n\t${props.map(p => `${p.name}: ${p.type};`).join('\n\t')}\n}>()`);
|
|
188
|
+
}
|
|
189
|
+
for (const model of models) {
|
|
190
|
+
lines.push(`const ${model.name} = defineModel<${model.type}>('${model.name}', { required: true })`);
|
|
191
|
+
}
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
function generateReplaceTemplate() {
|
|
195
|
+
const props = [...toExtract.values()].filter(p => !p.model);
|
|
196
|
+
const models = [...toExtract.values()].filter(p => p.model);
|
|
197
|
+
return [
|
|
198
|
+
`<${newName}`,
|
|
199
|
+
...props.map(p => `:${p.name}="${p.name}"`),
|
|
200
|
+
...models.map(p => `v-model:${p.name}="${p.name}"`),
|
|
201
|
+
`/>`,
|
|
202
|
+
].join(' ');
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
transformCodeAction(item) {
|
|
206
|
+
return item; // ignore mapping
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
exports.default = default_1;
|
|
212
|
+
function selectTemplateCode(startOffset, endOffset, templateBlock, templateAst) {
|
|
213
|
+
if (startOffset < templateBlock.startTagEnd || endOffset > templateBlock.endTagStart)
|
|
214
|
+
return;
|
|
215
|
+
const insideNodes = [];
|
|
216
|
+
templateAst.children.forEach(function visit(node) {
|
|
217
|
+
if (node.loc.start.offset + templateBlock.startTagEnd >= startOffset
|
|
218
|
+
&& node.loc.end.offset + templateBlock.startTagEnd <= endOffset) {
|
|
219
|
+
insideNodes.push(node);
|
|
220
|
+
}
|
|
221
|
+
if ('children' in node) {
|
|
222
|
+
node.children.forEach(node => {
|
|
223
|
+
if (typeof node === 'object') {
|
|
224
|
+
visit(node);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
else if ('branches' in node) {
|
|
229
|
+
node.branches.forEach(visit);
|
|
230
|
+
}
|
|
231
|
+
else if ('content' in node) {
|
|
232
|
+
if (typeof node.content === 'object') {
|
|
233
|
+
visit(node.content);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
if (insideNodes.length) {
|
|
238
|
+
const first = insideNodes.sort((a, b) => a.loc.start.offset - b.loc.start.offset)[0];
|
|
239
|
+
const last = insideNodes.sort((a, b) => b.loc.end.offset - a.loc.end.offset)[0];
|
|
240
|
+
return [first.loc.start.offset, last.loc.end.offset];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function constructTag(name, attributes, initialIndent, content) {
|
|
244
|
+
if (initialIndent)
|
|
245
|
+
content = content.split('\n').map(line => `\t${line}`).join('\n');
|
|
246
|
+
const attributesString = attributes.length ? ` ${attributes.join(' ')}` : '';
|
|
247
|
+
return `<${name}${attributesString}>\n${content}\n</${name}>\n`;
|
|
248
|
+
}
|
|
249
|
+
function isInitialIndentNeeded(ts, languageKind, initialIndentSetting) {
|
|
250
|
+
const languageKindIdMap = {
|
|
251
|
+
[ts.ScriptKind.JS]: 'javascript',
|
|
252
|
+
[ts.ScriptKind.TS]: 'typescript',
|
|
253
|
+
[ts.ScriptKind.JSX]: 'javascriptreact',
|
|
254
|
+
[ts.ScriptKind.TSX]: 'typescriptreact',
|
|
255
|
+
};
|
|
256
|
+
return initialIndentSetting[languageKindIdMap[languageKind]] ?? false;
|
|
257
|
+
}
|
|
258
|
+
function createAddComponentToOptionEdit(ts, ast, componentName) {
|
|
259
|
+
const exportDefault = language_core_1.scriptRanges.parseScriptRanges(ts, ast, false, true).exportDefault;
|
|
260
|
+
if (!exportDefault)
|
|
261
|
+
return;
|
|
262
|
+
// https://github.com/microsoft/TypeScript/issues/36174
|
|
263
|
+
const printer = ts.createPrinter();
|
|
264
|
+
if (exportDefault.componentsOption && exportDefault.componentsOptionNode) {
|
|
265
|
+
const newNode = {
|
|
266
|
+
...exportDefault.componentsOptionNode,
|
|
267
|
+
properties: [
|
|
268
|
+
...exportDefault.componentsOptionNode.properties,
|
|
269
|
+
ts.factory.createShorthandPropertyAssignment(componentName),
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
|
|
273
|
+
return {
|
|
274
|
+
range: exportDefault.componentsOption,
|
|
275
|
+
newText: unescape(printText.replace(unicodeReg, '%u')),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
else if (exportDefault.args && exportDefault.argsNode) {
|
|
279
|
+
const newNode = {
|
|
280
|
+
...exportDefault.argsNode,
|
|
281
|
+
properties: [
|
|
282
|
+
...exportDefault.argsNode.properties,
|
|
283
|
+
ts.factory.createShorthandPropertyAssignment(`components: { ${componentName} }`),
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
|
|
287
|
+
return {
|
|
288
|
+
range: exportDefault.args,
|
|
289
|
+
newText: unescape(printText.replace(unicodeReg, '%u')),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
exports.createAddComponentToOptionEdit = createAddComponentToOptionEdit;
|
|
294
|
+
//# sourceMappingURL=vue-extract-file.js.map
|
|
@@ -85,7 +85,7 @@ exports.default = (options) => (_context, modules) => {
|
|
|
85
85
|
if (virtualFile && virtualFile instanceof vue.VueFile && scanner) {
|
|
86
86
|
// visualize missing required props
|
|
87
87
|
const casing = await (0, nameCasing_1.getNameCasing)(ts, _context, map.sourceFileDocument.uri, options.vueCompilerOptions);
|
|
88
|
-
const components = (0, helpers_1.
|
|
88
|
+
const components = (0, helpers_1.getComponentNames)(ts, languageService, virtualFile, options.vueCompilerOptions);
|
|
89
89
|
const componentProps = {};
|
|
90
90
|
let token;
|
|
91
91
|
let current;
|
|
@@ -97,7 +97,7 @@ exports.default = (options) => (_context, modules) => {
|
|
|
97
97
|
: components.find(component => component === tagName || (0, shared_1.hyphenate)(component) === tagName);
|
|
98
98
|
const checkTag = tagName.indexOf('.') >= 0 ? tagName : component;
|
|
99
99
|
if (checkTag) {
|
|
100
|
-
componentProps[checkTag] ??= (0, helpers_1.
|
|
100
|
+
componentProps[checkTag] ??= (0, helpers_1.getPropsByTag)(ts, languageService, virtualFile, checkTag, options.vueCompilerOptions, true);
|
|
101
101
|
current = {
|
|
102
102
|
unburnedRequiredProps: [...componentProps[checkTag]],
|
|
103
103
|
labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(),
|
|
@@ -229,7 +229,7 @@ exports.default = (options) => (_context, modules) => {
|
|
|
229
229
|
const virtualFile = _context.documents.getSourceByUri(map.sourceFileDocument.uri)?.root;
|
|
230
230
|
if (!virtualFile || !(virtualFile instanceof vue.VueFile))
|
|
231
231
|
continue;
|
|
232
|
-
const templateScriptData = (0, helpers_1.
|
|
232
|
+
const templateScriptData = (0, helpers_1.getComponentNames)(ts, languageService, virtualFile, options.vueCompilerOptions);
|
|
233
233
|
const components = new Set([
|
|
234
234
|
...templateScriptData,
|
|
235
235
|
...templateScriptData.map(shared_1.hyphenate),
|
|
@@ -290,7 +290,7 @@ exports.default = (options) => (_context, modules) => {
|
|
|
290
290
|
getId: () => 'vue-template',
|
|
291
291
|
isApplicable: () => true,
|
|
292
292
|
provideTags: () => {
|
|
293
|
-
const components = (0, helpers_1.
|
|
293
|
+
const components = (0, helpers_1.getComponentNames)(ts, languageService, vueSourceFile, options.vueCompilerOptions)
|
|
294
294
|
.filter(name => name !== 'Transition'
|
|
295
295
|
&& name !== 'TransitionGroup'
|
|
296
296
|
&& name !== 'KeepAlive'
|
|
@@ -326,9 +326,24 @@ exports.default = (options) => (_context, modules) => {
|
|
|
326
326
|
},
|
|
327
327
|
provideAttributes: (tag) => {
|
|
328
328
|
const attrs = (0, helpers_1.getElementAttrs)(ts, languageService, languageServiceHost, tag);
|
|
329
|
-
const props = new Set((0, helpers_1.
|
|
330
|
-
const events = (0, helpers_1.
|
|
329
|
+
const props = new Set((0, helpers_1.getPropsByTag)(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions));
|
|
330
|
+
const events = (0, helpers_1.getEventsOfTag)(ts, languageService, vueSourceFile, tag, options.vueCompilerOptions);
|
|
331
331
|
const attributes = [];
|
|
332
|
+
const tsCodegen = vue.tsCodegen.get(vueSourceFile.sfc);
|
|
333
|
+
if (tsCodegen) {
|
|
334
|
+
let ctxVars = [
|
|
335
|
+
...tsCodegen.scriptRanges.value?.bindings.map(binding => vueSourceFile.sfc.script.content.substring(binding.start, binding.end)) ?? [],
|
|
336
|
+
...tsCodegen.scriptSetupRanges.value?.bindings.map(binding => vueSourceFile.sfc.scriptSetup.content.substring(binding.start, binding.end)) ?? [],
|
|
337
|
+
...(0, helpers_1.getTemplateCtx)(ts, languageService, vueSourceFile) ?? [],
|
|
338
|
+
];
|
|
339
|
+
ctxVars = [...new Set(ctxVars)];
|
|
340
|
+
const dirs = ctxVars.map(shared_1.hyphenate).filter(v => v.startsWith('v-'));
|
|
341
|
+
for (const dir of dirs) {
|
|
342
|
+
attributes.push({
|
|
343
|
+
name: dir,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
332
347
|
for (const prop of [...props, ...attrs]) {
|
|
333
348
|
const isGlobal = !props.has(prop);
|
|
334
349
|
const name = casing.attr === types_1.AttrNameCasing.Camel ? prop : (0, shared_1.hyphenate)(prop);
|
|
@@ -407,7 +422,7 @@ exports.default = (options) => (_context, modules) => {
|
|
|
407
422
|
function afterHtmlCompletion(completionList, map, vueSourceFile) {
|
|
408
423
|
const languageService = _context.inject('typescript/languageService');
|
|
409
424
|
const replacement = getReplacement(completionList, map.sourceFileDocument);
|
|
410
|
-
const componentNames = new Set((0, helpers_1.
|
|
425
|
+
const componentNames = new Set((0, helpers_1.getComponentNames)(ts, languageService, vueSourceFile, options.vueCompilerOptions).map(shared_1.hyphenate));
|
|
411
426
|
if (replacement) {
|
|
412
427
|
const isEvent = replacement.text.startsWith('v-on:') || replacement.text.startsWith('@');
|
|
413
428
|
const isProp = replacement.text.startsWith('v-bind:') || replacement.text.startsWith(':');
|
|
@@ -469,39 +484,37 @@ exports.default = (options) => (_context, modules) => {
|
|
|
469
484
|
if (itemId) {
|
|
470
485
|
item.documentation = undefined;
|
|
471
486
|
}
|
|
472
|
-
if (
|
|
473
|
-
|
|
474
|
-
|
|
487
|
+
if (item.kind === 10 && componentNames.has((0, shared_1.hyphenate)(item.label))) {
|
|
488
|
+
item.kind = 6;
|
|
489
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
490
|
+
}
|
|
491
|
+
else if (itemId && (itemId.type === 'componentProp' || itemId.type === 'componentEvent')) {
|
|
492
|
+
const [componentName] = itemId.args;
|
|
493
|
+
if (componentName !== '*') {
|
|
494
|
+
item.sortText = '\u0000' + (item.sortText ?? item.label);
|
|
495
|
+
}
|
|
496
|
+
if (itemId.type === 'componentProp') {
|
|
475
497
|
if (componentName !== '*') {
|
|
476
|
-
item.
|
|
498
|
+
item.kind = 5;
|
|
477
499
|
}
|
|
478
|
-
if (itemId.type === 'componentProp') {
|
|
479
|
-
if (componentName !== '*') {
|
|
480
|
-
item.kind = 5;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
item.kind = componentName !== '*' ? 3 : 23;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
else if (item.label === 'v-if'
|
|
488
|
-
|| item.label === 'v-else-if'
|
|
489
|
-
|| item.label === 'v-else'
|
|
490
|
-
|| item.label === 'v-for') {
|
|
491
|
-
item.kind = 2;
|
|
492
|
-
item.sortText = '\u0003' + (item.sortText ?? item.label);
|
|
493
|
-
}
|
|
494
|
-
else if (item.label.startsWith('v-')) {
|
|
495
|
-
item.kind = 3;
|
|
496
|
-
item.sortText = '\u0002' + (item.sortText ?? item.label);
|
|
497
500
|
}
|
|
498
501
|
else {
|
|
499
|
-
item.
|
|
502
|
+
item.kind = componentName !== '*' ? 3 : 23;
|
|
500
503
|
}
|
|
501
504
|
}
|
|
502
|
-
else if (item.
|
|
503
|
-
item.
|
|
504
|
-
item.
|
|
505
|
+
else if (item.label === 'v-if'
|
|
506
|
+
|| item.label === 'v-else-if'
|
|
507
|
+
|| item.label === 'v-else'
|
|
508
|
+
|| item.label === 'v-for') {
|
|
509
|
+
item.kind = 14;
|
|
510
|
+
item.sortText = '\u0003' + (item.sortText ?? item.label);
|
|
511
|
+
}
|
|
512
|
+
else if (item.label.startsWith('v-')) {
|
|
513
|
+
item.kind = 3;
|
|
514
|
+
item.sortText = '\u0002' + (item.sortText ?? item.label);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
item.sortText = '\u0001' + (item.sortText ?? item.label);
|
|
505
518
|
}
|
|
506
519
|
}
|
|
507
520
|
options.updateCustomData(htmlOrPugService, []);
|