urlsearchparams
Advanced tools
Comparing version 0.0.5 to 0.0.6
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "implementaion of URLSearchParams at WHATWG Living Standard https://url.spec.whatwg.org/", | ||
@@ -8,0 +8,0 @@ "homepage": "https://github.com/Jxck/URLSearchParams", |
149
test/test.js
var URLSearchParams = URLSearchParams || require('../urlsearchparams').URLSearchParams; | ||
var percentEncoder = percentEncoder || require('../urlsearchparams').percentEncoder; | ||
var percentDecoder = percentDecoder || require('../urlsearchparams').percentDecoder; | ||
@@ -9,21 +11,40 @@ // tests | ||
(function TestPercentEncoding() { | ||
[ 'aAzZ09', | ||
'~`!@', | ||
'#$%^&', | ||
'*()_+-=', | ||
'{}|[]\:', | ||
';"<>?,./', | ||
"'", | ||
'あ亞', | ||
'叱𠮟', | ||
'🍻', | ||
'', | ||
'aAzZ09%E3%81%82%F0%A0%AE%9F%E5%8F%B1=' | ||
].forEach(function(expected) { | ||
var actual = percentDecoder(percentEncoder(expected)); | ||
assert(actual, expected); | ||
}); | ||
})(); | ||
(function TestURLSearchPrams() { | ||
(function parse() { | ||
var s = new URLSearchParams(""); | ||
assert(s.toString(), ""); | ||
var s = new URLSearchParams(''); | ||
assert(s.toString(), ''); | ||
var s = new URLSearchParams(null); | ||
assert(s.toString(), ""); | ||
assert(s.toString(), ''); | ||
var q = "a=b&c=d"; | ||
var q = 'a=b&c=d'; | ||
var s = new URLSearchParams(q); | ||
assert(s.toString(), q); | ||
var a = "aAzZ09あ𠮟叱"; | ||
var e = "aAzZ09%E3%81%82%F0%A0%AE%9F%E5%8F%B1="; | ||
var a = 'aAzZ09あ𠮟叱'; | ||
var e = 'aAzZ09%E3%81%82%F0%A0%AE%9F%E5%8F%B1='; | ||
var s = new URLSearchParams(a); | ||
assert(s.toString(), e); | ||
var a = " *-._"; | ||
var e = "+*-._="; | ||
var a = ' *-._'; | ||
var e = '+*-._='; | ||
var s = new URLSearchParams(a); | ||
@@ -33,7 +54,7 @@ assert(s.toString(), e); | ||
var a = "!~'()"; | ||
var e = "%21%7E%27%28%29="; | ||
var e = '%21%7E%27%28%29='; | ||
var s = new URLSearchParams(a); | ||
assert(s.toString(), e); | ||
var q = "a=b&c=d"; | ||
var q = 'a=b&c=d'; | ||
var s = new URLSearchParams(q); | ||
@@ -48,52 +69,52 @@ var ss = new URLSearchParams(s); | ||
var s = new URLSearchParams(); | ||
s.append("a", "b"); | ||
s.append("c", "d"); | ||
assert(s.toString(), "a=b&c=d"); | ||
s.append("a", "b"); | ||
assert(s.toString(), "a=b&c=d&a=b"); | ||
s.append('a', 'b'); | ||
s.append('c', 'd'); | ||
assert(s.toString(), 'a=b&c=d'); | ||
s.append('a', 'b'); | ||
assert(s.toString(), 'a=b&c=d&a=b'); | ||
var s = new URLSearchParams("a=b"); | ||
s.append("a", "b"); | ||
assert(s.toString(), "a=b&a=b"); | ||
var s = new URLSearchParams('a=b'); | ||
s.append('a', 'b'); | ||
assert(s.toString(), 'a=b&a=b'); | ||
// get | ||
var s = new URLSearchParams("a=b"); | ||
assert(s.get("a"), "b"); | ||
var s = new URLSearchParams('a=b'); | ||
assert(s.get('a'), 'b'); | ||
var s = new URLSearchParams("a=b&a=c"); | ||
assert(s.get("a"), "b"); | ||
assert(s.get("b"), null); | ||
s.append("a", "d"); | ||
assert(s.get("a"), "b"); | ||
var s = new URLSearchParams('a=b&a=c'); | ||
assert(s.get('a'), 'b'); | ||
assert(s.get('b'), null); | ||
s.append('a', 'd'); | ||
assert(s.get('a'), 'b'); | ||
// getAll | ||
var s = new URLSearchParams("a=b&b=c&a=c"); | ||
var all = s.getAll("a"); | ||
var s = new URLSearchParams('a=b&b=c&a=c'); | ||
var all = s.getAll('a'); | ||
assert(all.length, 2); | ||
assert(all[0], "b"); | ||
assert(all[1], "c"); | ||
assert(s.getAll("z").length, 0); | ||
assert(all[0], 'b'); | ||
assert(all[1], 'c'); | ||
assert(s.getAll('z').length, 0); | ||
// set | ||
var s = new URLSearchParams("a=b&b=c&a=c"); | ||
s.set("a", "d"); | ||
var all = s.getAll("a"); | ||
var s = new URLSearchParams('a=b&b=c&a=c'); | ||
s.set('a', 'd'); | ||
var all = s.getAll('a'); | ||
assert(all.length, 1); | ||
assert(all[0], "d"); | ||
assert(s.toString(), "a=d&b=c"); | ||
assert(all[0], 'd'); | ||
assert(s.toString(), 'a=d&b=c'); | ||
// delete | ||
var s = new URLSearchParams("a=b&a=c&x=y"); | ||
s.delete("a"); | ||
var all = s.getAll("a"); | ||
var s = new URLSearchParams('a=b&a=c&x=y'); | ||
s.delete('a'); | ||
var all = s.getAll('a'); | ||
assert(all.length, 0); | ||
s.delete("z"); | ||
assert(s.get("x"), "y"); | ||
s.delete('z'); | ||
assert(s.get('x'), 'y'); | ||
// has | ||
var s = new URLSearchParams("a=b&a=c&x=y"); | ||
assert(s.has("a"), true); | ||
assert(s.has("x"), true); | ||
assert(s.has("z"), false); | ||
var s = new URLSearchParams('a=b&a=c&x=y'); | ||
assert(s.has('a'), true); | ||
assert(s.has('x'), true); | ||
assert(s.has('z'), false); | ||
})(); | ||
@@ -103,19 +124,19 @@ | ||
// from https://developer.mozilla.org/ja/docs/Web/API/URLSearchParams | ||
var paramsString = "q=URLUtils.s&topic=api" | ||
var paramsString = 'q=URLUtils.s&topic=api' | ||
var s = new URLSearchParams(paramsString); | ||
assert(s.has("topic"), true); | ||
assert(s.get("topic"), "api"); | ||
assert(s.getAll("topic")[0], "api"); | ||
assert(s.get("foo"), null); // true | ||
assert(s.has('topic'), true); | ||
assert(s.get('topic'), 'api'); | ||
assert(s.getAll('topic')[0], 'api'); | ||
assert(s.get('foo'), null); // true | ||
s.append("topic", "webdev"); | ||
assert(s.toString(), "q=URLUtils.s&topic=api&topic=webdev"); | ||
s.append('topic', 'webdev'); | ||
assert(s.toString(), 'q=URLUtils.s&topic=api&topic=webdev'); | ||
s.delete("topic"); | ||
assert(s.toString(), "q=URLUtils.s"); | ||
s.delete('topic'); | ||
assert(s.toString(), 'q=URLUtils.s'); | ||
})(); | ||
(function argumentsErrorTest() { | ||
var error_message = "Not enough arguments to URLSearchParams" | ||
var error_message = 'Not enough arguments to URLSearchParams' | ||
var s = new URLSearchParams(); | ||
@@ -127,3 +148,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".append."); | ||
assert(err.message, error_message + '.append.'); | ||
} | ||
@@ -134,3 +155,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".append."); | ||
assert(err.message, error_message + '.append.'); | ||
} | ||
@@ -141,3 +162,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".append."); | ||
assert(err.message, error_message + '.append.'); | ||
} | ||
@@ -149,3 +170,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".get."); | ||
assert(err.message, error_message + '.get.'); | ||
} | ||
@@ -157,3 +178,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".getAll."); | ||
assert(err.message, error_message + '.getAll.'); | ||
} | ||
@@ -165,3 +186,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".set."); | ||
assert(err.message, error_message + '.set.'); | ||
} | ||
@@ -172,3 +193,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".set."); | ||
assert(err.message, error_message + '.set.'); | ||
} | ||
@@ -179,3 +200,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".set."); | ||
assert(err.message, error_message + '.set.'); | ||
} | ||
@@ -187,3 +208,3 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".has."); | ||
assert(err.message, error_message + '.has.'); | ||
} | ||
@@ -195,5 +216,5 @@ | ||
} catch(err) { | ||
assert(err.message, error_message + ".delete."); | ||
assert(err.message, error_message + '.delete.'); | ||
} | ||
})(); | ||
})(); |
@@ -9,9 +9,16 @@ /// <reference path="types/webidl.d.ts" /> | ||
} | ||
var encoder = new TextEncoder("utf-8"); | ||
var decoder = new TextDecoder(); | ||
function encode(s) { | ||
return encoder.encode(s); | ||
} | ||
function decode(bytes) { | ||
return decoder.decode(bytes); | ||
} | ||
function copy(obj) { | ||
return JSON.parse(JSON.stringify(obj)); | ||
} | ||
function encode(s, encodeOverride) { | ||
var encoder = new TextEncoder(encodeOverride); | ||
return encoder.encode(s); | ||
} | ||
/** | ||
* TODO: separate percentEncoder to module | ||
*/ | ||
// https://url.spec.whatwg.org/#percent-encode | ||
@@ -21,2 +28,55 @@ function percentEncode(byt) { | ||
} | ||
function percentEncoder(str) { | ||
var encoded = encode(str); | ||
var result = ""; | ||
for (var i = 0; i < encoded.length; i++) { | ||
result += percentEncode(encoded[i]); | ||
} | ||
return result; | ||
} | ||
// TODO: better using []number ? | ||
// https://url.spec.whatwg.org/#percent-decode | ||
function percentDecode(input) { | ||
// step 1 | ||
var output = []; | ||
for (var i = 0; i < input.length; i++) { | ||
var byt = input[i]; | ||
// step 2-1 | ||
if (byt !== 37) { | ||
output.push(byt); | ||
continue; | ||
} | ||
// step 2-2 | ||
if (byt === 37) { | ||
// has more 2 byte | ||
if (i + 2 < input.length) { | ||
var b1 = input[i + 1]; | ||
var b2 = input[i + 2]; | ||
var range1 = (0x30 <= b1 && b1 <= 0x39) || (0x41 <= b1 && b1 <= 0x46) || (0x61 <= b1 && b1 <= 0x66); | ||
var range2 = (0x30 <= b2 && b2 <= 0x39) || (0x41 <= b2 && b2 <= 0x46) || (0x61 <= b2 && b2 <= 0x66); | ||
if (!range1 && !range2) { | ||
output.push(byt); | ||
continue; | ||
} | ||
} | ||
} | ||
// step 2-3 | ||
// step 2-3-1 | ||
var u1 = input[i + 1]; | ||
var u2 = input[i + 2]; | ||
var hex = decode(new Uint8Array([u1, u2])); | ||
var bytePoint = parseInt(hex, 16); | ||
// step 2-3-2 | ||
output.push(bytePoint); | ||
// step 2-3-3 | ||
i = i + 2; | ||
continue; | ||
} | ||
// step 3 | ||
return new Uint8Array(output); | ||
} | ||
function percentDecoder(str) { | ||
var encoded = encode(str); | ||
return decode(percentDecode(encoded)); | ||
} | ||
// https://url.spec.whatwg.org/#concept-urlencoded-parser | ||
@@ -33,29 +93,43 @@ function formURLEncodedParse(input, encodingOverride, useCharset, isIndex) { | ||
// step 3 | ||
var sequences = input.split('&'); | ||
var array = Array.prototype.slice.call(input); | ||
var sequences = []; | ||
while (true) { | ||
var i = array.indexOf(38); // & | ||
if (i < 0) { | ||
sequences.push(array); | ||
break; | ||
} | ||
sequences.push(array.splice(0, i)); | ||
array.shift(); | ||
} | ||
// step 4 | ||
if (isIndex === true) { | ||
var first = sequences[0]; | ||
if (first.indexOf("=") === -1) { | ||
sequences[0] = "=" + first; | ||
if (sequences[0].indexOf(61) === -1) { | ||
sequences[0].unshift(61); | ||
} | ||
} | ||
// step 5 | ||
// step 5, 6 | ||
var pairs = sequences.map(function (bytes) { | ||
if (bytes === "") | ||
// step 6-1 | ||
if (bytes.length === 0) | ||
return; | ||
// step 3 | ||
// step 6-2 | ||
var name, value; | ||
if (bytes.indexOf("=")) { | ||
var b = bytes.split("="); | ||
name = b.shift(); | ||
value = b.join("="); | ||
var i = bytes.indexOf(61); | ||
if (i > 0) { | ||
name = bytes.splice(0, i); | ||
bytes.shift(); | ||
value = bytes; | ||
} | ||
else { | ||
name = bytes; | ||
value = ""; | ||
value = []; | ||
} | ||
// step 4 | ||
var c0x20 = String.fromCharCode(0x20); | ||
name.replace(/\+/g, c0x20); | ||
value.replace(/\+/g, c0x20); | ||
name.map(function (e) { | ||
if (e === 43) { | ||
e = 0x20; | ||
} | ||
return e; | ||
}); | ||
// step 5 | ||
@@ -65,5 +139,5 @@ if (useCharset && name === "_charset_") { | ||
} | ||
// TODO: step 8 parsent decode | ||
name = decodeURIComponent(name); | ||
value = decodeURIComponent(value); | ||
// step 8 parsent decode | ||
name = decode(percentDecode(new Uint8Array(name))); | ||
value = decode(percentDecode(new Uint8Array(value))); | ||
return { name: name, value: value }; | ||
@@ -211,12 +285,2 @@ }); | ||
}; | ||
// shim of byte serializer using encodeURIComponent | ||
// without Encoding API | ||
URLSearchParams.prototype._byteSerialize = function (input) { | ||
input = encodeURIComponent(input); | ||
// revert space to '+' | ||
input = input.replace("%20", "+"); | ||
// replace chars which encodeURIComponent dosen't cover | ||
input = input.replace("!", "%21").replace("~", "%7E").replace("'", "%27").replace("(", "%28").replace(")", "%29"); | ||
return input; | ||
}; | ||
// https://url.spec.whatwg.org/#concept-urlencoded-serializer | ||
@@ -236,15 +300,7 @@ URLSearchParams.prototype.serialize = function (pairs, encodingOverride) { | ||
// step 3-2 | ||
if (TextEncoder !== undefined) { | ||
// using TextEncoder | ||
var encodedName = encode(outputPair.name, encodingOverride); | ||
var encodedValue = encode(outputPair.value, encodingOverride); | ||
// step 3-3 | ||
outputPair.name = _this.byteSerialize(encodedName); | ||
outputPair.value = _this.byteSerialize(encodedValue); | ||
} | ||
else { | ||
// using encodeURIComponents | ||
outputPair.name = _this._byteSerialize(outputPair.name); | ||
outputPair.value = _this._byteSerialize(outputPair.value); | ||
} | ||
var encodedName = encode(outputPair.name); | ||
var encodedValue = encode(outputPair.value); | ||
// step 3-3 | ||
outputPair.name = _this.byteSerialize(encodedName); | ||
outputPair.value = _this.byteSerialize(encodedValue); | ||
// step 3-4 | ||
@@ -266,4 +322,4 @@ if (index !== 0) { | ||
URLSearchParams.prototype.parse = function (input) { | ||
// var encoded = new TextEncoder("utf-8").encode(input); | ||
return formURLEncodedParse(input); | ||
var encoded = new TextEncoder("utf-8").encode(input); | ||
return formURLEncodedParse(encoded); | ||
}; | ||
@@ -285,2 +341,5 @@ // https://url.spec.whatwg.org/#concept-urlsearchparams-update | ||
})(); | ||
// Export | ||
this.percentEncoder = percentEncoder; | ||
this.percentDecoder = percentDecoder; | ||
this.URLSearchParams = URLSearchParams; |
@@ -17,2 +17,13 @@ /// <reference path="types/webidl.d.ts" /> | ||
var encoder = new TextEncoder("utf-8"); | ||
var decoder = new TextDecoder(); | ||
function encode(s: string): Uint8Array { | ||
return encoder.encode(s); | ||
} | ||
function decode(bytes: Uint8Array): string { | ||
return decoder.decode(bytes); | ||
} | ||
function copy<T>(obj: T): T { | ||
@@ -22,6 +33,5 @@ return JSON.parse(JSON.stringify(obj)); | ||
function encode(s: string, encodeOverride: string): Uint8Array { | ||
var encoder = new TextEncoder(encodeOverride); | ||
return encoder.encode(s); | ||
} | ||
/** | ||
* TODO: separate percentEncoder to module | ||
*/ | ||
@@ -33,4 +43,73 @@ // https://url.spec.whatwg.org/#percent-encode | ||
function percentEncoder(str: string): string { | ||
var encoded = encode(str); | ||
var result = ""; | ||
for (var i = 0; i < encoded.length; i ++) { | ||
result += percentEncode(encoded[i]); | ||
} | ||
return result; | ||
} | ||
// TODO: better using []number ? | ||
// https://url.spec.whatwg.org/#percent-decode | ||
function percentDecode(input: Uint8Array): Uint8Array { | ||
// step 1 | ||
var output: number[] = []; | ||
// step 2 | ||
for (var i = 0; i < input.length; i ++) { | ||
var byt = input[i]; | ||
// step 2-1 | ||
if (byt !== 37) { | ||
output.push(byt); | ||
continue; | ||
} | ||
// step 2-2 | ||
if (byt === 37) { | ||
// has more 2 byte | ||
if (i + 2 < input.length) { | ||
var b1 = input[i+1]; | ||
var b2 = input[i+2]; | ||
var range1 = (0x30 <= b1 && b1 <= 0x39) || | ||
(0x41 <= b1 && b1 <= 0x46) || | ||
(0x61 <= b1 && b1 <= 0x66); | ||
var range2 = (0x30 <= b2 && b2 <= 0x39) || | ||
(0x41 <= b2 && b2 <= 0x46) || | ||
(0x61 <= b2 && b2 <= 0x66); | ||
if(!range1 && !range2) { | ||
output.push(byt); | ||
continue; | ||
} | ||
} | ||
} | ||
// step 2-3 | ||
// step 2-3-1 | ||
var u1 = input[i+1]; | ||
var u2 = input[i+2] | ||
var hex = decode(new Uint8Array([u1, u2])) | ||
var bytePoint = parseInt(hex, 16); | ||
// step 2-3-2 | ||
output.push(bytePoint); | ||
// step 2-3-3 | ||
i = i + 2; | ||
continue; | ||
} | ||
// step 3 | ||
return new Uint8Array(output); | ||
} | ||
function percentDecoder(str: string): string { | ||
var encoded = encode(str); | ||
return decode(percentDecode(encoded)); | ||
} | ||
// https://url.spec.whatwg.org/#concept-urlencoded-parser | ||
function formURLEncodedParse(input: USVString, encodingOverride?: string, useCharset?: boolean, isIndex?: boolean): pair[] { | ||
function formURLEncodedParse(input: Uint8Array, encodingOverride?: string, useCharset?: boolean, isIndex?: boolean): pair[] { | ||
// step 1 | ||
@@ -48,31 +127,48 @@ if (encodingOverride === undefined) { | ||
// step 3 | ||
var sequences = input.split('&'); | ||
var array = Array.prototype.slice.call(input); | ||
var sequences = []; | ||
while (true) { | ||
var i = array.indexOf(38); // & | ||
if (i < 0) { | ||
sequences.push(array); | ||
break; | ||
} | ||
sequences.push(array.splice(0, i)); | ||
array.shift(); | ||
} | ||
// step 4 | ||
if(isIndex === true) { | ||
var first= sequences[0]; | ||
if (first.indexOf("=") === -1) { | ||
sequences[0] = "=" + first; | ||
if (sequences[0].indexOf(61) === -1) { // = | ||
sequences[0].unshift(61); | ||
} | ||
} | ||
// step 5 | ||
var pairs: pair[] = sequences.map((bytes: USVString): pair => { | ||
if (bytes === "") return; | ||
// step 5, 6 | ||
var pairs: pair[] = sequences.map((bytes: number[]): pair => { | ||
// step 6-1 | ||
if (bytes.length === 0) return; | ||
// step 3 | ||
var name: USVString, value: USVString; | ||
if (bytes.indexOf("=")) { | ||
var b = bytes.split("="); | ||
name = b.shift(); | ||
value = b.join("="); | ||
} else { | ||
// step 6-2 | ||
var name, value; | ||
var i = bytes.indexOf(61); | ||
if (i > 0) { // = | ||
name = bytes.splice(0, i); | ||
bytes.shift(); | ||
value = bytes; | ||
} | ||
// step 6-3 | ||
else { | ||
name = bytes; | ||
value = ""; | ||
value = []; | ||
} | ||
// step 4 | ||
var c0x20 = String.fromCharCode(0x20); | ||
name.replace(/\+/g, c0x20); | ||
value.replace(/\+/g, c0x20); | ||
name.map((e) => { | ||
if (e === 43) { // + | ||
e = 0x20; | ||
} | ||
return e; | ||
}); | ||
@@ -84,5 +180,5 @@ // step 5 | ||
// TODO: step 8 parsent decode | ||
name = decodeURIComponent(name); | ||
value = decodeURIComponent(value); | ||
// step 8 parsent decode | ||
name = decode(percentDecode(new Uint8Array(name))); | ||
value = decode(percentDecode(new Uint8Array(value))); | ||
@@ -294,20 +390,2 @@ return { name: name, value: value }; | ||
// shim of byte serializer using encodeURIComponent | ||
// without Encoding API | ||
private _byteSerialize(input: string): string { | ||
input = encodeURIComponent(input); | ||
// revert space to '+' | ||
input = input.replace("%20", "+"); | ||
// replace chars which encodeURIComponent dosen't cover | ||
input = input.replace("!", "%21") | ||
.replace("~", "%7E") | ||
.replace("'", "%27") | ||
.replace("(", "%28") | ||
.replace(")", "%29") | ||
return input | ||
} | ||
// https://url.spec.whatwg.org/#concept-urlencoded-serializer | ||
@@ -329,15 +407,8 @@ private serialize(pairs: pair[], encodingOverride?: string): string { | ||
// step 3-2 | ||
if (TextEncoder !== undefined) { | ||
// using TextEncoder | ||
var encodedName = encode(outputPair.name, encodingOverride); | ||
var encodedValue = encode(outputPair.value, encodingOverride); | ||
var encodedName = encode(outputPair.name); | ||
var encodedValue = encode(outputPair.value); | ||
// step 3-3 | ||
outputPair.name = this.byteSerialize(encodedName); | ||
outputPair.value = this.byteSerialize(encodedValue); | ||
} else { | ||
// using encodeURIComponents | ||
outputPair.name = this._byteSerialize(outputPair.name); | ||
outputPair.value = this._byteSerialize(outputPair.value); | ||
} | ||
// step 3-3 | ||
outputPair.name = this.byteSerialize(encodedName); | ||
outputPair.value = this.byteSerialize(encodedValue); | ||
@@ -364,4 +435,4 @@ // step 3-4 | ||
private parse(input: USVString): pair[] { | ||
// var encoded = new TextEncoder("utf-8").encode(input); | ||
return formURLEncodedParse(input); | ||
var encoded = new TextEncoder("utf-8").encode(input); | ||
return formURLEncodedParse(encoded); | ||
} | ||
@@ -386,2 +457,5 @@ | ||
// Export | ||
this.percentEncoder = percentEncoder; | ||
this.percentDecoder = percentDecoder; | ||
this.URLSearchParams = URLSearchParams; |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
29435
915
0