feat: add unified code quality dashboard with multiple validators

- Add validator_type field to scans and violations (architecture,
  security, performance)
- Create security validator with SEC-xxx rules
- Create performance validator with PERF-xxx rules
- Add base validator class for shared functionality
- Add validate_all.py script to run all validators
- Update code quality service with validator type filtering
- Add validator type tabs to dashboard UI
- Add validator type filter to violations list
- Update stats response with per-validator breakdown
- Add security and performance rules documentation
- Add chat-bubble icons to icon library

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 20:57:47 +01:00
parent 6a903e16c6
commit 26b3dc9e3b
27 changed files with 5270 additions and 119 deletions

View File

@@ -0,0 +1,66 @@
# Performance Rules Configuration
# ================================
# Performance-focused validation rules for the codebase.
# Run with: python scripts/validate_performance.py
version: "1.0"
project: "letzshop-product-import"
description: |
Performance validation rules to detect inefficient patterns and ensure
optimal performance across the application.
principles:
- name: "Minimize Database Queries"
description: "Reduce N+1 queries and optimize data fetching"
- name: "Efficient Data Structures"
description: "Use appropriate data structures for the task"
- name: "Lazy Loading"
description: "Load data only when needed"
- name: "Caching Strategy"
description: "Cache expensive computations and frequent queries"
- name: "Async I/O"
description: "Use async for I/O-bound operations"
includes:
- database.yaml
- caching.yaml
- api.yaml
- async.yaml
- memory.yaml
- frontend.yaml
severity_levels:
error:
description: "Critical performance issue that must be fixed"
exit_code: 1
warning:
description: "Performance concern that should be addressed"
exit_code: 0
info:
description: "Performance optimization recommendation"
exit_code: 0
ignore:
files:
- "**/test_*.py"
- "**/tests/**"
- "**/*_test.py"
- "**/conftest.py"
- "**/migrations/**"
- "**/.venv/**"
- "**/venv/**"
- "**/node_modules/**"
- "**/site/**"
- "**/scripts/**"
- "**/__pycache__/**"
- "**/*.pyc"
patterns:
# Allow patterns in test files
- file: "**/tests/**"
pattern: ".*"
reason: "Test files may have different performance requirements"
# Allow patterns in scripts
- file: "**/scripts/**"
pattern: "\\.all\\(\\)"
reason: "Scripts may need to process all records"

135
.performance-rules/api.yaml Normal file
View File

