190 lines
5.7 KiB
JavaScript
Executable File
190 lines
5.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Hyperscript Syntax Validator
|
|
* Validates hyperscript 0.9.12 syntax rules
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname, join } from 'path';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const HYPERSCRIPT_FILE = join(__dirname, 'static/hyperscript/functions._hs');
|
|
|
|
// Validation Rules for Hyperscript 0.9.12
|
|
const validationRules = [
|
|
{
|
|
name: 'No else statements',
|
|
pattern: /\belse\b/,
|
|
shouldNotMatch: true,
|
|
severity: 'error',
|
|
message: 'Hyperscript 0.9.12 does not support "else". Use separate "if not" blocks.'
|
|
},
|
|
{
|
|
name: 'All def blocks have end',
|
|
pattern: /^def\s+\w+/gm,
|
|
validator: (content, matches) => {
|
|
const defCount = matches.length;
|
|
const endCount = (content.match(/^end$/gm) || []).length;
|
|
|
|
// We need at least as many 'end' as 'def' (some 'end' are for loops/conditionals)
|
|
return endCount >= defCount;
|
|
},
|
|
severity: 'error',
|
|
message: 'All function definitions must end with "end"'
|
|
},
|
|
{
|
|
name: 'Valid function names',
|
|
pattern: /^def\s+([a-zA-Z]\w*)/gm,
|
|
validator: (content, matches) => {
|
|
return matches.every(match => /^[a-zA-Z][a-zA-Z0-9]*$/.test(match[1]));
|
|
},
|
|
severity: 'error',
|
|
message: 'Function names must start with a letter and contain only alphanumeric characters'
|
|
},
|
|
{
|
|
name: 'No nested event handlers',
|
|
pattern: /def\s+\w+[\s\S]*?on\s+\w+[\s\S]*?end/,
|
|
shouldNotMatch: true,
|
|
severity: 'error',
|
|
message: 'Hyperscript 0.9.12 does not support nested event handlers (on ... end inside def)'
|
|
},
|
|
{
|
|
name: 'Proper localStorage calls',
|
|
pattern: /localStorage\.(setItem|getItem)/,
|
|
validator: (content, matches) => {
|
|
// Check if all localStorage calls use proper syntax
|
|
const setItemCalls = content.match(/call\s+localStorage\.setItem\([^)]+\)/g) || [];
|
|
const getItemCalls = content.match(/localStorage\.getItem\([^)]+\)/g) || [];
|
|
return setItemCalls.length + getItemCalls.length === matches.length;
|
|
},
|
|
severity: 'warning',
|
|
message: 'localStorage should be called with "call localStorage.setItem(...)" or "localStorage.getItem(...)"'
|
|
},
|
|
{
|
|
name: 'Proper class manipulation',
|
|
pattern: /(add|remove)\s+\./,
|
|
validator: (content, matches) => {
|
|
// All class additions/removals should use proper syntax
|
|
return matches.every(match => {
|
|
const fullMatch = match[0];
|
|
return /^(add|remove)\s+\.\w+\s+(to|from)\s+/.test(fullMatch);
|
|
});
|
|
},
|
|
severity: 'error',
|
|
message: 'Class manipulation must use "add .class to element" or "remove .class from element"'
|
|
}
|
|
];
|
|
|
|
function validateFile(filePath) {
|
|
console.log('🔍 Validating Hyperscript File:', filePath);
|
|
console.log('='.repeat(80));
|
|
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const lines = content.split('\n');
|
|
|
|
console.log(`📄 File: ${lines.length} lines\n`);
|
|
|
|
let errors = 0;
|
|
let warnings = 0;
|
|
let passed = 0;
|
|
|
|
validationRules.forEach(rule => {
|
|
const matches = [...content.matchAll(new RegExp(rule.pattern, 'gm'))];
|
|
|
|
let isValid = true;
|
|
let details = '';
|
|
|
|
if (rule.shouldNotMatch) {
|
|
isValid = matches.length === 0;
|
|
if (!isValid) {
|
|
details = `Found ${matches.length} occurrence(s)`;
|
|
}
|
|
} else if (rule.validator) {
|
|
isValid = rule.validator(content, matches);
|
|
} else {
|
|
isValid = matches.length > 0;
|
|
}
|
|
|
|
const icon = isValid ? '✅' : (rule.severity === 'error' ? '❌' : '⚠️');
|
|
const status = isValid ? 'PASS' : rule.severity.toUpperCase();
|
|
|
|
console.log(`${icon} ${rule.name}: ${status}`);
|
|
|
|
if (!isValid) {
|
|
console.log(` ${rule.message}`);
|
|
if (details) {
|
|
console.log(` ${details}`);
|
|
}
|
|
|
|
if (rule.severity === 'error') {
|
|
errors++;
|
|
} else {
|
|
warnings++;
|
|
}
|
|
} else {
|
|
passed++;
|
|
}
|
|
});
|
|
|
|
console.log('\n' + '='.repeat(80));
|
|
console.log('📊 VALIDATION SUMMARY:');
|
|
console.log(` ✅ Passed: ${passed}`);
|
|
console.log(` ⚠️ Warnings: ${warnings}`);
|
|
console.log(` ❌ Errors: ${errors}`);
|
|
console.log('='.repeat(80));
|
|
|
|
if (errors > 0) {
|
|
console.log('\n❌ VALIDATION FAILED - Errors found!');
|
|
process.exit(1);
|
|
} else if (warnings > 0) {
|
|
console.log('\n⚠️ VALIDATION PASSED WITH WARNINGS');
|
|
process.exit(0);
|
|
} else {
|
|
console.log('\n✅ VALIDATION PASSED - No issues found!');
|
|
process.exit(0);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ ERROR:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Additional checks
|
|
function performAdditionalChecks(filePath) {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
|
|
console.log('\n📋 ADDITIONAL CHECKS:');
|
|
console.log('='.repeat(80));
|
|
|
|
// Count functions
|
|
const functions = [...content.matchAll(/^def\s+(\w+)/gm)];
|
|
console.log(`✅ Total functions defined: ${functions.length}`);
|
|
functions.forEach(([, name]) => {
|
|
console.log(` - ${name}()`);
|
|
});
|
|
|
|
// Check for localStorage usage
|
|
const localStorageUse = content.match(/localStorage\.(setItem|getItem)/g) || [];
|
|
console.log(`\n✅ LocalStorage operations: ${localStorageUse.length}`);
|
|
|
|
// Check for class manipulations
|
|
const classOps = content.match(/(add|remove)\s+\.\w+/g) || [];
|
|
console.log(`✅ Class manipulations: ${classOps.length}`);
|
|
|
|
// Check for loops
|
|
const loops = content.match(/for\s+\w+\s+in/g) || [];
|
|
console.log(`✅ Loop constructs: ${loops.length}`);
|
|
|
|
console.log('='.repeat(80));
|
|
}
|
|
|
|
// Run validation
|
|
validateFile(HYPERSCRIPT_FILE);
|
|
performAdditionalChecks(HYPERSCRIPT_FILE);
|