bare-url 2.5.1 → 2.5.3

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/index.js CHANGED
@@ -7,6 +7,67 @@ const kind = Symbol.for('bare.url.kind')
7
7
 
8
8
  const isWindows = Bare.platform === 'win32'
9
9
 
10
+ // Scratch buffer that the binding writes the parsed component offsets into. A
11
+ // single shared buffer is reused across every parse.
12
+ //
13
+ // The offsets are copied out into fields on the URL immediately after a
14
+ // successful parse, so nothing observes the buffer across calls.
15
+ const components = new Uint32Array(8)
16
+
17
+ // The value used for a component that is not present in the URL.
18
+ const unset = 0xffffffff
19
+
20
+ // The schemes the parser treats specially. A URL cannot be switched between a
21
+ // special and a non-special scheme, and a backslash only terminates a host for
22
+ // the former.
23
+ const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])
24
+
25
+ // https://url.spec.whatwg.org/#scheme-start-state
26
+ const scheme = /^[a-z][a-z0-9+\-.]*$/
27
+
28
+ // ASCII tab and newline are removed from input before it is parsed rather than
29
+ // percent-encoded like the other C0 controls.
30
+ const whitespaceAll = /[\t\n\r]/g
31
+
32
+ // The characters that terminate a host, and so bound the value the host and
33
+ // hostname setters accept.
34
+ const hostEnd = /[/\\?#]/
35
+ const hostEndOpaque = /[/?#]/
36
+
37
+ // The delimiters that would let a setter's value escape the component it is
38
+ // spliced into. Everything else is left to the reparse, which applies the full
39
+ // percent-encode set for the component. As elsewhere in this package, each set
40
+ // needs two patterns because test() advances a global pattern's lastIndex.
41
+ //
42
+ // Credentials are not run through the parser and so keep their tabs and
43
+ // newlines, percent-encoded, rather than having them stripped.
44
+ const userinfoDelimiter = /[\t\n\r/\\?#@:]/
45
+ const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g
46
+
47
+ const pathDelimiter = /[?#]/
48
+ const pathDelimiterAll = /[?#]/g
49
+
50
+ // A leading or trailing run of C0 control or space in a value that ends up at
51
+ // either end of the href. The parser strips those, but only when parsing a URL
52
+ // as a whole, so a setter has to encode its own.
53
+ const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g
54
+
55
+ const escapes = {
56
+ '\t': '%09',
57
+ '\n': '%0A',
58
+ '\r': '%0D',
59
+ '/': '%2F',
60
+ '\\': '%5C',
61
+ ':': '%3A',
62
+ '?': '%3F',
63
+ '@': '%40',
64
+ '#': '%23'
65
+ }
66
+
67
+ // The characters that pathToFileURL() has to percent-encode itself. A backslash
68
+ // is a path separator on Windows and so is left alone there.
69
+ const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/
70
+
10
71
  class URL {
11
72
  static get [kind]() {
12
73
  return 0 // Compatibility version
@@ -19,11 +80,17 @@ class URL {
19
80
 
20
81
  if (base !== undefined) base = String(base)
21
82
 
22
- this._components = new Uint32Array(8)
83
+ this._href = undefined
84
+ this._schemeEnd = 0
85
+ this._usernameEnd = 0
86
+ this._hostStart = 0
87
+ this._hostEnd = 0
88
+ this._pathStart = 0
89
+ this._queryStart = 0
90
+ this._fragmentStart = 0
91
+ this._params = null
23
92
 
24
93
  this._parse(input, base, opts.throw !== false)
25
-
26
- if (this._href) this._params = new URLSearchParams(this.search, this)
27
94
  }
28
95
 
29
96
  get [kind]() {
@@ -37,25 +104,47 @@ class URL {
37
104
  }
38
105
 
39
106
  set href(value) {
40
- this._update(value)
107
+ // Unlike every other setter, the href setter reports a parse failure rather
108
+ // than leaving the URL untouched.
109
+ this._parse(String(value), null, true)
41
110
 
42
- this._params._parse(this.search)
111
+ if (this._params) this._params._parse(this.search)
43
112
  }
44
113
 
45
114
  // https://url.spec.whatwg.org/#dom-url-protocol
46
115
 
47
116
  get protocol() {
48
- return this._slice(0, this._components[0]) + ':'
117
+ return this._slice(0, this._schemeEnd) + ':'
49
118
  }
50
119
 
51
120
  set protocol(value) {
52
- this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
121
+ value = strip(String(value))
122
+
123
+ const end = value.indexOf(':')
124
+
125
+ if (end !== -1) value = value.slice(0, end)
126
+
127
+ value = value.toLowerCase()
128
+
129
+ if (!scheme.test(value)) return
130
+
131
+ const current = this._slice(0, this._schemeEnd)
132
+
133
+ if (special.has(current) !== special.has(value)) return
134
+
135
+ if (value === 'file' && (this.username || this.password || this.port)) {
136
+ return
137
+ }
138
+
139
+ if (current === 'file' && this._hostStart === this._hostEnd) return
140
+
141
+ this._update(this._replace(value, 0, this._schemeEnd))
53
142
  }
54
143
 
55
144
  // https://url.spec.whatwg.org/#dom-url-username
56
145
 
57
146
  get username() {
58
- return this._slice(this._components[0] + 3 /* :// */, this._components[1])
147
+ return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
59
148
  }
60
149
 
61
150
  set username(value) {
@@ -63,15 +152,17 @@ class URL {
63
152
  return
64
153
  }
65
154
 
66
- if (this.username === '') value += '@'
155
+ value = encodeUserinfo(String(value))
67
156
 
68
- this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
157
+ if (!hasCredentials(this)) value += '@'
158
+
159
+ this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
69
160
  }
70
161
 
71
162
  // https://url.spec.whatwg.org/#dom-url-password
72
163
 
73
164
  get password() {
74
- return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
165
+ return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
75
166
  }
76
167
 
77
168
  set password(value) {
@@ -79,26 +170,22 @@ class URL {
79
170
  return
80
171
  }
81
172
 
82
- let start = this._components[1] + 1 /* : */
83
- let end = this._components[2] - 1 /* @ */
173
+ value = ':' + encodeUserinfo(String(value))
84
174
 
85
- if (this.password === '') {
86
- value = ':' + value
87
- start--
88
- }
175
+ let end = this._hostStart - 1 /* @ */
89
176
 
90
- if (this.username === '') {
177
+ if (!hasCredentials(this)) {
91
178
  value += '@'
92
- end++
179
+ end = this._usernameEnd
93
180
  }
94
181
 
95
- this._update(this._replace(value, start, end))
182
+ this._update(this._replace(value, this._usernameEnd, end))
96
183
  }
97
184
 
98
185
  // https://url.spec.whatwg.org/#dom-url-host
99
186
 
100
187
  get host() {
101
- return this._slice(this._components[2], this._components[5])
188
+ return this._slice(this._hostStart, this._pathStart)
102
189
  }
103
190
 
104
191
  set host(value) {
@@ -106,15 +193,44 @@ class URL {
106
193
  return
107
194
  }
108
195
 
109
- this._update(
110
- this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
111
- )
196
+ const protocol = this._slice(0, this._schemeEnd)
197
+
198
+ value = truncateHost(protocol, String(value))
199
+
200
+ // An `@` would make the reparse read the value as credentials rather than
201
+ // as a host, so it is rejected outright.
202
+ if (value.includes('@')) return
203
+
204
+ const separator = portSeparator(value)
205
+
206
+ let end = this._hostEnd
207
+
208
+ // A port in the value is parsed separately so that an invalid one leaves
209
+ // the existing port in place rather than rejecting the host along with it.
210
+ if (separator !== -1) {
211
+ // A file URL cannot have a port, so a value carrying one is rejected
212
+ // rather than split.
213
+ if (protocol === 'file') return
214
+
215
+ const port = parsePort(value.slice(separator + 1))
216
+
217
+ value = value.slice(0, separator)
218
+
219
+ if (port !== null) {
220
+ value += port
221
+ end = this._pathStart
222
+ }
223
+ }
224
+
225
+ if (value === '' && cannotHaveEmptyHost(protocol)) return
226
+
227
+ this._update(this._replace(value, this._hostStart, end))
112
228
  }
113
229
 
114
230
  // https://url.spec.whatwg.org/#dom-url-hostname
115
231
 
116
232
  get hostname() {
117
- return this._slice(this._components[2], this._components[3])
233
+ return this._slice(this._hostStart, this._hostEnd)
118
234
  }
119
235
 
120
236
  set hostname(value) {
@@ -122,13 +238,24 @@ class URL {
122
238
  return
123
239
  }
124
240
 
125
- this._update(this._replace(value, this._components[2], this._components[3]))
241
+ const protocol = this._slice(0, this._schemeEnd)
242
+
243
+ value = truncateHost(protocol, String(value))
244
+
245
+ // A port cannot be set through this setter, and a value that carries one is
246
+ // rejected outright rather than truncated. An `@` would make the reparse
247
+ // read the value as credentials rather than as a host.
248
+ if (value.includes('@') || portSeparator(value) !== -1) return
249
+
250
+ if (value === '' && cannotHaveEmptyHost(protocol)) return
251
+
252
+ this._update(this._replace(value, this._hostStart, this._hostEnd))
126
253
  }
127
254
 
128
255
  // https://url.spec.whatwg.org/#dom-url-port
129
256
 
130
257
  get port() {
131
- return this._slice(this._components[3] + 1 /* : */, this._components[5])
258
+ return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
132
259
  }
133
260
 
134
261
  set port(value) {
@@ -136,20 +263,21 @@ class URL {
136
263
  return
137
264
  }
138
265
 
139
- let start = this._components[3] + 1 /* : */
266
+ value = strip(String(value))
267
+
268
+ if (value !== '') {
269
+ value = parsePort(value)
140
270
 
141
- if (this.port === '') {
142
- value = ':' + value
143
- start--
271
+ if (value === null) return
144
272
  }
145
273
 
146
- this._update(this._replace(value, start, this._components[5]))
274
+ this._update(this._replace(value, this._hostEnd, this._pathStart))
147
275
  }
148
276
 
149
277
  // https://url.spec.whatwg.org/#dom-url-pathname
150
278
 
151
279
  get pathname() {
152
- return this._slice(this._components[5], this._components[6] - 1 /* ? */)
280
+ return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
153
281
  }
154
282
 
155
283
  set pathname(value) {
@@ -157,45 +285,65 @@ class URL {
157
285
  return
158
286
  }
159
287
 
160
- if (value[0] !== '/' && value[0] !== '\\') {
288
+ value = encodePath(encodeEdges(String(value)))
289
+
290
+ // An empty path is left alone, as only a special scheme is required to have
291
+ // one and the reparse inserts it there.
292
+ if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
161
293
  value = '/' + value
162
294
  }
163
295
 
164
- this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
296
+ this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
165
297
  }
166
298
 
167
299
  // https://url.spec.whatwg.org/#dom-url-search
168
300
 
169
301
  get search() {
170
- return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
302
+ return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
171
303
  }
172
304
 
173
305
  set search(value) {
174
- if (value && value[0] !== '?') value = '?' + value
306
+ value = String(value)
307
+
308
+ if (value !== '') {
309
+ if (value[0] === '?') value = value.slice(1)
310
+
311
+ value = '?' + encodeQuery(encodeEdges(value))
312
+ }
175
313
 
176
314
  this._update(
177
- this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
315
+ this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
178
316
  )
179
-
180
- this._params._parse(this.search)
181
317
  }
182
318
 
183
319
  // https://url.spec.whatwg.org/#dom-url-searchparams
184
320
 
185
321
  get searchParams() {
322
+ if (this._params === null) {
323
+ this._params = new URLSearchParams(this.search, this)
324
+ }
325
+
186
326
  return this._params
187
327
  }
188
328
 
189
329
  // https://url.spec.whatwg.org/#dom-url-hash
190
330
 
191
331
  get hash() {
192
- return this._slice(this._components[7] - 1 /* # */)
332
+ return this._slice(this._fragmentStart - 1 /* # */)
193
333
  }
194
334
 
195
335
  set hash(value) {
196
- if (value && value[0] !== '#') value = '#' + value
336
+ value = String(value)
337
+
338
+ // The fragment runs to the end of the URL, so nothing in it can escape into
339
+ // another component and no delimiter needs encoding here.
340
+ if (value !== '') {
341
+ if (value[0] === '#') value = value.slice(1)
342
+
343
+ value = '#' + encodeEdges(value)
344
+ }
197
345
 
198
- this._update(this._replace(value, this._components[7] - 1 /* # */))
346
+ this._update(this._replace(value, this._fragmentStart - 1 /* # */))
199
347
  }
200
348
 
201
349
  toString() {
@@ -233,18 +381,32 @@ class URL {
233
381
  }
234
382
 
235
383
  _parse(input, base, shouldThrow) {
384
+ let href
385
+
236
386
  try {
237
- this._href = binding.parse(
238
- String(input),
239
- base ? String(base) : null,
240
- this._components,
241
- shouldThrow
242
- )
387
+ href = binding.parse(input, base || null, components, shouldThrow)
243
388
  } catch (err) {
244
- if (err instanceof TypeError) throw err
389
+ if (err instanceof TypeError || err.code !== undefined) throw err
245
390
 
246
391
  throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
247
392
  }
393
+
394
+ if (href === undefined) return
395
+
396
+ this._href = href
397
+ this._schemeEnd = components[0]
398
+ this._usernameEnd = components[1]
399
+ this._hostStart = components[2]
400
+ this._hostEnd = components[3]
401
+ this._pathStart = components[5]
402
+
403
+ const queryStart = components[6]
404
+ const fragmentStart = components[7]
405
+
406
+ const end = href.length + 1
407
+
408
+ this._queryStart = queryStart === unset ? end : queryStart
409
+ this._fragmentStart = fragmentStart === unset ? end : fragmentStart
248
410
  }
249
411
 
250
412
  _update(input) {
@@ -252,7 +414,11 @@ class URL {
252
414
  this._parse(input, null, true)
253
415
  } catch (err) {
254
416
  if (err instanceof TypeError) throw err
417
+
418
+ return
255
419
  }
420
+
421
+ if (this._params) this._params._parse(this.search)
256
422
  }
257
423
  }
258
424
 
@@ -268,6 +434,93 @@ function cannotHaveCredentialsOrPort(url) {
268
434
  return url.hostname === '' || url.protocol === 'file:'
269
435
  }
270
436
 
437
+ // Whether the URL carries a userinfo section, and so an `@` before its host.
438
+ function hasCredentials(url) {
439
+ return url._hostStart !== url._usernameEnd
440
+ }
441
+
442
+ // Almost no input contains any of these, so it is worth ruling all three out
443
+ // before rewriting anything.
444
+ function strip(value) {
445
+ if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
446
+ return value
447
+ }
448
+
449
+ return value.replace(whitespaceAll, '')
450
+ }
451
+
452
+ // https://url.spec.whatwg.org/#host-state
453
+ //
454
+ // Host parsing stops at the first character that starts another component, so
455
+ // anything from there on is dropped rather than spliced into the host.
456
+ function truncateHost(protocol, value) {
457
+ const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)
458
+
459
+ return end === -1 ? value : value.slice(0, end)
460
+ }
461
+
462
+ // Only a special scheme other than file has to have a host.
463
+ function cannotHaveEmptyHost(protocol) {
464
+ return protocol !== 'file' && special.has(protocol)
465
+ }
466
+
467
+ // The index of the colon that separates a host from its port, disregarding the
468
+ // colons of an IPv6 address, or -1 if the host carries no port.
469
+ function portSeparator(host) {
470
+ return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
471
+ }
472
+
473
+ // https://url.spec.whatwg.org/#port-state
474
+ //
475
+ // Parsing stops at the first character that is not a digit, so a value with no
476
+ // leading digits, or one that overflows, leaves the port as it was.
477
+ function parsePort(value) {
478
+ value = /^\d*/.exec(value)[0]
479
+
480
+ if (value === '' || Number(value) > 65535) return null
481
+
482
+ return ':' + value
483
+ }
484
+
485
+ function encodeUserinfo(value) {
486
+ if (!userinfoDelimiter.test(value)) return value
487
+
488
+ return value.replace(userinfoDelimiterAll, (match) => escapes[match])
489
+ }
490
+
491
+ function encodePath(value) {
492
+ if (!pathDelimiter.test(value)) return value
493
+
494
+ return value.replace(pathDelimiterAll, (match) => escapes[match])
495
+ }
496
+
497
+ function encodeQuery(value) {
498
+ if (value.indexOf('#') === -1) return value
499
+
500
+ return value.replaceAll('#', '%23')
501
+ }
502
+
503
+ // Percent-encodes a leading or trailing run of C0 control or space, which the
504
+ // parser would otherwise strip from a value that lands at either end of the
505
+ // href. Every component that can end a URL encodes them anyway, so this only
506
+ // brings the encoding forward.
507
+ function encodeEdges(value) {
508
+ const len = value.length
509
+
510
+ if (len === 0) return value
511
+ if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value
512
+
513
+ return value.replace(edges, (match) => {
514
+ let encoded = ''
515
+
516
+ for (let i = 0, n = match.length; i < n; i++) {
517
+ encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
518
+ }
519
+
520
+ return encoded
521
+ })
522
+ }
523
+
271
524
  exports.URL = URL
272
525
  exports.URLSearchParams = URLSearchParams
273
526
 
@@ -301,27 +554,35 @@ exports.fileURLToPath = function fileURLToPath(url) {
301
554
  throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
302
555
  }
303
556
 
304
- if (isWindows) {
305
- if (/%2f|%5c/i.test(url.pathname)) {
306
- throw errors.INVALID_FILE_URL_PATH(
307
- 'The file: URL path must not include encoded \\ or / characters'
308
- )
309
- }
310
- } else {
311
- if (url.hostname) {
312
- throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
313
- }
557
+ if (!isWindows && url.hostname) {
558
+ throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
559
+ }
560
+
561
+ const encoded = url.pathname
314
562
 
315
- if (/%2f/i.test(url.pathname)) {
563
+ // Every check below looks for a percent encoded sequence, as does the decoding
564
+ // that follows, so a path without any can skip all of them.
565
+ const hasEncoded = encoded.includes('%')
566
+
567
+ if (hasEncoded) {
568
+ if (isWindows) {
569
+ if (/%2f|%5c/i.test(encoded)) {
570
+ throw errors.INVALID_FILE_URL_PATH(
571
+ 'The file: URL path must not include encoded \\ or / characters'
572
+ )
573
+ }
574
+ } else if (/%2f/i.test(encoded)) {
316
575
  throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
317
576
  }
318
- }
319
577
 
320
- if (/%00/i.test(url.pathname)) {
321
- throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
578
+ if (/%00/i.test(encoded)) {
579
+ throw errors.INVALID_FILE_URL_PATH(
580
+ 'The file: URL path must not include encoded NUL characters'
581
+ )
582
+ }
322
583
  }
323
584
 
324
- const pathname = path.normalize(decodeURIComponent(url.pathname))
585
+ const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)
325
586
 
326
587
  if (isWindows) {
327
588
  if (url.hostname) return '\\\\' + url.hostname + pathname
@@ -347,16 +608,20 @@ exports.pathToFileURL = function pathToFileURL(pathname) {
347
608
  resolved += '\\'
348
609
  }
349
610
 
350
- resolved = resolved
351
- .replaceAll('%', '%25') // Must be first
352
- .replaceAll('#', '%23')
353
- .replaceAll('?', '%3f')
354
- .replaceAll('\n', '%0a')
355
- .replaceAll('\r', '%0d')
356
- .replaceAll('\t', '%09')
611
+ // Paths hardly ever contain any of these, so one pass to rule them out is
612
+ // cheaper than the six or seven replacements it stands in for.
613
+ if (reserved.test(resolved)) {
614
+ resolved = resolved
615
+ .replaceAll('%', '%25') // Must be first
616
+ .replaceAll('#', '%23')
617
+ .replaceAll('?', '%3f')
618
+ .replaceAll('\n', '%0a')
619
+ .replaceAll('\r', '%0d')
620
+ .replaceAll('\t', '%09')
357
621
 
358
- if (!isWindows) {
359
- resolved = resolved.replaceAll('\\', '%5c')
622
+ if (!isWindows) {
623
+ resolved = resolved.replaceAll('\\', '%5c')
624
+ }
360
625
  }
361
626
 
362
627
  return new URL('file:' + resolved)
@@ -1,22 +1,65 @@
1
1
  interface URLSearchParams extends Iterable<[name: string, value: string]> {
2
+ /** The total number of search parameters. */
2
3
  readonly size: number
3
4
 
5
+ /**
6
+ * Append a new `name`/`value` pair.
7
+ * @param name - The parameter name.
8
+ * @param value - The parameter value.
9
+ */
4
10
  append(name: string, value: string): void
11
+ /**
12
+ * Remove all pairs with `name`. If `value` is provided, only pairs with both the matching `name`
13
+ * and `value` are removed.
14
+ * @param name - The parameter name to remove.
15
+ * @param value - If provided, only pairs also matching this value are removed.
16
+ */
5
17
  delete(name: string, value?: string): void
18
+ /**
19
+ * Return the first value for `name`, or `null` if not present.
20
+ * @param name - The parameter name to look up.
21
+ */
6
22
  get(name: string): string | undefined
23
+ /**
24
+ * Return all values for `name` as an array.
25
+ * @param name - The parameter name to look up.
26
+ */
7
27
  getAll(name: string): string[]
28
+ /**
29
+ * Return `true` if a pair with `name` exists. If `value` is provided, the pair must also match
30
+ * `value`.
31
+ * @param name - The parameter name to check.
32
+ * @param value - If provided, the pair must also match this value.
33
+ */
8
34
  has(name: string, value?: string): boolean
35
+ /**
36
+ * Set the value for `name`, replacing any existing pairs with that name.
37
+ * @param name - The parameter name.
38
+ * @param value - The value to set.
39
+ */
9
40
  set(name: string, value: string): void
10
41
 
42
+ /** Returns the serialized string form. */
11
43
  toString(): string
44
+ /** Returns the serialized string form. Suitable for JSON serialization. */
12
45
  toJSON(): string
13
46
  }
14
47
 
15
48
  declare class URLSearchParams {
49
+ /**
50
+ * Create a new `URLSearchParams` instance. `init` may be a query string, an iterable of `[name,
51
+ * value]` pairs, or an object of key-value pairs.
52
+ * @param init - A query string, an iterable of `[name, value]` pairs, or an object of key-value
53
+ * pairs to initialize the params from.
54
+ */
16
55
  constructor(init: string | Record<string, string> | Iterable<[string, string]>)
17
56
  }
18
57
 
19
58
  declare namespace URLSearchParams {
59
+ /**
60
+ * Return `true` if `value` is a `URLSearchParams` instance.
61
+ * @param value - The value to test.
62
+ */
20
63
  export function isURLSearchParams(value: unknown): value is URLSearchParams
21
64
  }
22
65