ttsc 0.8.1 → 0.10.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.
Files changed (88) hide show
  1. package/README.md +39 -68
  2. package/cmd/ttsc/api_compile.go +109 -115
  3. package/cmd/ttsc/api_transform.go +54 -51
  4. package/cmd/ttsc/main.go +103 -121
  5. package/driver/program.go +30 -2
  6. package/driver/rewrite.go +126 -9
  7. package/go.mod +2 -0
  8. package/lib/TtscCompiler.js +28 -46
  9. package/lib/TtscCompiler.js.map +1 -1
  10. package/lib/compiler/internal/buildNativeCompiler.js +8 -38
  11. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  12. package/lib/compiler/internal/compileProjectInMemory.js +36 -63
  13. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  14. package/lib/compiler/internal/project/readProjectConfig.js +108 -60
  15. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  16. package/lib/compiler/internal/project/resolveProjectConfig.js +15 -45
  17. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  18. package/lib/compiler/internal/resolveBinary.js +10 -40
  19. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  20. package/lib/compiler/internal/resolveEmittedJavaScript.js +21 -48
  21. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  22. package/lib/compiler/internal/resolveTsgo.js +14 -44
  23. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  24. package/lib/compiler/internal/runBuild.d.ts +7 -0
  25. package/lib/compiler/internal/runBuild.js +47 -66
  26. package/lib/compiler/internal/runBuild.js.map +1 -1
  27. package/lib/compiler/internal/runSingleFileEmit.js +18 -48
  28. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  29. package/lib/compiler/internal/transformProjectInMemory.js +41 -75
  30. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  31. package/lib/launcher/internal/getCompilerVersionText.js +7 -37
  32. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  33. package/lib/launcher/internal/prepareExecution.js +48 -69
  34. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  35. package/lib/launcher/internal/runTtsc.js +53 -64
  36. package/lib/launcher/internal/runTtsc.js.map +1 -1
  37. package/lib/launcher/internal/runTtsx.js +328 -57
  38. package/lib/launcher/internal/runTtsx.js.map +1 -1
  39. package/lib/plugin/internal/buildSourcePlugin.d.ts +8 -0
  40. package/lib/plugin/internal/buildSourcePlugin.js +287 -139
  41. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  42. package/lib/plugin/internal/loadProjectPlugins.d.ts +2 -0
  43. package/lib/plugin/internal/loadProjectPlugins.js +197 -9
  44. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  45. package/lib/structures/ITtscCompilerContext.d.ts +16 -3
  46. package/lib/structures/ITtscCompilerResult.d.ts +4 -0
  47. package/lib/structures/ITtscCompilerTransformation.d.ts +2 -0
  48. package/lib/structures/ITtscPluginFactoryContext.d.ts +6 -3
  49. package/lib/structures/ITtscProjectPluginConfig.d.ts +8 -7
  50. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  51. package/lib/structures/internal/ITtscProjectLocatorOptions.d.ts +2 -0
  52. package/lib/structures/internal/TtscCommonOptions.d.ts +2 -0
  53. package/package.json +9 -14
  54. package/shim/diagnosticwriter/lint.go +8 -0
  55. package/shim/printer/go.mod +5 -0
  56. package/shim/printer/shim.go +23 -0
  57. package/src/TtscCompiler.ts +20 -5
  58. package/src/compiler/internal/buildNativeCompiler.ts +2 -2
  59. package/src/compiler/internal/compileProjectInMemory.ts +21 -16
  60. package/src/compiler/internal/project/readProjectConfig.ts +134 -35
  61. package/src/compiler/internal/project/resolveProjectConfig.ts +2 -2
  62. package/src/compiler/internal/resolveBinary.ts +2 -2
  63. package/src/compiler/internal/resolveEmittedJavaScript.ts +11 -7
  64. package/src/compiler/internal/resolveTsgo.ts +2 -2
  65. package/src/compiler/internal/runBuild.ts +34 -21
  66. package/src/compiler/internal/runSingleFileEmit.ts +3 -3
  67. package/src/compiler/internal/transformProjectInMemory.ts +37 -37
  68. package/src/launcher/internal/getCompilerVersionText.ts +2 -2
  69. package/src/launcher/internal/prepareExecution.ts +14 -4
  70. package/src/launcher/internal/runTtsc.ts +30 -4
  71. package/src/launcher/internal/runTtsx.ts +377 -26
  72. package/src/plugin/internal/buildSourcePlugin.ts +321 -50
  73. package/src/plugin/internal/loadProjectPlugins.ts +266 -10
  74. package/src/structures/ITtscCompilerContext.ts +17 -3
  75. package/src/structures/ITtscCompilerResult.ts +5 -0
  76. package/src/structures/ITtscCompilerTransformation.ts +3 -0
  77. package/src/structures/ITtscPluginFactoryContext.ts +6 -3
  78. package/src/structures/ITtscProjectPluginConfig.ts +8 -7
  79. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  80. package/src/structures/internal/ITtscProjectLocatorOptions.ts +2 -0
  81. package/src/structures/internal/TtscCommonOptions.ts +2 -0
  82. package/utility/host.go +622 -20
  83. package/cmd/ttsc/main_test.go +0 -288
  84. package/test/compiler.test.ts +0 -422
  85. package/test/platform.test.ts +0 -12
  86. package/test/project.test.ts +0 -197
  87. package/test/source-build.test.ts +0 -45
  88. package/test/tsgo.test.ts +0 -69
