qs 6.11.0 → 6.11.1

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/.eslintrc CHANGED
@@ -15,7 +15,7 @@
15
15
  "indent": [2, 4],
16
16
  "max-lines-per-function": [2, { "max": 150 }],
17
17
  "max-params": [2, 16],
18
- "max-statements": [2, 53],
18
+ "max-statements": [2, 100],
19
19
  "multiline-comment-style": 0,
20
20
  "no-continue": 1,
21
21
  "no-magic-numbers": 0,
package/CHANGELOG.md CHANGED
@@ -1,4 +1,13 @@
1
- ## **6.11.0
1
+ ## **6.11.1**
2
+ - [Fix] `stringify`: encode comma values more consistently (#463)
3
+ - [readme] add usage of `filter` option for injecting custom serialization, i.e. of custom types (#447)
4
+ - [meta] remove extraneous code backticks (#457)
5
+ - [meta] fix changelog markdown
6
+ - [actions] update checkout action
7
+ - [actions] restrict action permissions
8
+ - [Dev Deps] update `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape`
9
+
10
+ ## **6.11.0**
2
11
  - [New] [Fix] `stringify`: revert 0e903c0; add `commaRoundTrip` option (#442)
3
12
  - [readme] fix version badge
4
13
 
@@ -238,7 +247,7 @@
238
247
 
239
248
  ## **6.5.3**
240
249
  - [Fix] `parse`: ignore `__proto__` keys (#428)
241
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
250
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
242
251
  - [Fix] correctly parse nested arrays
243
252
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
244
253
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
@@ -291,7 +300,7 @@
291
300
  - [Fix] fix for an impossible situation: when the formatter is called with a non-string value
292
301
  - [Fix] use `safer-buffer` instead of `Buffer` constructor
293
302
  - [Fix] `utils.merge`: avoid a crash with a null target and an array source
294
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
303
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
295
304
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
296
305
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
297
306
  - [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
@@ -320,7 +329,7 @@
320
329
  - [Fix] `parse`: ignore `__proto__` keys (#428)
321
330
  - [Fix] fix for an impossible situation: when the formatter is called with a non-string value
322
331
  - [Fix] `utils.merge`: avoid a crash with a null target and an array source
323
- - [Fix]` `utils.merge`: avoid a crash with a null target and a truthy non-array source
332
+ - [Fix] `utils.merge`: avoid a crash with a null target and a truthy non-array source
324
333
  - [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
325
334
  - [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
326
335
  - [Fix] when `parseArrays` is false, properly handle keys ending in `[]`
@@ -407,7 +416,7 @@
407
416
  - [New] add "encoder" and "decoder" options, for custom param encoding/decoding (#160)
408
417
  - [Fix] fix compacting of nested sparse arrays (#150)
409
418
 
410
- ## **6.1.2
419
+ ## **6.1.2**
411
420
  - [Fix] follow `allowPrototypes` option during merge (#201, #200)
412
421
  - [Fix] chmod a-x
413
422
  - [Fix] support keys starting with brackets (#202, #200)
package/README.md CHANGED
@@ -498,6 +498,44 @@ qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
498
498
  // 'a[0]=b&a[2]=d'
499
499
  ```
500
500
 
501
+ You could also use `filter` to inject custom serialization for user defined types. Consider you're working with
502
+ some api that expects query strings of the format for ranges:
503
+
504
+ ```
505
+ https://domain.com/endpoint?range=30...70
506
+ ```
507
+
508
+ For which you model as:
509
+
510
+ ```javascript
511
+ class Range {
512
+ constructor(from, to) {
513
+ this.from = from;
514
+ this.to = to;
515
+ }
516
+ }
517
+ ```
518
+
519
+ You could _inject_ a custom serializer to handle values of this type:
520
+
521
+ ```javascript
522
+ qs.stringify(
523
+ {
524
+ range: new Range(30, 70),
525
+ },
526
+ {
527
+ filter: (prefix, value) => {
528
+ if (value instanceof Range) {
529
+ return `${value.from}...${value.to}`;
530
+ }
531
+ // serialize the usual way
532
+ return value;
533
+ },
534
+ }
535
+ );
536
+ // range=30...70
537
+ ```
538
+
501
539
  ### Handling of `null` values
502
540
 
503
541
  By default, `null` values are treated like empty strings:
package/dist/qs.js CHANGED
@@ -323,7 +323,6 @@ var arrayPrefixGenerators = {
323
323
  };
324
324
 
325
325
  var isArray = Array.isArray;
326
- var split = String.prototype.split;
327
326
  var push = Array.prototype.push;
328
327
  var pushToArray = function (arr, valueOrArray) {
329
328
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -425,14 +424,6 @@ var stringify = function stringify(
425
424
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
426
425
  if (encoder) {
427
426
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
428
- if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
429
- var valuesArray = split.call(String(obj), ',');
430
- var valuesJoined = '';
431
- for (var i = 0; i < valuesArray.length; ++i) {
432
- valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
433
- }
434
- return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined];
435
- }
436
427
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
437
428
  }
438
429
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -447,6 +438,9 @@ var stringify = function stringify(
447
438
  var objKeys;
448
439
  if (generateArrayPrefix === 'comma' && isArray(obj)) {
449
440
  // we need to join elements in
441
+ if (encodeValuesOnly && encoder) {
442
+ obj = utils.maybeMap(obj, encoder);
443
+ }
450
444
  objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
451
445
  } else if (isArray(filter)) {
452
446
  objKeys = filter;
@@ -479,7 +473,7 @@ var stringify = function stringify(
479
473
  commaRoundTrip,
480
474
  strictNullHandling,
481
475
  skipNulls,
482
- encoder,
476
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
483
477
  filter,
484
478
  sort,
485
479
  allowDots,
@@ -1077,6 +1071,8 @@ var INTRINSICS = {
1077
1071
  '%AsyncIteratorPrototype%': needsEval,
1078
1072
  '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics,
1079
1073
  '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt,
1074
+ '%BigInt64Array%': typeof BigInt64Array === 'undefined' ? undefined : BigInt64Array,
1075
+ '%BigUint64Array%': typeof BigUint64Array === 'undefined' ? undefined : BigUint64Array,
1080
1076
  '%Boolean%': Boolean,
1081
1077
  '%DataView%': typeof DataView === 'undefined' ? undefined : DataView,
1082
1078
  '%Date%': Date,
@@ -1132,6 +1128,14 @@ var INTRINSICS = {
1132
1128
  '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet
1133
1129
  };
1134
1130
 
1131
+ try {
1132
+ null.error; // eslint-disable-line no-unused-expressions
1133
+ } catch (e) {
1134
+ // https://github.com/tc39/proposal-shadowrealm/pull/384#issuecomment-1364264229
1135
+ var errorProto = getProto(getProto(e));
1136
+ INTRINSICS['%Error.prototype%'] = errorProto;
1137
+ }
1138
+
1135
1139
  var doEval = function doEval(name) {
1136
1140
  var value;
1137
1141
  if (name === '%AsyncFunction%') {
@@ -1217,6 +1221,7 @@ var $concat = bind.call(Function.call, Array.prototype.concat);
1217
1221
  var $spliceApply = bind.call(Function.apply, Array.prototype.splice);
1218
1222
  var $replace = bind.call(Function.call, String.prototype.replace);
1219
1223
  var $strSlice = bind.call(Function.call, String.prototype.slice);
1224
+ var $exec = bind.call(Function.call, RegExp.prototype.exec);
1220
1225
 
1221
1226
  /* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */
1222
1227
  var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g;
@@ -1272,6 +1277,9 @@ module.exports = function GetIntrinsic(name, allowMissing) {
1272
1277
  throw new $TypeError('"allowMissing" argument must be a boolean');
1273
1278
  }
1274
1279
 
1280
+ if ($exec(/^%?[^%]*%?$/, name) === null) {
1281
+ throw new $SyntaxError('`%` may not be present anywhere but at the beginning and end of the intrinsic name');
1282
+ }
1275
1283
  var parts = stringToPath(name);
1276
1284
  var intrinsicBaseName = parts.length > 0 ? parts[0] : '';
1277
1285
 
@@ -1615,16 +1623,20 @@ module.exports = function inspect_(obj, options, depth, seen) {
1615
1623
  }
1616
1624
  if (isMap(obj)) {
1617
1625
  var mapParts = [];
1618
- mapForEach.call(obj, function (value, key) {
1619
- mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));
1620
- });
1626
+ if (mapForEach) {
1627
+ mapForEach.call(obj, function (value, key) {
1628
+ mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));
1629
+ });
1630
+ }
1621
1631
  return collectionOf('Map', mapSize.call(obj), mapParts, indent);
1622
1632
  }
1623
1633
  if (isSet(obj)) {
1624
1634
  var setParts = [];
1625
- setForEach.call(obj, function (value) {
1626
- setParts.push(inspect(value, obj));
1627
- });
1635
+ if (setForEach) {
1636
+ setForEach.call(obj, function (value) {
1637
+ setParts.push(inspect(value, obj));
1638
+ });
1639
+ }
1628
1640
  return collectionOf('Set', setSize.call(obj), setParts, indent);
1629
1641
  }
1630
1642
  if (isWeakMap(obj)) {
package/lib/stringify.js CHANGED
@@ -19,7 +19,6 @@ var arrayPrefixGenerators = {
19
19
  };
20
20
 
21
21
  var isArray = Array.isArray;
22
- var split = String.prototype.split;
23
22
  var push = Array.prototype.push;
24
23
  var pushToArray = function (arr, valueOrArray) {
25
24
  push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
@@ -121,14 +120,6 @@ var stringify = function stringify(
121
120
  if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
122
121
  if (encoder) {
123
122
  var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
124
- if (generateArrayPrefix === 'comma' && encodeValuesOnly) {
125
- var valuesArray = split.call(String(obj), ',');
126
- var valuesJoined = '';
127
- for (var i = 0; i < valuesArray.length; ++i) {
128
- valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));
129
- }
130
- return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined];
131
- }
132
123
  return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
133
124
  }
134
125
  return [formatter(prefix) + '=' + formatter(String(obj))];
@@ -143,6 +134,9 @@ var stringify = function stringify(
143
134
  var objKeys;
144
135
  if (generateArrayPrefix === 'comma' && isArray(obj)) {
145
136
  // we need to join elements in
137
+ if (encodeValuesOnly && encoder) {
138
+ obj = utils.maybeMap(obj, encoder);
139
+ }
146
140
  objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];
147
141
  } else if (isArray(filter)) {
148
142
  objKeys = filter;
@@ -175,7 +169,7 @@ var stringify = function stringify(
175
169
  commaRoundTrip,
176
170
  strictNullHandling,
177
171
  skipNulls,
178
- encoder,
172
+ generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder,
179
173
  filter,
180
174
  sort,
181
175
  allowDots,
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.11.0",
5
+ "version": "6.11.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -33,8 +33,8 @@
33
33
  "side-channel": "^1.0.4"
34
34
  },
35
35
  "devDependencies": {
36
- "@ljharb/eslint-config": "^21.0.0",
37
- "aud": "^2.0.0",
36
+ "@ljharb/eslint-config": "^21.0.1",
37
+ "aud": "^2.0.2",
38
38
  "browserify": "^16.5.2",
39
39
  "eclint": "^2.8.1",
40
40
  "eslint": "=8.8.0",
@@ -46,11 +46,11 @@
46
46
  "mkdirp": "^0.5.5",
47
47
  "npmignore": "^0.3.0",
48
48
  "nyc": "^10.3.2",
49
- "object-inspect": "^1.12.2",
49
+ "object-inspect": "^1.12.3",
50
50
  "qs-iconv": "^1.0.4",
51
51
  "safe-publish-latest": "^2.0.0",
52
52
  "safer-buffer": "^2.1.2",
53
- "tape": "^5.5.3"
53
+ "tape": "^5.6.3"
54
54
  },
55
55
  "scripts": {
56
56
  "prepack": "npmignore --auto --commentLines=autogenerated",
package/test/stringify.js CHANGED
@@ -160,6 +160,13 @@ test('stringify()', function (t) {
160
160
  s2t.end();
161
161
  });
162
162
 
163
+ st.test('array with multiple items with a comma inside', function (s2t) {
164
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c%2Cd,e');
165
+ s2t.equal(qs.stringify({ a: ['c,d', 'e'] }, { arrayFormat: 'comma' }), 'a=c%2Cd%2Ce');
166
+
167
+ s2t.end();
168
+ });
169
+
163
170
  st.end();
164
171
  });
165
172
 
@@ -171,6 +178,44 @@ test('stringify()', function (t) {
171
178
  st.end();
172
179
  });
173
180
 
181
+ t.test('stringifies comma and empty array values', function (st) {
182
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'indices' }), 'a[0]=,&a[1]=&a[2]=c,d%');
183
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'brackets' }), 'a[]=,&a[]=&a[]=c,d%');
184
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'comma' }), 'a=,,,c,d%');
185
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: false, arrayFormat: 'repeat' }), 'a=,&a=&a=c,d%');
186
+
187
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=%2C&a[1]=&a[2]=c%2Cd%25');
188
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=%2C&a[]=&a[]=c%2Cd%25');
189
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C,,c%2Cd%25');
190
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
191
+
192
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a%5B0%5D=%2C&a%5B1%5D=&a%5B2%5D=c%2Cd%25');
193
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a%5B%5D=%2C&a%5B%5D=&a%5B%5D=c%2Cd%25');
194
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C%2C%2Cc%2Cd%25');
195
+ st.equal(qs.stringify({ a: [',', '', 'c,d%'] }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&a=&a=c%2Cd%25');
196
+
197
+ st.end();
198
+ });
199
+
200
+ t.test('stringifies comma and empty non-array values', function (st) {
201
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'indices' }), 'a=,&b=&c=c,d%');
202
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'brackets' }), 'a=,&b=&c=c,d%');
203
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'comma' }), 'a=,&b=&c=c,d%');
204
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: false, arrayFormat: 'repeat' }), 'a=,&b=&c=c,d%');
205
+
206
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
207
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
208
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
209
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: true, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
210
+
211
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'indices' }), 'a=%2C&b=&c=c%2Cd%25');
212
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'brackets' }), 'a=%2C&b=&c=c%2Cd%25');
213
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'comma' }), 'a=%2C&b=&c=c%2Cd%25');
214
+ st.equal(qs.stringify({ a: ',', b: '', c: 'c,d%' }, { encode: true, encodeValuesOnly: false, arrayFormat: 'repeat' }), 'a=%2C&b=&c=c%2Cd%25');
215
+
216
+ st.end();
217
+ });
218
+
174
219
  t.test('stringifies a nested array value with dots notation', function (st) {
175
220
  st.equal(
176
221
  qs.stringify(