@@ -0,0 +1,135 @@
# API Performance Rules
# =====================
api_rules:
- id: "PERF-026"
name: "Pagination required for list endpoints"
severity: error
description: |
All list endpoints must support pagination.
Unbounded lists cause performance issues:
- Memory exhaustion
- Slow response times
- Database locks
file_pattern: "**/api/**/*.py"
anti_patterns:
- '@router\\.get\\([^)]*\\)\\s*\\n(?:(?!limit|skip|offset|page).)*def\\s+\\w+.*:\\s*\\n(?:(?!limit|skip|offset|page).)*return.*\\.all\\(\\)'
required_patterns:
- "limit|skip|offset|page"
example_bad: |
@router.get("/products")
def list_products(db: Session):
return db.query(Product).all()
example_good: |
@router.get("/products")
def list_products(
skip: int = 0,
limit: int = Query(default=20, le=100),
db: Session = Depends(get_db)
):
return db.query(Product).offset(skip).limit(limit).all()
- id: "PERF-027"
name: "Reasonable default page sizes"
severity: warning
description: |
Default page sizes should be reasonable:
- Default: 20-50 items
- Maximum: 100-200 items
Very large page sizes negate pagination benefits.
file_pattern: "**/api/**/*.py"
anti_patterns:
- 'limit.*=.*Query\\([^)]*default\\s*=\\s*[5-9]\\d{2,}'
- 'limit.*=.*Query\\([^)]*le\\s*=\\s*[1-9]\\d{3,}'
example_bad: |
limit: int = Query(default=500, le=10000)
example_good: |
limit: int = Query(default=20, ge=1, le=100)
- id: "PERF-028"
name: "Response compression"
severity: info
description: |
Enable response compression for large responses:
- GZip or Brotli
- Significant bandwidth savings
- Faster load times
file_pattern: "**/main.py|**/app.py"
suggested_patterns:
- "GZipMiddleware|BrotliMiddleware|compress"
- id: "PERF-029"
name: "Efficient serialization"
severity: info
description: |
Use Pydantic's response_model for efficient serialization.
Avoid manual dict conversion.
file_pattern: "**/api/**/*.py"
anti_patterns:
- 'return\\s+\\{[^}]*for\\s+\\w+\\s+in'
- 'return\\s+\\[\\{.*for.*in.*\\]'
suggested_patterns:
- "response_model"
- id: "PERF-030"
name: "Avoid redundant queries in response"
severity: warning
description: |
Don't trigger lazy-loaded relationships during serialization.
Use eager loading or carefully control serialization.
file_pattern: "**/api/**/*.py"
- id: "PERF-031"
name: "Streaming for large responses"
severity: info
description: |
Use streaming responses for large data:
- File downloads
- Large exports (CSV, JSON)
- Real-time data feeds
file_pattern: "**/api/**/*.py"
suggested_patterns:
- "StreamingResponse|yield|generator"
- id: "PERF-032"
name: "Conditional requests support"
severity: info
description: |
Support conditional requests to reduce bandwidth:
- ETag validation
- If-None-Match handling
- 304 Not Modified responses
file_pattern: "**/api/**/*.py"
suggested_patterns:
- "ETag|If-None-Match|304"
- id: "PERF-033"
name: "Field selection support"
severity: info
description: |
Allow clients to request only needed fields.
Reduces response size and serialization cost.
file_pattern: "**/api/**/*.py"
suggested_patterns:
- "fields|include|exclude|sparse"
- id: "PERF-034"
name: "Avoid deep nesting in responses"
severity: info
description: |
Deeply nested responses are slow to serialize.
Consider flattening or using links instead.
file_pattern: "**/api/**/*.py"
- id: "PERF-035"
name: "Endpoint response time monitoring"
severity: info
description: |
Monitor API response times:
- Set SLA targets
- Alert on degradation
- Track percentiles (p50, p95, p99)
file_pattern: "**/main.py|**/middleware*.py"
suggested_patterns:
- "prometheus|metrics|timing|latency"

View File

