Skip to content
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

Un-deprecate length and add guard #897

Merged
merged 2 commits into from
Jan 3, 2017
Merged

Conversation

meeber
Copy link
Contributor

@meeber meeber commented Jan 2, 2017

This PR is broken into two commits. The first commit makes length and lengthOf true aliases, with documentation updated to remove the deprecation notice and favor lengthOf over length. The second commit adds a guard against chaining length directly off of an uninvoked method assertion.

- The method part of the `length` assertion was slated for deprecation
  due to a compatibility issue in legacy environments. The decision to
  deprecate `length` was reversed per chaijs#684. This commit replaces the
  deprecation notice with a recommendation to favor `lengthOf` over
  `length` due to the compatibility issue.

- The `lengthOf` assertion wasn't a true alias of `length` because it
  was a method assertion instead of a chainable method assertion. This
  commit changes `lengthOf` into a chainable method assertion that's
  identical to `length`, and updates tests and docs accordingly.

- Updates docs to classify `length` as an alias of `lengthOf`.

- Updates docs of related assertions to use `lengthOf` instead of
  `length`.
@shvaikalesh
Copy link
Contributor

shvaikalesh commented Jan 2, 2017

Great work, @meeber.

However, I think trapping length makes things more complicated than they could be. What about checking configurable attr of a function's length and set up a getter?

EDIT: why that's better:

  • proxiyfy: SRP (well, more specific responsibility), -1 parameter, easier logic
  • Works down to Node.js 4
  • Will make proxy optimizable

@meeber
Copy link
Contributor Author

meeber commented Jan 2, 2017

@shvaikalesh Seems like a good suggestion, even if only for the benefit of "Works down to Node.js 4". Can you elaborate on the third point about optimization?

@lucasfcosta
Copy link
Member

Awesome work! Thanks @meeber!
As @shvaikalesh suggested, since we're targeting newer environments by fixing this problem with Proxies I think that checking for configurable length properties would be a great idea.

However, due to the fact that older browsers don't have neither configurable length nor Proxy support I think this is a problem we can't really handle without an explicit warning.

@meeber
Copy link
Contributor Author

meeber commented Jan 2, 2017

@lucasfcosta Just to make sure we're on the same page regarding your second point: With this PR, the proxy-based length protection would throw a helpful error message in environments that support proxies, but in other environments, it would still throw the somewhat vague error message that it currently does in master. Likewise, if I refactor this PR to instead use @shvaikalesh's suggestion, then the getter-based length protection would throw a helpful error message in environments that support configurable .length on functions, but in other environments, it would still throw the somewhat vague error message that it currently does in master. So either way, the error messages in the legacy environments aren't improved upon, but at least @shvaikalesh's idea allows more environments to benefit from the helpful error messages.

@shvaikalesh
Copy link
Contributor

shvaikalesh commented Jan 2, 2017

@meeber

Right now, we wrap every Assertion instance/chainable in proxy. That is kinda expensive. But what more expensive is that every [[Get]] getting trapped and invariant-checked.

Now

Correct method

Proxy {expensive} --> Assertion instance --> Assertion.prototype [hit!] -!- Object.prototype -!- null

Misspelled method

Proxy {expensive}[error!] -!- Assertion instance -!- Assertion.prototype [hit!] -!- Object.prototype -!- null

Suggested

Correct method

Assertion instance --> Assertion.prototype [hit!] -!- Proxy {expensive} -!- Object.prototype -!- null

Misspelled method

Assertion instance --> Assertion.prototype --> Proxy {expensive}[error!] -!- Object.prototype -!- null

EDIT: oops, sent too early.

So, by putting proxies upper on the prototype chain, [[Get]] will hit them only when they would throw and they won't reduce performance otherwise (tested in V8).

@meeber
Copy link
Contributor Author

meeber commented Jan 2, 2017

@shvaikalesh Gotcha. Seems like another strong reason to limit proxy protection to undefined properties.

@meeber meeber force-pushed the proxy-length branch 3 times, most recently from 8cfcf1b to 3397835 Compare January 3, 2017 01:20
@meeber
Copy link
Contributor Author

meeber commented Jan 3, 2017

Pushed a new version of this that uses getters instead of proxies.

var config = require('../config');

var fnDesc = Object.getOwnPropertyDescriptor(function () {}, 'length');
var isFnLengthConfigurable = typeof fnDesc === 'object'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Descriptor is guaranteed to be an object here.

*/

module.exports = function addLengthGuard (fn, assertionName, isChainable) {
if (isFnLengthConfigurable !== true) return fn;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can do if (!fnDesc.configurable) here?


Object.defineProperty(fn, 'length', {
get: function () {
if (isChainable === true) {
Copy link
Contributor

@shvaikalesh shvaikalesh Jan 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe if (isChainable) will suffice?

@meeber
Copy link
Contributor Author

meeber commented Jan 3, 2017

Updated!


describe('addLengthGuard', function () {
var fnDesc = Object.getOwnPropertyDescriptor(function () {}, 'length');
if (typeof fnDesc !== 'object' || fnDesc.configurable !== true) return;
Copy link
Contributor

@shvaikalesh shvaikalesh Jan 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've missed this before. I believe, if (!fnDesc.configurable) would be enough.

When the `length` assertion is chained directly off of an uninvoked
method, it references `function`'s built-in `length` property instead
of Chai's `length` assertion. This commit adds a guard to Chai methods
to detect this problem and throw a helpful error message that advises
the user on how to correct it.
@meeber
Copy link
Contributor Author

meeber commented Jan 3, 2017

Gottem.

@shvaikalesh
Copy link
Contributor

Thanks for addressing the issues, @meeber! LGTM.

PS. Maybe we should change PR title to reflect the changes?

@meeber meeber changed the title Un-deprecate length and add proxy protection Un-deprecate length and add guard Jan 3, 2017
@keithamus
Copy link
Member

This LGTM too. I'll force a merge, LGTM.co is no longer working so we need to disable and find a workable alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants