Repository: toss/es-toolkit
File: src/compat/function/wrap.ts (TypeScript)

Code before:

/**
 * Creates a new function that passes the original function or value `value` as the first argument to the `wrapper`.
 * This allows you to decorate or extend the behavior of the original value in a flexible way.
 *
 * @example
 * // Wrap a function
 * const greet = (name: string) => `Hi, ${name}`;
 * const wrapped = wrap(greet, (value, name) => `[LOG] ${value(name)}`);
 * wrapped('Bob'); // => "[LOG] Hi, Bob"
 *
 * @example
 * // Wrap a primitive value
 * const wrapped = wrap('value', v => `<p>${v}</p>`);
 * wrapped(); // => "<p>value</p>"
 */
export function wrap(value: unknown, wrapper: (...args: unknown[]) => unknown): (...args: unknown[]) => unknown {
  return function (this: unknown, ...args: unknown[]): unknown {
    const wrapFn = isFunction(wrapper)
      ? wrapper
      : // eslint-disable-next-line @typescript-eslint/no-unused-vars
        (fn: unknown, ..._args: unknown[]) => fn;

    return wrapFn.apply(this, [value, ...args]);
  };
}


Review comment (suggestion):
Your type signatures define wrapper as always being a function, making this conditional check inconsistent with the type system. This creates an unreachable code path from TypeScript's perspective.

Consider:

1. Removing the check (preferred for strongly typed code)
2. Throwing an error instead of providing a fallback
3. Updating types to reflect that wrapper might not be a function

This would better align your runtime code with the TypeScript contract.


Code after:
import { identity } from '../../function/identity.ts';
import { isFunction } from '../../predicate/isFunction.ts';

/**
 * Creates a new function that passes the original function `value` as the first argument to the `wrapper`.
 * This allows you to decorate or extend the behavior of the original function in a flexible way.
 *
 * @param {T} value - A function to wrap.
 * @param {(value: T, ...args: Parameters<T>) => ReturnType<T>} wrapper - A wrapper function that receives the original function and its arguments.
 *
 * @example
 * const greet = (name: string) => `Hi, ${name}`;
 * const wrapped = wrap(greet, (value, name) => `[LOG] ${value(name)}`);
 * wrapped('Bob'); // => "[LOG] Hi, Bob"
 */
export function wrap<T extends (...args: unknown[]) => unknown>(
  value: T,
  wrapper: (value: T, ...args: Parameters<T>) => ReturnType<T>
): (...args: Parameters<T>) => ReturnType<T>;

/**
 * Creates a new function that passes the original value `value` as the first argument to the `wrapper`.
 * This allows you to decorate or extend the behavior of the original value in a flexible way.
 *
 * @param {T} value - A non-function value to wrap.
 * @param {(value: T, ...args: TArgs) => TResult} wrapper - A wrapper function that receives the value and arguments.
 *
 * @example
 * const wrapped = wrap('value', v => `<p>${v}</p>`);
 * wrapped(); // => "<p>value</p>"
 */
export function wrap<T, TArgs extends unknown[], TResult>(
  value: T,
  wrapper: (value: T, ...args: TArgs) => TResult
): (...args: TArgs) => TResult;

/**
 * Creates a new function that passes the original function or value `value` as the first argument to the `wrapper`.
 * This allows you to decorate or extend the behavior of the original value in a flexible way.
 *
 * @example
 * // Wrap a function
 * const greet = (name: string) => `Hi, ${name}`;
 * const wrapped = wrap(greet, (value, name) => `[LOG] ${value(name)}`);
 * wrapped('Bob'); // => "[LOG] Hi, Bob"
 *
 * @example
 * // Wrap a primitive value
 * const wrapped = wrap('value', v => `<p>${v}</p>`);
 * wrapped(); // => "<p>value</p>"
 */
export function wrap(value: unknown, wrapper: unknown): (...args: unknown[]) => unknown {
  return function (this: unknown, ...args: unknown[]): unknown {
    const wrapFn = isFunction(wrapper) ? (wrapper as (value: unknown, ...args: unknown[]) => unknown) : identity;

    return wrapFn.apply(this, [value, ...args]);
  };
}