@esportsplus/typescript 0.29.6 → 0.31.0
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/README.md +11 -3
- package/bin/tsc-alias +1 -1
- package/build/cli/diagnostics.d.ts +4 -0
- package/build/cli/diagnostics.js +90 -0
- package/build/cli/tsc.d.ts +10 -2
- package/build/cli/tsc.js +352 -51
- package/build/compiler/ast.d.ts +7 -5
- package/build/compiler/ast.js +6 -6
- package/build/compiler/code.d.ts +5 -4
- package/build/compiler/code.js +12 -1
- package/build/compiler/coordinator.d.ts +17 -7
- package/build/compiler/coordinator.js +54 -31
- package/build/compiler/imports.d.ts +6 -3
- package/build/compiler/imports.js +26 -23
- package/build/compiler/index.d.ts +3 -0
- package/build/compiler/index.js +1 -0
- package/build/compiler/language-service.d.ts +24 -5
- package/build/compiler/language-service.js +215 -53
- package/build/compiler/plugins/index.d.ts +4 -2
- package/build/compiler/plugins/tsc.d.ts +1 -1
- package/build/compiler/plugins/vite.d.ts +6 -3
- package/build/compiler/plugins/vite.js +12 -7
- package/build/compiler/sourcemap.d.ts +50 -0
- package/build/compiler/sourcemap.js +247 -0
- package/build/compiler/types.d.ts +7 -6
- package/build/compiler/uid.d.ts +5 -2
- package/build/compiler/uid.js +33 -4
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/ts.d.ts +3 -0
- package/build/ts.js +3 -0
- package/package.json +22 -8
- package/tsconfig.dev.json +13 -0
- package/tsconfig.package.json +4 -1
- package/.claude/skills/code-audit/registry-typescript.json +0 -326
- package/.editorconfig +0 -9
- package/.gitattributes +0 -2
- package/.github/dependabot.yml +0 -25
- package/.github/workflows/bump.yml +0 -27
- package/.github/workflows/dependabot.yml +0 -58
- package/.github/workflows/publish.yml +0 -42
- package/.github/workflows/templates/bump.yml +0 -9
- package/.github/workflows/templates/dependabot.yml +0 -12
- package/.github/workflows/templates/publish.yml +0 -16
- package/pnpm-workspace.yaml +0 -6
- package/src/cli/tsc.ts +0 -235
- package/src/compiler/ast.ts +0 -60
- package/src/compiler/code.ts +0 -27
- package/src/compiler/coordinator.ts +0 -284
- package/src/compiler/imports.ts +0 -185
- package/src/compiler/index.ts +0 -7
- package/src/compiler/language-service.ts +0 -121
- package/src/compiler/plugins/index.ts +0 -5
- package/src/compiler/plugins/tsc.ts +0 -6
- package/src/compiler/plugins/vite.ts +0 -91
- package/src/compiler/types.ts +0 -83
- package/src/compiler/uid.ts +0 -10
- package/src/constants.ts +0 -4
- package/src/index.ts +0 -1
- package/tests/cli/tsc.test.ts +0 -155
- package/tests/compiler/ast.test.ts +0 -131
- package/tests/compiler/code.test.ts +0 -73
- package/tests/compiler/coordinator.bench.ts +0 -101
- package/tests/compiler/coordinator.test.ts +0 -872
- package/tests/compiler/imports.test.ts +0 -167
- package/tests/compiler/language-service.test.ts +0 -90
- package/tests/compiler/plugins.test.ts +0 -172
- package/tests/compiler/uid.test.ts +0 -70
- package/tsconfig.json +0 -3
- package/vitest.config.ts +0 -14
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
workflow_call:
|
|
7
|
-
secrets:
|
|
8
|
-
NPM_TOKEN:
|
|
9
|
-
required: true
|
|
10
|
-
workflow_dispatch:
|
|
11
|
-
workflow_run:
|
|
12
|
-
workflows: [bump version]
|
|
13
|
-
types:
|
|
14
|
-
- completed
|
|
15
|
-
|
|
16
|
-
concurrency:
|
|
17
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
18
|
-
cancel-in-progress: true
|
|
19
|
-
|
|
20
|
-
jobs:
|
|
21
|
-
build:
|
|
22
|
-
runs-on: ubuntu-latest
|
|
23
|
-
permissions:
|
|
24
|
-
contents: read
|
|
25
|
-
id-token: write
|
|
26
|
-
steps:
|
|
27
|
-
- uses: actions/checkout@v7
|
|
28
|
-
- uses: pnpm/action-setup@v6
|
|
29
|
-
name: Install pnpm
|
|
30
|
-
with:
|
|
31
|
-
run_install: false
|
|
32
|
-
version: latest
|
|
33
|
-
- uses: actions/setup-node@v6
|
|
34
|
-
with:
|
|
35
|
-
cache: 'pnpm'
|
|
36
|
-
node-version: 'latest'
|
|
37
|
-
registry-url: 'https://registry.npmjs.org'
|
|
38
|
-
- run: pnpm i
|
|
39
|
-
env:
|
|
40
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
41
|
-
- run: pnpm build
|
|
42
|
-
- run: pnpm publish --no-git-checks
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name: publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
workflow_run:
|
|
8
|
-
workflows: [bump version]
|
|
9
|
-
types:
|
|
10
|
-
- completed
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
publish:
|
|
14
|
-
secrets:
|
|
15
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
16
|
-
uses: esportsplus/typescript/.github/workflows/publish.yml@main
|
package/pnpm-workspace.yaml
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# Approve dependency build scripts (pnpm errors on unapproved ones during install/publish) and trust
|
|
2
|
-
# our own scope so freshly published first-party deps bypass the minimumReleaseAge gate.
|
|
3
|
-
allowBuilds:
|
|
4
|
-
esbuild: true
|
|
5
|
-
minimumReleaseAgeExclude:
|
|
6
|
-
- '@esportsplus/*'
|
package/src/cli/tsc.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import type { Plugin, SharedContext } from '~/compiler/types';
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
import { PACKAGE_NAME } from '~/constants';
|
|
4
|
-
import { pathToFileURL } from 'url';
|
|
5
|
-
import { spawn } from 'child_process';
|
|
6
|
-
|
|
7
|
-
import coordinator from '~/compiler/coordinator';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import ts from 'typescript';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type PluginConfig = {
|
|
13
|
-
transform: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const BACKSLASH_REGEX = /\\/g;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let require = createRequire(import.meta.url),
|
|
21
|
-
skipFlags = new Set(['--help', '--init', '--noEmit', '--showConfig', '--version', '-h', '-noEmit', '-v']);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async function build(config: object, tsconfig: string, pluginConfigs: PluginConfig[]): Promise<void> {
|
|
25
|
-
let root = path.dirname(path.resolve(tsconfig)),
|
|
26
|
-
parsed = ts.parseJsonConfigFileContent(config, ts.sys, root);
|
|
27
|
-
|
|
28
|
-
if (parsed.errors.length > 0) {
|
|
29
|
-
for (let i = 0, n = parsed.errors.length; i < n; i++) {
|
|
30
|
-
console.error(
|
|
31
|
-
ts.flattenDiagnosticMessageText(parsed.errors[i].messageText, '\n')
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let plugins = await loadPlugins(pluginConfigs, root);
|
|
39
|
-
|
|
40
|
-
let program = ts.createProgram(parsed.fileNames, parsed.options),
|
|
41
|
-
shared: SharedContext = new Map(),
|
|
42
|
-
transformedFiles = new Map<string, string>();
|
|
43
|
-
|
|
44
|
-
for (let i = 0, n = parsed.fileNames.length; i < n; i++) {
|
|
45
|
-
let fileName = parsed.fileNames[i],
|
|
46
|
-
sourceFile = program.getSourceFile(fileName);
|
|
47
|
-
|
|
48
|
-
if (!sourceFile) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let result = coordinator.transform(
|
|
53
|
-
plugins,
|
|
54
|
-
sourceFile.getFullText(),
|
|
55
|
-
sourceFile,
|
|
56
|
-
program,
|
|
57
|
-
root,
|
|
58
|
-
shared
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
if (result.changed) {
|
|
62
|
-
transformedFiles.set(normalizePath(fileName), result.code);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (transformedFiles.size > 0) {
|
|
67
|
-
let customHost = ts.createCompilerHost(parsed.options),
|
|
68
|
-
originalGetSourceFile = customHost.getSourceFile.bind(customHost),
|
|
69
|
-
originalReadFile = customHost.readFile.bind(customHost);
|
|
70
|
-
|
|
71
|
-
customHost.getSourceFile = (
|
|
72
|
-
fileName: string,
|
|
73
|
-
languageVersion: ts.ScriptTarget,
|
|
74
|
-
onError?: (message: string) => void,
|
|
75
|
-
shouldCreateNewSourceFile?: boolean
|
|
76
|
-
): ts.SourceFile | undefined => {
|
|
77
|
-
let transformed = transformedFiles.get(normalizePath(fileName));
|
|
78
|
-
|
|
79
|
-
if (transformed) {
|
|
80
|
-
return ts.createSourceFile(fileName, transformed, languageVersion, true);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
customHost.readFile = (fileName: string): string | undefined => {
|
|
87
|
-
return transformedFiles.get(normalizePath(fileName)) ?? originalReadFile(fileName);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
program = ts.createProgram(parsed.fileNames, parsed.options, customHost);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let { diagnostics, emitSkipped } = program.emit();
|
|
94
|
-
|
|
95
|
-
diagnostics = ts.getPreEmitDiagnostics(program).concat(diagnostics);
|
|
96
|
-
|
|
97
|
-
if (diagnostics.length > 0) {
|
|
98
|
-
console.error(
|
|
99
|
-
ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
100
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
101
|
-
getCurrentDirectory: () => root,
|
|
102
|
-
getNewLine: () => '\n'
|
|
103
|
-
})
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (emitSkipped) {
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return runTscAlias(process.argv.slice(2)).then((code) => process.exit(code));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isPlugin(value: unknown): value is Plugin {
|
|
115
|
-
return typeof value === 'object' && value !== null && 'transform' in value && typeof (value as Plugin).transform === 'function';
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function loadPlugins(configs: PluginConfig[], root: string): Promise<Plugin[]> {
|
|
119
|
-
let plugins: Plugin[] = [],
|
|
120
|
-
promises: Promise<void>[] = [];
|
|
121
|
-
|
|
122
|
-
for (let i = 0, n = configs.length; i < n; i++) {
|
|
123
|
-
let config = configs[i],
|
|
124
|
-
pluginPath = config.transform;
|
|
125
|
-
|
|
126
|
-
if (pluginPath.startsWith('.')) {
|
|
127
|
-
pluginPath = pathToFileURL(path.resolve(root, pluginPath)).href;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
pluginPath = pathToFileURL(require.resolve(pluginPath, { paths: [root] })).href;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
promises.push(
|
|
134
|
-
import(pluginPath).then((module) => {
|
|
135
|
-
let plugin = module.default ?? module;
|
|
136
|
-
|
|
137
|
-
if (typeof plugin === 'function') {
|
|
138
|
-
plugin = plugin();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (Array.isArray(plugin)) {
|
|
142
|
-
for (let j = 0, m = plugin.length; j < m; j++) {
|
|
143
|
-
if (isPlugin(plugin[j])) {
|
|
144
|
-
plugins.push(plugin[j]);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
console.error(`${PACKAGE_NAME}: plugin ${config.transform}[${j}] uses an invalid plugin format`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!isPlugin(plugin)) {
|
|
155
|
-
console.error(`${PACKAGE_NAME}: plugin ${config.transform} uses an invalid plugin format, expected { transform: Function } or Plugin[]`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
plugins.push(plugin);
|
|
160
|
-
})
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
await Promise.all(promises);
|
|
165
|
-
|
|
166
|
-
return plugins;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function main(): void {
|
|
170
|
-
let tsconfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists, 'tsconfig.json');
|
|
171
|
-
|
|
172
|
-
if (!tsconfig) {
|
|
173
|
-
return passthrough();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
let { config, error } = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
177
|
-
|
|
178
|
-
if (error) {
|
|
179
|
-
return passthrough();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
let pluginConfigs = config?.compilerOptions?.plugins?.filter(
|
|
183
|
-
(p: unknown) => typeof p === 'object' && p !== null && 'transform' in p
|
|
184
|
-
) ?? [];
|
|
185
|
-
|
|
186
|
-
if (pluginConfigs.length === 0) {
|
|
187
|
-
return passthrough();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
console.log(`${PACKAGE_NAME}: found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
|
|
191
|
-
|
|
192
|
-
build(config, tsconfig, pluginConfigs).catch((err) => {
|
|
193
|
-
console.error(`${PACKAGE_NAME}: ${err}`);
|
|
194
|
-
process.exit(1);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function normalizePath(fileName: string): string {
|
|
199
|
-
return path.resolve(fileName).replace(BACKSLASH_REGEX, '/');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function passthrough(): void {
|
|
203
|
-
let args = process.argv.slice(2);
|
|
204
|
-
|
|
205
|
-
spawn(process.execPath, [require.resolve('typescript/lib/tsc.js'), ...args], { stdio: 'inherit' })
|
|
206
|
-
.on('exit', async (code) => {
|
|
207
|
-
if (code === 0) {
|
|
208
|
-
code = await runTscAlias(args);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
process.exit(code ?? 0);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function runTscAlias(args: string[]): Promise<number> {
|
|
216
|
-
for (let i = 0, n = args.length; i < n; i++) {
|
|
217
|
-
if (skipFlags.has(args[i])) {
|
|
218
|
-
return Promise.resolve(0);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return new Promise((resolve) => {
|
|
223
|
-
let child = spawn(process.execPath, [require.resolve('tsc-alias/dist/bin/index.js'), ...args], { stdio: 'inherit' });
|
|
224
|
-
|
|
225
|
-
child.on('exit', (code) => resolve(code ?? 0));
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (process.env.VITEST === undefined) {
|
|
231
|
-
main();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
export { build, isPlugin, loadPlugins, normalizePath, runTscAlias };
|
package/src/compiler/ast.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { Range } from './types';
|
|
2
|
-
import { ts } from '~/index';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const expression = {
|
|
6
|
-
name: (node: ts.Expression): string | null => {
|
|
7
|
-
if (ts.isIdentifier(node)) {
|
|
8
|
-
return node.text;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
12
|
-
return property.path(node);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const inRange = (ranges: Range[], start: number, end: number): boolean => {
|
|
20
|
-
for (let i = 0, n = ranges.length; i < n; i++) {
|
|
21
|
-
let r = ranges[i];
|
|
22
|
-
|
|
23
|
-
if (start >= r.start && end <= r.end) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return false;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const property = {
|
|
32
|
-
path: (node: ts.Expression): string | null => {
|
|
33
|
-
let current: ts.Node = node,
|
|
34
|
-
parts: string[] = [];
|
|
35
|
-
|
|
36
|
-
while (ts.isPropertyAccessExpression(current)) {
|
|
37
|
-
parts.push(current.name.text);
|
|
38
|
-
current = current.expression;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (ts.isIdentifier(current)) {
|
|
42
|
-
parts.push(current.text);
|
|
43
|
-
return parts.reverse().join('.');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const test = (node: ts.Node, fn: (n: ts.Node) => boolean): boolean => {
|
|
51
|
-
if (fn(node)) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return !!ts.forEachChild(node, child => test(child, fn) || undefined);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
export default { expression, inRange, property, test };
|
|
60
|
-
export type { Range };
|
package/src/compiler/code.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const SINGLE_QUOTE_REGEX = /'/g;
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const code = (literals: TemplateStringsArray, ...values: unknown[]): string => {
|
|
5
|
-
let buffer = '';
|
|
6
|
-
|
|
7
|
-
for (let i = 0, n = literals.length; i < n; i++) {
|
|
8
|
-
buffer += literals[i];
|
|
9
|
-
|
|
10
|
-
let value = values[i];
|
|
11
|
-
|
|
12
|
-
if (value == null || value === false) {
|
|
13
|
-
value = '';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
buffer += value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return buffer;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
code.escape = (str: string): string => {
|
|
23
|
-
return str.replace(SINGLE_QUOTE_REGEX, "\\'");
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export default code;
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import type { ImportIntent, Plugin, Replacement, ReplacementIntent, SharedContext } from './types';
|
|
2
|
-
import type { ModifyOptions } from './imports';
|
|
3
|
-
import { ts } from '~/index';
|
|
4
|
-
|
|
5
|
-
import imports from './imports';
|
|
6
|
-
import languageService from './language-service';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type CoordinatorResult = {
|
|
10
|
-
changed: boolean;
|
|
11
|
-
code: string;
|
|
12
|
-
sourceFile: ts.SourceFile;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function applyImports(code: string, file: ts.SourceFile, intents: ImportIntent[]): string {
|
|
17
|
-
let merged = new Map<string, { add?: string[]; namespace?: string; remove?: string[] }>();
|
|
18
|
-
|
|
19
|
-
for (let i = 0, n = intents.length; i < n; i++) {
|
|
20
|
-
let intent = intents[i],
|
|
21
|
-
existing = merged.get(intent.package);
|
|
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])!);
|
|
47
|
-
|
|
48
|
-
if (i < n - 1) {
|
|
49
|
-
file = ts.createSourceFile(file.fileName, code, file.languageVersion, true);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return code;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function applyIntents(code: string, file: ts.SourceFile, intents: ReplacementIntent[]): string {
|
|
57
|
-
if (intents.length === 0) {
|
|
58
|
-
return code;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return replaceReverse(
|
|
62
|
-
code,
|
|
63
|
-
intents.map(intent => ({
|
|
64
|
-
end: intent.node.end,
|
|
65
|
-
newText: intent.generate(file),
|
|
66
|
-
start: intent.node.getStart(file)
|
|
67
|
-
}))
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function applyPrepend(code: string, file: ts.SourceFile, prepend: string[]): string {
|
|
72
|
-
if (prepend.length === 0) {
|
|
73
|
-
return code;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let position = 0;
|
|
77
|
-
|
|
78
|
-
for (let i = 0, n = file.statements.length; i < n; i++) {
|
|
79
|
-
let stmt = file.statements[i];
|
|
80
|
-
|
|
81
|
-
if (ts.isImportDeclaration(stmt)) {
|
|
82
|
-
position = stmt.end;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (position === 0) {
|
|
90
|
-
return prepend.join('\n') + '\n' + code;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return code.slice(0, position) + '\n' + prepend.join('\n') + '\n' + code.slice(position);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function hasPattern(code: string, patterns: string[]): boolean {
|
|
97
|
-
for (let i = 0, n = patterns.length; i < n; i++) {
|
|
98
|
-
if (code.indexOf(patterns[i]) !== -1) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyOptions): string {
|
|
107
|
-
if (!options.add && !options.namespace && !options.remove) {
|
|
108
|
-
return code;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let { namespace } = options,
|
|
112
|
-
add = options.add ? new Set(options.add) : null,
|
|
113
|
-
found = imports.all(file, pkg);
|
|
114
|
-
|
|
115
|
-
if (found.length === 0) {
|
|
116
|
-
let statements: string[] = [];
|
|
117
|
-
|
|
118
|
-
if (namespace) {
|
|
119
|
-
statements.push(`import * as ${namespace} from '${pkg}';`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (add && add.size > 0) {
|
|
123
|
-
statements.push(`import { ${[...add].sort().join(', ')} } from '${pkg}';`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (statements.length === 0) {
|
|
127
|
-
return code;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return statements.join('\n') + '\n' + code;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let remove = options.remove ? new Set(options.remove) : null,
|
|
134
|
-
specifiers = new Set<string>();
|
|
135
|
-
|
|
136
|
-
for (let i = 0, n = found.length; i < n; i++) {
|
|
137
|
-
for (let [name, alias] of found[i].specifiers) {
|
|
138
|
-
if (!remove || (!remove.has(name) && !remove.has(alias))) {
|
|
139
|
-
let base = name === alias ? name : `${name} as ${alias}`;
|
|
140
|
-
|
|
141
|
-
// Preserve type-only specifiers with an inline `type` modifier so the merged import
|
|
142
|
-
// stays erasable — otherwise `import type { X }` re-emits as a runtime `import { X }`
|
|
143
|
-
// and fails at load time when X has no runtime export.
|
|
144
|
-
specifiers.add(found[i].typeOnly.has(name) ? `type ${base}` : base);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (add) {
|
|
150
|
-
for (let name of add) {
|
|
151
|
-
specifiers.add(name);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let statements: string[] = [];
|
|
156
|
-
|
|
157
|
-
if (namespace) {
|
|
158
|
-
statements.push(`import * as ${namespace} from '${pkg}';`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (specifiers.size > 0) {
|
|
162
|
-
statements.push(`import { ${[...specifiers].sort().join(', ')} } from '${pkg}';`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let replacements: Replacement[] = [];
|
|
166
|
-
|
|
167
|
-
for (let i = 0, n = found.length; i < n; i++) {
|
|
168
|
-
replacements.push({
|
|
169
|
-
end: found[i].end,
|
|
170
|
-
newText: i === 0 ? statements.join('\n') : '',
|
|
171
|
-
start: found[i].start
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return replaceReverse(code, replacements);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function replaceReverse(code: string, replacements: Replacement[]): string {
|
|
179
|
-
if (replacements.length === 0) {
|
|
180
|
-
return code;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
replacements.sort((a, b) => b.start - a.start);
|
|
184
|
-
|
|
185
|
-
let parts: string[] = [],
|
|
186
|
-
pos = code.length;
|
|
187
|
-
|
|
188
|
-
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
189
|
-
let r = replacements[i];
|
|
190
|
-
|
|
191
|
-
if (r.end < pos) {
|
|
192
|
-
parts.push(code.substring(r.end, pos));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
parts.push(r.newText);
|
|
196
|
-
pos = r.start;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (pos > 0) {
|
|
200
|
-
parts.push(code.substring(0, pos));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return parts.reverse().join('');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const transform = (
|
|
208
|
-
plugins: Plugin[],
|
|
209
|
-
code: string,
|
|
210
|
-
file: ts.SourceFile,
|
|
211
|
-
program: ts.Program,
|
|
212
|
-
root: string,
|
|
213
|
-
shared: SharedContext
|
|
214
|
-
) => {
|
|
215
|
-
if (plugins.length === 0) {
|
|
216
|
-
return { changed: false, code, sourceFile: file };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let changed = false,
|
|
220
|
-
currentCode = code,
|
|
221
|
-
currentFile = file,
|
|
222
|
-
currentProgram = program,
|
|
223
|
-
fileName = file.fileName,
|
|
224
|
-
last = plugins.length - 1;
|
|
225
|
-
|
|
226
|
-
for (let i = 0, n = plugins.length; i < n; i++) {
|
|
227
|
-
let plugin = plugins[i];
|
|
228
|
-
|
|
229
|
-
if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) {
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let { imports, prepend, replacements } = plugin.transform({
|
|
234
|
-
checker: currentProgram.getTypeChecker(),
|
|
235
|
-
code: currentCode,
|
|
236
|
-
program: currentProgram,
|
|
237
|
-
shared,
|
|
238
|
-
sourceFile: currentFile
|
|
239
|
-
}),
|
|
240
|
-
pluginChanged = false;
|
|
241
|
-
|
|
242
|
-
if (replacements?.length) {
|
|
243
|
-
currentCode = applyIntents(currentCode, currentFile, replacements);
|
|
244
|
-
pluginChanged = true;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (prepend?.length) {
|
|
248
|
-
if (pluginChanged) {
|
|
249
|
-
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
currentCode = applyPrepend(currentCode, currentFile, prepend);
|
|
253
|
-
pluginChanged = true;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (imports?.length) {
|
|
257
|
-
if (pluginChanged) {
|
|
258
|
-
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
currentCode = applyImports(currentCode, currentFile, imports);
|
|
262
|
-
pluginChanged = true;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (pluginChanged) {
|
|
266
|
-
changed = true;
|
|
267
|
-
|
|
268
|
-
if (i < last) {
|
|
269
|
-
currentProgram = languageService.update(root, fileName, currentCode);
|
|
270
|
-
currentFile = currentProgram.getSourceFile(fileName) ||
|
|
271
|
-
ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return { changed, code: currentCode, sourceFile: currentFile };
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
export default { transform };
|
|
284
|
-
export type { CoordinatorResult };
|