ttsc 0.14.1 → 0.15.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/driver/emit_plugin.go +131 -0
- package/driver/plugins.go +37 -0
- package/go.mod +0 -3
- package/go.sum +0 -4
- package/lib/compiler/internal/resolveEmittedJavaScript.js +25 -4
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.d.ts +4 -1
- package/lib/launcher/internal/prepareExecution.js +13 -17
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/registerRuntimeHooks.d.ts +1 -0
- package/lib/launcher/internal/registerRuntimeHooks.js +54 -0
- package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -0
- package/lib/launcher/internal/runTtsx.js +49 -370
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHookPreload.d.ts +1 -0
- package/lib/launcher/internal/runtimeHookPreload.js +16 -0
- package/lib/launcher/internal/runtimeHookPreload.js.map +1 -0
- package/lib/launcher/internal/runtimeHooks.d.ts +21 -0
- package/lib/launcher/internal/runtimeHooks.js +810 -0
- package/lib/launcher/internal/runtimeHooks.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.js +120 -16
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +15 -4
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +9 -9
- package/shim/ast/parent.go +31 -0
- package/shim/compiler/emitassemble.go +30 -0
- package/shim/compiler/emithook.go +22 -0
- package/shim/compiler/go.mod +9 -0
- package/shim/compiler/go.sum +22 -0
- package/shim/core/modulekind.go +10 -0
- package/shim/printer/emithost.go +24 -0
- package/shim/printer/go.mod +9 -0
- package/shim/printer/go.sum +22 -0
- package/shim/transformers/go.mod +14 -0
- package/shim/transformers/go.sum +20 -0
- package/shim/transformers/shim.go +15 -0
- package/src/compiler/internal/resolveEmittedJavaScript.ts +35 -4
- package/src/launcher/internal/prepareExecution.ts +18 -21
- package/src/launcher/internal/registerRuntimeHooks.ts +59 -0
- package/src/launcher/internal/runTtsx.ts +65 -437
- package/src/launcher/internal/runtimeHookPreload.ts +14 -0
- package/src/launcher/internal/runtimeHooks.ts +976 -0
- package/src/plugin/internal/buildSourcePlugin.ts +147 -16
- package/src/plugin/internal/loadProjectPlugins.ts +16 -4
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
|
|
5
6
|
import {
|
|
6
7
|
getBoolean,
|
|
7
8
|
getNumber,
|
|
@@ -69,7 +70,7 @@ function run(argv: readonly string[]): number {
|
|
|
69
70
|
project: parsed.project,
|
|
70
71
|
singleThreaded: parsed.singleThreaded,
|
|
71
72
|
});
|
|
72
|
-
return runPreparedEntry(parsed, prepared, cwd);
|
|
73
|
+
return runPreparedEntry(parsed, prepared, cwd, entry);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function formatError(error: unknown): string {
|
|
@@ -213,6 +214,20 @@ function printHelp(): void {
|
|
|
213
214
|
process.stdout.write("\n");
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Append a Node flag to an existing `NODE_OPTIONS` value (or start one). Used
|
|
219
|
+
* to propagate the runtime-hook installer into every child process the program
|
|
220
|
+
* spawns, so workers launched as `node worker.ts` inherit the source loader.
|
|
221
|
+
*/
|
|
222
|
+
function appendNodeOption(
|
|
223
|
+
existing: string | undefined,
|
|
224
|
+
option: string,
|
|
225
|
+
): string {
|
|
226
|
+
return existing && existing.trim().length !== 0
|
|
227
|
+
? `${existing} ${option}`
|
|
228
|
+
: option;
|
|
229
|
+
}
|
|
230
|
+
|
|
216
231
|
function resolvePreload(cwd: string, preload: string): string {
|
|
217
232
|
if (path.isAbsolute(preload) || isRelativeSpecifier(preload)) {
|
|
218
233
|
return path.resolve(cwd, preload);
|
|
@@ -231,33 +246,71 @@ function isRelativeSpecifier(specifier: string): boolean {
|
|
|
231
246
|
);
|
|
232
247
|
}
|
|
233
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Run the TypeScript entry from source in a child Node process whose runtime
|
|
251
|
+
* module hooks serve the already-built entry project and build raw `.ts`
|
|
252
|
+
* dependencies on demand.
|
|
253
|
+
*
|
|
254
|
+
* The child is `node [-r preload...] registerRuntimeHooks.js <source-entry>
|
|
255
|
+
* <argv...>` (the bootstrap, run as the main module — not `--import`, so a
|
|
256
|
+
* CommonJS `require` chain reaches the hooks). A runtime manifest pins the
|
|
257
|
+
* entry project's emit for the hooks; `TTSC_TSGO_BINARY` lets dependency builds
|
|
258
|
+
* find tsgo without re-resolving it from inside the hook.
|
|
259
|
+
*/
|
|
234
260
|
function runPreparedEntry(
|
|
235
261
|
parsed: Exclude<ReturnType<typeof parseCLI>, "help" | "version">,
|
|
236
262
|
execution: ReturnType<typeof prepareExecution>,
|
|
237
263
|
cwd: string,
|
|
264
|
+
sourceEntry: string,
|
|
238
265
|
): number {
|
|
239
266
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
267
|
+
const depCacheDir = path.join(execution.cleanupDir, "deps");
|
|
268
|
+
const manifestPath = path.join(
|
|
269
|
+
execution.cleanupDir,
|
|
270
|
+
"runtime-manifest.json",
|
|
271
|
+
);
|
|
272
|
+
fs.mkdirSync(execution.cleanupDir, { recursive: true });
|
|
273
|
+
fs.writeFileSync(
|
|
274
|
+
manifestPath,
|
|
275
|
+
JSON.stringify({
|
|
276
|
+
depCacheDir,
|
|
277
|
+
emitDir: execution.emitDir,
|
|
278
|
+
emittedFiles: execution.emittedFiles,
|
|
279
|
+
moduleOption: execution.moduleOption,
|
|
280
|
+
projectRoot: execution.projectRoot,
|
|
281
|
+
rootDir: execution.rootDir,
|
|
282
|
+
}),
|
|
283
|
+
"utf8",
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const tsgo = resolveTsgo({
|
|
287
|
+
binary: parsed.binary,
|
|
288
|
+
cwd: execution.projectRoot,
|
|
289
|
+
}).binary;
|
|
290
|
+
|
|
291
|
+
const bootstrap = path.join(__dirname, "registerRuntimeHooks.js");
|
|
249
292
|
const args = [
|
|
293
|
+
"--disable-warning=ExperimentalWarning",
|
|
250
294
|
...parsed.preload.flatMap((preload) => [
|
|
251
295
|
"-r",
|
|
252
296
|
resolvePreload(cwd, preload),
|
|
253
297
|
]),
|
|
254
|
-
|
|
298
|
+
bootstrap,
|
|
299
|
+
sourceEntry,
|
|
255
300
|
...parsed.passthrough,
|
|
256
301
|
];
|
|
257
302
|
const result = spawnSync(process.execPath, args, {
|
|
258
303
|
cwd,
|
|
304
|
+
env: {
|
|
305
|
+
...process.env,
|
|
306
|
+
NODE_OPTIONS: appendNodeOption(
|
|
307
|
+
process.env.NODE_OPTIONS,
|
|
308
|
+
`--require ${JSON.stringify(path.join(__dirname, "runtimeHookPreload.js"))}`,
|
|
309
|
+
),
|
|
310
|
+
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo,
|
|
311
|
+
TTSX_RUNTIME_MANIFEST: manifestPath,
|
|
312
|
+
},
|
|
259
313
|
stdio: "inherit",
|
|
260
|
-
env: process.env,
|
|
261
314
|
windowsHide: true,
|
|
262
315
|
});
|
|
263
316
|
if (result.error) {
|
|
@@ -277,428 +330,3 @@ function removeRuntimeOutput(directory: string): void {
|
|
|
277
330
|
// Best effort: cleanup must not replace the child process exit status.
|
|
278
331
|
}
|
|
279
332
|
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Walk every `.js`/`.mjs`/`.cjs` file in `root` and rewrite bare relative
|
|
283
|
-
* specifiers (e.g. `"./foo"`) to include the resolved file extension so Node
|
|
284
|
-
* can load them with `--input-type=module`. Files that did not change are not
|
|
285
|
-
* written back.
|
|
286
|
-
*/
|
|
287
|
-
function rewriteEsmSpecifiers(root: string): void {
|
|
288
|
-
const stack = [root];
|
|
289
|
-
while (stack.length !== 0) {
|
|
290
|
-
const current = stack.pop()!;
|
|
291
|
-
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
292
|
-
const next = path.join(current, entry.name);
|
|
293
|
-
if (entry.isDirectory()) {
|
|
294
|
-
stack.push(next);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (!entry.isFile() || !isJavaScriptOutput(next)) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
const before = fs.readFileSync(next, "utf8");
|
|
301
|
-
const after = rewriteEsmSpecifiersInText(next, before);
|
|
302
|
-
if (after !== before) {
|
|
303
|
-
fs.writeFileSync(next, after, "utf8");
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function rewriteEsmSpecifiersInText(fromFile: string, input: string): string {
|
|
310
|
-
const replacements: Array<{ start: number; end: number; text: string }> = [];
|
|
311
|
-
scanEsmSpecifiers(fromFile, input, 0, input.length, replacements);
|
|
312
|
-
if (replacements.length === 0) {
|
|
313
|
-
return input;
|
|
314
|
-
}
|
|
315
|
-
let output = "";
|
|
316
|
-
let last = 0;
|
|
317
|
-
for (const replacement of replacements) {
|
|
318
|
-
output += input.slice(last, replacement.start);
|
|
319
|
-
output += replacement.text;
|
|
320
|
-
last = replacement.end;
|
|
321
|
-
}
|
|
322
|
-
return output + input.slice(last);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function scanEsmSpecifiers(
|
|
326
|
-
fromFile: string,
|
|
327
|
-
input: string,
|
|
328
|
-
start: number,
|
|
329
|
-
end: number,
|
|
330
|
-
replacements: Array<{ start: number; end: number; text: string }>,
|
|
331
|
-
): void {
|
|
332
|
-
let i = start;
|
|
333
|
-
while (i < end) {
|
|
334
|
-
if (input[i] === "`") {
|
|
335
|
-
i = scanTemplateExpressions(fromFile, input, i, replacements);
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
const skipped = skipNonCode(input, i);
|
|
339
|
-
if (skipped !== i) {
|
|
340
|
-
i = skipped;
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
if (isKeywordAt(input, i, "import")) {
|
|
344
|
-
const next = skipWhitespace(input, i + "import".length);
|
|
345
|
-
if (input[next] === "(") {
|
|
346
|
-
rewriteDynamicImportSpecifier(fromFile, input, next, replacements);
|
|
347
|
-
} else if (isQuote(input[next])) {
|
|
348
|
-
rewriteStringSpecifier(fromFile, input, next, replacements);
|
|
349
|
-
} else {
|
|
350
|
-
rewriteFromSpecifier(fromFile, input, next, replacements);
|
|
351
|
-
}
|
|
352
|
-
i += "import".length;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (isKeywordAt(input, i, "export")) {
|
|
356
|
-
rewriteFromSpecifier(fromFile, input, i + "export".length, replacements);
|
|
357
|
-
i += "export".length;
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
i += 1;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function rewriteDynamicImportSpecifier(
|
|
365
|
-
fromFile: string,
|
|
366
|
-
input: string,
|
|
367
|
-
openParen: number,
|
|
368
|
-
replacements: Array<{ start: number; end: number; text: string }>,
|
|
369
|
-
): void {
|
|
370
|
-
const specifier = skipWhitespace(input, openParen + 1);
|
|
371
|
-
if (isQuote(input[specifier])) {
|
|
372
|
-
rewriteStringSpecifier(fromFile, input, specifier, replacements);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function rewriteFromSpecifier(
|
|
377
|
-
fromFile: string,
|
|
378
|
-
input: string,
|
|
379
|
-
start: number,
|
|
380
|
-
replacements: Array<{ start: number; end: number; text: string }>,
|
|
381
|
-
): void {
|
|
382
|
-
let i = start;
|
|
383
|
-
let depth = 0;
|
|
384
|
-
while (i < input.length) {
|
|
385
|
-
const skipped = skipNonCode(input, i);
|
|
386
|
-
if (skipped !== i) {
|
|
387
|
-
i = skipped;
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
const current = input[i]!;
|
|
391
|
-
if (depth === 0 && current === ";") {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (current === "{" || current === "[" || current === "(") {
|
|
395
|
-
depth += 1;
|
|
396
|
-
i += 1;
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
if (current === "}" || current === "]" || current === ")") {
|
|
400
|
-
depth = Math.max(0, depth - 1);
|
|
401
|
-
i += 1;
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
if (depth === 0 && isKeywordAt(input, i, "from")) {
|
|
405
|
-
const specifier = skipWhitespace(input, i + "from".length);
|
|
406
|
-
if (isQuote(input[specifier])) {
|
|
407
|
-
rewriteStringSpecifier(fromFile, input, specifier, replacements);
|
|
408
|
-
}
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
i += 1;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function rewriteStringSpecifier(
|
|
416
|
-
fromFile: string,
|
|
417
|
-
input: string,
|
|
418
|
-
quoteIndex: number,
|
|
419
|
-
replacements: Array<{ start: number; end: number; text: string }>,
|
|
420
|
-
): void {
|
|
421
|
-
const literal = readSimpleStringLiteral(input, quoteIndex);
|
|
422
|
-
if (literal === null) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
const next = withResolvableExtension(fromFile, literal.value);
|
|
426
|
-
if (next === literal.value) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
replacements.push({
|
|
430
|
-
start: literal.start,
|
|
431
|
-
end: literal.end,
|
|
432
|
-
text: next,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function scanTemplateExpressions(
|
|
437
|
-
fromFile: string,
|
|
438
|
-
input: string,
|
|
439
|
-
start: number,
|
|
440
|
-
replacements: Array<{ start: number; end: number; text: string }>,
|
|
441
|
-
): number {
|
|
442
|
-
for (let i = start + 1; i < input.length; i += 1) {
|
|
443
|
-
const current = input[i]!;
|
|
444
|
-
if (current === "\\") {
|
|
445
|
-
i += 1;
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
if (current === "`") {
|
|
449
|
-
return i + 1;
|
|
450
|
-
}
|
|
451
|
-
if (current === "$" && input[i + 1] === "{") {
|
|
452
|
-
const expressionStart = i + 2;
|
|
453
|
-
const expressionEnd = findTemplateExpressionEnd(input, expressionStart);
|
|
454
|
-
if (expressionEnd === null) {
|
|
455
|
-
return input.length;
|
|
456
|
-
}
|
|
457
|
-
scanEsmSpecifiers(
|
|
458
|
-
fromFile,
|
|
459
|
-
input,
|
|
460
|
-
expressionStart,
|
|
461
|
-
expressionEnd,
|
|
462
|
-
replacements,
|
|
463
|
-
);
|
|
464
|
-
i = expressionEnd;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return input.length;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function findTemplateExpressionEnd(
|
|
471
|
-
input: string,
|
|
472
|
-
expressionStart: number,
|
|
473
|
-
): number | null {
|
|
474
|
-
let depth = 1;
|
|
475
|
-
for (let i = expressionStart; i < input.length; i += 1) {
|
|
476
|
-
const skipped = skipNonCode(input, i);
|
|
477
|
-
if (skipped !== i) {
|
|
478
|
-
i = skipped - 1;
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
const current = input[i]!;
|
|
482
|
-
if (current === "{") {
|
|
483
|
-
depth += 1;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
if (current === "}") {
|
|
487
|
-
depth -= 1;
|
|
488
|
-
if (depth === 0) {
|
|
489
|
-
return i;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Append or fix a file extension on a relative specifier so that Node's ESM
|
|
498
|
-
* loader can resolve it. The resolution order mirrors Node's own algorithm:
|
|
499
|
-
* exact file match → directory index → `.js` fallback. Non-relative specifiers
|
|
500
|
-
* and already-extensioned paths are returned unchanged.
|
|
501
|
-
*/
|
|
502
|
-
function withResolvableExtension(fromFile: string, specifier: string): string {
|
|
503
|
-
if (!specifier.startsWith(".")) {
|
|
504
|
-
// Bare specifiers (packages, builtins) need no rewriting.
|
|
505
|
-
return specifier;
|
|
506
|
-
}
|
|
507
|
-
const [pathname, suffix = ""] = splitSpecifierSuffix(specifier);
|
|
508
|
-
if (/\.(?:[cm]?js|json|node)$/i.test(pathname)) {
|
|
509
|
-
// Already has a concrete extension the loader understands.
|
|
510
|
-
return specifier;
|
|
511
|
-
}
|
|
512
|
-
const fromDir = path.dirname(fromFile);
|
|
513
|
-
// 1. Try the specifier as a file path with each JS extension.
|
|
514
|
-
for (const extension of [".js", ".mjs", ".cjs"]) {
|
|
515
|
-
if (fs.existsSync(path.resolve(fromDir, pathname + extension))) {
|
|
516
|
-
return pathname + extension + suffix;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// 2. Try interpreting it as a directory with an index file.
|
|
520
|
-
const directory = pathname.replace(/\/+$/, "");
|
|
521
|
-
for (const extension of [".js", ".mjs", ".cjs"]) {
|
|
522
|
-
if (fs.existsSync(path.resolve(fromDir, directory, "index" + extension))) {
|
|
523
|
-
return `${directory}/index${extension}${suffix}`;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
// 3. Last resort: append `.js` and let Node surface the error if it's wrong.
|
|
527
|
-
return `${pathname}.js${suffix}`;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function splitSpecifierSuffix(specifier: string): [string, string?] {
|
|
531
|
-
const index = specifier.search(/[?#]/);
|
|
532
|
-
if (index === -1) {
|
|
533
|
-
return [specifier];
|
|
534
|
-
}
|
|
535
|
-
return [specifier.slice(0, index), specifier.slice(index)];
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function isJavaScriptOutput(filename: string): boolean {
|
|
539
|
-
return /\.(?:[cm]?js)$/i.test(filename);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function skipNonCode(input: string, start: number): number {
|
|
543
|
-
const current = input[start];
|
|
544
|
-
const next = input[start + 1];
|
|
545
|
-
if (current === '"' || current === "'") {
|
|
546
|
-
return skipString(input, start);
|
|
547
|
-
}
|
|
548
|
-
if (current === "`") {
|
|
549
|
-
return skipTemplate(input, start);
|
|
550
|
-
}
|
|
551
|
-
if (current === "/" && next === "/") {
|
|
552
|
-
return skipLineComment(input, start);
|
|
553
|
-
}
|
|
554
|
-
if (current === "/" && next === "*") {
|
|
555
|
-
return skipBlockComment(input, start);
|
|
556
|
-
}
|
|
557
|
-
if (current === "/" && looksLikeRegexStart(input, start)) {
|
|
558
|
-
return skipRegex(input, start);
|
|
559
|
-
}
|
|
560
|
-
return start;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function skipString(input: string, start: number): number {
|
|
564
|
-
const quote = input[start]!;
|
|
565
|
-
for (let i = start + 1; i < input.length; i += 1) {
|
|
566
|
-
const current = input[i]!;
|
|
567
|
-
if (current === "\\") {
|
|
568
|
-
i += 1;
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
if (current === quote) {
|
|
572
|
-
return i + 1;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
return input.length;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function skipTemplate(input: string, start: number): number {
|
|
579
|
-
for (let i = start + 1; i < input.length; i += 1) {
|
|
580
|
-
const current = input[i]!;
|
|
581
|
-
if (current === "\\") {
|
|
582
|
-
i += 1;
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
if (current === "`") {
|
|
586
|
-
return i + 1;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return input.length;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function skipLineComment(input: string, start: number): number {
|
|
593
|
-
const end = input.indexOf("\n", start + 2);
|
|
594
|
-
return end === -1 ? input.length : end + 1;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function skipBlockComment(input: string, start: number): number {
|
|
598
|
-
const end = input.indexOf("*/", start + 2);
|
|
599
|
-
return end === -1 ? input.length : end + 2;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function skipRegex(input: string, start: number): number {
|
|
603
|
-
let inClass = false;
|
|
604
|
-
for (let i = start + 1; i < input.length; i += 1) {
|
|
605
|
-
const current = input[i]!;
|
|
606
|
-
if (current === "\\") {
|
|
607
|
-
i += 1;
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
if (current === "[") {
|
|
611
|
-
inClass = true;
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
if (current === "]") {
|
|
615
|
-
inClass = false;
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
if (current === "/" && !inClass) {
|
|
619
|
-
i += 1;
|
|
620
|
-
while (/[A-Za-z]/.test(input[i] ?? "")) {
|
|
621
|
-
i += 1;
|
|
622
|
-
}
|
|
623
|
-
return i;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return input.length;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Determine whether the `/` at `start` opens a regex literal rather than a
|
|
631
|
-
* division operator. This is a conservative approximation: check the preceding
|
|
632
|
-
* non-whitespace token — if it is an operator character or a keyword that must
|
|
633
|
-
* be followed by an expression, we assume regex.
|
|
634
|
-
*/
|
|
635
|
-
function looksLikeRegexStart(input: string, start: number): boolean {
|
|
636
|
-
let i = start - 1;
|
|
637
|
-
while (i >= 0 && /\s/.test(input[i]!)) {
|
|
638
|
-
i -= 1;
|
|
639
|
-
}
|
|
640
|
-
if (i < 0) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
const previous = input[i]!;
|
|
644
|
-
if ("([{=,:;!?&|+-*~^<>".includes(previous)) {
|
|
645
|
-
return true;
|
|
646
|
-
}
|
|
647
|
-
const word = readPreviousWord(input, i);
|
|
648
|
-
return /^(?:return|throw|case|delete|void|typeof|instanceof|in|of|yield|await)$/.test(
|
|
649
|
-
word,
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function readPreviousWord(input: string, end: number): string {
|
|
654
|
-
let start = end;
|
|
655
|
-
while (start >= 0 && isIdentifierPart(input[start]!)) {
|
|
656
|
-
start -= 1;
|
|
657
|
-
}
|
|
658
|
-
return input.slice(start + 1, end + 1);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function readSimpleStringLiteral(
|
|
662
|
-
input: string,
|
|
663
|
-
quoteIndex: number,
|
|
664
|
-
): { start: number; end: number; value: string } | null {
|
|
665
|
-
const quote = input[quoteIndex]!;
|
|
666
|
-
for (let i = quoteIndex + 1; i < input.length; i += 1) {
|
|
667
|
-
const current = input[i]!;
|
|
668
|
-
if (current === "\\") {
|
|
669
|
-
return null;
|
|
670
|
-
}
|
|
671
|
-
if (current === quote) {
|
|
672
|
-
return {
|
|
673
|
-
start: quoteIndex + 1,
|
|
674
|
-
end: i,
|
|
675
|
-
value: input.slice(quoteIndex + 1, i),
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function skipWhitespace(input: string, start: number): number {
|
|
683
|
-
let i = start;
|
|
684
|
-
while (i < input.length && /\s/.test(input[i]!)) {
|
|
685
|
-
i += 1;
|
|
686
|
-
}
|
|
687
|
-
return i;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function isKeywordAt(input: string, start: number, keyword: string): boolean {
|
|
691
|
-
return (
|
|
692
|
-
input.startsWith(keyword, start) &&
|
|
693
|
-
!isIdentifierPart(input[start - 1] ?? "") &&
|
|
694
|
-
!isIdentifierPart(input[start + keyword.length] ?? "")
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function isIdentifierPart(value: string): boolean {
|
|
699
|
-
return /[$\p{ID_Continue}]/u.test(value);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function isQuote(value: string | undefined): value is '"' | "'" {
|
|
703
|
-
return value === '"' || value === "'";
|
|
704
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { installRuntimeHooks } from "./runtimeHooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `--require` target that only installs the runtime module hooks. `ttsx` puts
|
|
5
|
+
* it on `NODE_OPTIONS` for the child process so every descendant the program
|
|
6
|
+
* spawns (e.g. a worker started with `node worker.ts`) inherits the same
|
|
7
|
+
* source-loading hooks, the way `ts-node` propagates through `--require
|
|
8
|
+
* ts-node/register`. The preload itself is CommonJS and only registers the
|
|
9
|
+
* hooks; ESM support comes from the `registerHooks` they install, not from how
|
|
10
|
+
* the preload is loaded. The entry process additionally goes through the
|
|
11
|
+
* bootstrap, which loads the entry itself; here we only register
|
|
12
|
+
* (idempotently).
|
|
13
|
+
*/
|
|
14
|
+
installRuntimeHooks();
|