Fix TypeScript type narrowing issue with isUrlString

Fixes #212
This commit is contained in:
Sindre Sorhus 2025-09-12 03:58:08 +07:00
parent ef35cc350a
commit c68ad76062
3 changed files with 33 additions and 3 deletions

View file

@ -8,6 +8,7 @@ import type {
Predicate,
Primitive,
TypedArray,
UrlString,
WeakRef,
Whitespace,
} from './types.js';
@ -760,7 +761,7 @@ export function isUrlSearchParams(value: unknown): value is URLSearchParams {
return getObjectType(value) === 'URLSearchParams';
}
export function isUrlString(value: unknown): value is string {
export function isUrlString(value: unknown): value is UrlString {
if (!isString(value)) {
return false;
}
@ -894,7 +895,7 @@ type Assert = {
dataView: (value: unknown, message?: string) => asserts value is DataView;
enumCase: <T = unknown>(value: unknown, targetEnum: T, message?: string) => asserts value is T[keyof T];
urlInstance: (value: unknown, message?: string) => asserts value is URL;
urlString: (value: unknown, message?: string) => asserts value is string;
urlString: (value: unknown, message?: string) => asserts value is UrlString;
truthy: <T>(value: T | Falsy, message?: string) => asserts value is T;
falsy: (value: unknown, message?: string) => asserts value is Falsy;
nan: (value: unknown, message?: string) => asserts value is number;
@ -1650,7 +1651,7 @@ export function assertUrlSearchParams(value: unknown, message?: string): asserts
}
}
export function assertUrlString(value: unknown, message?: string): asserts value is string {
export function assertUrlString(value: unknown, message?: string): asserts value is UrlString {
if (!isUrlString(value)) {
throw new TypeError(message ?? typeErrorMessage('string with a URL', value));
}
@ -1705,4 +1706,5 @@ export type {
Predicate,
Primitive,
TypedArray,
UrlString,
} from './types.js';

View file

@ -75,3 +75,10 @@ export type Predicate = (value: unknown) => boolean;
export type NonEmptyString = string & {0: string};
export type Whitespace = ' ';
/**
A string that represents a valid URL.
This is a branded type to prevent incorrect TypeScript type narrowing.
*/
export type UrlString = string & {readonly __brand: 'UrlString'};

View file

@ -17,6 +17,7 @@ import is, {
type Primitive,
type TypedArray,
type TypeName,
type UrlString,
} from '../source/index.js';
import {keysOf} from '../source/utilities.js';
@ -757,6 +758,26 @@ test('is.urlString', t => {
});
});
// Type test for urlString narrowing fix (issue #212)
// This test demonstrates that the fix allows proper type narrowing in both branches
(() => {
const value: unknown = 'test';
if (is.urlString(value)) {
// ✅ In true branch: value is narrowed to UrlString
expectTypeOf(value).toEqualTypeOf<UrlString>();
expectTypeOf(value).toMatchTypeOf<string>();
} else {
// ✅ In false branch: value remains unknown (not incorrectly narrowed)
expectTypeOf(value).toEqualTypeOf<unknown>();
// ✅ Manual narrowing to string still works
if (typeof value === 'string') {
expectTypeOf(value).toEqualTypeOf<string>();
}
}
})();
test('is.truthy', t => {
t.true(is.truthy('unicorn'));
t.true(is.truthy('🦄'));