@volar/typescript 2.4.11 → 2.4.13
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/lib/node/proxyCreateProgram.js +0 -2
- package/lib/node/proxyLanguageService.d.ts +12 -0
- package/lib/node/proxyLanguageService.js +26 -22
- package/lib/node/transform.d.ts +7 -0
- package/lib/node/transform.js +12 -0
- package/lib/quickstart/createAsyncLanguageServicePlugin.d.ts +21 -5
- package/lib/quickstart/createAsyncLanguageServicePlugin.js +85 -108
- package/lib/quickstart/createLanguageServicePlugin.d.ts +8 -10
- package/lib/quickstart/createLanguageServicePlugin.js +16 -77
- package/lib/quickstart/languageServicePluginCommon.d.ts +24 -0
- package/lib/quickstart/languageServicePluginCommon.js +109 -0
- package/package.json +4 -4
|
@@ -170,8 +170,6 @@ function proxyCreateProgram(ts, original, create) {
|
|
|
170
170
|
}
|
|
171
171
|
const program = Reflect.apply(target, thisArg, args);
|
|
172
172
|
(0, decorateProgram_1.decorateProgram)(language, program);
|
|
173
|
-
// TODO: #128
|
|
174
|
-
program.__volar__ = { language };
|
|
175
173
|
return program;
|
|
176
174
|
},
|
|
177
175
|
});
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { Language } from '@volar/language-core';
|
|
2
2
|
import type * as ts from 'typescript';
|
|
3
|
+
/**
|
|
4
|
+
* Creates and returns a Proxy around the base TypeScript LanguageService.
|
|
5
|
+
*
|
|
6
|
+
* This is used by the Volar TypeScript Plugin (which can be created by `createLanguageServicePlugin`
|
|
7
|
+
* and `createAsyncLanguageServicePlugin`) as an adapter layer between the TypeScript Language Service
|
|
8
|
+
* plugin API (see https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin)
|
|
9
|
+
* and a Volar `Language`.
|
|
10
|
+
*
|
|
11
|
+
* Once the `initialize` method is called, the proxy will begin intercepting requests and
|
|
12
|
+
* enhancing the default behavior of the LanguageService with enhancements based on
|
|
13
|
+
* the Volar `Language` that has been passed to `initialize`.
|
|
14
|
+
*/
|
|
3
15
|
export declare function createProxyLanguageService(languageService: ts.LanguageService): {
|
|
4
16
|
initialize(language: Language<string>): void;
|
|
5
17
|
proxy: ts.LanguageService;
|
|
@@ -6,6 +6,18 @@ const dedupe_1 = require("./dedupe");
|
|
|
6
6
|
const transform_1 = require("./transform");
|
|
7
7
|
const utils_1 = require("./utils");
|
|
8
8
|
const windowsPathReg = /\\/g;
|
|
9
|
+
/**
|
|
10
|
+
* Creates and returns a Proxy around the base TypeScript LanguageService.
|
|
11
|
+
*
|
|
12
|
+
* This is used by the Volar TypeScript Plugin (which can be created by `createLanguageServicePlugin`
|
|
13
|
+
* and `createAsyncLanguageServicePlugin`) as an adapter layer between the TypeScript Language Service
|
|
14
|
+
* plugin API (see https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin)
|
|
15
|
+
* and a Volar `Language`.
|
|
16
|
+
*
|
|
17
|
+
* Once the `initialize` method is called, the proxy will begin intercepting requests and
|
|
18
|
+
* enhancing the default behavior of the LanguageService with enhancements based on
|
|
19
|
+
* the Volar `Language` that has been passed to `initialize`.
|
|
20
|
+
*/
|
|
9
21
|
function createProxyLanguageService(languageService) {
|
|
10
22
|
const proxyCache = new Map();
|
|
11
23
|
let getProxyMethod;
|
|
@@ -128,10 +140,9 @@ function getFormattingEditsForRange(language, getFormattingEditsForRange) {
|
|
|
128
140
|
return [];
|
|
129
141
|
}
|
|
130
142
|
if (serviceScript) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const edits = getFormattingEditsForRange(targetScript.id, generateStart, generateEnd, options);
|
|
143
|
+
const generatedRange = (0, transform_1.toGeneratedRange)(language, serviceScript, sourceScript, start, end, language_core_1.isFormattingEnabled);
|
|
144
|
+
if (generatedRange !== undefined) {
|
|
145
|
+
const edits = getFormattingEditsForRange(targetScript.id, generatedRange[0], generatedRange[1], options);
|
|
135
146
|
return edits
|
|
136
147
|
.map(edit => (0, transform_1.transformTextChange)(sourceScript, language, serviceScript, edit, false, language_core_1.isFormattingEnabled)?.[1])
|
|
137
148
|
.filter(edit => !!edit);
|
|
@@ -523,10 +534,9 @@ function getCodeFixesAtPosition(language, getCodeFixesAtPosition) {
|
|
|
523
534
|
return [];
|
|
524
535
|
}
|
|
525
536
|
if (serviceScript) {
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
fixes = getCodeFixesAtPosition(targetScript.id, generateStart, generateEnd, errorCodes, formatOptions, preferences);
|
|
537
|
+
const generateRange = (0, transform_1.toGeneratedRange)(language, serviceScript, sourceScript, start, end, language_core_1.isCodeActionsEnabled);
|
|
538
|
+
if (generateRange !== undefined) {
|
|
539
|
+
fixes = getCodeFixesAtPosition(targetScript.id, generateRange[0], generateRange[1], errorCodes, formatOptions, preferences);
|
|
530
540
|
}
|
|
531
541
|
}
|
|
532
542
|
else {
|
|
@@ -550,23 +560,17 @@ function getEncodedSemanticClassifications(language, getEncodedSemanticClassific
|
|
|
550
560
|
};
|
|
551
561
|
}
|
|
552
562
|
if (serviceScript) {
|
|
553
|
-
let start;
|
|
554
|
-
let end;
|
|
555
563
|
const map = language.maps.get(serviceScript.code, targetScript);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
end = Math.max(end, mapping.generatedOffsets[mapping.generatedOffsets.length - 1] + (mapping.generatedLengths ?? mapping.lengths)[mapping.lengths.length - 1]);
|
|
563
|
-
}
|
|
564
|
+
const mapped = (0, language_core_1.findOverlapCodeRange)(span.start, span.start + span.length, map, language_core_1.isSemanticTokensEnabled);
|
|
565
|
+
if (!mapped) {
|
|
566
|
+
return {
|
|
567
|
+
spans: [],
|
|
568
|
+
endOfLineState: 0
|
|
569
|
+
};
|
|
564
570
|
}
|
|
565
|
-
start ??= 0;
|
|
566
|
-
end ??= targetScript.snapshot.getLength();
|
|
567
571
|
const mappingOffset = (0, transform_1.getMappingOffset)(language, serviceScript);
|
|
568
|
-
start
|
|
569
|
-
end
|
|
572
|
+
const start = mapped.start + mappingOffset;
|
|
573
|
+
const end = mapped.end + mappingOffset;
|
|
570
574
|
const result = getEncodedSemanticClassifications(targetScript.id, { start, length: end - start }, format);
|
|
571
575
|
const spans = [];
|
|
572
576
|
for (let i = 0; i < result.spans.length; i += 3) {
|
package/lib/node/transform.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { CodeInformation, SourceScript } from '@volar/language-core';
|
|
|
2
2
|
import { Language } from '@volar/language-core';
|
|
3
3
|
import type * as ts from 'typescript';
|
|
4
4
|
import type { TypeScriptServiceScript } from '../..';
|
|
5
|
+
/**
|
|
6
|
+
* This file contains a number of facilities for transforming `ts.Diagnostic`s returned
|
|
7
|
+
* from the base TypeScript LanguageService, which reference locations in generated
|
|
8
|
+
* TS code (e.g. the TypeScript codegen'd from the script portion of a .vue file) into locations
|
|
9
|
+
* in the script portion of the .vue file.
|
|
10
|
+
*/
|
|
5
11
|
export declare function transformCallHierarchyItem(language: Language<string>, item: ts.CallHierarchyItem, fallbackToAnyMatch: boolean, filter: (data: CodeInformation) => boolean): ts.CallHierarchyItem;
|
|
6
12
|
export declare function transformDiagnostic<T extends ts.Diagnostic>(language: Language<string>, diagnostic: T, program: ts.Program | undefined, isTsc: boolean): T | undefined;
|
|
7
13
|
export declare function fillSourceFileText(language: Language<string>, sourceFile: ts.SourceFile): void;
|
|
@@ -16,6 +22,7 @@ export declare function transformTextSpan(sourceScript: SourceScript<string> | u
|
|
|
16
22
|
export declare function toSourceOffset(sourceScript: SourceScript<string> | undefined, language: Language<string>, serviceScript: TypeScriptServiceScript, position: number, filter: (data: CodeInformation) => boolean): [fileName: string, offset: number] | undefined;
|
|
17
23
|
export declare function toSourceRanges(sourceScript: SourceScript<string> | undefined, language: Language<string>, serviceScript: TypeScriptServiceScript, start: number, end: number, fallbackToAnyMatch: boolean, filter: (data: CodeInformation) => boolean): Generator<[fileName: string, start: number, end: number]>;
|
|
18
24
|
export declare function toSourceOffsets(sourceScript: SourceScript<string> | undefined, language: Language<string>, serviceScript: TypeScriptServiceScript, position: number, filter: (data: CodeInformation) => boolean): Generator<[fileName: string, offset: number]>;
|
|
25
|
+
export declare function toGeneratedRange(language: Language, serviceScript: TypeScriptServiceScript, sourceScript: SourceScript<string>, start: number, end: number, filter: (data: CodeInformation) => boolean): readonly [number, number] | undefined;
|
|
19
26
|
export declare function toGeneratedRanges(language: Language, serviceScript: TypeScriptServiceScript, sourceScript: SourceScript<string>, start: number, end: number, filter: (data: CodeInformation) => boolean): Generator<readonly [number, number], void, unknown>;
|
|
20
27
|
export declare function toGeneratedOffset(language: Language, serviceScript: TypeScriptServiceScript, sourceScript: SourceScript<string>, position: number, filter: (data: CodeInformation) => boolean): number | undefined;
|
|
21
28
|
export declare function toGeneratedOffsets(language: Language, serviceScript: TypeScriptServiceScript, sourceScript: SourceScript<string>, position: number, filter: (data: CodeInformation) => boolean): Generator<readonly [number, import("@volar/language-core").Mapping<CodeInformation>], void, unknown>;
|
package/lib/node/transform.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.transformTextSpan = transformTextSpan;
|
|
|
11
11
|
exports.toSourceOffset = toSourceOffset;
|
|
12
12
|
exports.toSourceRanges = toSourceRanges;
|
|
13
13
|
exports.toSourceOffsets = toSourceOffsets;
|
|
14
|
+
exports.toGeneratedRange = toGeneratedRange;
|
|
14
15
|
exports.toGeneratedRanges = toGeneratedRanges;
|
|
15
16
|
exports.toGeneratedOffset = toGeneratedOffset;
|
|
16
17
|
exports.toGeneratedOffsets = toGeneratedOffsets;
|
|
@@ -19,6 +20,12 @@ const language_core_1 = require("@volar/language-core");
|
|
|
19
20
|
const utils_1 = require("./utils");
|
|
20
21
|
const transformedDiagnostics = new WeakMap();
|
|
21
22
|
const transformedSourceFile = new WeakSet();
|
|
23
|
+
/**
|
|
24
|
+
* This file contains a number of facilities for transforming `ts.Diagnostic`s returned
|
|
25
|
+
* from the base TypeScript LanguageService, which reference locations in generated
|
|
26
|
+
* TS code (e.g. the TypeScript codegen'd from the script portion of a .vue file) into locations
|
|
27
|
+
* in the script portion of the .vue file.
|
|
28
|
+
*/
|
|
22
29
|
function transformCallHierarchyItem(language, item, fallbackToAnyMatch, filter) {
|
|
23
30
|
const span = transformSpan(language, item.file, item.span, fallbackToAnyMatch, filter);
|
|
24
31
|
const selectionSpan = transformSpan(language, item.file, item.selectionSpan, fallbackToAnyMatch, filter);
|
|
@@ -224,6 +231,11 @@ function* toSourceOffsets(sourceScript, language, serviceScript, position, filte
|
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
}
|
|
234
|
+
function toGeneratedRange(language, serviceScript, sourceScript, start, end, filter) {
|
|
235
|
+
for (const result of toGeneratedRanges(language, serviceScript, sourceScript, start, end, filter)) {
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
227
239
|
function* toGeneratedRanges(language, serviceScript, sourceScript, start, end, filter) {
|
|
228
240
|
const map = language.maps.get(serviceScript.code, sourceScript);
|
|
229
241
|
for (const [generateStart, generateEnd] of map.toGeneratedRange(start, end, true, filter)) {
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import { Language, LanguagePlugin } from '@volar/language-core';
|
|
2
1
|
import type * as ts from 'typescript';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import type { createPluginCallbackAsync } from './languageServicePluginCommon';
|
|
3
|
+
/**
|
|
4
|
+
* Creates and returns a TS Service Plugin that supports async initialization.
|
|
5
|
+
* Essentially, this functions the same as `createLanguageServicePlugin`, but supports
|
|
6
|
+
* use cases in which the plugin callback must be async. For example in mdx-analyzer
|
|
7
|
+
* and Glint, this async variant is required because Glint + mdx-analyzer are written
|
|
8
|
+
* in ESM and get transpiled to CJS, which requires usage of `await import()` to load
|
|
9
|
+
* the necessary dependencies and fully initialize the plugin.
|
|
10
|
+
*
|
|
11
|
+
* To handle the period of time in which the plugin is initializing, this async
|
|
12
|
+
* variant stubs a number of methods on the LanguageServiceHost to handle the uninitialized state.
|
|
13
|
+
*
|
|
14
|
+
* Additionally, this async variant requires a few extra args pertaining to
|
|
15
|
+
* file extensions intended to be handled by the TS Plugin. In the synchronous variant,
|
|
16
|
+
* these can be synchronously inferred from elsewhere but for the async variant, they
|
|
17
|
+
* need to be passed in.
|
|
18
|
+
*
|
|
19
|
+
* See https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin for
|
|
20
|
+
* more information.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createAsyncLanguageServicePlugin(extensions: string[], getScriptKindForExtraExtensions: ts.ScriptKind | ((fileName: string) => ts.ScriptKind), createPluginCallbackAsync: createPluginCallbackAsync): ts.server.PluginModuleFactory;
|
|
@@ -1,130 +1,107 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAsyncLanguageServicePlugin = createAsyncLanguageServicePlugin;
|
|
4
|
-
const language_core_1 = require("@volar/language-core");
|
|
5
|
-
const common_1 = require("../common");
|
|
6
4
|
const proxyLanguageService_1 = require("../node/proxyLanguageService");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const languageServicePluginCommon_1 = require("./languageServicePluginCommon");
|
|
6
|
+
/**
|
|
7
|
+
* Creates and returns a TS Service Plugin that supports async initialization.
|
|
8
|
+
* Essentially, this functions the same as `createLanguageServicePlugin`, but supports
|
|
9
|
+
* use cases in which the plugin callback must be async. For example in mdx-analyzer
|
|
10
|
+
* and Glint, this async variant is required because Glint + mdx-analyzer are written
|
|
11
|
+
* in ESM and get transpiled to CJS, which requires usage of `await import()` to load
|
|
12
|
+
* the necessary dependencies and fully initialize the plugin.
|
|
13
|
+
*
|
|
14
|
+
* To handle the period of time in which the plugin is initializing, this async
|
|
15
|
+
* variant stubs a number of methods on the LanguageServiceHost to handle the uninitialized state.
|
|
16
|
+
*
|
|
17
|
+
* Additionally, this async variant requires a few extra args pertaining to
|
|
18
|
+
* file extensions intended to be handled by the TS Plugin. In the synchronous variant,
|
|
19
|
+
* these can be synchronously inferred from elsewhere but for the async variant, they
|
|
20
|
+
* need to be passed in.
|
|
21
|
+
*
|
|
22
|
+
* See https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin for
|
|
23
|
+
* more information.
|
|
24
|
+
*/
|
|
25
|
+
function createAsyncLanguageServicePlugin(extensions, getScriptKindForExtraExtensions, createPluginCallbackAsync) {
|
|
10
26
|
return modules => {
|
|
11
27
|
const { typescript: ts } = modules;
|
|
12
28
|
const pluginModule = {
|
|
13
29
|
create(info) {
|
|
14
|
-
if (!
|
|
15
|
-
|
|
16
|
-
createLanguageServicePlugin_1.decoratedLanguageServices.add(info.languageService);
|
|
17
|
-
createLanguageServicePlugin_1.decoratedLanguageServiceHosts.add(info.languageServiceHost);
|
|
18
|
-
const emptySnapshot = ts.ScriptSnapshot.fromString('');
|
|
19
|
-
const getScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
|
|
20
|
-
const getScriptVersion = info.languageServiceHost.getScriptVersion.bind(info.languageServiceHost);
|
|
21
|
-
const getScriptKind = info.languageServiceHost.getScriptKind?.bind(info.languageServiceHost);
|
|
22
|
-
const getProjectVersion = info.languageServiceHost.getProjectVersion?.bind(info.languageServiceHost);
|
|
23
|
-
let initialized = false;
|
|
24
|
-
info.languageServiceHost.getScriptSnapshot = fileName => {
|
|
25
|
-
if (!initialized) {
|
|
26
|
-
if (extensions.some(ext => fileName.endsWith(ext))) {
|
|
27
|
-
return emptySnapshot;
|
|
28
|
-
}
|
|
29
|
-
if (getScriptInfo(fileName)?.isScriptOpen()) {
|
|
30
|
-
return emptySnapshot;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return getScriptSnapshot(fileName);
|
|
34
|
-
};
|
|
35
|
-
info.languageServiceHost.getScriptVersion = fileName => {
|
|
36
|
-
if (!initialized) {
|
|
37
|
-
if (extensions.some(ext => fileName.endsWith(ext))) {
|
|
38
|
-
return 'initializing...';
|
|
39
|
-
}
|
|
40
|
-
if (getScriptInfo(fileName)?.isScriptOpen()) {
|
|
41
|
-
return getScriptVersion(fileName) + ',initializing...';
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return getScriptVersion(fileName);
|
|
45
|
-
};
|
|
46
|
-
if (getScriptKind) {
|
|
47
|
-
info.languageServiceHost.getScriptKind = fileName => {
|
|
48
|
-
if (!initialized && extensions.some(ext => fileName.endsWith(ext))) {
|
|
49
|
-
// bypass upstream bug https://github.com/microsoft/TypeScript/issues/57631
|
|
50
|
-
// TODO: check if the bug is fixed in 5.5
|
|
51
|
-
if (typeof getScriptKindForExtraExtensions === 'function') {
|
|
52
|
-
return getScriptKindForExtraExtensions(fileName);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
return getScriptKindForExtraExtensions;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return getScriptKind(fileName);
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (getProjectVersion) {
|
|
62
|
-
info.languageServiceHost.getProjectVersion = () => {
|
|
63
|
-
if (!initialized) {
|
|
64
|
-
return getProjectVersion() + ',initializing...';
|
|
65
|
-
}
|
|
66
|
-
return getProjectVersion();
|
|
67
|
-
};
|
|
68
|
-
}
|
|
30
|
+
if (!(0, languageServicePluginCommon_1.isHasAlreadyDecoratedLanguageService)(info)) {
|
|
31
|
+
const state = decorateWithAsyncInitializationHandling(ts, info, extensions, getScriptKindForExtraExtensions);
|
|
69
32
|
const { proxy, initialize } = (0, proxyLanguageService_1.createProxyLanguageService)(info.languageService);
|
|
70
33
|
info.languageService = proxy;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{ getLanguageId: common_1.resolveFileLanguageId },
|
|
75
|
-
], new language_core_1.FileMap(ts.sys.useCaseSensitiveFileNames), (fileName, _, shouldRegister) => {
|
|
76
|
-
let snapshot;
|
|
77
|
-
if (shouldRegister) {
|
|
78
|
-
// We need to trigger registration of the script file with the project, see #250
|
|
79
|
-
snapshot = getScriptSnapshot(fileName);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
83
|
-
if (!snapshot) {
|
|
84
|
-
// trigger projectService.getOrCreateScriptInfoNotOpenedByClient
|
|
85
|
-
info.project.getScriptVersion(fileName);
|
|
86
|
-
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (snapshot) {
|
|
90
|
-
language.scripts.set(fileName, snapshot);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
language.scripts.delete(fileName);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
initialize(language);
|
|
97
|
-
(0, decorateLanguageServiceHost_1.decorateLanguageServiceHost)(ts, language, info.languageServiceHost);
|
|
98
|
-
setup?.(language);
|
|
99
|
-
initialized = true;
|
|
34
|
+
createPluginCallbackAsync(ts, info).then(createPluginResult => {
|
|
35
|
+
(0, languageServicePluginCommon_1.createLanguageCommon)(createPluginResult, ts, info, initialize);
|
|
36
|
+
state.initialized = true;
|
|
100
37
|
if ('markAsDirty' in info.project && typeof info.project.markAsDirty === 'function') {
|
|
38
|
+
// This is an attempt to mark the project as dirty so that in case the IDE/tsserver
|
|
39
|
+
// already finished a first pass of generating diagnostics (or other things), another
|
|
40
|
+
// pass will be triggered which should hopefully make use of this now-initialized plugin.
|
|
101
41
|
info.project.markAsDirty();
|
|
102
42
|
}
|
|
103
43
|
});
|
|
104
44
|
}
|
|
105
45
|
return info.languageService;
|
|
106
|
-
function getScriptInfo(fileName) {
|
|
107
|
-
// getSnapshot could be crashed if the file is too large
|
|
108
|
-
try {
|
|
109
|
-
return info.project.getScriptInfo(fileName);
|
|
110
|
-
}
|
|
111
|
-
catch { }
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
getExternalFiles(project, updateLevel = 0) {
|
|
115
|
-
if (updateLevel >= 1
|
|
116
|
-
|| !createLanguageServicePlugin_1.externalFiles.has(project)) {
|
|
117
|
-
const oldFiles = createLanguageServicePlugin_1.externalFiles.get(project);
|
|
118
|
-
const newFiles = extensions.length ? (0, decorateLanguageServiceHost_1.searchExternalFiles)(ts, project, extensions) : [];
|
|
119
|
-
createLanguageServicePlugin_1.externalFiles.set(project, newFiles);
|
|
120
|
-
if (oldFiles && !(0, createLanguageServicePlugin_1.arrayItemsEqual)(oldFiles, newFiles)) {
|
|
121
|
-
project.refreshDiagnostics();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return createLanguageServicePlugin_1.externalFiles.get(project);
|
|
125
46
|
},
|
|
47
|
+
getExternalFiles: (0, languageServicePluginCommon_1.makeGetExternalFiles)(ts),
|
|
126
48
|
};
|
|
127
49
|
return pluginModule;
|
|
128
50
|
};
|
|
129
51
|
}
|
|
52
|
+
function decorateWithAsyncInitializationHandling(ts, info, extensions, getScriptKindForExtraExtensions) {
|
|
53
|
+
const emptySnapshot = ts.ScriptSnapshot.fromString('');
|
|
54
|
+
const getScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
|
|
55
|
+
const getScriptVersion = info.languageServiceHost.getScriptVersion.bind(info.languageServiceHost);
|
|
56
|
+
const getScriptKind = info.languageServiceHost.getScriptKind?.bind(info.languageServiceHost);
|
|
57
|
+
const getProjectVersion = info.languageServiceHost.getProjectVersion?.bind(info.languageServiceHost);
|
|
58
|
+
const getScriptInfo = (0, languageServicePluginCommon_1.makeGetScriptInfoWithLargeFileFailsafe)(info);
|
|
59
|
+
const state = { initialized: false };
|
|
60
|
+
info.languageServiceHost.getScriptSnapshot = fileName => {
|
|
61
|
+
if (!state.initialized) {
|
|
62
|
+
if (extensions.some(ext => fileName.endsWith(ext))) {
|
|
63
|
+
return emptySnapshot;
|
|
64
|
+
}
|
|
65
|
+
if (getScriptInfo(fileName)?.isScriptOpen()) {
|
|
66
|
+
return emptySnapshot;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return getScriptSnapshot(fileName);
|
|
70
|
+
};
|
|
71
|
+
info.languageServiceHost.getScriptVersion = fileName => {
|
|
72
|
+
if (!state.initialized) {
|
|
73
|
+
if (extensions.some(ext => fileName.endsWith(ext))) {
|
|
74
|
+
return 'initializing...';
|
|
75
|
+
}
|
|
76
|
+
if (getScriptInfo(fileName)?.isScriptOpen()) {
|
|
77
|
+
return getScriptVersion(fileName) + ',initializing...';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return getScriptVersion(fileName);
|
|
81
|
+
};
|
|
82
|
+
if (getScriptKind) {
|
|
83
|
+
info.languageServiceHost.getScriptKind = fileName => {
|
|
84
|
+
if (!state.initialized && extensions.some(ext => fileName.endsWith(ext))) {
|
|
85
|
+
// bypass upstream bug https://github.com/microsoft/TypeScript/issues/57631
|
|
86
|
+
// TODO: check if the bug is fixed in 5.5
|
|
87
|
+
if (typeof getScriptKindForExtraExtensions === 'function') {
|
|
88
|
+
return getScriptKindForExtraExtensions(fileName);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return getScriptKindForExtraExtensions;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return getScriptKind(fileName);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (getProjectVersion) {
|
|
98
|
+
info.languageServiceHost.getProjectVersion = () => {
|
|
99
|
+
if (!state.initialized) {
|
|
100
|
+
return getProjectVersion() + ',initializing...';
|
|
101
|
+
}
|
|
102
|
+
return getProjectVersion();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
130
107
|
//# sourceMappingURL=createAsyncLanguageServicePlugin.js.map
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { Language, LanguagePlugin } from '@volar/language-core';
|
|
2
1
|
import type * as ts from 'typescript';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export declare function arrayItemsEqual(a: string[], b: string[]): boolean;
|
|
2
|
+
import type { createPluginCallbackSync } from './languageServicePluginCommon';
|
|
3
|
+
/**
|
|
4
|
+
* Creates and returns a TS Service Plugin using Volar primitives.
|
|
5
|
+
*
|
|
6
|
+
* See https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin for
|
|
7
|
+
* more information.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createLanguageServicePlugin(createPluginCallback: createPluginCallbackSync): ts.server.PluginModuleFactory;
|
|
@@ -1,97 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decoratedLanguageServiceHosts = exports.decoratedLanguageServices = exports.projectExternalFileExtensions = exports.externalFiles = void 0;
|
|
4
3
|
exports.createLanguageServicePlugin = createLanguageServicePlugin;
|
|
5
|
-
exports.arrayItemsEqual = arrayItemsEqual;
|
|
6
|
-
const language_core_1 = require("@volar/language-core");
|
|
7
|
-
const common_1 = require("../common");
|
|
8
4
|
const proxyLanguageService_1 = require("../node/proxyLanguageService");
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
const languageServicePluginCommon_1 = require("./languageServicePluginCommon");
|
|
6
|
+
/**
|
|
7
|
+
* Creates and returns a TS Service Plugin using Volar primitives.
|
|
8
|
+
*
|
|
9
|
+
* See https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin for
|
|
10
|
+
* more information.
|
|
11
|
+
*/
|
|
12
|
+
function createLanguageServicePlugin(createPluginCallback) {
|
|
15
13
|
return modules => {
|
|
16
14
|
const { typescript: ts } = modules;
|
|
17
15
|
const pluginModule = {
|
|
18
16
|
create(info) {
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
exports.decoratedLanguageServiceHosts.add(info.languageServiceHost);
|
|
23
|
-
const { languagePlugins, setup } = create(ts, info);
|
|
24
|
-
const extensions = languagePlugins
|
|
17
|
+
if (!(0, languageServicePluginCommon_1.isHasAlreadyDecoratedLanguageService)(info)) {
|
|
18
|
+
const createPluginResult = createPluginCallback(ts, info);
|
|
19
|
+
const extensions = createPluginResult.languagePlugins
|
|
25
20
|
.map(plugin => plugin.typescript?.extraFileExtensions.map(ext => '.' + ext.extension) ?? [])
|
|
26
21
|
.flat();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...languagePlugins,
|
|
31
|
-
{ getLanguageId: common_1.resolveFileLanguageId },
|
|
32
|
-
], new language_core_1.FileMap(ts.sys.useCaseSensitiveFileNames), (fileName, _, shouldRegister) => {
|
|
33
|
-
let snapshot;
|
|
34
|
-
if (shouldRegister) {
|
|
35
|
-
// We need to trigger registration of the script file with the project, see #250
|
|
36
|
-
snapshot = getScriptSnapshot(fileName);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
40
|
-
if (!snapshot) {
|
|
41
|
-
// trigger projectService.getOrCreateScriptInfoNotOpenedByClient
|
|
42
|
-
info.project.getScriptVersion(fileName);
|
|
43
|
-
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (snapshot) {
|
|
47
|
-
language.scripts.set(fileName, snapshot);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
language.scripts.delete(fileName);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
22
|
+
// TODO: this logic does not seem to appear in the async variant
|
|
23
|
+
// (createAsyncLanguageServicePlugin)... bug?
|
|
24
|
+
languageServicePluginCommon_1.projectExternalFileExtensions.set(info.project, extensions);
|
|
53
25
|
const { proxy, initialize } = (0, proxyLanguageService_1.createProxyLanguageService)(info.languageService);
|
|
54
26
|
info.languageService = proxy;
|
|
55
|
-
|
|
56
|
-
(0, decorateLanguageServiceHost_1.decorateLanguageServiceHost)(ts, language, info.languageServiceHost);
|
|
57
|
-
setup?.(language);
|
|
27
|
+
(0, languageServicePluginCommon_1.createLanguageCommon)(createPluginResult, ts, info, initialize);
|
|
58
28
|
}
|
|
59
29
|
return info.languageService;
|
|
60
|
-
function getScriptInfo(fileName) {
|
|
61
|
-
// getSnapshot could be crashed if the file is too large
|
|
62
|
-
try {
|
|
63
|
-
return info.project.getScriptInfo(fileName);
|
|
64
|
-
}
|
|
65
|
-
catch { }
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
getExternalFiles(project, updateLevel = 0) {
|
|
69
|
-
if (updateLevel >= 1
|
|
70
|
-
|| !exports.externalFiles.has(project)) {
|
|
71
|
-
const oldFiles = exports.externalFiles.get(project);
|
|
72
|
-
const extensions = exports.projectExternalFileExtensions.get(project);
|
|
73
|
-
const newFiles = extensions?.length ? (0, decorateLanguageServiceHost_1.searchExternalFiles)(ts, project, extensions) : [];
|
|
74
|
-
exports.externalFiles.set(project, newFiles);
|
|
75
|
-
if (oldFiles && !arrayItemsEqual(oldFiles, newFiles)) {
|
|
76
|
-
project.refreshDiagnostics();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return exports.externalFiles.get(project);
|
|
80
30
|
},
|
|
31
|
+
getExternalFiles: (0, languageServicePluginCommon_1.makeGetExternalFiles)(ts),
|
|
81
32
|
};
|
|
82
33
|
return pluginModule;
|
|
83
34
|
};
|
|
84
35
|
}
|
|
85
|
-
function arrayItemsEqual(a, b) {
|
|
86
|
-
if (a.length !== b.length) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
const set = new Set(a);
|
|
90
|
-
for (const file of b) {
|
|
91
|
-
if (!set.has(file)) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
36
|
//# sourceMappingURL=createLanguageServicePlugin.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Language, LanguagePlugin } from '@volar/language-core/lib/types';
|
|
2
|
+
import type * as ts from 'typescript';
|
|
3
|
+
export declare const externalFiles: WeakMap<ts.server.Project, string[]>;
|
|
4
|
+
export declare const projectExternalFileExtensions: WeakMap<ts.server.Project, string[]>;
|
|
5
|
+
export declare const decoratedLanguageServices: WeakSet<ts.LanguageService>;
|
|
6
|
+
export declare const decoratedLanguageServiceHosts: WeakSet<ts.LanguageServiceHost>;
|
|
7
|
+
/**
|
|
8
|
+
* Wrap `getScriptInfo` to handle large files that may crash the language service.
|
|
9
|
+
*
|
|
10
|
+
* Introduced to fix issues with converting `relatedInformation` (in Diagnostics)
|
|
11
|
+
* when working with large files.
|
|
12
|
+
*
|
|
13
|
+
* https://github.com/volarjs/volar.js/commit/e242709a91e9d2919dc4fa59278dd266fd11e7a3
|
|
14
|
+
*/
|
|
15
|
+
export declare function makeGetScriptInfoWithLargeFileFailsafe(info: ts.server.PluginCreateInfo): (fileName: string) => ts.server.ScriptInfo | undefined;
|
|
16
|
+
export declare function createLanguageCommon(createPluginResult: createPluginCallbackReturnValue, ts: typeof import('typescript'), info: ts.server.PluginCreateInfo, initializeProxiedLanguageService: (language: Language<string>) => void): void;
|
|
17
|
+
export declare const makeGetExternalFiles: (ts: typeof import("typescript")) => (project: ts.server.Project, updateLevel?: number) => string[];
|
|
18
|
+
export type createPluginCallbackReturnValue = {
|
|
19
|
+
languagePlugins: LanguagePlugin<string>[];
|
|
20
|
+
setup?: (language: Language<string>) => void;
|
|
21
|
+
};
|
|
22
|
+
export type createPluginCallbackSync = (ts: typeof import('typescript'), info: ts.server.PluginCreateInfo) => createPluginCallbackReturnValue;
|
|
23
|
+
export type createPluginCallbackAsync = (ts: typeof import('typescript'), info: ts.server.PluginCreateInfo) => Promise<createPluginCallbackReturnValue>;
|
|
24
|
+
export declare function isHasAlreadyDecoratedLanguageService(info: ts.server.PluginCreateInfo): boolean;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeGetExternalFiles = exports.decoratedLanguageServiceHosts = exports.decoratedLanguageServices = exports.projectExternalFileExtensions = exports.externalFiles = void 0;
|
|
4
|
+
exports.makeGetScriptInfoWithLargeFileFailsafe = makeGetScriptInfoWithLargeFileFailsafe;
|
|
5
|
+
exports.createLanguageCommon = createLanguageCommon;
|
|
6
|
+
exports.isHasAlreadyDecoratedLanguageService = isHasAlreadyDecoratedLanguageService;
|
|
7
|
+
const language_core_1 = require("@volar/language-core");
|
|
8
|
+
const common_1 = require("../common");
|
|
9
|
+
const decorateLanguageServiceHost_1 = require("../node/decorateLanguageServiceHost");
|
|
10
|
+
exports.externalFiles = new WeakMap();
|
|
11
|
+
exports.projectExternalFileExtensions = new WeakMap();
|
|
12
|
+
exports.decoratedLanguageServices = new WeakSet();
|
|
13
|
+
exports.decoratedLanguageServiceHosts = new WeakSet();
|
|
14
|
+
/**
|
|
15
|
+
* Wrap `getScriptInfo` to handle large files that may crash the language service.
|
|
16
|
+
*
|
|
17
|
+
* Introduced to fix issues with converting `relatedInformation` (in Diagnostics)
|
|
18
|
+
* when working with large files.
|
|
19
|
+
*
|
|
20
|
+
* https://github.com/volarjs/volar.js/commit/e242709a91e9d2919dc4fa59278dd266fd11e7a3
|
|
21
|
+
*/
|
|
22
|
+
function makeGetScriptInfoWithLargeFileFailsafe(info) {
|
|
23
|
+
return (fileName) => {
|
|
24
|
+
// getSnapshot could be crashed if the file is too large
|
|
25
|
+
try {
|
|
26
|
+
return info.project.getScriptInfo(fileName);
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function createLanguageCommon(createPluginResult, ts, info, initializeProxiedLanguageService) {
|
|
32
|
+
const getScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
|
|
33
|
+
const getScriptInfo = makeGetScriptInfoWithLargeFileFailsafe(info);
|
|
34
|
+
const language = (0, language_core_1.createLanguage)([
|
|
35
|
+
...createPluginResult.languagePlugins,
|
|
36
|
+
{ getLanguageId: common_1.resolveFileLanguageId },
|
|
37
|
+
], new language_core_1.FileMap(ts.sys.useCaseSensitiveFileNames), (fileName, _, shouldRegister) => {
|
|
38
|
+
let snapshot;
|
|
39
|
+
if (shouldRegister) {
|
|
40
|
+
// We need to trigger registration of the script file with the project, see #250
|
|
41
|
+
snapshot = getScriptSnapshot(fileName);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
45
|
+
if (!snapshot) {
|
|
46
|
+
// trigger projectService.getOrCreateScriptInfoNotOpenedByClient
|
|
47
|
+
info.project.getScriptVersion(fileName);
|
|
48
|
+
snapshot = getScriptInfo(fileName)?.getSnapshot();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (snapshot) {
|
|
52
|
+
language.scripts.set(fileName, snapshot);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
language.scripts.delete(fileName);
|
|
56
|
+
}
|
|
57
|
+
}, targetFileName => {
|
|
58
|
+
// https://github.com/JetBrains/intellij-plugins/blob/6435723ad88fa296b41144162ebe3b8513f4949b/Angular/src-js/angular-service/src/ngCommands.ts#L88
|
|
59
|
+
info.session.change({
|
|
60
|
+
file: targetFileName,
|
|
61
|
+
line: 1,
|
|
62
|
+
offset: 1,
|
|
63
|
+
endLine: 1,
|
|
64
|
+
endOffset: 1,
|
|
65
|
+
insertString: '',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
initializeProxiedLanguageService(language);
|
|
69
|
+
(0, decorateLanguageServiceHost_1.decorateLanguageServiceHost)(ts, language, info.languageServiceHost);
|
|
70
|
+
createPluginResult.setup?.(language);
|
|
71
|
+
}
|
|
72
|
+
const makeGetExternalFiles = (ts) => (project, updateLevel = 0) => {
|
|
73
|
+
if (updateLevel >= 1
|
|
74
|
+
|| !exports.externalFiles.has(project)) {
|
|
75
|
+
const oldFiles = exports.externalFiles.get(project);
|
|
76
|
+
const extensions = exports.projectExternalFileExtensions.get(project);
|
|
77
|
+
const newFiles = extensions?.length ? (0, decorateLanguageServiceHost_1.searchExternalFiles)(ts, project, extensions) : [];
|
|
78
|
+
exports.externalFiles.set(project, newFiles);
|
|
79
|
+
if (oldFiles && !arrayItemsEqual(oldFiles, newFiles)) {
|
|
80
|
+
project.refreshDiagnostics();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return exports.externalFiles.get(project);
|
|
84
|
+
};
|
|
85
|
+
exports.makeGetExternalFiles = makeGetExternalFiles;
|
|
86
|
+
function arrayItemsEqual(a, b) {
|
|
87
|
+
if (a.length !== b.length) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const set = new Set(a);
|
|
91
|
+
for (const file of b) {
|
|
92
|
+
if (!set.has(file)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
function isHasAlreadyDecoratedLanguageService(info) {
|
|
99
|
+
if (exports.decoratedLanguageServices.has(info.languageService)
|
|
100
|
+
|| exports.decoratedLanguageServiceHosts.has(info.languageServiceHost)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
exports.decoratedLanguageServices.add(info.languageService);
|
|
105
|
+
exports.decoratedLanguageServiceHosts.add(info.languageServiceHost);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=languageServicePluginCommon.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volar/typescript",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"**/*.js",
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
"directory": "packages/typescript"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@volar/language-core": "2.4.
|
|
15
|
+
"@volar/language-core": "2.4.13",
|
|
16
16
|
"path-browserify": "^1.0.1",
|
|
17
17
|
"vscode-uri": "^3.0.8"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "latest",
|
|
21
21
|
"@types/path-browserify": "latest",
|
|
22
|
-
"@volar/language-service": "2.4.
|
|
22
|
+
"@volar/language-service": "2.4.13"
|
|
23
23
|
},
|
|
24
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "c5d727b9e73d0c6a45c64b38d74ca5c52363818f"
|
|
25
25
|
}
|