javascript

examples

examples.js⚔
/**
 * 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,
  };
}
Examples - JavaScript Tutorial | DeepML