@sun-asterisk/sunlint
Version: 
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
386 lines (324 loc) • 11.6 kB
JavaScript
/**
 * ESLint to Heuristic Migration Converter
 * Automated tool for migrating ESLint rules to heuristic engine
 */
const fs = require('fs');
const path = require('path');
const mapping = require('./mapping.json');
class MigrationConverter {
    constructor() {
        this.mapping = mapping;
        this.rulesDir = path.join(__dirname, '..');
        this.eslintRulesDir = path.join(__dirname, '../../integrations/eslint/plugin/rules');
    }
    /**
     * Get migration info for a specific rule
     * @param {string} ruleId - Rule ID (e.g., 'C006', 'S001')
     * @returns {Object|null} Migration info
     */
    getMigrationInfo(ruleId) {
        return this.mapping.migrations.find(m => 
            m.heuristic_rule.startsWith(ruleId)
        );
    }
    /**
     * Create heuristic rule directory structure
     * @param {string} category - Rule category
     * @param {string} ruleId - Rule ID
     * @returns {string} Created directory path
     */
    createRuleStructure(category, ruleId) {
        const ruleDir = path.join(this.rulesDir, category, ruleId);
        
        if (!fs.existsSync(ruleDir)) {
            fs.mkdirSync(ruleDir, { recursive: true });
            console.log(`✅ Created rule directory: ${ruleDir}`);
        }
        // Create rule files if they don't exist
        const files = ['analyzer.js', 'config.json', 'test.js', 'README.md'];
        files.forEach(file => {
            const filePath = path.join(ruleDir, file);
            if (!fs.existsSync(filePath)) {
                this.createRuleFile(filePath, file, ruleId, category);
            }
        });
        return ruleDir;
    }
    /**
     * Create individual rule file with template content
     * @param {string} filePath - File path to create
     * @param {string} fileName - File name
     * @param {string} ruleId - Rule ID
     * @param {string} category - Rule category
     */
    createRuleFile(filePath, fileName, ruleId, category) {
        let content = '';
        switch (fileName) {
            case 'analyzer.js':
                content = this.generateAnalyzerTemplate(ruleId, category);
                break;
            case 'config.json':
                content = this.generateConfigTemplate(ruleId, category);
                break;
            case 'test.js':
                content = this.generateTestTemplate(ruleId, category);
                break;
            case 'README.md':
                content = this.generateReadmeTemplate(ruleId, category);
                break;
        }
        fs.writeFileSync(filePath, content);
        console.log(`✅ Created: ${filePath}`);
    }
    /**
     * Generate analyzer template
     */
    generateAnalyzerTemplate(ruleId, category) {
        return `/**
 * ${ruleId} - Heuristic Rule Analyzer
 * Category: ${category}
 * 
 * TODO: Migrate logic from ESLint rule
 * ESLint rule: integrations/eslint/plugin/rules/${category}/${ruleId.toLowerCase().replace('_', '-')}.js
 */
const { PatternMatcher } = require('../common/pattern-matchers');
const { RuleHelper } = require('../common/rule-helpers');
class ${ruleId}Analyzer {
    constructor(config = {}) {
        this.config = config;
        this.patternMatcher = new PatternMatcher();
        this.helper = new RuleHelper();
    }
    /**
     * Analyze code content for rule violations
     * @param {string} content - File content
     * @param {string} filePath - File path
     * @param {Object} context - Analysis context
     * @returns {Array} Array of violations
     */
    analyze(content, filePath, context = {}) {
        const violations = [];
        // TODO: Implement heuristic analysis logic
        // This should replicate the ESLint rule behavior using pattern matching
        
        try {
            // Example pattern-based analysis
            // const patterns = this.getViolationPatterns();
            // const matches = this.patternMatcher.findMatches(content, patterns);
            // 
            // matches.forEach(match => {
            //     violations.push(this.helper.createViolation({
            //         ruleId: '${ruleId}',
            //         message: 'Rule violation detected',
            //         line: match.line,
            //         column: match.column,
            //         severity: 'error'
            //     }));
            // });
        } catch (error) {
            console.warn(\`Error analyzing \${filePath} with ${ruleId}:\`, error.message);
        }
        return violations;
    }
    /**
     * Get violation patterns for this rule
     * @returns {Array} Array of patterns to match
     */
    getViolationPatterns() {
        // TODO: Define patterns based on ESLint rule logic
        return [];
    }
}
module.exports = ${ruleId}Analyzer;
`;
    }
    /**
     * Generate config template
     */
    generateConfigTemplate(ruleId, category) {
        const migration = this.getMigrationInfo(ruleId);
        return JSON.stringify({
            "id": ruleId,
            "name": migration ? migration.heuristic_rule : ruleId,
            "category": category,
            "description": `${ruleId} heuristic rule - migrated from ESLint`,
            "severity": "error",
            "enabled": true,
            "migration": {
                "from_eslint": migration ? migration.eslint_rule : "unknown",
                "compatibility": migration ? migration.compatibility : "pending",
                "status": migration ? migration.status : "pending"
            },
            "patterns": {
                "include": ["**/*.js", "**/*.ts"],
                "exclude": ["**/*.test.*", "**/*.spec.*"]
            }
        }, null, 2);
    }
    /**
     * Generate test template
     */
    generateTestTemplate(ruleId, category) {
        return `/**
 * ${ruleId} - Rule Tests
 * Tests for heuristic rule analyzer
 */
const ${ruleId}Analyzer = require('./analyzer');
describe('${ruleId} Heuristic Rule', () => {
    let analyzer;
    beforeEach(() => {
        analyzer = new ${ruleId}Analyzer();
    });
    describe('Valid Code', () => {
        test('should not report violations for valid code', () => {
            const code = \`
                // TODO: Add valid code examples
            \`;
            const violations = analyzer.analyze(code, 'test.js');
            expect(violations).toHaveLength(0);
        });
    });
    describe('Invalid Code', () => {
        test('should report violations for invalid code', () => {
            const code = \`
                // TODO: Add invalid code examples
            \`;
            const violations = analyzer.analyze(code, 'test.js');
            expect(violations.length).toBeGreaterThan(0);
            expect(violations[0].ruleId).toBe('${ruleId}');
        });
    });
    describe('Edge Cases', () => {
        test('should handle empty code', () => {
            const violations = analyzer.analyze('', 'test.js');
            expect(violations).toHaveLength(0);
        });
        test('should handle syntax errors gracefully', () => {
            const code = 'invalid javascript syntax {{{';
            const violations = analyzer.analyze(code, 'test.js');
            expect(Array.isArray(violations)).toBe(true);
        });
    });
});
`;
    }
    /**
     * Generate README template
     */
    generateReadmeTemplate(ruleId, category) {
        const migration = this.getMigrationInfo(ruleId);
        return `# ${ruleId} - ${category.toUpperCase()} Rule
## 📋 Overview
**Rule ID**: \`${ruleId}\`  
**Category**: ${category}  
**Severity**: Error  
**Status**: ${migration ? migration.status : 'Pending Migration'}
## 🎯 Description
TODO: Add rule description after migration from ESLint.
${migration ? `
## 🔄 Migration Info
**ESLint Rule**: \`${migration.eslint_rule}\`  
**Compatibility**: ${migration.compatibility}  
**Priority**: ${migration.priority}
` : ''}
## ✅ Valid Code Examples
\`\`\`javascript
// TODO: Add valid code examples
\`\`\`
## ❌ Invalid Code Examples
\`\`\`javascript  
// TODO: Add invalid code examples that should trigger violations
\`\`\`
## ⚙️ Configuration
\`\`\`json
{
  "rules": {
    "${ruleId}": "error"
  }
}
\`\`\`
## 🧪 Testing
\`\`\`bash
# Run rule-specific tests
npm test -- ${ruleId.toLowerCase()}
# Test with SunLint CLI
sunlint --rules=${ruleId} --input=examples/
\`\`\`
---
**Migration Status**: ${migration ? migration.status : 'Pending'}  
**Last Updated**: ${new Date().toISOString().split('T')[0]}
`;
    }
    /**
     * Migrate a specific rule
     * @param {string} ruleId - Rule ID to migrate
     * @returns {boolean} Success status
     */
    async migrateRule(ruleId) {
        const migration = this.getMigrationInfo(ruleId);
        
        if (!migration) {
            console.error(`❌ No migration mapping found for rule: ${ruleId}`);
            return false;
        }
        if (migration.status === 'completed') {
            console.log(`✅ Rule ${ruleId} already migrated`);
            return true;
        }
        console.log(`🔄 Migrating rule: ${ruleId}`);
        console.log(`   ESLint: ${migration.eslint_rule}`);
        console.log(`   Category: ${migration.category}`);
        console.log(`   Compatibility: ${migration.compatibility}`);
        try {
            // Create heuristic rule structure
            this.createRuleStructure(migration.category, migration.heuristic_rule);
            console.log(`✅ Migration template created for ${ruleId}`);
            console.log(`📝 Next steps:`);
            console.log(`   1. Implement analyzer logic in rules/${migration.category}/${migration.heuristic_rule}/analyzer.js`);
            console.log(`   2. Add test cases in rules/${migration.category}/${migration.heuristic_rule}/test.js`);
            console.log(`   3. Update rule documentation`);
            console.log(`   4. Test against ESLint rule behavior`);
            return true;
        } catch (error) {
            console.error(`❌ Migration failed for ${ruleId}:`, error.message);
            return false;
        }
    }
    /**
     * Show migration statistics
     */
    showStats() {
        const stats = this.mapping.migration_stats;
        console.log('📊 Migration Statistics:');
        console.log(`   Total Rules: ${stats.total_rules}`);
        console.log(`   Completed: ${stats.completed}`);
        console.log(`   Pending: ${stats.pending}`);
        console.log('');
        console.log('📋 By Category:');
        Object.entries(stats.by_category).forEach(([category, data]) => {
            console.log(`   ${category}: ${data.completed}/${data.total} completed`);
        });
    }
}
// CLI usage
if (require.main === module) {
    const converter = new MigrationConverter();
    const args = process.argv.slice(2);
    
    if (args.includes('--stats')) {
        converter.showStats();
    } else if (args.includes('--rule')) {
        const ruleIndex = args.indexOf('--rule');
        const ruleId = args[ruleIndex + 1];
        if (ruleId) {
            converter.migrateRule(ruleId);
        } else {
            console.error('❌ Please specify a rule ID with --rule');
        }
    } else {
        console.log('🚀 SunLint Migration Converter');
        console.log('');
        console.log('Usage:');
        console.log('  node converter.js --stats          # Show migration statistics');
        console.log('  node converter.js --rule C006     # Migrate specific rule');
        console.log('');
        converter.showStats();
    }
}
module.exports = MigrationConverter;