@@ -0,0 +1,142 @@
# Async & Concurrency Performance Rules
# =====================================
async_rules:
- id: "PERF-036"
name: "Async for I/O operations"
severity: info
description: |
Use async for I/O-bound operations:
- Database queries (with async driver)
- External API calls
- File operations
- Network requests
file_pattern: "**/api/**/*.py|**/service*.py"
suggested_patterns:
- "async def|await|asyncio"
- id: "PERF-037"
name: "Parallel independent operations"
severity: warning
description: |
Multiple independent async operations should run in parallel.
Use asyncio.gather() instead of sequential awaits.
file_pattern: "**/*.py"
anti_patterns:
- 'await\\s+\\w+\\([^)]*\\)\\s*\\n\\s*await\\s+\\w+\\([^)]*\\)\\s*\\n\\s*await\\s+\\w+\\('
suggested_patterns:
- "asyncio\\.gather|asyncio\\.create_task"
example_bad: |
user = await get_user(user_id)
orders = await get_orders(user_id)
preferences = await get_preferences(user_id)
example_good: |
user, orders, preferences = await asyncio.gather(
get_user(user_id),
get_orders(user_id),
get_preferences(user_id)
)
- id: "PERF-038"
name: "Background tasks for slow operations"
severity: warning
description: |
Operations taking > 500ms should run in background:
- Email sending
- Report generation
- External API syncs
- File processing
file_pattern: "**/api/**/*.py"
suggested_patterns:
- "BackgroundTasks|background_task|celery|rq|dramatiq"
- id: "PERF-039"
name: "Connection pooling for HTTP clients"
severity: warning
description: |
HTTP clients should reuse connections.
Create client once, not per request.
file_pattern: "**/*client*.py|**/service*.py"
anti_patterns:
- 'def\\s+\\w+\\([^)]*\\):\\s*\\n[^}]*requests\\.get\\('
- 'httpx\\.get\\('
- 'aiohttp\\.request\\('
suggested_patterns:
- "httpx\\.AsyncClient|aiohttp\\.ClientSession|requests\\.Session"
example_bad: |
def fetch_data(url):
response = requests.get(url) # New connection each time
example_good: |
# Use a session (connection pool)
async with httpx.AsyncClient() as client:
response = await client.get(url)
- id: "PERF-040"
name: "Timeout configuration"
severity: error
description: |
All external calls must have timeouts.
Without timeouts, requests can hang indefinitely.
file_pattern: "**/*client*.py|**/service*.py"
context_patterns:
- "requests|httpx|aiohttp|urllib"
required_patterns:
- "timeout"
example_bad: |
response = requests.get(url)
example_good: |
response = requests.get(url, timeout=30)
- id: "PERF-041"
name: "Connection pool limits"
severity: info
description: |
Configure appropriate connection pool limits:
- max_connections: Total connections
- max_keepalive_connections: Idle connections
- keepalive_expiry: Time before closing idle
file_pattern: "**/*client*.py"
suggested_patterns:
- "max_connections|pool_connections|pool_maxsize"
- id: "PERF-042"
name: "Retry with backoff"
severity: info
description: |
External calls should retry with exponential backoff.
Prevents cascade failures and respects rate limits.
file_pattern: "**/*client*.py|**/service*.py"
suggested_patterns:
- "retry|backoff|tenacity|Retry"
- id: "PERF-043"
name: "Circuit breaker pattern"
severity: info
description: |
Use circuit breaker for unreliable external services.
Prevents repeated failures from slowing down the system.
file_pattern: "**/*client*.py"
suggested_patterns:
- "circuit_breaker|CircuitBreaker|pybreaker"
- id: "PERF-044"
name: "Task queues for heavy processing"
severity: info
description: |
Heavy processing should use task queues:
- Celery
- RQ (Redis Queue)
- Dramatiq
- Huey
file_pattern: "**/tasks/**/*.py"
suggested_patterns:
- "celery|rq|dramatiq|huey|@task"
- id: "PERF-045"
name: "Worker pool sizing"
severity: info
description: |
Size worker pools appropriately:
- CPU-bound: Number of cores
- I/O-bound: Higher multiplier (2-4x cores)
- Memory-constrained: Based on available RAM

View File

@@ -0,0 +1,125 @@
# Caching Performance Rules
# =========================
caching_rules:
- id: "PERF-016"
name: "Cache expensive computations"
severity: info
description: |
Computationally expensive operations should be cached:
- Complex aggregations
- External API results
- Template rendering
- Data transformations
file_pattern: "**/service*.py"
suggested_patterns:
- "@cache|@lru_cache|@cached|redis|memcache"
- id: "PERF-017"
name: "Cache key includes tenant context"
severity: warning
description: |
Multi-tenant cache keys must include vendor_id.
Otherwise, cached data may leak between tenants.
file_pattern: "**/*cache*.py|**/service*.py"
context_patterns:
- "cache|@cached|redis"
required_patterns:
- "vendor_id|tenant"
example_bad: |
@cache.memoize()
def get_products():
return db.query(Product).all()
example_good: |
@cache.memoize()
def get_products(vendor_id: int):
return db.query(Product).filter_by(vendor_id=vendor_id).all()
- id: "PERF-018"
name: "Cache TTL configuration"
severity: info
description: |
Cache entries should have appropriate TTL:
- Short TTL (1-5 min): Frequently changing data
- Medium TTL (5-60 min): Semi-static data
- Long TTL (1+ hour): Reference data
file_pattern: "**/*cache*.py"
suggested_patterns:
- "ttl|expire|timeout"
- id: "PERF-019"
name: "Cache invalidation strategy"
severity: warning
description: |
Define cache invalidation strategy:
- Time-based (TTL)
- Event-based (on data change)
- Manual (admin action)
Without invalidation, stale data may be served.
file_pattern: "**/*cache*.py|**/service*.py"
suggested_patterns:
- "invalidate|delete|clear|purge"
- id: "PERF-020"
name: "Response caching headers"
severity: info
description: |
API responses can use HTTP caching headers:
- Cache-Control for browser/CDN caching
- ETag for conditional requests
- Last-Modified for validation
file_pattern: "**/api/**/*.py"
suggested_patterns:
- "Cache-Control|ETag|Last-Modified"
- id: "PERF-021"
name: "Query result caching"
severity: info
description: |
Frequently accessed, rarely changed data should be cached:
- User preferences
- Configuration settings
- Static reference data
file_pattern: "**/service*.py"
- id: "PERF-022"
name: "Session-level caching"
severity: info
description: |
Use SQLAlchemy's identity map for request-scoped caching.
Avoid re-fetching the same entity within a request.
file_pattern: "**/service*.py"
- id: "PERF-023"
name: "Distributed cache for scalability"
severity: info
description: |
For multi-instance deployments, use distributed cache:
- Redis
- Memcached
- Database-backed cache
Local caches don't work across instances.
file_pattern: "**/config*.py"
suggested_patterns:
- "redis|memcache|CACHE_TYPE"
- id: "PERF-024"
name: "Cache warming strategy"
severity: info
description: |
Pre-warm cache for predictable high-traffic patterns:
- On application startup
- Before marketing campaigns
- After cache flush
- id: "PERF-025"
name: "Monitor cache hit rates"
severity: info
description: |
Track cache performance:
- Hit rate (should be > 80%)
- Miss penalty (time saved)
- Memory usage
- Eviction rate

