Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions doc/api/path.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,38 @@ path.format({
// Returns: 'C:\\path\\dir\\file.txt'
```

## `path.fromURL(pathOrUrl)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the name misleading, given that the input is not necessarily a URL? In fact, URL strings of the form file:///home are ignored and not treated as URLs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would you consider to be a better name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about url.toPathIfFileURL?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe path.fromURLInstance?


<!-- YAML
added: REPLACEME
-->

* `pathOrUrl` {string|URL}.
* Returns: {string}

Converts a [`URL`][] instance to a path string,
returns `pathOrUrl` if it is already a string.

A [`TypeError`][] is thrown if `pathOrUrl` is a
[`URL`][] with a schema other than `file://`.

```js
path.fromURL(new URL('file:///Users/node/dev'));
// Returns: '/Users/node/dev'

path.fromURL('file:///Users/node/dev');
// Returns: 'file:///Users/node/dev'

path.fromURL(new URL('file:///c:/foo.txt'));
// Returns On Windows: 'c:\\foo.txt'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still unsure if I like the fact that fromURL(URL) normalizes the path in an OS-specific manner while fromURL(string) does not. I would assume that

path.fromURL(url.pathToFileURL(absolutePath)) === path.fromURL(absolutePath)

but that does not seem to be true in all cases.

On the other hand, this seems to be what @sindresorhus expects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this makes more sence:
path.fromURL('file:///Users/node/dev') === '/Users/node/dev' and not 'file:///Users/node/dev'
@sindresorhus WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you decide if the user refers to a file: URL or to a relative path inside a directory named file:?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this makes more sence:
path.fromURL('file:///Users/node/dev') === '/Users/node/dev' and not 'file:///Users/node/dev'

I think that's orthogonal to my concern regarding OS-specific path normalization, and not what this comment is about.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am actually less concerned about that since you can be explicit and use path.win32/path.posix, like we do in our tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, that's also not what my comment is about. My concern is the fact that fromURL appears to sometimes -- but not always -- normalize the path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tniessen do you think we should change the behavior even if the name of the method is changed to one of the names suggested here (toPathIfFileURL/fromURLInstance)?


path.fromURL('index/path');
// Returns: 'index/path'

path.fromURL(new URL('http://example.com'));
// Throws 'TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file'
```

## `path.isAbsolute(path)`

<!-- YAML
Expand Down Expand Up @@ -604,6 +636,7 @@ The API is accessible via `require('node:path').win32` or `require('node:path/wi

[MSDN-Rel-Path]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#fully-qualified-vs-relative-paths
[`TypeError`]: errors.md#class-typeerror
[`URL`]: url.md#the-whatwg-url-api
[`path.parse()`]: #pathparsepath
[`path.posix`]: #pathposix
[`path.sep`]: #pathsep
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,8 @@ module.exports = {
toUSVString,
fileURLToPath,
pathToFileURL,
getPathFromURLPosix,
getPathFromURLWin32,
toPathIfFileURL,
isURLInstance,
URL,
Expand Down
42 changes: 42 additions & 0 deletions lib/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ const {
validateString,
} = require('internal/validators');

const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_URL_SCHEME,
},
} = require('internal/errors');


let urlModule;
function lazyURLModule() {
// Lazy require to avoid circular dependency
urlModule ??= require('internal/url');
return urlModule;
}

const platformIsWin32 = (process.platform === 'win32');

function isPathSeparator(code) {
Expand Down Expand Up @@ -1061,6 +1076,19 @@ const win32 = {
return ret;
},

fromURL(fileURLOrPath) {
if (typeof fileURLOrPath === 'string') {
return fileURLOrPath;
}
if (!lazyURLModule().isURLInstance(fileURLOrPath)) {
throw new ERR_INVALID_ARG_TYPE('fileURLOrPath', ['string', 'URL'], fileURLOrPath);
}
if (fileURLOrPath.protocol !== 'file:')
throw new ERR_INVALID_URL_SCHEME('file');

return lazyURLModule().getPathFromURLWin32(fileURLOrPath);
},

sep: '\\',
delimiter: ';',
win32: null,
Expand Down Expand Up @@ -1527,12 +1555,26 @@ const posix = {
return ret;
},

fromURL(fileURLOrPath) {
if (typeof fileURLOrPath === 'string') {
return fileURLOrPath;
}
if (!lazyURLModule().isURLInstance(fileURLOrPath)) {
throw new ERR_INVALID_ARG_TYPE('fileURLOrPath', ['string', 'URL'], fileURLOrPath);
}
if (fileURLOrPath.protocol !== 'file:')
throw new ERR_INVALID_URL_SCHEME('file');

return lazyURLModule().getPathFromURLPosix(fileURLOrPath);
},

sep: '/',
delimiter: ':',
win32: null,
posix: null
};


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unrelated change.

posix.win32 = win32.win32 = win32;
posix.posix = win32.posix = posix;

Expand Down
31 changes: 31 additions & 0 deletions test/parallel/test-path-from-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const common = require('../common');
const assert = require('node:assert');
const { fromURL, win32 } = require('node:path');
const { describe, it } = require('node:test');


describe('path.fromURL', { concurrency: true }, () => {
it('should passthrough path string', () => assert
.strictEqual(fromURL('/Users/node/dev'), '/Users/node/dev'));
it('should passthrough an empty string', () => assert
.strictEqual(fromURL(''), ''));
it('should passthrough a URL string with file schema', () => assert
.strictEqual(fromURL('file:///Users/node/dev'), 'file:///Users/node/dev'));
it('should parse a URL instance with file schema', () => assert
.strictEqual(fromURL(new URL('file:///Users/node/dev')), '/Users/node/dev'));
it('should parse a URL instance with file schema', () => assert
.strictEqual(fromURL(new URL('file:///path/to/file')), '/path/to/file'));
it('should remove the host from a URL instance', () => assert
.strictEqual(fromURL(new URL('file://localhost/etc/fstab')), '/etc/fstab'));
it('should parse a windows file URI', () => {
const fn = common.isWindows ? fromURL : win32.fromURL;
assert.strictEqual(fn(new URL('file:///c:/foo.txt')), 'c:\\foo.txt');
});
it('should throw an error if the URL is not a file URL', () => assert
.throws(() => fromURL(new URL('http://localhost/etc/fstab')), { code: 'ERR_INVALID_URL_SCHEME' }));
it('should throw an error if converting invalid types', () => [{}, [], 1, null, undefined,
Promise.resolve(new URL('file:///some/path/')), Symbol(), true, false, 1n, 0, 0n, () => {}]
.forEach((item) => assert.throws(() => fromURL(item), { code: 'ERR_INVALID_ARG_TYPE' })));
});