Skip to content

Commit dfc8017

Browse files
TimothyGuevanlucas
authored andcommitted
url: improve WHATWG URL inspection
PR-URL: #12507 Reviewed-By: James M Snell <[email protected]>
1 parent b2a9e60 commit dfc8017

File tree

2 files changed

+103
-89
lines changed

2 files changed

+103
-89
lines changed

lib/internal/url.js

+54-24
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@ function onParseHashComplete(flags, protocol, username, password,
184184
}
185185
}
186186

187+
function getEligibleConstructor(obj) {
188+
while (obj !== null) {
189+
if (Object.prototype.hasOwnProperty.call(obj, 'constructor') &&
190+
typeof obj.constructor === 'function') {
191+
return obj.constructor;
192+
}
193+
obj = Object.getPrototypeOf(obj);
194+
}
195+
return null;
196+
}
197+
187198
class URL {
188199
constructor(input, base) {
189200
// toUSVString is not needed.
@@ -204,33 +215,43 @@ class URL {
204215
}
205216

206217
[util.inspect.custom](depth, opts) {
218+
if (this == null ||
219+
Object.getPrototypeOf(this[context]) !== URLContext.prototype) {
220+
throw new TypeError('Value of `this` is not a URL');
221+
}
222+
207223
const ctx = this[context];
208-
var ret = 'URL {\n';
209-
ret += ` href: ${this.href}\n`;
210-
if (ctx.scheme !== undefined)
211-
ret += ` protocol: ${this.protocol}\n`;
212-
if (ctx.username !== undefined)
213-
ret += ` username: ${this.username}\n`;
214-
if (ctx.password !== undefined) {
215-
const pwd = opts.showHidden ? ctx.password : '--------';
216-
ret += ` password: ${pwd}\n`;
217-
}
218-
if (ctx.host !== undefined)
219-
ret += ` hostname: ${this.hostname}\n`;
220-
if (ctx.port !== undefined)
221-
ret += ` port: ${this.port}\n`;
222-
if (ctx.path !== undefined)
223-
ret += ` pathname: ${this.pathname}\n`;
224-
if (ctx.query !== undefined)
225-
ret += ` search: ${this.search}\n`;
226-
if (ctx.fragment !== undefined)
227-
ret += ` hash: ${this.hash}\n`;
224+
225+
if (typeof depth === 'number' && depth < 0)
226+
return opts.stylize('[Object]', 'special');
227+
228+
const ctor = getEligibleConstructor(this);
229+
230+
const obj = Object.create({
231+
constructor: ctor === null ? URL : ctor
232+
});
233+
234+
obj.href = this.href;
235+
obj.origin = this.origin;
236+
obj.protocol = this.protocol;
237+
obj.username = this.username;
238+
obj.password = (opts.showHidden || ctx.password == null) ?
239+
this.password : '--------';
240+
obj.host = this.host;
241+
obj.hostname = this.hostname;
242+
obj.port = this.port;
243+
obj.pathname = this.pathname;
244+
obj.search = this.search;
245+
obj.searchParams = this.searchParams;
246+
obj.hash = this.hash;
247+
228248
if (opts.showHidden) {
229-
ret += ` cannot-be-base: ${this[cannotBeBase]}\n`;
230-
ret += ` special: ${this[special]}\n`;
249+
obj.cannotBeBase = this[cannotBeBase];
250+
obj.special = this[special];
251+
obj[context] = this[context];
231252
}
232-
ret += '}';
233-
return ret;
253+
254+
return util.inspect(obj, opts);
234255
}
235256
}
236257

@@ -858,6 +879,9 @@ class URLSearchParams {
858879
throw new TypeError('Value of `this` is not a URLSearchParams');
859880
}
860881

882+
if (typeof recurseTimes === 'number' && recurseTimes < 0)
883+
return ctx.stylize('[Object]', 'special');
884+
861885
const separator = ', ';
862886
const innerOpts = Object.assign({}, ctx);
863887
if (recurseTimes !== null) {
@@ -1211,6 +1235,12 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', {
12111235
};
12121236
},
12131237
[util.inspect.custom](recurseTimes, ctx) {
1238+
if (this == null || this[context] == null || this[context].target == null)
1239+
throw new TypeError('Value of `this` is not a URLSearchParamsIterator');
1240+
1241+
if (typeof recurseTimes === 'number' && recurseTimes < 0)
1242+
return ctx.stylize('[Object]', 'special');
1243+
12141244
const innerOpts = Object.assign({}, ctx);
12151245
if (recurseTimes !== null) {
12161246
innerOpts.depth = recurseTimes - 1;

test/parallel/test-whatwg-url-inspect.js

+49-65
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const common = require('../common');
44
const util = require('util');
55
const URL = require('url').URL;
6-
const path = require('path');
76
const assert = require('assert');
87

98
if (!common.hasIntl) {
@@ -13,71 +12,56 @@ if (!common.hasIntl) {
1312
}
1413

1514
// Tests below are not from WPT.
16-
const tests = require(path.join(common.fixturesDir, 'url-tests'));
17-
const additional_tests = require(
18-
path.join(common.fixturesDir, 'url-tests-additional'));
15+
const url = new URL('https://username:[email protected]:8080/path/name/?que=ry#hash');
1916

20-
const allTests = additional_tests.slice();
21-
for (const test of tests) {
22-
if (test.failure || typeof test === 'string') continue;
23-
allTests.push(test);
24-
}
25-
26-
for (const test of allTests) {
27-
const url = test.url ? new URL(test.url) : new URL(test.input, test.base);
28-
29-
for (const showHidden of [true, false]) {
30-
const res = util.inspect(url, {
31-
showHidden
32-
});
33-
34-
const lines = res.split('\n');
17+
assert.strictEqual(
18+
util.inspect(url),
19+
`URL {
20+
href: 'https://username:[email protected]:8080/path/name/?que=ry#hash',
21+
origin: 'https://host.name:8080',
22+
protocol: 'https:',
23+
username: 'username',
24+
password: '--------',
25+
host: 'host.name:8080',
26+
hostname: 'host.name',
27+
port: '8080',
28+
pathname: '/path/name/',
29+
search: '?que=ry',
30+
searchParams: URLSearchParams { 'que' => 'ry' },
31+
hash: '#hash' }`);
3532

36-
const firstLine = lines[0];
37-
assert.strictEqual(firstLine, 'URL {');
33+
assert.strictEqual(
34+
util.inspect(url, { showHidden: true }),
35+
`URL {
36+
href: 'https://username:[email protected]:8080/path/name/?que=ry#hash',
37+
origin: 'https://host.name:8080',
38+
protocol: 'https:',
39+
username: 'username',
40+
password: 'password',
41+
host: 'host.name:8080',
42+
hostname: 'host.name',
43+
port: '8080',
44+
pathname: '/path/name/',
45+
search: '?que=ry',
46+
searchParams: URLSearchParams { 'que' => 'ry' },
47+
hash: '#hash',
48+
cannotBeBase: false,
49+
special: true,
50+
[Symbol(context)]:\x20
51+
URLContext {
52+
flags: 2032,
53+
scheme: 'https:',
54+
username: 'username',
55+
password: 'password',
56+
host: 'host.name',
57+
port: 8080,
58+
path: [ 'path', 'name', '', [length]: 3 ],
59+
query: 'que=ry',
60+
fragment: 'hash' } }`);
3861

39-
const lastLine = lines[lines.length - 1];
40-
assert.strictEqual(lastLine, '}');
62+
assert.strictEqual(
63+
util.inspect({ a: url }, { depth: 0 }),
64+
'{ a: [Object] }');
4165

42-
const innerLines = lines.slice(1, lines.length - 1);
43-
const keys = new Set();
44-
for (const line of innerLines) {
45-
const i = line.indexOf(': ');
46-
const k = line.slice(0, i).trim();
47-
const v = line.slice(i + 2);
48-
assert.strictEqual(keys.has(k), false, 'duplicate key found: ' + k);
49-
keys.add(k);
50-
51-
const hidden = new Set([
52-
'password',
53-
'cannot-be-base',
54-
'special'
55-
]);
56-
if (showHidden) {
57-
if (!hidden.has(k)) {
58-
assert.strictEqual(v, url[k], k);
59-
continue;
60-
}
61-
62-
if (k === 'password') {
63-
assert.strictEqual(v, url[k], k);
64-
}
65-
if (k === 'cannot-be-base') {
66-
assert.ok(v.match(/^true$|^false$/), k + ' is Boolean');
67-
}
68-
if (k === 'special') {
69-
assert.ok(v.match(/^true$|^false$/), k + ' is Boolean');
70-
}
71-
continue;
72-
}
73-
74-
// showHidden is false
75-
if (k === 'password') {
76-
assert.strictEqual(v, '--------', k);
77-
continue;
78-
}
79-
assert.strictEqual(hidden.has(k), false, 'no hidden keys: ' + k);
80-
assert.strictEqual(v, url[k], k);
81-
}
82-
}
83-
}
66+
class MyURL extends URL {}
67+
assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {'));

0 commit comments

Comments
 (0)