-
Notifications
You must be signed in to change notification settings - Fork 140
Open
Labels
choreLinting, formatting, dependency hygiene, or project maintenance choresLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)Issue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)DevOps activities (containers, automation, deployment, makefiles, etc)triageIssues / Features awaiting triageIssues / Features awaiting triage
Milestone
Description
[CHORE]: Add file/directory-specific linting support to Makefile
🔧 Chore Description
Priority: High (Developer Experience)
Description:
Add support for linting specific files or directories by passing them as arguments to make lint
. This allows developers to quickly check only the files they're working on without running linters across the entire codebase.
Additional: Option to only run make lint
on the staged files from a commit (or anything that's modified), and a way to run this as part of a pre-commit
hook.
📋 Current Behavior
$ make lint
# Runs ALL linters on ENTIRE codebase (slow)
$ make lint mcpgateway/server.py
# Error: No rule to make target 'mcpgateway/server.py'
✅ Expected Behavior
$ make lint mcpgateway/server.py
# Runs all applicable linters on just that file
$ make lint mcpgateway/auth/
# Runs all linters on just that directory
$ make lint
# Still works as before (entire codebase)
🛠️ Implementation
1. Add new TARGET variable and lint-target:
# =============================================================================
# 🔍 LINTING & STATIC ANALYSIS
# =============================================================================
# Allow specific file/directory targeting
TARGET ?= mcpgateway tests
# Individual linter flags for file/directory support
ISORT_TARGET = $(TARGET)
BLACK_TARGET = $(TARGET)
FLAKE8_TARGET = $(TARGET)
PYLINT_TARGET = $(TARGET)
MYPY_TARGET = $(TARGET)
BANDIT_TARGET = $(if $(filter mcpgateway tests,$(TARGET)),-r $(TARGET),$(TARGET))
RUFF_TARGET = $(TARGET)
PYRIGHT_TARGET = $(TARGET)
# List of linters that support file/directory targeting
FILE_AWARE_LINTERS := isort black flake8 pylint mypy bandit ruff pyright \
pydocstyle pycodestyle
# Master lint target with file/directory support
.PHONY: lint
lint:
@if [ "$(MAKECMDGOALS)" = "lint" ] && [ -n "$(filter-out lint,$(MAKECMDGOALS))" ]; then \
TARGET="$(filter-out lint,$(MAKECMDGOALS))"; \
echo "🔍 Linting specific target: $$TARGET"; \
$(MAKE) lint-target TARGET="$$TARGET"; \
else \
echo "🔍 Running full lint suite on: $(TARGET)"; \
$(MAKE) lint-all TARGET="$(TARGET)"; \
fi
# Handle file/directory as a target
%:
@if [ "$(firstword $(MAKECMDGOALS))" = "lint" ] && [ -e "$@" ]; then \
true; \
else \
echo "Error: Unknown target '$@'"; \
exit 1; \
fi
.PHONY: lint-target
lint-target:
@echo "🎯 Linting $(TARGET)..."
@for linter in $(FILE_AWARE_LINTERS); do \
if command -v $(VENV_DIR)/bin/$$linter >/dev/null 2>&1 || [ "$$linter" = "pre-commit" ]; then \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "- $$linter on $(TARGET)"; \
$(MAKE) $$linter-file TARGET="$(TARGET)" || true; \
fi; \
done
.PHONY: lint-all
lint-all:
@set -e; for t in $(LINTERS); do \
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; \
echo "- $$t"; \
$(MAKE) $$t TARGET="$(TARGET)" || true; \
done
2. Update individual linter targets to support TARGET:
# --- Python formatters/linters with file support ---
isort-file isort:
@echo "🔀 isort $(TARGET)..."
@$(VENV_DIR)/bin/isort $(TARGET)
black-file black:
@echo "🎨 black $(TARGET)..."
@$(VENV_DIR)/bin/black -l 200 $(TARGET)
flake8-file flake8:
@echo "🐍 flake8 $(TARGET)..."
@$(VENV_DIR)/bin/flake8 $(TARGET)
pylint-file pylint:
@echo "🐛 pylint $(TARGET)..."
@if [ -f "$(TARGET)" ]; then \
$(VENV_DIR)/bin/pylint $(TARGET); \
else \
$(VENV_DIR)/bin/pylint $(TARGET); \
fi
mypy-file mypy:
@echo "🏷️ mypy $(TARGET)..."
@$(VENV_DIR)/bin/mypy $(TARGET)
bandit-file bandit:
@echo "🛡️ bandit $(TARGET)..."
@if [ -d "$(TARGET)" ]; then \
$(VENV_DIR)/bin/bandit -r $(TARGET); \
else \
$(VENV_DIR)/bin/bandit $(TARGET); \
fi
ruff-file ruff:
@echo "⚡ ruff $(TARGET)..."
@$(VENV_DIR)/bin/ruff check $(TARGET)
@$(VENV_DIR)/bin/ruff format --check $(TARGET)
pyright-file pyright:
@echo "🏷️ pyright $(TARGET)..."
@$(VENV_DIR)/bin/pyright $(TARGET)
pydocstyle-file pydocstyle:
@echo "📚 pydocstyle $(TARGET)..."
@$(VENV_DIR)/bin/pydocstyle $(TARGET)
pycodestyle-file pycodestyle:
@echo "📝 pycodestyle $(TARGET)..."
@$(VENV_DIR)/bin/pycodestyle --max-line-length=200 $(TARGET)
3. Add convenience targets for common use cases:
# Convenience targets for specific file types
.PHONY: lint-py lint-yaml lint-json lint-md
# Lint only Python files in target
lint-py:
@if [ -f "$(TARGET)" ]; then \
echo "🐍 Linting Python file: $(TARGET)"; \
$(MAKE) lint-target TARGET="$(TARGET)"; \
else \
echo "🐍 Linting Python files in: $(TARGET)"; \
find $(TARGET) -name "*.py" -type f | while read f; do \
$(MAKE) lint-target TARGET="$$f"; \
done; \
fi
# Quick lint - only fast linters
lint-quick:
@echo "⚡ Quick lint of $(TARGET) (ruff + black + isort)..."
@$(MAKE) ruff-file TARGET="$(TARGET)"
@$(MAKE) black-file TARGET="$(TARGET)" ARGS="--check --diff"
@$(MAKE) isort-file TARGET="$(TARGET)" ARGS="--check-only"
# Fix formatting issues in target
lint-fix:
@echo "🔧 Fixing lint issues in $(TARGET)..."
@$(MAKE) black-file TARGET="$(TARGET)"
@$(MAKE) isort-file TARGET="$(TARGET)"
@$(MAKE) ruff-file TARGET="$(TARGET)" ARGS="--fix"
4. Handle file type detection:
# Smart linting based on file extension
.PHONY: lint-smart
lint-smart:
@for target in $(TARGET); do \
if [ ! -e "$$target" ]; then \
echo "❌ File/directory not found: $$target"; \
continue; \
fi; \
case "$$target" in \
*.py) \
echo "🐍 Python file detected: $$target"; \
$(MAKE) lint-py TARGET="$$target" ;; \
*.yaml|*.yml) \
echo "📄 YAML file detected: $$target"; \
$(MAKE) yamllint TARGET="$$target" ;; \
*.json) \
echo "📄 JSON file detected: $$target"; \
$(MAKE) jsonlint TARGET="$$target" ;; \
*.md) \
echo "📝 Markdown file detected: $$target"; \
$(MAKE) markdownlint TARGET="$$target" ;; \
*.toml) \
echo "📄 TOML file detected: $$target"; \
$(MAKE) tomllint TARGET="$$target" ;; \
*) \
if [ -d "$$target" ]; then \
echo "📁 Directory detected: $$target"; \
$(MAKE) lint-target TARGET="$$target"; \
else \
echo "❓ Unknown file type, running all applicable linters"; \
$(MAKE) lint-target TARGET="$$target"; \
fi ;; \
esac; \
done
📋 Usage Examples
# Lint a specific file
make lint mcpgateway/server.py
# Lint a directory
make lint mcpgateway/auth/
# Lint multiple files
make lint mcpgateway/server.py mcpgateway/config.py
# Quick lint (fast checks only)
make lint-quick mcpgateway/server.py
# Fix formatting issues
make lint-fix mcpgateway/server.py
# Smart lint (auto-detect file type)
make lint-smart config.yaml
# Traditional full project lint
make lint
# Lint with specific tool only
make black mcpgateway/server.py
make flake8 mcpgateway/auth/
🧪 Testing Requirements
# Test 1: Single file
make lint mcpgateway/__init__.py
# Test 2: Directory
make lint mcpgateway/auth/
# Test 3: Non-existent file
make lint nonexistent.py # Should error gracefully
# Test 4: Multiple targets
make lint mcpgateway/*.py
# Test 5: Different file types
make lint README.md
make lint pyproject.toml
# Test 6: Quick lint
make lint-quick mcpgateway/
# Test 7: Fix mode
make lint-fix mcpgateway/server.py
✅ Acceptance Criteria
-
make lint filename
works for single files -
make lint dirname
works for directories -
make lint
without args still lints entire project - All Python linters support file/directory targeting
- Config file linters detect file type and run appropriately
- Clear error messages for non-existent files
-
make lint-fix
repairs formatting issues -
make lint-quick
provides fast feedback - Performance: Single file linting is notably faster than full project
🔧 Implementation Notes
-
Linter Compatibility:
- Most Python linters accept files/directories directly
- Some tools may need special handling (e.g., mypy with imports)
- Config linters need file-type detection
-
Performance Considerations:
- Avoid re-running slow linters unnecessarily
- Consider caching for incremental linting
- Quick-lint mode for rapid development
-
Error Handling:
- Gracefully handle non-existent files
- Continue on linter failures (don't stop the pipeline)
- Clear indication of which linter failed
📚 Additional Features (Future)
# Watch mode - lint on file changes
lint-watch:
@echo "👁️ Watching $(TARGET) for changes..."
@watchmedo shell-command \
--patterns="*.py" \
--recursive \
--command='make lint-quick ${watch_src_path}' \
$(TARGET)
# Lint only changed files (git)
lint-changed:
@echo "🔍 Linting changed files..."
@git diff --name-only --diff-filter=ACM | grep -E '\.(py|yaml|yml|json|md|toml)$$' | \
xargs -I {} $(MAKE) lint-smart TARGET={}
# Lint with error threshold
lint-strict:
@$(MAKE) lint TARGET="$(TARGET)" | tee lint-report.txt
@errors=$$(grep -c "error:" lint-report.txt || true); \
if [ $$errors -gt 0 ]; then \
echo "❌ Found $$errors errors"; \
exit 1; \
fi
Benefits:
- Faster feedback loop during development
- Ability to check files before committing
- Reduced CI time by pre-checking locally
- Better integration with editors/IDEs
- Flexible workflow support
Metadata
Metadata
Assignees
Labels
choreLinting, formatting, dependency hygiene, or project maintenance choresLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)Issue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)DevOps activities (containers, automation, deployment, makefiles, etc)triageIssues / Features awaiting triageIssues / Features awaiting triage