-
-
Notifications
You must be signed in to change notification settings - Fork 32.8k
Implement URL.from #28482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement URL.from #28482
Changes from all commits
68de7d4
6d4126c
68924d9
361115d
49a5fa0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,6 +153,45 @@ myURL = new URL('foo:Example.com/', 'https://example.org/'); | |
// foo:Example.com/ | ||
``` | ||
|
||
#### Class Method: URL.from(input) | ||
|
||
* `input` {Object} | ||
* `protocol` {string} any valid protocol. See [`url.protocol`][]. | ||
* `username` {string} any valid username. See [`url.username`][]. | ||
* `password` {string} any valid password. See [`url.password`][]. | ||
* `host` {string} any valid host. See [`url.host`][]. | ||
* `port` {string|number} any valid port. See [`url.port`][]. | ||
* `query` {string} is a string representing the query. | ||
It gets parsed into a valid [`URLSearchParams`][] and used inside of | ||
the [`url.href`][] and [`url.search`][]. | ||
* `path` {string[]} unlike [`url.pathname`][] it is not a string, | ||
but rather an array representing the path. | ||
* `fragment` {string} represents the fragment (part after `#`). | ||
|
||
Creates a new `URL` instance based on the input object. | ||
|
||
```js | ||
const myURL = URL.from({ | ||
protocol: 'http', | ||
username: 'root', | ||
password: '1234', | ||
port: '3000', | ||
host: 'localhost', | ||
path: ['main'], | ||
fragment: 'app', | ||
query: 'el=%3Cdiv%20%2F%3E', | ||
}); | ||
// -> "http://root:1234@localhost:3000/main?el=%3Cdiv%20%2F%3E#app" | ||
``` | ||
|
||
All properties are optional in case you want to build your `URL` object | ||
gradually (note that you have to pass an empty object in this case anyway) | ||
|
||
```js | ||
const myURL = URL.from({}); | ||
// -> ":" | ||
``` | ||
|
||
#### url.hash | ||
|
||
* {string} | ||
|
@@ -1323,6 +1362,12 @@ console.log(myURL.origin); | |
[`url.format()`]: #url_url_format_urlobject | ||
[`url.href`]: #url_url_href | ||
[`url.parse()`]: #url_url_parse_urlstring_parsequerystring_slashesdenotehost | ||
[`url.protocol`]: #url_url_protocol | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: bottom references are usually sorted in ASCII order. |
||
[`url.username`]: #url_url_username | ||
[`url.password`]: #url_url_password | ||
[`url.host`]: #url_url_host | ||
[`url.port`]: #url_url_port | ||
[`url.pathname`]: #url_url_pathname | ||
[`url.search`]: #url_url_search | ||
[`url.toJSON()`]: #url_url_tojson | ||
[`url.toString()`]: #url_url_tostring | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -320,6 +320,65 @@ class URL { | |
onParseError); | ||
} | ||
|
||
static from(input) { | ||
viktor-ku marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!input || typeof input !== 'object') | ||
throw new ERR_INVALID_ARG_TYPE('input', 'Object', input); | ||
|
||
const { | ||
username = '', | ||
password = '', | ||
host = null, | ||
port = null, | ||
query = null, | ||
path = [], | ||
fragment = null, | ||
} = input; | ||
const protocol = `${input.protocol || ''}:`; | ||
|
||
let flags = 0; | ||
|
||
if ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we instead wrote this more like we do in the existing codebase?: if (protocol === 'file:' ||
protocol === 'https:' ||
protocol === 'wss:' ||
protocol === 'http:' ||
protocol === 'ftp:' ||
protocol === 'ws:' ||
protocol === 'gopher:') {
flags |= URL_FLAGS_SPECIAL;
} else if (!host) {
flags |= URL_FLAGS_CANNOT_BE_BASE;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? Where would be such example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example of what it would look like? I provided one above. |
||
protocol === 'file:' || | ||
protocol === 'https:' || | ||
protocol === 'wss:' || | ||
protocol === 'http:' || | ||
protocol === 'ftp:' || | ||
protocol === 'ws:' || | ||
protocol === 'gopher:' | ||
) | ||
flags |= URL_FLAGS_SPECIAL; | ||
else if (!host) | ||
flags |= URL_FLAGS_CANNOT_BE_BASE; | ||
|
||
if (path.length) | ||
flags |= URL_FLAGS_HAS_PATH; | ||
if (host) | ||
flags |= URL_FLAGS_HAS_HOST; | ||
if (query) | ||
flags |= URL_FLAGS_HAS_QUERY; | ||
if (username) | ||
flags |= URL_FLAGS_HAS_USERNAME; | ||
if (password) | ||
flags |= URL_FLAGS_HAS_PASSWORD; | ||
if (fragment) | ||
flags |= URL_FLAGS_HAS_FRAGMENT; | ||
|
||
// TODO: maybe don't need to support this since it was used for | ||
// the host setter hack at src/node_url.cc:1816 | ||
if (port == '-1') // eslint-disable-line eqeqeq | ||
flags |= URL_FLAGS_IS_DEFAULT_SCHEME_PORT; | ||
|
||
const self = Object.create(URL.prototype); | ||
self[context] = new URLContext(); | ||
|
||
onParseComplete.apply(self, [ | ||
flags, protocol, username, password, | ||
host, port, path, query, fragment | ||
]); | ||
|
||
return self; | ||
} | ||
|
||
get [special]() { | ||
return (this[context].flags & URL_FLAGS_SPECIAL) !== 0; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
'use strict'; | ||
|
||
require('../common'); | ||
|
||
const assert = require('assert'); | ||
const { URL } = require('url'); | ||
|
||
function t(expectedUrl, actualConfig) { | ||
viktor-ku marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const url = URL.from(actualConfig); | ||
assert.strictEqual(String(url), expectedUrl); | ||
} | ||
|
||
assert.throws( | ||
() => { URL.from(); }, | ||
{ | ||
name: 'TypeError', | ||
}, | ||
'when argument is ommited altogether' | ||
); | ||
|
||
[ | ||
['undefined', undefined], | ||
['null', null], | ||
['false', false], | ||
['true', true], | ||
['0', 0], | ||
['42', 42], | ||
['NaN', NaN], | ||
['empty string', ''], | ||
['symbol', Symbol()], | ||
['class', class {}], | ||
['function', function() {}], | ||
['string', 'string'], | ||
].forEach(([desc, arg]) => { | ||
assert.throws( | ||
() => { URL.from(arg); }, | ||
{ | ||
name: 'TypeError', | ||
}, | ||
`when ${desc} passed` | ||
); | ||
}); | ||
|
||
t(':', {}); | ||
|
||
t('https://nodejs.org', { | ||
protocol: 'https', | ||
host: 'nodejs.org' | ||
}); | ||
|
||
t('https://[email protected]', { | ||
protocol: 'https', | ||
host: 'site.com', | ||
username: 'root' | ||
}); | ||
|
||
t('https://:[email protected]', { | ||
protocol: 'https', | ||
host: 'site.com', | ||
password: '1234' | ||
}); | ||
|
||
t('https://root:[email protected]', { | ||
protocol: 'https', | ||
host: 'site.com', | ||
username: 'root', | ||
password: '1234' | ||
}); | ||
|
||
t('https://site.com?a=1&b=2', { | ||
protocol: 'https', | ||
host: 'site.com', | ||
query: 'a=1&b=2' | ||
}); | ||
|
||
t('https://site.com/one/two', { | ||
protocol: 'https', | ||
host: 'site.com', | ||
path: ['one', 'two'] | ||
}); | ||
|
||
t('http://localhost:3000', { | ||
protocol: 'http', | ||
host: 'localhost', | ||
port: '3000', | ||
}); | ||
|
||
t('http://localhost#fr', { | ||
protocol: 'http', | ||
host: 'localhost', | ||
fragment: 'fr' | ||
}); | ||
|
||
t('http://root:1234@localhost:3000/main?el=%3Cdiv%20%2F%3E#app', { | ||
protocol: 'http', | ||
username: 'root', | ||
password: '1234', | ||
port: '3000', | ||
host: 'localhost', | ||
path: ['main'], | ||
fragment: 'app', | ||
query: 'el=%3Cdiv%20%2F%3E' | ||
}); | ||
|
||
t('https://nodejs.org', new Proxy({}, { | ||
get(target, p) { | ||
if (p === 'protocol') | ||
return 'https'; | ||
else if (p === 'host') | ||
return 'nodejs.org'; | ||
} | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: