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

Add length option #18

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
declare namespace mimicFn {
interface Options {
/**
Modify `Function#length`

Default: Use the existing `Function#length`.
*/
readonly length?: (length: number) => number;
}
}

declare const mimicFn: {
/**
Make a function mimic another one. It will copy over the properties `name`, `length`, `displayName`, and any custom properties you may have set.
Expand Down Expand Up @@ -35,7 +46,8 @@ declare const mimicFn: {
FunctionType extends (...arguments: ArgumentsType) => ReturnType
>(
to: (...arguments: ArgumentsType) => ReturnType,
from: FunctionType
from: FunctionType,
options?: mimicFn.Options
): FunctionType;

// TODO: Remove this for the next major release, refactor the whole definition to:
Expand Down
31 changes: 29 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
'use strict';

const mimicFn = (to, from) => {
const mimicFn = (to, from, {length} = {}) => {
for (const prop of Reflect.ownKeys(from)) {
Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop));
Object.defineProperty(to, prop, getFuncProp(from, prop, length));
}

return to;
};

const getFuncProp = (func, prop, length) => {
const descriptor = Object.getOwnPropertyDescriptor(func, prop);

if (prop === 'length' && length !== undefined) {
return getLengthProp(descriptor, length);
}

return descriptor;
};

// The function `length` can be changed with the `length` option, which is a
// function that takes the function `length` as input and returns it.
const getLengthProp = (descriptor, length) => {
if (typeof length !== 'function') {
return descriptor;
}

const newLength = length(descriptor.value);

if (!Number.isInteger(newLength)) {
return descriptor;
}

const value = Math.max(0, newLength);
return Object.assign({}, descriptor, {value});
};

module.exports = mimicFn;
// TODO: Remove this for the next major release
module.exports.default = mimicFn;
30 changes: 29 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ console.log(wrapper.unicorn);

It will copy over the properties `name`, `length`, `displayName`, and any custom properties you may have set.

### mimicFn(to, from)
### mimicFn(to, from, options?)

Modifies the `to` function and returns it.

Expand All @@ -57,6 +57,34 @@ Type: `Function`

Function to mimic.

#### options

Type: `object`

##### length

Type: `Function`

Modifies the function's `length` property. Useful when `from` and `to` do not
have the same number of arguments. This happens for example when binding or
currying.

```js
const mimicFn = require('mimic-fn');

const identity = value => value;
const getTrue = identity.bind(null, true);

mimicFn(getTrue, identity, { length: len => len - 1 });
console.log(getTrue.name);
//=> 'identity'

console.log(identity.length);
//=> 1

console.log(getTrue.length);
//=> 0
```

## Related

Expand Down
70 changes: 62 additions & 8 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,75 @@
import test from 'ava';
import mimickFn from '.';
import mimicFn from '.';

test('main', t => {
const symbol = Symbol('🦄');
const symbol = Symbol('🦄');

function foo(bar) {} // eslint-disable-line no-unused-vars
foo.unicorn = '🦄';
foo[symbol] = '✨';
const foo = function (bar) {
return bar;
};

function wrapper() {}
foo.unicorn = '🦄';
foo[symbol] = '✨';

test('main', t => {
t.is(foo.name, 'foo');

t.is(mimickFn(wrapper, foo), wrapper);
const wrapper = function () {};
t.is(mimicFn(wrapper, foo), wrapper);

t.is(wrapper.name, 'foo');
t.is(wrapper.length, 1);
t.is(wrapper.unicorn, '🦄');
t.is(wrapper[symbol], '✨');
});

test('should keep descriptors', t => {
const wrapper = function () {};
mimicFn(wrapper, foo);

// TODO: use `Object.getOwnPropertyDescriptors()` after dropping support for
// Node 6
for (const prop of Reflect.ownKeys(foo)) {
t.deepEqual(
Object.getOwnPropertyDescriptor(wrapper, prop),
Object.getOwnPropertyDescriptor(foo, prop)
);
}
});

test('"length" option: should set function length', t => {
const wrapper = function () {};
mimicFn(wrapper, foo, {length: len => len + 1});

t.is(wrapper.length, 2);
});

test('"length" option: should keep descriptors', t => {
const wrapper = function () {};
mimicFn(wrapper, foo, {length: len => len});

t.deepEqual(
Object.getOwnPropertyDescriptor(wrapper, 'length'),
Object.getOwnPropertyDescriptor(foo, 'length')
);
});

test('"length" option: should fix negative function length', t => {
const wrapper = function () {};
mimicFn(wrapper, foo, {length: len => len - 2});

t.is(wrapper.length, 0);
});

test('"length" option: should ignore non-integer function length', t => {
const wrapper = function () {};
mimicFn(wrapper, foo, {length: () => NaN});

t.is(wrapper.length, 1);
});

test('`length` option: should ignore if not a function', t => {
const wrapper = function () {};
mimicFn(wrapper, foo, {length: 2});

t.is(wrapper.length, 1);
});