Skip to content
Open
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
29 changes: 29 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ repos:
language: node
additional_dependencies: ["prettier@3.7.4"]
stages: [manual]
- id: check-hook-descriptions
name: make sure all pre-commit hooks have descriptions
description: >-
Validates that every hook in the pre-commit configuration includes a
descriptive explanation of at least 15 characters.
entry: python3 scripts/pre-commit/check_hook_descriptions.py
language: python
additional_dependencies: [PyYAML]
files: ^\.pre-commit-config\.yaml$
pass_filenames: false
- id: check-zip-file-is-not-committed
name: disallow zip files
description: Zip files are not allowed in the repository
Expand All @@ -51,29 +61,48 @@ repos:
rev: v6.0.0
hooks:
- id: check-added-large-files
description: prevent giant files from being committed
- id: check-case-conflict
description: check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT
- id: check-executables-have-shebangs
description: checks that non-binary executables have a proper shebang
exclude: ^test/t/lang\.rb$
- id: check-illegal-windows-names
description: check for files that cannot be created on Windows
- id: pretty-format-json
description: checks that all your JSON files are pretty. "Pretty" here means that keys are sorted and indented
args: [--autofix, --no-sort-keys]
- id: check-json
description: attempts to load all json files to verify syntax
- id: check-merge-conflict
description: check for files that contain merge conflict strings
- id: check-shebang-scripts-are-executable
description: checks that scripts with shebangs are executable
- id: check-vcs-permalinks
description: ensures that links to vcs websites are permalinks
- id: check-yaml
description: attempts to load all yaml files to verify syntax
- id: destroyed-symlinks
description: detects symlinks which are changed to regular files with a content of a path which that symlink was pointing to
- id: detect-aws-credentials
description: checks for the existence of AWS secrets that you have set up with the AWS CLI
args: [--allow-missing-credentials]
- id: detect-private-key
description: checks for the existence of private keys
- id: end-of-file-fixer
description: makes sure files end in a newline and only a newline
- id: file-contents-sorter
description: sort the lines in specified files (defaults to alphabetical)
args: [--unique]
files: ^\.github/linters/codespell\.txt$
- id: fix-byte-order-marker
description: removes UTF-8 byte order marker
- id: forbid-submodules
description: forbids any submodules in the repository
- id: mixed-line-ending
description: replaces or checks mixed line ending
- id: trailing-whitespace
description: trims trailing whitespace
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
Expand Down
69 changes: 69 additions & 0 deletions scripts/pre-commit/check_hook_descriptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import sys
import yaml
import os

# Configuration Constants
CONFIG_FILE = os.environ.get('PRE_COMMIT_CONFIG', '.pre-commit-config.yaml')
MIN_DESC_LENGTH = 15


def validate_hook(repo_url, hook, min_length):
"""Validates a single hook and returns an error message or None."""
hook_id = hook.get('id', 'unknown-id')
description = hook.get('description', '')

# Ensure description is a string and strip whitespace
clean_desc = str(description).strip() if description is not None else ""

if not clean_desc:
return f"Repo: {repo_url} | Hook: {hook_id} -> Missing 'description' key."

if len(clean_desc) < min_length:
return (f"Repo: {repo_url} | Hook: {hook_id} -> Description too short "
f"({len(clean_desc)} chars). Minimum required: {min_length}.")

return None


def main():
if not os.path.exists(CONFIG_FILE):
print(f"ERROR: Configuration file '{CONFIG_FILE}' not found.")
return 1

try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
# Use CSafeLoader if available for better performance on large files
loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
config = yaml.load(f, Loader=loader)
Comment on lines +36 to +38

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved code clarity and to adhere to modern PyYAML API usage, it's better to use yaml.safe_load() directly. This function is the recommended safe way to parse YAML and it will automatically use the faster C-based loader (CSafeLoader) if it's available, so the explicit check is not necessary.

Suggested change
# Use CSafeLoader if available for better performance on large files
loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
config = yaml.load(f, Loader=loader)
config = yaml.safe_load(f)

except yaml.YAMLError as exc:
print(f"ERROR: Failed to parse YAML in {CONFIG_FILE}: {exc}")
return 1

if not config or 'repos' not in config:
print(f"INFO: No repos found in {CONFIG_FILE}. Nothing to check.")
return 0

errors = []
for repo in config.get('repos', []):
repo_url = repo.get('repo', 'local')
for hook in repo.get('hooks', []):
error = validate_hook(repo_url, hook, MIN_DESC_LENGTH)
if error:
errors.append(error)

if errors:
print(f"FAIL: {len(errors)} hook(s) failed documentation requirements.")
print("=" * 80)
for error in errors:
print(f" [!] {error}")
print("=" * 80)
print(f"Tip: Ensure each hook has a 'description' of {MIN_DESC_LENGTH}+ chars.")
return 1

print(f"PASS: All hooks in {CONFIG_FILE} are properly documented.")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading