UNPKG

@sun-asterisk/sunlint

Version:

☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards

204 lines (178 loc) 6.27 kB
/** * Rule Selection Service * Following Rule C005: Single responsibility - only handle rule selection * REFACTORED: Now uses SunlintRuleAdapter instead of direct registry access * UPDATED: Load rules from released-rules.json for consistency */ const chalk = require('chalk'); const fs = require('fs'); const path = require('path'); const RuleMappingService = require('./rule-mapping-service'); const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter'); class RuleSelectionService { constructor() { this.ruleAdapter = SunlintRuleAdapter.getInstance(); this.ruleMappingService = new RuleMappingService(); this.initialized = false; // Path works both in dev (from pages/) and npm package (from config/) this.releasedRulesPath = path.join(__dirname, '../config/released-rules.json'); } async initialize() { if (!this.initialized) { await this.ruleAdapter.initialize(); this.initialized = true; } } /** * Load released rules from released-rules.json * @param {string} [version] - Version to load (default: latest) * @returns {Object} Object with Common, Security arrays */ loadReleasedRules(version = null) { try { if (!fs.existsSync(this.releasedRulesPath)) { return null; } const data = JSON.parse(fs.readFileSync(this.releasedRulesPath, 'utf8')); const versions = data.versions || []; if (versions.length === 0) { return null; } // Get specified version or latest const targetVersion = version ? versions.find(v => v.version === version) : versions[versions.length - 1]; // Latest version if (!targetVersion) { return null; } return targetVersion.rulesByCategory; } catch (error) { return null; } } async selectRules(config, options) { // Ensure adapter is initialized await this.initialize(); const allRules = config.rules || {}; let selectedRules = []; // Try to load from released-rules.json first const releasedRules = this.loadReleasedRules(); // Determine rule selection strategy if (options.rule) { selectedRules = [options.rule]; } else if (options.rules) { selectedRules = options.rules.split(',').map(r => r.trim()); } else if (options.all) { // Load all rules from released-rules.json if (releasedRules) { selectedRules = [ ...(releasedRules.Common || []), ...(releasedRules.Security || []), ...(releasedRules.Frontend || []), ...(releasedRules.Backend || []), ...(releasedRules.Mobile || []) ]; } else { // Fallback to preset file selectedRules = this.loadPresetRules('all'); } } else if (options.quality) { // Load Common rules from released-rules.json if (releasedRules && releasedRules.Common) { selectedRules = releasedRules.Common; } else { selectedRules = this.loadPresetRules('quality'); } } else if (options.security) { // Load Security rules from released-rules.json if (releasedRules && releasedRules.Security) { selectedRules = releasedRules.Security; } else { selectedRules = this.loadPresetRules('security'); } } else if (options.category) { // Handle --category shortcut (standardized approach) const categoryRules = this.ruleAdapter.getStandardCategoryRules(options.category); selectedRules = categoryRules.map(rule => rule.id); } else { // Default: load all from released-rules.json if (releasedRules) { selectedRules = [ ...(releasedRules.Common || []), ...(releasedRules.Security || []) ]; } else { // Fallback to config rules or minimal set selectedRules = Object.keys(allRules).filter(ruleId => allRules[ruleId] !== 'off' && allRules[ruleId] !== false ); if (selectedRules.length === 0) { selectedRules = ['C006', 'C019']; // Default minimal set } } } // Convert to rule objects return selectedRules.map(ruleId => { const adapterRule = this.ruleAdapter.getRuleById(ruleId); return { id: ruleId, name: this.getRuleName(ruleId), severity: 'warning', ...(adapterRule || {}) }; }).filter(rule => rule.id); } getMinimalRuleSet() { return { rules: { 'C006': { name: 'Function Naming Convention', description: 'Function names should follow verb-noun pattern', category: 'naming', severity: 'warning' }, 'C019': { name: 'Log Level Usage', description: 'Use appropriate log levels', category: 'logging', severity: 'warning' } } }; } getRulesByCategory(category) { // Use adapter to get rules by category return this.ruleAdapter.getRulesByCategory(category).map(rule => rule.id); } getRuleName(ruleId) { // Use adapter to get rule name const rule = this.ruleAdapter.getRuleById(ruleId); return rule ? rule.name : `Rule ${ruleId}`; } /** * Load rules from preset configuration files * @param {string} presetName - Name of preset (quality, security, all) * @returns {Array} Array of rule IDs */ loadPresetRules(presetName) { try { const presetPath = path.join(__dirname, '../config/presets', `${presetName}.json`); if (!fs.existsSync(presetPath)) { console.warn(chalk.yellow(`⚠️ Preset file not found: ${presetPath}`)); return []; } const presetConfig = JSON.parse(fs.readFileSync(presetPath, 'utf8')); const ruleIds = Object.keys(presetConfig.rules || {}); if (ruleIds.length === 0) { console.warn(chalk.yellow(`⚠️ No rules found in preset: ${presetName}`)); return []; } console.log(chalk.green(`✅ Loaded ${ruleIds.length} rules from ${presetName} preset`)); return ruleIds; } catch (error) { console.error(chalk.red(`❌ Failed to load preset ${presetName}:`, error.message)); return []; } } } module.exports = RuleSelectionService;