/
proxy.ts
127 lines (111 loc) · 3.58 KB
/
proxy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import * as ts from 'typescript/lib/tsserverlibrary';
import * as vue from '@volar/vue-typescript';
import * as apis from './apis';
import { createTypeScriptRuntime, TypeScriptRuntime } from '@volar/vue-typescript';
import { tsShared } from '@volar/vue-typescript';
let projectVersion = 0;
export function createProgramProxy(
options: ts.CreateProgramOptions, // rootNamesOrOptions: readonly string[] | CreateProgramOptions,
_options?: ts.CompilerOptions,
_host?: ts.CompilerHost,
_oldProgram?: ts.Program,
_configFileParsingDiagnostics?: readonly ts.Diagnostic[],
) {
if (!options.options.noEmit && !options.options.emitDeclarationOnly)
return doThrow('js emit is not support');
if (!options.host)
return doThrow('!options.host');
projectVersion++;
const host = options.host;
const vueCompilerOptions = getVueCompilerOptions();
const scripts = new Map<string, {
projectVersion: number,
modifiedTime: number,
scriptSnapshot: ts.IScriptSnapshot,
version: string,
}>();
const vueLsHost: vue.LanguageServiceHost = {
...host,
resolveModuleNames: undefined, // avoid failed with tsc built-in fileExists
writeFile: (fileName, content) => {
if (fileName.indexOf('__VLS_') === -1) {
host.writeFile(fileName, content, false);
}
},
getCompilationSettings: () => options.options,
getVueCompilationSettings: () => vueCompilerOptions,
getScriptFileNames: () => {
return options.rootNames as string[];
},
getScriptVersion,
getScriptSnapshot,
getProjectVersion: () => {
return projectVersion.toString();
},
getProjectReferences: () => options.projectReferences,
};
const tsRuntime: TypeScriptRuntime = (options.oldProgram as any)?.__VLS_tsRuntime ?? createTypeScriptRuntime({
typescript: ts,
baseCssModuleType: 'any',
getCssClasses: () => ({}),
vueLsHost: vueLsHost,
isVueTsc: true,
});
tsRuntime.update(); // must update before getProgram() to update virtual scripts
const proxyApis = apis.register(ts, tsRuntime);
const program = new Proxy<ts.Program>({} as ts.Program, {
get: (_, property: keyof ts.Program) => {
tsRuntime.update();
if (property in proxyApis) {
return proxyApis[property as keyof typeof proxyApis];
}
return tsRuntime.getTsLs().getProgram()![property];
},
});
(program as any).__VLS_tsRuntime = tsRuntime;
for (const rootName of options.rootNames) {
// register file watchers
host.getSourceFile(rootName, ts.ScriptTarget.ESNext);
}
return program;
function getVueCompilerOptions(): vue.VueCompilerOptions {
const tsConfig = options.options.configFilePath;
if (typeof tsConfig === 'string') {
return tsShared.createParsedCommandLine(ts, ts.sys, tsConfig).vueOptions;
}
return {};
}
function getScriptVersion(fileName: string) {
return getScript(fileName)?.version ?? '';
}
function getScriptSnapshot(fileName: string) {
return getScript(fileName)?.scriptSnapshot;
}
function getScript(fileName: string) {
const script = scripts.get(fileName);
if (script?.projectVersion === projectVersion) {
return script;
}
const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf() ?? 0;
if (script?.modifiedTime === modifiedTime) {
return script;
}
if (host.fileExists(fileName)) {
const fileContent = host.readFile(fileName);
if (fileContent !== undefined) {
const script = {
projectVersion,
modifiedTime,
scriptSnapshot: ts.ScriptSnapshot.fromString(fileContent),
version: host.createHash?.(fileContent) ?? fileContent,
};
scripts.set(fileName, script);
return script;
}
}
}
}
function doThrow(msg: string) {
console.error(msg);
throw msg;
}