diff --git a/browser.js b/browser.js index cc5d2e00..241202c7 100644 --- a/browser.js +++ b/browser.js @@ -2,7 +2,7 @@ // specific libraries, such as "path". // // TODO: figure out reasonable web equivalents for "resolve", "normalize", etc. -import { camelCase, decamelize } from './build/lib/string-utils.js' +import { camelCase, decamelize, looksLikeNumber } from './build/lib/string-utils.js' import { YargsParser } from './build/lib/yargs-parser.js' const parser = new YargsParser({ cwd: () => { return '' }, @@ -24,5 +24,6 @@ yargsParser.detailed = function (args, opts) { } yargsParser.camelCase = camelCase yargsParser.decamelize = decamelize +yargsParser.looksLikeNumber = looksLikeNumber export default yargsParser diff --git a/deno.ts b/deno.ts index 36bae99f..f3a77f8a 100644 --- a/deno.ts +++ b/deno.ts @@ -3,7 +3,7 @@ // // TODO: find reasonable replacement for require logic. import * as path from 'https://deno.land/std/path/mod.ts' -import { camelCase, decamelize } from './build/lib/string-utils.js' +import { camelCase, decamelize, looksLikeNumber } from './build/lib/string-utils.js' import { YargsParser } from './build/lib/yargs-parser.js' import { Arguments, ArgsInput, Parser, Options, DetailedArguments } from './build/lib/yargs-parser-types.d.ts' @@ -33,5 +33,6 @@ yargsParser.detailed = function (args: ArgsInput, opts?: Partial): Deta } yargsParser.camelCase = camelCase yargsParser.decamelize = decamelize +yargsParser.looksLikeNumber = looksLikeNumber export default yargsParser diff --git a/lib/index.ts b/lib/index.ts index 104e082d..5132599e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -4,7 +4,7 @@ import { format } from 'util' import { readFileSync } from 'fs' import { normalize, resolve } from 'path' import { ArgsInput, Arguments, Parser, Options, DetailedArguments } from './yargs-parser-types.js' -import { camelCase, decamelize } from './string-utils.js' +import { camelCase, decamelize, looksLikeNumber } from './string-utils.js' import { YargsParser } from './yargs-parser.js' // See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our @@ -49,4 +49,5 @@ yargsParser.detailed = function (args: ArgsInput, opts?: Partial): Deta } yargsParser.camelCase = camelCase yargsParser.decamelize = decamelize +yargsParser.looksLikeNumber = looksLikeNumber export default yargsParser diff --git a/lib/string-utils.ts b/lib/string-utils.ts index 29ce039b..732e82a7 100644 --- a/lib/string-utils.ts +++ b/lib/string-utils.ts @@ -38,3 +38,14 @@ export function decamelize (str: string, joinString?: string): string { } return notCamelcase } + +export function looksLikeNumber (x: null | undefined | number | string): boolean { + if (x === null || x === undefined) return false + // if loaded from config, may already be a number. + if (typeof x === 'number') return true + // hexadecimal. + if (/^0x[0-9a-f]+$/i.test(x)) return true + // don't treat 0123 as a number; as it drops the leading '0'. + if (x.length > 1 && x[0] === '0') return false + return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) +} diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts index 16dd35c7..1f3b9c36 100644 --- a/lib/yargs-parser-types.ts +++ b/lib/yargs-parser-types.ts @@ -131,6 +131,7 @@ export interface Parser { detailed(args: ArgsInput, opts?: Partial): DetailedArguments; camelCase(str: string): string; decamelize(str: string, joinString?: string): string; + looksLikeNumber(x: null | undefined | number | string): boolean; } export type StringFlag = Dictionary; diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts index 87f687bc..b682c4c6 100644 --- a/lib/yargs-parser.ts +++ b/lib/yargs-parser.ts @@ -22,7 +22,7 @@ import type { YargsParserMixin } from './yargs-parser-types.js' import type { Dictionary, ValueOf } from './common-types.js' -import { camelCase, decamelize } from './string-utils.js' +import { camelCase, decamelize, looksLikeNumber } from './string-utils.js' let mixin: YargsParserMixin export class YargsParser { @@ -621,7 +621,7 @@ export class YargsParser { function maybeCoerceNumber (key: string, value: string | number | null | undefined) { if (!configuration['parse-positional-numbers'] && key === '_') return value if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) { - const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( + const shouldCoerceNumber = looksLikeNumber(value) && configuration['parse-numbers'] && ( Number.isSafeInteger(Math.floor(parseFloat(`${value}`))) ) if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) { @@ -1004,17 +1004,6 @@ export class YargsParser { return type } - function isNumber (x: null | undefined | number | string): boolean { - if (x === null || x === undefined) return false - // if loaded from config, may already be a number. - if (typeof x === 'number') return true - // hexadecimal. - if (/^0x[0-9a-f]+$/i.test(x)) return true - // don't treat 0123 as a number; as it drops the leading '0'. - if (x.length > 1 && x[0] === '0') return false - return /^[-]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) - } - function isUndefined (num: any): num is undefined { return num === undefined } diff --git a/test/deno/yargs-test.ts b/test/deno/yargs-test.ts index 9a5442e7..8ee2ab82 100644 --- a/test/deno/yargs-test.ts +++ b/test/deno/yargs-test.ts @@ -54,3 +54,12 @@ Deno.test('convert hyphenated string to camelcase', () => { Deno.test('convert camelcase string to hyphenated', () => { assertEquals(parser.decamelize('helloWorld'), 'hello-world') }) + +Deno.test('it detects strings that could be parsed as numbers', () => { + assertEquals(parser.looksLikeNumber('3293'), true) + assertEquals(parser.looksLikeNumber('0x10'), true) + assertEquals(parser.looksLikeNumber('0x10'), true) + + assertEquals(parser.looksLikeNumber('0100'), false) + assertEquals(parser.looksLikeNumber('apple'), false) +}) diff --git a/test/string-utils.cjs b/test/string-utils.cjs index 545311c2..1129777c 100644 --- a/test/string-utils.cjs +++ b/test/string-utils.cjs @@ -1,20 +1,30 @@ /* global describe, it */ -const { expect } = require('chai') -const { camelCase, decamelize } = require('../build/index.cjs') +const { strictEqual } = require('assert') +const { camelCase, decamelize, looksLikeNumber } = require('../build/index.cjs') describe('string-utils', function () { describe('camelCase', () => { it('converts string with hypen in middle to camel case', () => { - expect(camelCase('hello-world')).to.equal('helloWorld') + strictEqual(camelCase('hello-world'), 'helloWorld') }) it('removes leading hyphens', () => { - expect(camelCase('-goodnight-moon')).to.equal('goodnightMoon') + strictEqual(camelCase('-goodnight-moon'), 'goodnightMoon') }) }) describe('decamelize', () => { it('adds hyphens back to camelcase string', () => { - expect(decamelize('helloWorld')).to.equal('hello-world') + strictEqual(decamelize('helloWorld'), 'hello-world') + }) + }) + describe('looksLikeNumber', () => { + it('it detects strings that could be parsed as numbers', () => { + strictEqual(looksLikeNumber('3293'), true) + strictEqual(looksLikeNumber('0x10'), true) + strictEqual(looksLikeNumber('0x10'), true) + + strictEqual(looksLikeNumber('0100'), false) + strictEqual(looksLikeNumber('apple'), false) }) }) })