Skip to content

Commit 5942ae9

Browse files
fix: prefer closer config file over parent Java build file in monorepos (TODO-37)
Java detection in parse_config_file() short-circuited before the existing depth-comparison logic, so a parent pom.xml would override a closer package.json or pyproject.toml. Now all config sources are detected first and the closest one to CWD wins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3c63b60 commit 5942ae9

2 files changed

Lines changed: 99 additions & 7 deletions

File tree

codeflash/code_utils/config_parser.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,21 @@ def find_conftest_files(test_paths: list[Path]) -> list[Path]:
106106
def parse_config_file(
107107
config_file_path: Path | None = None, override_formatter_check: bool = False
108108
) -> tuple[dict[str, Any], Path]:
109-
# Java projects: read config from pom.xml/gradle.properties (no standalone config file needed)
110-
if config_file_path is None:
111-
java_config = _try_parse_java_build_config()
112-
if java_config is not None:
113-
config, project_root = java_config
114-
return config, project_root
115-
109+
# Detect all config sources — Java, package.json, pyproject.toml
110+
java_result = _try_parse_java_build_config() if config_file_path is None else None
116111
package_json_path = find_package_json(config_file_path)
117112
pyproject_toml_path = find_closest_config_file("pyproject.toml") if config_file_path is None else None
118113

114+
# Use Java config only if no closer JS/Python config exists (monorepo support).
115+
# In a monorepo with a parent pom.xml and a child package.json, the closer config wins.
116+
if java_result is not None:
117+
java_depth = len(java_result[1].parts)
118+
has_closer = (package_json_path is not None and len(package_json_path.parent.parts) >= java_depth) or (
119+
pyproject_toml_path is not None and len(pyproject_toml_path.parent.parts) >= java_depth
120+
)
121+
if not has_closer:
122+
return java_result
123+
119124
# When both config files exist, prefer the one closer to CWD.
120125
# This prevents a parent-directory package.json (e.g., monorepo root)
121126
# from overriding a closer pyproject.toml.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Tests for config_parser.py — monorepo language detection priority."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import os
7+
from pathlib import Path
8+
from unittest.mock import patch
9+
10+
import pytest
11+
12+
from codeflash.code_utils.config_parser import parse_config_file
13+
14+
15+
class TestMonorepoConfigPriority:
16+
"""Verify that closer config files win over parent Java build files in monorepos."""
17+
18+
def test_closer_package_json_wins_over_parent_pom_xml(self, tmp_path: Path) -> None:
19+
"""In monorepo/frontend/, a local package.json should win over a parent pom.xml."""
20+
# Parent Java project
21+
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
22+
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
23+
24+
# Child JS project
25+
frontend = tmp_path / "frontend"
26+
frontend.mkdir()
27+
(frontend / "package.json").write_text(
28+
json.dumps({"name": "frontend", "codeflash": {"moduleRoot": "src"}}),
29+
encoding="utf-8",
30+
)
31+
(frontend / "src").mkdir()
32+
33+
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
34+
mock_path_cls.cwd.return_value = frontend
35+
# find_package_json also uses Path.cwd; mock it at the source
36+
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
37+
mock_js_path_cls.cwd.return_value = frontend
38+
# Also need to let normal Path operations work
39+
mock_path_cls.side_effect = Path
40+
mock_path_cls.cwd.return_value = frontend
41+
mock_js_path_cls.side_effect = Path
42+
mock_js_path_cls.cwd.return_value = frontend
43+
44+
config, root = parse_config_file()
45+
46+
# Should detect JS, not Java
47+
assert config.get("language") != "java", (
48+
"Closer package.json should take priority over parent pom.xml"
49+
)
50+
51+
def test_java_wins_when_no_closer_js_config(self, tmp_path: Path) -> None:
52+
"""When only a pom.xml exists (no package.json/pyproject.toml closer), Java config wins."""
53+
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
54+
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
55+
56+
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
57+
mock_path_cls.side_effect = Path
58+
mock_path_cls.cwd.return_value = tmp_path
59+
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
60+
mock_js_path_cls.side_effect = Path
61+
mock_js_path_cls.cwd.return_value = tmp_path
62+
63+
config, root = parse_config_file()
64+
65+
assert config.get("language") == "java"
66+
67+
def test_same_level_package_json_wins_over_pom_xml(self, tmp_path: Path) -> None:
68+
"""When pom.xml and package.json are at the same level, package.json wins (more specific)."""
69+
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
70+
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
71+
(tmp_path / "package.json").write_text(
72+
json.dumps({"name": "mixed-project", "codeflash": {"moduleRoot": "src"}}),
73+
encoding="utf-8",
74+
)
75+
76+
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
77+
mock_path_cls.side_effect = Path
78+
mock_path_cls.cwd.return_value = tmp_path
79+
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
80+
mock_js_path_cls.side_effect = Path
81+
mock_js_path_cls.cwd.return_value = tmp_path
82+
83+
config, root = parse_config_file()
84+
85+
assert config.get("language") != "java", (
86+
"Same-level package.json should take priority over pom.xml"
87+
)

0 commit comments

Comments
 (0)