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