rlsbl v0.60.1 /rlsbl.dep_validation
On this page

Dependency validation for monorepo workspaces that detects unused declared dependencies and undeclared imports across workspace projects.

#rlsbl.dep_validation

#rlsbl.dep_validation

Dependency validation for monorepo workspaces.

Checks for unused declared dependencies and undeclared imports across workspace projects. Uses import scanners to compare actual source imports against manifest-declared dependencies.

#load_dep_overrides

python
def load_dep_overrides(root: str) -> dict[tuple[str, str], str]

Load dep-overrides.toml from the monorepo config directory.

Returns a dict mapping (package, dep) to reason string. Raises ValueError if an entry is missing a required 'reason' field. Returns empty dict if the file does not exist.

#_get_imported_workspace_packages

python
def _get_imported_workspace_packages(project_dir: str, workspace_names: set[str], exclude_dirs: list[str] | None=None, *, module_path_map: dict[str, str] | None=None) -> tuple[set[str], set[str]]

Scan a project for workspace imports, split by context.

Args:

  • project_dir: absolute path to the project root.
  • workspace_names: set of all workspace member package names.
  • exclude_dirs: directory paths to skip during the walk.
  • module_path_map: mapping of workspace project name to its Go

module path (from go.mod). Passed through to GoImportScanner.

Returns (lib_imports, test_imports) where each is a set of workspace package names found in lib/test contexts respectively.

#check_unused_deps

python
def check_unused_deps(project_name: str, project_dir: str, manifest_deps: set[str], workspace_names: set[str], whitelist: dict[tuple[str, str], str], *, _cached_imports: tuple[set[str], set[str]] | None=None) -> list[str]

Check for declared workspace deps that no source file imports.

Args:

  • project_name: name of the project being checked.
  • project_dir: absolute path to the project directory.
  • manifest_deps: set of declared intra-workspace dependency names.
  • workspace_names: set of all workspace member package names.
  • whitelist: mapping of (package, dep) -> reason for allowed unused deps.
  • _cached_imports: optional pre-computed (lib_imports, test_imports) tuple

to avoid redundant scans when multiple checks share the same project.

Returns:

  • list of error strings (empty means all good).

#check_undeclared_deps

python
def check_undeclared_deps(project_name: str, project_dir: str, manifest_deps: set[str], workspace_names: set[str], *, _cached_imports: tuple[set[str], set[str]] | None=None) -> list[str]

Check for imports from workspace packages not declared as deps.

Only checks lib/ imports (non-test context) against declared dependencies. Test files have more lenient rules and are skipped.

Args:

  • project_name: name of the project being checked.
  • project_dir: absolute path to the project directory.
  • manifest_deps: set of declared intra-workspace dependency names.
  • workspace_names: set of all workspace member package names.
  • _cached_imports: optional pre-computed (lib_imports, test_imports) tuple

to avoid redundant scans when multiple checks share the same project.

Returns:

  • list of error strings (empty means all good).

#check_runtime_test_only

python
def check_runtime_test_only(manifest_deps_with_scope: dict[str, str], lib_imports: set[str], test_imports: set[str]) -> list[str]

Find runtime deps that are only used in test code.

For each dependency where scope="runtime": if it appears in test_imports but NOT in lib_imports, it is flagged.

Args:

  • manifest_deps_with_scope: mapping of dep name -> scope string.
  • lib_imports: workspace package names found in production code.
  • test_imports: workspace package names found in test code.

Returns:

  • list of flagged dependency names.

#check_dev_in_lib

python
def check_dev_in_lib(manifest_deps_with_scope: dict[str, str], lib_imports: set[str]) -> list[str]

Find dev deps that are imported in production code.

For each dependency where scope="dev": if it appears in lib_imports, it is flagged.

Args:

  • manifest_deps_with_scope: mapping of dep name -> scope string.
  • lib_imports: workspace package names found in production code.

Returns:

  • list of flagged dependency names.

#_is_non_production_path

python
def _is_non_production_path(filepath: str, project_dir: str) -> bool

Check if a file path is in a non-production context.

Uses _NON_PRODUCTION_PATTERNS from import_scanners.py to detect test directories, example directories, and test file patterns.

#_python_module_name

python
def _python_module_name(filepath: str, project_dir: str) -> str | None

Derive the dotted module name from a Python file path.

Returns None for files that cannot be mapped to a module name (e.g. files outside a package structure).

#_collect_python_imports

python
def _collect_python_imports(filepath: str, project_dir: str) -> set[str]

Collect all import targets from a Python file.

Returns a set of dotted module names that are imported. Handles absolute imports (import foo, from foo.bar import baz) and relative imports (from .utils import helper, from ..core import x). Relative imports are resolved to absolute dotted paths using the file's position within the project directory.

#_collect_init_exports

python
def _collect_init_exports(filepath: str) -> set[str]

Collect names exported from an __init__.py file.

Looks for __all__ definitions and import statements. Returns module names that are imported by the __init__.py.

#find_dead_modules

python
def find_dead_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Python modules not referenced by any other module in the project.

A module is considered dead if:

  1. No other module in the project imports it (by any prefix match)
  2. It is not listed in any __init__.py's __all__ or imported by

any __init__.py

Only checks Python projects. Non-production files (tests, examples) are excluded from the scan.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • list of relative paths of dead modules (e.g. ["mylib/unused.py"]).

#_read_go_module_path

python
def _read_go_module_path(project_dir: str) -> str | None

Read the module path from go.mod.

Returns None if go.mod does not exist or cannot be parsed.

#_go_package_dir

python
def _go_package_dir(filepath: str) -> str

Return the directory of a Go file (its package directory).

#find_dead_go_packages

python
def find_dead_go_packages(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Go internal packages not referenced by any non-test code.

A Go internal package is dead if no non-test .go file outside that package imports it. Only packages under internal/ subdirectories are checked, since those are the packages with restricted visibility in Go's module system.

Args:

  • project_dir: absolute path to the Go project root (where go.mod lives).
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • list of relative paths of dead internal packages
  • (e.g. ["internal/unused"]).

#_resolve_npm_file

python
def _resolve_npm_file(path: str) -> str | None

Resolve a single path to an existing file using npm conventions.

Tries the exact path, then with each extension appended, then as a directory with index files. Also handles .js -> .ts mapping for TypeScript projects.

Returns the absolute file path if found, None otherwise.

#_collect_export_paths

python
def _collect_export_paths(value: object) -> list[str]

Recursively collect all file path strings from a package.json exports value.

The exports field can be:

  • A string: "./dist/index.js"
  • A dict with condition keys: {"import": "./dist/index.mjs", "require": "./dist/index.cjs"}
  • A nested subpath map: {".": {"import": "..."}, "./sub": "..."}
  • A list (rarely): ["./a.js", "./b.js"]

Collects all string values that look like file paths (start with ".").

#_resolve_npm_entry_points

python
def _resolve_npm_entry_points(project_dir: str) -> set[str]

Extract and resolve entry point file paths from package.json.

Parses exports, main, and bin fields. Resolves each declared path to an absolute filesystem path, handling .js -> .ts mapping and directory -> index file resolution.

Returns a set of absolute file paths. Missing files are skipped.

#_build_npm_import_graph

python
def _build_npm_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for an npm project.

Uses NpmAstLinter.scan_imports() to collect all imports, then resolves relative imports to absolute file paths.

Returns a dict mapping each source file's absolute path to a set of absolute paths it imports (only resolved relative imports).

#find_dead_npm_modules

python
def find_dead_npm_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find npm source files unreachable from any package.json entry point.

A source file is "dead" if there is no path through the import graph from any declared entry point (exports, main, bin) to that file.

Args:

  • project_dir: absolute path to the npm project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • sorted list of relative paths of dead source files.

#_read_dart_package_name

python
def _read_dart_package_name(project_dir: str) -> str | None

Read the package name from pubspec.yaml.

Returns None if pubspec.yaml does not exist or has no 'name' field.

#_resolve_dart_entry_points

python
def _resolve_dart_entry_points(project_dir: str) -> set[str]

Determine Dart entry point files for reachability analysis.

Entry points are:

  • lib/.dart (barrel file / main library entry)
  • bin/*.dart (executable scripts)

Returns a set of absolute file paths.

#_resolve_dart_import

python
def _resolve_dart_import(specifier: str, importing_file: str, project_dir: str, package_name: str | None) -> str | None

Resolve a Dart import specifier to an absolute file path.

Handles:

  • Relative imports: 'src/foo.dart', '../utils.dart'
  • Self-package imports: 'package:mylib/src/foo.dart' -> lib/src/foo.dart

Returns absolute path if resolved, None otherwise. Skips dart: imports and external package: imports.

#_build_dart_import_graph

python
def _build_dart_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for a Dart project.

Walks all .dart files, extracts import/export statements via regex, and resolves relative and self-package imports to absolute paths.

Returns a dict mapping each source file's absolute path to a set of absolute paths it imports/exports (only resolved intra-package refs).

#find_dead_dart_modules

python
def find_dead_dart_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Dart source files unreachable from any entry point.

A .dart file is "dead" if there is no path through the import/export graph from any entry point (barrel file, bin scripts) to that file.

Test files (test/, *_test.dart) are excluded from the scan.

Args:

  • project_dir: absolute path to the Dart project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • sorted list of relative paths of dead source files.

#DeadWorkspacePackage

A workspace package with no workspace importers.

#find_dead_workspace_packages

python
def find_dead_workspace_packages(projects: list[dict], import_cache: dict[str, tuple[set[str], set[str]]]) -> list[DeadWorkspacePackage]

Find library packages that no other workspace package imports.

A library package is "dead" at the workspace level if its name does not appear in any other project's lib_imports or test_imports sets.

Args:

  • projects: list of workspace project dicts (must have "name",

and optionally "library" and "dev_node" keys).

  • import_cache: mapping of project name to (lib_imports, test_imports)

as produced by _build_dep_import_cache in checks.py.

Returns:

  • list of DeadWorkspacePackage for packages with no workspace importers.

#find_circular_deps

python
def find_circular_deps(import_graph: dict[str, set[str]]) -> list[list[str]]

Find circular dependencies in a file-level import graph using Tarjan's SCC.

Args:

  • import_graph: mapping of file path to the set of file paths it imports.

Returns:

  • list of cycles, where each cycle is a list of file paths forming
  • the strongly connected component. Only SCCs with 2+ nodes are
  • returned (self-loops are not interesting).

#_build_python_import_graph

python
def _build_python_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for a Python project.

Uses _collect_python_imports to get dotted module names, then resolves them to file paths via a module-name-to-file mapping.

Returns a dict mapping each source file's relative path to a set of relative paths it imports (only intra-project imports that resolve to actual files).

#find_circular_python_deps

python
def find_circular_python_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in a Python project.

Builds a file-level import graph from Python source files and runs Tarjan's SCC algorithm to detect cycles.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.

#find_circular_npm_deps

python
def find_circular_npm_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in an npm project.

Reuses _build_npm_import_graph() and runs Tarjan's SCC algorithm.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.

#find_circular_dart_deps

python
def find_circular_dart_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in a Dart project.

Builds a file-level import graph from Dart source files using regex to extract relative imports, then runs Tarjan's SCC algorithm.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.