@vue/typescript-plugin 3.0.0-alpha.0 → 3.0.0-alpha.2
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/index.js +0 -6
- package/lib/client.d.ts +16 -0
- package/lib/client.js +43 -0
- package/lib/proxy.d.ts +3 -0
- package/lib/proxy.js +356 -0
- package/lib/requests/getSlotNames.d.ts +6 -0
- package/lib/requests/getSlotNames.js +30 -0
- package/lib/requests/index.d.ts +4 -1
- package/lib/server.d.ts +25 -0
- package/lib/server.js +264 -0
- package/lib/utils.d.ts +31 -0
- package/lib/utils.js +248 -0
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -11,7 +11,6 @@ const getElementAttrs_1 = require("./lib/requests/getElementAttrs");
|
|
|
11
11
|
const getElementNames_1 = require("./lib/requests/getElementNames");
|
|
12
12
|
const getImportPathForFile_1 = require("./lib/requests/getImportPathForFile");
|
|
13
13
|
const getPropertiesAtLocation_1 = require("./lib/requests/getPropertiesAtLocation");
|
|
14
|
-
const getQuickInfoAtPosition_1 = require("./lib/requests/getQuickInfoAtPosition");
|
|
15
14
|
const windowsPathReg = /\\/g;
|
|
16
15
|
const project2Service = new WeakMap();
|
|
17
16
|
module.exports = (0, createLanguageServicePlugin_1.createLanguageServicePlugin)((ts, info) => {
|
|
@@ -74,11 +73,6 @@ module.exports = (0, createLanguageServicePlugin_1.createLanguageServicePlugin)(
|
|
|
74
73
|
response: getPropertiesAtLocation_1.getPropertiesAtLocation.apply(getRequestContext(args[0]), args),
|
|
75
74
|
};
|
|
76
75
|
});
|
|
77
|
-
session.addProtocolHandler('vue:getQuickInfoAtPosition', ({ arguments: args }) => {
|
|
78
|
-
return {
|
|
79
|
-
response: getQuickInfoAtPosition_1.getQuickInfoAtPosition.apply(getRequestContext(args[0]), args),
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
76
|
session.addProtocolHandler('vue:getComponentNames', ({ arguments: args }) => {
|
|
83
77
|
return {
|
|
84
78
|
response: getComponentNames_1.getComponentNames.apply(getRequestContext(args[0]), args) ?? [],
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const collectExtractProps: (fileName: string, templateCodeRange: [number, number]) => Promise<{
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
model: boolean;
|
|
5
|
+
}[] | null | undefined>;
|
|
6
|
+
export declare const getImportPathForFile: (fileName: string, incomingFileName: string, preferences: import("typescript").UserPreferences) => Promise<string | null | undefined>;
|
|
7
|
+
export declare const getPropertiesAtLocation: (fileName: string, position: number) => Promise<string[] | null | undefined>;
|
|
8
|
+
export declare const getQuickInfoAtPosition: (fileName: string, position: number) => Promise<string | null | undefined>;
|
|
9
|
+
export declare function getComponentProps(fileName: string, componentName: string): Promise<import("./requests/getComponentProps").ComponentPropInfo[] | null | undefined>;
|
|
10
|
+
export declare const getComponentEvents: (fileName: string, tag: string) => Promise<string[] | null | undefined>;
|
|
11
|
+
export declare const getComponentDirectives: (fileName: string) => Promise<string[] | null | undefined>;
|
|
12
|
+
export declare function getComponentNames(fileName: string): Promise<string[] | undefined>;
|
|
13
|
+
export declare const getElementAttrs: (fileName: string, tagName: string) => Promise<{
|
|
14
|
+
name: string;
|
|
15
|
+
}[] | null | undefined>;
|
|
16
|
+
export declare const getSlotNames: (fileName: string) => Promise<string[] | null | undefined>;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSlotNames = exports.getElementAttrs = exports.getComponentDirectives = exports.getComponentEvents = exports.getQuickInfoAtPosition = exports.getPropertiesAtLocation = exports.getImportPathForFile = exports.collectExtractProps = void 0;
|
|
4
|
+
exports.getComponentProps = getComponentProps;
|
|
5
|
+
exports.getComponentNames = getComponentNames;
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
exports.collectExtractProps = createRequest('collectExtractProps');
|
|
8
|
+
exports.getImportPathForFile = createRequest('getImportPathForFile');
|
|
9
|
+
exports.getPropertiesAtLocation = createRequest('getPropertiesAtLocation');
|
|
10
|
+
exports.getQuickInfoAtPosition = createRequest('getQuickInfoAtPosition');
|
|
11
|
+
// Component Infos
|
|
12
|
+
async function getComponentProps(fileName, componentName) {
|
|
13
|
+
const server = await (0, utils_1.getBestServer)(fileName);
|
|
14
|
+
if (!server) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
return await server.getComponentProps(fileName, componentName);
|
|
18
|
+
}
|
|
19
|
+
exports.getComponentEvents = createRequest('getComponentEvents');
|
|
20
|
+
exports.getComponentDirectives = createRequest('getComponentDirectives');
|
|
21
|
+
async function getComponentNames(fileName) {
|
|
22
|
+
const server = await (0, utils_1.getBestServer)(fileName);
|
|
23
|
+
if (!server) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const componentAndProps = server.componentNamesAndProps.get(fileName);
|
|
27
|
+
if (!componentAndProps) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
return Object.keys(componentAndProps);
|
|
31
|
+
}
|
|
32
|
+
exports.getElementAttrs = createRequest('getElementAttrs');
|
|
33
|
+
exports.getSlotNames = createRequest('getSlotNames');
|
|
34
|
+
function createRequest(requestType) {
|
|
35
|
+
return async function (...[fileName, ...rest]) {
|
|
36
|
+
const server = await (0, utils_1.getBestServer)(fileName);
|
|
37
|
+
if (!server) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
return server.sendRequest(requestType, fileName, ...rest);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=client.js.map
|
package/lib/proxy.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Language, VueCompilerOptions } from '@vue/language-core';
|
|
2
|
+
import type * as ts from 'typescript';
|
|
3
|
+
export declare function proxyLanguageServiceForVue<T>(ts: typeof import('typescript'), language: Language<T>, languageService: ts.LanguageService, vueOptions: VueCompilerOptions, asScriptId: (fileName: string) => T): ts.LanguageService;
|
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proxyLanguageServiceForVue = proxyLanguageServiceForVue;
|
|
4
|
+
const language_core_1 = require("@vue/language-core");
|
|
5
|
+
const shared_1 = require("@vue/shared");
|
|
6
|
+
const getComponentNames_1 = require("./requests/getComponentNames");
|
|
7
|
+
const windowsPathReg = /\\/g;
|
|
8
|
+
function proxyLanguageServiceForVue(ts, language, languageService, vueOptions, asScriptId) {
|
|
9
|
+
const proxyCache = new Map();
|
|
10
|
+
const getProxyMethod = (target, p) => {
|
|
11
|
+
switch (p) {
|
|
12
|
+
case 'getCompletionsAtPosition': return getCompletionsAtPosition(vueOptions, target[p]);
|
|
13
|
+
case 'getCompletionEntryDetails': return getCompletionEntryDetails(language, asScriptId, target[p]);
|
|
14
|
+
case 'getCodeFixesAtPosition': return getCodeFixesAtPosition(target[p]);
|
|
15
|
+
case 'getDefinitionAndBoundSpan': return getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, asScriptId, target[p]);
|
|
16
|
+
case 'getEncodedSemanticClassifications': return getEncodedSemanticClassifications(ts, language, target, asScriptId, target[p]);
|
|
17
|
+
case 'getQuickInfoAtPosition': return getQuickInfoAtPosition(ts, target, target[p]);
|
|
18
|
+
case 'getSemanticDiagnostics': return getSemanticDiagnostics(ts, language, languageService, asScriptId, target[p]);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return new Proxy(languageService, {
|
|
22
|
+
get(target, p, receiver) {
|
|
23
|
+
if (getProxyMethod) {
|
|
24
|
+
if (!proxyCache.has(p)) {
|
|
25
|
+
proxyCache.set(p, getProxyMethod(target, p));
|
|
26
|
+
}
|
|
27
|
+
const proxyMethod = proxyCache.get(p);
|
|
28
|
+
if (proxyMethod) {
|
|
29
|
+
return proxyMethod;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Reflect.get(target, p, receiver);
|
|
33
|
+
},
|
|
34
|
+
set(target, p, value, receiver) {
|
|
35
|
+
return Reflect.set(target, p, value, receiver);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function getCompletionsAtPosition(vueOptions, getCompletionsAtPosition) {
|
|
40
|
+
return (filePath, position, options, formattingSettings) => {
|
|
41
|
+
const fileName = filePath.replace(windowsPathReg, '/');
|
|
42
|
+
const result = getCompletionsAtPosition(fileName, position, options, formattingSettings);
|
|
43
|
+
if (result) {
|
|
44
|
+
// filter __VLS_
|
|
45
|
+
result.entries = result.entries.filter(entry => !entry.name.includes('__VLS_')
|
|
46
|
+
&& !entry.labelDetails?.description?.includes('__VLS_'));
|
|
47
|
+
// modify label
|
|
48
|
+
for (const item of result.entries) {
|
|
49
|
+
if (item.source) {
|
|
50
|
+
const originalName = item.name;
|
|
51
|
+
for (const vueExt of vueOptions.extensions) {
|
|
52
|
+
const suffix = (0, shared_1.capitalize)(vueExt.slice(1)); // .vue -> Vue
|
|
53
|
+
if (item.source.endsWith(vueExt) && item.name.endsWith(suffix)) {
|
|
54
|
+
item.name = (0, shared_1.capitalize)(item.name.slice(0, -suffix.length));
|
|
55
|
+
if (item.insertText) {
|
|
56
|
+
// #2286
|
|
57
|
+
item.insertText = item.insertText.replace(`${suffix}$1`, '$1');
|
|
58
|
+
}
|
|
59
|
+
if (item.data) {
|
|
60
|
+
// @ts-expect-error
|
|
61
|
+
item.data.__isComponentAutoImport = {
|
|
62
|
+
ext: vueExt,
|
|
63
|
+
suffix,
|
|
64
|
+
originalName,
|
|
65
|
+
newName: item.insertText,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (item.data) {
|
|
72
|
+
// @ts-expect-error
|
|
73
|
+
item.data.__isAutoImport = {
|
|
74
|
+
fileName,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function getCompletionEntryDetails(language, asScriptId, getCompletionEntryDetails) {
|
|
84
|
+
return (...args) => {
|
|
85
|
+
const details = getCompletionEntryDetails(...args);
|
|
86
|
+
// modify import statement
|
|
87
|
+
// @ts-expect-error
|
|
88
|
+
if (args[6]?.__isComponentAutoImport) {
|
|
89
|
+
// @ts-expect-error
|
|
90
|
+
const { ext, suffix, originalName, newName } = args[6]?.__isComponentAutoImport;
|
|
91
|
+
for (const codeAction of details?.codeActions ?? []) {
|
|
92
|
+
for (const change of codeAction.changes) {
|
|
93
|
+
for (const textChange of change.textChanges) {
|
|
94
|
+
textChange.newText = textChange.newText.replace('import ' + originalName + ' from ', 'import ' + newName + ' from ');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// @ts-expect-error
|
|
100
|
+
if (args[6]?.__isAutoImport) {
|
|
101
|
+
// @ts-expect-error
|
|
102
|
+
const { fileName } = args[6]?.__isAutoImport;
|
|
103
|
+
const sourceScript = language.scripts.get(asScriptId(fileName));
|
|
104
|
+
if (sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode) {
|
|
105
|
+
const sfc = sourceScript.generated.root.vueSfc;
|
|
106
|
+
if (!sfc?.descriptor.script && !sfc?.descriptor.scriptSetup) {
|
|
107
|
+
for (const codeAction of details?.codeActions ?? []) {
|
|
108
|
+
for (const change of codeAction.changes) {
|
|
109
|
+
for (const textChange of change.textChanges) {
|
|
110
|
+
textChange.newText = `<script setup lang="ts">${textChange.newText}</script>\n\n`;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return details;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function getCodeFixesAtPosition(getCodeFixesAtPosition) {
|
|
124
|
+
return (...args) => {
|
|
125
|
+
let result = getCodeFixesAtPosition(...args);
|
|
126
|
+
// filter __VLS_
|
|
127
|
+
result = result.filter(entry => !entry.description.includes('__VLS_'));
|
|
128
|
+
return result;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function getDefinitionAndBoundSpan(ts, language, languageService, vueOptions, asScriptId, getDefinitionAndBoundSpan) {
|
|
132
|
+
return (fileName, position) => {
|
|
133
|
+
const result = getDefinitionAndBoundSpan(fileName, position);
|
|
134
|
+
if (!result?.definitions?.length) {
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
const program = languageService.getProgram();
|
|
138
|
+
const sourceScript = language.scripts.get(asScriptId(fileName));
|
|
139
|
+
if (!sourceScript?.generated) {
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
const root = sourceScript.generated.root;
|
|
143
|
+
if (!(root instanceof language_core_1.VueVirtualCode)) {
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
if (!root.sfc.template
|
|
147
|
+
|| position < root.sfc.template.startTagEnd
|
|
148
|
+
|| position > root.sfc.template.endTagStart) {
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
const definitions = new Set(result.definitions);
|
|
152
|
+
const skippedDefinitions = [];
|
|
153
|
+
for (const definition of result.definitions) {
|
|
154
|
+
if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const sourceFile = program.getSourceFile(definition.fileName);
|
|
158
|
+
if (!sourceFile) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
visit(sourceFile, definition, sourceFile);
|
|
162
|
+
}
|
|
163
|
+
for (const definition of skippedDefinitions) {
|
|
164
|
+
definitions.delete(definition);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
definitions: [...definitions],
|
|
168
|
+
textSpan: result.textSpan,
|
|
169
|
+
};
|
|
170
|
+
function visit(node, definition, sourceFile) {
|
|
171
|
+
if (ts.isPropertySignature(node) && node.type) {
|
|
172
|
+
proxy(node.name, node.type, definition, sourceFile);
|
|
173
|
+
}
|
|
174
|
+
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type && !node.initializer) {
|
|
175
|
+
proxy(node.name, node.type, definition, sourceFile);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
ts.forEachChild(node, child => visit(child, definition, sourceFile));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function proxy(name, type, definition, sourceFile) {
|
|
182
|
+
const { textSpan, fileName } = definition;
|
|
183
|
+
const start = name.getStart(sourceFile);
|
|
184
|
+
const end = name.getEnd();
|
|
185
|
+
if (start !== textSpan.start || end - start !== textSpan.length) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!ts.isIndexedAccessTypeNode(type)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const pos = type.indexType.getStart(sourceFile);
|
|
192
|
+
const res = getDefinitionAndBoundSpan(fileName, pos);
|
|
193
|
+
if (res?.definitions?.length) {
|
|
194
|
+
for (const definition of res.definitions) {
|
|
195
|
+
definitions.add(definition);
|
|
196
|
+
}
|
|
197
|
+
skippedDefinitions.push(definition);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getQuickInfoAtPosition(ts, languageService, getQuickInfoAtPosition) {
|
|
203
|
+
return (...args) => {
|
|
204
|
+
const result = getQuickInfoAtPosition(...args);
|
|
205
|
+
if (result && result.documentation?.length === 1 && result.documentation[0].text.startsWith('__VLS_emit,')) {
|
|
206
|
+
const [_, emitVarName, eventName] = result.documentation[0].text.split(',');
|
|
207
|
+
const program = languageService.getProgram();
|
|
208
|
+
const typeChecker = program.getTypeChecker();
|
|
209
|
+
const sourceFile = program.getSourceFile(args[0]);
|
|
210
|
+
result.documentation = undefined;
|
|
211
|
+
let symbolNode;
|
|
212
|
+
sourceFile?.forEachChild(function visit(node) {
|
|
213
|
+
if (ts.isIdentifier(node) && node.text === emitVarName) {
|
|
214
|
+
symbolNode = node;
|
|
215
|
+
}
|
|
216
|
+
if (symbolNode) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
ts.forEachChild(node, visit);
|
|
220
|
+
});
|
|
221
|
+
if (symbolNode) {
|
|
222
|
+
const emitSymbol = typeChecker.getSymbolAtLocation(symbolNode);
|
|
223
|
+
if (emitSymbol) {
|
|
224
|
+
const type = typeChecker.getTypeOfSymbolAtLocation(emitSymbol, symbolNode);
|
|
225
|
+
const calls = type.getCallSignatures();
|
|
226
|
+
for (const call of calls) {
|
|
227
|
+
const callEventName = typeChecker.getTypeOfSymbolAtLocation(call.parameters[0], symbolNode).value;
|
|
228
|
+
call.getJsDocTags();
|
|
229
|
+
if (callEventName === eventName) {
|
|
230
|
+
result.documentation = call.getDocumentationComment(typeChecker);
|
|
231
|
+
result.tags = call.getJsDocTags();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function getSemanticDiagnostics(ts, language, languageService, asScriptId, getSemanticDiagnostics) {
|
|
241
|
+
return (fileName) => {
|
|
242
|
+
const result = getSemanticDiagnostics(fileName);
|
|
243
|
+
const program = languageService.getProgram();
|
|
244
|
+
const sourceScript = language.scripts.get(asScriptId(fileName));
|
|
245
|
+
if (!sourceScript?.generated) {
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
const root = sourceScript.generated.root;
|
|
249
|
+
if (!(root instanceof language_core_1.VueVirtualCode)) {
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
const { template } = root.sfc;
|
|
253
|
+
if (!template) {
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
257
|
+
if (!sourceFile) {
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
const additionalResult = [];
|
|
261
|
+
const { commentDirectives } = template;
|
|
262
|
+
for (const dir of commentDirectives) {
|
|
263
|
+
const start = template.startTagEnd + dir.start;
|
|
264
|
+
const end = template.startTagEnd + dir.end;
|
|
265
|
+
const rangeStart = template.startTagEnd + dir.rangeStart;
|
|
266
|
+
const rangeEnd = template.startTagEnd + dir.rangeEnd;
|
|
267
|
+
if (dir.name === 'expect-error') {
|
|
268
|
+
let containError = false;
|
|
269
|
+
for (let i = 0; i < result.length; i++) {
|
|
270
|
+
const diag = result[i];
|
|
271
|
+
if (diag.start >= rangeStart && diag.start + diag.length <= rangeEnd) {
|
|
272
|
+
containError = true;
|
|
273
|
+
result.splice(i, 1);
|
|
274
|
+
i--;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!containError) {
|
|
278
|
+
additionalResult.push({
|
|
279
|
+
category: ts.DiagnosticCategory.Error,
|
|
280
|
+
code: 2578,
|
|
281
|
+
file: sourceFile,
|
|
282
|
+
start,
|
|
283
|
+
length: end - start,
|
|
284
|
+
messageText: `Unused '@vue-expect-error' directive.`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (dir.name === 'ignore') {
|
|
289
|
+
for (let i = 0; i < result.length; i++) {
|
|
290
|
+
const diag = result[i];
|
|
291
|
+
if (diag.start >= rangeStart && diag.start + diag.length <= rangeEnd) {
|
|
292
|
+
result.splice(i, 1);
|
|
293
|
+
i--;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return [...result, ...additionalResult];
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function getEncodedSemanticClassifications(ts, language, languageService, asScriptId, getEncodedSemanticClassifications) {
|
|
302
|
+
return (filePath, span, format) => {
|
|
303
|
+
const fileName = filePath.replace(windowsPathReg, '/');
|
|
304
|
+
const result = getEncodedSemanticClassifications(fileName, span, format);
|
|
305
|
+
const sourceScript = language.scripts.get(asScriptId(fileName));
|
|
306
|
+
const root = sourceScript?.generated?.root;
|
|
307
|
+
if (root instanceof language_core_1.VueVirtualCode) {
|
|
308
|
+
const { template } = root.sfc;
|
|
309
|
+
if (template) {
|
|
310
|
+
for (const componentSpan of getComponentSpans.call({ typescript: ts, languageService }, root, template, {
|
|
311
|
+
start: span.start - template.startTagEnd,
|
|
312
|
+
length: span.length,
|
|
313
|
+
})) {
|
|
314
|
+
result.spans.push(componentSpan.start + template.startTagEnd, componentSpan.length, 256 // class
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function getComponentSpans(vueCode, template, spanTemplateRange) {
|
|
323
|
+
const { typescript: ts, languageService } = this;
|
|
324
|
+
const result = [];
|
|
325
|
+
const validComponentNames = (0, getComponentNames_1._getComponentNames)(ts, languageService, vueCode);
|
|
326
|
+
const elements = new Set((0, getComponentNames_1._getElementNames)(ts, languageService, vueCode));
|
|
327
|
+
const components = new Set([
|
|
328
|
+
...validComponentNames,
|
|
329
|
+
...validComponentNames.map(language_core_1.hyphenateTag),
|
|
330
|
+
]);
|
|
331
|
+
if (template.ast) {
|
|
332
|
+
for (const node of (0, language_core_1.forEachTemplateChild)(template.ast)) {
|
|
333
|
+
if (node.loc.end.offset <= spanTemplateRange.start || node.loc.start.offset >= (spanTemplateRange.start + spanTemplateRange.length)) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (components.has(node.tag) && !elements.has(node.tag)) {
|
|
337
|
+
let start = node.loc.start.offset;
|
|
338
|
+
if (template.lang === 'html') {
|
|
339
|
+
start += '<'.length;
|
|
340
|
+
}
|
|
341
|
+
result.push({
|
|
342
|
+
start,
|
|
343
|
+
length: node.tag.length,
|
|
344
|
+
});
|
|
345
|
+
if (template.lang === 'html' && !node.isSelfClosing) {
|
|
346
|
+
result.push({
|
|
347
|
+
start: node.loc.start.offset + node.loc.source.lastIndexOf(node.tag),
|
|
348
|
+
length: node.tag.length,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSlotNames = getSlotNames;
|
|
4
|
+
const language_core_1 = require("@vue/language-core");
|
|
5
|
+
function getSlotNames(fileName) {
|
|
6
|
+
const { typescript: ts, language, languageService, getFileId } = this;
|
|
7
|
+
const sourceScript = language.scripts.get(getFileId(fileName));
|
|
8
|
+
if (!sourceScript?.generated) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const root = sourceScript.generated.root;
|
|
12
|
+
if (!(root instanceof language_core_1.VueVirtualCode)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const program = languageService.getProgram();
|
|
16
|
+
const tsSourceFile = program.getSourceFile(fileName);
|
|
17
|
+
if (!tsSourceFile) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const checker = program.getTypeChecker();
|
|
21
|
+
const typeNode = tsSourceFile.statements
|
|
22
|
+
.filter(ts.isTypeAliasDeclaration)
|
|
23
|
+
.find(node => node.name.getText() === '__VLS_Slots');
|
|
24
|
+
if (typeNode) {
|
|
25
|
+
const attrs = checker.getTypeFromTypeNode(typeNode.type).getProperties();
|
|
26
|
+
return attrs.map(attr => attr.name);
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=getSlotNames.js.map
|
package/lib/requests/index.d.ts
CHANGED
|
@@ -3,12 +3,15 @@ export type Requests = {
|
|
|
3
3
|
collectExtractProps: ToRequest<typeof import('./collectExtractProps.js')['collectExtractProps']>;
|
|
4
4
|
getImportPathForFile: ToRequest<typeof import('./getImportPathForFile.js')['getImportPathForFile']>;
|
|
5
5
|
getPropertiesAtLocation: ToRequest<typeof import('./getPropertiesAtLocation.js')['getPropertiesAtLocation']>;
|
|
6
|
-
getQuickInfoAtPosition: ToRequest<typeof import('./getQuickInfoAtPosition.js')['getQuickInfoAtPosition']>;
|
|
7
6
|
getComponentNames: ToRequest<typeof import('./getComponentNames.js')['getComponentNames']>;
|
|
8
7
|
getComponentProps: ToRequest<typeof import('./getComponentProps.js')['getComponentProps']>;
|
|
9
8
|
getComponentEvents: ToRequest<typeof import('./getComponentEvents.js')['getComponentEvents']>;
|
|
10
9
|
getComponentDirectives: ToRequest<typeof import('./getComponentDirectives.js')['getComponentDirectives']>;
|
|
11
10
|
getElementAttrs: ToRequest<typeof import('./getElementAttrs.js')['getElementAttrs']>;
|
|
12
11
|
getElementNames: ToRequest<typeof import('./getElementNames.js')['getElementNames']>;
|
|
12
|
+
getQuickInfoAtPosition: ToRequest<(fileName: string, position: {
|
|
13
|
+
line: number;
|
|
14
|
+
character: number;
|
|
15
|
+
}) => string>;
|
|
13
16
|
};
|
|
14
17
|
export {};
|
package/lib/server.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Language } from '@vue/language-core';
|
|
2
|
+
import type * as ts from 'typescript';
|
|
3
|
+
export type RequestType = 'containsFile' | 'projectInfo' | 'collectExtractProps' | 'getImportPathForFile' | 'getPropertiesAtLocation' | 'getQuickInfoAtPosition' | 'subscribeComponentProps' | 'getComponentEvents' | 'getComponentDirectives' | 'getElementAttrs' | 'getSlotNames';
|
|
4
|
+
export type NotificationType = 'componentNamesUpdated' | 'componentPropsUpdated';
|
|
5
|
+
export type RequestData = [
|
|
6
|
+
seq: number,
|
|
7
|
+
type: RequestType,
|
|
8
|
+
fileName: string,
|
|
9
|
+
...args: any[]
|
|
10
|
+
];
|
|
11
|
+
export type ResponseData = [
|
|
12
|
+
seq: number,
|
|
13
|
+
data: any
|
|
14
|
+
];
|
|
15
|
+
export type NotificationData = [
|
|
16
|
+
type: NotificationType,
|
|
17
|
+
fileName: string,
|
|
18
|
+
data: any
|
|
19
|
+
];
|
|
20
|
+
export interface ProjectInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
kind: ts.server.ProjectKind;
|
|
23
|
+
currentDirectory: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function startNamedPipeServer(ts: typeof import('typescript'), info: ts.server.PluginCreateInfo, language: Language<string>, projectKind: ts.server.ProjectKind.Inferred | ts.server.ProjectKind.Configured): Promise<void>;
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startNamedPipeServer = startNamedPipeServer;
|
|
4
|
+
const language_core_1 = require("@vue/language-core");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const net = require("node:net");
|
|
7
|
+
const collectExtractProps_1 = require("./requests/collectExtractProps");
|
|
8
|
+
const getComponentDirectives_1 = require("./requests/getComponentDirectives");
|
|
9
|
+
const getComponentEvents_1 = require("./requests/getComponentEvents");
|
|
10
|
+
const getComponentNames_1 = require("./requests/getComponentNames");
|
|
11
|
+
const getComponentProps_1 = require("./requests/getComponentProps");
|
|
12
|
+
const getElementAttrs_1 = require("./requests/getElementAttrs");
|
|
13
|
+
const getImportPathForFile_1 = require("./requests/getImportPathForFile");
|
|
14
|
+
const getPropertiesAtLocation_1 = require("./requests/getPropertiesAtLocation");
|
|
15
|
+
const getQuickInfoAtPosition_1 = require("./requests/getQuickInfoAtPosition");
|
|
16
|
+
const getSlotNames_1 = require("./requests/getSlotNames");
|
|
17
|
+
const utils_1 = require("./utils");
|
|
18
|
+
async function startNamedPipeServer(ts, info, language, projectKind) {
|
|
19
|
+
let lastProjectVersion;
|
|
20
|
+
const requestContext = {
|
|
21
|
+
typescript: ts,
|
|
22
|
+
languageService: info.languageService,
|
|
23
|
+
languageServiceHost: info.languageServiceHost,
|
|
24
|
+
language: language,
|
|
25
|
+
isTsPlugin: true,
|
|
26
|
+
getFileId: (fileName) => fileName,
|
|
27
|
+
};
|
|
28
|
+
const dataChunks = [];
|
|
29
|
+
const currentData = new language_core_1.FileMap(false);
|
|
30
|
+
const allConnections = new Set();
|
|
31
|
+
const pendingRequests = new Set();
|
|
32
|
+
const server = net.createServer(connection => {
|
|
33
|
+
allConnections.add(connection);
|
|
34
|
+
connection.on('end', () => {
|
|
35
|
+
allConnections.delete(connection);
|
|
36
|
+
});
|
|
37
|
+
connection.on('data', buffer => {
|
|
38
|
+
dataChunks.push(buffer);
|
|
39
|
+
const text = dataChunks.toString();
|
|
40
|
+
if (text.endsWith('\n\n')) {
|
|
41
|
+
dataChunks.length = 0;
|
|
42
|
+
const requests = text.split('\n\n');
|
|
43
|
+
for (let json of requests) {
|
|
44
|
+
json = json.trim();
|
|
45
|
+
if (!json) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
onRequest(connection, JSON.parse(json));
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
console.error('[Vue Named Pipe Server] JSON parse error:', e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
connection.on('error', err => console.error('[Vue Named Pipe Server]', err.message));
|
|
58
|
+
for (const [fileName, [componentNames, componentProps]] of currentData) {
|
|
59
|
+
notify(connection, 'componentNamesUpdated', fileName, componentNames);
|
|
60
|
+
for (const [name, props] of Object.entries(componentProps)) {
|
|
61
|
+
notify(connection, 'componentPropsUpdated', fileName, [name, props]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
for (let i = 0; i < 10; i++) {
|
|
66
|
+
const path = (0, utils_1.getServerPath)(projectKind, i);
|
|
67
|
+
const socket = await connect(path, 100);
|
|
68
|
+
if (typeof socket === 'object') {
|
|
69
|
+
socket.end();
|
|
70
|
+
}
|
|
71
|
+
const namedPipeOccupied = typeof socket === 'object' || socket === 'timeout';
|
|
72
|
+
if (namedPipeOccupied) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const success = await tryListen(server, path);
|
|
76
|
+
if (success) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
updateWhile();
|
|
81
|
+
async function updateWhile() {
|
|
82
|
+
while (true) {
|
|
83
|
+
await sleep(500);
|
|
84
|
+
const projectVersion = info.project.getProjectVersion();
|
|
85
|
+
if (lastProjectVersion === projectVersion) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const connections = [...allConnections].filter(c => !c.destroyed);
|
|
89
|
+
if (!connections.length) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const token = info.languageServiceHost.getCancellationToken?.();
|
|
93
|
+
const openedScriptInfos = info.project.getRootScriptInfos().filter(info => info.isScriptOpen());
|
|
94
|
+
if (!openedScriptInfos.length) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const scriptInfo of openedScriptInfos) {
|
|
98
|
+
await sleep(10);
|
|
99
|
+
if (token?.isCancellationRequested()) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
let data = currentData.get(scriptInfo.fileName);
|
|
103
|
+
if (!data) {
|
|
104
|
+
data = [[], {}];
|
|
105
|
+
currentData.set(scriptInfo.fileName, data);
|
|
106
|
+
}
|
|
107
|
+
const [oldComponentNames, componentProps] = data;
|
|
108
|
+
const newComponentNames = getComponentNames_1.getComponentNames.apply(requestContext, [scriptInfo.fileName]) ?? [];
|
|
109
|
+
if (JSON.stringify(oldComponentNames) !== JSON.stringify(newComponentNames)) {
|
|
110
|
+
data[0] = newComponentNames;
|
|
111
|
+
for (const connection of connections) {
|
|
112
|
+
notify(connection, 'componentNamesUpdated', scriptInfo.fileName, newComponentNames);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const [name, props] of Object.entries(componentProps)) {
|
|
116
|
+
await sleep(10);
|
|
117
|
+
if (token?.isCancellationRequested()) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const newProps = getComponentProps_1.getComponentProps.apply(requestContext, [scriptInfo.fileName, name]) ?? [];
|
|
121
|
+
if (JSON.stringify(props) !== JSON.stringify(newProps)) {
|
|
122
|
+
componentProps[name] = newProps;
|
|
123
|
+
for (const connection of connections) {
|
|
124
|
+
notify(connection, 'componentPropsUpdated', scriptInfo.fileName, [name, newProps]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
lastProjectVersion = projectVersion;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function sleep(ms) {
|
|
133
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
134
|
+
}
|
|
135
|
+
function notify(connection, type, fileName, data) {
|
|
136
|
+
connection.write(JSON.stringify([type, fileName, data]) + '\n\n');
|
|
137
|
+
}
|
|
138
|
+
function onRequest(connection, [seq, requestType, ...args]) {
|
|
139
|
+
if (pendingRequests.has(seq)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
setTimeout(() => pendingRequests.delete(seq), 500);
|
|
143
|
+
pendingRequests.add(seq);
|
|
144
|
+
let data;
|
|
145
|
+
try {
|
|
146
|
+
data = handleRequest(requestType, ...args);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
data = null;
|
|
150
|
+
}
|
|
151
|
+
connection.write(JSON.stringify([seq, data ?? null]) + '\n\n');
|
|
152
|
+
}
|
|
153
|
+
function handleRequest(requestType, ...args) {
|
|
154
|
+
const fileName = args[0];
|
|
155
|
+
if (requestType === 'projectInfo') {
|
|
156
|
+
return {
|
|
157
|
+
name: info.project.getProjectName(),
|
|
158
|
+
kind: info.project.projectKind,
|
|
159
|
+
currentDirectory: info.project.getCurrentDirectory(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
else if (requestType === 'containsFile') {
|
|
163
|
+
return info.project.containsFile(ts.server.toNormalizedPath(fileName));
|
|
164
|
+
}
|
|
165
|
+
else if (requestType === 'collectExtractProps') {
|
|
166
|
+
return collectExtractProps_1.collectExtractProps.apply(requestContext, args);
|
|
167
|
+
}
|
|
168
|
+
else if (requestType === 'getImportPathForFile') {
|
|
169
|
+
return getImportPathForFile_1.getImportPathForFile.apply(requestContext, args);
|
|
170
|
+
}
|
|
171
|
+
else if (requestType === 'getPropertiesAtLocation') {
|
|
172
|
+
return getPropertiesAtLocation_1.getPropertiesAtLocation.apply(requestContext, args);
|
|
173
|
+
}
|
|
174
|
+
else if (requestType === 'getQuickInfoAtPosition') {
|
|
175
|
+
return getQuickInfoAtPosition_1.getQuickInfoAtPosition.apply(requestContext, args);
|
|
176
|
+
}
|
|
177
|
+
else if (requestType === 'subscribeComponentProps') {
|
|
178
|
+
const tag = args[1];
|
|
179
|
+
const props = getComponentProps_1.getComponentProps.apply(requestContext, [fileName, tag]) ?? [];
|
|
180
|
+
let data = currentData.get(fileName);
|
|
181
|
+
if (!data) {
|
|
182
|
+
data = [[], {}];
|
|
183
|
+
currentData.set(fileName, data);
|
|
184
|
+
}
|
|
185
|
+
data[1][tag] = props;
|
|
186
|
+
return props;
|
|
187
|
+
}
|
|
188
|
+
else if (requestType === 'getComponentEvents') {
|
|
189
|
+
return getComponentEvents_1.getComponentEvents.apply(requestContext, args);
|
|
190
|
+
}
|
|
191
|
+
else if (requestType === 'getComponentDirectives') {
|
|
192
|
+
return getComponentDirectives_1.getComponentDirectives.apply(requestContext, args);
|
|
193
|
+
}
|
|
194
|
+
else if (requestType === 'getElementAttrs') {
|
|
195
|
+
return getElementAttrs_1.getElementAttrs.apply(requestContext, args);
|
|
196
|
+
}
|
|
197
|
+
else if (requestType === 'getSlotNames') {
|
|
198
|
+
return getSlotNames_1.getSlotNames.apply(requestContext, args);
|
|
199
|
+
}
|
|
200
|
+
console.warn('[Vue Named Pipe Server] Unknown request:', requestType);
|
|
201
|
+
debugger;
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function connect(namedPipePath, timeout) {
|
|
206
|
+
return new Promise(resolve => {
|
|
207
|
+
const socket = net.connect(namedPipePath);
|
|
208
|
+
if (timeout) {
|
|
209
|
+
socket.setTimeout(timeout);
|
|
210
|
+
}
|
|
211
|
+
const onConnect = () => {
|
|
212
|
+
cleanup();
|
|
213
|
+
resolve(socket);
|
|
214
|
+
};
|
|
215
|
+
const onError = (err) => {
|
|
216
|
+
if (err.code === 'ECONNREFUSED') {
|
|
217
|
+
try {
|
|
218
|
+
console.log('[Vue Named Pipe Client] Deleting:', namedPipePath);
|
|
219
|
+
fs.promises.unlink(namedPipePath);
|
|
220
|
+
}
|
|
221
|
+
catch { }
|
|
222
|
+
}
|
|
223
|
+
cleanup();
|
|
224
|
+
resolve('error');
|
|
225
|
+
socket.end();
|
|
226
|
+
};
|
|
227
|
+
const onTimeout = () => {
|
|
228
|
+
cleanup();
|
|
229
|
+
resolve('timeout');
|
|
230
|
+
socket.end();
|
|
231
|
+
};
|
|
232
|
+
const cleanup = () => {
|
|
233
|
+
socket.off('connect', onConnect);
|
|
234
|
+
socket.off('error', onError);
|
|
235
|
+
socket.off('timeout', onTimeout);
|
|
236
|
+
};
|
|
237
|
+
socket.on('connect', onConnect);
|
|
238
|
+
socket.on('error', onError);
|
|
239
|
+
socket.on('timeout', onTimeout);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function tryListen(server, namedPipePath) {
|
|
243
|
+
return new Promise(resolve => {
|
|
244
|
+
const onSuccess = () => {
|
|
245
|
+
server.off('error', onError);
|
|
246
|
+
resolve(true);
|
|
247
|
+
};
|
|
248
|
+
const onError = (err) => {
|
|
249
|
+
if (err.code === 'ECONNREFUSED') {
|
|
250
|
+
try {
|
|
251
|
+
console.log('[Vue Named Pipe Client] Deleting:', namedPipePath);
|
|
252
|
+
fs.promises.unlink(namedPipePath);
|
|
253
|
+
}
|
|
254
|
+
catch { }
|
|
255
|
+
}
|
|
256
|
+
server.off('error', onError);
|
|
257
|
+
server.close();
|
|
258
|
+
resolve(false);
|
|
259
|
+
};
|
|
260
|
+
server.listen(namedPipePath, onSuccess);
|
|
261
|
+
server.on('error', onError);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=server.js.map
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FileMap } from '@vue/language-core';
|
|
2
|
+
import * as net from 'node:net';
|
|
3
|
+
import type * as ts from 'typescript';
|
|
4
|
+
import type { ComponentPropInfo } from './requests/getComponentProps';
|
|
5
|
+
import type { NotificationData, ProjectInfo, RequestData } from './server';
|
|
6
|
+
export { TypeScriptProjectHost } from '@volar/typescript';
|
|
7
|
+
export declare function getServerPath(kind: ts.server.ProjectKind, id: number): string;
|
|
8
|
+
declare class NamedPipeServer {
|
|
9
|
+
path: string;
|
|
10
|
+
connecting: boolean;
|
|
11
|
+
projectInfo?: ProjectInfo;
|
|
12
|
+
containsFileCache: Map<string, Promise<boolean | null | undefined>>;
|
|
13
|
+
componentNamesAndProps: FileMap<Record<string, ComponentPropInfo[] | null>>;
|
|
14
|
+
constructor(kind: ts.server.ProjectKind, id: number);
|
|
15
|
+
containsFile(fileName: string): Promise<boolean | null | undefined> | undefined;
|
|
16
|
+
getComponentProps(fileName: string, tag: string): Promise<ComponentPropInfo[] | null | undefined>;
|
|
17
|
+
update(): void;
|
|
18
|
+
connect(): void;
|
|
19
|
+
close(): void;
|
|
20
|
+
socket?: net.Socket;
|
|
21
|
+
seq: number;
|
|
22
|
+
dataChunks: Buffer[];
|
|
23
|
+
requestHandlers: Map<number, (res: any) => void>;
|
|
24
|
+
onData(chunk: Buffer): void;
|
|
25
|
+
onNotification(type: NotificationData[0], fileName: string, data: any): void;
|
|
26
|
+
sendRequest<T>(requestType: RequestData[1], fileName: string, ...args: any[]): Promise<T | null | undefined>;
|
|
27
|
+
}
|
|
28
|
+
export declare const configuredServers: NamedPipeServer[];
|
|
29
|
+
export declare const inferredServers: NamedPipeServer[];
|
|
30
|
+
export declare const onServerReady: (() => void)[];
|
|
31
|
+
export declare function getBestServer(fileName: string): Promise<NamedPipeServer | undefined>;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.onServerReady = exports.inferredServers = exports.configuredServers = void 0;
|
|
4
|
+
exports.getServerPath = getServerPath;
|
|
5
|
+
exports.getBestServer = getBestServer;
|
|
6
|
+
const language_core_1 = require("@vue/language-core");
|
|
7
|
+
const shared_1 = require("@vue/shared");
|
|
8
|
+
const fs = require("node:fs");
|
|
9
|
+
const net = require("node:net");
|
|
10
|
+
const os = require("node:os");
|
|
11
|
+
const path = require("node:path");
|
|
12
|
+
const { version } = require('../package.json');
|
|
13
|
+
const platform = os.platform();
|
|
14
|
+
const pipeDir = platform === 'win32'
|
|
15
|
+
? `\\\\.\\pipe\\`
|
|
16
|
+
: `/tmp/`;
|
|
17
|
+
function getServerPath(kind, id) {
|
|
18
|
+
if (kind === 1) {
|
|
19
|
+
return `${pipeDir}vue-named-pipe-${version}-configured-${id}`;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return `${pipeDir}vue-named-pipe-${version}-inferred-${id}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class NamedPipeServer {
|
|
26
|
+
constructor(kind, id) {
|
|
27
|
+
this.connecting = false;
|
|
28
|
+
this.containsFileCache = new Map();
|
|
29
|
+
this.componentNamesAndProps = new language_core_1.FileMap(false);
|
|
30
|
+
this.seq = 0;
|
|
31
|
+
this.dataChunks = [];
|
|
32
|
+
this.requestHandlers = new Map();
|
|
33
|
+
this.path = getServerPath(kind, id);
|
|
34
|
+
}
|
|
35
|
+
containsFile(fileName) {
|
|
36
|
+
if (this.projectInfo) {
|
|
37
|
+
if (!this.containsFileCache.has(fileName)) {
|
|
38
|
+
this.containsFileCache.set(fileName, (async () => {
|
|
39
|
+
const res = await this.sendRequest('containsFile', fileName);
|
|
40
|
+
if (typeof res !== 'boolean') {
|
|
41
|
+
// If the request fails, delete the cache
|
|
42
|
+
this.containsFileCache.delete(fileName);
|
|
43
|
+
}
|
|
44
|
+
return res;
|
|
45
|
+
})());
|
|
46
|
+
}
|
|
47
|
+
return this.containsFileCache.get(fileName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async getComponentProps(fileName, tag) {
|
|
51
|
+
const componentAndProps = this.componentNamesAndProps.get(fileName);
|
|
52
|
+
if (!componentAndProps) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const props = componentAndProps[tag]
|
|
56
|
+
?? componentAndProps[(0, shared_1.camelize)(tag)]
|
|
57
|
+
?? componentAndProps[(0, shared_1.capitalize)((0, shared_1.camelize)(tag))];
|
|
58
|
+
if (props) {
|
|
59
|
+
return props;
|
|
60
|
+
}
|
|
61
|
+
return await this.sendRequest('subscribeComponentProps', fileName, tag);
|
|
62
|
+
}
|
|
63
|
+
update() {
|
|
64
|
+
if (!this.connecting && !this.projectInfo) {
|
|
65
|
+
this.connecting = true;
|
|
66
|
+
this.connect();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
connect() {
|
|
70
|
+
this.socket = net.connect(this.path);
|
|
71
|
+
this.socket.on('data', this.onData.bind(this));
|
|
72
|
+
this.socket.on('connect', async () => {
|
|
73
|
+
const projectInfo = await this.sendRequest('projectInfo', '');
|
|
74
|
+
if (projectInfo) {
|
|
75
|
+
console.log('TSServer project ready:', projectInfo.name);
|
|
76
|
+
this.projectInfo = projectInfo;
|
|
77
|
+
this.containsFileCache.clear();
|
|
78
|
+
exports.onServerReady.forEach(cb => cb());
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.close();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.socket.on('error', err => {
|
|
85
|
+
if (err.code === 'ECONNREFUSED') {
|
|
86
|
+
try {
|
|
87
|
+
console.log('Deleteing invalid named pipe file:', this.path);
|
|
88
|
+
fs.promises.unlink(this.path);
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
}
|
|
92
|
+
this.close();
|
|
93
|
+
});
|
|
94
|
+
this.socket.on('timeout', () => {
|
|
95
|
+
this.close();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
close() {
|
|
99
|
+
this.connecting = false;
|
|
100
|
+
this.projectInfo = undefined;
|
|
101
|
+
this.socket?.end();
|
|
102
|
+
}
|
|
103
|
+
onData(chunk) {
|
|
104
|
+
this.dataChunks.push(chunk);
|
|
105
|
+
const data = Buffer.concat(this.dataChunks);
|
|
106
|
+
const text = data.toString();
|
|
107
|
+
if (text.endsWith('\n\n')) {
|
|
108
|
+
this.dataChunks.length = 0;
|
|
109
|
+
const results = text.split('\n\n');
|
|
110
|
+
for (let result of results) {
|
|
111
|
+
result = result.trim();
|
|
112
|
+
if (!result) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const data = JSON.parse(result.trim());
|
|
117
|
+
if (typeof data[0] === 'number') {
|
|
118
|
+
const [seq, res] = data;
|
|
119
|
+
this.requestHandlers.get(seq)?.(res);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const [type, fileName, res] = data;
|
|
123
|
+
this.onNotification(type, fileName, res);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
console.error('JSON parse error:', e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
onNotification(type, fileName, data) {
|
|
133
|
+
// console.log(`[${type}] ${fileName} ${JSON.stringify(data)}`);
|
|
134
|
+
if (type === 'componentNamesUpdated') {
|
|
135
|
+
let components = this.componentNamesAndProps.get(fileName);
|
|
136
|
+
if (!components) {
|
|
137
|
+
components = {};
|
|
138
|
+
this.componentNamesAndProps.set(fileName, components);
|
|
139
|
+
}
|
|
140
|
+
const newNames = data;
|
|
141
|
+
const newNameSet = new Set(newNames);
|
|
142
|
+
for (const name in components) {
|
|
143
|
+
if (!newNameSet.has(name)) {
|
|
144
|
+
delete components[name];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const name of newNames) {
|
|
148
|
+
if (!components[name]) {
|
|
149
|
+
components[name] = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (type === 'componentPropsUpdated') {
|
|
154
|
+
const components = this.componentNamesAndProps.get(fileName) ?? {};
|
|
155
|
+
const [name, props] = data;
|
|
156
|
+
components[name] = props;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.error('Unknown notification type:', type);
|
|
160
|
+
debugger;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
sendRequest(requestType, fileName, ...args) {
|
|
164
|
+
return new Promise(resolve => {
|
|
165
|
+
const seq = this.seq++;
|
|
166
|
+
// console.time(`[${seq}] ${requestType} ${fileName}`);
|
|
167
|
+
this.requestHandlers.set(seq, data => {
|
|
168
|
+
// console.timeEnd(`[${seq}] ${requestType} ${fileName}`);
|
|
169
|
+
this.requestHandlers.delete(seq);
|
|
170
|
+
resolve(data);
|
|
171
|
+
clearInterval(retryTimer);
|
|
172
|
+
});
|
|
173
|
+
const retry = () => {
|
|
174
|
+
const data = [seq, requestType, fileName, ...args];
|
|
175
|
+
this.socket.write(JSON.stringify(data) + '\n\n');
|
|
176
|
+
};
|
|
177
|
+
retry();
|
|
178
|
+
const retryTimer = setInterval(retry, 1000);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.configuredServers = [];
|
|
183
|
+
exports.inferredServers = [];
|
|
184
|
+
exports.onServerReady = [];
|
|
185
|
+
for (let i = 0; i < 10; i++) {
|
|
186
|
+
exports.configuredServers.push(new NamedPipeServer(1, i));
|
|
187
|
+
exports.inferredServers.push(new NamedPipeServer(0, i));
|
|
188
|
+
}
|
|
189
|
+
async function getBestServer(fileName) {
|
|
190
|
+
for (const server of exports.configuredServers) {
|
|
191
|
+
server.update();
|
|
192
|
+
}
|
|
193
|
+
let servers = (await Promise.all(exports.configuredServers.map(async (server) => {
|
|
194
|
+
const projectInfo = server.projectInfo;
|
|
195
|
+
if (!projectInfo) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const containsFile = await server.containsFile(fileName);
|
|
199
|
+
if (!containsFile) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
return server;
|
|
203
|
+
}))).filter(server => !!server);
|
|
204
|
+
// Sort servers by tsconfig
|
|
205
|
+
servers.sort((a, b) => sortTSConfigs(fileName, a.projectInfo.name, b.projectInfo.name));
|
|
206
|
+
if (servers.length) {
|
|
207
|
+
// Return the first server
|
|
208
|
+
return servers[0];
|
|
209
|
+
}
|
|
210
|
+
for (const server of exports.inferredServers) {
|
|
211
|
+
server.update();
|
|
212
|
+
}
|
|
213
|
+
servers = (await Promise.all(exports.inferredServers.map(server => {
|
|
214
|
+
const projectInfo = server.projectInfo;
|
|
215
|
+
if (!projectInfo) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Check if the file is in the project's directory
|
|
219
|
+
if (path.relative(projectInfo.currentDirectory, fileName).startsWith('..')) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
return server;
|
|
223
|
+
}))).filter(server => !!server);
|
|
224
|
+
// Sort servers by directory
|
|
225
|
+
servers.sort((a, b) => b.projectInfo.currentDirectory.replace(/\\/g, '/').split('/').length
|
|
226
|
+
- a.projectInfo.currentDirectory.replace(/\\/g, '/').split('/').length);
|
|
227
|
+
if (servers.length) {
|
|
228
|
+
// Return the first server
|
|
229
|
+
return servers[0];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function sortTSConfigs(file, a, b) {
|
|
233
|
+
const inA = isFileInDir(file, path.dirname(a));
|
|
234
|
+
const inB = isFileInDir(file, path.dirname(b));
|
|
235
|
+
if (inA !== inB) {
|
|
236
|
+
const aWeight = inA ? 1 : 0;
|
|
237
|
+
const bWeight = inB ? 1 : 0;
|
|
238
|
+
return bWeight - aWeight;
|
|
239
|
+
}
|
|
240
|
+
const aLength = a.split('/').length;
|
|
241
|
+
const bLength = b.split('/').length;
|
|
242
|
+
return bLength - aLength;
|
|
243
|
+
}
|
|
244
|
+
function isFileInDir(fileName, dir) {
|
|
245
|
+
const relative = path.relative(dir, fileName);
|
|
246
|
+
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vue/typescript-plugin",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"**/*.js",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@volar/typescript": "~2.4.11",
|
|
17
|
-
"@vue/language-core": "3.0.0-alpha.
|
|
17
|
+
"@vue/language-core": "3.0.0-alpha.2",
|
|
18
18
|
"@vue/shared": "^3.5.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^22.10.4"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "79247b7c24b7202ec676723440fdb36c38e6d450"
|
|
24
24
|
}
|