From 2841f478e46c77feb56aa9712f78afb64daba004 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 4 Apr 2017 21:03:14 -0700 Subject: [PATCH] url: improve WHATWG URL inspection PR-URL: https://github.com/nodejs/node/pull/12253 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen --- lib/internal/url.js | 78 +++++++++++----- test/parallel/test-whatwg-url-inspect.js | 114 ++++++++++------------- 2 files changed, 103 insertions(+), 89 deletions(-) diff --git a/lib/internal/url.js b/lib/internal/url.js index 629bbb2ecedd02..771a916d704bac 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -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. @@ -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); } } @@ -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) { @@ -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; diff --git a/test/parallel/test-whatwg-url-inspect.js b/test/parallel/test-whatwg-url-inspect.js index 4afbbc13102905..a8a59b77873f12 100644 --- a/test/parallel/test-whatwg-url-inspect.js +++ b/test/parallel/test-whatwg-url-inspect.js @@ -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) { @@ -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 {'));