UNPKG

minangscript

Version:

Modern programming language with Minangkabau philosophy. Features native arrays (kumpulan), objects (benda), web development support, and comprehensive algorithm examples. Ready for web applications, data structures, and algorithmic programming.

429 lines (347 loc) 16 kB
// MinangScript Package Validator // Validates packages according to Minangkabau cultural principles const fs = require('fs'); const path = require('path'); const { MinangLexer } = require('../lexer/MinangLexer'); const { MinangParser } = require('../parser/MinangParser'); class MinangValidator { constructor() { this.lexer = new MinangLexer(); this.parser = new MinangParser(); } // Validate package configuration async validatePackage(packagePath) { const validation = { isValid: true, errors: [], warnings: [], qualityScore: 0, recommendations: [] }; try { console.log('🔍 Memvalidasi paket MinangScript...'); // Check package structure const structureValidation = await this.validateStructure(packagePath); validation.errors.push(...structureValidation.errors); validation.warnings.push(...structureValidation.warnings); // Validate configuration file const configValidation = await this.validateConfiguration(packagePath); validation.errors.push(...configValidation.errors); validation.warnings.push(...configValidation.warnings); // Validate MinangScript code (skip config files) const codeValidation = await this.validateMinangScriptCode(packagePath); validation.errors.push(...codeValidation.errors); validation.warnings.push(...codeValidation.warnings); // Validate code quality const qualityValidation = await this.validateCodeQuality(packagePath); validation.qualityScore = qualityValidation.score; validation.warnings.push(...qualityValidation.warnings); validation.recommendations.push(...qualityValidation.recommendations); // Validate documentation const docValidation = await this.validateDocumentation(packagePath); validation.warnings.push(...docValidation.warnings); validation.recommendations.push(...docValidation.recommendations); validation.isValid = validation.errors.length === 0; return validation; } catch (error) { validation.isValid = false; validation.errors.push(`Error validasi: ${error.message}`); return validation; } } async validateStructure(packagePath) { const validation = { errors: [], warnings: [] }; // Required files const requiredFiles = ['paket.minang', 'main.minang']; const recommendedFiles = ['README.md', 'CHANGELOG.md', 'LICENSE']; for (const file of requiredFiles) { const filePath = path.join(packagePath, file); if (!fs.existsSync(filePath)) { validation.errors.push(`File wajib tidak ditemukan: ${file}`); } } for (const file of recommendedFiles) { const filePath = path.join(packagePath, file); if (!fs.existsSync(filePath)) { validation.warnings.push(`File rekomendasi tidak ada: ${file}`); } } // Check directory structure const recommendedDirs = ['src', 'test', 'docs', 'examples']; for (const dir of recommendedDirs) { const dirPath = path.join(packagePath, dir); if (!fs.existsSync(dirPath)) { validation.warnings.push(`Direktori rekomendasi tidak ada: ${dir}`); } } return validation; } async validateConfiguration(packagePath) { const validation = { errors: [], warnings: [] }; const configPath = path.join(packagePath, 'paket.minang'); if (!fs.existsSync(configPath)) { validation.errors.push('File paket.minang tidak ditemukan'); return validation; } try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Required fields const requiredFields = ['nama', 'versi', 'deskripsi', 'pengarang']; for (const field of requiredFields) { if (!config[field]) { validation.errors.push(`Field wajib tidak ada: ${field}`); } } // Version format validation if (config.versi && !this.isValidVersion(config.versi)) { validation.errors.push(`Format versi tidak valid: ${config.versi}`); } // Quality validation if (!config.filosofi || config.filosofi.length === 0) { validation.warnings.push('Consider defining development principles'); } // License validation if (!config.lisensi) { validation.warnings.push('Lisensi tidak ditentukan'); } // Dependencies validation if (config.dependencies) { for (const [dep, version] of Object.entries(config.dependencies)) { if (!this.isValidVersion(version)) { validation.warnings.push(`Versi dependency tidak valid: ${dep}@${version}`); } } } } catch (error) { validation.errors.push(`Error parsing paket.minang: ${error.message}`); } return validation; } async validateMinangScriptCode(packagePath) { const validation = { errors: [], warnings: [] }; // Find all .minang files (exclude config files) const minangFiles = this.findMinangFiles(packagePath).filter(file => !file.endsWith('paket.minang') && !file.includes('/paket.minang') ); if (minangFiles.length === 0) { validation.warnings.push('Tidak ada file .minang ditemukan'); return validation; } for (const file of minangFiles) { try { const code = fs.readFileSync(file, 'utf8'); // Lexical analysis const tokens = this.lexer.tokenize(code); if (tokens.length === 0) { validation.warnings.push(`File kosong atau tidak valid: ${path.relative(packagePath, file)}`); continue; } // Syntax analysis try { this.parser.parse(code); } catch (parseError) { validation.errors.push(`Syntax error di ${path.relative(packagePath, file)}: ${parseError.message}`); } // Code quality analysis const qualityMetrics = this.analyzeCodeMetrics(code); if (qualityMetrics.score === 0) { validation.warnings.push(`File ${path.relative(packagePath, file)} tidak menggunakan fungsi budaya Minangkabau`); } } catch (error) { validation.errors.push(`Error membaca file ${path.relative(packagePath, file)}: ${error.message}`); } } return validation; } async validateCodeQuality(packagePath) { const validation = { score: 0, warnings: [], recommendations: [] }; // Read configuration const configPath = path.join(packagePath, 'paket.minang'); if (!fs.existsSync(configPath)) { return validation; } try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Check basic quality metrics const qualityFeatures = config.features || []; validation.score += qualityFeatures.length * 10; // Analyze code for quality implementation const minangFiles = this.findMinangFiles(packagePath); let totalQualityScore = 0; let fileCount = 0; for (const file of minangFiles) { try { const code = fs.readFileSync(file, 'utf8'); const qualityMetrics = this.analyzeCodeMetrics(code); totalQualityScore += qualityMetrics.score; fileCount++; } catch (error) { // Skip files with errors } } if (fileCount > 0) { const avgQualityScore = totalQualityScore / fileCount; validation.score += avgQualityScore; } // Check documentation quality const readmePath = path.join(packagePath, 'README.md'); if (fs.existsSync(readmePath)) { const readme = fs.readFileSync(readmePath, 'utf8').toLowerCase(); let qualityMentions = 0; const qualityKeywords = ['example', 'usage', 'installation', 'api', 'documentation']; qualityKeywords.forEach(keyword => { if (readme.includes(keyword)) { qualityMentions++; } }); validation.score += qualityMentions * 5; } // Generate recommendations based on score if (validation.score < 20) { validation.warnings.push('Low quality score - consider improving code structure'); validation.recommendations.push('Add proper documentation and examples'); validation.recommendations.push('Improve code structure and organization'); validation.recommendations.push('Add error handling where appropriate'); } else if (validation.score < 40) { validation.recommendations.push('Document implementation details in README'); validation.recommendations.push('Consider adding more examples and comments'); } else { validation.recommendations.push('Good code quality! Keep up the excellent work'); } } catch (error) { validation.warnings.push(`Error in quality validation: ${error.message}`); } return validation; } async validateDocumentation(packagePath) { const validation = { warnings: [], recommendations: [] }; // Check README.md const readmePath = path.join(packagePath, 'README.md'); if (fs.existsSync(readmePath)) { const readme = fs.readFileSync(readmePath, 'utf8'); if (readme.length < 200) { validation.warnings.push('README.md terlalu pendek - pertimbangkan menambah dokumentasi'); } // Check for required sections const requiredSections = ['installation', 'usage', 'example']; const missingSections = requiredSections.filter(section => !readme.toLowerCase().includes(section) ); if (missingSections.length > 0) { validation.recommendations.push(`Tambahkan section: ${missingSections.join(', ')}`); } // Check for cultural documentation const culturalTerms = Object.keys(this.culturalPrinciples); const hasCulturalDoc = culturalTerms.some(term => readme.toLowerCase().includes(term.replace('-', ' ')) ); if (!hasCulturalDoc) { validation.recommendations.push('Jelaskan implementasi filosofi Minangkabau dalam dokumentasi'); } } else { validation.warnings.push('README.md tidak ditemukan'); } // Check for changelog const changelogPath = path.join(packagePath, 'CHANGELOG.md'); if (!fs.existsSync(changelogPath)) { validation.recommendations.push('Tambahkan CHANGELOG.md untuk tracking perubahan'); } // Check for license const licensePath = path.join(packagePath, 'LICENSE'); if (!fs.existsSync(licensePath)) { validation.recommendations.push('Tambahkan file LICENSE'); } return validation; } findMinangFiles(packagePath) { const files = []; const walkDir = (dir) => { const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'minang_modules') { walkDir(fullPath); } else if (stat.isFile() && item.endsWith('.minang')) { files.push(fullPath); } } }; try { walkDir(packagePath); } catch (error) { // Handle permission errors gracefully } return files; } analyzeCodeMetrics(code) { let score = 0; const metrics = { functions: 0, comments: 0, documentation: 0, errorHandling: 0 }; // Check for function definitions const functionMatches = code.match(/karojo\s+\w+/g) || []; metrics.functions = functionMatches.length; score += functionMatches.length * 5; // Check for comments const commentMatches = code.match(/\/\/.*|\/\*[\s\S]*?\*\//g) || []; metrics.comments = commentMatches.length; score += Math.min(commentMatches.length * 2, 20); // Cap at 20 points // Check for error handling if (code.includes('cubo(') && code.includes('tangkok(')) { metrics.errorHandling++; score += 10; } // Check for documentation strings const docStrings = code.match(/"""[\s\S]*?"""|'''[\s\S]*?'''/g) || []; metrics.documentation = docStrings.length; score += docStrings.length * 5; return { score, metrics }; } isValidVersion(version) { // Semantic versioning validation const semverRegex = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/; return semverRegex.test(version) || version === 'latest'; } // Generate validation report generateReport(validation, packageName) { const report = []; report.push('🏔️ Laporan Validasi MinangScript'); report.push('═'.repeat(50)); report.push(`📦 Paket: ${packageName}`); report.push(`✅ Status: ${validation.isValid ? 'VALID' : 'TIDAK VALID'}`); report.push(`🎯 Skor Budaya: ${validation.culturalScore}/100`); report.push(''); if (validation.errors.length > 0) { report.push('❌ ERROR:'); validation.errors.forEach(error => { report.push(` • ${error}`); }); report.push(''); } if (validation.warnings.length > 0) { report.push('⚠️ PERINGATAN:'); validation.warnings.forEach(warning => { report.push(` • ${warning}`); }); report.push(''); } if (validation.recommendations.length > 0) { report.push('💡 REKOMENDASI:'); validation.recommendations.forEach(rec => { report.push(` • ${rec}`); }); report.push(''); } report.push('🏔️ Filosofi Minangkabau dalam Kode:'); Object.entries(this.culturalPrinciples).forEach(([key, principle]) => { const icon = validation.culturalScore > 20 ? '✅' : '⭕'; report.push(` ${icon} ${principle.name} - ${principle.description}`); }); return report.join('\n'); } } module.exports = { MinangValidator };