Closed vtgn closed 4 months ago
One possibility would be to redefine isWhitespaceString
and isEmptyStringOrWhitespace
as follows:
function isWhitespaceString(value: unknown): value is " " {
return isString(value) && /^\s+$/.test(value);
}
function isEmptyStringOrWhitespace(value: unknown): value is "" | " " {
return isEmptyString(value) || isWhitespaceString(value);
}
The behavior would be more accurate, but still not 100% correct as a string could contain any number of whitespaces. However, I don't think that it's possible to express that in TS. Also, it would be it would be a breaking change.
We can actually type it correctly: https://github.com/sindresorhus/type-fest/blob/0f732371f607fe44e934d178eb97ad71eccda873/source/internal.d.ts#L315-L322 But I don't think that would be very useful.
I think the solution above is the most practical one.
@marlun78 it is not perfect indeed, but it is a better solution than the current situation.
@sindresorhus there is another solution, more appropriate in my opinion, which consists of using the "opaque types" concept. You must create a simple type that "simulates" the complex type you are interested in, and create a type guard function using it. You can see what the developer of the zod library did with the brand feature: https://github.com/colinhacks/zod#brand You define in your library a unique symbol and a parameterized type that you use to create any "simulated" complex type. Zod does it like this:
export const BRAND: unique symbol = Symbol("zod_brand");
export type BRAND<T extends string | number | symbol> = {
[BRAND]: { [k in T]: true };
};
Then you create the type for the empty or whitespaces (
export type EmptyOrWhitespacesString = string & BRAND<"EmptyOrWhitespacesString">;
Then you modify the declaration of your isEmptyStringOrWhitespace type guard function (same for your assertEmptyStringOrWhitespace assertion guard function) to use this type:
function isEmptyStringOrWhitespace(value: unknown): value is EmptyOrWhitespacesString
Thus, you have a type matching perfectly the empty or whitespaces strings, even if it is "virtually", but it is enough to manage the typing correctly in every cases. Example of use:
let value: ATYPE = <any value>; // ATYPE can be any type (string, unknown, Object, any, number | boolean | null, etc...)
if (is.emptyStringOrWhitespace(value)) {
value; // => is typed by EmptyOrWhitespacesString and allows to use value as a string because this is the global type we use to define the EmptyOrWhitespacesString type (value.length is allowed for example)
}
else {
value; // => is typed by ATYPE
}
value; // => is typed by ATYPE
and this is exactly the behavior we wanted. ;) If you replace ATYPE by string, and you store a non empty and non whitespaces string value in the value variable, the type guard returns false, but you can nevertheless continue to use value as string in the else block, what is not possible with the current implementation of the type guard.
Regards.
=/ ...
@vtgn Sorry, I missed your last comment here. Are there any benefits of the "brand" over what's in https://github.com/sindresorhus/is/commit/25a376875d3c10e3963e2e948960162a221fe583? The benefit of '' | Whitespace
is that it has more info. For example, the user could potentially statically check if the string is empty.
One hybrid approach would be to use:
export type EmptyOrWhitespacesString = '' | (string & BRAND<"WhitespacesString">);
export function isEmptyStringOrWhitespace(value: unknown): value is EmptyOrWhitespacesString {
Hi!
There is a problem similar to the issue #176 with the isEmptyStringOrWhitespace type guard:
Indeed, this type guard is incorrect because the type is not enough precise and it leads to typing problems when it returns false. Example:
The workaround is to use a type assertion with the as keyword for the cases where the compiler see values as non string whereas they are. :/
I don't know if it is possible to create a precise type for empty or whitespaces strings. I will try and tell you if I find.
Regards.