diff --git a/packages/fs/fs.scandir/src/providers/async.ts b/packages/fs/fs.scandir/src/providers/async.ts index a5e892c9..1ab93a20 100644 --- a/packages/fs/fs.scandir/src/providers/async.ts +++ b/packages/fs/fs.scandir/src/providers/async.ts @@ -5,6 +5,7 @@ import { IS_SUPPORT_READDIR_WITH_FILE_TYPES } from '../constants'; import Settings from '../settings'; import { Entry, Stats } from '../types'; import * as utils from '../utils'; +import * as common from './common'; type RplTaskStats = rpl.Task; type RplTaskEntry = rpl.Task; @@ -30,7 +31,7 @@ export function readdirWithFileTypes(directory: string, settings: Settings, call const entries: Entry[] = dirents.map((dirent) => ({ dirent, name: dirent.name, - path: `${directory}${settings.pathSegmentSeparator}${dirent.name}` + path: common.joinPathSegments(directory, dirent.name, settings.pathSegmentSeparator) })); if (!settings.followSymbolicLinks) { @@ -77,7 +78,7 @@ export function readdir(directory: string, settings: Settings, callback: AsyncCa return callFailureCallback(callback, readdirError); } - const filepaths = names.map((name) => `${directory}${settings.pathSegmentSeparator}${name}`); + const filepaths = names.map((name) => common.joinPathSegments(directory, name, settings.pathSegmentSeparator)); const tasks: RplTaskStats[] = filepaths.map((filepath): RplTaskStats => { return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); diff --git a/packages/fs/fs.scandir/src/providers/common.spec.ts b/packages/fs/fs.scandir/src/providers/common.spec.ts new file mode 100644 index 00000000..4c4b3986 --- /dev/null +++ b/packages/fs/fs.scandir/src/providers/common.spec.ts @@ -0,0 +1,24 @@ +import * as assert from 'assert'; + +import * as common from './common'; + +describe('Readers → Common', () => { + describe('.joinPathSegments', () => { + it('should return concatenated string', () => { + assert.strictEqual(common.joinPathSegments('.', 'a', '/'), './a'); + }); + + it('should return correct string when the first segment ens with the separator symbol', () => { + // Unix + assert.strictEqual(common.joinPathSegments('/', 'a', '/'), '/a'); + assert.strictEqual(common.joinPathSegments('//', 'a', '/'), '//a'); + assert.strictEqual(common.joinPathSegments('/a/', 'b', '/'), '/a/b'); + + // Windows + assert.strictEqual(common.joinPathSegments('C:/', 'Users', '/'), 'C:/Users'); + assert.strictEqual(common.joinPathSegments('C:\\', 'Users', '\\'), 'C:\\Users'); + assert.strictEqual(common.joinPathSegments('//?/C:/', 'Users', '/'), '//?/C:/Users'); + assert.strictEqual(common.joinPathSegments('\\\\?\\C:\\', 'Users', '\\'), '\\\\?\\C:\\Users'); + }); + }); +}); diff --git a/packages/fs/fs.scandir/src/providers/common.ts b/packages/fs/fs.scandir/src/providers/common.ts new file mode 100644 index 00000000..e13e8060 --- /dev/null +++ b/packages/fs/fs.scandir/src/providers/common.ts @@ -0,0 +1,10 @@ +export function joinPathSegments(a: string, b: string, separator: string): string { + /** + * The correct handling of cases when the first segment is a root (`/`, `C:/`) or UNC path (`//?/C:/`). + */ + if (a.endsWith(separator)) { + return a + b; + } + + return a + separator + b; +} diff --git a/packages/fs/fs.scandir/src/providers/sync.ts b/packages/fs/fs.scandir/src/providers/sync.ts index 749c9fda..5a15c0f7 100644 --- a/packages/fs/fs.scandir/src/providers/sync.ts +++ b/packages/fs/fs.scandir/src/providers/sync.ts @@ -4,6 +4,7 @@ import { IS_SUPPORT_READDIR_WITH_FILE_TYPES } from '../constants'; import Settings from '../settings'; import { Entry } from '../types'; import * as utils from '../utils'; +import * as common from './common'; export function read(directory: string, settings: Settings): Entry[] { if (!settings.stats && IS_SUPPORT_READDIR_WITH_FILE_TYPES) { @@ -20,7 +21,7 @@ export function readdirWithFileTypes(directory: string, settings: Settings): Ent const entry: Entry = { dirent, name: dirent.name, - path: `${directory}${settings.pathSegmentSeparator}${dirent.name}` + path: common.joinPathSegments(directory, dirent.name, settings.pathSegmentSeparator) }; if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { @@ -43,7 +44,7 @@ export function readdir(directory: string, settings: Settings): Entry[] { const names = settings.fs.readdirSync(directory); return names.map((name) => { - const entryPath = `${directory}${settings.pathSegmentSeparator}${name}`; + const entryPath = common.joinPathSegments(directory, name, settings.pathSegmentSeparator); const stats = fsStat.statSync(entryPath, settings.fsStatSettings); const entry: Entry = {