qs 6.10.1 → 6.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.editorconfig CHANGED
@@ -8,6 +8,7 @@ charset = utf-8
8
8
  trim_trailing_whitespace = true
9
9
  insert_final_newline = true
10
10
  max_line_length = 160
11
+ quote_type = single
11
12
 
12
13
  [test/*]
13
14
  max_line_length = off
package/.eslintrc CHANGED
@@ -16,7 +16,6 @@
16
16
  "no-continue": 1,
17
17
  "no-magic-numbers": 0,
18
18
  "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"],
19
- "operator-linebreak": [2, "before"],
20
19
  },
21
20
 
22
21
  "overrides": [
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## **6.10.2**
2
+ - [Fix] `stringify`: actually fix cyclic references (#426)
3
+ - [Fix] `stringify`: avoid encoding arrayformat comma when `encodeValuesOnly = true` (#424)
4
+ - [readme] remove travis badge; add github actions/codecov badges; update URLs
5
+ - [Docs] add note and links for coercing primitive values (#408)
6
+ - [actions] update codecov uploader
7
+ - [actions] update workflows
8
+ - [Tests] clean up stringify tests slightly
9
+ - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `object-inspect`, `safe-publish-latest`, `tape`
10
+
1
11
  ## **6.10.1**
2
12
  - [Fix] `stringify`: avoid exception on repeated object values (#402)
3
13
 
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # qs <sup>[![Version Badge][2]][1]</sup>
2
2
 
3
- [![Build Status][3]][4]
4
- [![dependency status][5]][6]
5
- [![dev dependency status][7]][8]
3
+ [![github actions][actions-image]][actions-url]
4
+ [![coverage][codecov-image]][codecov-url]
5
+ [![dependency status][deps-svg]][deps-url]
6
+ [![dev dependency status][dev-deps-svg]][dev-deps-url]
6
7
  [![License][license-image]][license-url]
7
8
  [![Downloads][downloads-image]][downloads-url]
8
9
 
9
- [![npm badge][11]][1]
10
+ [![npm badge][npm-badge-png]][package-url]
10
11
 
11
12
  A querystring parsing and stringifying library with some added security.
12
13
 
@@ -287,6 +288,17 @@ assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] })
287
288
  ```
288
289
  (_this cannot convert nested objects, such as `a={b:1},{c:d}`_)
289
290
 
291
+ ### Parsing primitive/scalar values (numbers, booleans, null, etc)
292
+
293
+ By default, all values are parsed as strings. This behavior will not change and is explained in [issue #91](https://github.com/ljharb/qs/issues/91).
294
+
295
+ ```javascript
296
+ var primitiveValues = qs.parse('a=15&b=true&c=null');
297
+ assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });
298
+ ```
299
+
300
+ If you wish to auto-convert values which look like numbers, booleans, and other values into their primitive counterparts, you can use the [query-types Express JS middleware](https://github.com/xpepermint/query-types) which will auto-convert all request query parameters.
301
+
290
302
  ### Stringifying
291
303
 
292
304
  [](#preventEval)
@@ -594,18 +606,18 @@ Available as part of the Tidelift Subscription
594
606
 
595
607
  The maintainers of qs and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-qs?utm_source=npm-qs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
596
608
 
597
- [1]: https://npmjs.org/package/qs
598
- [2]: http://versionbadg.es/ljharb/qs.svg
599
- [3]: https://api.travis-ci.org/ljharb/qs.svg
600
- [4]: https://travis-ci.org/ljharb/qs
601
- [5]: https://david-dm.org/ljharb/qs.svg
602
- [6]: https://david-dm.org/ljharb/qs
603
- [7]: https://david-dm.org/ljharb/qs/dev-status.svg
604
- [8]: https://david-dm.org/ljharb/qs?type=dev
605
- [9]: https://ci.testling.com/ljharb/qs.png
606
- [10]: https://ci.testling.com/ljharb/qs
607
- [11]: https://nodei.co/npm/qs.png?downloads=true&stars=true
608
- [license-image]: http://img.shields.io/npm/l/qs.svg
609
+ [package-url]: https://npmjs.org/package/qs
610
+ [npm-version-svg]: https://versionbadg.es/ljharb/qs.svg
611
+ [deps-svg]: https://david-dm.org/ljharb/qs.svg
612
+ [deps-url]: https://david-dm.org/ljharb/qs
613
+ [dev-deps-svg]: https://david-dm.org/ljharb/qs/dev-status.svg
614
+ [dev-deps-url]: https://david-dm.org/ljharb/qs#info=devDependencies
615
+ [npm-badge-png]: https://nodei.co/npm/qs.png?downloads=true&stars=true
616
+ [license-image]: https://img.shields.io/npm/l/qs.svg
609
617
  [license-url]: LICENSE
610
- [downloads-image]: http://img.shields.io/npm/dm/qs.svg
611
- [downloads-url]: http://npm-stat.com/charts.html?package=qs
618
+ [downloads-image]: https://img.shields.io/npm/dm/qs.svg
619
+ [downloads-url]: https://npm-stat.com/charts.html?package=qs
620
+ [codecov-image]: https://codecov.io/gh/ljharb/qs/branch/main/graphs/badge.svg
621
+ [codecov-url]: https://app.codecov.io/gh/ljharb/qs/
622
+ [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/qs
623
+ [actions-url]: https://github.com/ljharb/qs/actions
package/dist/qs.js CHANGED
@@ -323,6 +323,7 @@ var arrayPrefixGenerators = {
323
323
  };
324
324
 
325
325
  var isArray = Array.isArray;
326
+ var split = String.prototype.split;
326
327
  var push = Array.prototype.push;
327
328
  var pushToArray = function (arr, valueOrArray) {
328
329
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -359,6 +360,8 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
359
360
  || typeof v === 'bigint';
360
361
  };
361
362
 
363
+ var sentinel = {};
364
+
362
365
  var stringify = function stringify(
363
366
  object,
364
367
  prefix,
@@ -378,8 +381,23 @@ var stringify = function stringify(
378
381
  ) {
379
382
  var obj = object;
380
383
 
381
- if (sideChannel.has(object)) {
382
- throw new RangeError('Cyclic object value');
384
+ var tmpSc = sideChannel;
385
+ var step = 0;
386
+ var findFlag = false;
387
+ while ((tmpSc = tmpSc.get(sentinel)) !== undefined && !findFlag) {
388
+ // Where object last appeared in the ref tree
389
+ var pos = tmpSc.get(object);
390
+ step += 1;
391
+ if (typeof pos !== 'undefined') {
392
+ if (pos === step) {
393
+ throw new RangeError('Cyclic object value');
394
+ } else {
395
+ findFlag = true; // Break while
396
+ }
397
+ }
398
+ if (typeof tmpSc.get(sentinel) === 'undefined') {
399
+ step = 0;
400
+ }
383
401
  }
384
402
 
385
403
  if (typeof filter === 'function') {
@@ -406,6 +424,14 @@ var stringify = function stringify(
406
424
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
407
425
  if (encoder) {
408
426
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
427
+ if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
428
+ var valuesArray = split.call(String(obj), ',');
429
+ var valuesJoined = '';
430
+ for (var i = 0; i < valuesArray.length; ++i) {
431
+ valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
432
+ }
433
+ return [formatter(keyValue) + '=' + valuesJoined];
434
+ }
409
435
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
410
436
  }
411
437
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -428,8 +454,8 @@ var stringify = function stringify(
428
454
  objKeys = sort ? keys.sort(sort) : keys;
429
455
  }
430
456
 
431
- for (var i = 0; i < objKeys.length; ++i) {
432
- var key = objKeys[i];
457
+ for (var j = 0; j < objKeys.length; ++j) {
458
+ var key = objKeys[j];
433
459
  var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
434
460
 
435
461
  if (skipNulls && value === null) {
@@ -440,7 +466,9 @@ var stringify = function stringify(
440
466
  ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
441
467
  : prefix + (allowDots ? '.' + key : '[' + key + ']');
442
468
 
443
- sideChannel.set(object, true);
469
+ sideChannel.set(object, step);
470
+ var valueSideChannel = getSideChannel();
471
+ valueSideChannel.set(sentinel, sideChannel);
444
472
  pushToArray(values, stringify(
445
473
  value,
446
474
  keyPrefix,
@@ -456,7 +484,7 @@ var stringify = function stringify(
456
484
  formatter,
457
485
  encodeValuesOnly,
458
486
  charset,
459
- sideChannel
487
+ valueSideChannel
460
488
  ));
461
489
  }
462
490
 
@@ -772,6 +800,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
772
800
 
773
801
  i += 1;
774
802
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
803
+ /* eslint operator-linebreak: [2, "before"] */
775
804
  out += hexTable[0xF0 | (c >> 18)]
776
805
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
777
806
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
@@ -1385,17 +1414,29 @@ var hasWeakMap = typeof WeakMap === 'function' && WeakMap.prototype;
1385
1414
  var weakMapHas = hasWeakMap ? WeakMap.prototype.has : null;
1386
1415
  var hasWeakSet = typeof WeakSet === 'function' && WeakSet.prototype;
1387
1416
  var weakSetHas = hasWeakSet ? WeakSet.prototype.has : null;
1417
+ var hasWeakRef = typeof WeakRef === 'function' && WeakRef.prototype;
1418
+ var weakRefDeref = hasWeakRef ? WeakRef.prototype.deref : null;
1388
1419
  var booleanValueOf = Boolean.prototype.valueOf;
1389
1420
  var objectToString = Object.prototype.toString;
1390
1421
  var functionToString = Function.prototype.toString;
1391
1422
  var match = String.prototype.match;
1392
1423
  var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null;
1393
1424
  var gOPS = Object.getOwnPropertySymbols;
1394
- var symToString = typeof Symbol === 'function' ? Symbol.prototype.toString : null;
1425
+ var symToString = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? Symbol.prototype.toString : null;
1426
+ var hasShammedSymbols = typeof Symbol === 'function' && typeof Symbol.iterator === 'object';
1395
1427
  var isEnumerable = Object.prototype.propertyIsEnumerable;
1396
1428
 
1429
+ var gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPrototypeOf) || (
1430
+ [].__proto__ === Array.prototype // eslint-disable-line no-proto
1431
+ ? function (O) {
1432
+ return O.__proto__; // eslint-disable-line no-proto
1433
+ }
1434
+ : null
1435
+ );
1436
+
1397
1437
  var inspectCustom = require('./util.inspect').custom;
