From 2f425290ee077d9d013af77fa978d6b6d6885634 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Sun, 16 Feb 2025 12:24:26 -0500 Subject: [PATCH] [fix] Handle non-primitives in isNumber (#12034) While investigating https://github.com/chartjs/chartjs-plugin-zoom/issues/928, I found that `isNonPrimitive` will throw TypeError on a Moment.js object after it's passed through Chart.js's options proxy, because the object has its `Symbol.toPrimitive`, `toString`, and `valueOf` all set to null. (See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion for background reading.) Since isNumber appears to be a low-level function that can take any arbitrary input, it seems worth letting it handle this case. --- src/helpers/helpers.math.ts | 9 ++++++++- test/specs/helpers.math.tests.js | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/helpers/helpers.math.ts b/src/helpers/helpers.math.ts index da463aeb0..0fd2a9513 100644 --- a/src/helpers/helpers.math.ts +++ b/src/helpers/helpers.math.ts @@ -57,8 +57,15 @@ export function _factorize(value: number) { return result; } +/** + * Verifies that attempting to coerce n to string or number won't throw a TypeError. + */ +function isNonPrimitive(n: unknown) { + return typeof n === 'symbol' || (typeof n === 'object' && n !== null && !(Symbol.toPrimitive in n || 'toString' in n || 'valueOf' in n)); +} + export function isNumber(n: unknown): n is number { - return !isNaN(parseFloat(n as string)) && isFinite(n as number); + return !isNonPrimitive(n) && !isNaN(parseFloat(n as string)) && isFinite(n as number); } export function almostWhole(x: number, epsilon: number) { diff --git a/test/specs/helpers.math.tests.js b/test/specs/helpers.math.tests.js index b6b8e125f..938742959 100644 --- a/test/specs/helpers.math.tests.js +++ b/test/specs/helpers.math.tests.js @@ -103,6 +103,8 @@ describe('Chart.helpers.math', function() { expect(math.isNumber(NaN)).toBe(false); expect(math.isNumber(undefined)).toBe(false); expect(math.isNumber('cbc')).toBe(false); + expect(math.isNumber(Symbol())).toBe(false); + expect(math.isNumber(Object.create(null))).toBe(false); }); it('should compute shortest distance between angles', function() { -- 2.47.3