fix stack trace filename format for transpiled files#398
fix stack trace filename format for transpiled files#398kirkwaiblinger wants to merge 11 commits intounjs:mainfrom
Conversation
|
Hi dear @kirkwaiblinger Do you think you can minimize changes (changes to Also, I noticed typescript-eslint/typescript-eslint#11546 is already merged, fixing the original typescript-eslint issue. Do you think it is still worth upstreaming it? |
👋
It is, actually! This is what decides between a
What would be the value in making this configurable? The most (and only?) important consideration I could think of in deciding how to present the filename would be alignment with node, which uses backslashes on CJS paths (and in particular, a path such that
Yes. |
|
@pi0 I've updated the tests. If you try out various combinations of reverting the changes to eval.ts and running the tests with windows/non-windows, it should hopefully demonstrate pretty clearly the things I'm trying to point out! 🙂 |
📝 WalkthroughWalkthroughThis pull request fixes Windows path normalization in stack traces by enhancing module type detection (treating .mts/.cts extensions appropriately) and introducing formattedFileName logic that converts ESM module paths to file URLs while preserving CommonJS paths as resolved file paths. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/fixtures/filename/get-stack-trace.cjs`:
- Around line 3-10: Wrap the temporary modifications of Error.stackTraceLimit
and Error.prepareStackTrace and the call to Error.captureStackTrace(dummyObj,
getTopOfStackTrace) in a try/finally block so that Error.prepareStackTrace and
Error.stackTraceLimit are always restored even if an exception is thrown;
specifically save oldStackTraceLimit and oldPrepareStackTrace before changing
them, perform the capture in the try, and restore oldPrepareStackTrace and
oldStackTraceLimit in the finally block to guarantee global state is reset.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
test/__snapshots__/fixtures.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (7)
src/eval.tstest/fixtures/filename/cjs-module.cjstest/fixtures/filename/cts-module.ctstest/fixtures/filename/esm-module.mjstest/fixtures/filename/get-stack-trace.cjstest/fixtures/filename/index.tstest/fixtures/filename/mts-module.mts
| const oldStackTraceLimit = Error.stackTraceLimit; | ||
| Error.stackTraceLimit = 1; | ||
| const oldPrepareStackTrace = Error.prepareStackTrace; | ||
| Error.prepareStackTrace = (_, stack) => stack; | ||
| Error.captureStackTrace(dummyObj, getTopOfStackTrace); | ||
| const stack = dummyObj.stack; | ||
| Error.prepareStackTrace = oldPrepareStackTrace; | ||
| Error.stackTraceLimit = oldStackTraceLimit; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "get-stack-trace.cjs" -type fRepository: unjs/jiti
Length of output: 97
🏁 Script executed:
cat -n ./test/fixtures/filename/get-stack-trace.cjsRepository: unjs/jiti
Length of output: 675
🏁 Script executed:
# Check how this function is used in tests to understand the impact
rg "getStackTrace|get-stack-trace" test/ --type js --type cjs -B 2 -A 2Repository: unjs/jiti
Length of output: 80
🏁 Script executed:
# Check how this function is used in tests to understand the impact
rg "getStackTrace|get-stack-trace" test/ -B 2 -A 2Repository: unjs/jiti
Length of output: 1392
🏁 Script executed:
# Check if there are any tests that exercise this function or if there's error handling around it
rg "Error\.captureStackTrace|Error\.prepareStackTrace|Error\.stackTraceLimit" --type-list | head -20Repository: unjs/jiti
Length of output: 442
🏁 Script executed:
# Check if Error properties are used elsewhere
rg "Error\.captureStackTrace|Error\.prepareStackTrace|Error\.stackTraceLimit"Repository: unjs/jiti
Length of output: 813
Restore global Error hooks via finally block.
Error.prepareStackTrace and Error.stackTraceLimit are process-global properties. If an exception occurs during stack capture (lines 6–8), the restoration on lines 9–10 will not execute, leaving the Error object in a modified state and potentially affecting subsequent code execution.
Suggested fix
function getTopOfStackTrace() {
const dummyObj = {};
const oldStackTraceLimit = Error.stackTraceLimit;
- Error.stackTraceLimit = 1;
const oldPrepareStackTrace = Error.prepareStackTrace;
- Error.prepareStackTrace = (_, stack) => stack;
- Error.captureStackTrace(dummyObj, getTopOfStackTrace);
- const stack = dummyObj.stack;
- Error.prepareStackTrace = oldPrepareStackTrace;
- Error.stackTraceLimit = oldStackTraceLimit;
- return stack.at(-1);
+ try {
+ Error.stackTraceLimit = 1;
+ Error.prepareStackTrace = (_, stack) => stack;
+ Error.captureStackTrace(dummyObj, getTopOfStackTrace);
+ const stack = dummyObj.stack;
+ return stack.at(-1);
+ } finally {
+ Error.prepareStackTrace = oldPrepareStackTrace;
+ Error.stackTraceLimit = oldStackTraceLimit;
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@test/fixtures/filename/get-stack-trace.cjs` around lines 3 - 10, Wrap the
temporary modifications of Error.stackTraceLimit and Error.prepareStackTrace and
the call to Error.captureStackTrace(dummyObj, getTopOfStackTrace) in a
try/finally block so that Error.prepareStackTrace and Error.stackTraceLimit are
always restored even if an exception is thrown; specifically save
oldStackTraceLimit and oldPrepareStackTrace before changing them, perform the
capture in the try, and restore oldPrepareStackTrace and oldStackTraceLimit in
the finally block to guarantee global state is reset.
| ((ext === ".js" || ext === ".ts") && | ||
| readNearestPackageJSON(filename)?.type === "module"); | ||
| const isCommonJS = ext === ".cjs" || ext === ".cts"; | ||
| const needsTranspile = |
There was a problem hiding this comment.
btw this logic is obviously convoluted. It is equivalent to the following:
const needsTranspile = (() => {
// force takes precedence
if (evalOptions.forceTranspile != null) {
return evalOptions.forceTranspile;
}
// TS always needs transpile, regardless whether ESM or CJS
if (isTypescript) {
return true;
}
// CommonJS skips transpile
if (isCommonJS) {
return false;
}
// In async mode, we can skip transpiling native ESM as well
if (isESM && evalOptions.async) {
return false;
}
return isESM || ctx.isTransformRe.test(filename) || hasESMSyntax(source);
})();which I'd actually prefer to transcribe as
const needsTranspile = (() => {
// force takes precedence
if (evalOptions.forceTranspile != null) {
return evalOptions.forceTranspile;
}
// TS always needs transpile, regardless whether ESM or CJS
if (isTypescript) {
return true;
}
// CommonJS skips transpile
if (isCommonJS) {
return false;
}
if (isESM) {
// In async mode, we can skip transpiling native ESM as well
// otherwise it needs to be transformed
return evalOptions.async;
}
// I don't know what this does
if (ctx.isTransformRe.test(filename)) {
return true;
}
if (hasESMSyntax(source)) {
// Neither explicitly specified as ESM nor CJS, but contains
// ESM syntax. This should be transpiled.
// See https://nodejs.org/api/packages.html#syntax-detection
return true;
}
return false;
})();The only reason I didn't do one of these is that the prettier-ignore made it seem like a previous author wanted the code to presented exactly as it is rather than in a more explicit manner.
The important logic change here is that isTypescript is no longer disjoint from isESM and isCJS. Distinguishing isTypescript && isESM from isTypescript && isCJS is necessary to present stack traces correctly for transpiled TS. Thus, now that isTypescript && isCommonJS is possible, the isTypescript check needed to be moved before the isCommonJS, else the isCommonJS check would cause needsTranspile to short-circuit to false.
resolves #397
Summary by CodeRabbit
Release Notes