-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature]: Print error.cause #11935
Comments
From what I can see in the codebase this would likely involve changes to (or additons to) the export type AssertionResult = {
failureDetails: Array<unknown>;
failureMessages: Array<string>;
};
The most powerful way to expose the functionality would be to simply change |
I like this idea! We currently print a codeframe - I assume we wouldn't want a frame for any I'm happy to take a PR implementing this. 🙂
Yeah, that sounds reasonable to me |
Yeah I was considering that. It might become a lot of extra space, particularly as the cause feature starts to be used more heavily. In the use case I have right now the least specific cause is the one that really benefits from a code frame, but I'm not sure that will always be the case. I'll leave it at one frame to start out. |
I'm going to hard fork the |
OK that was a lot more work than I anticipated (and I'm still not quite done), but I've accomplished what I set out to so I'm pretty happy about that. This implementation already beats the pants off the old one in many regards. My work is up in tapjs/stack-utils#62. I'm sure it's still got some bugs, but I think I'm to the point where I could test integrating it with jest. |
Correction, I've completed my hard fork and further development will be done at https://github.com/conartist6/stack-tools and released under the name |
Looking forward to seeing how it shakes out! Looks promising 🙂 |
OK I've now got a proper AST for errors and printing based on traversal with a visitor. This lays the groundwork for me to offer base printing behavior while ensuring it is still possible for the end user to override any part of the printing as they see fit. This is great as it means the project can offer several extensible printers, for example a colored terminal printer with chalk, an html printer, and even (eventually) a React component hierarchy which could support some very rich functionality indeed. I have 100% test coverage, but my plan is to test the whole thing out by finishing the integration with jest and hopefully getting some review on an integration PR here once I have that ready. |
If you're after encouragement I'm super excited about this direction 😃 But I do not have any comment about the implementation up until any integration with Jest starts to materialize. I have faith in this improving errors for our users though, which is essentially one of our strongest value propositions |
I'll always take encouragement! I should have the integration ready pretty soon. |
Here's what I've got. Don't pay too much attention to the specific formatting, as I was mostly just testing whether I could apply formatting. Here is the content of const { relative } = require('path');
const { ChalkPrintVisitorMixin } = require('@stack-tools/chalk-tools');
const {
parseErrors,
cleanErrors,
PrintVisitor: V8PrintVisitor,
} = require('@stack-tools/node-tools');
const PrintVisitor = class extends ChalkPrintVisitorMixin(V8PrintVisitor) {
PathLocator(locator) {
if (locator.path.startsWith('/')) {
locator.path = relative(process.cwd(), locator.path);
}
return super.PathLocator(locator);
}
};
function doStuff() {
throw new Error('Uh oh');
}
try {
doStuff();
} catch (e) {
process.stdout.write(`${PrintVisitor.visit(cleanErrors(parseErrors(e)))}\n\n`);
} |
Actually I'm not even sure I really want a chalk package, since it's not clear what formatting I would put in there. |
OK one more try with everything: ...which is generated by: const { relative } = require('path');
const chalk = require('chalk');
const {
parseErrors,
cleanErrors,
PrintVisitor: V8PrintVisitor,
} = require('@stack-tools/node-tools');
class PrintVisitor extends V8PrintVisitor {
ErrorChain(errors) {
return chalk.magenta.bold`${super.ErrorChain(errors)}`;
}
Error(error) {
return chalk.magenta.bold`${super.Error(error)}`;
}
ErrorMessage(message) {
return chalk.white`${super.ErrorMessage(message)}`;
}
Frame(frame) {
return chalk.reset.magenta`${super.Frame(frame).replace(/^ {2}/, '')}`;
}
Call(call) {
return chalk.white`${super.Call(call)}`;
}
Site(site) {
return chalk.white.underline`${super.Site(site)}`;
}
PathLocator(locator) {
const { cwd } = this.options;
const { path } = locator;
return super.PathLocator({
...locator,
path: path.startsWith('/') ? relative(cwd, path) : path,
});
}
}
function doLowLevelStuff() {
throw new Error('fubar');
}
function doStuff() {
try {
doLowLevelStuff();
} catch (cause) {
throw Object.assign(new Error('Uh oh'), { cause });
}
}
try {
doStuff();
} catch (e) {
process.stdout.write(
`${PrintVisitor.visit(cleanErrors(parseErrors(e)), { cwd: process.cwd() })}\n\n`,
);
} |
Sorry I had to make a bunch of edits as I noticed little things that were wrong. Edit: Oooh I just cannot stop myself making more and more edits, sorry. It's like eating a chip and then the next one and the next one. But if this is something that might get copy pasted around, I may as well make sure it's good code. |
I've got an integration and it type checks now. Moving on to runtime debugging. |
Oh no oh no I didn't consider that jest uses itself to test itself so because I broke the package that formats errors in tests I can't see the errors from the tests I broke O_O |
Oooh I can actually |
I can also rebuild a single package const packages = [path.resolve(__dirname, '../packages/pgk-name')]; |
I've got at least a couple tests passing on the integration now. Current puzzle: the tests assert that frames from Jasmine are filtered out and not displayed, and yet I can't find the code that would be doing it if indeed those tests are presently passing. Here's the relevant test stack trace:
And the snapshot indicates that the correct set of frames after filtering is:
...but why? How? I really don't know. I don't see any defining pattern to those three frames at all. All the frames are basically the same format. Some frames from OH! Ok, brainwave. I am remembering that the snapshot for this test was all kinds of a mess, and I know that I must be cognizant that the old code may not have worked the way I assumed it did, and I think this is one of those moments. I couldn't understand why the snapshot had a blank line in the middle of the stack trace, but that's crucial. The "parsing" code was cutting off the first line assuming it to be the message (it is not), and then it was treating the remaining lines as the stack trace. One of them legitimately doesn't match the jest internals ignore pattern, and the other one is saved because the code always saves the top frame, even if it's from Jest. I just couldn't see that this was what was happening, because I thought the other frame was the top one. So the corrected expected trace is:
|
All stack filtering is in that file. Feeding new (full) traces through the tests in https://github.com/facebook/jest/blob/2a5e51590ed77f5db2ab0beec8020ce116f06e67/packages/jest-message-util/src/__tests__/messages.test.ts is probably a good idea, that way you can see that it spits out the correct thing quicker that the integration tests can |
I recommend running |
Yep those are the tests I've been running since that's the code I've been changing. It's pretty cool how integrating with jest has allowed me to catch many bugs and flaws in my design and API that would have been difficult to see otherwise. I think I've nearly got them all passing too... |
The tests show two contradictory behaviors likely due to bugs I've resolved in the logic. I need to know which is correct. Should There's also a comment that says, |
The documentation for |
OK all the jest-message-util tests pass now that I've cleared up that ambiguity. Initial state of the broader test suite at this point is:
I'm not too worried though -- it looks like most of the failures are due to easily fixable problems. |
The broader test suite seems to indicate that |
The console e2e contains a I'm not gonna say that makes sense to me, but if that's the behavior I have to replicate that's what I'll make it do. |
|
I'm in the home stretch! Most of those remaining failures are caused by one of the 3 remaining anomalies that I still need to debug.
|
Oh and there are a few places left in the implementation where I don't yet support errors with causes, which I absolutely do need to fix because the old code did "support" them at least in that it didn't blow up if there's a message in the middle of your stack trace, which my code will. This will require a little thought as to what the correct formatting and behavior should be. |
The PR will go up today though, as most of the work I've done so far has just been to capture the existing logic with better code. The review for that code will be an excellent place to discuss what changes to the existing logic might be desired. |
Oh and I probably broke windows |
Draft PR is up! Let the feedback roll in! I'll be doing my own review there as well. |
Looks like this issue has stalled - but it would be super valuable to ensure error chains are being correctly reported |
This was closed in #13606, unless there's some additional request here I'm missing. |
Maybe this one has to be merged before? #13965 |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
🚀 Feature Proposal
The proposal for error.cause is currently stage 3. This proposal is to support jest printing chains of
cause
properties if they are present on errors.Motivation
Nested errors can be critical to debugging.
JSON.parse
might throw a syntax error when reading a file. But if the filename isn't hardcoded in the stack from that calledJSON.parse
, a developer debugging would have no idea which particular file could not be parsed. The solution is to support chains of causally related errors: as errors bubble up through layers of code they can be caught and rethrown as a cause, allowing additional information to be captured.Example
Imagine the application contains this code
If the application throws during testing complete information is available about the problem. The code throws an
error
with acause
property defined, per the spec. The cause contains critical information about what the error is, but if jest does not opt in to printing it that information is only present in interactive environments for those who know to look for it. Currently jest would printIf this feature is implemented jest would print:
Pitch
Jest has the ability to bring the improved debuggability that this proposal creates to a large audience. It is relevant and stable enough for inclusion.
The text was updated successfully, but these errors were encountered: