add uncommitted files
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
from fnmatch import fnmatch
|
||||
from pathlib import Path
|
||||
|
||||
CPP_SUFFIXES = frozenset({".c", ".cc", ".cpp", ".cxx", ".h", ".hpp", ".hxx"})
|
||||
DEFAULT_EXCLUDES = (
|
||||
".harness/build/**",
|
||||
"build/**",
|
||||
"out/**",
|
||||
"cmake-build-*/**",
|
||||
"third_party/**",
|
||||
"external/**",
|
||||
"vendor/**",
|
||||
"generated/**",
|
||||
)
|
||||
|
||||
|
||||
def is_excluded_path(path: Path, root: Path, extra: tuple[str, ...] = ()) -> bool:
|
||||
try:
|
||||
relative = path.resolve().relative_to(root.resolve()).as_posix()
|
||||
except ValueError:
|
||||
return False
|
||||
return any(fnmatch(relative, pattern) for pattern in DEFAULT_EXCLUDES + extra)
|
||||
|
||||
|
||||
def find_cpp_files(
|
||||
root: Path,
|
||||
extra_excludes: tuple[str, ...] = (),
|
||||
) -> tuple[Path, ...]:
|
||||
found = (
|
||||
path
|
||||
for path in root.resolve().rglob("*")
|
||||
if path.is_file()
|
||||
and path.suffix.lower() in CPP_SUFFIXES
|
||||
and not is_excluded_path(path, root, extra_excludes)
|
||||
)
|
||||
return tuple(sorted(found))
|
||||
|
||||
|
||||
def is_test_file(path: Path, root: Path, config) -> bool:
|
||||
resolved = path.resolve()
|
||||
if any(root == resolved or root in resolved.parents for root in config.test_roots):
|
||||
return True
|
||||
relative = resolved.relative_to(root.resolve())
|
||||
if {"test", "tests"} & {part.lower() for part in relative.parts}:
|
||||
return True
|
||||
stem = resolved.stem.lower()
|
||||
return stem.startswith("test_") or stem.endswith(("_test", "_tests", ".test"))
|
||||
|
||||
|
||||
def matching_test_exists(path: Path, config) -> bool:
|
||||
names = tuple(pattern.format(stem=path.stem) for pattern in config.test_patterns)
|
||||
roots = (
|
||||
*config.test_roots,
|
||||
path.parent / "tests",
|
||||
path.parent / "test",
|
||||
)
|
||||
for root in roots:
|
||||
if not root.is_dir():
|
||||
continue
|
||||
for name in names:
|
||||
if any(candidate.is_file() for candidate in root.rglob(name)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def evaluate_paths(paths, root: Path, config) -> str | None:
|
||||
root = root.resolve()
|
||||
for raw in paths:
|
||||
path = raw.resolve()
|
||||
try:
|
||||
path.relative_to(root)
|
||||
except ValueError:
|
||||
return f"TDD GUARD: '{path}' is outside the repository"
|
||||
if path.suffix.lower() not in CPP_SUFFIXES:
|
||||
continue
|
||||
if (
|
||||
path.name.lower() == "main.cpp"
|
||||
or is_excluded_path(path, root, config.exclude)
|
||||
or is_test_file(path, root, config)
|
||||
):
|
||||
continue
|
||||
if not matching_test_exists(path, config):
|
||||
expected = config.test_patterns[0].format(stem=path.stem)
|
||||
return (
|
||||
f"TDD GUARD: '{path.name}' requires an existing test such as "
|
||||
f"'{expected}'. Add the test in a configured test root first."
|
||||
)
|
||||
return None
|
||||
Reference in New Issue
Block a user