javascript
examples
examples.jsā”javascript
/**
* 20.5 Code Quality & Coverage - Examples
*
* Demonstrates code quality tools, coverage concepts, and static analysis
*/
// ============================================
// ESLINT CONFIGURATION EXAMPLES
// ============================================
console.log('=== ESLint Configuration Examples ===\n');
/**
* Example ESLint configuration generator
*/
const eslintConfigs = {
// Minimal configuration
minimal: {
env: { es2021: true, node: true },
extends: ['eslint:recommended'],
parserOptions: { ecmaVersion: 'latest' },
rules: {},
},
// Strict configuration
strict: {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// Possible Errors
'no-console': 'warn',
'no-debugger': 'error',
'no-duplicate-case': 'error',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-extra-semi': 'error',
'no-template-curly-in-string': 'error',
// Best Practices
curly: ['error', 'all'],
'default-case': 'error',
eqeqeq: ['error', 'always'],
'no-eval': 'error',
'no-implied-eval': 'error',
'no-return-await': 'error',
'no-throw-literal': 'error',
'prefer-promise-reject-errors': 'error',
// Variables
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-use-before-define': ['error', { functions: false }],
'no-shadow': 'error',
// Stylistic
indent: ['error', 2],
quotes: ['error', 'single', { avoidEscape: true }],
semi: ['error', 'always'],
'comma-dangle': ['error', 'never'],
'max-len': ['warn', { code: 100, ignoreStrings: true }],
// ES6
'prefer-const': 'error',
'prefer-arrow-callback': 'error',
'no-var': 'error',
'arrow-spacing': 'error',
'prefer-template': 'error',
},
},
// With TypeScript
typescript: {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
plugins: ['@typescript-eslint'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
},
},
};
console.log('Minimal config:', JSON.stringify(eslintConfigs.minimal, null, 2));
console.log(
'\nStrict config rules count:',
Object.keys(eslintConfigs.strict.rules).length
);
// ============================================
// CODE COVERAGE ANALYZER
// ============================================
console.log('\n=== Code Coverage Analyzer ===\n');
/**
* Simple coverage tracker implementation
* (Real coverage uses Istanbul/NYC)
*/
class CoverageTracker {
constructor() {
this.coverage = new Map();
}
// Initialize coverage for a file
initFile(filename, totalLines, totalBranches, totalFunctions) {
this.coverage.set(filename, {
lines: {
total: totalLines,
covered: new Set(),
uncovered: new Set(Array.from({ length: totalLines }, (_, i) => i + 1)),
},
branches: {
total: totalBranches,
covered: new Set(),
},
functions: {
total: totalFunctions,
covered: new Set(),
},
statements: {
total: totalLines, // Simplified
covered: new Set(),
},
});
}
// Mark line as covered
coverLine(filename, lineNumber) {
const file = this.coverage.get(filename);
if (!file) return;
file.lines.covered.add(lineNumber);
file.lines.uncovered.delete(lineNumber);
file.statements.covered.add(lineNumber);
}
// Mark branch as covered
coverBranch(filename, branchId) {
const file = this.coverage.get(filename);
if (!file) return;
file.branches.covered.add(branchId);
}
// Mark function as covered
coverFunction(filename, functionName) {
const file = this.coverage.get(filename);
if (!file) return;
file.functions.covered.add(functionName);
}
// Calculate percentage
getPercentage(covered, total) {
if (total === 0) return 100;
return ((covered / total) * 100).toFixed(2);
}
// Get file coverage
getFileCoverage(filename) {
const file = this.coverage.get(filename);
if (!file) return null;
return {
filename,
lines: {
pct: this.getPercentage(file.lines.covered.size, file.lines.total),
covered: file.lines.covered.size,
total: file.lines.total,
uncovered: Array.from(file.lines.uncovered),
},
branches: {
pct: this.getPercentage(
file.branches.covered.size,
file.branches.total
),
covered: file.branches.covered.size,
total: file.branches.total,
},
functions: {
pct: this.getPercentage(
file.functions.covered.size,
file.functions.total
),
covered: file.functions.covered.size,
total: file.functions.total,
},
statements: {
pct: this.getPercentage(
file.statements.covered.size,
file.statements.total
),
covered: file.statements.covered.size,
total: file.statements.total,
},
};
}
// Get summary
getSummary() {
let totalLines = 0,
coveredLines = 0;
let totalBranches = 0,
coveredBranches = 0;
let totalFunctions = 0,
coveredFunctions = 0;
let totalStatements = 0,
coveredStatements = 0;
for (const file of this.coverage.values()) {
totalLines += file.lines.total;
coveredLines += file.lines.covered.size;
totalBranches += file.branches.total;
coveredBranches += file.branches.covered.size;
totalFunctions += file.functions.total;
coveredFunctions += file.functions.covered.size;
totalStatements += file.statements.total;
coveredStatements += file.statements.covered.size;
}
return {
lines: {
pct: this.getPercentage(coveredLines, totalLines),
covered: coveredLines,
total: totalLines,
},
branches: {
pct: this.getPercentage(coveredBranches, totalBranches),
covered: coveredBranches,
total: totalBranches,
},
functions: {
pct: this.getPercentage(coveredFunctions, totalFunctions),
covered: coveredFunctions,
total: totalFunctions,
},
statements: {
pct: this.getPercentage(coveredStatements, totalStatements),
covered: coveredStatements,
total: totalStatements,
},
};
}
// Generate text report
generateReport() {
const files = [];
for (const filename of this.coverage.keys()) {
files.push(this.getFileCoverage(filename));
}
const summary = this.getSummary();
return { files, summary };
}
// Print console report
printReport() {
const report = this.generateReport();
console.log(
'\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'
);
console.log(
'ā Coverage Report ā'
);
console.log(
'ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£'
);
// Print each file
for (const file of report.files) {
console.log(`ā ${file.filename.padEnd(56)} ā`);
console.log(
`ā Lines: ${this.formatBar(file.lines.pct)} ${file.lines.pct.padStart(
6
)}% ā`
);
console.log(
`ā Branch: ${this.formatBar(
file.branches.pct
)} ${file.branches.pct.padStart(5)}% ā`
);
console.log(
`ā Funcs: ${this.formatBar(
file.functions.pct
)} ${file.functions.pct.padStart(6)}% ā`
);
if (file.lines.uncovered.length > 0) {
console.log(
`ā Uncovered: ${file.lines.uncovered.slice(0, 10).join(', ')} ā`
);
}
}
console.log(
'ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£'
);
console.log(
'ā Summary ā'
);
console.log(
`ā Lines: ${this.formatBar(
report.summary.lines.pct
)} ${report.summary.lines.pct.padStart(6)}% ā`
);
console.log(
`ā Branches: ${this.formatBar(
report.summary.branches.pct
)} ${report.summary.branches.pct.padStart(6)}% ā`
);
console.log(
`ā Functions: ${this.formatBar(
report.summary.functions.pct
)} ${report.summary.functions.pct.padStart(6)}% ā`
);
console.log(
`ā Statements: ${this.formatBar(
report.summary.statements.pct
)} ${report.summary.statements.pct.padStart(6)}% ā`
);
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'
);
}
formatBar(pct) {
const filled = Math.round(parseFloat(pct) / 5);
const empty = 20 - filled;
return 'ā'.repeat(filled) + 'ā'.repeat(empty);
}
}
// Demo coverage tracking
const tracker = new CoverageTracker();
// Initialize files
tracker.initFile('calculator.js', 20, 4, 5);
tracker.initFile('validator.js', 15, 6, 3);
// Simulate test coverage
// Calculator - well covered
for (let i = 1; i <= 20; i++) tracker.coverLine('calculator.js', i);
tracker.coverBranch('calculator.js', 1);
tracker.coverBranch('calculator.js', 2);
tracker.coverBranch('calculator.js', 3);
tracker.coverBranch('calculator.js', 4);
['add', 'subtract', 'multiply', 'divide', 'modulo'].forEach((fn) =>
tracker.coverFunction('calculator.js', fn)
);
// Validator - partially covered
for (let i = 1; i <= 10; i++) tracker.coverLine('validator.js', i);
tracker.coverBranch('validator.js', 1);
tracker.coverBranch('validator.js', 2);
tracker.coverBranch('validator.js', 3);
['validateEmail', 'validatePhone'].forEach((fn) =>
tracker.coverFunction('validator.js', fn)
);
tracker.printReport();
// ============================================
// COMPLEXITY ANALYZER
// ============================================
console.log('\n=== Complexity Analyzer ===\n');
/**
* Cyclomatic complexity calculator (simplified)
*/
class ComplexityAnalyzer {
constructor() {
this.decisionPoints = [
'if',
'else if',
'for',
'while',
'case',
'catch',
'&&',
'||',
'?',
];
}
// Count complexity from source code
analyze(source, functionName = 'anonymous') {
let complexity = 1;
// Count if statements
const ifCount = (source.match(/\bif\s*\(/g) || []).length;
// Count else if
const elseIfCount = (source.match(/\belse\s+if\s*\(/g) || []).length;
// Count loops
const forCount = (source.match(/\bfor\s*\(/g) || []).length;
const whileCount = (source.match(/\bwhile\s*\(/g) || []).length;
const doWhileCount = (source.match(/\bdo\s*\{/g) || []).length;
// Count switch cases
const caseCount = (source.match(/\bcase\s+/g) || []).length;
// Count catch blocks
const catchCount = (source.match(/\bcatch\s*\(/g) || []).length;
// Count logical operators
const andCount = (source.match(/&&/g) || []).length;
const orCount = (source.match(/\|\|/g) || []).length;
// Count ternary operators
const ternaryCount = (source.match(/\?[^?]/g) || []).length;
// Count nullish coalescing
const nullishCount = (source.match(/\?\?/g) || []).length;
complexity +=
ifCount +
forCount +
whileCount +
doWhileCount +
caseCount +
catchCount +
andCount +
orCount +
ternaryCount +
nullishCount;
// Don't double count else if (already counted in if)
// complexity -= elseIfCount;
return {
name: functionName,
complexity,
breakdown: {
if: ifCount,
elseIf: elseIfCount,
for: forCount,
while: whileCount + doWhileCount,
case: caseCount,
catch: catchCount,
'&&': andCount,
'||': orCount,
'?:': ternaryCount,
'??': nullishCount,
},
risk: this.getRisk(complexity),
};
}
getRisk(complexity) {
if (complexity <= 4)
return {
level: 'low',
color: 'green',
recommendation: 'Well structured',
};
if (complexity <= 7)
return {
level: 'moderate',
color: 'yellow',
recommendation: 'Consider simplifying',
};
if (complexity <= 10)
return {
level: 'high',
color: 'orange',
recommendation: 'Should refactor',
};
return {
level: 'very high',
color: 'red',
recommendation: 'Must refactor immediately',
};
}
// Analyze multiple functions
analyzeMultiple(functions) {
return functions.map(({ source, name }) => this.analyze(source, name));
}
// Print complexity report
printReport(results) {
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'
);
console.log(
'ā Complexity Report ā'
);
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāā¤'
);
console.log(
'ā Function ā Complexity ā Risk Level ā'
);
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāā¤'
);
for (const result of results) {
const name = result.name.padEnd(22);
const complexity = result.complexity.toString().padStart(5);
const risk = result.risk.level.padEnd(18);
console.log(`ā ${name} ā ${complexity} ā ${risk} ā`);
}
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāā'
);
// Warnings
const highRisk = results.filter((r) => r.complexity > 10);
if (highRisk.length > 0) {
console.log('\nā ļø High complexity functions that need refactoring:');
highRisk.forEach((r) => {
console.log(` - ${r.name} (complexity: ${r.complexity})`);
});
}
}
}
const complexityAnalyzer = new ComplexityAnalyzer();
// Analyze some example functions
const functions = [
{
name: 'simpleAdd',
source: `
function simpleAdd(a, b) {
return a + b;
}
`,
},
{
name: 'validateInput',
source: `
function validateInput(input) {
if (!input) return false;
if (typeof input !== 'string') return false;
if (input.length < 3 || input.length > 50) return false;
return true;
}
`,
},
{
name: 'processOrder',
source: `
function processOrder(order) {
if (!order) return null;
if (!order.items || order.items.length === 0) {
return { error: 'No items' };
}
let total = 0;
for (const item of order.items) {
if (item.quantity > 0 && item.price > 0) {
total += item.quantity * item.price;
}
}
if (order.coupon) {
switch (order.coupon.type) {
case 'percent':
total *= (1 - order.coupon.value / 100);
break;
case 'fixed':
total -= order.coupon.value;
break;
case 'bogo':
// Buy one get one
break;
}
}
return { total };
}
`,
},
{
name: 'complexConditions',
source: `
function complexConditions(a, b, c) {
if ((a && b) || (c && !a) || (a && b && c)) {
for (let i = 0; i < 10; i++) {
while (condition) {
if (x > y) {
return a ? b : c;
}
}
}
}
return null;
}
`,
},
];
const results = complexityAnalyzer.analyzeMultiple(functions);
complexityAnalyzer.printReport(results);
// ============================================
// QUALITY GATE CHECKER
// ============================================
console.log('\n=== Quality Gate Checker ===\n');
/**
* Quality gate implementation
*/
class QualityGate {
constructor(config = {}) {
this.thresholds = {
coverage: {
lines: config.coverageLines ?? 80,
branches: config.coverageBranches ?? 80,
functions: config.coverageFunctions ?? 80,
statements: config.coverageStatements ?? 80,
},
complexity: {
max: config.maxComplexity ?? 10,
avgMax: config.avgMaxComplexity ?? 5,
},
duplication: {
maxPercent: config.maxDuplication ?? 3,
},
lint: {
maxErrors: config.maxLintErrors ?? 0,
maxWarnings: config.maxLintWarnings ?? 10,
},
security: {
maxCritical: 0,
maxHigh: 0,
maxMedium: config.maxMediumVulns ?? 5,
},
};
}
// Check coverage
checkCoverage(coverage) {
const issues = [];
if (coverage.lines < this.thresholds.coverage.lines) {
issues.push({
type: 'coverage',
metric: 'lines',
actual: coverage.lines,
threshold: this.thresholds.coverage.lines,
message: `Line coverage ${coverage.lines}% is below ${this.thresholds.coverage.lines}%`,
});
}
if (coverage.branches < this.thresholds.coverage.branches) {
issues.push({
type: 'coverage',
metric: 'branches',
actual: coverage.branches,
threshold: this.thresholds.coverage.branches,
message: `Branch coverage ${coverage.branches}% is below ${this.thresholds.coverage.branches}%`,
});
}
if (coverage.functions < this.thresholds.coverage.functions) {
issues.push({
type: 'coverage',
metric: 'functions',
actual: coverage.functions,
threshold: this.thresholds.coverage.functions,
message: `Function coverage ${coverage.functions}% is below ${this.thresholds.coverage.functions}%`,
});
}
return issues;
}
// Check complexity
checkComplexity(complexityResults) {
const issues = [];
for (const result of complexityResults) {
if (result.complexity > this.thresholds.complexity.max) {
issues.push({
type: 'complexity',
function: result.name,
actual: result.complexity,
threshold: this.thresholds.complexity.max,
message: `${result.name} has complexity ${result.complexity} (max: ${this.thresholds.complexity.max})`,
});
}
}
// Check average
const avgComplexity =
complexityResults.reduce((sum, r) => sum + r.complexity, 0) /
complexityResults.length;
if (avgComplexity > this.thresholds.complexity.avgMax) {
issues.push({
type: 'complexity',
metric: 'average',
actual: avgComplexity.toFixed(2),
threshold: this.thresholds.complexity.avgMax,
message: `Average complexity ${avgComplexity.toFixed(2)} exceeds ${
this.thresholds.complexity.avgMax
}`,
});
}
return issues;
}
// Check lint results
checkLint(lintResults) {
const issues = [];
const errors = lintResults.filter((r) => r.severity === 'error').length;
const warnings = lintResults.filter((r) => r.severity === 'warning').length;
if (errors > this.thresholds.lint.maxErrors) {
issues.push({
type: 'lint',
severity: 'error',
actual: errors,
threshold: this.thresholds.lint.maxErrors,
message: `${errors} lint errors (max: ${this.thresholds.lint.maxErrors})`,
});
}
if (warnings > this.thresholds.lint.maxWarnings) {
issues.push({
type: 'lint',
severity: 'warning',
actual: warnings,
threshold: this.thresholds.lint.maxWarnings,
message: `${warnings} lint warnings (max: ${this.thresholds.lint.maxWarnings})`,
});
}
return issues;
}
// Run full quality check
check(metrics) {
const allIssues = [];
if (metrics.coverage) {
allIssues.push(...this.checkCoverage(metrics.coverage));
}
if (metrics.complexity) {
allIssues.push(...this.checkComplexity(metrics.complexity));
}
if (metrics.lint) {
allIssues.push(...this.checkLint(metrics.lint));
}
const passed = allIssues.length === 0;
return {
passed,
issueCount: allIssues.length,
issues: allIssues,
summary: passed
? 'ā
Quality gate passed'
: `ā Quality gate failed with ${allIssues.length} issues`,
};
}
// Print result
printResult(result) {
console.log(
'\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'
);
console.log(
`ā Quality Gate Result ā`
);
console.log(
'ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£'
);
console.log(
`ā Status: ${
result.passed ? 'ā
PASSED' : 'ā FAILED'
} ā`
);
console.log(`ā Issues: ${result.issueCount.toString().padEnd(47)} ā`);
console.log(
'ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£'
);
if (result.issues.length > 0) {
console.log(
'ā Issues: ā'
);
for (const issue of result.issues) {
const msg = ` - ${issue.message}`.substring(0, 57).padEnd(57);
console.log(`ā${msg} ā`);
}
} else {
console.log(
'ā No issues found! ā'
);
}
console.log(
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'
);
}
}
// Demo quality gate
const gate = new QualityGate({
coverageLines: 80,
coverageBranches: 75,
maxComplexity: 10,
});
const metrics = {
coverage: {
lines: 85,
branches: 72, // Below threshold
functions: 90,
statements: 85,
},
complexity: results, // From previous analysis
lint: [
{ rule: 'no-unused-vars', severity: 'warning' },
{ rule: 'no-console', severity: 'warning' },
],
};
const gateResult = gate.check(metrics);
gate.printResult(gateResult);
// ============================================
// LINTING RULE EXAMPLES
// ============================================
console.log('\n=== Common Lint Issues ===\n');
/**
* Examples of code that would trigger lint errors
* (These are intentionally "bad" for demonstration)
*/
// Example lint issues and fixes
const lintExamples = [
{
rule: 'no-unused-vars',
bad: `
const unused = 'value'; // Never used
function fn() {
return 42;
}`,
good: `
function fn() {
return 42;
}`,
fix: 'Remove unused variable or prefix with underscore',
},
{
rule: 'eqeqeq',
bad: `
if (x == null) { } // Type coercion
if (y == '5') { }`,
good: `
if (x === null || x === undefined) { }
if (y === '5') { }`,
fix: 'Use === and !== instead of == and !=',
},
{
rule: 'no-var',
bad: `
var name = 'John';
var count = 0;`,
good: `
const name = 'John';
let count = 0;`,
fix: 'Use const for constants, let for variables',
},
{
rule: 'prefer-const',
bad: `
let value = 'never changes';
console.log(value);`,
good: `
const value = 'never changes';
console.log(value);`,
fix: 'Use const when variable is never reassigned',
},
{
rule: 'no-console',
bad: `
console.log('debug info');
console.error('something went wrong');`,
good: `
logger.debug('debug info');
logger.error('something went wrong');`,
fix: 'Use a proper logging library in production',
},
{
rule: 'curly',
bad: `
if (condition) doSomething();
for (let i = 0; i < 5; i++) process(i);`,
good: `
if (condition) {
doSomething();
}
for (let i = 0; i < 5; i++) {
process(i);
}`,
fix: 'Always use curly braces for control structures',
},
];
console.log('Common ESLint Issues and Fixes:\n');
lintExamples.forEach(({ rule, bad, good, fix }, index) => {
console.log(`${index + 1}. Rule: ${rule}`);
console.log(` Fix: ${fix}\n`);
});
// ============================================
// CODE DUPLICATION DETECTOR
// ============================================
console.log('\n=== Code Duplication Detector ===\n');
/**
* Simple code duplication detector
*/
class DuplicationDetector {
constructor(options = {}) {
this.minLines = options.minLines || 3;
this.minTokens = options.minTokens || 20;
}
// Normalize code for comparison
normalize(code) {
return code
.replace(/\/\/.*/g, '') // Remove single-line comments
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
.replace(/\s+/g, ' ') // Normalize whitespace
.replace(/['"].*?['"]/g, '""') // Normalize strings
.replace(/\b\d+\b/g, '0') // Normalize numbers
.trim();
}
// Get chunks of code
getChunks(code, chunkSize = 3) {
const lines = code.split('\n').filter((l) => l.trim());
const chunks = [];
for (let i = 0; i <= lines.length - chunkSize; i++) {
const chunk = lines.slice(i, i + chunkSize).join('\n');
chunks.push({
start: i + 1,
end: i + chunkSize,
code: chunk,
normalized: this.normalize(chunk),
});
}
return chunks;
}
// Find duplicates
findDuplicates(files) {
const allChunks = [];
// Collect all chunks
for (const file of files) {
const chunks = this.getChunks(file.code);
chunks.forEach((chunk) => {
allChunks.push({
...chunk,
file: file.name,
});
});
}
// Find duplicates
const duplicates = [];
const seen = new Map();
for (const chunk of allChunks) {
const key = chunk.normalized;
if (key.length < this.minTokens) continue;
if (seen.has(key)) {
const original = seen.get(key);
// Skip if same file and overlapping
if (
original.file === chunk.file &&
Math.abs(original.start - chunk.start) < this.minLines
) {
continue;
}
duplicates.push({
original,
duplicate: chunk,
});
} else {
seen.set(key, chunk);
}
}
return duplicates;
}
// Calculate duplication percentage
calculatePercentage(files, duplicates) {
const totalLines = files.reduce(
(sum, f) => sum + f.code.split('\n').length,
0
);
const duplicateLines = duplicates.length * this.minLines;
return ((duplicateLines / totalLines) * 100).toFixed(2);
}
// Generate report
report(files) {
const duplicates = this.findDuplicates(files);
const percentage = this.calculatePercentage(files, duplicates);
console.log(
`Found ${duplicates.length} duplicate blocks (${percentage}% duplication)`
);
if (duplicates.length > 0) {
console.log('\nDuplicate blocks:');
duplicates.slice(0, 5).forEach(({ original, duplicate }, i) => {
console.log(`\n${i + 1}. Duplicate found:`);
console.log(
` Original: ${original.file}:${original.start}-${original.end}`
);
console.log(
` Duplicate: ${duplicate.file}:${duplicate.start}-${duplicate.end}`
);
});
}
return { duplicates, percentage };
}
}
// Demo duplication detection
const detector = new DuplicationDetector();
const sourceFiles = [
{
name: 'file1.js',
code: `
function validateEmail(email) {
if (!email) return false;
if (!email.includes('@')) return false;
return true;
}
function processUser(user) {
if (!user.email) return null;
if (!user.email.includes('@')) return null;
return user;
}
`,
},
{
name: 'file2.js',
code: `
function checkEmail(value) {
if (!value) return false;
if (!value.includes('@')) return false;
return true;
}
`,
},
];
detector.report(sourceFiles);
// ============================================
// SUMMARY
// ============================================
console.log('\n=== Code Quality Summary ===\n');
console.log('Key Metrics Covered:');
console.log('1. Code Coverage - Line, branch, function, statement');
console.log('2. Complexity - Cyclomatic complexity analysis');
console.log('3. Quality Gates - Automated quality checks');
console.log('4. Linting - ESLint configuration and rules');
console.log('5. Duplication - Code clone detection');
console.log('\nTools Demonstrated:');
console.log('- CoverageTracker: Custom coverage measurement');
console.log('- ComplexityAnalyzer: Cyclomatic complexity calculation');
console.log('- QualityGate: Automated quality threshold checking');
console.log('- DuplicationDetector: Code clone detection');
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
CoverageTracker,
ComplexityAnalyzer,
QualityGate,
DuplicationDetector,
eslintConfigs,
lintExamples,
};
}