From c69a1a8747aa47d07eb711c7d3c6840231f9a82e Mon Sep 17 00:00:00 2001 From: Yunare Maia Date: Fri, 4 Sep 2026 17:18:05 +0000 Subject: [PATCH 1/2] feat(detector): add Maven pom.xml drift detection (v0.1.10) Detects Java version drift between pom.xml (java.version, maven.compiler.source/target, release) and README mentions. Major-version comparison. 7 new tests covering parsing, no-drift, drift detection, and scan integration. --- README.md | 3 ++- pyproject.toml | 2 +- src/driftcheck/cli.py | 7 +++++-- src/driftcheck/detector.py | 42 ++++++++++++++++++++++++++++++++++++++ tests/test_detector.py | 41 +++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e217ed..ad8d3a9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ driftcheck --json # machine-readable driftcheck --fix # auto-fix drifts in documentation files ``` -Checks (v0.1.9): +Checks (v0.1.10): +- Maven: `pom.xml` `java.version`, `maven.compiler.source`, `maven.compiler.target`, `release` vs README mentions — major-version comparison - Docker: `Dockerfile` `FROM :` vs `README.md` / `docs/README*.md` / `CONTRIBUTING*.md` — handles variant tags (`24` matches `24-slim`, `24-alpine`), multi-stage builds (`FROM golang:1.23 AS builder` → `FROM alpine:3.21`) - Java/Gradle: `build.gradle` `sourceCompatibility`, `jvmTarget`, `JavaVersion.VERSION_*` vs README mentions - Rust: `rust-toolchain.toml` `channel` **and** `Cargo.toml` `rust-version` vs `README.md` / `docs/README*.md` / `CONTRIBUTING*.md` diff --git a/pyproject.toml b/pyproject.toml index 755af65..0668579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "driftcheck" -version = "0.1.9" +version = "0.1.10" description = "Detect version drift between docs and toolchain files (README vs Dockerfile, build.gradle, rust-toolchain.toml, package.json, etc.)" readme = "README.md" requires-python = ">=3.10" diff --git a/src/driftcheck/cli.py b/src/driftcheck/cli.py index 6b2e9bc..68ec0f3 100644 --- a/src/driftcheck/cli.py +++ b/src/driftcheck/cli.py @@ -37,17 +37,18 @@ def main(argv=None) -> int: external_resource_drifts = result.get("external_resource_drifts", []) docker_drifts = result.get("docker_drifts", []) java_drifts = result.get("java_drifts", []) + maven_drifts = result.get("maven_drifts", []) tv = result.get("toolchain_version") cv = result.get("cargo_rust_version") nv = result.get("package_node") pv = result.get("pyproject_python") gv = result.get("gomod_version") - if not tv and not cv and not nv and not pv and not gv and not count_drifts and not actions_drifts and not lineending_drifts and not external_resource_drifts and not docker_drifts and not java_drifts: + if not tv and not cv and not nv and not pv and not gv and not count_drifts and not actions_drifts and not lineending_drifts and not external_resource_drifts and not docker_drifts and not java_drifts and not maven_drifts: print("driftcheck: no toolchain version found") return 0 - if not drifts and not rust_drifts and not node_drifts and not python_drifts and not go_drifts and not count_drifts and not actions_drifts and not lineending_drifts and not docker_drifts and not java_drifts: + if not drifts and not rust_drifts and not node_drifts and not python_drifts and not go_drifts and not count_drifts and not actions_drifts and not lineending_drifts and not docker_drifts and not java_drifts and not maven_drifts: parts = [] if tv: parts.append(f"Rust {tv}") if cv: parts.append(f"Rust(Cargo) {cv}") @@ -86,6 +87,8 @@ def main(argv=None) -> int: print(f"driftcheck: {d['file']}: {d['doc_image']} → should be {d['dockerfile_image']} (Dockerfile)") for d in java_drifts: print(f"driftcheck: {d['file']}: Java {d['doc_version']} → should be {d['gradle_version']} (build.gradle)") + for d in maven_drifts: + print(f"driftcheck: {d['file']}: Java {d['doc_version']} → should be {d['maven_version']} (pom.xml)") return 1 if __name__ == "__main__": diff --git a/src/driftcheck/detector.py b/src/driftcheck/detector.py index 93437e8..a1646d1 100644 --- a/src/driftcheck/detector.py +++ b/src/driftcheck/detector.py @@ -460,6 +460,39 @@ def find_java_drift(gradle_text: str, docs: dict[str, str]) -> list[dict]: return drifts +# --------------------------------------------------------------------------- +# Maven drift: pom.xml version vs README +# --------------------------------------------------------------------------- +MAVEN_VER_RE = re.compile(r'<(?:java\.version|maven\.compiler\.source|maven\.compiler\.target|release)>(?P\d+(?:\.\d+)?)\d+(?:\.\d+)?)', re.I) + +def parse_maven_java_version(text: str) -> str | None: + """Parse Java version from pom.xml java.version or maven.compiler.source.""" + m = MAVEN_VER_RE.search(text) + return m.group("ver") if m else None + +def find_maven_drift(pom_text: str, docs: dict[str, str]) -> list[dict]: + """Detect drift between pom.xml Java version and README mentions.""" + mv = parse_maven_java_version(pom_text) + if not mv: + return [] + drifts = [] + for fname, content in docs.items(): + for m in MAVEN_DOC_RE.finditer(content): + dv = m.group("ver") + dv_major = dv.split(".")[0] + mv_major = mv.split(".")[0] + if dv_major != mv_major: + drifts.append({ + "file": fname, + "doc_version": dv, + "maven_version": mv, + "pos": m.start(), + }) + break + return drifts + + def apply_fixes(root: Path, result: dict) -> list[str]: """Apply fixes for all detected drifts. Returns list of fixed file paths.""" fixed = [] @@ -590,6 +623,13 @@ def scan_repo(root: Path = Path(".")) -> dict: if p.is_file(): gradle_files[str(p.relative_to(root))] = p.read_text(encoding="utf-8", errors="replace") + # Maven pom.xml files + maven_files = {} + for pattern in ["pom.xml", "maven/pom.xml"]: + for p in root.glob(pattern): + if p.is_file(): + maven_files[str(p.relative_to(root))] = p.read_text(encoding="utf-8", errors="replace") + rust_drifts = find_rust_drift(toolchain_text, docs) rust_drifts_multi = find_rust_drift_multi(toolchain_text, cargo_text, docs) node_drifts = find_node_drift(package_text, docs) @@ -601,6 +641,7 @@ def scan_repo(root: Path = Path(".")) -> dict: external_resource_drifts = find_external_resource_drift(root) docker_drifts = find_docker_drift(dockerfiles, docs) java_drifts = find_java_drift("\n".join(gradle_files.values()), docs) + maven_drifts = find_maven_drift("\n".join(maven_files.values()), docs) return { "toolchain_version": parse_toolchain_version(toolchain_text), @@ -619,4 +660,5 @@ def scan_repo(root: Path = Path(".")) -> dict: "external_resource_drifts": external_resource_drifts, "docker_drifts": docker_drifts, "java_drifts": java_drifts, + "maven_drifts": maven_drifts, } \ No newline at end of file diff --git a/tests/test_detector.py b/tests/test_detector.py index 7cb25c9..2a70614 100644 --- a/tests/test_detector.py +++ b/tests/test_detector.py @@ -408,6 +408,47 @@ def test_java_detects_drift(): def test_java_no_gradle_returns_empty(): assert find_java_drift("", {"README.md": "Java 17"}) == [] + +# ---- Maven drift tests ---- +from driftcheck.detector import find_maven_drift, parse_maven_java_version + +def test_parse_maven_java_version(): + pom = "17" + assert parse_maven_java_version(pom) == "17" + +def test_parse_maven_compiler_source(): + pom = "21" + assert parse_maven_java_version(pom) == "21" + +def test_parse_maven_no_java_info(): + pom = "com.example" + assert parse_maven_java_version(pom) is None + +def test_maven_no_drift(): + pom = "17" + docs = {"README.md": "Requires Java 17"} + assert find_maven_drift(pom, docs) == [] + +def test_maven_detects_drift(): + pom = "17" + docs = {"README.md": "Requires Java 11"} + drifts = find_maven_drift(pom, docs) + assert len(drifts) == 1 + assert drifts[0]["doc_version"] == "11" + assert drifts[0]["maven_version"] == "17" + +def test_maven_no_pom_returns_empty(): + assert find_maven_drift("", {"README.md": "Java 17"}) == [] + +def test_maven_drift_included_in_scan(): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + (root / "pom.xml").write_text("21") + (root / "README.md").write_text("Requires Java 17 to build") + result = scan_repo(root) + assert "maven_drifts" in result + assert len(result["maven_drifts"]) == 1 + def test_java_drift_included_in_scan(): with tempfile.TemporaryDirectory() as td: root = Path(td) From 38b2120011ef5c1a91f0308c4e075e54521f51dd Mon Sep 17 00:00:00 2001 From: Yunare Maia Date: Fri, 4 Sep 2026 17:18:48 +0000 Subject: [PATCH 2/2] chore: bump __version__ to 0.1.10 --- src/driftcheck/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/driftcheck/__init__.py b/src/driftcheck/__init__.py index 8582149..708c513 100644 --- a/src/driftcheck/__init__.py +++ b/src/driftcheck/__init__.py @@ -1,2 +1,2 @@ """driftcheck — detect version drift between docs and toolchain.""" -__version__ = "0.1.3" +__version__ = "0.1.10"