UNPKG

ttsc

Version:

General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.

162 lines 6.62 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveEmittedJavaScript = resolveEmittedJavaScript; const node_fs_1 = __importDefault(require("node:fs")); const node_path_1 = __importDefault(require("node:path")); const paths_1 = require("./paths"); /** * Locate the JavaScript file emitted for a TypeScript source file. * * Resolution strategy: * * 1. Try to derive the exact output path by mirroring the source's relative * position inside `projectRoot` into `outDir`, applying the correct JS * extension (`.js` / `.jsx` / `.mjs` / `.cjs`). Use this path if it exists * on disk. * 2. Fall back to scoring each candidate in `emittedFiles` (or a recursive * directory scan of `outDir`) by the number of trailing path-stem segments * shared with the source file name, and pick the highest-scoring existing * file. * * Returns `null` when no matching output file is found on disk. */ function resolveEmittedJavaScript(options) { const exact = resolveExactEmittedFiles(options.outDir, options.projectRoot, options.sourceFile); const emitted = new Set(options.emittedFiles?.map((file) => emittedPathKey(file)) ?? []); for (const candidate of exact) { if (emitted.has(emittedPathKey(candidate)) && node_fs_1.default.existsSync(candidate)) { return candidate; } } for (const candidate of exact) { if (node_fs_1.default.existsSync(candidate)) { return candidate; } } // Score the pre-computed emit list first (cheap). When it yields nothing — // because the list is incomplete (a native transform host such as typia emits // without printing the `--listEmittedFiles` lines) or because the emit landed // at a path the exact mirror did not predict (tsgo shifts every output path // when the program pulls a raw-`.ts` dependency that sits outside `rootDir`, // so it strips the common source root rather than `rootDir`) — fall back to a // full recursive scan of `outDir`. Trailing-stem scoring still pins the right // file regardless of how deep the shifted prefix is. const primary = bestStemMatch(options.emittedFiles ?? listJavaScriptFiles(options.outDir), options.sourceFile); if (primary !== null && node_fs_1.default.existsSync(primary)) { return primary; } if (options.emittedFiles !== undefined) { const fromDir = bestStemMatch(listJavaScriptFiles(options.outDir), options.sourceFile); if (fromDir !== null && node_fs_1.default.existsSync(fromDir)) { return fromDir; } } return null; } /** Highest trailing-stem-scoring JavaScript output among `files`, or `null`. */ function bestStemMatch(files, sourceFile) { let best = null; let bestScore = 0; for (const file of files) { if (!isJavaScriptOutput(file)) continue; const score = sharedSourceStemSegments(file, sourceFile); if (score > bestScore) { best = file; bestScore = score; } } return best; } /** * Derive the exact output path for `sourceFile` by mirroring its position * relative to `projectRoot` into `outDir`. Returns no candidates when the * source is not inside the project root or when the path cannot be determined. */ function resolveExactEmittedFiles(outDir, projectRoot, sourceFile) { const relative = node_path_1.default.relative(projectRoot, sourceFile); if (relative === "" || (0, paths_1.isOutsideRelativePath)(relative)) { return []; } const stem = relative.slice(0, relative.length - node_path_1.default.extname(relative).length); return emittedJavaScriptExtensions(sourceFile).map((extension) => node_path_1.default.resolve(outDir, stem + extension)); } /** * Recursively enumerate every JavaScript output file under `root`. Uses an * explicit stack instead of recursion to avoid call-stack overflow on deep * directory trees. Non-existent roots are silently skipped. */ function listJavaScriptFiles(root) { const out = []; const stack = [root]; while (stack.length !== 0) { const current = stack.pop(); if (!node_fs_1.default.existsSync(current)) continue; for (const entry of node_fs_1.default.readdirSync(current, { withFileTypes: true })) { const next = node_path_1.default.join(current, entry.name); if (entry.isDirectory()) { stack.push(next); } else if (entry.isFile() && isJavaScriptOutput(next)) { out.push(node_path_1.default.resolve(next)); } } } return out; } /** * Count the number of consecutive trailing path-stem segments that `outPath` * and `srcPath` share when both are stripped of their extensions and normalised * to forward slashes. * * Example: `dist/lib/foo.js` vs `src/lib/foo.ts` → 2 (`lib`, `foo`). */ function sharedSourceStemSegments(outPath, srcPath) { const stripExtAndSplit = (location) => { const normalized = location.replace(/\\/g, "/"); return normalized .slice(0, normalized.length - node_path_1.default.extname(normalized).length) .split("/"); }; const a = stripExtAndSplit(outPath); const b = stripExtAndSplit(srcPath); const count = Math.min(a.length, b.length); let shared = 0; for (let i = 1; i <= count; i += 1) { if (a[a.length - i] !== b[b.length - i]) break; shared += 1; } return shared; } /** * Map a source extension to every JavaScript output counterpart tsgo can use. * JSX preserve mode writes `.tsx`/`.jsx` inputs as `.jsx`; all other JSX modes * write `.js`. */ function emittedJavaScriptExtensions(filename) { switch (node_path_1.default.extname(filename).toLowerCase()) { case ".mts": return [".mjs"]; case ".cts": return [".cjs"]; case ".tsx": case ".jsx": return [".js", ".jsx"]; default: return [".js"]; } } /** Return true when `filename` has a JavaScript output extension. */ function isJavaScriptOutput(filename) { return /\.(?:[cm]?js|jsx)$/i.test(filename); } function emittedPathKey(filename) { const resolved = node_path_1.default.resolve(filename); return process.platform === "win32" ? resolved.toLowerCase() : resolved; } //# sourceMappingURL=resolveEmittedJavaScript.js.map