-
-
Notifications
You must be signed in to change notification settings - Fork 701
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
Remove proxy frames from stack traces and improve docs/tests #884
Changes from all commits
5a1e676
78f1808
96f958c
8fa4f78
b88e536
bf71196
82ca613
0c09836
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,7 +59,7 @@ var call = Function.prototype.call, | |
* @api public | ||
*/ | ||
|
||
module.exports = function (ctx, name, method, chainingBehavior) { | ||
module.exports = function addChainableMethod(ctx, name, method, chainingBehavior) { | ||
if (typeof chainingBehavior !== 'function') { | ||
chainingBehavior = function () { }; | ||
} | ||
|
@@ -76,13 +76,18 @@ module.exports = function (ctx, name, method, chainingBehavior) { | |
ctx.__methods[name] = chainableBehavior; | ||
|
||
Object.defineProperty(ctx, name, | ||
{ get: function () { | ||
{ get: function chainableMethodGetter() { | ||
chainableBehavior.chainingBehavior.call(this); | ||
|
||
var assert = function assert() { | ||
var old_ssfi = flag(this, 'ssfi'); | ||
if (old_ssfi) | ||
flag(this, 'ssfi', assert); | ||
var chainableMethodWrapper = function () { | ||
// Use this chainable method wrapper as the starting point for | ||
// removing implementation frames from the stack trace of a failed | ||
// assertion. Note that this is the correct starting point even if | ||
// this assertion has been overwritten since overwriting a chainable | ||
// method merely replaces the saved methods in `ctx.__methods` instead | ||
// of completely replacing the overwritten assertion. | ||
flag(this, 'ssfi', chainableMethodWrapper); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just out of curiosity, I've always wondered what does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to this comment, it's "start stack function indicator". Also mentioned on http://chaijs.com/guide/plugins/. I don't feel strongly one way or another about renaming it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, since it's pretty specific I think there's nothing we can do about it. Maybe a comment indicating what it means somewhere would also be nice, especially if we added a link to that comment, which was really useful for me when understanding how the whole stack trace manipulation works. |
||
|
||
var result = chainableBehavior.method.apply(this, arguments); | ||
|
||
if (result !== undefined) { | ||
|
@@ -94,12 +99,12 @@ module.exports = function (ctx, name, method, chainingBehavior) { | |
return newAssertion; | ||
}; | ||
|
||
addLengthGuard(assert, name, true); | ||
addLengthGuard(chainableMethodWrapper, name, true); | ||
|
||
// Use `__proto__` if available | ||
if (hasProtoSupport) { | ||
// Inherit all properties from the object by replacing the `Function` prototype | ||
var prototype = assert.__proto__ = Object.create(this); | ||
var prototype = chainableMethodWrapper.__proto__ = Object.create(this); | ||
// Restore the `call` and `apply` methods from `Function` | ||
prototype.call = call; | ||
prototype.apply = apply; | ||
|
@@ -110,13 +115,13 @@ module.exports = function (ctx, name, method, chainingBehavior) { | |
asserterNames.forEach(function (asserterName) { | ||
if (!excludeNames.test(asserterName)) { | ||
var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); | ||
Object.defineProperty(assert, asserterName, pd); | ||
Object.defineProperty(chainableMethodWrapper, asserterName, pd); | ||
} | ||
}); | ||
} | ||
|
||
transferFlags(this, assert); | ||
return proxify(assert); | ||
transferFlags(this, chainableMethodWrapper); | ||
return proxify(chainableMethodWrapper); | ||
} | ||
, configurable: true | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
var config = require('../config'); | ||
|
||
/*! | ||
* Chai - isProxyEnabled helper | ||
* Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com> | ||
* MIT Licensed | ||
*/ | ||
|
||
/** | ||
* # isProxyEnabled() | ||
* | ||
* Helper function to check if Chai's proxy protection feature is enabled. If | ||
* proxies are unsupported or disabled via the user's Chai config, then return | ||
* false. Otherwise, return true. | ||
* | ||
* @namespace Utils | ||
* @name isProxyEnabled | ||
*/ | ||
|
||
module.exports = function isProxyEnabled() { | ||
return config.useProxy && | ||
typeof Proxy !== 'undefined' && | ||
typeof Reflect !== 'undefined'; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,7 @@ var transferFlags = require('./transferFlags'); | |
* @api public | ||
*/ | ||
|
||
module.exports = function (ctx, name, method) { | ||
module.exports = function overwriteMethod(ctx, name, method) { | ||
var _method = ctx[name] | ||
, _super = function () { | ||
throw new Error(name + ' is not a function'); | ||
|
@@ -53,12 +53,24 @@ module.exports = function (ctx, name, method) { | |
if (_method && 'function' === typeof _method) | ||
_super = _method; | ||
|
||
var fn = function () { | ||
var keep_ssfi = flag(this, 'keep_ssfi'); | ||
var old_ssfi = flag(this, 'ssfi'); | ||
if (!keep_ssfi && old_ssfi) | ||
flag(this, 'ssfi', fn); | ||
var overwritingMethodWrapper = function () { | ||
// If proxy protection is disabled and this overwriting assertion hasn't | ||
// been overwritten again by yet another assertion, then use this method | ||
// wrapper as the starting point for removing implementation frames from the | ||
// stack trace of a failed assertion. | ||
// | ||
// Note: If this assertion has been overwritten, and thus the `keep_ssfi` | ||
// flag is set, then the overwriting method wrapper is used as the starting | ||
// point instead. This prevents the overwriting method wrapper from showing | ||
// up in the stack trace since it's invoked before this method wrapper. | ||
if (!flag(this, 'keep_ssfi')) { | ||
flag(this, 'ssfi', overwritingMethodWrapper); | ||
} | ||
|
||
// The `keep_ssfi` flag is set so that if this assertion ends up calling | ||
// the overwritten assertion, then the overwritten assertion doesn't attempt | ||
// to use itself as the starting point for removing implementation frames | ||
// from the stack trace of a failed assertion. | ||
flag(this, 'keep_ssfi', true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me see if I've got this right, I'm not sure if I fully understand what this does. So, we use the SSFI flag to store the function which should indicate where the real stack trace will start in order to remove internal implementation details, right? Whenever an assertion gets overwritten we must turn the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, so the function stored in the But what about all functions that came before the function stored in the (Note that because the As for your final question, yes, the |
||
var result = method(_super).apply(this, arguments); | ||
flag(this, 'keep_ssfi', false); | ||
|
@@ -72,6 +84,6 @@ module.exports = function (ctx, name, method) { | |
return newAssertion; | ||
} | ||
|
||
addLengthGuard(fn, name, false); | ||
ctx[name] = proxify(fn, name); | ||
addLengthGuard(overwritingMethodWrapper, name, false); | ||
ctx[name] = proxify(overwritingMethodWrapper, name); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a big fan of naming every function even if they're being assigned to a variable.
Bonus style points for this excellent practice.