Skip to content

Commit

Permalink
url: improve WHATWG URL inspection
Browse files Browse the repository at this point in the history
PR-URL: #12507
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
TimothyGu authored and evanlucas committed May 1, 2017
1 parent b2a9e60 commit dfc8017
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 89 deletions.
78 changes: 54 additions & 24 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ function onParseHashComplete(flags, protocol, username, password,
}
}

function getEligibleConstructor(obj) {
while (obj !== null) {
if (Object.prototype.hasOwnProperty.call(obj, 'constructor') &&
typeof obj.constructor === 'function') {
return obj.constructor;
}
obj = Object.getPrototypeOf(obj);
}
return null;
}

class URL {
constructor(input, base) {
// toUSVString is not needed.
Expand All @@ -204,33 +215,43 @@ class URL {
}

[util.inspect.custom](depth, opts) {
if (this == null ||
Object.getPrototypeOf(this[context]) !== URLContext.prototype) {
throw new TypeError('Value of `this` is not a URL');
}

const ctx = this[context];
var ret = 'URL {\n';
ret += ` href: ${this.href}\n`;
if (ctx.scheme !== undefined)
ret += ` protocol: ${this.protocol}\n`;
if (ctx.username !== undefined)
ret += ` username: ${this.username}\n`;
if (ctx.password !== undefined) {
const pwd = opts.showHidden ? ctx.password : '--------';
ret += ` password: ${pwd}\n`;
}
if (ctx.host !== undefined)
ret += ` hostname: ${this.hostname}\n`;
if (ctx.port !== undefined)
ret += ` port: ${this.port}\n`;
if (ctx.path !== undefined)
ret += ` pathname: ${this.pathname}\n`;
if (ctx.query !== undefined)
ret += ` search: ${this.search}\n`;
if (ctx.fragment !== undefined)
ret += ` hash: ${this.hash}\n`;

if (typeof depth === 'number' && depth < 0)
return opts.stylize('[Object]', 'special');

const ctor = getEligibleConstructor(this);

const obj = Object.create({
constructor: ctor === null ? URL : ctor
});

obj.href = this.href;
obj.origin = this.origin;
obj.protocol = this.protocol;
obj.username = this.username;
obj.password = (opts.showHidden || ctx.password == null) ?
this.password : '--------';
obj.host = this.host;
obj.hostname = this.hostname;
obj.port = this.port;
obj.pathname = this.pathname;
obj.search = this.search;
obj.searchParams = this.searchParams;
obj.hash = this.hash;

if (opts.showHidden) {
ret += ` cannot-be-base: ${this[cannotBeBase]}\n`;
ret += ` special: ${this[special]}\n`;
obj.cannotBeBase = this[cannotBeBase];
obj.special = this[special];
obj[context] = this[context];
}
ret += '}';
return ret;

return util.inspect(obj, opts);
}
}

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

if (typeof recurseTimes === 'number' && recurseTimes < 0)
return ctx.stylize('[Object]', 'special');

const separator = ', ';
const innerOpts = Object.assign({}, ctx);
if (recurseTimes !== null) {
Expand Down Expand Up @@ -1211,6 +1235,12 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', {
};
},
[util.inspect.custom](recurseTimes, ctx) {
if (this == null || this[context] == null || this[context].target == null)
throw new TypeError('Value of `this` is not a URLSearchParamsIterator');

if (typeof recurseTimes === 'number' && recurseTimes < 0)
return ctx.stylize('[Object]', 'special');

const innerOpts = Object.assign({}, ctx);
if (recurseTimes !== null) {
innerOpts.depth = recurseTimes - 1;
Expand Down
114 changes: 49 additions & 65 deletions test/parallel/test-whatwg-url-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const common = require('../common');
const util = require('util');
const URL = require('url').URL;
const path = require('path');
const assert = require('assert');

if (!common.hasIntl) {
Expand All @@ -13,71 +12,56 @@ if (!common.hasIntl) {
}

// Tests below are not from WPT.
const tests = require(path.join(common.fixturesDir, 'url-tests'));
const additional_tests = require(
path.join(common.fixturesDir, 'url-tests-additional'));
const url = new URL('https://username:password@host.name:8080/path/name/?que=ry#hash');

const allTests = additional_tests.slice();
for (const test of tests) {
if (test.failure || typeof test === 'string') continue;
allTests.push(test);
}

for (const test of allTests) {
const url = test.url ? new URL(test.url) : new URL(test.input, test.base);

for (const showHidden of [true, false]) {
const res = util.inspect(url, {
showHidden
});

const lines = res.split('\n');
assert.strictEqual(
util.inspect(url),
`URL {
href: 'https://username:password@host.name:8080/path/name/?que=ry#hash',
origin: 'https://host.name:8080',
protocol: 'https:',
username: 'username',
password: '--------',
host: 'host.name:8080',
hostname: 'host.name',
port: '8080',
pathname: '/path/name/',
search: '?que=ry',
searchParams: URLSearchParams { 'que' => 'ry' },
hash: '#hash' }`);

const firstLine = lines[0];
assert.strictEqual(firstLine, 'URL {');
assert.strictEqual(
util.inspect(url, { showHidden: true }),
`URL {
href: 'https://username:password@host.name:8080/path/name/?que=ry#hash',
origin: 'https://host.name:8080',
protocol: 'https:',
username: 'username',
password: 'password',
host: 'host.name:8080',
hostname: 'host.name',
port: '8080',
pathname: '/path/name/',
search: '?que=ry',
searchParams: URLSearchParams { 'que' => 'ry' },
hash: '#hash',
cannotBeBase: false,
special: true,
[Symbol(context)]:\x20
URLContext {
flags: 2032,
scheme: 'https:',
username: 'username',
password: 'password',
host: 'host.name',
port: 8080,
path: [ 'path', 'name', '', [length]: 3 ],
query: 'que=ry',
fragment: 'hash' } }`);

const lastLine = lines[lines.length - 1];
assert.strictEqual(lastLine, '}');
assert.strictEqual(
util.inspect({ a: url }, { depth: 0 }),
'{ a: [Object] }');

const innerLines = lines.slice(1, lines.length - 1);
const keys = new Set();
for (const line of innerLines) {
const i = line.indexOf(': ');
const k = line.slice(0, i).trim();
const v = line.slice(i + 2);
assert.strictEqual(keys.has(k), false, 'duplicate key found: ' + k);
keys.add(k);

const hidden = new Set([
'password',
'cannot-be-base',
'special'
]);
if (showHidden) {
if (!hidden.has(k)) {
assert.strictEqual(v, url[k], k);
continue;
}

if (k === 'password') {
assert.strictEqual(v, url[k], k);
}
if (k === 'cannot-be-base') {
assert.ok(v.match(/^true$|^false$/), k + ' is Boolean');
}
if (k === 'special') {
assert.ok(v.match(/^true$|^false$/), k + ' is Boolean');
}
continue;
}

// showHidden is false
if (k === 'password') {
assert.strictEqual(v, '--------', k);
continue;
}
assert.strictEqual(hidden.has(k), false, 'no hidden keys: ' + k);
assert.strictEqual(v, url[k], k);
}
}
}
class MyURL extends URL {}
assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {'));

0 comments on commit dfc8017

Please sign in to comment.