{"version":3,"file":"isEmptyish.js","names":[],"sources":["../src/isEmptyish.ts"],"sourcesContent":["import type {\n  And,\n  HasRequiredKeys,\n  IsAny,\n  IsEqual,\n  IsNever,\n  IsNumericLiteral,\n  IsUnknown,\n  OmitIndexSignature,\n  Or,\n  Tagged,\n  ValueOf,\n} from \"type-fest\";\nimport type { HasWritableKeys } from \"./internal/types/HasWritableKeys\";\nimport type { TupleParts } from \"./internal/types/TupleParts\";\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- we use a non-exported unique symbol to prevent users from faking our return type.\ndeclare const EMPTYISH_BRAND: unique symbol;\n\n// Because our function is a type-predicate and it narrows the input based on\n// the result of our type, we sometimes need a way to \"turn off\" narrowing while\n// still returning the input type. By tagging/branding our return type we stop\n// TypeScript from narrowing it while still allowing it to be used as if it was\n// the input type (because it still extends the type).\ntype Empty<T> = Tagged<T, typeof EMPTYISH_BRAND>;\n\n// The goal of this type is to return the empty \"view\" of the input type. This\n// makes it possible for TypeScript to narrow it precisely.\ntype Emptyish<T> =\n  // There are effectively 4 types that can be empty:\n  | (T extends string ? \"\" : never)\n  | (T extends object ? EmptyishObjectLike<T> : never)\n  | (T extends null ? null : never)\n  | (T extends undefined ? undefined : never);\n\n// Because of TypeScript's duck-typing, a lot of sub-types of `object` can\n// extend each other so we need to cascade between the different \"kinds\" of\n// objects.\ntype EmptyishObjectLike<T extends object> = T extends readonly unknown[]\n  ? EmptyishArray<T>\n  : T extends ReadonlyMap<infer Key, unknown>\n    ? T extends Map<unknown, unknown>\n      ? // Mutable maps should remain mutable so we can't narrow them down.\n        Empty<T>\n      : // But immutable maps could be rewritten to prevent any mutations.\n        ReadonlyMap<Key, never>\n    : T extends ReadonlySet<unknown>\n      ? T extends Set<unknown>\n        ? // Mutable sets should remain mutable so we can't narrow them down.\n          Empty<T>\n        : // But immutable sets could be rewritten to prevent any mutations.\n          ReadonlySet<never>\n      : EmptyishObject<T>;\n\ntype EmptyishArray<T extends readonly unknown[]> = T extends readonly []\n  ? // By returning T we effectively narrow the \"else\" branch to `never`.\n    T\n  : And<\n        IsEqual<TupleParts<T>[\"required\"], []>,\n        IsEqual<TupleParts<T>[\"suffix\"], []>\n      > extends true\n    ? T extends unknown[]\n      ? // A mutable array should remain mutable so we can't narrow it down.\n        Empty<T>\n      : // But immutable arrays could be rewritten to prevent any mutations.\n        readonly []\n    : // An array with a required prefix or suffix would never be empty, we can\n      // use that fact to narrow the \"if\" branch to `never`.\n      never;\n\ntype EmptyishObject<T extends object> = T extends {\n  length: infer Length extends number;\n}\n  ? T extends string\n    ? // When a string is tagged/branded it also extends `object` and also has\n      // a `length` prop so we need to prevent handling it because it's\n      // irrelevant here!\n      never\n    : // Because of how the implementation works, we need to consider any object\n      // with a `length` prop as potentially \"empty\".\n      EmptyishArbitrary<T, Length>\n  : T extends { size: infer Size extends number }\n    ? // Because of how the implementation works, we need to consider any object\n      // with a `size` prop as potentially \"empty\".\n      EmptyishArbitrary<T, Size>\n    : IsNever<ValueOf<T>> extends true\n      ? // This handles empty objects; by returning T we effectively narrow the\n        // \"else\" branch to `never`.\n        T\n      : HasRequiredKeys<OmitIndexSignature<T>> extends true\n        ? // If the object has required keys it can never be empty, we can use\n          // that fact to narrow the \"if\" branch to `never`.\n          never\n        : HasWritableKeys<T> extends true\n          ? // A mutable object should remain mutable so we can't narrow it\n            // down.\n            Empty<T>\n          : // But immutable objects could be rewritten to prevent any\n            // mutations.\n            { readonly [P in keyof T]: never };\n\n// We use certain props to check for emptiness effectively, but that means we\n// will return those values for any object that has them. Because we don't know\n// anything about those objects we need to be careful about narrowing.\ntype EmptyishArbitrary<T, N> =\n  IsNumericLiteral<N> extends true\n    ? [0] extends [N]\n      ? [N] extends [0]\n        ? // If the prop is a literal 0 the object is and always will be empty\n          // so we can return it to narrow the \"else\" branch as `never`.\n          T\n        : // If it accepts 0, but might accept other values too we need to\n          // consider the object mutable and not narrow it down.\n          Empty<T>\n      : // If the prop will never be 0 we can say it will never be empty and can\n        // return `never` for the \"if\" branch.\n        never\n    : // If the prop isn't a literal value we don't know enough about the object\n      // and should consider it mutable.\n      Empty<T>;\n\n// Overly generic types interfere with our already pretty complex return type.\n// To make our lives easier we can filter them out at the function declaration\n// step and we never need to think about them again.\ntype ShouldNotNarrow<T> = Or<\n  Or<IsAny<T>, IsUnknown<T>>,\n  IsEqual<\n    T,\n    // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n    {}\n  >\n>;\n\n/**\n * A function that checks if the input is empty. Empty is defined as anything\n * exposing a numerical `length`, or `size` property that is equal to `0`. This\n * definition covers strings, arrays, Maps, Sets, plain objects, and custom\n * classes. Additionally, `null` and `undefined` are also considered empty.\n *\n * `number`, `bigint`, `boolean`, `symbol`, and `function` will always return\n * `false`. `RegExp`, `Date`, and weak collections will always return `true`.\n * Classes and Errors are treated as plain objects: if they expose any public\n * property they would be considered non-empty, unless they expose a numerical\n * `length` or `size` property, which defines their emptiness regardless of\n * other properties.\n *\n * This function has *limited* utility at the type level because **negating** it\n * does not yield a useful type in most cases because of TypeScript\n * limitations. Additionally, utilities which accept a narrower input type\n * provide better type-safety on their inputs. In most cases, you should use\n * one of the following functions instead:\n * * `isEmpty` - provides better type-safety on inputs by accepting a narrower set of cases.\n * * `hasAtLeast` - when the input is just an array/tuple.\n * * `isStrictEqual` - when you just need to check for a specific literal value.\n * * `isNullish` - when you just care about `null` and `undefined`.\n * * `isTruthy` - when you need to also filter `number` and `boolean`.\n *\n * @param data - The variable to check.\n * @signature\n *    isEmptyish(data)\n * @example\n *    isEmptyish(undefined); //=> true\n *    isEmptyish(null); //=> true\n *    isEmptyish(''); //=> true\n *    isEmptyish([]); //=> true\n *    isEmptyish({}); //=> true\n *    isEmptyish(new Map()); //=> true\n *    isEmptyish(new Set()); //=> true\n *    isEmptyish({ a: \"hello\", size: 0 }); //=> true\n *    isEmptyish(/abc/); //=> true\n *    isEmptyish(new Date()); //=> true\n *    isEmptyish(new WeakMap()); //=> true\n *\n *    isEmptyish('test'); //=> false\n *    isEmptyish([1, 2, 3]); //=> false\n *    isEmptyish({ a: \"hello\" }); //=> false\n *    isEmptyish({ length: 1 }); //=> false\n *    isEmptyish(0); //=> false\n *    isEmptyish(true); //=> false\n *    isEmptyish(() => {}); //=> false\n * @category Guard\n */\nexport function isEmptyish<T>(\n  data: ShouldNotNarrow<T> extends true\n    ? never\n    : T | Readonly<Emptyish<NoInfer<T>>>,\n): data is ShouldNotNarrow<T> extends true\n  ? never\n  : T extends unknown\n    ? Emptyish<NoInfer<T>>\n    : never;\nexport function isEmptyish(data: unknown): boolean;\n\nexport function isEmptyish(data: unknown): boolean {\n  // eslint-disable-next-line eqeqeq -- Less code to ship...\n  if (data == undefined || data === \"\") {\n    // These are the only literal values that are considered emptyish.\n    return true;\n  }\n\n  if (typeof data !== \"object\") {\n    // There are no non-object types that could be empty at this point...\n    return false;\n  }\n\n  if (\"length\" in data && typeof data.length === \"number\") {\n    // Arrays and array-likes.\n    return data.length === 0;\n  }\n\n  if (\"size\" in data && typeof data.size === \"number\") {\n    // Maps and Sets.\n    return data.size === 0;\n  }\n\n  // eslint-disable-next-line guard-for-in, no-unreachable-loop -- Instead of taking Object.keys just to check its length, which will be inefficient if the object has a lot of keys, we have a backdoor into an iterator of the object's properties via the `for...in` loop.\n  for (const _ in data) {\n    return false;\n  }\n\n  // We can't do a similar optimization for symbol props, so we leave them for\n  // the very last check when the object is practically empty. Assuming that\n  // even if an object has a symbol prop, it probably doesn't have thousands of\n  // them.\n  return Object.getOwnPropertySymbols(data).length === 0;\n}\n"],"mappings":"AAiMA,SAAgB,EAAW,EAAwB,CAEjD,GAAI,GAAQ,MAAa,IAAS,GAEhC,MAAO,GAGT,GAAI,OAAO,GAAS,SAElB,MAAO,GAGT,GAAI,WAAY,GAAQ,OAAO,EAAK,QAAW,SAE7C,OAAO,EAAK,SAAW,EAGzB,GAAI,SAAU,GAAQ,OAAO,EAAK,MAAS,SAEzC,OAAO,EAAK,OAAS,EAIvB,IAAK,IAAM,KAAK,EACd,MAAO,GAOT,OAAO,OAAO,sBAAsB,EAAK,CAAC,SAAW"}