@esportsplus/typescript 0.29.0 → 0.29.5
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/bump.yml +1 -1
- package/.github/workflows/dependabot.yml +4 -4
- package/.github/workflows/publish.yml +4 -5
- package/README.md +146 -0
- package/build/cli/tsc.d.ts +10 -1
- package/build/cli/tsc.js +7 -4
- package/build/compiler/ast.d.ts +2 -5
- package/build/compiler/ast.js +1 -1
- package/build/compiler/coordinator.js +42 -11
- package/build/compiler/imports.js +7 -3
- package/build/compiler/language-service.d.ts +2 -5
- package/build/compiler/language-service.js +4 -14
- package/build/compiler/plugins/vite.js +7 -2
- package/package.json +14 -8
- package/src/cli/tsc.ts +11 -6
- package/src/compiler/ast.ts +2 -7
- package/src/compiler/coordinator.ts +55 -18
- package/src/compiler/imports.ts +11 -3
- package/src/compiler/language-service.ts +5 -19
- package/src/compiler/plugins/vite.ts +13 -4
- 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/tsconfig.node.json +3 -0
- package/vitest.config.ts +14 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ts } from '../index.js';
|
|
2
2
|
let cache = new WeakMap();
|
|
3
|
+
function fileNameMatchesPackage(fileName, pkg) {
|
|
4
|
+
let normalized = fileName.replace(/\\/g, '/'), marker = `/node_modules/${pkg}/`;
|
|
5
|
+
return normalized.includes(marker);
|
|
6
|
+
}
|
|
3
7
|
const all = (file, pkg) => {
|
|
4
8
|
let imports = [];
|
|
5
9
|
for (let i = 0, n = file.statements.length; i < n; i++) {
|
|
@@ -60,7 +64,7 @@ const includes = (checker, node, pkg, symbolName) => {
|
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
|
-
if (decl.getSourceFile().fileName
|
|
67
|
+
if (fileNameMatchesPackage(decl.getSourceFile().fileName, pkg)) {
|
|
64
68
|
return true;
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -76,7 +80,7 @@ const includes = (checker, node, pkg, symbolName) => {
|
|
|
76
80
|
if (declarations && declarations.length > 0) {
|
|
77
81
|
for (let i = 0, n = declarations.length; i < n; i++) {
|
|
78
82
|
let decl = declarations[i];
|
|
79
|
-
if (decl.getSourceFile().fileName
|
|
83
|
+
if (fileNameMatchesPackage(decl.getSourceFile().fileName, pkg)) {
|
|
80
84
|
return true;
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -87,7 +91,7 @@ const includes = (checker, node, pkg, symbolName) => {
|
|
|
87
91
|
let aliasedDecls = aliased.getDeclarations();
|
|
88
92
|
if (aliasedDecls) {
|
|
89
93
|
for (let i = 0, n = aliasedDecls.length; i < n; i++) {
|
|
90
|
-
if (aliasedDecls[i].getSourceFile().fileName
|
|
94
|
+
if (fileNameMatchesPackage(aliasedDecls[i].getSourceFile().fileName, pkg)) {
|
|
91
95
|
return true;
|
|
92
96
|
}
|
|
93
97
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import ts from '
|
|
2
|
-
declare const get: (root: string) => ts.Program;
|
|
1
|
+
import { ts } from '../index.js';
|
|
3
2
|
declare const invalidate: (root: string, fileName: string) => void;
|
|
4
3
|
declare const update: (root: string, fileName: string, content: string) => ts.Program;
|
|
5
4
|
declare const _default: {
|
|
6
|
-
delete: (root: string) => void;
|
|
7
|
-
get: (root: string) => ts.Program;
|
|
8
5
|
invalidate: (root: string, fileName: string) => void;
|
|
9
6
|
update: (root: string, fileName: string, content: string) => ts.Program;
|
|
10
7
|
};
|
|
11
8
|
export default _default;
|
|
12
|
-
export {
|
|
9
|
+
export { invalidate, update };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import ts from 'typescript';
|
|
3
1
|
import { PACKAGE_NAME } from '../constants.js';
|
|
2
|
+
import { ts } from '../index.js';
|
|
3
|
+
import path from 'path';
|
|
4
4
|
let cache = new Map();
|
|
5
5
|
function create(root) {
|
|
6
6
|
let tsconfig = ts.findConfigFile(root, ts.sys.fileExists, 'tsconfig.json');
|
|
@@ -48,16 +48,6 @@ function getEntry(root) {
|
|
|
48
48
|
}
|
|
49
49
|
return entry;
|
|
50
50
|
}
|
|
51
|
-
const del = (root) => {
|
|
52
|
-
cache.delete(root);
|
|
53
|
-
};
|
|
54
|
-
const get = (root) => {
|
|
55
|
-
let entry = getEntry(root), program = entry.service.getProgram();
|
|
56
|
-
if (!program) {
|
|
57
|
-
throw new Error(`${PACKAGE_NAME}: failed to get program from language service`);
|
|
58
|
-
}
|
|
59
|
-
return program;
|
|
60
|
-
};
|
|
61
51
|
const invalidate = (root, fileName) => {
|
|
62
52
|
let entry = cache.get(root);
|
|
63
53
|
if (entry) {
|
|
@@ -79,5 +69,5 @@ const update = (root, fileName, content) => {
|
|
|
79
69
|
}
|
|
80
70
|
return program;
|
|
81
71
|
};
|
|
82
|
-
export default {
|
|
83
|
-
export {
|
|
72
|
+
export default { invalidate, update };
|
|
73
|
+
export { invalidate, update };
|
|
@@ -19,9 +19,14 @@ export default ({ name, onWatchChange, plugins }) => {
|
|
|
19
19
|
try {
|
|
20
20
|
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'), prog = languageService.update(root || '', normalizedId, code), sourceFile = prog.getSourceFile(normalizedId);
|
|
21
21
|
if (!sourceFile) {
|
|
22
|
-
sourceFile = ts.createSourceFile(
|
|
22
|
+
sourceFile = ts.createSourceFile(normalizedId, code, ts.ScriptTarget.Latest, true);
|
|
23
23
|
}
|
|
24
|
-
let
|
|
24
|
+
let key = root || '', ctx = contexts.get(key);
|
|
25
|
+
if (!ctx) {
|
|
26
|
+
ctx = new Map();
|
|
27
|
+
contexts.set(key, ctx);
|
|
28
|
+
}
|
|
29
|
+
let result = coordinator.transform(plugins, code, sourceFile, prog, key, ctx);
|
|
25
30
|
if (!result.changed) {
|
|
26
31
|
return null;
|
|
27
32
|
}
|
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.
|
|
12
|
-
"@types/node": "^
|
|
13
|
-
"tsc-alias": "^1.8.
|
|
14
|
-
"typescript": "^
|
|
10
|
+
"@esportsplus/cli-passthrough": "^0.0.15",
|
|
11
|
+
"@esportsplus/utilities": "^0.28.0",
|
|
12
|
+
"@types/node": "^26.0.1",
|
|
13
|
+
"tsc-alias": "^1.8.17",
|
|
14
|
+
"typescript": "^6.0.3"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"vite": "^
|
|
17
|
+
"vite": "^8.1.0",
|
|
18
|
+
"vitest": "^4.1.9"
|
|
18
19
|
},
|
|
19
20
|
"exports": {
|
|
20
21
|
"./package.json": "./package.json",
|
|
@@ -32,6 +33,9 @@
|
|
|
32
33
|
},
|
|
33
34
|
"main": "build/index.js",
|
|
34
35
|
"name": "@esportsplus/typescript",
|
|
36
|
+
"overrides": {
|
|
37
|
+
"postcss": "8.5.15"
|
|
38
|
+
},
|
|
35
39
|
"private": false,
|
|
36
40
|
"repository": {
|
|
37
41
|
"type": "git",
|
|
@@ -39,9 +43,11 @@
|
|
|
39
43
|
},
|
|
40
44
|
"type": "module",
|
|
41
45
|
"types": "build/index.d.ts",
|
|
42
|
-
"version": "0.29.
|
|
46
|
+
"version": "0.29.5",
|
|
43
47
|
"scripts": {
|
|
48
|
+
"bench:run": "vitest bench --run",
|
|
44
49
|
"build": "tsc && tsc-alias",
|
|
45
|
-
"-": "-"
|
|
50
|
+
"-": "-",
|
|
51
|
+
"test": "vitest run"
|
|
46
52
|
}
|
|
47
53
|
}
|
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
|
|
|
@@ -227,4 +227,9 @@ function runTscAlias(args: string[]): Promise<number> {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
|
|
230
|
-
|
|
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,8 @@
|
|
|
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';
|
|
4
6
|
import languageService from './language-service';
|
|
5
7
|
|
|
6
8
|
|
|
@@ -12,22 +14,39 @@ type CoordinatorResult = {
|
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
function applyImports(code: string, file: ts.SourceFile, intents: ImportIntent[]): string {
|
|
17
|
+
let merged = new Map<string, { add?: string[]; namespace?: string; remove?: string[] }>();
|
|
18
|
+
|
|
15
19
|
for (let i = 0, n = intents.length; i < n; i++) {
|
|
16
|
-
let intent = intents[i]
|
|
20
|
+
let intent = intents[i],
|
|
21
|
+
existing = merged.get(intent.package);
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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])!);
|
|
23
47
|
|
|
24
48
|
if (i < n - 1) {
|
|
25
|
-
file = ts.createSourceFile(
|
|
26
|
-
file.fileName,
|
|
27
|
-
code,
|
|
28
|
-
file.languageVersion,
|
|
29
|
-
true
|
|
30
|
-
);
|
|
49
|
+
file = ts.createSourceFile(file.fileName, code, file.languageVersion, true);
|
|
31
50
|
}
|
|
32
51
|
}
|
|
33
52
|
|
|
@@ -68,10 +87,10 @@ function applyPrepend(code: string, file: ts.SourceFile, prepend: string[]): str
|
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
if (position === 0) {
|
|
71
|
-
return prepend.join('\n') + code;
|
|
90
|
+
return prepend.join('\n') + '\n' + code;
|
|
72
91
|
}
|
|
73
92
|
|
|
74
|
-
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);
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
function hasPattern(code: string, patterns: string[]): boolean {
|
|
@@ -158,15 +177,25 @@ function replaceReverse(code: string, replacements: Replacement[]): string {
|
|
|
158
177
|
|
|
159
178
|
replacements.sort((a, b) => b.start - a.start);
|
|
160
179
|
|
|
161
|
-
let
|
|
180
|
+
let parts: string[] = [],
|
|
181
|
+
pos = code.length;
|
|
162
182
|
|
|
163
183
|
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
164
184
|
let r = replacements[i];
|
|
165
185
|
|
|
166
|
-
|
|
186
|
+
if (r.end < pos) {
|
|
187
|
+
parts.push(code.substring(r.end, pos));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
parts.push(r.newText);
|
|
191
|
+
pos = r.start;
|
|
167
192
|
}
|
|
168
193
|
|
|
169
|
-
|
|
194
|
+
if (pos > 0) {
|
|
195
|
+
parts.push(code.substring(0, pos));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return parts.reverse().join('');
|
|
170
199
|
}
|
|
171
200
|
|
|
172
201
|
|
|
@@ -211,11 +240,19 @@ const transform = (
|
|
|
211
240
|
}
|
|
212
241
|
|
|
213
242
|
if (prepend?.length) {
|
|
243
|
+
if (pluginChanged) {
|
|
244
|
+
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
245
|
+
}
|
|
246
|
+
|
|
214
247
|
currentCode = applyPrepend(currentCode, currentFile, prepend);
|
|
215
248
|
pluginChanged = true;
|
|
216
249
|
}
|
|
217
250
|
|
|
218
251
|
if (imports?.length) {
|
|
252
|
+
if (pluginChanged) {
|
|
253
|
+
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
254
|
+
}
|
|
255
|
+
|
|
219
256
|
currentCode = applyImports(currentCode, currentFile, imports);
|
|
220
257
|
pluginChanged = true;
|
|
221
258
|
}
|
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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import ts from 'typescript';
|
|
3
1
|
import { PACKAGE_NAME } from '~/constants';
|
|
2
|
+
import { ts } from '~/index';
|
|
3
|
+
|
|
4
|
+
import path from 'path';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
type LanguageServiceEntry = {
|
|
@@ -84,21 +85,6 @@ function getEntry(root: string): LanguageServiceEntry {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
const del = (root: string): void => {
|
|
88
|
-
cache.delete(root);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const get = (root: string): ts.Program => {
|
|
92
|
-
let entry = getEntry(root),
|
|
93
|
-
program = entry.service.getProgram();
|
|
94
|
-
|
|
95
|
-
if (!program) {
|
|
96
|
-
throw new Error(`${PACKAGE_NAME}: failed to get program from language service`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return program;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
88
|
const invalidate = (root: string, fileName: string): void => {
|
|
103
89
|
let entry = cache.get(root);
|
|
104
90
|
|
|
@@ -131,5 +117,5 @@ const update = (root: string, fileName: string, content: string): ts.Program =>
|
|
|
131
117
|
};
|
|
132
118
|
|
|
133
119
|
|
|
134
|
-
export default {
|
|
135
|
-
export {
|
|
120
|
+
export default { invalidate, update };
|
|
121
|
+
export { invalidate, update };
|
|
@@ -1,6 +1,7 @@
|
|
|
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
6
|
import languageService from '../language-service';
|
|
6
7
|
|
|
@@ -47,7 +48,15 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
|
|
|
47
48
|
sourceFile = prog.getSourceFile(normalizedId);
|
|
48
49
|
|
|
49
50
|
if (!sourceFile) {
|
|
50
|
-
sourceFile = ts.createSourceFile(
|
|
51
|
+
sourceFile = ts.createSourceFile(normalizedId, code, ts.ScriptTarget.Latest, true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let key = root || '',
|
|
55
|
+
ctx = contexts.get(key);
|
|
56
|
+
|
|
57
|
+
if (!ctx) {
|
|
58
|
+
ctx = new Map();
|
|
59
|
+
contexts.set(key, ctx);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
let result = coordinator.transform(
|
|
@@ -55,8 +64,8 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
|
|
|
55
64
|
code,
|
|
56
65
|
sourceFile,
|
|
57
66
|
prog,
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
key,
|
|
68
|
+
ctx
|
|
60
69
|
);
|
|
61
70
|
|
|
62
71
|
if (!result.changed) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { isPlugin, loadPlugins, normalizePath, runTscAlias } from '~/cli/tsc';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
describe('isPlugin', () => {
|
|
10
|
+
it('returns true for valid plugin', () => {
|
|
11
|
+
expect(isPlugin({ transform: () => {} })).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns false for null', () => {
|
|
15
|
+
expect(isPlugin(null)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns false for empty object', () => {
|
|
19
|
+
expect(isPlugin({})).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false when transform is not a function', () => {
|
|
23
|
+
expect(isPlugin({ transform: 'not-fn' })).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns false for primitives', () => {
|
|
27
|
+
expect(isPlugin(42)).toBe(false);
|
|
28
|
+
expect(isPlugin('str')).toBe(false);
|
|
29
|
+
expect(isPlugin(undefined)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
describe('normalizePath', () => {
|
|
35
|
+
it('converts backslashes to forward slashes', () => {
|
|
36
|
+
let result = normalizePath('C:\\foo\\bar.ts');
|
|
37
|
+
|
|
38
|
+
expect(result).not.toContain('\\');
|
|
39
|
+
expect(result).toContain('/foo/bar');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('resolves to absolute path', () => {
|
|
43
|
+
let result = normalizePath('relative/file.ts');
|
|
44
|
+
|
|
45
|
+
expect(path.isAbsolute(result)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
describe('loadPlugins', () => {
|
|
51
|
+
let tmpDir: string;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tsc-test-'));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('loads a valid plugin with transform export', async () => {
|
|
62
|
+
let pluginFile = 'plugin.mjs';
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default { transform: () => ({}) };');
|
|
65
|
+
|
|
66
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
67
|
+
|
|
68
|
+
expect(plugins).toHaveLength(1);
|
|
69
|
+
expect(typeof plugins[0].transform).toBe('function');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('loads a factory function that returns a plugin', async () => {
|
|
73
|
+
let pluginFile = 'factory.mjs';
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default function() { return { transform: () => ({}) }; };');
|
|
76
|
+
|
|
77
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
78
|
+
|
|
79
|
+
expect(plugins).toHaveLength(1);
|
|
80
|
+
expect(typeof plugins[0].transform).toBe('function');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('loads array of plugins', async () => {
|
|
84
|
+
let pluginFile = 'array.mjs';
|
|
85
|
+
|
|
86
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default [{ transform: () => ({}) }, { transform: () => ({}) }];');
|
|
87
|
+
|
|
88
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
89
|
+
|
|
90
|
+
expect(plugins).toHaveLength(2);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('skips invalid plugin format with error', async () => {
|
|
94
|
+
let pluginFile = 'invalid.mjs';
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default { notTransform: true };');
|
|
97
|
+
|
|
98
|
+
let spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
99
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
100
|
+
|
|
101
|
+
expect(plugins).toHaveLength(0);
|
|
102
|
+
expect(spy).toHaveBeenCalled();
|
|
103
|
+
spy.mockRestore();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('skips invalid array element with error', async () => {
|
|
107
|
+
let pluginFile = 'mixed.mjs';
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default [{ transform: () => ({}) }, { bad: true }];');
|
|
110
|
+
|
|
111
|
+
let spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
112
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
113
|
+
|
|
114
|
+
expect(plugins).toHaveLength(1);
|
|
115
|
+
expect(spy).toHaveBeenCalled();
|
|
116
|
+
spy.mockRestore();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('resolves relative paths from root', async () => {
|
|
120
|
+
let pluginFile = 'relative.mjs';
|
|
121
|
+
|
|
122
|
+
fs.writeFileSync(path.join(tmpDir, pluginFile), 'export default { transform: () => ({}) };');
|
|
123
|
+
|
|
124
|
+
let plugins = await loadPlugins([{ transform: './' + pluginFile }], tmpDir);
|
|
125
|
+
|
|
126
|
+
expect(plugins).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
describe('runTscAlias', () => {
|
|
132
|
+
it('returns 0 for --noEmit flag', async () => {
|
|
133
|
+
let code = await runTscAlias(['--noEmit']);
|
|
134
|
+
|
|
135
|
+
expect(code).toBe(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('returns 0 for --help flag', async () => {
|
|
139
|
+
let code = await runTscAlias(['--help']);
|
|
140
|
+
|
|
141
|
+
expect(code).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('returns 0 for --version flag', async () => {
|
|
145
|
+
let code = await runTscAlias(['--version']);
|
|
146
|
+
|
|
147
|
+
expect(code).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns 0 for -v flag', async () => {
|
|
151
|
+
let code = await runTscAlias(['-v']);
|
|
152
|
+
|
|
153
|
+
expect(code).toBe(0);
|
|
154
|
+
});
|
|
155
|
+
});
|