ttsc 0.4.4 → 0.5.0-dev.20260429
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 +35 -3
- package/cmd/ttsc/main.go +2 -2
- package/driver/program.go +120 -15
- package/go.mod +3 -3
- package/go.sum +6 -6
- package/lib/api.d.ts +3 -3
- package/lib/api.js +275 -624
- package/lib/api.js.map +1 -1
- package/lib/cli.js +78 -2
- package/lib/cli.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +3 -3
- package/lib/native.d.ts +5 -0
- package/lib/native.js +8 -0
- package/lib/native.js.map +1 -1
- package/lib/plugin.d.ts +8 -48
- package/lib/plugin.js +79 -132
- package/lib/plugin.js.map +1 -1
- package/lib/runner/cli.js +1 -0
- package/lib/runner/cli.js.map +1 -1
- package/lib/source-build.d.ts +30 -0
- package/lib/source-build.js +365 -0
- package/lib/source-build.js.map +1 -0
- package/package.json +9 -9
- package/shim/ast/go.mod +1 -1
- package/shim/ast/lint.go +328 -0
- package/shim/bundled/go.mod +1 -1
- package/shim/checker/go.mod +1 -1
- package/shim/compiler/go.mod +1 -1
- package/shim/core/go.mod +1 -1
- package/shim/core/shim.go +15 -0
- package/shim/diagnosticwriter/go.mod +1 -1
- package/shim/diagnosticwriter/lint.go +137 -0
- package/shim/parser/go.mod +1 -1
- package/shim/scanner/go.mod +1 -1
- package/shim/tsoptions/go.mod +1 -1
- package/shim/tspath/go.mod +1 -1
- package/shim/vfs/cachedvfs/go.mod +1 -1
- package/shim/vfs/go.mod +1 -1
- package/shim/vfs/osvfs/go.mod +1 -1
- package/src/api.ts +413 -771
- package/src/cli.ts +82 -2
- package/src/index.ts +3 -3
- package/src/native.ts +18 -0
- package/src/plugin.ts +107 -213
- package/src/runner/cli.ts +5 -0
- package/src/source-build.ts +383 -0
- package/test/native.test.ts +43 -0
- package/test/source-build.test.ts +219 -0
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "./api";
|
|
13
13
|
import { installHint, resolveBinary } from "./platform";
|
|
14
14
|
import { resolveProjectConfig, resolveProjectRoot } from "./project";
|
|
15
|
+
import { pluginCacheCleanupTargets } from "./source-build";
|
|
15
16
|
|
|
16
17
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
17
18
|
try {
|
|
@@ -35,6 +36,8 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
35
36
|
return runCompatibleBuild(rest, false);
|
|
36
37
|
case "check":
|
|
37
38
|
return runCompatibleBuild(rest, true);
|
|
39
|
+
case "clean":
|
|
40
|
+
return runClean(rest);
|
|
38
41
|
case "transform":
|
|
39
42
|
return runTransform(rest);
|
|
40
43
|
case "demo":
|
|
@@ -99,6 +102,49 @@ function runTransform(argv: readonly string[]): number {
|
|
|
99
102
|
return 0;
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
interface CleanInvocation {
|
|
106
|
+
cwd?: string;
|
|
107
|
+
tsconfig?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function runClean(argv: readonly string[]): number {
|
|
111
|
+
const options = parseCleanArgs(argv);
|
|
112
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
113
|
+
const projectRoot = resolveCleanProjectRoot(cwd, options.tsconfig);
|
|
114
|
+
const targets = pluginCacheCleanupTargets(projectRoot);
|
|
115
|
+
const removed: string[] = [];
|
|
116
|
+
for (const target of targets) {
|
|
117
|
+
if (!fs.existsSync(target)) continue;
|
|
118
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
119
|
+
removed.push(target);
|
|
120
|
+
}
|
|
121
|
+
if (removed.length === 0) {
|
|
122
|
+
process.stdout.write(`ttsc: no cache directories found under ${projectRoot}\n`);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
for (const target of removed) {
|
|
126
|
+
process.stdout.write(`ttsc: removed ${formatCleanPath(cwd, target)}\n`);
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveCleanProjectRoot(cwd: string, tsconfig?: string): string {
|
|
132
|
+
try {
|
|
133
|
+
return path.dirname(resolveProjectConfig({ cwd, tsconfig }));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (tsconfig) throw error;
|
|
136
|
+
return cwd;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function formatCleanPath(cwd: string, target: string): string {
|
|
141
|
+
const relative = path.relative(cwd, target);
|
|
142
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
143
|
+
return target;
|
|
144
|
+
}
|
|
145
|
+
return relative;
|
|
146
|
+
}
|
|
147
|
+
|
|
102
148
|
function delegateToNative(argv: readonly string[]): number {
|
|
103
149
|
const bin = resolveBinary();
|
|
104
150
|
if (!bin) {
|
|
@@ -122,6 +168,38 @@ function delegateToNative(argv: readonly string[]): number {
|
|
|
122
168
|
return result.status ?? 1;
|
|
123
169
|
}
|
|
124
170
|
|
|
171
|
+
function parseCleanArgs(argv: readonly string[]): CleanInvocation {
|
|
172
|
+
let cwd: string | undefined;
|
|
173
|
+
let tsconfig: string | undefined;
|
|
174
|
+
|
|
175
|
+
const rest = [...argv];
|
|
176
|
+
while (rest.length !== 0) {
|
|
177
|
+
const current = rest.shift()!;
|
|
178
|
+
switch (current) {
|
|
179
|
+
case "--cwd":
|
|
180
|
+
cwd = takeValue(current, rest);
|
|
181
|
+
break;
|
|
182
|
+
case "-p":
|
|
183
|
+
case "--tsconfig":
|
|
184
|
+
case "--project":
|
|
185
|
+
tsconfig = takeValue(current, rest);
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
if (current.startsWith("--cwd=")) {
|
|
189
|
+
cwd = current.slice("--cwd=".length);
|
|
190
|
+
} else if (current.startsWith("--tsconfig=")) {
|
|
191
|
+
tsconfig = current.slice("--tsconfig=".length);
|
|
192
|
+
} else if (current.startsWith("--project=")) {
|
|
193
|
+
tsconfig = current.slice("--project=".length);
|
|
194
|
+
} else {
|
|
195
|
+
throw new Error(`ttsc: unknown option ${current}`);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { cwd, tsconfig };
|
|
201
|
+
}
|
|
202
|
+
|
|
125
203
|
function parseBuildArgs(
|
|
126
204
|
argv: readonly string[],
|
|
127
205
|
checkOnly: boolean,
|
|
@@ -298,6 +376,7 @@ function printHelp(): void {
|
|
|
298
376
|
" ttsc -p tsconfig.json",
|
|
299
377
|
" ttsc --watch",
|
|
300
378
|
" ttsc --noEmit",
|
|
379
|
+
" ttsc clean [options]",
|
|
301
380
|
" ttsc transform --file <path> [options]",
|
|
302
381
|
" ttsc version",
|
|
303
382
|
" ttsc --help",
|
|
@@ -319,12 +398,13 @@ function printHelp(): void {
|
|
|
319
398
|
"",
|
|
320
399
|
"Plugin contract:",
|
|
321
400
|
" ttsc reads compilerOptions.plugins from tsconfig.json.",
|
|
322
|
-
"
|
|
323
|
-
"
|
|
401
|
+
" Plugin modules are descriptors for ordered native transformer backends.",
|
|
402
|
+
" JS transformOutput/transformSource hooks are not part of the public contract.",
|
|
324
403
|
"",
|
|
325
404
|
"Compatibility aliases:",
|
|
326
405
|
" ttsc build [options] Same project build lane as `ttsc [options]`.",
|
|
327
406
|
" ttsc check [options] Same as `ttsc --noEmit [options]`.",
|
|
407
|
+
" ttsc clean [options] Delete local source-plugin cache directories.",
|
|
328
408
|
].join("\n"),
|
|
329
409
|
);
|
|
330
410
|
process.stdout.write("\n");
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* - platform helpers (`resolveBinary`, `installHint`, …) — compatibility
|
|
8
8
|
* helper resolution for commands such as `ttsc demo`.
|
|
9
9
|
* - programmatic API (`transform`, `build`, `check`, `version`) — a thin
|
|
10
|
-
* TS wrapper around the consumer `tsgo` binary
|
|
11
|
-
*
|
|
12
|
-
* helpers and get back a string or a result record.
|
|
10
|
+
* TS wrapper around the consumer `tsgo` binary and native plugin
|
|
11
|
+
* descriptors. Adapters never have to shell out themselves; they call
|
|
12
|
+
* these helpers and get back a string or a result record.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
export * from "./platform";
|
package/src/native.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
export type NativeRewriteMode = string;
|
|
2
2
|
export type NativePluginContractVersion = 1;
|
|
3
3
|
|
|
4
|
+
export interface TtscNativeSource {
|
|
5
|
+
dir: string;
|
|
6
|
+
entry?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
export interface TtscNativeBackend {
|
|
5
10
|
mode: NativeRewriteMode;
|
|
6
11
|
binary?: string;
|
|
12
|
+
source?: TtscNativeSource;
|
|
7
13
|
contractVersion?: NativePluginContractVersion;
|
|
8
14
|
capabilities?: readonly string[];
|
|
9
15
|
}
|
|
@@ -53,6 +59,18 @@ export function resolveNativeBackend(
|
|
|
53
59
|
`ttsc: plugin "${plugin.name}" requested unsupported native contract version ${backend.contractVersion}`,
|
|
54
60
|
);
|
|
55
61
|
}
|
|
62
|
+
if (backend.source) {
|
|
63
|
+
if (backend.binary) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`ttsc: plugin "${plugin.name}" must use either native.binary or native.source, not both`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (typeof backend.source.dir !== "string" || backend.source.dir.length === 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`ttsc: plugin "${plugin.name}" native.source.dir must be a non-empty string`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
56
74
|
return {
|
|
57
75
|
...backend,
|
|
58
76
|
contractVersion: backend.contractVersion ?? 1,
|
package/src/plugin.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
@@ -11,11 +12,13 @@ import {
|
|
|
11
12
|
type ProjectPluginConfig,
|
|
12
13
|
readProjectConfig,
|
|
13
14
|
} from "./project";
|
|
15
|
+
import { buildSourcePlugin } from "./source-build";
|
|
14
16
|
|
|
15
17
|
export type {
|
|
16
18
|
NativePluginContractVersion,
|
|
17
19
|
NativeRewriteMode,
|
|
18
20
|
TtscNativeBackend,
|
|
21
|
+
TtscNativeSource,
|
|
19
22
|
} from "./native";
|
|
20
23
|
|
|
21
24
|
export interface TtscPluginFactoryContext {
|
|
@@ -25,53 +28,6 @@ export interface TtscPluginFactoryContext {
|
|
|
25
28
|
tsconfig: string;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export interface TtscTransformContext {
|
|
29
|
-
code: string;
|
|
30
|
-
command: "build" | "transform";
|
|
31
|
-
cwd: string;
|
|
32
|
-
compilerOptions: Record<string, unknown>;
|
|
33
|
-
outputFile?: string;
|
|
34
|
-
projectRoot: string;
|
|
35
|
-
sourceFile?: string;
|
|
36
|
-
tsconfig: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface TtscSourceTransformContext {
|
|
40
|
-
code: string;
|
|
41
|
-
command: "build" | "transform";
|
|
42
|
-
compilerOptions: Record<string, unknown>;
|
|
43
|
-
cwd: string;
|
|
44
|
-
projectRoot: string;
|
|
45
|
-
sourceFile: string;
|
|
46
|
-
tsconfig: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface TtscSourceEdit {
|
|
50
|
-
code: string;
|
|
51
|
-
end: number;
|
|
52
|
-
start: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface TtscSourceTransformResult {
|
|
56
|
-
code?: string;
|
|
57
|
-
edits?: readonly TtscSourceEdit[];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface AppliedTtscSourceEdit extends TtscSourceEdit {
|
|
61
|
-
newEnd: number;
|
|
62
|
-
newStart: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface TtscSourceTransformStage {
|
|
66
|
-
before: string;
|
|
67
|
-
edits: readonly AppliedTtscSourceEdit[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface TtscSourceTransformOutput {
|
|
71
|
-
code: string;
|
|
72
|
-
stages: readonly TtscSourceTransformStage[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
31
|
export interface TtscPlugin {
|
|
76
32
|
name: string;
|
|
77
33
|
native?: TtscNativeBackend;
|
|
@@ -79,10 +35,6 @@ export interface TtscPlugin {
|
|
|
79
35
|
nativeMode?: NativeRewriteMode;
|
|
80
36
|
/** @deprecated Use `native.binary` instead. */
|
|
81
37
|
nativeBinary?: string;
|
|
82
|
-
transformSource?(
|
|
83
|
-
context: TtscSourceTransformContext,
|
|
84
|
-
): string | TtscSourceTransformResult | readonly TtscSourceEdit[] | undefined;
|
|
85
|
-
transformOutput?(context: TtscTransformContext): string;
|
|
86
38
|
}
|
|
87
39
|
|
|
88
40
|
export type TtscPluginFactory = (
|
|
@@ -92,11 +44,17 @@ export type TtscPluginFactory = (
|
|
|
92
44
|
|
|
93
45
|
export type TtscPluginModule = TtscPlugin | TtscPluginFactory;
|
|
94
46
|
|
|
47
|
+
export interface LoadedNativePlugin {
|
|
48
|
+
backend: TtscNativeBackend;
|
|
49
|
+
config: ProjectPluginConfig;
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
95
53
|
export interface LoadedPlugins {
|
|
96
54
|
compatibilityFallback: boolean;
|
|
97
|
-
nativeBackend: TtscNativeBackend | null;
|
|
98
55
|
nativeBinary: string | null;
|
|
99
|
-
|
|
56
|
+
nativeBinaries: string[];
|
|
57
|
+
nativePlugins: LoadedNativePlugin[];
|
|
100
58
|
plugins: TtscPlugin[];
|
|
101
59
|
project: ParsedProjectConfig;
|
|
102
60
|
}
|
|
@@ -128,9 +86,9 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins {
|
|
|
128
86
|
if (entries.length === 0) {
|
|
129
87
|
return {
|
|
130
88
|
compatibilityFallback: false,
|
|
131
|
-
nativeBackend: null,
|
|
132
89
|
nativeBinary: null,
|
|
133
|
-
|
|
90
|
+
nativeBinaries: [],
|
|
91
|
+
nativePlugins: [],
|
|
134
92
|
plugins: [],
|
|
135
93
|
project,
|
|
136
94
|
};
|
|
@@ -144,169 +102,51 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins {
|
|
|
144
102
|
};
|
|
145
103
|
const plugins = entries.map((entry) => loadPluginEntry(entry, context));
|
|
146
104
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
105
|
+
const nativePlugins: LoadedNativePlugin[] = [];
|
|
106
|
+
const ttscVersion = readTtscVersion();
|
|
107
|
+
const tsgoVersion = readTsgoVersion(context.projectRoot);
|
|
108
|
+
plugins.forEach((plugin, index) => {
|
|
109
|
+
let backend = resolveNativeBackend(plugin);
|
|
152
110
|
if (!backend) {
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
if (nativeMode !== "none" && nativeMode !== backend.mode) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`ttsc: multiple native plugin modes requested (${nativeMode}, ${backend.mode})`,
|
|
158
|
-
);
|
|
111
|
+
return;
|
|
159
112
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
113
|
+
if (backend.source && !backend.binary) {
|
|
114
|
+
const built = buildSourcePlugin({
|
|
115
|
+
baseDir: context.projectRoot,
|
|
116
|
+
pluginName: plugin.name,
|
|
117
|
+
source: backend.source,
|
|
118
|
+
ttscVersion,
|
|
119
|
+
tsgoVersion,
|
|
120
|
+
});
|
|
121
|
+
backend = { ...backend, binary: built };
|
|
169
122
|
}
|
|
123
|
+
nativePlugins.push({ backend, config: entries[index]!, name: plugin.name });
|
|
124
|
+
});
|
|
125
|
+
if (nativePlugins.length !== plugins.length) {
|
|
126
|
+
const missing = plugins
|
|
127
|
+
.filter((plugin) => !resolveNativeBackend(plugin))
|
|
128
|
+
.map((plugin) => plugin.name)
|
|
129
|
+
.join(", ");
|
|
130
|
+
throw new Error(
|
|
131
|
+
`ttsc: every plugin must declare a native backend; missing native for ${missing}`,
|
|
132
|
+
);
|
|
170
133
|
}
|
|
171
134
|
return {
|
|
172
135
|
compatibilityFallback: false,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
136
|
+
nativeBinary: nativePlugins[0]?.backend.binary ?? null,
|
|
137
|
+
nativeBinaries: [
|
|
138
|
+
...new Set(
|
|
139
|
+
nativePlugins
|
|
140
|
+
.map((plugin) => plugin.backend.binary)
|
|
141
|
+
.filter((binary): binary is string => typeof binary === "string"),
|
|
142
|
+
),
|
|
143
|
+
],
|
|
144
|
+
nativePlugins,
|
|
176
145
|
plugins,
|
|
177
146
|
project,
|
|
178
147
|
};
|
|
179
148
|
}
|
|
180
149
|
|
|
181
|
-
export function applyPluginTransforms(
|
|
182
|
-
plugins: readonly TtscPlugin[],
|
|
183
|
-
context: TtscTransformContext,
|
|
184
|
-
): string {
|
|
185
|
-
let code = context.code;
|
|
186
|
-
for (const plugin of plugins) {
|
|
187
|
-
if (!plugin.transformOutput) {
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
code = plugin.transformOutput({ ...context, code });
|
|
191
|
-
}
|
|
192
|
-
return code;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function applyPluginSourceTransforms(
|
|
196
|
-
plugins: readonly TtscPlugin[],
|
|
197
|
-
context: TtscSourceTransformContext,
|
|
198
|
-
): string {
|
|
199
|
-
return applyPluginSourceTransformsWithMap(plugins, context).code;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function applyPluginSourceTransformsWithMap(
|
|
203
|
-
plugins: readonly TtscPlugin[],
|
|
204
|
-
context: TtscSourceTransformContext,
|
|
205
|
-
): TtscSourceTransformOutput {
|
|
206
|
-
let code = context.code;
|
|
207
|
-
const stages: TtscSourceTransformStage[] = [];
|
|
208
|
-
for (const plugin of plugins) {
|
|
209
|
-
if (!plugin.transformSource) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
const before = code;
|
|
213
|
-
const result = plugin.transformSource({ ...context, code });
|
|
214
|
-
if (typeof result === "string") {
|
|
215
|
-
code = result;
|
|
216
|
-
if (code !== before) {
|
|
217
|
-
stages.push({
|
|
218
|
-
before,
|
|
219
|
-
edits: createAppliedSourceEdits(before, [
|
|
220
|
-
{ code, end: before.length, start: 0 },
|
|
221
|
-
]),
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (isSourceEditArray(result)) {
|
|
227
|
-
const edits = createAppliedSourceEdits(before, result);
|
|
228
|
-
code = applySourceEdits(before, edits);
|
|
229
|
-
if (edits.length > 0) {
|
|
230
|
-
stages.push({ before, edits });
|
|
231
|
-
}
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (result?.code !== undefined) {
|
|
235
|
-
code = result.code;
|
|
236
|
-
if (code !== before) {
|
|
237
|
-
stages.push({
|
|
238
|
-
before,
|
|
239
|
-
edits: createAppliedSourceEdits(before, [
|
|
240
|
-
{ code, end: before.length, start: 0 },
|
|
241
|
-
]),
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (result?.edits) {
|
|
247
|
-
const edits = createAppliedSourceEdits(before, result.edits);
|
|
248
|
-
code = applySourceEdits(before, edits);
|
|
249
|
-
if (edits.length > 0) {
|
|
250
|
-
stages.push({ before, edits });
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return { code, stages };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function isSourceEditArray(value: unknown): value is readonly TtscSourceEdit[] {
|
|
258
|
-
return Array.isArray(value);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function applySourceEdits(
|
|
262
|
-
source: string,
|
|
263
|
-
edits: readonly TtscSourceEdit[],
|
|
264
|
-
): string {
|
|
265
|
-
if (edits.length === 0) {
|
|
266
|
-
return source;
|
|
267
|
-
}
|
|
268
|
-
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
269
|
-
let output = source;
|
|
270
|
-
for (const edit of ordered) {
|
|
271
|
-
output = output.slice(0, edit.start) + edit.code + output.slice(edit.end);
|
|
272
|
-
}
|
|
273
|
-
return output;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function createAppliedSourceEdits(
|
|
277
|
-
source: string,
|
|
278
|
-
edits: readonly TtscSourceEdit[],
|
|
279
|
-
): AppliedTtscSourceEdit[] {
|
|
280
|
-
if (edits.length === 0) {
|
|
281
|
-
return [];
|
|
282
|
-
}
|
|
283
|
-
const ordered = [...edits].sort((a, b) => a.start - b.start);
|
|
284
|
-
let previousEnd = 0;
|
|
285
|
-
let delta = 0;
|
|
286
|
-
const applied: AppliedTtscSourceEdit[] = [];
|
|
287
|
-
for (const edit of ordered) {
|
|
288
|
-
if (!Number.isInteger(edit.start) || !Number.isInteger(edit.end)) {
|
|
289
|
-
throw new Error("ttsc: transformSource edit offsets must be integers");
|
|
290
|
-
}
|
|
291
|
-
if (
|
|
292
|
-
edit.start < 0 ||
|
|
293
|
-
edit.end < edit.start ||
|
|
294
|
-
edit.end > source.length ||
|
|
295
|
-
edit.start < previousEnd
|
|
296
|
-
) {
|
|
297
|
-
throw new Error(
|
|
298
|
-
`ttsc: invalid or overlapping transformSource edit ${edit.start}:${edit.end}`,
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
const newStart = edit.start + delta;
|
|
302
|
-
const newEnd = newStart + edit.code.length;
|
|
303
|
-
applied.push({ ...edit, newEnd, newStart });
|
|
304
|
-
delta += edit.code.length - (edit.end - edit.start);
|
|
305
|
-
previousEnd = edit.end;
|
|
306
|
-
}
|
|
307
|
-
return applied;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
150
|
function loadPluginEntry(
|
|
311
151
|
entry: ProjectPluginConfig,
|
|
312
152
|
context: TtscPluginFactoryContext,
|
|
@@ -327,13 +167,17 @@ function loadPluginEntry(
|
|
|
327
167
|
mod.plugin ??
|
|
328
168
|
(mod as unknown as TtscPluginModule);
|
|
329
169
|
if (typeof candidate === "function") {
|
|
330
|
-
|
|
170
|
+
const plugin = candidate(entry, context);
|
|
171
|
+
if (!isTtscPlugin(plugin)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
rejectJsTransformHooks(specifier, plugin);
|
|
177
|
+
return plugin;
|
|
331
178
|
}
|
|
332
|
-
if (
|
|
333
|
-
candidate
|
|
334
|
-
typeof candidate === "object" &&
|
|
335
|
-
typeof candidate.name === "string"
|
|
336
|
-
) {
|
|
179
|
+
if (isTtscPlugin(candidate)) {
|
|
180
|
+
rejectJsTransformHooks(specifier, candidate);
|
|
337
181
|
return candidate;
|
|
338
182
|
}
|
|
339
183
|
throw new Error(
|
|
@@ -341,6 +185,23 @@ function loadPluginEntry(
|
|
|
341
185
|
);
|
|
342
186
|
}
|
|
343
187
|
|
|
188
|
+
function isTtscPlugin(value: unknown): value is TtscPlugin {
|
|
189
|
+
return (
|
|
190
|
+
typeof value === "object" &&
|
|
191
|
+
value !== null &&
|
|
192
|
+
typeof (value as { name?: unknown }).name === "string"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function rejectJsTransformHooks(specifier: string, candidate: object): void {
|
|
197
|
+
if ("transformSource" in candidate || "transformOutput" in candidate) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`ttsc: plugin "${specifier}" declares unsupported JS transform hooks; ` +
|
|
200
|
+
"declare a native backend instead",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
344
205
|
function resolvePluginRequest(specifier: string, projectRoot: string): string {
|
|
345
206
|
if (path.isAbsolute(specifier)) {
|
|
346
207
|
return specifier;
|
|
@@ -394,3 +255,36 @@ function resolveSourceCheckoutPlugin(
|
|
|
394
255
|
return null;
|
|
395
256
|
}
|
|
396
257
|
}
|
|
258
|
+
|
|
259
|
+
let cachedTtscVersion: string | null = null;
|
|
260
|
+
|
|
261
|
+
function readTtscVersion(): string {
|
|
262
|
+
if (cachedTtscVersion !== null) {
|
|
263
|
+
return cachedTtscVersion;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const file = path.resolve(__dirname, "..", "package.json");
|
|
267
|
+
const pkg = JSON.parse(fs.readFileSync(file, "utf8")) as {
|
|
268
|
+
version?: string;
|
|
269
|
+
};
|
|
270
|
+
cachedTtscVersion = pkg.version ?? "0.0.0";
|
|
271
|
+
} catch {
|
|
272
|
+
cachedTtscVersion = "0.0.0";
|
|
273
|
+
}
|
|
274
|
+
return cachedTtscVersion;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function readTsgoVersion(projectRoot: string): string {
|
|
278
|
+
try {
|
|
279
|
+
const projectRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
280
|
+
const pkgPath = projectRequire.resolve(
|
|
281
|
+
"@typescript/native-preview/package.json",
|
|
282
|
+
);
|
|
283
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as {
|
|
284
|
+
version?: string;
|
|
285
|
+
};
|
|
286
|
+
return pkg.version ?? "unknown";
|
|
287
|
+
} catch {
|
|
288
|
+
return "unknown";
|
|
289
|
+
}
|
|
290
|
+
}
|
package/src/runner/cli.ts
CHANGED
|
@@ -244,6 +244,11 @@ function rewriteEsmSpecifiers(root: string): void {
|
|
|
244
244
|
(_, quote: string, specifier: string) =>
|
|
245
245
|
`from ${quote}${withResolvableExtension(next, specifier)}${quote}`,
|
|
246
246
|
)
|
|
247
|
+
.replace(
|
|
248
|
+
/\bimport\s+(['"])(\.[^'"]+)\1/g,
|
|
249
|
+
(_, quote: string, specifier: string) =>
|
|
250
|
+
`import ${quote}${withResolvableExtension(next, specifier)}${quote}`,
|
|
251
|
+
)
|
|
247
252
|
.replace(
|
|
248
253
|
/\bimport\(\s*(['"])(\.[^'"]+)\1\s*\)/g,
|
|
249
254
|
(_, quote: string, specifier: string) =>
|