Add and update name convention
Some checks failed
Continuous Integration / Code Formatting (push) Successful in 30s
Continuous Integration / Code Quality Check (push) Successful in 30s
Continuous Integration / Test Execution (push) Failing after 17s
Continuous Integration / CI Summary (push) Failing after 4s

This commit is contained in:
2025-09-30 00:09:55 +04:00
parent 61951a047b
commit 5275c5ca94
57 changed files with 455 additions and 68 deletions

View File

@@ -11,6 +11,7 @@ Usage examples:
python tools/run_development.py --ruff # Only Python format & lint
python tools/run_development.py --validate # Only file validation
python tools/run_development.py --validate --silent # Silent validation (only errors)
python tools/run_development.py --naming # Only naming convention check
python tools/run_development.py --test --yaml # Test results in YAML format
python tools/run_development.py --steps lint ruff test # Custom workflow
python tools/run_development.py --async-mode # Force async mode
@@ -20,6 +21,7 @@ Features:
- Python formatting & linting with Ruff (fast formatter + linter with auto-fix)
- Test execution
- YAML, TOML, and JSON file validation (respects .gitignore)
- PascalCase naming convention checks for .tscn and .gd files
- Colored output and comprehensive error reporting
- Machine-readable YAML output for CI/CD integration
- **Async processing for significantly faster execution with multiple files**
@@ -622,6 +624,57 @@ def validate_json_file(file_path: Path) -> Tuple[bool, str]:
return False, f"Error reading file: {e}"
def check_naming_convention(file_path: Path) -> Tuple[bool, str]:
"""Check if file follows PascalCase naming convention for .tscn and .gd files."""
file_name = file_path.name
# Skip files that shouldn't follow PascalCase
if file_path.suffix not in [".tscn", ".gd"]:
return True, ""
# Skip certain directories and files
skip_dirs = {"autoloads", "helpers"}
if any(part in skip_dirs for part in file_path.parts):
return True, ""
# Skip project-specific files that are exempt
exempt_files = {
"project.godot",
"icon.svg",
"export_presets.cfg",
"default_bus_layout.tres",
}
if file_name in exempt_files:
return True, ""
# Check PascalCase pattern
name_without_ext = file_path.stem
# PascalCase: starts with capital letter, can have more capitals, no underscores or hyphens
if not re.match(r"^[A-Z][a-zA-Z0-9]*$", name_without_ext):
return (
False,
f"File name '{file_name}' should use PascalCase (e.g., 'MainMenu.gd', 'Match3Gameplay.tscn')",
)
return True, ""
def get_naming_files(project_root: Path) -> List[Path]:
"""Get all .tscn and .gd files that should follow naming conventions."""
files = []
for pattern in ["**/*.tscn", "**/*.gd"]:
files.extend(project_root.glob(pattern))
# Filter out files that should be ignored
filtered_files = []
for file_path in files:
if not should_skip_file(file_path):
filtered_files.append(file_path)
return filtered_files
async def validate_json_file_async(file_path: Path) -> Tuple[bool, str]:
"""Validate a JSON file asynchronously."""
if aiofiles is None:
@@ -1249,6 +1302,100 @@ async def run_validate_async(
)
def run_naming(
project_root: Path, silent: bool = False, yaml_output: bool = False
) -> Tuple[bool, Dict]:
"""Check naming conventions for .tscn and .gd files."""
if not silent and not yaml_output:
print_header("📝 Naming Convention Check")
# Get all files that should follow naming conventions
naming_files = get_naming_files(project_root)
total_files = len(naming_files)
if total_files == 0:
if not silent:
msg = "No .tscn or .gd files found to check naming conventions."
colored_msg = Colors.colorize(msg, Colors.YELLOW)
print(colored_msg)
return True, {"Total files": 0, "Valid files": 0, "Invalid files": 0}
if not silent and not yaml_output:
count_msg = f"Checking naming conventions for {total_files} files..."
colored_count = Colors.colorize(count_msg, Colors.BLUE)
print(colored_count)
print()
# Process files
valid_files = 0
invalid_files = 0
all_output = []
for file_path in naming_files:
is_valid, error_msg = check_naming_convention(file_path)
relative_path = file_path.relative_to(project_root)
if is_valid:
valid_files += 1
if not silent and not yaml_output:
file_msg = f"📄 Checking: {relative_path}"
colored_file = Colors.colorize(file_msg, Colors.CYAN)
success_msg = " ✅ Follows PascalCase convention"
colored_success = Colors.colorize(success_msg, Colors.GREEN)
all_output.extend([file_msg, success_msg])
print(colored_file)
print(colored_success)
else:
invalid_files += 1
# Always show naming errors, even in silent mode
if not yaml_output:
file_msg = f"📄 Checking: {relative_path}"
colored_file = Colors.colorize(file_msg, Colors.CYAN)
error_msg_display = f"{error_msg}"
colored_error = Colors.colorize(error_msg_display, Colors.RED)
all_output.extend([file_msg, error_msg_display])
print(colored_file)
print(colored_error)
# Results summary
overall_success = invalid_files == 0
stats = {
"Total files": total_files,
"Valid files": valid_files,
"Invalid files": invalid_files,
}
if not silent and not yaml_output:
print()
if overall_success:
success_msg = "✅ All files follow PascalCase naming convention!"
colored_success = Colors.colorize(success_msg, Colors.GREEN)
print(colored_success)
else:
error_msg = f"{invalid_files} file(s) don't follow PascalCase convention"
colored_error = Colors.colorize(error_msg, Colors.RED)
print(colored_error)
if yaml_output:
# Collect only failed files for YAML output (don't include all files)
failed_paths = []
for file_path in naming_files:
relative_path_str = str(file_path.relative_to(project_root))
is_valid, error_msg = check_naming_convention(file_path)
if not is_valid:
failed_paths.append(relative_path_str)
results = {
**stats, # Include stats at top level for consistency
"failed_paths": failed_paths, # For failed_items extraction
}
output_yaml_results("naming", results, overall_success)
return overall_success, stats
def run_format(
project_root: Path, silent: bool = False, yaml_output: bool = False
) -> Tuple[bool, Dict]:
@@ -1476,7 +1623,7 @@ def discover_test_files(project_root: Path) -> List[Tuple[Path, str]]:
for test_dir, prefix in test_dirs:
test_path = project_root / test_dir
if test_path.exists():
for test_file in test_path.glob("test_*.gd"):
for test_file in test_path.glob("Test*.gd"):
test_files.append((test_file, prefix))
return test_files
@@ -1754,6 +1901,10 @@ def run_workflow(
"📋 File format validation (yaml/toml/json)",
lambda root: run_validate(root, silent, yaml_output),
),
"naming": (
"📝 Naming convention check (PascalCase)",
lambda root: run_naming(root, silent, yaml_output),
),
}
if not silent:
@@ -1903,6 +2054,12 @@ async def run_workflow_async(
"📋 File format validation (yaml/toml/json)",
lambda root: run_validate_async(root, silent, yaml_output),
),
"naming": (
"📝 Naming convention check (PascalCase)",
lambda root: run_naming(
root, silent, yaml_output
), # Using sync version - lightweight
),
}
if not silent:
@@ -2017,8 +2174,8 @@ async def main_async():
parser.add_argument(
"--steps",
nargs="+",
choices=["lint", "format", "test", "validate", "ruff"],
default=["format", "lint", "ruff", "test", "validate"],
choices=["lint", "format", "test", "validate", "ruff", "naming"],
default=["format", "lint", "ruff", "test", "validate", "naming"],
help="Workflow steps to run",
)
parser.add_argument("--lint", action="store_true", help="Run GDScript linting")
@@ -2030,6 +2187,9 @@ async def main_async():
parser.add_argument(
"--ruff", action="store_true", help="Run Python formatting & linting with ruff"
)
parser.add_argument(
"--naming", action="store_true", help="Check PascalCase naming conventions"
)
parser.add_argument(
"--silent",
"-s",
@@ -2078,6 +2238,7 @@ async def main_async():
root, args.silent, args.yaml
),
"ruff": lambda root: run_ruff_async(root, args.silent, args.yaml),
"naming": lambda root: run_naming(root, args.silent, args.yaml),
}
success, _ = await step_funcs[steps[0]](project_root)
else:
@@ -2087,6 +2248,7 @@ async def main_async():
"test": lambda root: run_tests(root, args.silent, args.yaml),
"validate": lambda root: run_validate(root, args.silent, args.yaml),
"ruff": lambda root: run_ruff(root, args.silent, args.yaml),
"naming": lambda root: run_naming(root, args.silent, args.yaml),
}
success, _ = step_funcs[steps[0]](project_root)
else:
@@ -2114,8 +2276,8 @@ def main():
parser.add_argument(
"--steps",
nargs="+",
choices=["lint", "format", "test", "validate", "ruff"],
default=["format", "lint", "ruff", "test", "validate"],
choices=["lint", "format", "test", "validate", "ruff", "naming"],
default=["format", "lint", "ruff", "test", "validate", "naming"],
help="Workflow steps to run",
)
parser.add_argument("--lint", action="store_true", help="Run GDScript linting")
@@ -2131,6 +2293,9 @@ def main():
action="store_true",
help="Run Python formatting & linting with ruff",
)
parser.add_argument(
"--naming", action="store_true", help="Check PascalCase naming conventions"
)
parser.add_argument(
"--silent",
"-s",
@@ -2173,6 +2338,7 @@ def main():
"test": lambda root: run_tests(root, args.silent, args.yaml),
"validate": lambda root: run_validate(root, args.silent, args.yaml),
"ruff": lambda root: run_ruff(root, args.silent, args.yaml),
"naming": lambda root: run_naming(root, args.silent, args.yaml),
}
success, _ = step_funcs[steps[0]](project_root)
else: