The internal implementation of TreeFS uses a tree of maps representing file path segments, with a "root" node at the project root. For paths outside the project root, we traverse through '..' nodes, so that ../outside traverses from the project root through '..' and 'outside'. Basing the representation around the project root (as opposed to a volume root) minimises traversal through irrelevant paths and makes the cache portable.
Problem
However, because this map of maps is a simple (acyclic) tree, a '..' node has no entry for one of its logical (=on disk) children - the node closer to the project root. With a project root /foo/bar, the project-root-relative path ../bar cannot be traversed, because '..' is a key of 'bar' and not vice-versa.
For lookups, if instead of a literal indirection we have a symlink 'link-to-parent/bar', normalisation cannot (and should not) collapse away the symlink - so after dereferencing the symlink during traversal to '..' and joining it with the remaining bar we are unable to lookup '../bar'. For the module resolver this might appear as a failed existence check, and cause a resolution failure.
This fix
When dereferencing a symlink as part of _lookupByNormalPath, we now join the symlink target to the remaining subpath using a (mostly pre-exisitng) utility function that is aware of the project root, and can collapse away the project root segments. This leaves a normalised, root-relative target path that's guaranteed not to leave and re-enter the project root.
Changelog:
- **[Fix]**: Fix some paths being unresolvable when traversing a symlink that points to an ancestor of the project root.
Summary:
Background
The internal implementation of
TreeFS
uses a tree of maps representing file path segments, with a "root" node at the project root. For paths outside the project root, we traverse through'..'
nodes, so that../outside
traverses from the project root through'..'
and'outside'
. Basing the representation around the project root (as opposed to a volume root) minimises traversal through irrelevant paths and makes the cache portable.Problem
However, because this map of maps is a simple (acyclic) tree, a
'..'
node has no entry for one of its logical (=on disk) children - the node closer to the project root. With a project root/foo/bar
, the project-root-relative path../bar
cannot be traversed, because'..'
is a key of'bar'
and not vice-versa.This mostly isn't a problem because
'../bar'
is not a normal path - normalisation collapses this down to''
: https://github.com/facebook/metro/blob/6856d00cfdddc1dd5ed9ab35249fe7ca1610ca75/packages/metro-file-map/src/lib/RootPathUtils.js#L142-L148Observable bug
For lookups, if instead of a literal indirection we have a symlink
'link-to-parent/bar'
, normalisation cannot (and should not) collapse away the symlink - so after dereferencing the symlink during traversal to'..'
and joining it with the remainingbar
we are unable to lookup'../bar'
. For the module resolver this might appear as a failed existence check, and cause a resolution failure.This fix
When dereferencing a symlink as part of
_lookupByNormalPath
, we now join the symlink target to the remaining subpath using a (mostly pre-exisitng) utility function that is aware of the project root, and can collapse away the project root segments. This leaves a normalised, root-relative target path that's guaranteed not to leave and re-enter the project root.Changelog:
Reviewed By: huntie
Differential Revision: D57719224