Skip to content

Commit e616e88

Browse files
requested changes team review
1 parent 88fb767 commit e616e88

File tree

6 files changed

+108
-70
lines changed

6 files changed

+108
-70
lines changed

src/int_32.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BSON_INT32_MAX, BSON_INT32_MIN } from './constants';
33
import { BSONError } from './error';
44
import type { EJSONOptions } from './extended_json';
55
import { type InspectFn, defaultInspect } from './parser/utils';
6-
import { removeLeadingZeros } from './utils/string_utils';
6+
import { removeLeadingZerosandExplicitPlus } from './utils/string_utils';
77

88
/** @public */
99
export interface Int32Extended {
@@ -49,7 +49,7 @@ export class Int32 extends BSONValue {
4949
* @param value - the string we want to represent as an int32.
5050
*/
5151
static fromString(value: string): Int32 {
52-
const cleanedValue = removeLeadingZeros(value);
52+
const cleanedValue = removeLeadingZerosandExplicitPlus(value);
5353

5454
const coercedValue = Number(value);
5555

src/long.ts

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BSONError } from './error';
33
import type { EJSONOptions } from './extended_json';
44
import { type InspectFn, defaultInspect } from './parser/utils';
55
import type { Timestamp } from './timestamp';
6-
import { removeLeadingZeros } from './utils/string_utils';
6+
import * as StringUtils from './utils/string_utils';
77

88
interface LongWASMHelpers {
99
/** Gets the high bits of the last operation performed */
@@ -246,20 +246,6 @@ export class Long extends BSONValue {
246246
return Long.fromString(value.toString(), unsigned);
247247
}
248248

249-
/**
250-
* @internal
251-
* Returns false for an string that contains invalid characters for its radix, else returns the original string.
252-
* @param str - The textual representation of the Long
253-
* @param radix - The radix in which the text is written (2-36), defaults to 10
254-
*/
255-
private static validateStringCharacters(str: string, radix?: number): false | string {
256-
radix = radix ?? 10;
257-
const validCharacters = '0123456789abcdefghijklmnopqrstuvwxyz'.slice(0, radix);
258-
// regex is case insensitive and checks that each character within the string is one of the validCharacters
259-
const regex = new RegExp(`[^-+${validCharacters}]`, 'i');
260-
return regex.test(str) ? false : str;
261-
}
262-
263249
/**
264250
* @internal
265251
* Returns a Long representation of the given string, written using the specified radix.
@@ -291,19 +277,19 @@ export class Long extends BSONValue {
291277

292278
if (radix < 2 || 36 < radix) throw new BSONError('radix');
293279

294-
if (validateStringCharacters && !Long.validateStringCharacters(str, radix)) {
295-
throw new BSONError(`Input: '${str}' contains invalid characters for radix: ${radix}`);
296-
}
297-
if (validateStringCharacters && str.trim() !== str) {
298-
throw new BSONError(`Input: '${str}' contains leading and/or trailing whitespace.`);
299-
}
300-
301280
let p;
302281
if ((p = str.indexOf('-')) > 0) throw new BSONError('interior hyphen');
303282
else if (p === 0) {
304283
return Long._fromString(str.substring(1), validateStringCharacters, unsigned, radix).neg();
305284
}
306285

286+
if (str.trim() !== str) {
287+
throw new BSONError(`Input: '${str}' contains leading and/or trailing whitespace`);
288+
}
289+
if (!StringUtils.validateStringCharacters(str, radix)) {
290+
throw new BSONError(`Input: '${str}' contains invalid characters for radix: ${radix}`);
291+
}
292+
307293
// Do several (8) digits each time through the loop, so as to
308294
// minimize the calls to the very expensive emulated div.
309295
const radixToPower = Long.fromNumber(Math.pow(radix, 8));
@@ -336,13 +322,17 @@ export class Long extends BSONValue {
336322
* @returns The corresponding Long value
337323
*/
338324
static fromStringStrict(str: string, unsigned?: boolean, radix?: number): Long {
325+
if (str === 'NaN' || str === 'Infinity' || str === '+Infinity' || str === '-Infinity')
326+
return Long.ZERO;
327+
339328
// remove leading zeros (for later string comparison and to make math faster)
340-
const cleanedStr = removeLeadingZeros(str);
341-
// doing this check outside of recursive function so cleanedStr value is consistent
329+
const cleanedStr = StringUtils.removeLeadingZerosandExplicitPlus(str);
330+
331+
// check roundtrip result
342332
const result = Long._fromString(cleanedStr, true, unsigned, radix);
343333
if (result.toString(radix).toLowerCase() !== cleanedStr.toLowerCase()) {
344334
throw new BSONError(
345-
`Input: ${str} is not representable as ${result.unsigned ? 'an unsigned' : 'a signed'} 64-bit Long with radix: ${radix}`
335+
`Input: ${str} is not representable as ${result.unsigned ? 'an unsigned' : 'a signed'} 64-bit Long ${radix != null ? `with radix: ${radix}` : ''}`
346336
);
347337
}
348338
return result;
@@ -356,7 +346,7 @@ export class Long extends BSONValue {
356346
* @returns The corresponding Long value
357347
*/
358348
static fromString(str: string, unsigned?: boolean, radix?: number): Long {
359-
return Long._fromString(str, false, unsigned, radix);
349+
return Long._fromString(str, true, unsigned, radix);
360350
}
361351

362352
/**

src/utils/string_utils.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
/**
22
* @internal
3-
* Removes leading zeros from textual representation of a number.
3+
* Removes leading zeros and explicit plus from textual representation of a number.
44
*/
5-
export function removeLeadingZeros(str: string): string {
6-
return !/[^0]+/.test(str)
7-
? str.replace(/^0+/, '0') // all zeros case
5+
export function removeLeadingZerosandExplicitPlus(str: string): string {
6+
return !/[^+?0]+/.test(str)
7+
? str.replace(/^\+?0+/, '0') // all zeros case (remove explicit plus if it exists)
88
: str[0] === '-'
99
? str.replace(/^-0+/, '-') // negative number with leading zeros
10-
: str.replace(/^\+?0+/, '');
10+
: str.replace(/^\+?0*/, ''); // remove explicit plus
11+
}
12+
13+
/**
14+
* @internal
15+
* Returns false for an string that contains invalid characters for its radix, else returns the original string.
16+
* @param str - The textual representation of the Long
17+
* @param radix - The radix in which the text is written (2-36), defaults to 10
18+
*/
19+
export function validateStringCharacters(str: string, radix?: number): false | string {
20+
radix = radix ?? 10;
21+
const validCharacters = '0123456789abcdefghijklmnopqrstuvwxyz'.slice(0, radix);
22+
// regex is case insensitive and checks that each character within the string is one of the validCharacters
23+
const regex = new RegExp(`[^-+${validCharacters}]`, 'i');
24+
return regex.test(str) ? false : str;
1125
}

test/node/int_32_tests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ describe('Int32', function () {
108108
['a string with zero with leading zeros', '000000', 0],
109109
['a string with positive leading zeros', '000000867', 867],
110110
['a string with explicity positive leading zeros', '+000000867', 867],
111-
['a string with negative leading zeros', '-00007', -7]
111+
['a string with negative leading zeros', '-00007', -7],
112+
['a string with explicit positive zeros', '+000000', 0],
113+
['a string explicit positive no leading zeros', '+32', 32]
112114
];
113115
const errorInputs = [
114116
['Int32.max + 1', '2147483648', 'larger than the maximum value for Int32'],

test/node/long.test.ts

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,13 @@ describe('Long', function () {
189189
['min signed binary input', Long.MIN_VALUE.toString(2), false, 2],
190190
['min signed decimal input', Long.MIN_VALUE.toString(10), false, 10],
191191
['min signed hex input', Long.MIN_VALUE.toString(16), false, 16],
192-
['signed zero', '0', false, 10],
193-
['unsigned zero', '0', true, 10]
192+
['signed zeros', '+000000', false, 10, '0'],
193+
['unsigned zero', '0', true, 10],
194+
['explicit positive no leading zeros', '+32', true, 10, '32'],
195+
['Infinity', 'Infinity', false, 21, '0'],
196+
['-Infinity', '-Infinity', false, 13, '0'],
197+
['+Infinity', '+Infinity', false, 13, '0'],
198+
['NaN', 'NaN', false, 11, '0']
194199
];
195200

196201
const failureInputs: [name: string, input: string, unsigned: boolean, radix: number][] = [
@@ -208,12 +213,17 @@ describe('Long', function () {
208213
['under min signed binary input', Long.MIN_VALUE.toString(2) + '1', false, 2],
209214
['under min signed decimal input', Long.MIN_VALUE.toString(10) + '1', false, 10],
210215
['under min signed hex input', Long.MIN_VALUE.toString(16) + '1', false, 16],
211-
['string with whitespace', ' 3503a ', false, 11]
216+
['string with whitespace', ' 3503a ', false, 11],
217+
['negative zero unsigned', '-0', true, 9],
218+
['negative zero signed', '-0', false, 13],
219+
['radix 1', '12', false, 1],
220+
['negative radix', '12', false, -4],
221+
['radix over 36', '12', false, 37]
212222
];
213223

214224
for (const [testName, str, unsigned, radix, expectedStr] of successInputs) {
215225
context(`when the input is ${testName}`, () => {
216-
it(`should return a Long represenation of the input`, () => {
226+
it(`should return a Long representation of the input`, () => {
217227
expect(Long.fromStringStrict(str, unsigned, radix).toString(radix)).to.equal(
218228
expectedStr ?? str.toLowerCase()
219229
);
@@ -228,36 +238,4 @@ describe('Long', function () {
228238
});
229239
}
230240
});
231-
232-
describe('static validateStringCharacters()', function () {
233-
const successInputs = [
234-
['radix does allows given alphabet letter', 'eEe', 15],
235-
['empty string', '', 2],
236-
['all possible hexadecimal characters', '12efabc689873dADCDEF', 16],
237-
['leading zeros', '0000000004567e', 16]
238-
];
239-
240-
const failureInputs = [
241-
['multiple decimal points', '..', 30],
242-
['non a-z or numeric string', '~~', 36],
243-
['alphabet in radix < 10', 'a', 4],
244-
['radix does not allow all alphabet letters', 'eee', 14]
245-
];
246-
247-
for (const [testName, str, radix] of successInputs) {
248-
context(`when the input is ${testName}`, () => {
249-
it(`should return a input string`, () => {
250-
expect(Long.validateStringCharacters(str, radix)).to.equal(str);
251-
});
252-
});
253-
}
254-
255-
for (const [testName, str, radix] of failureInputs) {
256-
context(`when the input is ${testName}`, () => {
257-
it(`should return false`, () => {
258-
expect(Long.validateStringCharacters(str, radix)).to.equal(false);
259-
});
260-
});
261-
}
262-
});
263241
});

test/node/utils/string_utils.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as StringUtils from '../../../src/utils/string_utils';
2+
3+
describe('removeLeadingZerosandExplicitPlus()', function () {
4+
const inputs: [testName: string, str: string, expectedStr: string][] = [
5+
['a string with zero with leading zeros', '000000', '0'],
6+
['a string with positive leading zeros', '000000867', '867'],
7+
['a string with explicity positive leading zeros', '+000000867', '867'],
8+
['a string with negative leading zeros', '-00007', '-7'],
9+
['a string with explicit positive zeros', '+000000', '0'],
10+
['a string explicit positive no leading zeros', '+32', '32'],
11+
['a string explicit positive no leading zeros and letters', '+ab00', 'ab00']
12+
];
13+
14+
for (const [testName, str, expectedStr] of inputs) {
15+
context(`when the input is ${testName}`, () => {
16+
it(`should return a input string`, () => {
17+
expect(StringUtils.removeLeadingZerosandExplicitPlus(str)).to.equal(expectedStr);
18+
});
19+
});
20+
}
21+
});
22+
23+
describe('validateStringCharacters()', function () {
24+
const successInputs: [testName: string, str: string, radix: number][] = [
25+
['radix does allows given alphabet letter', 'eEe', 15],
26+
['empty string', '', 2],
27+
['all possible hexadecimal characters', '12efabc689873dADCDEF', 16],
28+
['leading zeros', '0000000004567e', 16],
29+
['explicit positive no leading zeros', '+32', 10]
30+
];
31+
32+
const failureInputs = [
33+
['multiple decimal points', '..', 30],
34+
['non a-z or numeric string', '~~', 36],
35+
['alphabet in radix < 10', 'a', 4],
36+
['radix does not allow all alphabet letters', 'eee', 14]
37+
];
38+
39+
for (const [testName, str, radix] of successInputs) {
40+
context(`when the input is ${testName}`, () => {
41+
it(`should return a input string`, () => {
42+
expect(StringUtils.validateStringCharacters(str, radix)).to.equal(str);
43+
});
44+
});
45+
}
46+
47+
for (const [testName, str, radix] of failureInputs) {
48+
context(`when the input is ${testName}`, () => {
49+
it(`should return false`, () => {
50+
expect(StringUtils.validateStringCharacters(str, radix)).to.equal(false);
51+
});
52+
});
53+
}
54+
});

0 commit comments

Comments
 (0)