Skip to content

Commit ffe8061

Browse files
refactor: replace the parse-link-header dev dependency
Remove the external `parse-link-header` dependency from the root development dependency and replace its only in-repo usage with a local parser in `_tools/github/get`. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent 74f9832 commit ffe8061

File tree

4 files changed

+306
-2
lines changed

4 files changed

+306
-2
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2026 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var parseURL = require( 'url' ).parse;
24+
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
25+
var trim = require( '@stdlib/string/base/trim' );
26+
var objectKeys = require( '@stdlib/utils/keys' );
27+
28+
29+
// VARIABLES //
30+
31+
var MAX_LENGTH = 2000;
32+
33+
34+
// FUNCTIONS //
35+
36+
/**
37+
* Splits a link header into individual link values.
38+
*
39+
* @private
40+
* @param {string} str - header string
41+
* @returns {StringArray} link values
42+
*/
43+
function splitValues( str ) {
44+
var values;
45+
var quoted;
46+
var angled;
47+
var start;
48+
var i;
49+
50+
values = [];
51+
start = 0;
52+
quoted = false;
53+
angled = false;
54+
55+
for ( i = 0; i < str.length; i++ ) {
56+
if ( str.charCodeAt( i ) === 34 ) {
57+
quoted = !quoted;
58+
} else if ( quoted === false ) {
59+
if ( str.charCodeAt( i ) === 60 ) {
60+
angled = true;
61+
} else if ( str.charCodeAt( i ) === 62 ) {
62+
angled = false;
63+
} else if ( str.charCodeAt( i ) === 44 && angled === false ) {
64+
values.push( str.substring( start, i ) );
65+
start = i + 1;
66+
}
67+
}
68+
}
69+
values.push( str.substring( start ) );
70+
return values;
71+
}
72+
73+
/**
74+
* Removes wrapping double quotes from a string.
75+
*
76+
* @private
77+
* @param {string} str - input string
78+
* @returns {string} unwrapped string
79+
*/
80+
function unwrap( str ) {
81+
if (
82+
str.length >= 2 &&
83+
str.charCodeAt( 0 ) === 34 &&
84+
str.charCodeAt( str.length-1 ) === 34
85+
) {
86+
return str.substring( 1, str.length-1 );
87+
}
88+
return str;
89+
}
90+
91+
/**
92+
* Parses an individual link header value.
93+
*
94+
* @private
95+
* @param {string} str - link value
96+
* @returns {(Object|null)} parsed link or null
97+
*/
98+
function parseValue( str ) {
99+
var rels;
100+
var out;
101+
var url;
102+
var idx;
103+
var q;
104+
var v;
105+
var k;
106+
var i;
107+
108+
str = trim( str );
109+
if ( str.length === 0 || str.charCodeAt( 0 ) !== 60 ) {
110+
return null;
111+
}
112+
idx = str.indexOf( '>' );
113+
if ( idx < 0 ) {
114+
return null;
115+
}
116+
url = str.substring( 1, idx );
117+
out = {
118+
'url': url
119+
};
120+
121+
q = parseURL( url, true ).query;
122+
for ( k in q ) {
123+
if ( hasOwnProp( q, k ) ) {
124+
out[ k ] = q[ k ];
125+
}
126+
}
127+
str = str.substring( idx+1 );
128+
str = str.split( ';' );
129+
130+
for ( i = 0; i < str.length; i++ ) {
131+
v = trim( str[ i ] );
132+
if ( v.length === 0 ) {
133+
continue;
134+
}
135+
idx = v.indexOf( '=' );
136+
if ( idx < 0 ) {
137+
continue;
138+
}
139+
k = trim( v.substring( 0, idx ) );
140+
v = unwrap( trim( v.substring( idx+1 ) ) );
141+
out[ k ] = v;
142+
}
143+
if ( typeof out.rel !== 'string' || out.rel.length === 0 ) {
144+
return null;
145+
}
146+
rels = out.rel.split( /\s+/ );
147+
return {
148+
'rels': rels,
149+
'value': out
150+
};
151+
}
152+
153+
154+
// MAIN //
155+
156+
/**
157+
* Parses a Link header.
158+
*
159+
* @private
160+
* @param {string} header - link header
161+
* @returns {(Object|null)} parsed links or null
162+
*/
163+
function parseLinkHeader( header ) {
164+
var values;
165+
var out;
166+
var obj;
167+
var i;
168+
var j;
169+
170+
if ( typeof header !== 'string' || header.length === 0 ) {
171+
return null;
172+
}
173+
if ( header.length > MAX_LENGTH ) {
174+
return null;
175+
}
176+
values = splitValues( header );
177+
out = {};
178+
for ( i = 0; i < values.length; i++ ) {
179+
obj = parseValue( values[ i ] );
180+
if ( obj === null ) {
181+
continue;
182+
}
183+
for ( j = 0; j < obj.rels.length; j++ ) {
184+
out[ obj.rels[ j ] ] = obj.value;
185+
}
186+
}
187+
if ( objectKeys( out ).length === 0 ) {
188+
return null;
189+
}
190+
return out;
191+
}
192+
193+
194+
// EXPORTS //
195+
196+
module.exports = parseLinkHeader;

