diff --git a/packages/metro-file-map/src/lib/TreeFS.js b/packages/metro-file-map/src/lib/TreeFS.js index 2f225532f0..6fdf43c367 100644 --- a/packages/metro-file-map/src/lib/TreeFS.js +++ b/packages/metro-file-map/src/lib/TreeFS.js @@ -458,6 +458,7 @@ export default class TreeFS implements MutableFileSystem { subtreeOnly: boolean, }>, pathPrefix: string = '', + followedLinks: $ReadOnlySet = new Set(), ): Iterable { const pathSep = opts.alwaysYieldPosix ? '/' : path.sep; const prefixWithSep = pathPrefix === '' ? pathPrefix : pathPrefix + pathSep; @@ -498,17 +499,27 @@ export default class TreeFS implements MutableFileSystem { // Symlink goes nowhere, nothing to report. continue; } - if (!(resolved.node instanceof Map)) { + const target = resolved.node; + if (!(target instanceof Map)) { // Symlink points to a file, just yield the path of the symlink. yield nodePath; - } else if (opts.recursive && opts.follow) { + } else if ( + opts.recursive && + opts.follow && + !followedLinks.has(node) + ) { // Symlink points to a directory - iterate over its contents using // the path where we found the symlink as a prefix. - yield* this._pathIterator(resolved.node, opts, nodePath); + yield* this._pathIterator( + target, + opts, + nodePath, + new Set([...followedLinks, node]), + ); } } } else if (opts.recursive) { - yield* this._pathIterator(node, opts, nodePath); + yield* this._pathIterator(node, opts, nodePath, followedLinks); } } } diff --git a/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js b/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js index 83ca6790cf..3805c3a944 100644 --- a/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js +++ b/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js @@ -32,11 +32,13 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { rootDir: p('/project'), files: new Map([ [p('foo/another.js'), ['', 123, 0, 0, '', '', 0]], + [p('foo/owndir'), ['', 0, 0, 0, '', '', '.']], [p('foo/link-to-bar.js'), ['', 0, 0, 0, '', '', p('../bar.js')]], [p('foo/link-to-another.js'), ['', 0, 0, 0, '', '', p('another.js')]], [p('../outside/external.js'), ['', 0, 0, 0, '', '', 0]], [p('bar.js'), ['', 234, 0, 0, '', '', 0]], - [p('link-to-foo'), ['', 456, 0, 0, '', '', p('./foo')]], + [p('link-to-foo'), ['', 456, 0, 0, '', '', p('././abnormal/../foo')]], + [p('abs-link-out'), ['', 456, 0, 0, '', '', p('/outside/./baz/..')]], [p('root'), ['', 0, 0, 0, '', '', '..']], [p('link-to-nowhere'), ['', 123, 0, 0, '', '', p('./nowhere')]], [p('link-to-self'), ['', 123, 0, 0, '', '', p('./link-to-self')]], @@ -129,10 +131,12 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { [p('link-to-self'), ['', 123, 0, 0, '', '', 0]], ]), removedFiles: new Set([ + p('foo/owndir'), p('foo/link-to-bar.js'), p('foo/link-to-another.js'), p('../outside/external.js'), p('bar.js'), + p('abs-link-out'), p('root'), ]), }); @@ -162,6 +166,9 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { }), ).toEqual([ p('/project/foo/another.js'), + p('/project/foo/owndir/another.js'), + p('/project/foo/owndir/link-to-bar.js'), + p('/project/foo/owndir/link-to-another.js'), p('/project/foo/link-to-bar.js'), p('/project/foo/link-to-another.js'), ]); @@ -186,12 +193,19 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { }), ).toEqual([ p('/project/foo/another.js'), + p('/project/foo/owndir/another.js'), + p('/project/foo/owndir/link-to-bar.js'), + p('/project/foo/owndir/link-to-another.js'), p('/project/foo/link-to-bar.js'), p('/project/foo/link-to-another.js'), p('/project/bar.js'), p('/project/link-to-foo/another.js'), + p('/project/link-to-foo/owndir/another.js'), + p('/project/link-to-foo/owndir/link-to-bar.js'), + p('/project/link-to-foo/owndir/link-to-another.js'), p('/project/link-to-foo/link-to-bar.js'), p('/project/link-to-foo/link-to-another.js'), + p('/project/abs-link-out/external.js'), p('/project/root/outside/external.js'), ]); }); @@ -220,7 +234,9 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { }), ).toEqual([ p('/project/foo/another.js'), + p('/project/foo/owndir/another.js'), p('/project/link-to-foo/another.js'), + p('/project/link-to-foo/owndir/another.js'), ]); }); });