View File

@@ -0,0 +1,223 @@
# Database Performance Rules
# ==========================
database_rules:
- id: "PERF-001"
name: "N+1 query detection"
severity: warning
description: |
Accessing relationships in loops causes N+1 queries.
For each item in a list, a separate query is executed.
Solutions:
- joinedload(): Eager load with JOIN
- selectinload(): Eager load with IN clause
- subqueryload(): Eager load with subquery
file_pattern: "**/service*.py|**/api/**/*.py"
anti_patterns:
- 'for\s+\w+\s+in\s+\w+\.all\(\):\s*\n[^}]*\.\w+\.\w+'
suggested_patterns:
- "joinedload|selectinload|subqueryload"
example_bad: |
orders = db.query(Order).all()
for order in orders:
customer_name = order.customer.name # N+1 query!
example_good: |
orders = db.query(Order).options(
joinedload(Order.customer)
).all()
for order in orders:
customer_name = order.customer.name # Already loaded
- id: "PERF-002"
name: "Eager loading for known relationships"
severity: info
description: |
When you always need related data, use eager loading
to reduce the number of database round trips.
file_pattern: "**/service*.py"
suggested_patterns:
- "joinedload|selectinload|subqueryload"
- id: "PERF-003"
name: "Query result limiting"
severity: warning
description: |
All list queries should have pagination or limits.
Unbounded queries can cause memory issues and slow responses.
file_pattern: "**/service*.py|**/api/**/*.py"
anti_patterns:
- '\\.all\\(\\)(?![^\\n]*limit|[^\\n]*\\[:)'
exclude_patterns:
- "# noqa: PERF-003"
- "# bounded query"
- ".filter("
suggested_patterns:
- "limit|offset|skip|paginate"
example_bad: |
all_products = db.query(Product).all()
example_good: |
products = db.query(Product).limit(100).all()
# Or with pagination
products = db.query(Product).offset(skip).limit(limit).all()
- id: "PERF-004"
name: "Index usage for filtered columns"
severity: info
description: |
Columns frequently used in WHERE clauses should have indexes:
- Foreign keys (vendor_id, customer_id)
- Status fields
- Date fields used for filtering
- Boolean flags used for filtering
file_pattern: "**/models/database/*.py"
suggested_patterns:
- "index=True|Index\\("
- id: "PERF-005"
name: "Select only needed columns"
severity: info
description: |
For large tables, select only the columns you need.
Use .with_entities() or load_only() to reduce data transfer.
file_pattern: "**/service*.py"
suggested_patterns:
- "with_entities|load_only|defer"
example_good: |
# Only load id and name columns
products = db.query(Product).options(
load_only(Product.id, Product.name)
).all()
- id: "PERF-006"
name: "Bulk operations for multiple records"
severity: warning
description: |
Use bulk operations instead of individual operations in loops:
- bulk_insert_mappings() for inserts
- bulk_update_mappings() for updates
- add_all() for ORM inserts
file_pattern: "**/service*.py"
anti_patterns:
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*db\\.add\\s*\\('
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\.save\\s*\\('
suggested_patterns:
- "bulk_insert_mappings|bulk_update_mappings|add_all"
example_bad: |
for item in items:
product = Product(**item)
db.add(product)
example_good: |
products = [Product(**item) for item in items]
db.add_all(products)
- id: "PERF-007"
name: "Connection pool configuration"
severity: info
description: |
Configure database connection pool for optimal performance:
- pool_size: Number of persistent connections
- max_overflow: Additional connections allowed
- pool_pre_ping: Check connection health
- pool_recycle: Recycle connections periodically
file_pattern: "**/database.py|**/config*.py"
suggested_patterns:
- "pool_size|pool_pre_ping|pool_recycle|max_overflow"
- id: "PERF-008"
name: "Use EXISTS for existence checks"
severity: info
description: |
Use EXISTS or .first() is not None instead of count() > 0.
EXISTS stops at first match, count() scans all matches.
file_pattern: "**/service*.py"
anti_patterns:
- '\\.count\\(\\)\\s*>\\s*0'
- '\\.count\\(\\)\\s*>=\\s*1'
- '\\.count\\(\\)\\s*!=\\s*0'
suggested_patterns:
- "exists\\(\\)|scalar\\(exists"
example_bad: |
if db.query(Order).filter_by(customer_id=id).count() > 0:
example_good: |
exists_query = db.query(exists().where(Order.customer_id == id))
if db.scalar(exists_query):
- id: "PERF-009"
name: "Batch updates instead of loops"
severity: warning
description: |
Use .update() with filters instead of updating in a loop.
One UPDATE statement is faster than N individual updates.
file_pattern: "**/service*.py"
anti_patterns:
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\w+\\.\\w+\\s*='
suggested_patterns:
- "\\.update\\(\\{"
example_bad: |
for product in products:
product.is_active = False
db.add(product)
example_good: |
db.query(Product).filter(
Product.id.in_(product_ids)
).update({"is_active": False}, synchronize_session=False)
- id: "PERF-010"
name: "Avoid SELECT * patterns"
severity: info
description: |
When you only need specific columns, don't load entire rows.
This reduces memory usage and network transfer.
file_pattern: "**/service*.py"
- id: "PERF-011"
name: "Use appropriate join strategies"
severity: info
description: |
Choose the right join strategy:
- joinedload: Few related items, always needed
- selectinload: Many related items, always needed
- subqueryload: Complex queries, many related items
- lazyload: Rarely accessed relationships
file_pattern: "**/service*.py"
- id: "PERF-012"
name: "Transaction scope optimization"
severity: warning
description: |
Keep transactions short and focused:
- Don't hold transactions during I/O
- Commit after bulk operations
- Use read-only transactions when possible
file_pattern: "**/service*.py"
- id: "PERF-013"
name: "Query result caching"
severity: info
description: |
Consider caching for:
- Frequently accessed, rarely changed data
- Configuration tables
- Reference data (categories, statuses)
file_pattern: "**/service*.py"
suggested_patterns:
- "@cache|@lru_cache|redis|memcache"
- id: "PERF-014"
name: "Composite indexes for multi-column filters"
severity: info
description: |
Queries filtering on multiple columns benefit from composite indexes.
Order columns by selectivity (most selective first).
file_pattern: "**/models/database/*.py"
suggested_patterns:
- "Index\\([^)]*,[^)]*\\)"
- id: "PERF-015"
name: "Avoid correlated subqueries"
severity: info
description: |
Correlated subqueries execute once per row.
Use JOINs or window functions instead when possible.
file_pattern: "**/service*.py"

View File

@@ -0,0 +1,177 @@
# Frontend Performance Rules
# ==========================
frontend_rules:
- id: "PERF-056"
name: "Debounce search inputs"
severity: warning
description: |
Search inputs should debounce API calls.
Recommended: 300-500ms delay.
Prevents excessive API calls while user is typing.
file_pattern: "**/*.js"
context_patterns:
- "search|filter|query"
anti_patterns:
- '@input=".*search.*fetch'
- '@keyup=".*search.*fetch'
suggested_patterns:
- "debounce|setTimeout.*search|\\$watch.*search"
example_bad: |
<input @input="searchProducts($event.target.value)">
example_good: |
<input @input="debouncedSearch($event.target.value)">
// With: debouncedSearch = debounce(searchProducts, 300)
- id: "PERF-057"
name: "Lazy load off-screen content"
severity: info
description: |
Defer loading of off-screen content:
- Modals
- Tabs (inactive)
- Below-the-fold content
- Images
file_pattern: "**/*.html"
suggested_patterns:
- 'loading="lazy"|x-intersect|x-show|x-if'
- id: "PERF-058"
name: "Image optimization"
severity: warning
description: |
Images should be optimized:
- Use appropriate formats (WebP, AVIF)
- Serve responsive sizes
- Lazy load off-screen images
- Use CDN for static assets
file_pattern: "**/*.html"
required_patterns:
- 'loading="lazy"|srcset|x-intersect'
example_good: |
<img src="image.webp" loading="lazy" alt="Product">
- id: "PERF-059"
name: "Minimize Alpine.js watchers"
severity: info
description: |
Excessive $watch calls impact performance.
Use computed properties or event handlers instead.
file_pattern: "**/*.js"
anti_patterns:
- '\\$watch\\([^)]+\\).*\\$watch\\([^)]+\\).*\\$watch\\('
- id: "PERF-060"
name: "Virtual scrolling for long lists"
severity: info
description: |
Lists with 100+ items should use virtual scrolling.
Only render visible items in the viewport.
file_pattern: "**/*.html|**/*.js"
suggested_patterns:
- "virtual-scroll|x-intersect|IntersectionObserver"
- id: "PERF-061"
name: "Minimize bundle size"
severity: info
description: |
Reduce JavaScript bundle size:
- Import only needed modules
- Use tree-shaking
- Split code by route
file_pattern: "**/*.js"
- id: "PERF-062"
name: "Reasonable polling intervals"
severity: warning
description: |
Polling should be >= 10 seconds for non-critical updates.
Lower intervals waste bandwidth and server resources.
file_pattern: "**/*.js"
anti_patterns:
- 'setInterval\\s*\\([^,]+,\\s*[1-9]\\d{0,3}\\s*\\)'
exclude_patterns:
- "# real-time required"
example_bad: |
setInterval(fetchUpdates, 1000); // Every second
example_good: |
setInterval(fetchUpdates, 30000); // Every 30 seconds
- id: "PERF-063"
name: "CSS containment"
severity: info
description: |
Use CSS containment for complex layouts.
Limits rendering scope for better performance.
file_pattern: "**/*.css|**/*.html"
suggested_patterns:
- "contain:|content-visibility"
- id: "PERF-064"
name: "Avoid layout thrashing"
severity: warning
description: |
Don't interleave DOM reads and writes.
Batch reads first, then writes.
file_pattern: "**/*.js"
anti_patterns:
- 'offsetHeight.*style\\.|style\\..*offsetHeight'
- id: "PERF-065"
name: "Use CSS animations over JavaScript"
severity: info
description: |
CSS animations are hardware-accelerated.
Use CSS for simple animations, JS for complex ones.
file_pattern: "**/*.js"
suggested_patterns:
- "transition|animation|transform"
- id: "PERF-066"
name: "Preload critical resources"
severity: info
description: |
Preload critical CSS, fonts, and above-the-fold images.
Reduces perceived load time.
file_pattern: "**/*.html"
suggested_patterns:
- 'rel="preload"|rel="prefetch"|rel="preconnect"'
- id: "PERF-067"
name: "Defer non-critical JavaScript"
severity: info
description: |
Non-critical JavaScript should be deferred.
Allows page rendering to complete first.
file_pattern: "**/*.html"
suggested_patterns:
- 'defer|async'
- id: "PERF-068"
name: "Minimize DOM nodes"
severity: info
description: |
Excessive DOM nodes slow rendering.
Target: < 1500 nodes, depth < 32, children < 60
file_pattern: "**/*.html"
- id: "PERF-069"
name: "Efficient event handlers"
severity: info
description: |
Use event delegation for repeated elements.
Add listener to parent, not each child.
file_pattern: "**/*.js"
suggested_patterns:
- "@click.delegate|event.target.closest"
- id: "PERF-070"
name: "Cache DOM queries"
severity: info
description: |
Store DOM element references instead of re-querying.
Each querySelector has performance cost.
file_pattern: "**/*.js"
anti_patterns:
- 'document\\.querySelector\\([^)]+\\).*document\\.querySelector\\('

View File

@@ -0,0 +1,156 @@
# Memory Management Performance Rules
# ====================================
memory_rules:
- id: "PERF-046"
name: "Generators for large datasets"
severity: warning
description: |
Use generators/iterators for processing large datasets.
Avoids loading everything into memory at once.
file_pattern: "**/service*.py"
anti_patterns:
- '\\.all\\(\\).*for\\s+\\w+\\s+in'
suggested_patterns:
- "yield|yield_per|iter"
example_bad: |
products = db.query(Product).all() # Loads all into memory
for product in products:
process(product)
example_good: |
for product in db.query(Product).yield_per(100):
process(product)
- id: "PERF-047"
name: "Stream large file uploads"
severity: warning
description: |
Large files should be streamed to disk, not held in memory.
Use SpooledTemporaryFile or direct disk writing.
file_pattern: "**/upload*.py|**/attachment*.py"
suggested_patterns:
- "SpooledTemporaryFile|chunk|stream"
example_bad: |
content = await file.read() # Entire file in memory
with open(path, 'wb') as f:
f.write(content)
example_good: |
with open(path, 'wb') as f:
while chunk := await file.read(8192):
f.write(chunk)
- id: "PERF-048"
name: "Chunked processing for imports"
severity: warning
description: |
Bulk imports should process in chunks:
- Read in batches
- Commit in batches
- Report progress periodically
file_pattern: "**/import*.py|**/csv*.py"
required_patterns:
- "chunk|batch|yield"
example_bad: |
rows = list(csv_reader) # All rows in memory
for row in rows:
process(row)
example_good: |
def process_in_chunks(reader, chunk_size=1000):
chunk = []
for row in reader:
chunk.append(row)
if len(chunk) >= chunk_size:
yield chunk
chunk = []
if chunk:
yield chunk
- id: "PERF-049"
name: "Context managers for resources"
severity: error
description: |
Use context managers for file operations.
Ensures resources are properly released.
file_pattern: "**/*.py"
anti_patterns:
- 'f\\s*=\\s*open\\s*\\([^)]+\\)(?!\\s*#.*context)'
- '^(?!.*with).*open\\s*\\([^)]+\\)\\s*$'
exclude_patterns:
- "# noqa: PERF-049"
- "with open"
example_bad: |
f = open('file.txt')
content = f.read()
f.close() # May not run if exception
example_good: |
with open('file.txt') as f:
content = f.read()
- id: "PERF-050"
name: "Limit in-memory collections"
severity: info
description: |
Avoid building large lists in memory.
Use generators, itertools, or database pagination.
file_pattern: "**/service*.py"
anti_patterns:
- '\\[.*for.*in.*\\](?!.*[:10])'
- id: "PERF-051"
name: "String concatenation efficiency"
severity: info
description: |
For many string concatenations, use join() or StringIO.
Repeated += creates many intermediate strings.
file_pattern: "**/*.py"
anti_patterns:
- 'for.*:\\s*\\n[^}]*\\+='
suggested_patterns:
- "\\.join\\(|StringIO"
example_bad: |
result = ""
for item in items:
result += str(item)
example_good: |
result = "".join(str(item) for item in items)
- id: "PERF-052"
name: "Efficient data structures"
severity: info
description: |
Choose appropriate data structures:
- set for membership testing
- dict for key-value lookup
- deque for queue operations
- defaultdict for grouping
file_pattern: "**/*.py"
- id: "PERF-053"
name: "Object pooling for expensive objects"
severity: info
description: |
Reuse expensive-to-create objects:
- Database connections
- HTTP clients
- Template engines
file_pattern: "**/*.py"
- id: "PERF-054"
name: "Weak references for caches"
severity: info
description: |
Use weak references for large object caches.
Allows garbage collection when memory is needed.
file_pattern: "**/*cache*.py"
suggested_patterns:
- "WeakValueDictionary|WeakKeyDictionary|weakref"
- id: "PERF-055"
name: "Slots for frequently instantiated classes"
severity: info
description: |
Use __slots__ for classes with many instances.
Reduces memory footprint per instance.
file_pattern: "**/models/**/*.py"
suggested_patterns:
- "__slots__"