lib/node_modules/@stdlib/_tools/github/get/lib/resolve.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
// MODULES //
2222

2323
var logger = require( 'debug' );
24-
var parseHeader = require( 'parse-link-header' );
2524
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
25+
var parseHeader = require( './linkheader.js' );
2626
var request = require( './request.js' );
2727
var flatten = require( './flatten.js' );
2828
var getOptions = require( './options.js' );
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2026 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var tape = require( 'tape' );
24+
var parseLinkHeader = require( './../lib/linkheader.js' );
25+
26+
27+
// TESTS //
28+
29+
tape( 'main export is a function', function test( t ) {
30+
t.ok( true, __filename );
31+
t.strictEqual( typeof parseLinkHeader, 'function', 'main export is a function' );
32+
t.end();
33+
});
34+
35+
tape( 'the function returns `null` if provided an empty header', function test( t ) {
36+
t.strictEqual( parseLinkHeader( '' ), null, 'returns expected value' );
37+
t.end();
38+
});
39+
40+
tape( 'the function returns `null` if provided a malformed header', function test( t ) {
41+
t.strictEqual( parseLinkHeader( 'beep; boop' ), null, 'returns expected value' );
42+
t.end();
43+
});
44+
45+
tape( 'the function returns `null` if provided a header exceeding the maximum length', function test( t ) {
46+
var header;
47+
var tail;
48+
var i;
49+
50+
header = '<https://api.github.com/user/9287/repos?page=2&per_page=1>; rel="next", ';
51+
tail = '';
52+
for ( i = 0; i < 2100; i++ ) {
53+
tail += 'a';
54+
}
55+
header += tail;
56+
57+
t.strictEqual( header.length > 2000, true, 'test fixture exceeds maximum length' );
58+
t.strictEqual( parseLinkHeader( header ), null, 'returns expected value' );
59+
t.end();
60+
});
61+
62+
tape( 'the function parses paginated GitHub link headers', function test( t ) {
63+
var header;
64+
var out;
65+
66+
header = '<https://api.github.com/user/9287/repos?page=2&per_page=1>; rel="next", <https://api.github.com/user/9287/repos?page=3&per_page=1>; rel="last"';
67+
out = parseLinkHeader( header );
68+
69+
t.deepEqual( out, {
70+
'next': {
71+
'url': 'https://api.github.com/user/9287/repos?page=2&per_page=1',
72+
'page': '2',
73+
'per_page': '1',
74+
'rel': 'next'
75+
},
76+
'last': {
77+
'url': 'https://api.github.com/user/9287/repos?page=3&per_page=1',
78+
'page': '3',
79+
'per_page': '1',
80+
'rel': 'last'
81+
}
82+
}, 'returns expected value' );
83+
t.end();
84+
});
85+
86+
tape( 'the function supports multiple relation types for a single link value', function test( t ) {
87+
var header;
88+
var out;
89+
90+
header = '<https://api.github.com/user/9287/repos?page=1>; rel="prev previous"';
91+
out = parseLinkHeader( header );
92+
93+
t.strictEqual( out.prev, out.previous, 'relations reference the same value' );
94+
t.strictEqual( out.prev.page, '1', 'sets query parameter values' );
95+
t.strictEqual( out.prev.rel, 'prev previous', 'retains original relation string' );
96+
t.end();
97+
});
98+
99+
tape( 'the function preserves additional link parameters', function test( t ) {
100+
var header;
101+
var out;
102+
103+
header = '<https://api.github.com/user/9287/repos?page=2>; rel="next"; type="application/json"; title="next, page"';
104+
out = parseLinkHeader( header );
105+
106+
t.strictEqual( out.next.type, 'application/json', 'sets the media type' );
107+
t.strictEqual( out.next.title, 'next, page', 'sets the title' );
108+
t.end();
109+
});

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@
155155
"mathjax-node-sre": "^3.0.0",
156156
"mkdirp": "^0.5.1",
157157
"mustache": "^4.0.0",
158-
"parse-link-header": "^1.0.1",
159158
"plato": "^1.5.0",
160159
"process": "^0.11.10",
161160
"proxyquire": "^2.0.0",

0 commit comments

Comments
 (0)