package dunolint
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=1b064927c9e1ef5352a1886ae34a206fef0ce6a913c19a77b0162acc108e0e50
sha512=6cbc08ba318bef6584d15a4491e3dde1bf436109ce0f8b7c400a9f91bbcee64c5785bc924df11eafe98243ec2f188a7f92c58c5062729f3e2af1e9977f1a5e67
doc/dunolint.dunolint_engine/Dunolint_engine/Private/Path_in_workspace/index.html
Module Private.Path_in_workspace
Source
Path operations for workspace-relative paths with escaping prevention.
This module is exported for testing purposes. See Path_in_workspace
for documentation.
Paths relative to the workspace root, with escaping path prevention.
This module wraps Relative_path.t
to provide path operations specific to dunolint's workspace traversal, with additional safety guarantees for escaping paths.
Purpose
When traversing a dune workspace, dunolint needs to work with paths relative to the workspace root. This module provides:
- Safe path operations that prevent escaping the workspace root
- Validation that paths don't contain upward-escaping
".."
segments - Workspace-aware wrappers around
Relative_path
operations
Escaping Paths
An escaping path is a relative path that, after Fpath
normalization, contains leading ".."
segments. These segments indicate the path escapes upward past its starting point.
Examples of escaping paths (all rejected by this module):
".."
- escapes upward by one level"../config"
- escapes upward then descends"a/../.."
- normalizes to".."
, which escapes upward"../../../etc/passwd"
- escapes multiple levels upward
Why reject escaping paths?
- Workspace boundary enforcement: Paths in a dune workspace should reference locations within that workspace. Escaping paths reference locations outside the workspace root, which violates this invariant.
- Memory safety: In previous versions, operations like
parent
could create unbounded escaping paths when called repeatedly on the empty path, leading to memory growth bugs. By rejecting escaping paths at construction time, these bugs are prevented. - Semantic clarity: Escaping paths have ambiguous meaning without additional context about where the "starting point" is. By requiring all workspace paths to be non-escaping, the semantics are clear: they're paths relative to the workspace root.
Relationship to fpath-base
This module anticipates upcoming changes to the Relative_path
module in the fpath-base library (see fpath-base v0.4.0+). The upstream library will:
- Reject escaping paths in
Relative_path.of_fpath
,Relative_path.of_string
, etc. - Make
Relative_path.parent
returnNone
for the empty path instead of creating"../"
- Add runtime checks in operations like
Relative_path.extend
to prevent creating escaping paths
For the time being, we implement our own wrapper that provides these guarantees, using check_escape_path_exn
to validate paths. When the upstream changes are released, this module can be simplified to rely on the upstream guarantees.
See fpath-base/doc/docs/explanation/path-normalization.md
for detailed documentation of the upstream approach.
Migration Note
Once fpath-base v0.4.0+ is released with escaping path rejection built-in, the explicit check_escape_path_exn
calls in this module can be removed, as the upstream Relative_path
module will guarantee that no escaping paths can be constructed.
check_escape_path_exn t
validates that path t
does not escape upward.
Raises Invalid_argument
if t
contains leading ".."
segments after normalization (i.e., if it is an escaping path).
This function is used internally to validate results of path operations. It will become unnecessary once fpath-base v0.4.0+ guarantees that Relative_path.t
values cannot be escaping paths.
Examples:
These would raise Invalid_argument
:
check_escape_path_exn (Relative_path.v "..");
(* escapes upward *)
check_escape_path_exn (Relative_path.v "a/../..")
(* normalizes to ".." *)
These are OK:
check_escape_path_exn (Relative_path.v "a/b");
(* descends only *)
check_escape_path_exn (Relative_path.v "a/../b")
(* normalizes to "b" *)
chop_prefix t ~prefix
removes the prefix prefix
from path t
.
Returns:
Some result
whereresult
ist
withprefix
removed from the startSome t
(unchanged) whenprefix
isempty
- removing nothing returns the original pathNone
ifprefix
is not actually a prefix oft
Note: This operation works on path segments, not string prefixes. For example, "foo/bar-baz"
does not have prefix "foo/bar"
.
parent t
returns the parent directory of path t
, or None
if t
has no parent.
Returns None
when:
t
is equal toempty
(the path"./"
)
Raises Invalid_argument
if t
is an escaping path (contains leading ".."
after normalization). This should not occur for paths constructed through this module's API, as escaping paths are rejected during construction.
Note: This behavior prevents infinite loops that occurred in previous versions where parent empty
would return "../"
, creating paths that escape unboundedly.
If you need to navigate upward through parent directories (including above the starting point), use Absolute_path.parent
or work with Fpath.t
directly.
ancestors_autoloading_dirs ~path
returns all ancestor directories of path
, from the workspace root down to the parent of path
.
This function is specifically designed for config autoloading: it returns the list of directories that should be checked for dunolint configuration files when linting a file at path
.
The returned list is ordered from root to deepest ancestor (i.e., from shortest to longest paths), which matches the order in which configs should be loaded and accumulated.
Returns []
when:
path
is equal toempty
(the path"./"
)
Raises Invalid_argument
if path
is an escaping path (contains leading ".."
after normalization).
Examples:
Linting file "a/b/c.ml"
should check configs in:
ancestors_autoloading_dirs ~path:(v "a/b/c.ml")
Returns: ["./"; "a/"; "a/b/"]
Linting file "file.ml"
at workspace root checks root config:
ancestors_autoloading_dirs ~path:(v "file.ml")
Returns: ["./"]
.
Empty path has no ancestors:
ancestors_autoloading_dirs ~path:empty
Returns: []
This function is used internally by the engine when linting individual files to discover which configuration files should be loaded from ancestor directories.
paths_to_check_for_skip_predicates ~path
returns paths to check against skip predicates during tree traversal, including the path itself.
Returns parent directories plus the path itself, ordered from root to deepest. The workspace root "./"
is never included in the results.
Returns []
when path
is equal to empty
(the path "."
).
Raises Invalid_argument
if path
is an escaping path (contains leading ".."
after normalization).
Examples:
File paths return parent directories and the file itself:
paths_to_check_for_skip_predicates ~path:(v "foo/bar/bin")
(* Returns: ["foo/"; "foo/bar/"; "foo/bar/bin"] *)
Directory paths (trailing "/"
) include parent directories and the directory itself:
paths_to_check_for_skip_predicates ~path:(v "foo/bar/bin/")
(* Returns: ["foo/"; "foo/bar/"; "foo/bar/bin/"] *)
Single files at the root return just the file:
paths_to_check_for_skip_predicates ~path:(v "file.ml")
(* Returns: ["file.ml"] *)
Workspace root returns empty:
paths_to_check_for_skip_predicates ~path:empty
(* Returns: [] *)
This function is used when checking if paths match skip predicates in already-loaded configs. The path itself is included so that skip predicates can be checked against both the path and its ancestors.