| name | static-analysis-integration |
| description | Use when integrating SAST tools (SonarQube, ESLint, Pylint, Checkstyle), setting up security scanning, configuring code quality gates, managing false positives, or building CI/CD quality pipelines - provides tool selection, configuration patterns, and quality threshold strategies |
Static Analysis Integration
Overview
Core principle: Static analysis catches bugs,security vulnerabilities, and code quality issues before code review. Automate it in CI/CD.
Rule: Block merges on critical issues, warn on moderate issues, ignore noise. Configure thresholds carefully.
Static Analysis vs Other Quality Checks
| Check Type | When | What It Finds | Speed |
|---|---|---|---|
| Static Analysis | Pre-commit/PR | Bugs, security, style | Fast (seconds) |
| Unit Tests | Every commit | Logic errors | Fast (seconds) |
| Integration Tests | PR | Integration bugs | Medium (minutes) |
| Security Scanning | PR/Nightly | Dependencies, secrets | Medium (minutes) |
| Manual Code Review | PR | Design, readability | Slow (hours) |
Static analysis finds: Null pointer bugs, SQL injection, unused variables, complexity issues
Static analysis does NOT find: Business logic errors, performance issues (use profiling)
Tool Selection by Language
Python
| Tool | Purpose | When to Use |
|---|---|---|
| Pylint | Code quality, style, bugs | General-purpose, comprehensive |
| Flake8 | Style, simple bugs | Faster than Pylint, less strict |
| mypy | Type checking | Type-safe codebases |
| Bandit | Security vulnerabilities | Security-critical code |
| Black | Code formatting | Enforce consistent style |
Recommended combo: Black (formatting) + Flake8 (linting) + mypy (types) + Bandit (security)
JavaScript/TypeScript
| Tool | Purpose | When to Use |
|---|---|---|
| ESLint | Code quality, style, bugs | All JavaScript projects |
| TypeScript | Type checking | Type-safe codebases |
| Prettier | Code formatting | Enforce consistent style |
| SonarQube | Security, bugs, code smells | Enterprise, comprehensive |
Recommended combo: Prettier (formatting) + ESLint (linting) + TypeScript (types)
Java
| Tool | Purpose | When to Use |
|---|---|---|
| Checkstyle | Code style | Enforce coding standards |
| PMD | Bug detection, code smells | General-purpose |
| SpotBugs | Bug detection | Bytecode analysis |
| SonarQube | Comprehensive analysis | Enterprise, dashboards |
Recommended combo: Checkstyle (style) + SpotBugs (bugs) + SonarQube (comprehensive)
Configuration Patterns
ESLint Configuration (JavaScript)
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:security/recommended'
],
rules: {
// Error: Block merge
'no-console': 'error',
'no-debugger': 'error',
'@typescript-eslint/no-explicit-any': 'error',
// Warning: Allow merge, but warn
'complexity': ['warn', 10],
'max-lines': ['warn', 500],
// Off: Too noisy
'no-unused-vars': 'off', // TypeScript handles this
}
};
Run in CI:
eslint src/ --max-warnings 0 # Fail if any warnings
Pylint Configuration (Python)
# .pylintrc
[MESSAGES CONTROL]
disable=
missing-docstring, # Too noisy for small projects
too-few-public-methods, # Design choice
logging-fstring-interpolation # False positives
[DESIGN]
max-line-length=100
max-args=7
max-locals=15
[BASIC]
good-names=i,j,k,_,id,db,pk
Run in CI:
pylint src/ --fail-under=8.0 # Minimum score 8.0/10
SonarQube Quality Gates
# sonar-project.properties
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=tests
# Quality gate thresholds
sonar.qualitygate.wait=true
sonar.coverage.exclusions=**/*_test.py,**/migrations/**
# Fail conditions
sonar.qualitygate.timeout=300
Quality Gate Criteria:
- Blocker/Critical issues: 0 (block merge)
- Major issues: < 5 (block merge)
- Code coverage: > 80% (warn if lower)
- Duplicated lines: < 3%
- Maintainability rating: A or B
CI/CD Integration
GitHub Actions (Python)
# .github/workflows/static-analysis.yml
name: Static Analysis
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install pylint flake8 mypy bandit black
- name: Check formatting
run: black --check src/
- name: Run Flake8
run: flake8 src/ --max-line-length=100
- name: Run Pylint
run: pylint src/ --fail-under=8.0
- name: Run mypy
run: mypy src/ --strict
- name: Run Bandit (security)
run: bandit -r src/ -ll # Only high severity
GitHub Actions (JavaScript)
# .github/workflows/static-analysis.yml
name: Static Analysis
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Check formatting
run: npm run format:check # prettier --check
- name: Run ESLint
run: npm run lint # eslint --max-warnings 0
- name: Run TypeScript
run: npm run typecheck # tsc --noEmit
Managing False Positives
Strategy: Suppress selectively, document why
Inline Suppression (ESLint)
// eslint-disable-next-line no-console
console.log("Debugging production issue"); // TODO: Remove after fix
// Better: Explain WHY
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacyData: any = externalLibrary.getData(); // Library has no types
File-Level Suppression (Pylint)
# pylint: disable=too-many-arguments
def complex_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8):
"""Legacy API - cannot change signature for backward compatibility."""
pass
Configuration Suppression
# .pylintrc
[MESSAGES CONTROL]
disable=
fixme, # Allow TODO comments
missing-docstring # Too noisy for this codebase
Rule: Every suppression needs a comment explaining WHY.
Security-Focused Static Analysis
Bandit (Python Security)
# .bandit.yml
exclude_dirs:
- /tests
- /migrations
tests:
- B201 # Flask debug mode
- B601 # Parameterized shell calls
- B602 # Shell injection
- B608 # SQL injection
Run:
bandit -r src/ -ll -x tests/ # Only high/medium severity
ESLint Security Plugin (JavaScript)
// .eslintrc.js
module.exports = {
plugins: ['security'],
extends: ['plugin:security/recommended'],
rules: {
'security/detect-object-injection': 'error',
'security/detect-non-literal-regexp': 'warn',
'security/detect-unsafe-regex': 'error'
}
};
Code Quality Metrics
Complexity Analysis
Cyclomatic complexity: Measures decision paths through code
# Simple function: Complexity = 1
def add(a, b):
return a + b
# Complex function: Complexity = 5 (if/elif/else = 4 paths + 1 base)
def process_order(order):
if order.status == "pending":
return validate(order)
elif order.status == "confirmed":
return ship(order)
elif order.status == "cancelled":
return refund(order)
else:
return reject(order)
Threshold:
- < 10: Acceptable
- 10-20: Consider refactoring
- > 20: Must refactor (untestable)
Configure:
# Pylint
[DESIGN]
max-complexity=10
# ESLint
complexity: ['warn', 10]
Duplication Detection
SonarQube duplication threshold: < 3%
Find duplicates (Python):
pylint src/ --disable=all --enable=duplicate-code
Find duplicates (JavaScript):
jscpd src/ # JavaScript Copy/Paste Detector
Anti-Patterns Catalog
❌ Suppressing All Warnings
Symptom: Config disables most rules
// ❌ BAD
module.exports = {
rules: {
'no-console': 'off',
'no-debugger': 'off',
'@typescript-eslint/no-explicit-any': 'off',
// ... 50 more disabled rules
}
};
Why bad: Static analysis becomes useless
Fix: Address root causes, suppress selectively
###❌ No Quality Gates
Symptom: Static analysis runs but doesn't block merges
# ❌ BAD: Linting failures don't block merge
- name: Run ESLint
run: eslint src/ || true # Always succeeds!
Fix: Fail CI on critical issues
# ✅ GOOD
- name: Run ESLint
run: eslint src/ --max-warnings 0
❌ Ignoring Security Warnings
Symptom: Security findings marked as false positives without investigation
# ❌ BAD
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # nosec
Why bad: Real SQL injection vulnerability ignored
Fix: Fix the issue, don't suppress
# ✅ GOOD
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
❌ Running Static Analysis Only on Main Branch
Symptom: Issues discovered after merge
Fix: Run on every PR
on: [pull_request] # Not just 'push' to main
Quality Dashboard Setup
SonarQube Dashboard
Key metrics to track:
- Bugs: Code issues likely to cause failures
- Vulnerabilities: Security issues
- Code Smells: Maintainability issues
- Coverage: Test coverage %
- Duplications: Duplicated code blocks
Quality Gate Example:
- Bugs (Blocker/Critical): 0
- Vulnerabilities (Blocker/Critical): 0
- Code Smells (Blocker/Critical): < 5
- Coverage on new code: > 80%
- Duplicated lines on new code: < 3%
Gradual Adoption Strategy
For legacy codebases with thousands of issues:
Phase 1: Baseline (Week 1)
# Run analysis, capture current state
pylint src/ > baseline.txt
# Configure to only fail on NEW issues
# (Track baseline, don't enforce on old code)
Phase 2: Block New Issues (Week 2)
# Block PRs that introduce NEW issues
- name: Run incremental lint
run: |
pylint $(git diff --name-only origin/main...HEAD | grep '\.py$') --fail-under=8.0
Phase 3: Fix High-Priority Old Issues (Weeks 3-8)
- Security vulnerabilities first
- Bugs second
- Code smells third
Phase 4: Full Enforcement (Week 9+)
# Enforce on entire codebase
- name: Run lint
run: pylint src/ --fail-under=8.0
Bottom Line
Static analysis catches bugs and security issues before code review. Automate it in CI/CD with quality gates.
- Choose tools for your language: ESLint (JS), Pylint (Python), Checkstyle (Java)
- Configure thresholds: Block critical issues, warn on moderate, ignore noise
- Run on every PR, fail CI on violations
- Manage false positives selectively with documented suppressions
- Track quality metrics: complexity, duplication, coverage
If static analysis isn't blocking merges, you're just generating reports nobody reads. Use quality gates.