forked from orbit-oss/is
Fix isEnumCase incorrectly accepting numeric enum key names
This commit is contained in:
parent
54fc09406a
commit
cb4ee0e92c
2 changed files with 56 additions and 3 deletions
|
|
@ -542,8 +542,21 @@ export function isEmptyStringOrWhitespace(value: unknown): value is '' | Whitesp
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is T[keyof T] {
|
export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is T[keyof T] {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// Numeric enums have reverse mappings (e.g. `Direction[0] = "Up"`), so their runtime object contains both `{ Up: 0 }` and `{ "0": "Up" }`. Filtering out entries that round-trip like a canonical number and point back to an own property leaves only actual enum member values.
|
||||||
return Object.values(targetEnum as any).includes(value as string);
|
const enumObject = targetEnum as Record<PropertyKey, unknown>;
|
||||||
|
|
||||||
|
return Object.entries(enumObject).some(([key, enumValue]) => {
|
||||||
|
if (!isString(enumValue)) {
|
||||||
|
return enumValue === value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericKey = Number(key);
|
||||||
|
if (Number.isNaN(numericKey) || String(numericKey) !== key) {
|
||||||
|
return enumValue === value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return enumValue === value && !(Object.hasOwn(enumObject, enumValue) && enumObject[enumValue] === numericKey);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isError(value: unknown): value is Error {
|
export function isError(value: unknown): value is Error {
|
||||||
|
|
@ -786,7 +799,7 @@ export function isPromise<T = unknown>(value: unknown): value is Promise<T> {
|
||||||
return isNativePromise(value) || hasPromiseApi(value);
|
return isNativePromise(value) || hasPromiseApi(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `PropertyKey` is any value that can be used as an object key (string, number, or symbol)
|
// `PropertyKey` is any value that can be used as an object key (string, number, or symbol). Note: NaN is technically `typeof 'number'` and thus fits TypeScript's `PropertyKey`, but we intentionally exclude it here because using NaN as a property key is almost always a mistake.
|
||||||
export function isPropertyKey(value: unknown): value is PropertyKey {
|
export function isPropertyKey(value: unknown): value is PropertyKey {
|
||||||
return isAny([isString, isNumber, isSymbol], value);
|
return isAny([isString, isNumber, isSymbol], value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
test/test.ts
40
test/test.ts
|
|
@ -855,6 +855,12 @@ test('is.enumCase', () => {
|
||||||
Key2 = 'key2',
|
Key2 = 'key2',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NumericKeyStringEnum {
|
||||||
|
// eslint-disable-next-line @stylistic/quote-props
|
||||||
|
'0' = 'zero',
|
||||||
|
'01' = 'padded',
|
||||||
|
}
|
||||||
|
|
||||||
assert.ok(is.enumCase('key1', NonNumericalEnum));
|
assert.ok(is.enumCase('key1', NonNumericalEnum));
|
||||||
assert.doesNotThrow(() => {
|
assert.doesNotThrow(() => {
|
||||||
isAssert.enumCase('key1', NonNumericalEnum);
|
isAssert.enumCase('key1', NonNumericalEnum);
|
||||||
|
|
@ -864,6 +870,40 @@ test('is.enumCase', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
isAssert.enumCase('invalid', NonNumericalEnum);
|
isAssert.enumCase('invalid', NonNumericalEnum);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.ok(is.enumCase('zero', NumericKeyStringEnum));
|
||||||
|
assert.ok(is.enumCase('padded', NumericKeyStringEnum));
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.enumCase('zero', NumericKeyStringEnum);
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.enumCase('padded', NumericKeyStringEnum);
|
||||||
|
});
|
||||||
|
|
||||||
|
enum NumericalEnum {
|
||||||
|
Key1 = 0,
|
||||||
|
Key2 = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(is.enumCase(0, NumericalEnum));
|
||||||
|
assert.ok(is.enumCase(1, NumericalEnum));
|
||||||
|
assert.strictEqual(is.enumCase('Key1', NumericalEnum), false);
|
||||||
|
assert.strictEqual(is.enumCase('Key2', NumericalEnum), false);
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.enumCase(0, NumericalEnum);
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.enumCase('Key1', NumericalEnum);
|
||||||
|
});
|
||||||
|
|
||||||
|
enum HeterogeneousEnum {
|
||||||
|
A = 1,
|
||||||
|
B = 'hello',
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(is.enumCase(1, HeterogeneousEnum));
|
||||||
|
assert.ok(is.enumCase('hello', HeterogeneousEnum));
|
||||||
|
assert.strictEqual(is.enumCase('A', HeterogeneousEnum), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('is.directInstanceOf', () => {
|
test('is.directInstanceOf', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue