Skip to content

Commit 722fe64

Browse files
avivkellermarco-ippolito
authored andcommittedMay 3, 2024
lib, url: add a windows option to path parsing
PR-URL: #52509 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
1 parent 4d5ef4f commit 722fe64

File tree

6 files changed

+267
-216
lines changed

6 files changed

+267
-216
lines changed
 

‎doc/api/esm.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
11571157
[`package.json`]: packages.md#nodejs-packagejson-field-definitions
11581158
[`path.dirname()`]: path.md#pathdirnamepath
11591159
[`process.dlopen`]: process.md#processdlopenmodule-filename-flags
1160-
[`url.fileURLToPath()`]: url.md#urlfileurltopathurl
1160+
[`url.fileURLToPath()`]: url.md#urlfileurltopathurl-options
11611161
[cjs-module-lexer]: https://github.com/nodejs/cjs-module-lexer/tree/1.2.2
11621162
[commonjs-extension-resolution-loader]: https://github.com/nodejs/loaders-test/tree/main/commonjs-extension-resolution-loader
11631163
[custom https loader]: module.md#import-from-https
@@ -1166,4 +1166,4 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
11661166
[special scheme]: https://url.spec.whatwg.org/#special-scheme
11671167
[status code]: process.md#exit-codes
11681168
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
1169-
[url.pathToFileURL]: url.md#urlpathtofileurlpath
1169+
[url.pathToFileURL]: url.md#urlpathtofileurlpath-options

‎doc/api/url.md

+22-2
Original file line numberDiff line numberDiff line change
@@ -1137,13 +1137,23 @@ console.log(url.domainToUnicode('xn--iñvalid.com'));
11371137
// Prints an empty string
11381138
```
11391139

1140-
### `url.fileURLToPath(url)`
1140+
### `url.fileURLToPath(url[, options])`
11411141

11421142
<!-- YAML
11431143
added: v10.12.0
1144+
changes:
1145+
- version: REPLACEME
1146+
pr-url: https://github.com/nodejs/node/pull/52509
1147+
description: The `options` argument can now be used to
1148+
determine how to parse the `path` argument.
11441149
-->
11451150

11461151
* `url` {URL | string} The file URL string or URL object to convert to a path.
1152+
* `options` {Object}
1153+
* `windows` {boolean|undefined} `true` if the `path` should be
1154+
return as a windows filepath, `false` for posix, and
1155+
`undefined` for the system default.
1156+
**Default:** `undefined`.
11471157
* Returns: {string} The fully-resolved platform-specific Node.js file path.
11481158

11491159
This function ensures the correct decodings of percent-encoded characters as
@@ -1237,13 +1247,23 @@ console.log(url.format(myURL, { fragment: false, unicode: true, auth: false }));
12371247
// Prints 'https://測試/?abc'
12381248
```
12391249
1240-
### `url.pathToFileURL(path)`
1250+
### `url.pathToFileURL(path[, options])`
12411251
12421252
<!-- YAML
12431253
added: v10.12.0
1254+
changes:
1255+
- version: REPLACEME
1256+
pr-url: https://github.com/nodejs/node/pull/52509
1257+
description: The `options` argument can now be used to
1258+
determine how to return the `path` value.
12441259
-->
12451260
12461261
* `path` {string} The path to convert to a File URL.
1262+
* `options` {Object}
1263+
* `windows` {boolean|undefined} `true` if the `path` should be
1264+
treated as a windows filepath, `false` for posix, and
1265+
`undefined` for the system default.
1266+
**Default:** `undefined`.
12471267
* Returns: {URL} The file URL object.
12481268
12491269
This function ensures that `path` is resolved absolutely, and that the URL

‎lib/internal/url.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const {
4444
getConstructorOf,
4545
removeColors,
4646
kEnumerableProperty,
47+
kEmptyObject,
4748
SideEffectFreeRegExpPrototypeSymbolReplace,
4849
} = require('internal/util');
4950

@@ -1452,14 +1453,15 @@ function getPathFromURLPosix(url) {
14521453
return decodeURIComponent(pathname);
14531454
}
14541455

1455-
function fileURLToPath(path) {
1456+
function fileURLToPath(path, options = kEmptyObject) {
1457+
const windows = options?.windows;
14561458
if (typeof path === 'string')
14571459
path = new URL(path);
14581460
else if (!isURL(path))
14591461
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
14601462
if (path.protocol !== 'file:')
14611463
throw new ERR_INVALID_URL_SCHEME('file');
1462-
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
1464+
return (windows ?? isWindows) ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
14631465
}
14641466

14651467
// The following characters are percent-encoded when converting from file path
@@ -1481,11 +1483,12 @@ const tabRegEx = /\t/g;
14811483
const questionRegex = /\?/g;
14821484
const hashRegex = /#/g;
14831485

1484-
function encodePathChars(filepath) {
1486+
function encodePathChars(filepath, options = kEmptyObject) {
1487+
const windows = options?.windows;
14851488
if (StringPrototypeIndexOf(filepath, '%') !== -1)
14861489
filepath = RegExpPrototypeSymbolReplace(percentRegEx, filepath, '%25');
14871490
// In posix, backslash is a valid character in paths:
1488-
if (!isWindows && StringPrototypeIndexOf(filepath, '\\') !== -1)
1491+
if (!(windows ?? isWindows) && StringPrototypeIndexOf(filepath, '\\') !== -1)
14891492
filepath = RegExpPrototypeSymbolReplace(backslashRegEx, filepath, '%5C');
14901493
if (StringPrototypeIndexOf(filepath, '\n') !== -1)
14911494
filepath = RegExpPrototypeSymbolReplace(newlineRegEx, filepath, '%0A');
@@ -1496,8 +1499,9 @@ function encodePathChars(filepath) {
14961499
return filepath;
14971500
}
14981501

1499-
function pathToFileURL(filepath) {
1500-
if (isWindows && StringPrototypeStartsWith(filepath, '\\\\')) {
1502+
function pathToFileURL(filepath, options = kEmptyObject) {
1503+
const windows = options?.windows;
1504+
if ((windows ?? isWindows) && StringPrototypeStartsWith(filepath, '\\\\')) {
15011505
const outURL = new URL('file://');
15021506
// UNC path format: \\server\share\resource
15031507
const hostnameEndIndex = StringPrototypeIndexOf(filepath, '\\', 2);
@@ -1518,20 +1522,22 @@ function pathToFileURL(filepath) {
15181522
const hostname = StringPrototypeSlice(filepath, 2, hostnameEndIndex);
15191523
outURL.hostname = domainToASCII(hostname);
15201524
outURL.pathname = encodePathChars(
1521-
RegExpPrototypeSymbolReplace(backslashRegEx, StringPrototypeSlice(filepath, hostnameEndIndex), '/'));
1525+
RegExpPrototypeSymbolReplace(backslashRegEx, StringPrototypeSlice(filepath, hostnameEndIndex), '/'),
1526+
{ windows },
1527+
);
15221528
return outURL;
15231529
}
1524-
let resolved = path.resolve(filepath);
1530+
let resolved = (windows ?? isWindows) ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
15251531
// path.resolve strips trailing slashes so we must add them back
15261532
const filePathLast = StringPrototypeCharCodeAt(filepath,
15271533
filepath.length - 1);
15281534
if ((filePathLast === CHAR_FORWARD_SLASH ||
1529-
(isWindows && filePathLast === CHAR_BACKWARD_SLASH)) &&
1535+
((windows ?? isWindows) && filePathLast === CHAR_BACKWARD_SLASH)) &&
15301536
resolved[resolved.length - 1] !== path.sep)
15311537
resolved += '/';
15321538

15331539
// Call encodePathChars first to avoid encoding % again for ? and #.
1534-
resolved = encodePathChars(resolved);
1540+
resolved = encodePathChars(resolved, { windows });
15351541

15361542
// Question and hash character should be included in pathname.
15371543
// Therefore, encoding is required to eliminate parsing them in different states.

‎lib/url.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1020,10 +1020,10 @@ Url.prototype.parseHost = function parseHost() {
10201020
// When used internally, we are not obligated to associate TypeError with
10211021
// this function, so non-strings can be rejected by underlying implementation.
10221022
// Public API has to validate input and throw appropriate error.
1023-
function pathToFileURL(path) {
1023+
function pathToFileURL(path, options) {
10241024
validateString(path, 'path');
10251025

1026-
return _pathToFileURL(path);
1026+
return _pathToFileURL(path, options);
10271027
}
10281028

10291029
module.exports = {

‎test/parallel/test-url-fileurltopath.js

+115-101
Original file line numberDiff line numberDiff line change
@@ -49,106 +49,120 @@ assert.throws(() => url.fileURLToPath('https://a/b/c'), {
4949
}
5050
}
5151

52-
{
53-
let testCases;
54-
if (isWindows) {
55-
testCases = [
56-
// Lowercase ascii alpha
57-
{ path: 'C:\\foo', fileURL: 'file:///C:/foo' },
58-
// Uppercase ascii alpha
59-
{ path: 'C:\\FOO', fileURL: 'file:///C:/FOO' },
60-
// dir
61-
{ path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' },
62-
// trailing separator
63-
{ path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' },
64-
// dot
65-
{ path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },
66-
// space
67-
{ path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' },
68-
// question mark
69-
{ path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },
70-
// number sign
71-
{ path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' },
72-
// ampersand
73-
{ path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' },
74-
// equals
75-
{ path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' },
76-
// colon
77-
{ path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' },
78-
// semicolon
79-
{ path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' },
80-
// percent
81-
{ path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' },
82-
// backslash
83-
{ path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' },
84-
// backspace
85-
{ path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' },
86-
// tab
87-
{ path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' },
88-
// newline
89-
{ path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' },
90-
// carriage return
91-
{ path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' },
92-
// latin1
93-
{ path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
94-
// Euro sign (BMP code point)
95-
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
96-
// Rocket emoji (non-BMP code point)
97-
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
98-
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
99-
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
100-
];
101-
} else {
102-
testCases = [
103-
// Lowercase ascii alpha
104-
{ path: '/foo', fileURL: 'file:///foo' },
105-
// Uppercase ascii alpha
106-
{ path: '/FOO', fileURL: 'file:///FOO' },
107-
// dir
108-
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
109-
// trailing separator
110-
{ path: '/dir/', fileURL: 'file:///dir/' },
111-
// dot
112-
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
113-
// space
114-
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
115-
// question mark
116-
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
117-
// number sign
118-
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
119-
// ampersand
120-
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
121-
// equals
122-
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
123-
// colon
124-
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
125-
// semicolon
126-
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
127-
// percent
128-
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
129-
// backslash
130-
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
131-
// backspace
132-
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
133-
// tab
134-
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
135-
// newline
136-
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
137-
// carriage return
138-
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
139-
// latin1
140-
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
141-
// Euro sign (BMP code point)
142-
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
143-
// Rocket emoji (non-BMP code point)
144-
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },
145-
];
146-
}
52+
const windowsTestCases = [
53+
// Lowercase ascii alpha
54+
{ path: 'C:\\foo', fileURL: 'file:///C:/foo' },
55+
// Uppercase ascii alpha
56+
{ path: 'C:\\FOO', fileURL: 'file:///C:/FOO' },
57+
// dir
58+
{ path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' },
59+
// trailing separator
60+
{ path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' },
61+
// dot
62+
{ path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },
63+
// space
64+
{ path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' },
65+
// question mark
66+
{ path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },
67+
// number sign
68+
{ path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' },
69+
// ampersand
70+
{ path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' },
71+
// equals
72+
{ path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' },
73+
// colon
74+
{ path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' },
75+
// semicolon
76+
{ path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' },
77+
// percent
78+
{ path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' },
79+
// backslash
80+
{ path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' },
81+
// backspace
82+
{ path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' },
83+
// tab
84+
{ path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' },
85+
// newline
86+
{ path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' },
87+
// carriage return
88+
{ path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' },
89+
// latin1
90+
{ path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
91+
// Euro sign (BMP code point)
92+
{ path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' },
93+
// Rocket emoji (non-BMP code point)
94+
{ path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },
95+
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
96+
{ path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' },
97+
];
98+
const posixTestCases = [
99+
// Lowercase ascii alpha
100+
{ path: '/foo', fileURL: 'file:///foo' },
101+
// Uppercase ascii alpha
102+
{ path: '/FOO', fileURL: 'file:///FOO' },
103+
// dir
104+
{ path: '/dir/foo', fileURL: 'file:///dir/foo' },
105+
// trailing separator
106+
{ path: '/dir/', fileURL: 'file:///dir/' },
107+
// dot
108+
{ path: '/foo.mjs', fileURL: 'file:///foo.mjs' },
109+
// space
110+
{ path: '/foo bar', fileURL: 'file:///foo%20bar' },
111+
// question mark
112+
{ path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },
113+
// number sign
114+
{ path: '/foo#bar', fileURL: 'file:///foo%23bar' },
115+
// ampersand
116+
{ path: '/foo&bar', fileURL: 'file:///foo&bar' },
117+
// equals
118+
{ path: '/foo=bar', fileURL: 'file:///foo=bar' },
119+
// colon
120+
{ path: '/foo:bar', fileURL: 'file:///foo:bar' },
121+
// semicolon
122+
{ path: '/foo;bar', fileURL: 'file:///foo;bar' },
123+
// percent
124+
{ path: '/foo%bar', fileURL: 'file:///foo%25bar' },
125+
// backslash
126+
{ path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' },
127+
// backspace
128+
{ path: '/foo\bbar', fileURL: 'file:///foo%08bar' },
129+
// tab
130+
{ path: '/foo\tbar', fileURL: 'file:///foo%09bar' },
131+
// newline
132+
{ path: '/foo\nbar', fileURL: 'file:///foo%0Abar' },
133+
// carriage return
134+
{ path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' },
135+
// latin1
136+
{ path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
137+
// Euro sign (BMP code point)
138+
{ path: '/€', fileURL: 'file:///%E2%82%AC' },
139+
// Rocket emoji (non-BMP code point)
140+
{ path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },
141+
];
147142

148-
for (const { path, fileURL } of testCases) {
149-
const fromString = url.fileURLToPath(fileURL);
150-
assert.strictEqual(fromString, path);
151-
const fromURL = url.fileURLToPath(new URL(fileURL));
152-
assert.strictEqual(fromURL, path);
153-
}
143+
for (const { path, fileURL } of windowsTestCases) {
144+
const fromString = url.fileURLToPath(fileURL, { windows: true });
145+
assert.strictEqual(fromString, path);
146+
const fromURL = url.fileURLToPath(new URL(fileURL), { windows: true });
147+
assert.strictEqual(fromURL, path);
148+
}
149+
150+
for (const { path, fileURL } of posixTestCases) {
151+
const fromString = url.fileURLToPath(fileURL, { windows: false });
152+
assert.strictEqual(fromString, path);
153+
const fromURL = url.fileURLToPath(new URL(fileURL), { windows: false });
154+
assert.strictEqual(fromURL, path);
155+
}
156+
157+
const defaultTestCases = isWindows ? windowsTestCases : posixTestCases;
158+
159+
// Test when `options` is null
160+
const whenNullActual = url.fileURLToPath(new URL(defaultTestCases[0].fileURL), null);
161+
assert.strictEqual(whenNullActual, defaultTestCases[0].path);
162+
163+
for (const { path, fileURL } of defaultTestCases) {
164+
const fromString = url.fileURLToPath(fileURL);
165+
assert.strictEqual(fromString, path);
166+
const fromURL = url.fileURLToPath(new URL(fileURL));
167+
assert.strictEqual(fromURL, path);
154168
}

‎test/parallel/test-url-pathtofileurl.js

+110-99
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
23
const { isWindows } = require('../common');
34
const assert = require('assert');
45
const url = require('url');
@@ -60,106 +61,116 @@ const url = require('url');
6061
}
6162
}
6263

63-
{
64-
let testCases;
65-
if (isWindows) {
66-
testCases = [
67-
// Lowercase ascii alpha
68-
{ path: 'C:\\foo', expected: 'file:///C:/foo' },
69-
// Uppercase ascii alpha
70-
{ path: 'C:\\FOO', expected: 'file:///C:/FOO' },
71-
// dir
72-
{ path: 'C:\\dir\\foo', expected: 'file:///C:/dir/foo' },
73-
// trailing separator
74-
{ path: 'C:\\dir\\', expected: 'file:///C:/dir/' },
75-
// dot
76-
{ path: 'C:\\foo.mjs', expected: 'file:///C:/foo.mjs' },
77-
// space
78-
{ path: 'C:\\foo bar', expected: 'file:///C:/foo%20bar' },
79-
// question mark
80-
{ path: 'C:\\foo?bar', expected: 'file:///C:/foo%3Fbar' },
81-
// number sign
82-
{ path: 'C:\\foo#bar', expected: 'file:///C:/foo%23bar' },
83-
// ampersand
84-
{ path: 'C:\\foo&bar', expected: 'file:///C:/foo&bar' },
85-
// equals
86-
{ path: 'C:\\foo=bar', expected: 'file:///C:/foo=bar' },
87-
// colon
88-
{ path: 'C:\\foo:bar', expected: 'file:///C:/foo:bar' },
89-
// semicolon
90-
{ path: 'C:\\foo;bar', expected: 'file:///C:/foo;bar' },
91-
// percent
92-
{ path: 'C:\\foo%bar', expected: 'file:///C:/foo%25bar' },
93-
// backslash
94-
{ path: 'C:\\foo\\bar', expected: 'file:///C:/foo/bar' },
95-
// backspace
96-
{ path: 'C:\\foo\bbar', expected: 'file:///C:/foo%08bar' },
97-
// tab
98-
{ path: 'C:\\foo\tbar', expected: 'file:///C:/foo%09bar' },
99-
// newline
100-
{ path: 'C:\\foo\nbar', expected: 'file:///C:/foo%0Abar' },
101-
// carriage return
102-
{ path: 'C:\\foo\rbar', expected: 'file:///C:/foo%0Dbar' },
103-
// latin1
104-
{ path: 'C:\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
105-
// Euro sign (BMP code point)
106-
{ path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' },
107-
// Rocket emoji (non-BMP code point)
108-
{ path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' },
109-
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
110-
{ path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' },
111-
];
112-
} else {
113-
testCases = [
114-
// Lowercase ascii alpha
115-
{ path: '/foo', expected: 'file:///foo' },
116-
// Uppercase ascii alpha
117-
{ path: '/FOO', expected: 'file:///FOO' },
118-
// dir
119-
{ path: '/dir/foo', expected: 'file:///dir/foo' },
120-
// trailing separator
121-
{ path: '/dir/', expected: 'file:///dir/' },
122-
// dot
123-
{ path: '/foo.mjs', expected: 'file:///foo.mjs' },
124-
// space
125-
{ path: '/foo bar', expected: 'file:///foo%20bar' },
126-
// question mark
127-
{ path: '/foo?bar', expected: 'file:///foo%3Fbar' },
128-
// number sign
129-
{ path: '/foo#bar', expected: 'file:///foo%23bar' },
130-
// ampersand
131-
{ path: '/foo&bar', expected: 'file:///foo&bar' },
132-
// equals
133-
{ path: '/foo=bar', expected: 'file:///foo=bar' },
134-
// colon
135-
{ path: '/foo:bar', expected: 'file:///foo:bar' },
136-
// semicolon
137-
{ path: '/foo;bar', expected: 'file:///foo;bar' },
138-
// percent
139-
{ path: '/foo%bar', expected: 'file:///foo%25bar' },
140-
// backslash
141-
{ path: '/foo\\bar', expected: 'file:///foo%5Cbar' },
142-
// backspace
143-
{ path: '/foo\bbar', expected: 'file:///foo%08bar' },
144-
// tab
145-
{ path: '/foo\tbar', expected: 'file:///foo%09bar' },
146-
// newline
147-
{ path: '/foo\nbar', expected: 'file:///foo%0Abar' },
148-
// carriage return
149-
{ path: '/foo\rbar', expected: 'file:///foo%0Dbar' },
150-
// latin1
151-
{ path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
152-
// Euro sign (BMP code point)
153-
{ path: '/€', expected: 'file:///%E2%82%AC' },
154-
// Rocket emoji (non-BMP code point)
155-
{ path: '/🚀', expected: 'file:///%F0%9F%9A%80' },
156-
];
157-
}
64+
const windowsTestCases = [
65+
// Lowercase ascii alpha
66+
{ path: 'C:\\foo', expected: 'file:///C:/foo' },
67+
// Uppercase ascii alpha
68+
{ path: 'C:\\FOO', expected: 'file:///C:/FOO' },
69+
// dir
70+
{ path: 'C:\\dir\\foo', expected: 'file:///C:/dir/foo' },
71+
// trailing separator
72+
{ path: 'C:\\dir\\', expected: 'file:///C:/dir/' },
73+
// dot
74+
{ path: 'C:\\foo.mjs', expected: 'file:///C:/foo.mjs' },
75+
// space
76+
{ path: 'C:\\foo bar', expected: 'file:///C:/foo%20bar' },
77+
// question mark
78+
{ path: 'C:\\foo?bar', expected: 'file:///C:/foo%3Fbar' },
79+
// number sign
80+
{ path: 'C:\\foo#bar', expected: 'file:///C:/foo%23bar' },
81+
// ampersand
82+
{ path: 'C:\\foo&bar', expected: 'file:///C:/foo&bar' },
83+
// equals
84+
{ path: 'C:\\foo=bar', expected: 'file:///C:/foo=bar' },
85+
// colon
86+
{ path: 'C:\\foo:bar', expected: 'file:///C:/foo:bar' },
87+
// semicolon
88+
{ path: 'C:\\foo;bar', expected: 'file:///C:/foo;bar' },
89+
// percent
90+
{ path: 'C:\\foo%bar', expected: 'file:///C:/foo%25bar' },
91+
// backslash
92+
{ path: 'C:\\foo\\bar', expected: 'file:///C:/foo/bar' },
93+
// backspace
94+
{ path: 'C:\\foo\bbar', expected: 'file:///C:/foo%08bar' },
95+
// tab
96+
{ path: 'C:\\foo\tbar', expected: 'file:///C:/foo%09bar' },
97+
// newline
98+
{ path: 'C:\\foo\nbar', expected: 'file:///C:/foo%0Abar' },
99+
// carriage return
100+
{ path: 'C:\\foo\rbar', expected: 'file:///C:/foo%0Dbar' },
101+
// latin1
102+
{ path: 'C:\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
103+
// Euro sign (BMP code point)
104+
{ path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' },
105+
// Rocket emoji (non-BMP code point)
106+
{ path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' },
107+
// UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
108+
{ path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' },
109+
];
110+
const posixTestCases = [
111+
// Lowercase ascii alpha
112+
{ path: '/foo', expected: 'file:///foo' },
113+
// Uppercase ascii alpha
114+
{ path: '/FOO', expected: 'file:///FOO' },
115+
// dir
116+
{ path: '/dir/foo', expected: 'file:///dir/foo' },
117+
// trailing separator
118+
{ path: '/dir/', expected: 'file:///dir/' },
119+
// dot
120+
{ path: '/foo.mjs', expected: 'file:///foo.mjs' },
121+
// space
122+
{ path: '/foo bar', expected: 'file:///foo%20bar' },
123+
// question mark
124+
{ path: '/foo?bar', expected: 'file:///foo%3Fbar' },
125+
// number sign
126+
{ path: '/foo#bar', expected: 'file:///foo%23bar' },
127+
// ampersand
128+
{ path: '/foo&bar', expected: 'file:///foo&bar' },
129+
// equals
130+
{ path: '/foo=bar', expected: 'file:///foo=bar' },
131+
// colon
132+
{ path: '/foo:bar', expected: 'file:///foo:bar' },
133+
// semicolon
134+
{ path: '/foo;bar', expected: 'file:///foo;bar' },
135+
// percent
136+
{ path: '/foo%bar', expected: 'file:///foo%25bar' },
137+
// backslash
138+
{ path: '/foo\\bar', expected: 'file:///foo%5Cbar' },
139+
// backspace
140+
{ path: '/foo\bbar', expected: 'file:///foo%08bar' },
141+
// tab
142+
{ path: '/foo\tbar', expected: 'file:///foo%09bar' },
143+
// newline
144+
{ path: '/foo\nbar', expected: 'file:///foo%0Abar' },
145+
// carriage return
146+
{ path: '/foo\rbar', expected: 'file:///foo%0Dbar' },
147+
// latin1
148+
{ path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
149+
// Euro sign (BMP code point)
150+
{ path: '/€', expected: 'file:///%E2%82%AC' },
151+
// Rocket emoji (non-BMP code point)
152+
{ path: '/🚀', expected: 'file:///%F0%9F%9A%80' },
153+
];
158154

159-
for (const { path, expected } of testCases) {
160-
const actual = url.pathToFileURL(path).href;
161-
assert.strictEqual(actual, expected);
162-
}
155+
for (const { path, expected } of windowsTestCases) {
156+
const actual = url.pathToFileURL(path, { windows: true }).href;
157+
assert.strictEqual(actual, expected);
158+
}
159+
160+
for (const { path, expected } of posixTestCases) {
161+
const actual = url.pathToFileURL(path, { windows: false }).href;
162+
assert.strictEqual(actual, expected);
163+
}
164+
165+
const testCases = isWindows ? windowsTestCases : posixTestCases;
166+
167+
// Test when `options` is null
168+
const whenNullActual = url.pathToFileURL(testCases[0].path, null);
169+
assert.strictEqual(whenNullActual.href, testCases[0].expected);
170+
171+
for (const { path, expected } of testCases) {
172+
const actual = url.pathToFileURL(path).href;
173+
assert.strictEqual(actual, expected);
163174
}
164175

165176
// Test for non-string parameter

0 commit comments

Comments
 (0)
Please sign in to comment.