{"version":3,"file":"endsWith.cjs","names":["purry"],"sources":["../src/endsWith.ts"],"sourcesContent":["/* eslint-disable unicorn/consistent-boolean-name --\n * When we mirror a built-in function we use the same name for it.\n */\n\nimport type {\n  IsEqual,\n  IsNever,\n  IsStringLiteral,\n  UnionToIntersection,\n} from \"type-fest\";\nimport type { Boxed } from \"./internal/types/Boxed\";\nimport type { RemedaTypeError } from \"./internal/types/RemedaTypeError\";\nimport { purry } from \"./purry\";\n\n// By intersecting with a suffix template we force all types that satisfy this\n// type to also be of this shape. For a raw primitive string this narrows\n// exactly to the suffix template, for a literal TypeScript checks if it\n// satisfies the condition and narrow to `never` if not (and distribute the\n// check for unions). The only limitation is for unbounded template literals, as\n// TypeScript leaves the intersection as-is, even when they are disjoint.\ntype EndsWith<T, Suffix extends string> = T & `${string}${Suffix}`;\n\ntype IsDisjointSuffix<T extends string, Suffix extends string> =\n  // The tuple wrapping keeps the checks decidable while `T` or `Suffix` is an\n  // unresolved type parameter (a generic wrapper around the function):\n  // TypeScript probes a deferred conditional with a wildcard type, which bare\n  // `string extends T` resolves to, leaving the rejection branch a live\n  // candidate that no argument satisfies; `[string] extends [T]` resolves to\n  // `false` and rules it out.\n  [string] extends [Suffix]\n    ? // A primitive suffix could hold any value at runtime, so a check with it\n      // is never provably dead.\n      false\n    : [string] extends [T]\n      ? // A primitive string could hold any value at runtime, so a suffix is\n        // never provably dead for it.\n        false\n      : IsNever<EndsWith<T, Suffix>>;\n\n// The mirror image of `IsDisjointSuffix`: every value `T` could hold ends with\n// every value `Suffix` could hold, so the check is provably always `true`.\ntype IsGuaranteedSuffix<T extends string, Suffix extends string> = [T] extends [\n  EndsWithEvery<T, Suffix>,\n]\n  ? true\n  : false;\n\ntype DisjointSuffixError<Suffix extends string> = RemedaTypeError<\n  \"endsWith\",\n  \"This suffix doesn't match any of the inputs, the function will always return `false`\",\n  {\n    // A `string` base is already satisfied by any suffix argument, so the\n    // assignability failure is reported on the tag, which carries the\n    // message.\n    type: string;\n    metadata: Suffix;\n  }\n>;\n\n// The same intersection, but requiring *every* possible runtime value of the\n// suffix instead of any of them. Only one of them is the suffix at runtime, and\n// which one is unknowable, so a failed check can only rule out values that\n// would have matched no matter which one it was.\ntype EndsWithEvery<T, Suffix extends string> = T &\n  // 4. And then we intersect the suffixes instead of adding them to a union to\n  // flip the semantics from \"OR\" to \"AND\", so that the resulting suffix\n  // limitation is the tightest possible combination of all suffixes, and not\n  // the widest one, before unwrapping the box.\n  Boxed.Extract<\n    UnionToIntersection<\n      // 1. We first distribute the union to compute the suffix for each member\n      // of the union separately (otherwise the suffix itself would contain\n      // the union).\n      Suffix extends unknown\n        ? // 3. Each suffix is boxed so that it survives as a distinct union\n          // member until the intersection. Unboxed, a `never` would vanish from\n          // the union instead of emptying the intersection, and an empty\n          // suffix's `string` would absorb its siblings via subtype reduction.\n          Boxed<\n            // 2. Unbounded template strings represent infinite possible\n            // suffixes, which is exactly the kind of uncertainty that we are\n            // working to resolve here, only literals are workable here.\n            IsStringLiteral<Suffix> extends true ? `${string}${Suffix}` : never\n          >\n        : never\n    >\n  >;\n\n// TypeScript treats type-guards as complementary (e.g., everything either\n// fully satisfies the type, or fully doesn't, typing the falsy branch similar\n// to the result of `Exclude<T, Condition>`). `endsWith` doesn't have this\n// relationship when `Suffix` is a union because we don't **know** which of the\n// union members match, so we can't narrow the falsy branch at all. The only way\n// to prevent this is to prevent TypeScript from using the narrowing overload\n// in cases where we know the narrowing wouldn't be sound.\ntype IsNarrowingUnsound<T, Suffix extends string> = IsEqual<\n  // We simulate the falsy branch using the actual narrowing type we use and\n  // the type created by narrowing via *all* union members together.\n  IsEqual<\n    Exclude<T, EndsWith<T, Suffix>>,\n    Exclude<T, EndsWithEvery<T, Suffix>>\n  >,\n  // We want to find the cases where they don't agree, this means that narrowing\n  // would result in an unsound overly-narrow falsy branch.\n  false\n>;\n\n/**\n * **NOTE**: every possible value of `data` starts with every possible value of\n * `suffix` meaning the check can't fail; so the result is typed as a\n * **literal `true`**.\n *\n * @param data - The input string.\n * @param suffix - The string to check for at the end.\n * @hidden\n */\nexport function endsWith<T extends string, Suffix extends string>(\n  data: T,\n  // This signature has to come first because the narrowing overload accepts\n  // these inputs too, it would just narrow `data` to itself.\n  suffix: IsDisjointSuffix<T, Suffix> extends true\n    ? // Every data-first overload rejects a dead suffix so that no overload\n      // matches the call at all, which puts the error on the argument itself.\n      DisjointSuffixError<Suffix>\n    : IsGuaranteedSuffix<T, Suffix> extends true\n      ? Suffix\n      : never,\n): true;\n\n/**\n * Determines whether a string ends with the provided suffix, and refines the\n * output type if possible.\n *\n * This function is a wrapper around the built-in [`String.prototype.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\n * method, but doesn't expose the `endPosition` parameter. To check only up to a\n * specific position, use `endsWith(sliceString(data, 0, endPosition), suffix)`.\n *\n * @param data - The input string.\n * @param suffix - The string to check for at the end.\n * @signature\n *   endsWith(data, suffix);\n * @example\n *   endsWith(\"hello world\", \"world\"); //=> true\n *   endsWith(\"hello world\" as string, \"hello\"); //=> false\n * @dataFirst\n * @category String\n */\nexport function endsWith<T extends string, Suffix extends string>(\n  data: T,\n  suffix: string extends Suffix\n    ? // Reject primitive strings, they can't be used to narrow T. They would\n      // match the non-narrowing overload.\n      never\n    : IsDisjointSuffix<T, Suffix> extends true\n      ? DisjointSuffixError<Suffix>\n      : IsNarrowingUnsound<T, Suffix> extends true\n        ? // Union suffixes are rejected too when the guard they'd produce isn't\n          // sound.\n          never\n        : Suffix,\n): data is EndsWith<T, Suffix>;\n\nexport function endsWith<T extends string, Suffix extends string>(\n  data: T,\n  suffix: IsDisjointSuffix<T, Suffix> extends true\n    ? // Without the disjoint check here too, a dead suffix rejected by the\n      // previous overload would fall through to this one and be accepted.\n      DisjointSuffixError<Suffix>\n    : Suffix,\n): boolean;\n\n/**\n * Determines whether a string ends with the provided suffix, and refines the\n * output type if possible.\n *\n * This function is a wrapper around the built-in [`String.prototype.endsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\n * method, but doesn't expose the `endPosition` parameter. To check only up to a\n * specific position, use `endsWith(sliceString(data, 0, endPosition), suffix)`.\n *\n * @param suffix - The string to check for at the end.\n * @signature\n *   endsWith(suffix)(data);\n * @example\n *   pipe(\"hello world\", endsWith(\"world\")); //=> true\n *   pipe(\"hello world\", endsWith(\"hello\")); //=> false\n * @dataLast\n * @category String\n */\nexport function endsWith<T extends string, Suffix extends string>(\n  // This signature has to come first because the generic guard overload\n  // below accepts every literal suffix. Unlike the data-first overloads we\n  // can't fail the call on the suffix itself: an overload that rejects it\n  // just doesn't match, and the call falls through to the generic guard,\n  // which has no `T` to check it against. So the rejection is carried by the\n  // returned predicate instead, which rejects `data`.\n  suffix: IsDisjointSuffix<T, Suffix> extends true ? Suffix : never,\n): (data: T & DisjointSuffixError<Suffix>) => boolean;\n\nexport function endsWith<T extends string, Suffix extends string>(\n  // Like the rejection overload, `T` is inferred from the contextual type of\n  // the returned predicate; without one it falls back to `string`, which only\n  // an empty suffix is guaranteed for.\n  suffix: IsGuaranteedSuffix<T, Suffix> extends true ? Suffix : never,\n): (data: T) => true;\n\nexport function endsWith<T extends string, Suffix extends string>(\n  // In the narrowing data-last overload we move the type of `data` to the\n  // returned callback so that it could defer the inference to the wrapper,\n  // allowing it to support complex compositions (e.g., `isNot`); but our\n  // soundness check requires the `data` type so it could compare against it.\n  // To work around this we need an additional overload that would only match\n  // the unsound cases. If the inputs are sound, it wouldn't match and allow us\n  // to fall through to the next overload.\n  suffix: IsNarrowingUnsound<T, Suffix> extends true ? Suffix : never,\n): (data: T) => boolean;\n\nexport function endsWith<Suffix extends string>(\n  // Reject primitive strings, they can't be used to narrow T. They would match\n  // the non-narrowing overload.\n  suffix: string extends Suffix ? never : Suffix,\n): <T extends string>(data: T) => data is EndsWith<T, Suffix>;\n\nexport function endsWith(suffix: string): (data: string) => boolean;\n\nexport function endsWith(...args: readonly unknown[]): unknown {\n  return purry(endsWithImplementation, args);\n}\n\nconst endsWithImplementation = (data: string, suffix: string): boolean =>\n  data.endsWith(suffix);\n"],"mappings":"kGAgOA,SAAgB,EAAS,GAAG,EAAmC,CAC7D,OAAOA,EAAAA,MAAM,EAAwB,CAAI,CAC3C,CAEA,MAAM,GAA0B,EAAc,IAC5C,EAAK,SAAS,CAAM"}