1 line
9.8 KiB
Text
1 line
9.8 KiB
Text
|
|
{"version":3,"file":"isEmptyish.cjs","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 neve
|