1398
1438
  var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null;
1439
+ var toStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag !== 'undefined' ? Symbol.toStringTag : null;
1399
1440
 
1400
1441
  module.exports = function inspect_(obj, options, depth, seen) {
1401
1442
  var opts = options || {};
@@ -1412,8 +1453,8 @@ module.exports = function inspect_(obj, options, depth, seen) {
1412
1453
  throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');
1413
1454
  }
1414
1455
  var customInspect = has(opts, 'customInspect') ? opts.customInspect : true;
1415
- if (typeof customInspect !== 'boolean') {
1416
- throw new TypeError('option "customInspect", if provided, must be `true` or `false`');
1456
+ if (typeof customInspect !== 'boolean' && customInspect !== 'symbol') {
1457
+ throw new TypeError('option "customInspect", if provided, must be `true`, `false`, or `\'symbol\'`');
1417
1458
  }
1418
1459
 
1419
1460
  if (
@@ -1485,8 +1526,8 @@ module.exports = function inspect_(obj, options, depth, seen) {
1485
1526
  return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + keys.join(', ') + ' }' : '');
1486
1527
  }
1487
1528
  if (isSymbol(obj)) {
1488
- var symString = symToString.call(obj);
1489
- return typeof obj === 'object' ? markBoxed(symString) : symString;
1529
+ var symString = hasShammedSymbols ? String(obj).replace(/^(Symbol\(.*\))_[^)]*$/, '$1') : symToString.call(obj);
1530
+ return typeof obj === 'object' && !hasShammedSymbols ? markBoxed(symString) : symString;
1490
1531
  }
1491
1532
  if (isElement(obj)) {
1492
1533
  var s = '<' + String(obj.nodeName).toLowerCase();
@@ -1515,7 +1556,7 @@ module.exports = function inspect_(obj, options, depth, seen) {
1515
1556
  if (typeof obj === 'object' && customInspect) {
1516
1557
  if (inspectSymbol && typeof obj[inspectSymbol] === 'function') {
1517
1558
  return obj[inspectSymbol]();
1518
- } else if (typeof obj.inspect === 'function') {
1559
+ } else if (customInspect !== 'symbol' && typeof obj.inspect === 'function') {
1519
1560
  return obj.inspect();
1520
1561
  }
1521
1562
  }
@@ -1539,6 +1580,9 @@ module.exports = function inspect_(obj, options, depth, seen) {
1539
1580
  if (isWeakSet(obj)) {
1540
1581
  return weakCollectionOf('WeakSet');
1541
1582
  }
1583
+ if (isWeakRef(obj)) {
1584
+ return weakCollectionOf('WeakRef');
1585
+ }
1542
1586
  if (isNumber(obj)) {
1543
1587
  return markBoxed(inspect(Number(obj)));
1544
1588
  }
@@ -1553,11 +1597,16 @@ module.exports = function inspect_(obj, options, depth, seen) {
1553
1597
  }
1554
1598
  if (!isDate(obj) && !isRegExp(obj)) {
1555
1599
  var ys = arrObjKeys(obj, inspect);
1556
- if (ys.length === 0) { return '{}'; }
1600
+ var isPlainObject = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object;
1601
+ var protoTag = obj instanceof Object ? '' : 'null prototype';
1602
+ var stringTag = !isPlainObject && toStringTag && Object(obj) === obj && toStringTag in obj ? toStr(obj).slice(8, -1) : protoTag ? 'Object' : '';
1603
+ var constructorTag = isPlainObject || typeof obj.constructor !== 'function' ? '' : obj.constructor.name ? obj.constructor.name + ' ' : '';
1604
+ var tag = constructorTag + (stringTag || protoTag ? '[' + [].concat(stringTag || [], protoTag || []).join(': ') + '] ' : '');
1605
+ if (ys.length === 0) { return tag + '{}'; }
1557
1606
  if (indent) {
1558
- return '{' + indentedJoin(ys, indent) + '}';
1607
+ return tag + '{' + indentedJoin(ys, indent) + '}';
1559
1608
  }
1560
- return '{ ' + ys.join(', ') + ' }';
1609
+ return tag + '{ ' + ys.join(', ') + ' }';
1561
1610
  }
1562
1611
  return String(obj);
1563
1612
  };
@@ -1571,15 +1620,42 @@ function quote(s) {
1571
1620
  return String(s).replace(/"/g, '&quot;');
1572
1621
  }
1573
1622
 
1574
- function isArray(obj) { return toStr(obj) === '[object Array]'; }
1575
- function isDate(obj) { return toStr(obj) === '[object Date]'; }
1576
- function isRegExp(obj) { return toStr(obj) === '[object RegExp]'; }
1577
- function isError(obj) { return toStr(obj) === '[object Error]'; }
1578
- function isSymbol(obj) { return toStr(obj) === '[object Symbol]'; }
1579
- function isString(obj) { return toStr(obj) === '[object String]'; }
1580
- function isNumber(obj) { return toStr(obj) === '[object Number]'; }
1581
- function isBigInt(obj) { return toStr(obj) === '[object BigInt]'; }
1582
- function isBoolean(obj) { return toStr(obj) === '[object Boolean]'; }
1623
+ function isArray(obj) { return toStr(obj) === '[object Array]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1624
+ function isDate(obj) { return toStr(obj) === '[object Date]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1625
+ function isRegExp(obj) { return toStr(obj) === '[object RegExp]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1626
+ function isError(obj) { return toStr(obj) === '[object Error]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1627
+ function isString(obj) { return toStr(obj) === '[object String]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1628
+ function isNumber(obj) { return toStr(obj) === '[object Number]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1629
+ function isBoolean(obj) { return toStr(obj) === '[object Boolean]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }
1630
+
1631
+ // Symbol and BigInt do have Symbol.toStringTag by spec, so that can't be used to eliminate false positives
1632
+ function isSymbol(obj) {
1633
+ if (hasShammedSymbols) {
1634
+ return obj && typeof obj === 'object' && obj instanceof Symbol;
1635
+ }
1636
+ if (typeof obj === 'symbol') {
1637
+ return true;
1638
+ }
1639
+ if (!obj || typeof obj !== 'object' || !symToString) {
1640
+ return false;
1641
+ }
1642
+ try {
1643
+ symToString.call(obj);
1644
+ return true;
1645
+ } catch (e) {}
1646
+ return false;
1647
+ }
1648
+
1649
+ function isBigInt(obj) {
1650
+ if (!obj || typeof obj !== 'object' || !bigIntValueOf) {
1651
+ return false;
1652
+ }
1653
+ try {
1654
+ bigIntValueOf.call(obj);
1655
+ return true;
1656
+ } catch (e) {}
1657
+ return false;
1658
+ }
1583
1659
 
1584
1660
  var hasOwn = Object.prototype.hasOwnProperty || function (key) { return key in this; };
1585
1661
  function has(obj, key) {
@@ -1637,6 +1713,17 @@ function isWeakMap(x) {
1637
1713
  return false;
1638
1714
  }
1639
1715
 
1716
+ function isWeakRef(x) {
1717
+ if (!weakRefDeref || !x || typeof x !== 'object') {
1718
+ return false;
1719
+ }
1720
+ try {
1721
+ weakRefDeref.call(x);
1722
+ return true;
1723
+ } catch (e) {}
1724
+ return false;
1725
+ }
1726
+
1640
1727
  function isSet(x) {
1641
1728
  if (!setSize || !x || typeof x !== 'object') {
1642
1729
  return false;
@@ -1753,17 +1840,28 @@ function arrObjKeys(obj, inspect) {
1753
1840
  xs[i] = has(obj, i) ? inspect(obj[i], obj) : '';
1754
1841
  }
1755
1842
  }
1843
+ var syms = typeof gOPS === 'function' ? gOPS(obj) : [];
1844
+ var symMap;
1845
+ if (hasShammedSymbols) {
1846
+ symMap = {};
1847
+ for (var k = 0; k < syms.length; k++) {
1848
+ symMap['$' + syms[k]] = syms[k];
1849
+ }
1850
+ }
1851
+
1756
1852
  for (var key in obj) { // eslint-disable-line no-restricted-syntax
1757
1853
  if (!has(obj, key)) { continue; } // eslint-disable-line no-restricted-syntax, no-continue
1758
1854
  if (isArr && String(Number(key)) === key && key < obj.length) { continue; } // eslint-disable-line no-restricted-syntax, no-continue
1759
- if ((/[^\w$]/).test(key)) {
1855
+ if (hasShammedSymbols && symMap['$' + key] instanceof Symbol) {
1856
+ // this is to prevent shammed Symbols, which are stored as strings, from being included in the string key section
1857
+ continue; // eslint-disable-line no-restricted-syntax, no-continue
1858
+ } else if ((/[^\w$]/).test(key)) {
1760
1859
  xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj));
1761
1860
  } else {
1762
1861
  xs.push(key + ': ' + inspect(obj[key], obj));
1763
1862
  }
1764
1863
  }
1765
1864
  if (typeof gOPS === 'function') {
1766
- var syms = gOPS(obj);
1767
1865
  for (var j = 0; j < syms.length; j++) {
1768
1866
  if (isEnumerable.call(obj, syms[j])) {
1769
1867
  xs.push('[' + inspect(syms[j]) + ']: ' + inspect(obj[syms[j]], obj));
package/lib/stringify.js CHANGED
@@ -19,6 +19,7 @@ var arrayPrefixGenerators = {
19
19
  };
20
20
 
21
21
  var isArray = Array.isArray;
22
+ var split = String.prototype.split;
22
23
  var push = Array.prototype.push;
23
24
  var pushToArray = function (arr, valueOrArray) {
24
25
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -55,6 +56,8 @@ var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
55
56
  || typeof v === 'bigint';
56
57
  };
57
58
 
59
+ var sentinel = {};
60
+
58
61
  var stringify = function stringify(
59
62
  object,
60
63
  prefix,
@@ -74,8 +77,23 @@ var stringify = function stringify(
74
77
  ) {
75
78
  var obj = object;
76
79
 
77
- if (sideChannel.has(object)) {
78
- throw new RangeError('Cyclic object value');
80
+ var tmpSc = sideChannel;
81
+ var step = 0;
82
+ var findFlag = false;
83
+ while ((tmpSc = tmpSc.get(sentinel)) !== undefined && !findFlag) {
84
+ // Where object last appeared in the ref tree
85
+ var pos = tmpSc.get(object);
86
+ step += 1;
87
+ if (typeof pos !== 'undefined') {
88
+ if (pos === step) {
89
+ throw new RangeError('Cyclic object value');
90
+ } else {
91
+ findFlag = true; // Break while
92
+ }
93
+ }
94
+ if (typeof tmpSc.get(sentinel) === 'undefined') {
95
+ step = 0;
96
+ }
79
97
  }
80
98
 
81
99
  if (typeof filter === 'function') {
@@ -102,6 +120,14 @@ var stringify = function stringify(
102
120
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
103
121
  if (encoder) {
104
122
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
123
+ if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
124
+ var valuesArray = split.call(String(obj), ',');
125
+ var valuesJoined = '';
126
+ for (var i = 0; i < valuesArray.length; ++i) {
127
+ valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
128
+ }
129
+ return [formatter(keyValue) + '=' + valuesJoined];
130
+ }
105
131
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
106
132
  }
107
133
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -124,8 +150,8 @@ var stringify = function stringify(
124
150
  objKeys = sort ? keys.sort(sort) : keys;
125
151
  }
126
152
 
127
- for (var i = 0; i < objKeys.length; ++i) {
128
- var key = objKeys[i];
153
+ for (var j = 0; j < objKeys.length; ++j) {
154
+ var key = objKeys[j];
129
155
  var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
130
156
 
131
157
  if (skipNulls && value === null) {
@@ -136,8 +162,9 @@ var stringify = function stringify(
136
162
  ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
137
163
  : prefix + (allowDots ? '.' + key : '[' + key + ']');
138
164
 
139
- sideChannel.set(object, true);
165
+ sideChannel.set(object, step);
140
166
  var valueSideChannel = getSideChannel();
167
+ valueSideChannel.set(sentinel, sideChannel);
141
168
  pushToArray(values, stringify(
142
169
  value,
143
170
  keyPrefix,
package/lib/utils.js CHANGED
@@ -177,6 +177,7 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
177
177
 
178
178
  i += 1;
179
179
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
180
+ /* eslint operator-linebreak: [2, "before"] */
180
181
  out += hexTable[0xF0 | (c >> 18)]
181
182
  + hexTable[0x80 | ((c >> 12) & 0x3F)]
182
183
  + hexTable[0x80 | ((c >> 6) & 0x3F)]
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.10.1",
5
+ "version": "6.10.2",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -33,11 +33,11 @@
33
33
  "side-channel": "^1.0.4"
34
34
  },
35
35
  "devDependencies": {
36
- "@ljharb/eslint-config": "^17.5.1",
37
- "aud": "^1.1.4",
36
+ "@ljharb/eslint-config": "^20.0.0",
37
+ "aud": "^1.1.5",
38
38
  "browserify": "^16.5.2",
39
39
  "eclint": "^2.8.1",
40
- "eslint": "^7.22.0",
40
+ "eslint": "^8.4.0",
41
41
  "evalmd": "^0.0.19",
42
42
  "for-each": "^0.3.3",
43
43
  "has-symbols": "^1.0.2",
@@ -45,21 +45,22 @@
45
45
  "in-publish": "^2.0.1",
46
46
  "mkdirp": "^0.5.5",
47
47
  "nyc": "^10.3.2",
48
- "object-inspect": "^1.9.0",
48
+ "object-inspect": "^1.11.0",
49
49
  "qs-iconv": "^1.0.4",
50
- "safe-publish-latest": "^1.1.4",
50
+ "safe-publish-latest": "^2.0.0",
51
51
  "safer-buffer": "^2.1.2",
52
- "tape": "^5.2.2"
52
+ "tape": "^5.3.2"
53
53
  },
54
54
  "scripts": {
55
- "prepublish": "safe-publish-latest && (not-in-publish || npm run dist)",
55
+ "prepublishOnly": "safe-publish-latest && npm run dist",
56
+ "prepublish": "not-in-publish || npm run prepublishOnly",
56
57
  "pretest": "npm run --silent readme && npm run --silent lint",
57
58
  "test": "npm run tests-only",
58
59
  "tests-only": "nyc tape 'test/**/*.js'",
59
60
  "posttest": "aud --production",
60
61
  "readme": "evalmd README.md",
61
62
  "postlint": "eclint check * lib/* test/* !dist/*",
62
- "lint": "eslint lib/*.js test/*.js",
63
+ "lint": "eslint .",
63
64
  "dist": "mkdirp dist && browserify --standalone Qs lib/index.js > dist/qs.js"
64
65
  },
65
66
  "license": "BSD-3-Clause",
package/test/stringify.js CHANGED
@@ -132,10 +132,10 @@ test('stringify()', function (t) {
132
132
  });
133
133
 
134
134
  t.test('stringifies a nested array value', function (st) {
135
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'indices' }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
136
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'brackets' }), 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d');
137
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { arrayFormat: 'comma' }), 'a%5Bb%5D=c%2Cd'); // a[b]=c,d
138
- st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
135
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[b][0]=c&a[b][1]=d');
136
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[b][]=c&a[b][]=d');
137
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[b]=c,d');
138
+ st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { encodeValuesOnly: true }), 'a[b][0]=c&a[b][1]=d');
139
139
  st.end();
140
140
  });
141
141
 
@@ -143,7 +143,7 @@ test('stringify()', function (t) {
143
143
  st.equal(
144
144
  qs.stringify(
145
145
  { a: { b: ['c', 'd'] } },
146
- { allowDots: true, encode: false, arrayFormat: 'indices' }
146
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'indices' }
147
147
  ),
148
148
  'a.b[0]=c&a.b[1]=d',
149
149
  'indices: stringifies with dots + indices'
@@ -151,7 +151,7 @@ test('stringify()', function (t) {
151
151
  st.equal(
152
152
  qs.stringify(
153
153
  { a: { b: ['c', 'd'] } },
154
- { allowDots: true, encode: false, arrayFormat: 'brackets' }
154
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'brackets' }
155
155
  ),
156
156
  'a.b[]=c&a.b[]=d',
157
157
  'brackets: stringifies with dots + brackets'
@@ -159,7 +159,7 @@ test('stringify()', function (t) {
159
159
  st.equal(
160
160
  qs.stringify(
161
161
  { a: { b: ['c', 'd'] } },
162
- { allowDots: true, encode: false, arrayFormat: 'comma' }
162
+ { allowDots: true, encodeValuesOnly: true, arrayFormat: 'comma' }
163
163
  ),
164
164
  'a.b=c,d',
165
165
  'comma: stringifies with dots + comma'
@@ -167,7 +167,7 @@ test('stringify()', function (t) {
167
167
  st.equal(
168
168
  qs.stringify(
169
169
  { a: { b: ['c', 'd'] } },
170
- { allowDots: true, encode: false }
170
+ { allowDots: true, encodeValuesOnly: true }
171
171
  ),
172
172
  'a.b[0]=c&a.b[1]=d',
173
173
  'default: stringifies with dots + indices'
@@ -215,17 +215,23 @@ test('stringify()', function (t) {
215
215
 
216
216
  t.test('stringifies an array with mixed objects and primitives', function (st) {
217
217
  st.equal(
218
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'indices' }),
218
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'indices' }),
219
219
  'a[0][b]=1&a[1]=2&a[2]=3',
220
220
  'indices => indices'
221
221
  );
222
222
  st.equal(
223
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false, arrayFormat: 'brackets' }),
223
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }),
224
224
  'a[][b]=1&a[]=2&a[]=3',
225
225
  'brackets => brackets'
226
226
  );
227
227
  st.equal(
228
- qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }),
228
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true, arrayFormat: 'comma' }),
229
+ '???',
230
+ 'brackets => brackets',
231
+ { skip: 'TODO: figure out what this should do' }
232
+ );
233
+ st.equal(
234
+ qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encodeValuesOnly: true }),
229
235
  'a[0][b]=1&a[1]=2&a[2]=3',
230
236
  'default => indices'
231
237
  );
@@ -448,7 +454,7 @@ test('stringify()', function (t) {
448
454
 
449
455
  st['throws'](
450
456
  function () { qs.stringify({ 'foo[bar]': 'baz', 'foo[baz]': a }); },
451
- RangeError,
457
+ /RangeError: Cyclic object value/,
452
458
  'cyclic values throw'
453
459
  );
454
460
 
@@ -458,10 +464,16 @@ test('stringify()', function (t) {
458
464
  circular.a = circular;
459
465
  st['throws'](
460
466
  function () { qs.stringify(circular); },
461
- RangeError,
467
+ /RangeError: Cyclic object value/,
462
468
  'cyclic values throw'
463
469
  );
464
470
 
471
+ var arr = ['a'];
472
+ st.doesNotThrow(
473
+ function () { qs.stringify({ x: arr, y: arr }); },
474
+ 'non-cyclic values do not throw'
475
+ );
476
+
465
477
  st.end();
466
478
  });
467
479
 
@@ -829,7 +841,12 @@ test('stringify()', function (t) {
829
841
  st.equal(qs.stringify(withArray, { encode: false }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, no arrayFormat');
830
842
  st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'bracket' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, bracket');
831
843
  st.equal(qs.stringify(withArray, { encode: false, arrayFormat: 'indices' }), 'a[b][0][c]=d&a[b][0][e]=f', 'array, indices');
832
- st.equal(qs.stringify(obj, { encode: false, arrayFormat: 'comma' }), '???', 'array, comma (pending issue #378)', { skip: true });
844
+ st.equal(
845
+ qs.stringify(withArray, { encode: false, arrayFormat: 'comma' }),
846
+ '???',
847
+ 'array, comma',
848
+ { skip: 'TODO: figure out what this should do' }
849
+ );
833
850
 
834
851
  st.end();
835
852
  });