Code Quality Assurance
Implement comprehensive code quality assurance practices to maintain high standards throughout the CI/CD pipeline.
Static Code Analysis
Static code analysis examines source code without executing it, identifying potential issues, bugs, and code quality problems.
SonarQube Integration
1. SonarQube Server Setup
# docker-compose.yml for SonarQube
version: '3.8'
services:
sonarqube:
image: sonarqube:9.9-community
container_name: sonarqube
ports:
- "9000:9000"
environment:
- SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true
- SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar
- SONAR_JDBC_USERNAME=sonar
- SONAR_JDBC_PASSWORD=sonar
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_logs:/opt/sonarqube/logs
- sonarqube_extensions:/opt/sonarqube/extensions
depends_on:
- db
db:
image: postgres:13
container_name: sonarqube-db
environment:
- POSTGRES_USER=sonar
- POSTGRES_PASSWORD=sonar
- POSTGRES_DB=sonar
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
sonarqube_data:
sonarqube_logs:
sonarqube_extensions:
postgres_data:
2. SonarQube Project Configuration
# sonar-project.properties
sonar.projectKey=myapp
sonar.projectName=My Application
sonar.projectVersion=1.0
# Source code
sonar.sources=src
sonar.tests=test
# Language specific settings
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.typescript.lcov.reportPaths=coverage/lcov.info
sonar.java.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
# Exclusions
sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**
sonar.test.exclusions=**/node_modules/**,**/dist/**,**/build/**
# Quality gates
sonar.qualitygate.wait=true
3. Jenkins SonarQube Integration
pipeline {
agent any
environment {
SONAR_HOST_URL = 'http://sonarqube:9000'
SONAR_TOKEN = credentials('sonarqube-token')
}
stages {
stage('Build') {
steps {
sh 'npm install'
sh 'npm run build'
}
}
stage('Test') {
steps {
sh 'npm run test:coverage'
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('SonarQube Analysis') {
steps {
script {
def scannerHome = tool 'SonarQubeScanner'
withSonarQubeEnv('SonarQube') {
sh """
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=myapp \
-Dsonar.sources=src \
-Dsonar.tests=test \
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info \
-Dsonar.qualitygate.wait=true
"""
}
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
post {
always {
// Clean up
cleanWs()
}
failure {
// Notify on failure
emailext (
subject: "Quality Gate Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: "Quality gate failed. Check SonarQube for details.",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
}
}
ESLint and Prettier Integration
1. ESLint Configuration
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:import/typescript'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
plugins: [
'react',
'@typescript-eslint',
'jsx-a11y',
'import'
],
rules: {
// Error prevention
'no-console': 'warn',
'no-debugger': 'error',
'no-unused-vars': 'error',
'no-undef': 'error',
// Code style
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
// React specific
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
// Import organization
'import/order': ['error', {
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always'
}]
},
settings: {
react: {
version: 'detect'
},
'import/resolver': {
typescript: {
alwaysTryTypes: true
}
}
}
};
2. Prettier Configuration
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"bracketSameLine": false
}
3. CI/CD Integration
pipeline {
agent any
stages {
stage('Code Quality Checks') {
parallel {
stage('ESLint') {
steps {
sh 'npm run lint'
}
}
stage('Prettier Check') {
steps {
sh 'npm run format:check'
}
}
stage('TypeScript Check') {
steps {
sh 'npm run type-check'
}
}
}
}
stage('Fix Code Issues') {
when {
anyOf {
branch 'develop'
branch 'main'
}
}
steps {
sh 'npm run format:fix'
sh 'npm run lint:fix'
}
}
}
}
Code Coverage Analysis
1. Coverage Configuration
// jest.config.js
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html', 'json'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
'./src/services/': {
branches: 90,
functions: 90,
lines: 90,
statements: 90
},
'./src/utils/': {
branches: 95,
functions: 95,
lines: 95,
statements: 95
}
},
collectCoverageFrom: [
'src/**/*.{js,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{js,ts,tsx}',
'!src/**/*.spec.{js,ts,tsx}',
'!src/**/index.{js,ts}',
'!src/**/types/**',
'!src/**/constants/**'
],
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{js,ts,tsx}'
],
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts']
};
2. Coverage Reporting
pipeline {
agent any
stages {
stage('Test with Coverage') {
steps {
sh 'npm run test:coverage'
}
post {
always {
// Publish coverage reports
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
// Publish coverage to Codecov
withCredentials([string(credentialsId: 'codecov-token', variable: 'CODECOV_TOKEN')]) {
sh 'npx codecov'
}
}
}
}
stage('Coverage Gate') {
steps {
script {
// Check coverage thresholds
def coverage = readFile('coverage/coverage-summary.json')
def coverageData = new groovy.json.JsonSlurper().parseText(coverage)
def totalCoverage = coverageData.total.lines.pct
if (totalCoverage < 80) {
error "Coverage ${totalCoverage}% is below threshold of 80%"
}
echo "Coverage: ${totalCoverage}% - PASSED"
}
}
}
}
}
3. Coverage Badge Generation
// scripts/generate-coverage-badge.js
const fs = require('fs');
const path = require('path');
function generateCoverageBadge() {
const coveragePath = path.join(__dirname, '../coverage/coverage-summary.json');
if (!fs.existsSync(coveragePath)) {
console.error('Coverage summary not found');
process.exit(1);
}
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
const totalCoverage = Math.round(coverage.total.lines.pct);
// Generate badge URL
const badgeUrl = `https://img.shields.io/badge/coverage-${totalCoverage}%25-${
totalCoverage >= 80 ? 'green' : totalCoverage >= 60 ? 'yellow' : 'red'
}.svg`;
// Update README with badge
const readmePath = path.join(__dirname, '../README.md');
let readme = fs.readFileSync(readmePath, 'utf8');
const badgeRegex = /!\[Coverage\]\(https:\/\/img\.shields\.io\/badge\/coverage-.*?\)/;
const newBadge = ``;
if (badgeRegex.test(readme)) {
readme = readme.replace(badgeRegex, newBadge);
} else {
readme = `${newBadge}\n\n${readme}`;
}
fs.writeFileSync(readmePath, readme);
console.log(`Coverage badge updated: ${totalCoverage}%`);
}
generateCoverageBadge();
Code Review Automation
Automated Code Review
1. Pull Request Validation
# .github/workflows/pr-validation.yml
name: Pull Request Validation
on:
pull_request:
branches: [ main, develop ]
jobs:
code-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Prettier check
run: npm run format:check
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test:coverage
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Comment PR with results
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'));
const totalCoverage = coverage.total.lines.pct;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Code Quality Report
### Coverage
- **Total Coverage**: ${totalCoverage}%
- **Threshold**: 80%
- **Status**: ${totalCoverage >= 80 ? '✅ PASSED' : '❌ FAILED'}
### Checks
- **ESLint**: ✅ PASSED
- **Prettier**: ✅ PASSED
- **TypeScript**: ✅ PASSED
- **Tests**: ✅ PASSED
${totalCoverage < 80 ? '⚠️ Coverage is below threshold. Please add more tests.' : ''}`
});
2. Code Review Checklist
## Code Review Checklist
### Code Quality
- [ ] Code follows project style guidelines
- [ ] No console.log statements in production code
- [ ] Error handling is implemented properly
- [ ] Code is readable and well-documented
- [ ] No hardcoded values or magic numbers
### Testing
- [ ] Unit tests are included for new functionality
- [ ] Test coverage meets project requirements (80%+)
- [ ] Integration tests are added where necessary
- [ ] Tests are meaningful and test actual behavior
- [ ] Mock objects are used appropriately
### Security
- [ ] No sensitive data is exposed
- [ ] Input validation is implemented
- [ ] SQL injection prevention measures
- [ ] XSS prevention measures
- [ ] Authentication/authorization checks
### Performance
- [ ] No obvious performance bottlenecks
- [ ] Database queries are optimized
- [ ] Caching is implemented where appropriate
- [ ] Memory leaks are avoided
- [ ] API response times are reasonable
### Documentation
- [ ] Code is self-documenting
- [ ] Complex logic is commented
- [ ] API documentation is updated
- [ ] README is updated if necessary
- [ ] Changelog is updated
3. Automated Review Comments
// Automated code review script
const { execSync } = require('child_process');
const fs = require('fs');
class CodeReviewer {
constructor() {
this.issues = [];
}
checkCodeQuality() {
// Check for common issues
this.checkConsoleLogs();
this.checkErrorHandling();
this.checkCodeDuplication();
this.checkComplexity();
}
checkConsoleLogs() {
const files = this.getSourceFiles();
files.forEach(file => {
const content = fs.readFileSync(file, 'utf8');
const lines = content.split('\n');
lines.forEach((line, index) => {
if (line.includes('console.log') || line.includes('console.error')) {
this.issues.push({
type: 'warning',
file: file,
line: index + 1,
message: 'Console statement found - consider removing for production'
});
}
});
});
}
checkErrorHandling() {
const files = this.getSourceFiles();
files.forEach(file => {
const content = fs.readFileSync(file, 'utf8');
// Check for try-catch blocks
const hasTryCatch = content.includes('try {') && content.includes('catch');
// Check for async/await error handling
const hasAsyncErrorHandling = content.includes('async') &&
(content.includes('try') || content.includes('.catch('));
if (!hasTryCatch && !hasAsyncErrorHandling) {
this.issues.push({
type: 'suggestion',
file: file,
message: 'Consider adding error handling for this function'
});
}
});
}
generateReport() {
const report = {
timestamp: new Date().toISOString(),
totalIssues: this.issues.length,
issues: this.issues
};
fs.writeFileSync('code-review-report.json', JSON.stringify(report, null, 2));
return report;
}
getSourceFiles() {
// Implementation to get source files
return execSync('find src -name "*.js" -o -name "*.ts" -o -name "*.tsx"')
.toString()
.trim()
.split('\n');
}
}
// Usage
const reviewer = new CodeReviewer();
reviewer.checkCodeQuality();
const report = reviewer.generateReport();
console.log(`Found ${report.totalIssues} issues`);
Technical Debt Management
Technical Debt Tracking
1. Technical Debt Metrics
# Technical debt configuration
technical_debt:
code_smells:
threshold: 100
severity:
blocker: 0
critical: 5
major: 20
minor: 75
duplications:
threshold: 3
min_tokens: 100
complexity:
cognitive_complexity: 15
cyclomatic_complexity: 10
maintainability:
technical_debt_ratio: 5
maintainability_rating: "A"
2. Technical Debt Dashboard
// Technical debt tracking
class TechnicalDebtTracker {
constructor() {
this.debtItems = [];
this.metrics = {};
}
trackCodeSmells(smells) {
smells.forEach(smell => {
this.debtItems.push({
type: 'code_smell',
severity: smell.severity,
file: smell.file,
line: smell.line,
message: smell.message,
effort: smell.effort,
createdAt: new Date()
});
});
}
trackDuplications(duplications) {
duplications.forEach(dup => {
this.debtItems.push({
type: 'duplication',
file: dup.file,
lines: dup.lines,
effort: dup.effort,
createdAt: new Date()
});
});
}
calculateDebtRatio() {
const totalEffort = this.debtItems.reduce((sum, item) => sum + item.effort, 0);
const totalLines = this.getTotalLinesOfCode();
return (totalEffort / totalLines) * 100;
}
generateDebtReport() {
const report = {
timestamp: new Date().toISOString(),
totalDebtItems: this.debtItems.length,
debtRatio: this.calculateDebtRatio(),
debtByType: this.groupDebtByType(),
debtBySeverity: this.groupDebtBySeverity(),
recommendations: this.generateRecommendations()
};
return report;
}
groupDebtByType() {
return this.debtItems.reduce((groups, item) => {
groups[item.type] = (groups[item.type] || 0) + 1;
return groups;
}, {});
}
groupDebtBySeverity() {
return this.debtItems.reduce((groups, item) => {
if (item.severity) {
groups[item.severity] = (groups[item.severity] || 0) + 1;
}
return groups;
}, {});
}
generateRecommendations() {
const recommendations = [];
if (this.calculateDebtRatio() > 5) {
recommendations.push({
priority: 'high',
message: 'Technical debt ratio is above 5%. Consider refactoring.'
});
}
const duplications = this.groupDebtByType().duplication || 0;
if (duplications > 10) {
recommendations.push({
priority: 'medium',
message: `${duplications} duplications found. Consider extracting common functionality.`
});
}
return recommendations;
}
getTotalLinesOfCode() {
// Implementation to count lines of code
return 10000; // Placeholder
}
}
3. Technical Debt Remediation
pipeline {
agent any
stages {
stage('Technical Debt Analysis') {
steps {
script {
// Run SonarQube analysis
def scannerHome = tool 'SonarQubeScanner'
withSonarQubeEnv('SonarQube') {
sh """
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=myapp \
-Dsonar.sources=src \
-Dsonar.tests=test \
-Dsonar.qualitygate.wait=true
"""
}
}
}
}
stage('Technical Debt Report') {
steps {
script {
// Generate technical debt report
sh '''
# Extract technical debt metrics from SonarQube
curl -u ${SONAR_TOKEN}: \
"http://sonarqube:9000/api/measures/component?component=myapp&metricKeys=technical_debt,code_smells,duplicated_lines_density" \
-o technical-debt.json
'''
// Process and display report
def debtData = readFile('technical-debt.json')
def metrics = new groovy.json.JsonSlurper().parseText(debtData)
echo "Technical Debt Report:"
echo "======================"
metrics.component.measures.each { measure ->
echo "${measure.metric}: ${measure.value}"
}
}
}
}
stage('Debt Remediation Planning') {
steps {
script {
// Generate remediation plan
sh '''
# Generate remediation plan based on technical debt
node scripts/generate-remediation-plan.js
'''
// Comment on PR with remediation plan
if (env.CHANGE_ID) {
sh '''
# Post remediation plan as PR comment
gh pr comment ${CHANGE_ID} --body-file remediation-plan.md
'''
}
}
}
}
}
}
Quality Gates Implementation
Automated Quality Gates
1. Quality Gate Configuration
# Quality gate configuration
quality_gates:
coverage:
line_coverage: 80
branch_coverage: 70
function_coverage: 85
code_quality:
code_smells: 100
bugs: 0
vulnerabilities: 0
security_hotspots: 0
duplications:
duplicated_lines_density: 3
duplicated_blocks: 10
maintainability:
maintainability_rating: "A"
reliability_rating: "A"
security_rating: "A"
performance:
performance_rating: "A"
response_time: 2000
throughput: 1000
2. Quality Gate Pipeline
pipeline {
agent any
stages {
stage('Quality Gate Check') {
steps {
script {
def qualityGate = new QualityGate()
// Check code coverage
def coverage = getCoverageMetrics()
qualityGate.checkCoverage(coverage)
// Check code quality
def quality = getQualityMetrics()
qualityGate.checkQuality(quality)
// Check duplications
def duplications = getDuplicationMetrics()
qualityGate.checkDuplications(duplications)
// Overall quality gate
if (!qualityGate.passed) {
error "Quality gate failed: ${qualityGate.failures.join(', ')}"
}
echo "Quality gate passed! ✅"
}
}
}
}
}
class QualityGate {
def failures = []
def passed = true
def checkCoverage(coverage) {
if (coverage.line < 80) {
failures.add("Line coverage ${coverage.line}% is below 80%")
passed = false
}
if (coverage.branch < 70) {
failures.add("Branch coverage ${coverage.branch}% is below 70%")
passed = false
}
}
def checkQuality(quality) {
if (quality.bugs > 0) {
failures.add("${quality.bugs} bugs found")
passed = false
}
if (quality.vulnerabilities > 0) {
failures.add("${quality.vulnerabilities} vulnerabilities found")
passed = false
}
if (quality.code_smells > 100) {
failures.add("${quality.code_smells} code smells found (limit: 100)")
passed = false
}
}
def checkDuplications(duplications) {
if (duplications.density > 3) {
failures.add("Duplication density ${duplications.density}% is above 3%")
passed = false
}
}
}
3. Quality Dashboard
// Quality dashboard implementation
class QualityDashboard {
constructor() {
this.metrics = {};
this.trends = [];
}
async loadMetrics() {
// Load metrics from various sources
this.metrics.coverage = await this.getCoverageMetrics();
this.metrics.quality = await this.getQualityMetrics();
this.metrics.performance = await this.getPerformanceMetrics();
this.metrics.security = await this.getSecurityMetrics();
}
generateDashboard() {
return {
overview: this.generateOverview(),
trends: this.generateTrends(),
recommendations: this.generateRecommendations(),
alerts: this.generateAlerts()
};
}
generateOverview() {
return {
overall_health: this.calculateOverallHealth(),
coverage: this.metrics.coverage,
quality: this.metrics.quality,
performance: this.metrics.performance,
security: this.metrics.security
};
}
calculateOverallHealth() {
const scores = [
this.metrics.coverage.score,
this.metrics.quality.score,
this.metrics.performance.score,
this.metrics.security.score
];
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
}
generateAlerts() {
const alerts = [];
if (this.metrics.coverage.line < 80) {
alerts.push({
type: 'warning',
message: 'Code coverage is below threshold',
metric: 'coverage',
value: this.metrics.coverage.line
});
}
if (this.metrics.quality.bugs > 0) {
alerts.push({
type: 'error',
message: 'Critical bugs found',
metric: 'bugs',
value: this.metrics.quality.bugs
});
}
return alerts;
}
}
Key Takeaways
Code Quality Best Practices
- Automated Analysis: Implement automated static analysis in CI/CD
- Coverage Requirements: Set and enforce code coverage thresholds
- Review Process: Automate code review checks and validation
- Technical Debt: Track and manage technical debt proactively
- Quality Gates: Implement quality gates to prevent low-quality code
Implementation Strategy
- Start Small: Begin with basic linting and formatting
- Gradual Improvement: Increase quality requirements over time
- Team Buy-in: Ensure team understanding and adoption
- Tool Integration: Integrate quality tools into existing workflows
- Continuous Monitoring: Monitor quality metrics and trends
Common Patterns
- Pre-commit Hooks: Run quality checks before commits
- PR Validation: Validate quality in pull requests
- Quality Gates: Block deployments for quality issues
- Reporting: Generate quality reports and dashboards
- Remediation: Plan and execute quality improvements
Next Steps: Ready to implement security testing? Continue to Section 4.3: Security Testing Integration to learn about vulnerability scanning and security compliance.
Maintaining high code quality is essential for sustainable software development. In the next section, we'll explore security testing integration to ensure secure code delivery.