diff --git a/doc/api/fs.md b/doc/api/fs.md index f94cb794a14f63..81bacd32995095 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -3518,6 +3518,11 @@ with options `{ recursive: true, force: true }`. * `path` {string|Buffer|URL} @@ -5261,6 +5266,11 @@ with options `{ recursive: true, force: true }`. * `path` {string|Buffer|URL} diff --git a/lib/fs.js b/lib/fs.js index 9ec725ffebdf58..c1cc7aecaa49e4 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1185,6 +1185,7 @@ function rm(path, options, callback) { callback = options; options = undefined; } + path = getValidatedPath(path); validateRmOptions(path, options, false, (err, options) => { if (err) { @@ -1208,6 +1209,7 @@ function rm(path, options, callback) { * @returns {void} */ function rmSync(path, options) { + path = getValidatedPath(path); options = validateRmOptionsSync(path, options, false); lazyLoadRimraf(); diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 58173a6bd55f89..9712890139596d 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -32,6 +32,8 @@ const legacyExtensionFormatMap = { '.node': 'commonjs' }; +let experimentalSpecifierResolutionWarned = false; + if (experimentalWasmModules) extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm'; @@ -53,41 +55,57 @@ const protocolHandlers = ObjectAssign(ObjectCreate(null), { return format; }, - 'file:'(parsed, url) { - const ext = extname(parsed.pathname); - let format; - - if (ext === '.js') { - format = getPackageType(parsed.href) === 'module' ? 'module' : 'commonjs'; - } else { - format = extensionFormatMap[ext]; - } - if (!format) { - if (experimentalSpecifierResolution === 'node') { - process.emitWarning( - 'The Node.js specifier resolution in ESM is experimental.', - 'ExperimentalWarning'); - format = legacyExtensionFormatMap[ext]; - } else { - throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url)); - } - } - - return format || null; - }, + 'file:': getFileProtocolModuleFormat, 'node:'() { return 'builtin'; }, }); -function defaultGetFormat(url, context) { +function getLegacyExtensionFormat(ext) { + if ( + experimentalSpecifierResolution === 'node' && + !experimentalSpecifierResolutionWarned + ) { + process.emitWarning( + 'The Node.js specifier resolution in ESM is experimental.', + 'ExperimentalWarning'); + experimentalSpecifierResolutionWarned = true; + } + return legacyExtensionFormatMap[ext]; +} + +function getFileProtocolModuleFormat(url, ignoreErrors) { + const ext = extname(url.pathname); + if (ext === '.js') { + return getPackageType(url) === 'module' ? 'module' : 'commonjs'; + } + + const format = extensionFormatMap[ext]; + if (format) return format; + if (experimentalSpecifierResolution !== 'node') { + // Explicit undefined return indicates load hook should rerun format check + if (ignoreErrors) + return undefined; + throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url)); + } + return getLegacyExtensionFormat(ext) ?? null; +} + +function defaultGetFormatWithoutErrors(url, context) { const parsed = new URL(url); + if (!ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol)) + return null; + return protocolHandlers[parsed.protocol](parsed, true); +} +function defaultGetFormat(url, context) { + const parsed = new URL(url); return ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol) ? - protocolHandlers[parsed.protocol](parsed, url) : + protocolHandlers[parsed.protocol](parsed, false) : null; } module.exports = { defaultGetFormat, + defaultGetFormatWithoutErrors, extensionFormatMap, legacyExtensionFormatMap, }; diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 67123792e8903a..86fe0a77406ecf 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -2,7 +2,6 @@ const { defaultGetFormat } = require('internal/modules/esm/get_format'); const { defaultGetSource } = require('internal/modules/esm/get_source'); -const { translators } = require('internal/modules/esm/translators'); const { validateAssertions } = require('internal/modules/esm/assert'); /** @@ -18,7 +17,7 @@ async function defaultLoad(url, context) { } = context; const { importAssertions } = context; - if (!format || !translators.has(format)) { + if (format == null) { format = defaultGetFormat(url); } diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 635efaa459d76f..9c1e492cae4983 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -131,7 +131,7 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, isExports, base) { * @returns {void} */ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { - const format = defaultGetFormat(url); + const format = defaultGetFormatWithoutErrors(url); if (format !== 'module') return; const path = fileURLToPath(url); @@ -487,6 +487,7 @@ const patternRegEx = /\*/g; function resolvePackageTargetString( target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) { + if (subpath !== '' && !pattern && target[target.length - 1] !== '/') throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); @@ -502,7 +503,8 @@ function resolvePackageTargetString( const exportTarget = pattern ? RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) : target + subpath; - return packageResolve(exportTarget, packageJSONUrl, conditions); + return packageResolve( + exportTarget, packageJSONUrl, conditions); } } throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); @@ -523,9 +525,16 @@ function resolvePackageTargetString( if (RegExpPrototypeTest(invalidSegmentRegEx, subpath)) throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base); - if (pattern) - return new URL(RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, - () => subpath)); + if (pattern) { + return new URL( + RegExpPrototypeSymbolReplace( + patternRegEx, + resolved.href, + () => subpath + ) + ); + } + return new URL(subpath, resolved); } @@ -552,9 +561,9 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, let lastException; for (let i = 0; i < target.length; i++) { const targetItem = target[i]; - let resolved; + let resolveResult; try { - resolved = resolvePackageTarget( + resolveResult = resolvePackageTarget( packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern, internal, conditions); } catch (e) { @@ -563,13 +572,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, continue; throw e; } - if (resolved === undefined) + if (resolveResult === undefined) continue; - if (resolved === null) { + if (resolveResult === null) { lastException = null; continue; } - return resolved; + return resolveResult; } if (lastException === undefined || lastException === null) return lastException; @@ -588,12 +597,12 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, const key = keys[i]; if (key === 'default' || conditions.has(key)) { const conditionalTarget = target[key]; - const resolved = resolvePackageTarget( + const resolveResult = resolvePackageTarget( packageJSONUrl, conditionalTarget, subpath, packageSubpath, base, pattern, internal, conditions); - if (resolved === undefined) + if (resolveResult === undefined) continue; - return resolved; + return resolveResult; } } return undefined; @@ -655,8 +664,11 @@ function packageExportsResolve( const resolved = resolvePackageTarget( packageJSONUrl, target, '', packageSubpath, base, false, false, conditions ); - if (resolved === null || resolved === undefined) + + if (resolved == null) { throwExportsNotFound(packageSubpath, packageJSONUrl, base); + } + return { resolved, exact: true }; } @@ -693,13 +705,24 @@ function packageExportsResolve( if (bestMatch) { const target = exports[bestMatch]; const pattern = StringPrototypeIncludes(bestMatch, '*'); - const resolved = resolvePackageTarget(packageJSONUrl, target, - bestMatchSubpath, bestMatch, base, - pattern, false, conditions); - if (resolved === null || resolved === undefined) + const resolved = resolvePackageTarget( + packageJSONUrl, + target, + bestMatchSubpath, + bestMatch, + base, + pattern, + false, + conditions); + + if (resolved == null) { throwExportsNotFound(packageSubpath, packageJSONUrl, base); - if (!pattern) + } + + if (!pattern) { emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base); + } + return { resolved, exact: pattern }; } @@ -743,8 +766,9 @@ function packageImportsResolve(name, base, conditions) { const resolved = resolvePackageTarget( packageJSONUrl, imports[name], '', name, base, false, true, conditions ); - if (resolved !== null) + if (resolved != null) { return { resolved, exact: true }; + } } else { let bestMatch = ''; let bestMatchSubpath; @@ -776,10 +800,11 @@ function packageImportsResolve(name, base, conditions) { if (bestMatch) { const target = imports[bestMatch]; const pattern = StringPrototypeIncludes(bestMatch, '*'); - const resolved = resolvePackageTarget(packageJSONUrl, target, - bestMatchSubpath, bestMatch, - base, pattern, true, - conditions); + const resolved = resolvePackageTarget( + packageJSONUrl, target, + bestMatchSubpath, bestMatch, + base, pattern, true, + conditions); if (resolved !== null) { if (!pattern) emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base); @@ -882,12 +907,20 @@ function packageResolve(specifier, base, conditions) { // Package match. const packageConfig = getPackageConfig(packageJSONPath, specifier, base); - if (packageConfig.exports !== undefined && packageConfig.exports !== null) + if (packageConfig.exports !== undefined && packageConfig.exports !== null) { return packageExportsResolve( packageJSONUrl, packageSubpath, packageConfig, base, conditions ).resolved; - if (packageSubpath === '.') - return legacyMainResolve(packageJSONUrl, packageConfig, base); + } + + if (packageSubpath === '.') { + return legacyMainResolve( + packageJSONUrl, + packageConfig, + base + ); + } + return new URL(packageSubpath, packageJSONUrl); // Cross-platform root check. } while (packageJSONPath.length !== lastPath.length); @@ -994,6 +1027,13 @@ function resolveAsCommonJS(specifier, parentURL) { } } +function throwIfUnsupportedURLProtocol(url) { + if (url.protocol !== 'file:' && url.protocol !== 'data:' && + url.protocol !== 'node:') { + throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url); + } +} + function defaultResolve(specifier, context = {}, defaultResolveUnused) { let { parentURL, conditions } = context; if (parentURL && policy?.manifest) { @@ -1060,11 +1100,12 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) { throw error; } - if (url.protocol !== 'file:' && url.protocol !== 'data:' && - url.protocol !== 'node:') - throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url); + throwIfUnsupportedURLProtocol(url); - return { url: `${url}` }; + return { + url: `${url}`, + format: defaultGetFormatWithoutErrors(url), + }; } module.exports = { @@ -1078,4 +1119,6 @@ module.exports = { }; // cycle -const { defaultGetFormat } = require('internal/modules/esm/get_format'); +const { + defaultGetFormatWithoutErrors, +} = require('internal/modules/esm/get_format'); diff --git a/test/es-module/test-esm-loader-resolve-type.mjs b/test/es-module/test-esm-loader-resolve-type.mjs new file mode 100644 index 00000000000000..f4bab3723d1f46 --- /dev/null +++ b/test/es-module/test-esm-loader-resolve-type.mjs @@ -0,0 +1,41 @@ +// Flags: --loader ./test/fixtures/es-module-loaders/hook-resolve-type.mjs +import { allowGlobals } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { strict as assert } from 'assert'; +import * as fs from 'fs'; + +allowGlobals(global.getModuleTypeStats); + +const basePath = + new URL('./node_modules/', import.meta.url); + +const rel = (file) => new URL(file, basePath); +const createDir = (path) => { + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } +}; + +const moduleName = 'module-counter-by-type'; + +const moduleDir = rel(`${moduleName}`); +createDir(basePath); +createDir(moduleDir); +fs.cpSync( + fixtures.path('es-modules', moduleName), + moduleDir, + { recursive: true } +); + +const { importedESM: importedESMBefore, + importedCJS: importedCJSBefore } = global.getModuleTypeStats(); + +import(`${moduleName}`).finally(() => { + fs.rmSync(basePath, { recursive: true, force: true }); +}); + +const { importedESM: importedESMAfter, + importedCJS: importedCJSAfter } = global.getModuleTypeStats(); + +assert.strictEqual(importedESMBefore + 1, importedESMAfter); +assert.strictEqual(importedCJSBefore, importedCJSAfter); diff --git a/test/es-module/test-esm-resolve-type.js b/test/es-module/test-esm-resolve-type.js new file mode 100644 index 00000000000000..ba4dea03c8ac48 --- /dev/null +++ b/test/es-module/test-esm-resolve-type.js @@ -0,0 +1,250 @@ +'use strict'; +// Flags: --expose-internals + +/** + * This test ensures defaultResolve returns the found module format in the + * return object in the form: + * { url: , format: <'module'|'commonjs'|undefined> }; + */ + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +if (!common.isMainThread) { + common.skip( + 'test-esm-resolve-type.js: process.chdir is not available in Workers' + ); +} + +const assert = require('assert'); +const { + defaultResolve: resolve +} = require('internal/modules/esm/resolve'); + +const rel = (file) => path.join(tmpdir.path, file); +const previousCwd = process.cwd(); +const nmDir = rel('node_modules'); +try { + tmpdir.refresh(); + process.chdir(tmpdir.path); + /** + * ensure that resolving by full path does not return the format + * with the defaultResolver + */ + [ + [ '/es-modules/package-type-module/index.js', 'module' ], + [ '/es-modules/package-type-commonjs/index.js', 'commonjs' ], + [ '/es-modules/package-without-type/index.js', 'commonjs' ], + [ '/es-modules/package-without-pjson/index.js', 'commonjs' ], + ].forEach((testVariant) => { + const [ testScript, expectedType ] = testVariant; + const resolvedPath = path.resolve(fixtures.path(testScript)); + const resolveResult = resolve(url.pathToFileURL(resolvedPath)); + assert.strictEqual(resolveResult.format, expectedType); + }); + + /** + * create a test module and try to resolve it by module name. + * check the result is as expected + * + * for test-module-ne: everything .js that is not 'module' is 'commonjs' + */ + + [ [ 'test-module-mainjs', 'js', 'module', 'module'], + [ 'test-module-mainmjs', 'mjs', 'module', 'module'], + [ 'test-module-cjs', 'js', 'commonjs', 'commonjs'], + [ 'test-module-ne', 'js', undefined, 'commonjs'], + ].forEach((testVariant) => { + const [ moduleName, + moduleExtenstion, + moduleType, + expectedResolvedType ] = testVariant; + process.chdir(previousCwd); + tmpdir.refresh(); + process.chdir(tmpdir.path); + const createDir = (path) => { + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } + }; + + const mDir = rel(`node_modules/${moduleName}`); + const subDir = rel(`node_modules/${moduleName}/subdir`); + const pkg = rel(`node_modules/${moduleName}/package.json`); + const script = rel(`node_modules/${moduleName}/subdir/mainfile.${moduleExtenstion}`); + + createDir(nmDir); + createDir(mDir); + createDir(subDir); + const pkgJsonContent = { + ...(moduleType !== undefined) && { type: moduleType }, + main: `subdir/mainfile.${moduleExtenstion}` + }; + fs.writeFileSync(pkg, JSON.stringify(pkgJsonContent)); + fs.writeFileSync(script, + 'export function esm-resolve-tester() {return 42}'); + + const resolveResult = resolve(`${moduleName}`); + assert.strictEqual(resolveResult.format, expectedResolvedType); + + fs.rmSync(nmDir, { recursive: true, force: true }); + }); + + // Helpers + const createDir = (path) => { + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } + }; + + function testDualPackageWithJsMainScriptAndModuleType() { + // Create a dummy dual package + // + /** + * this creates the following directory structure: + * + * ./node_modules: + * |-> my-dual-package + * |-> es + * |-> index.js + * |-> package.json [2] + * |-> lib + * |-> index.js + * |->package.json [1] + * + * in case the package is imported: + * import * as my-package from 'my-dual-package' + * it will cause the resolve method to return: + * { + * url: '/node_modules/my-dual-package/es/index.js', + * format: 'module' + * } + * + * following testcase ensures that resolve works correctly in this case + * returning the information as specified above. Source for 'url' value + * is [1], source for 'format' value is [2] + */ + + const moduleName = 'my-dual-package'; + + const mDir = rel(`node_modules/${moduleName}`); + const esSubDir = rel(`node_modules/${moduleName}/es`); + const cjsSubDir = rel(`node_modules/${moduleName}/lib`); + const pkg = rel(`node_modules/${moduleName}/package.json`); + const esmPkg = rel(`node_modules/${moduleName}/es/package.json`); + const esScript = rel(`node_modules/${moduleName}/es/index.js`); + const cjsScript = rel(`node_modules/${moduleName}/lib/index.js`); + + createDir(nmDir); + createDir(mDir); + createDir(esSubDir); + createDir(cjsSubDir); + + const mainPkgJsonContent = { + type: 'commonjs', + exports: { + '.': { + 'require': './lib/index.js', + 'import': './es/index.js', + 'default': './lib/index.js' + }, + './package.json': './package.json', + } + }; + const esmPkgJsonContent = { + type: 'module' + }; + + fs.writeFileSync(pkg, JSON.stringify(mainPkgJsonContent)); + fs.writeFileSync(esmPkg, JSON.stringify(esmPkgJsonContent)); + fs.writeFileSync( + esScript, + 'export function esm-resolve-tester() {return 42}' + ); + fs.writeFileSync( + cjsScript, + 'module.exports = {esm-resolve-tester: () => {return 42}}' + ); + + // test the resolve + const resolveResult = resolve(`${moduleName}`); + assert.strictEqual(resolveResult.format, 'module'); + assert.ok(resolveResult.url.includes('my-dual-package/es/index.js')); + } + + testDualPackageWithJsMainScriptAndModuleType(); + + // TestParameters are ModuleName, mainRequireScript, mainImportScript, + // mainPackageType, subdirPkgJsonType, expectedResolvedFormat, mainSuffix + [ + [ 'mjs-mod-mod', 'index.js', 'index.mjs', 'module', 'module', 'module'], + [ 'mjs-com-com', 'idx.js', 'idx.mjs', 'commonjs', 'commonjs', 'module'], + [ 'mjs-mod-com', 'index.js', 'imp.mjs', 'module', 'commonjs', 'module'], + [ 'cjs-mod-mod', 'index.cjs', 'imp.cjs', 'module', 'module', 'commonjs'], + [ 'js-com-com', 'index.js', 'imp.js', 'commonjs', 'commonjs', 'commonjs'], + [ 'js-com-mod', 'index.js', 'imp.js', 'commonjs', 'module', 'module'], + [ 'qmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '?k=v'], + [ 'hmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '#Key'], + [ 'qhmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '?k=v#h'], + [ 'ts-mod-com', 'index.js', 'imp.ts', 'module', 'commonjs', undefined], + ].forEach((testVariant) => { + const [ + moduleName, + mainRequireScript, + mainImportScript, + mainPackageType, + subdirPackageType, + expectedResolvedFormat, + mainSuffix = '' ] = testVariant; + + const mDir = rel(`node_modules/${moduleName}`); + const subDir = rel(`node_modules/${moduleName}/subdir`); + const pkg = rel(`node_modules/${moduleName}/package.json`); + const subdirPkg = rel(`node_modules/${moduleName}/subdir/package.json`); + const esScript = rel(`node_modules/${moduleName}/subdir/${mainImportScript}`); + const cjsScript = rel(`node_modules/${moduleName}/subdir/${mainRequireScript}`); + + createDir(nmDir); + createDir(mDir); + createDir(subDir); + + const mainPkgJsonContent = { + type: mainPackageType, + exports: { + '.': { + 'require': `./subdir/${mainRequireScript}${mainSuffix}`, + 'import': `./subdir/${mainImportScript}${mainSuffix}`, + 'default': `./subdir/${mainRequireScript}${mainSuffix}` + }, + './package.json': './package.json', + } + }; + const subdirPkgJsonContent = { + type: `${subdirPackageType}` + }; + + fs.writeFileSync(pkg, JSON.stringify(mainPkgJsonContent)); + fs.writeFileSync(subdirPkg, JSON.stringify(subdirPkgJsonContent)); + fs.writeFileSync( + esScript, + 'export function esm-resolve-tester() {return 42}' + ); + fs.writeFileSync( + cjsScript, + 'module.exports = {esm-resolve-tester: () => {return 42}}' + ); + + // test the resolve + const resolveResult = resolve(`${moduleName}`); + assert.strictEqual(resolveResult.format, expectedResolvedFormat); + assert.ok(resolveResult.url.endsWith(`${moduleName}/subdir/${mainImportScript}${mainSuffix}`)); + }); + +} finally { + process.chdir(previousCwd); + fs.rmSync(nmDir, { recursive: true, force: true }); +} diff --git a/test/fixtures/es-module-loaders/hook-resolve-type.mjs b/test/fixtures/es-module-loaders/hook-resolve-type.mjs new file mode 100644 index 00000000000000..48692ba4eec544 --- /dev/null +++ b/test/fixtures/es-module-loaders/hook-resolve-type.mjs @@ -0,0 +1,21 @@ +let importedESM = 0; +let importedCJS = 0; +global.getModuleTypeStats = () => { return {importedESM, importedCJS} }; + +export function load(url, context, next) { + return next(url, context, next); +} + +export function resolve(specifier, context, next) { + const nextResult = next(specifier, context); + const { format } = nextResult; + + if (format === 'module' || specifier.endsWith('.mjs')) { + importedESM++; + } else if (format == null || format === 'commonjs') { + importedCJS++; + } + + return nextResult; +} + diff --git a/test/fixtures/es-modules/module-counter-by-type/index.js b/test/fixtures/es-modules/module-counter-by-type/index.js new file mode 100644 index 00000000000000..901fd8dae0a419 --- /dev/null +++ b/test/fixtures/es-modules/module-counter-by-type/index.js @@ -0,0 +1,3 @@ +let dummy = 42; + +export {dummy}; diff --git a/test/fixtures/es-modules/module-counter-by-type/package.json b/test/fixtures/es-modules/module-counter-by-type/package.json new file mode 100644 index 00000000000000..c60bc2b004572a --- /dev/null +++ b/test/fixtures/es-modules/module-counter-by-type/package.json @@ -0,0 +1,4 @@ +{ + "type": "module", + "main": "index.js" +} diff --git a/test/fixtures/es-modules/package-without-pjson/index.js b/test/fixtures/es-modules/package-without-pjson/index.js new file mode 100644 index 00000000000000..29560bd838d029 --- /dev/null +++ b/test/fixtures/es-modules/package-without-pjson/index.js @@ -0,0 +1,7 @@ +const identifier = 'package-without-pjson'; + +const common = require('../common'); +common.requireNoPackageJSONAbove(); + +console.log(identifier); +module.exports = identifier; diff --git a/test/parallel/test-fs-rm.js b/test/parallel/test-fs-rm.js index 5bb5d2de553eee..b1ef509a4e6ebf 100644 --- a/test/parallel/test-fs-rm.js +++ b/test/parallel/test-fs-rm.js @@ -5,6 +5,8 @@ const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); const path = require('path'); +const { pathToFileURL } = require('url'); + const { validateRmOptionsSync } = require('internal/fs/utils'); tmpdir.refresh(); @@ -95,6 +97,11 @@ function removeAsync(dir) { makeNonEmptyDirectory(2, 10, 2, dir, false); removeAsync(dir); + // Same test using URL instead of a path + dir = nextDirPath(); + makeNonEmptyDirectory(2, 10, 2, dir, false); + removeAsync(pathToFileURL(dir)); + // Create a flat folder including symlinks dir = nextDirPath(); makeNonEmptyDirectory(1, 10, 2, dir, true); @@ -154,6 +161,16 @@ function removeAsync(dir) { fs.rmSync(filePath, { force: true }); } + // Should accept URL + const fileURL = pathToFileURL(path.join(tmpdir.path, 'rm-file.txt')); + fs.writeFileSync(fileURL, ''); + + try { + fs.rmSync(fileURL, { recursive: true }); + } finally { + fs.rmSync(fileURL, { force: true }); + } + // Recursive removal should succeed. fs.rmSync(dir, { recursive: true }); @@ -200,6 +217,16 @@ function removeAsync(dir) { } finally { fs.rmSync(filePath, { force: true }); } + + // Should accept URL + const fileURL = pathToFileURL(path.join(tmpdir.path, 'rm-promises-file.txt')); + fs.writeFileSync(fileURL, ''); + + try { + await fs.promises.rm(fileURL, { recursive: true }); + } finally { + fs.rmSync(fileURL, { force: true }); + } })().then(common.mustCall()); // Test input validation.