ttsc 0.4.3 → 0.4.4-dev.20260426

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/src/plugin.ts CHANGED
@@ -29,12 +29,49 @@ export interface TtscTransformContext {
29
29
  code: string;
30
30
  command: "build" | "transform";
31
31
  cwd: string;
32
+ compilerOptions: Record<string, unknown>;
32
33
  outputFile?: string;
33
34
  projectRoot: string;
34
35
  sourceFile?: string;
35
36
  tsconfig: string;
36
37
  }
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
+
38
75
  export interface TtscPlugin {
39
76
  name: string;
40
77
  native?: TtscNativeBackend;
@@ -42,6 +79,9 @@ export interface TtscPlugin {
42
79
  nativeMode?: NativeRewriteMode;
43
80
  /** @deprecated Use `native.binary` instead. */
44
81
  nativeBinary?: string;
82
+ transformSource?(
83
+ context: TtscSourceTransformContext,
84
+ ): string | TtscSourceTransformResult | readonly TtscSourceEdit[] | undefined;
45
85
  transformOutput?(context: TtscTransformContext): string;
46
86
  }
47
87
 
@@ -152,6 +192,121 @@ export function applyPluginTransforms(
152
192
  return code;
153
193
  }
154
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
+
155
310
  function loadPluginEntry(
156
311
  entry: ProjectPluginConfig,
157
312
  context: TtscPluginFactoryContext,
package/src/project.ts CHANGED
@@ -19,7 +19,7 @@ export interface ParsedProjectConfig {
19
19
  compilerOptions: {
20
20
  outDir?: string;
21
21
  plugins: ProjectPluginConfig[];
22
- };
22
+ } & Record<string, unknown>;
23
23
  path: string;
24
24
  root: string;
25
25
  }
@@ -69,6 +69,7 @@ export function readProjectConfig(opts: ProjectLocatorOptions = {}): ParsedProje
69
69
  const compilerOptions = readResolvedCompilerOptions(tsconfig);
70
70
  return {
71
71
  compilerOptions: {
72
+ ...compilerOptions.options,
72
73
  outDir: compilerOptions.outDir,
73
74
  plugins: compilerOptions.plugins,
74
75
  },
@@ -130,13 +131,14 @@ function isProjectPluginConfig(value: unknown): value is ProjectPluginConfig {
130
131
 
131
132
  interface RawTsconfig {
132
133
  extends?: unknown;
133
- compilerOptions?: {
134
+ compilerOptions?: Record<string, unknown> & {
134
135
  outDir?: unknown;
135
136
  plugins?: unknown;
136
137
  };
137
138
  }
138
139
 
139
140
  interface ResolvedCompilerOptions {
141
+ options: Record<string, unknown>;
140
142
  outDir?: string;
141
143
  plugins: ProjectPluginConfig[];
142
144
  }
@@ -164,8 +166,13 @@ function readResolvedCompilerOptions(
164
166
  const base =
165
167
  typeof parsed.extends === "string"
166
168
  ? readResolvedCompilerOptions(resolveExtendsConfig(canonical, parsed.extends), seen)
167
- : { plugins: [] };
169
+ : { options: {}, plugins: [] };
170
+ const options = {
171
+ ...base.options,
172
+ ...(own ?? {}),
173
+ };
168
174
  return {
175
+ options,
169
176
  outDir:
170
177
  typeof own?.outDir === "string"
171
178
  ? resolveAbsolutePath(path.dirname(canonical), own.outDir)