Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/ansiblelint/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,17 @@ def kind_from_path(path: Path, *, base: bool = False) -> FileType:
When called with base=True, it will return the base file type instead
of the explicit one. That is expected to return 'yaml' for any yaml files.
"""
# pathlib.Path.match patterns are very limited, they do not support *a*.yml
# glob.glob supports **/foo.yml but not multiple extensions
pathex = wcmatch.pathlib.PurePath(str(path.absolute().resolve()))
# We attempt to use a relative path to the project root for glob matching.
# This prevents parent directory names (like 'tasks') from triggering
# false positives in kind discovery. See #4763.
try:
project_root, _ = find_project_root([str(path)])
# .resolve() ensures we handle symlinks and double-dots correctly
rel_path = path.resolve().relative_to(project_root.resolve())
pathex = wcmatch.pathlib.PurePath(str(rel_path))
except (ValueError, RuntimeError):
# Fallback to absolute if the file is outside the project root or can't be found
pathex = wcmatch.pathlib.PurePath(str(path.absolute().resolve()))
kinds = options.kinds if not base else BASE_KINDS
for entry in kinds:
for k, v in entry.items():
Expand Down
53 changes: 53 additions & 0 deletions test/test_file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
expand_path_vars,
expand_paths_vars,
find_project_root,
kind_from_path,
normpath,
normpath_path,
)
Expand Down Expand Up @@ -543,3 +544,55 @@ def test_bug_2513(
results = Runner(filename, rules=default_rules_collection).run()
assert len(results) == 1
assert results[0].rule.id == "name"


def test_kind_from_path_parent_collision(tmp_path: Path) -> None:
"""Verify that a parent directory named 'tasks' doesn't cause false positives.

See https://github.com/ansible/ansible-lint/issues/4763
"""
tasks_parent = tmp_path / "tasks"
project_dir = tasks_parent / "my_project"
project_dir.mkdir(parents=True)

(project_dir / ".git").mkdir()

playbook_path = project_dir / "site.yml"
playbook_path.touch()

kind = kind_from_path(playbook_path)

assert kind != "tasks", f"File {playbook_path} should not be identified as 'tasks'"


def test_kind_from_path_valid_tasks(tmp_path: Path) -> None:
"""Verify that legitimate tasks directories are still correctly identified."""
project_dir = tmp_path / "my_project"
project_dir.mkdir()
(project_dir / ".git").mkdir()

task_dir = project_dir / "tasks"
task_dir.mkdir()
task_path = task_dir / "main.yml"
task_path.touch()

kind = kind_from_path(task_path)

assert kind == "tasks", f"File {task_path} should be identified as 'tasks'"


def test_kind_from_path_outside_project_root(tmp_path: Path) -> None:
"""Verify fallback to absolute path when file is outside the project root.

This triggers the except block in kind_from_path
"""
project_dir = tmp_path / "actual_project"
project_dir.mkdir()
(project_dir / ".git").mkdir()

outside_file = tmp_path / "external_file.yml"
outside_file.touch()

kind = kind_from_path(outside_file)

assert kind == "yaml"
Loading