@esportsplus/typescript 0.28.5 → 0.29.1
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/.claude/skills/code-audit/registry-typescript.json +326 -0
- package/.github/workflows/publish.yml +1 -1
- package/build/cli/tsc.d.ts +10 -1
- package/build/cli/tsc.js +8 -5
- package/build/compiler/ast.d.ts +2 -5
- package/build/compiler/ast.js +1 -1
- package/build/compiler/coordinator.d.ts +1 -2
- package/build/compiler/coordinator.js +48 -30
- package/build/compiler/imports.js +7 -3
- package/build/compiler/language-service.d.ts +9 -0
- package/build/compiler/language-service.js +73 -0
- package/build/compiler/plugins/vite.js +11 -11
- package/package.json +10 -7
- package/src/cli/tsc.ts +12 -6
- package/src/compiler/ast.ts +2 -7
- package/src/compiler/coordinator.ts +63 -45
- package/src/compiler/imports.ts +11 -3
- package/src/compiler/language-service.ts +121 -0
- package/src/compiler/plugins/vite.ts +17 -15
- package/tests/cli/tsc.test.ts +155 -0
- package/tests/compiler/ast.test.ts +131 -0
- package/tests/compiler/code.test.ts +73 -0
- package/tests/compiler/coordinator.bench.ts +101 -0
- package/tests/compiler/coordinator.test.ts +855 -0
- package/tests/compiler/imports.test.ts +167 -0
- package/tests/compiler/language-service.test.ts +90 -0
- package/tests/compiler/plugins.test.ts +172 -0
- package/tests/compiler/uid.test.ts +70 -0
- package/tsconfig.base.json +0 -1
- package/vitest.config.ts +14 -0
- package/build/compiler/program.d.ts +0 -6
- package/build/compiler/program.js +0 -34
- package/src/compiler/program.ts +0 -55
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { ts } from '../../index.js';
|
|
2
2
|
import coordinator from '../coordinator.js';
|
|
3
|
-
import
|
|
4
|
-
const FILE_REGEX = /\.[tj]sx?$/;
|
|
3
|
+
import languageService from '../language-service.js';
|
|
5
4
|
const DIRECTORY_SEPARATOR_REGEX = /\\/g;
|
|
6
|
-
const
|
|
5
|
+
const FILE_REGEX = /\.[tj]sx?$/;
|
|
7
6
|
let contexts = new Map();
|
|
8
7
|
export default ({ name, onWatchChange, plugins }) => {
|
|
9
8
|
return ({ root } = {}) => {
|
|
@@ -18,15 +17,16 @@ export default ({ name, onWatchChange, plugins }) => {
|
|
|
18
17
|
return null;
|
|
19
18
|
}
|
|
20
19
|
try {
|
|
21
|
-
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'), prog =
|
|
22
|
-
if (sourceFile && sourceFile.getText().replace(LINE_ENDINGS_REGEX, '\n') !== code.replace(LINE_ENDINGS_REGEX, '\n')) {
|
|
23
|
-
sourceFile = undefined;
|
|
24
|
-
}
|
|
20
|
+
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'), prog = languageService.update(root || '', normalizedId, code), sourceFile = prog.getSourceFile(normalizedId);
|
|
25
21
|
if (!sourceFile) {
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
sourceFile = ts.createSourceFile(normalizedId, code, ts.ScriptTarget.Latest, true);
|
|
23
|
+
}
|
|
24
|
+
let key = root || '', ctx = contexts.get(key);
|
|
25
|
+
if (!ctx) {
|
|
26
|
+
ctx = new Map();
|
|
27
|
+
contexts.set(key, ctx);
|
|
28
28
|
}
|
|
29
|
-
let result = coordinator.transform(plugins, code, sourceFile, prog,
|
|
29
|
+
let result = coordinator.transform(plugins, code, sourceFile, prog, key, ctx);
|
|
30
30
|
if (!result.changed) {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
@@ -41,7 +41,7 @@ export default ({ name, onWatchChange, plugins }) => {
|
|
|
41
41
|
if (FILE_REGEX.test(id)) {
|
|
42
42
|
onWatchChange?.();
|
|
43
43
|
contexts.delete(root || '');
|
|
44
|
-
|
|
44
|
+
languageService.invalidate(root || '', id);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
};
|
package/package.json
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
"tsc-alias": "./bin/tsc-alias"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@esportsplus/cli-passthrough": "^0.0.
|
|
11
|
-
"@esportsplus/utilities": "^0.27.
|
|
12
|
-
"@types/node": "^25.0
|
|
10
|
+
"@esportsplus/cli-passthrough": "^0.0.15",
|
|
11
|
+
"@esportsplus/utilities": "^0.27.3",
|
|
12
|
+
"@types/node": "^25.6.0",
|
|
13
13
|
"tsc-alias": "^1.8.16",
|
|
14
|
-
"typescript": "^
|
|
14
|
+
"typescript": "^6.0.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"vite": "^
|
|
17
|
+
"vite": "^8.0.8",
|
|
18
|
+
"vitest": "^4.1.4"
|
|
18
19
|
},
|
|
19
20
|
"exports": {
|
|
20
21
|
"./package.json": "./package.json",
|
|
@@ -39,9 +40,11 @@
|
|
|
39
40
|
},
|
|
40
41
|
"type": "module",
|
|
41
42
|
"types": "build/index.d.ts",
|
|
42
|
-
"version": "0.
|
|
43
|
+
"version": "0.29.1",
|
|
43
44
|
"scripts": {
|
|
45
|
+
"bench:run": "vitest bench --run",
|
|
44
46
|
"build": "tsc && tsc-alias",
|
|
45
|
-
"-": "-"
|
|
47
|
+
"-": "-",
|
|
48
|
+
"test": "vitest run"
|
|
46
49
|
}
|
|
47
50
|
}
|
package/src/cli/tsc.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Plugin, SharedContext } from '~/compiler/types';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
+
import { PACKAGE_NAME } from '~/constants';
|
|
3
4
|
import { pathToFileURL } from 'url';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
|
|
7
|
+
import coordinator from '~/compiler/coordinator';
|
|
4
8
|
import path from 'path';
|
|
5
9
|
import ts from 'typescript';
|
|
6
|
-
import coordinator from '~/compiler/coordinator';
|
|
7
|
-
import type { Plugin, SharedContext } from '~/compiler/types';
|
|
8
|
-
import { PACKAGE_NAME } from '~/constants';
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
type PluginConfig = {
|
|
12
|
-
after?: boolean;
|
|
13
13
|
transform: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
@@ -54,6 +54,7 @@ async function build(config: object, tsconfig: string, pluginConfigs: PluginConf
|
|
|
54
54
|
sourceFile.getFullText(),
|
|
55
55
|
sourceFile,
|
|
56
56
|
program,
|
|
57
|
+
root,
|
|
57
58
|
shared
|
|
58
59
|
);
|
|
59
60
|
|
|
@@ -226,4 +227,9 @@ function runTscAlias(args: string[]): Promise<number> {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
if (process.env.VITEST === undefined) {
|
|
231
|
+
main();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
export { build, isPlugin, loadPlugins, normalizePath, runTscAlias };
|
package/src/compiler/ast.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ImportIntent, Plugin, Replacement, ReplacementIntent, SharedContext } from './types';
|
|
2
|
+
import type { ModifyOptions } from './imports';
|
|
2
3
|
import { ts } from '~/index';
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
import imports from './imports';
|
|
6
|
+
import languageService from './language-service';
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
type CoordinatorResult = {
|
|
@@ -11,22 +14,39 @@ type CoordinatorResult = {
|
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
function applyImports(code: string, file: ts.SourceFile, intents: ImportIntent[]): string {
|
|
17
|
+
let merged = new Map<string, { add?: string[]; namespace?: string; remove?: string[] }>();
|
|
18
|
+
|
|
14
19
|
for (let i = 0, n = intents.length; i < n; i++) {
|
|
15
|
-
let intent = intents[i]
|
|
20
|
+
let intent = intents[i],
|
|
21
|
+
existing = merged.get(intent.package);
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
if (existing) {
|
|
24
|
+
if (intent.add) {
|
|
25
|
+
(existing.add ??= []).push(...intent.add);
|
|
26
|
+
}
|
|
27
|
+
if (intent.namespace) {
|
|
28
|
+
existing.namespace = intent.namespace;
|
|
29
|
+
}
|
|
30
|
+
if (intent.remove) {
|
|
31
|
+
(existing.remove ??= []).push(...intent.remove);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
merged.set(intent.package, {
|
|
36
|
+
add: intent.add ? [...intent.add] : undefined,
|
|
37
|
+
namespace: intent.namespace,
|
|
38
|
+
remove: intent.remove ? [...intent.remove] : undefined
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let keys = [...merged.keys()];
|
|
44
|
+
|
|
45
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
46
|
+
code = modify(code, file, keys[i], merged.get(keys[i])!);
|
|
22
47
|
|
|
23
48
|
if (i < n - 1) {
|
|
24
|
-
file = ts.createSourceFile(
|
|
25
|
-
file.fileName,
|
|
26
|
-
code,
|
|
27
|
-
file.languageVersion,
|
|
28
|
-
true
|
|
29
|
-
);
|
|
49
|
+
file = ts.createSourceFile(file.fileName, code, file.languageVersion, true);
|
|
30
50
|
}
|
|
31
51
|
}
|
|
32
52
|
|
|
@@ -67,10 +87,10 @@ function applyPrepend(code: string, file: ts.SourceFile, prepend: string[]): str
|
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
if (position === 0) {
|
|
70
|
-
return prepend.join('\n') + code;
|
|
90
|
+
return prepend.join('\n') + '\n' + code;
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
return code.slice(0, position) + prepend.join('\n') + code.slice(position);
|
|
93
|
+
return code.slice(0, position) + '\n' + prepend.join('\n') + '\n' + code.slice(position);
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
function hasPattern(code: string, patterns: string[]): boolean {
|
|
@@ -157,45 +177,34 @@ function replaceReverse(code: string, replacements: Replacement[]): string {
|
|
|
157
177
|
|
|
158
178
|
replacements.sort((a, b) => b.start - a.start);
|
|
159
179
|
|
|
160
|
-
let
|
|
180
|
+
let parts: string[] = [],
|
|
181
|
+
pos = code.length;
|
|
161
182
|
|
|
162
183
|
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
163
184
|
let r = replacements[i];
|
|
164
185
|
|
|
165
|
-
|
|
186
|
+
if (r.end < pos) {
|
|
187
|
+
parts.push(code.substring(r.end, pos));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
parts.push(r.newText);
|
|
191
|
+
pos = r.start;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (pos > 0) {
|
|
195
|
+
parts.push(code.substring(0, pos));
|
|
166
196
|
}
|
|
167
197
|
|
|
168
|
-
return
|
|
198
|
+
return parts.reverse().join('');
|
|
169
199
|
}
|
|
170
200
|
|
|
171
201
|
|
|
172
|
-
const createPatchedProgram = (baseProgram: ts.Program, fileName: string, newContent: string) => {
|
|
173
|
-
let baseHost = ts.createCompilerHost(baseProgram.getCompilerOptions()),
|
|
174
|
-
options = baseProgram.getCompilerOptions();
|
|
175
|
-
|
|
176
|
-
return ts.createProgram(
|
|
177
|
-
baseProgram.getRootFileNames(),
|
|
178
|
-
options,
|
|
179
|
-
{
|
|
180
|
-
...baseHost,
|
|
181
|
-
getSourceFile: (name, languageVersion) => {
|
|
182
|
-
if (name === fileName || name === fileName.replace(/\\/g, '/')) {
|
|
183
|
-
return ts.createSourceFile(name, newContent, languageVersion, true);
|
|
184
|
-
}
|
|
185
|
-
return baseProgram.getSourceFile(name) ?? baseHost.getSourceFile(name, languageVersion);
|
|
186
|
-
},
|
|
187
|
-
fileExists: (name) => baseHost.fileExists(name),
|
|
188
|
-
readFile: (name) => name === fileName ? newContent : baseHost.readFile(name)
|
|
189
|
-
},
|
|
190
|
-
baseProgram
|
|
191
|
-
);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
202
|
const transform = (
|
|
195
203
|
plugins: Plugin[],
|
|
196
204
|
code: string,
|
|
197
205
|
file: ts.SourceFile,
|
|
198
206
|
program: ts.Program,
|
|
207
|
+
root: string,
|
|
199
208
|
shared: SharedContext
|
|
200
209
|
) => {
|
|
201
210
|
if (plugins.length === 0) {
|
|
@@ -206,7 +215,8 @@ const transform = (
|
|
|
206
215
|
currentCode = code,
|
|
207
216
|
currentFile = file,
|
|
208
217
|
currentProgram = program,
|
|
209
|
-
fileName = file.fileName
|
|
218
|
+
fileName = file.fileName,
|
|
219
|
+
last = plugins.length - 1;
|
|
210
220
|
|
|
211
221
|
for (let i = 0, n = plugins.length; i < n; i++) {
|
|
212
222
|
let plugin = plugins[i];
|
|
@@ -230,20 +240,28 @@ const transform = (
|
|
|
230
240
|
}
|
|
231
241
|
|
|
232
242
|
if (prepend?.length) {
|
|
243
|
+
if (pluginChanged) {
|
|
244
|
+
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
currentCode = applyPrepend(currentCode, currentFile, prepend);
|
|
234
248
|
pluginChanged = true;
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
if (imports?.length) {
|
|
252
|
+
if (pluginChanged) {
|
|
253
|
+
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
254
|
+
}
|
|
255
|
+
|
|
238
256
|
currentCode = applyImports(currentCode, currentFile, imports);
|
|
239
257
|
pluginChanged = true;
|
|
240
258
|
}
|
|
241
259
|
|
|
242
260
|
if (pluginChanged) {
|
|
243
261
|
changed = true;
|
|
244
|
-
|
|
245
|
-
if (i <
|
|
246
|
-
currentProgram =
|
|
262
|
+
|
|
263
|
+
if (i < last) {
|
|
264
|
+
currentProgram = languageService.update(root, fileName, currentCode);
|
|
247
265
|
currentFile = currentProgram.getSourceFile(fileName) ||
|
|
248
266
|
ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
249
267
|
}
|
|
@@ -257,5 +275,5 @@ const transform = (
|
|
|
257
275
|
};
|
|
258
276
|
|
|
259
277
|
|
|
260
|
-
export default {
|
|
278
|
+
export default { transform };
|
|
261
279
|
export type { CoordinatorResult };
|
package/src/compiler/imports.ts
CHANGED
|
@@ -17,6 +17,14 @@ type ModifyOptions = {
|
|
|
17
17
|
let cache = new WeakMap<ts.SourceFile, Map<string, Set<string>>>();
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
function fileNameMatchesPackage(fileName: string, pkg: string): boolean {
|
|
21
|
+
let normalized = fileName.replace(/\\/g, '/'),
|
|
22
|
+
marker = `/node_modules/${pkg}/`;
|
|
23
|
+
|
|
24
|
+
return normalized.includes(marker);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
// Find all named imports from a specific package
|
|
21
29
|
const all = (file: ts.SourceFile, pkg: string): ImportInfo[] => {
|
|
22
30
|
let imports: ImportInfo[] = [];
|
|
@@ -108,7 +116,7 @@ const includes = (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolNam
|
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
if (decl.getSourceFile().fileName
|
|
119
|
+
if (fileNameMatchesPackage(decl.getSourceFile().fileName, pkg)) {
|
|
112
120
|
return true;
|
|
113
121
|
}
|
|
114
122
|
}
|
|
@@ -133,7 +141,7 @@ const includes = (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolNam
|
|
|
133
141
|
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
134
142
|
let decl = declarations[i];
|
|
135
143
|
|
|
136
|
-
if (decl.getSourceFile().fileName
|
|
144
|
+
if (fileNameMatchesPackage(decl.getSourceFile().fileName, pkg)) {
|
|
137
145
|
return true;
|
|
138
146
|
}
|
|
139
147
|
}
|
|
@@ -148,7 +156,7 @@ const includes = (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolNam
|
|
|
148
156
|
|
|
149
157
|
if (aliasedDecls) {
|
|
150
158
|
for (let i = 0, n = aliasedDecls.length; i < n; i++) {
|
|
151
|
-
if (aliasedDecls[i].getSourceFile().fileName
|
|
159
|
+
if (fileNameMatchesPackage(aliasedDecls[i].getSourceFile().fileName, pkg)) {
|
|
152
160
|
return true;
|
|
153
161
|
}
|
|
154
162
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { PACKAGE_NAME } from '~/constants';
|
|
2
|
+
import { ts } from '~/index';
|
|
3
|
+
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type LanguageServiceEntry = {
|
|
8
|
+
contents: Map<string, string>;
|
|
9
|
+
host: ts.LanguageServiceHost;
|
|
10
|
+
rootFiles: Set<string>;
|
|
11
|
+
service: ts.LanguageService;
|
|
12
|
+
versions: Map<string, number>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
let cache = new Map<string, LanguageServiceEntry>();
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function create(root: string): LanguageServiceEntry {
|
|
20
|
+
let tsconfig = ts.findConfigFile(root, ts.sys.fileExists, 'tsconfig.json');
|
|
21
|
+
|
|
22
|
+
if (!tsconfig) {
|
|
23
|
+
throw new Error(`${PACKAGE_NAME}: tsconfig.json not found`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let file = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
27
|
+
|
|
28
|
+
if (file.error) {
|
|
29
|
+
throw new Error(`${PACKAGE_NAME}: error reading tsconfig.json ${file.error.messageText}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let parsed = ts.parseJsonConfigFileContent(
|
|
33
|
+
file.config,
|
|
34
|
+
ts.sys,
|
|
35
|
+
path.dirname(tsconfig)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (parsed.errors.length > 0) {
|
|
39
|
+
throw new Error(`${PACKAGE_NAME}: error parsing tsconfig.json ${parsed.errors[0].messageText}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let contents = new Map<string, string>(),
|
|
43
|
+
rootFiles = new Set(parsed.fileNames.map(f => f.replace(/\\/g, '/'))),
|
|
44
|
+
versions = new Map<string, number>();
|
|
45
|
+
|
|
46
|
+
for (let fileName of rootFiles) {
|
|
47
|
+
versions.set(fileName, 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let host: ts.LanguageServiceHost = {
|
|
51
|
+
fileExists: ts.sys.fileExists,
|
|
52
|
+
getCompilationSettings: () => parsed.options,
|
|
53
|
+
getCurrentDirectory: () => root,
|
|
54
|
+
getDefaultLibFileName: ts.getDefaultLibFilePath,
|
|
55
|
+
getScriptFileNames: () => [...rootFiles],
|
|
56
|
+
getScriptSnapshot: (fileName: string) => {
|
|
57
|
+
let content = contents.get(fileName);
|
|
58
|
+
|
|
59
|
+
if (content !== undefined) {
|
|
60
|
+
return ts.ScriptSnapshot.fromString(content);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!ts.sys.fileExists(fileName)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName) || '');
|
|
68
|
+
},
|
|
69
|
+
getScriptVersion: (fileName: string) => String(versions.get(fileName) || 0),
|
|
70
|
+
readFile: ts.sys.readFile
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return { contents, host, rootFiles, service: ts.createLanguageService(host), versions };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getEntry(root: string): LanguageServiceEntry {
|
|
77
|
+
let entry = cache.get(root);
|
|
78
|
+
|
|
79
|
+
if (!entry) {
|
|
80
|
+
entry = create(root);
|
|
81
|
+
cache.set(root, entry);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return entry;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
const invalidate = (root: string, fileName: string): void => {
|
|
89
|
+
let entry = cache.get(root);
|
|
90
|
+
|
|
91
|
+
if (entry) {
|
|
92
|
+
let normalized = fileName.replace(/\\/g, '/');
|
|
93
|
+
|
|
94
|
+
entry.contents.delete(normalized);
|
|
95
|
+
entry.versions.set(normalized, (entry.versions.get(normalized) || 0) + 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const update = (root: string, fileName: string, content: string): ts.Program => {
|
|
100
|
+
let entry = getEntry(root),
|
|
101
|
+
normalized = fileName.replace(/\\/g, '/');
|
|
102
|
+
|
|
103
|
+
if (!entry.rootFiles.has(normalized)) {
|
|
104
|
+
entry.rootFiles.add(normalized);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
entry.contents.set(normalized, content);
|
|
108
|
+
entry.versions.set(normalized, (entry.versions.get(normalized) || 0) + 1);
|
|
109
|
+
|
|
110
|
+
let program = entry.service.getProgram();
|
|
111
|
+
|
|
112
|
+
if (!program) {
|
|
113
|
+
throw new Error(`${PACKAGE_NAME}: failed to get program from language service`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return program;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export default { invalidate, update };
|
|
121
|
+
export { invalidate, update };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ResolvedConfig } from 'vite';
|
|
2
1
|
import type { Plugin, SharedContext } from '../types';
|
|
2
|
+
import type { ResolvedConfig } from 'vite';
|
|
3
3
|
import { ts } from '~/index';
|
|
4
|
+
|
|
4
5
|
import coordinator from '../coordinator';
|
|
5
|
-
import
|
|
6
|
+
import languageService from '../language-service';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
type VitePlugin = {
|
|
@@ -20,11 +21,9 @@ type VitePluginOptions = {
|
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
const FILE_REGEX = /\.[tj]sx?$/;
|
|
24
|
-
|
|
25
24
|
const DIRECTORY_SEPARATOR_REGEX = /\\/g;
|
|
26
25
|
|
|
27
|
-
const
|
|
26
|
+
const FILE_REGEX = /\.[tj]sx?$/;
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
let contexts = new Map<string, SharedContext>();
|
|
@@ -45,17 +44,19 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
|
|
|
45
44
|
|
|
46
45
|
try {
|
|
47
46
|
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'),
|
|
48
|
-
prog =
|
|
49
|
-
sourceFile = prog.getSourceFile(normalizedId)
|
|
47
|
+
prog = languageService.update(root || '', normalizedId, code),
|
|
48
|
+
sourceFile = prog.getSourceFile(normalizedId);
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
sourceFile = undefined;
|
|
50
|
+
if (!sourceFile) {
|
|
51
|
+
sourceFile = ts.createSourceFile(normalizedId, code, ts.ScriptTarget.Latest, true);
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
let key = root || '',
|
|
55
|
+
ctx = contexts.get(key);
|
|
56
|
+
|
|
57
|
+
if (!ctx) {
|
|
58
|
+
ctx = new Map();
|
|
59
|
+
contexts.set(key, ctx);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
let result = coordinator.transform(
|
|
@@ -63,7 +64,8 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
|
|
|
63
64
|
code,
|
|
64
65
|
sourceFile,
|
|
65
66
|
prog,
|
|
66
|
-
|
|
67
|
+
key,
|
|
68
|
+
ctx
|
|
67
69
|
);
|
|
68
70
|
|
|
69
71
|
if (!result.changed) {
|
|
@@ -81,7 +83,7 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
|
|
|
81
83
|
if (FILE_REGEX.test(id)) {
|
|
82
84
|
onWatchChange?.();
|
|
83
85
|
contexts.delete(root || '');
|
|
84
|
-
|
|
86
|
+
languageService.invalidate(root || '', id);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
};
|