@@ -1,6 +1,6 @@
1
- import * as fs from "node:fs";
1
+ import fs from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
- import * as path from "node:path";
3
+ import path from "node:path";
4
4
 
5
5
  import { getCompilerVersionText } from "./getCompilerVersionText";
6
6
  import { prepareExecution } from "./prepareExecution";
@@ -38,7 +38,7 @@ function run(argv: readonly string[]): number {
38
38
 
39
39
  const prepared = prepareExecution(entry, {
40
40
  binary: parsed.binary,
41
- cacheDir: parsed.cacheDir,
41
+ cacheDir: resolveCacheDir(cwd, parsed.cacheDir),
42
42
  cwd,
43
43
  project: parsed.project,
44
44
  });
@@ -140,9 +140,9 @@ function printHelp(): void {
140
140
  "Options:",
141
141
  " -P, --project <file> Use an explicit tsconfig.json",
142
142
  " --cwd <dir> Resolve entry/project relative to this directory",
143
- " --cache-dir <dir> Override the compiled JS cache directory",
143
+ " --cache-dir <dir> Override the runner and source-plugin cache root",
144
144
  " --binary <path> Use an explicit tsgo binary",
145
- " -r, --require <file> Preload a module before the entrypoint",
145
+ " -r, --require <module> Preload a module before the entrypoint",
146
146
  " -h, --help Show this help",
147
147
  " -v, --version Print the runner version",
148
148
  "",
@@ -155,16 +155,30 @@ function printHelp(): void {
155
155
  }
156
156
 
157
157
  function resolvePreload(cwd: string, preload: string): string {
158
- if (
159
- preload.startsWith(".") ||
160
- preload.startsWith("/") ||
161
- preload.includes(path.sep)
162
- ) {
158
+ if (path.isAbsolute(preload) || isRelativeSpecifier(preload)) {
163
159
  return path.resolve(cwd, preload);
164
160
  }
165
161
  return preload;
166
162
  }
167
163
 
164
+ function isRelativeSpecifier(specifier: string): boolean {
165
+ return (
166
+ specifier === "." ||
167
+ specifier === ".." ||
168
+ specifier.startsWith("./") ||
169
+ specifier.startsWith("../") ||
170
+ specifier.startsWith(".\\") ||
171
+ specifier.startsWith("..\\")
172
+ );
173
+ }
174
+
175
+ function resolveCacheDir(cwd: string, cacheDir?: string): string | undefined {
176
+ if (!cacheDir) {
177
+ return undefined;
178
+ }
179
+ return path.isAbsolute(cacheDir) ? cacheDir : path.resolve(cwd, cacheDir);
180
+ }
181
+
168
182
  function takeValue(flag: string, rest: string[]): string {
169
183
  const value = rest.shift();
170
184
  if (!value) {
@@ -196,6 +210,7 @@ function runPreparedEntry(
196
210
  ...parsed.passthrough,
197
211
  ];
198
212
  const result = spawnSync(process.execPath, args, {
213
+ cwd,
199
214
  stdio: "inherit",
200
215
  env: process.env,
201
216
  windowsHide: true,
@@ -221,22 +236,7 @@ function rewriteEsmSpecifiers(root: string): void {
221
236
  continue;
222
237
  }
223
238
  const before = fs.readFileSync(next, "utf8");
224
- const after = before
225
- .replace(
226
- /\bfrom\s+(['"])(\.[^'"]+)\1/g,
227
- (_, quote: string, specifier: string) =>
228
- `from ${quote}${withResolvableExtension(next, specifier)}${quote}`,
229
- )
230
- .replace(
231
- /\bimport\s+(['"])(\.[^'"]+)\1/g,
232
- (_, quote: string, specifier: string) =>
233
- `import ${quote}${withResolvableExtension(next, specifier)}${quote}`,
234
- )
235
- .replace(
236
- /\bimport\(\s*(['"])(\.[^'"]+)\1\s*\)/g,
237
- (_, quote: string, specifier: string) =>
238
- `import(${quote}${withResolvableExtension(next, specifier)}${quote})`,
239
- );
239
+ const after = rewriteEsmSpecifiersInText(next, before);
240
240
  if (after !== before) {
241
241
  fs.writeFileSync(next, after, "utf8");
242
242
  }
@@ -244,6 +244,193 @@ function rewriteEsmSpecifiers(root: string): void {
244
244
  }
245
245
  }
246
246
 
247
+ function rewriteEsmSpecifiersInText(fromFile: string, input: string): string {
248
+ const replacements: Array<{ start: number; end: number; text: string }> = [];
249
+ scanEsmSpecifiers(fromFile, input, 0, input.length, replacements);
250
+ if (replacements.length === 0) {
251
+ return input;
252
+ }
253
+ let output = "";
254
+ let last = 0;
255
+ for (const replacement of replacements) {
256
+ output += input.slice(last, replacement.start);
257
+ output += replacement.text;
258
+ last = replacement.end;
259
+ }
260
+ return output + input.slice(last);
261
+ }
262
+
263
+ function scanEsmSpecifiers(
264
+ fromFile: string,
265
+ input: string,
266
+ start: number,
267
+ end: number,
268
+ replacements: Array<{ start: number; end: number; text: string }>,
269
+ ): void {
270
+ let i = start;
271
+ while (i < end) {
272
+ if (input[i] === "`") {
273
+ i = scanTemplateExpressions(fromFile, input, i, replacements);
274
+ continue;
275
+ }
276
+ const skipped = skipNonCode(input, i);
277
+ if (skipped !== i) {
278
+ i = skipped;
279
+ continue;
280
+ }
281
+ if (isKeywordAt(input, i, "import")) {
282
+ const next = skipWhitespace(input, i + "import".length);
283
+ if (input[next] === "(") {
284
+ rewriteDynamicImportSpecifier(fromFile, input, next, replacements);
285
+ } else if (isQuote(input[next])) {
286
+ rewriteStringSpecifier(fromFile, input, next, replacements);
287
+ } else {
288
+ rewriteFromSpecifier(fromFile, input, next, replacements);
289
+ }
290
+ i += "import".length;
291
+ continue;
292
+ }
293
+ if (isKeywordAt(input, i, "export")) {
294
+ rewriteFromSpecifier(fromFile, input, i + "export".length, replacements);
295
+ i += "export".length;
296
+ continue;
297
+ }
298
+ i += 1;
299
+ }
300
+ }
301
+
302
+ function rewriteDynamicImportSpecifier(
303
+ fromFile: string,
304
+ input: string,
305
+ openParen: number,
306
+ replacements: Array<{ start: number; end: number; text: string }>,
307
+ ): void {
308
+ const specifier = skipWhitespace(input, openParen + 1);
309
+ if (isQuote(input[specifier])) {
310
+ rewriteStringSpecifier(fromFile, input, specifier, replacements);
311
+ }
312
+ }
313
+
314
+ function rewriteFromSpecifier(
315
+ fromFile: string,
316
+ input: string,
317
+ start: number,
318
+ replacements: Array<{ start: number; end: number; text: string }>,
319
+ ): void {
320
+ let i = start;
321
+ let depth = 0;
322
+ while (i < input.length) {
323
+ const skipped = skipNonCode(input, i);
324
+ if (skipped !== i) {
325
+ i = skipped;
326
+ continue;
327
+ }
328
+ const current = input[i]!;
329
+ if (depth === 0 && current === ";") {
330
+ return;
331
+ }
332
+ if (current === "{" || current === "[" || current === "(") {
333
+ depth += 1;
334
+ i += 1;
335
+ continue;
336
+ }
337
+ if (current === "}" || current === "]" || current === ")") {
338
+ depth = Math.max(0, depth - 1);
339
+ i += 1;
340
+ continue;
341
+ }
342
+ if (depth === 0 && isKeywordAt(input, i, "from")) {
343
+ const specifier = skipWhitespace(input, i + "from".length);
344
+ if (isQuote(input[specifier])) {
345
+ rewriteStringSpecifier(fromFile, input, specifier, replacements);
346
+ }
347
+ return;
348
+ }
349
+ i += 1;
350
+ }
351
+ }
352
+
353
+ function rewriteStringSpecifier(
354
+ fromFile: string,
355
+ input: string,
356
+ quoteIndex: number,
357
+ replacements: Array<{ start: number; end: number; text: string }>,
358
+ ): void {
359
+ const literal = readSimpleStringLiteral(input, quoteIndex);
360
+ if (literal === null) {
361
+ return;
362
+ }
363
+ const next = withResolvableExtension(fromFile, literal.value);
364
+ if (next === literal.value) {
365
+ return;
366
+ }
367
+ replacements.push({
368
+ start: literal.start,
369
+ end: literal.end,
370
+ text: next,
371
+ });
372
+ }
373
+
374
+ function scanTemplateExpressions(
375
+ fromFile: string,
376
+ input: string,
377
+ start: number,
378
+ replacements: Array<{ start: number; end: number; text: string }>,
379
+ ): number {
380
+ for (let i = start + 1; i < input.length; i += 1) {
381
+ const current = input[i]!;
382
+ if (current === "\\") {
383
+ i += 1;
384
+ continue;
385
+ }
386
+ if (current === "`") {
387
+ return i + 1;
388
+ }
389
+ if (current === "$" && input[i + 1] === "{") {
390
+ const expressionStart = i + 2;
391
+ const expressionEnd = findTemplateExpressionEnd(input, expressionStart);
392
+ if (expressionEnd === null) {
393
+ return input.length;
394
+ }
395
+ scanEsmSpecifiers(
396
+ fromFile,
397
+ input,
398
+ expressionStart,
399
+ expressionEnd,
400
+ replacements,
401
+ );
402
+ i = expressionEnd;
403
+ }
404
+ }
405
+ return input.length;
406
+ }
407
+
408
+ function findTemplateExpressionEnd(
409
+ input: string,
410
+ expressionStart: number,
411
+ ): number | null {
412
+ let depth = 1;
413
+ for (let i = expressionStart; i < input.length; i += 1) {
414
+ const skipped = skipNonCode(input, i);
415
+ if (skipped !== i) {
416
+ i = skipped - 1;
417
+ continue;
418
+ }
419
+ const current = input[i]!;
420
+ if (current === "{") {
421
+ depth += 1;
422
+ continue;
423
+ }
424
+ if (current === "}") {
425
+ depth -= 1;
426
+ if (depth === 0) {
427
+ return i;
428
+ }
429
+ }
430
+ }
431
+ return null;
432
+ }
433
+
247
434
  function withResolvableExtension(fromFile: string, specifier: string): string {
248
435
  if (!specifier.startsWith(".")) {
249
436
  return specifier;
@@ -258,6 +445,12 @@ function withResolvableExtension(fromFile: string, specifier: string): string {
258
445
  return pathname + extension + suffix;
259
446
  }
260
447
  }
448
+ const directory = pathname.replace(/\/+$/, "");
449
+ for (const extension of [".js", ".mjs", ".cjs"]) {
450
+ if (fs.existsSync(path.resolve(fromDir, directory, "index" + extension))) {
451
+ return `${directory}/index${extension}${suffix}`;
452
+ }
453
+ }
261
454
  return `${specifier}.js`;
262
455
  }
263
456
 
@@ -272,3 +465,161 @@ function splitSpecifierSuffix(specifier: string): [string, string?] {
272
465
  function isJavaScriptOutput(filename: string): boolean {
273
466
  return /\.(?:[cm]?js)$/i.test(filename);
274
467
  }
468
+
469
+ function skipNonCode(input: string, start: number): number {
470
+ const current = input[start];
471
+ const next = input[start + 1];
472
+ if (current === '"' || current === "'") {
473
+ return skipString(input, start);
474
+ }
475
+ if (current === "`") {
476
+ return skipTemplate(input, start);
477
+ }
478
+ if (current === "/" && next === "/") {
479
+ return skipLineComment(input, start);
480
+ }
481
+ if (current === "/" && next === "*") {
482
+ return skipBlockComment(input, start);
483
+ }
484
+ if (current === "/" && looksLikeRegexStart(input, start)) {
485
+ return skipRegex(input, start);
486
+ }
487
+ return start;
488
+ }
489
+
490
+ function skipString(input: string, start: number): number {
491
+ const quote = input[start]!;
492
+ for (let i = start + 1; i < input.length; i += 1) {
493
+ const current = input[i]!;
494
+ if (current === "\\") {
495
+ i += 1;
496
+ continue;
497
+ }
498
+ if (current === quote) {
499
+ return i + 1;
500
+ }
501
+ }
502
+ return input.length;
503
+ }
504
+
505
+ function skipTemplate(input: string, start: number): number {
506
+ for (let i = start + 1; i < input.length; i += 1) {
507
+ const current = input[i]!;
508
+ if (current === "\\") {
509
+ i += 1;
510
+ continue;
511
+ }
512
+ if (current === "`") {
513
+ return i + 1;
514
+ }
515
+ }
516
+ return input.length;
517
+ }
518
+
519
+ function skipLineComment(input: string, start: number): number {
520
+ const end = input.indexOf("\n", start + 2);
521
+ return end === -1 ? input.length : end + 1;
522
+ }
523
+
524
+ function skipBlockComment(input: string, start: number): number {
525
+ const end = input.indexOf("*/", start + 2);
526
+ return end === -1 ? input.length : end + 2;
527
+ }
528
+
529
+ function skipRegex(input: string, start: number): number {
530
+ let inClass = false;
531
+ for (let i = start + 1; i < input.length; i += 1) {
532
+ const current = input[i]!;
533
+ if (current === "\\") {
534
+ i += 1;
535
+ continue;
536
+ }
537
+ if (current === "[") {
538
+ inClass = true;
539
+ continue;
540
+ }
541
+ if (current === "]") {
542
+ inClass = false;
543
+ continue;
544
+ }
545
+ if (current === "/" && !inClass) {
546
+ i += 1;
547
+ while (/[A-Za-z]/.test(input[i] ?? "")) {
548
+ i += 1;
549
+ }
550
+ return i;
551
+ }
552
+ }
553
+ return input.length;
554
+ }
555
+
556
+ function looksLikeRegexStart(input: string, start: number): boolean {
557
+ let i = start - 1;
558
+ while (i >= 0 && /\s/.test(input[i]!)) {
559
+ i -= 1;
560
+ }
561
+ if (i < 0) {
562
+ return true;
563
+ }
564
+ const previous = input[i]!;
565
+ if ("([{=,:;!?&|+-*~^<>".includes(previous)) {
566
+ return true;
567
+ }
568
+ const word = readPreviousWord(input, i);
569
+ return /^(?:return|throw|case|delete|void|typeof|instanceof|in|of|yield|await)$/.test(
570
+ word,
571
+ );
572
+ }
573
+
574
+ function readPreviousWord(input: string, end: number): string {
575
+ let start = end;
576
+ while (start >= 0 && isIdentifierPart(input[start]!)) {
577
+ start -= 1;
578
+ }
579
+ return input.slice(start + 1, end + 1);
580
+ }
581
+
582
+ function readSimpleStringLiteral(
583
+ input: string,
584
+ quoteIndex: number,
585
+ ): { start: number; end: number; value: string } | null {
586
+ const quote = input[quoteIndex]!;
587
+ for (let i = quoteIndex + 1; i < input.length; i += 1) {
588
+ const current = input[i]!;
589
+ if (current === "\\") {
590
+ return null;
591
+ }
592
+ if (current === quote) {
593
+ return {
594
+ start: quoteIndex + 1,
595
+ end: i,
596
+ value: input.slice(quoteIndex + 1, i),
597
+ };
598
+ }
599
+ }
600
+ return null;
601
+ }
602
+
603
+ function skipWhitespace(input: string, start: number): number {
604
+ let i = start;
605
+ while (i < input.length && /\s/.test(input[i]!)) {
606
+ i += 1;
607
+ }
608
+ return i;
609
+ }
610
+
611
+ function isKeywordAt(input: string, start: number, keyword: string): boolean {
612
+ return (
613
+ input.startsWith(keyword, start) &&
614
+ !isIdentifierPart(input[start - 1] ?? "") &&
615
+ !isIdentifierPart(input[start + keyword.length] ?? "")
616
+ );
617
+ }
618
+
619
+ function isIdentifierPart(value: string): boolean {
620
+ return /[$\p{ID_Continue}]/u.test(value);
621
+ }
622
+
623
+ function isQuote(value: string | undefined): value is '"' | "'" {
624
+ return value === '"' || value === "'";
625
+ }