Files
moyosapp_beta.0.0.3.3_beta1/scripts/validate-handlers.js
moyoza 5d8a72b0a5 feat: Add handler validation script to prevent undefined function errors
- Create validate-handlers.js script to check for undefined event handlers
- Add npm run validate:handlers command
- Add prebuild hook to run validation before builds
- Add ESLint no-undef rule to catch undefined references
- Add documentation in scripts/README-validation.md

Prevents issues like 'ReferenceError: handleSaveEditGuestbook is not defined'
by validating all onClick/onChange/onSubmit handlers are defined before use.

The script:
- Scans all React components for event handlers
- Verifies functions are defined in component scope
- Excludes props and imported functions
- Runs automatically before builds
- Can be run manually: npm run validate:handlers
2026-01-22 17:30:22 +02:00

149 lines
4.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Validation script to check for undefined handler functions
* This script scans React components for onClick handlers and verifies
* that the referenced functions are defined in the component scope.
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const COMPONENT_DIR = path.join(__dirname, '../src/components');
const API_DIR = path.join(__dirname, '../src/app');
// Patterns to match handler references
const HANDLER_PATTERNS = [
/onClick\s*=\s*\{?\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}?/g,
/onChange\s*=\s*\{?\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}?/g,
/onSubmit\s*=\s*\{?\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}?/g,
/onClick\s*=\s*\([^)]*\)\s*=>\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
];
// Patterns to match function definitions
const FUNCTION_PATTERNS = [
/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)\s*=>|function)/g,
/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useCallback\s*\(/g,
/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useMemo\s*\(/g,
];
function findFiles(dir, extensions = ['.tsx', '.ts', '.jsx', '.js']) {
const files = [];
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
files.push(...findFiles(fullPath, extensions));
} else if (item.isFile() && extensions.some(ext => item.name.endsWith(ext))) {
files.push(fullPath);
}
}
return files;
}
function extractHandlers(content) {
const handlers = new Set();
for (const pattern of HANDLER_PATTERNS) {
let match;
while ((match = pattern.exec(content)) !== null) {
const handlerName = match[1];
// Skip common built-ins and React hooks
if (!['setState', 'useState', 'useEffect', 'useCallback', 'useMemo', 'prev', 'e', 'event', 'error'].includes(handlerName)) {
handlers.add(handlerName);
}
}
}
return handlers;
}
function extractFunctions(content) {
const functions = new Set();
for (const pattern of FUNCTION_PATTERNS) {
let match;
while ((match = pattern.exec(content)) !== null) {
functions.add(match[1]);
}
}
return functions;
}
function validateFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const handlers = extractHandlers(content);
const functions = extractFunctions(content);
const undefinedHandlers = [];
for (const handler of handlers) {
// Check if handler is defined as a function
if (!functions.has(handler)) {
// Check if it's a prop (passed from parent)
const isProp = new RegExp(`(?:props|\\{[^}]*)\\s*:\\s*\\{[^}]*${handler}[^}]*:`, 's').test(content) ||
new RegExp(`(?:interface|type)\\s+\\w+Props[^}]*${handler}[^}]*:`, 's').test(content);
// Check if it's from a hook or external import
const isImported = new RegExp(`(?:import|from).*${handler}`, 's').test(content);
if (!isProp && !isImported) {
undefinedHandlers.push(handler);
}
}
}
return undefinedHandlers;
}
function main() {
console.log('🔍 Validating handler functions...\n');
const componentFiles = findFiles(COMPONENT_DIR);
const apiFiles = findFiles(API_DIR);
const allFiles = [...componentFiles, ...apiFiles];
let hasErrors = false;
const errors = [];
for (const file of allFiles) {
const undefinedHandlers = validateFile(file);
if (undefinedHandlers.length > 0) {
hasErrors = true;
const relativePath = path.relative(process.cwd(), file);
errors.push({
file: relativePath,
handlers: undefinedHandlers,
});
console.error(`${relativePath}`);
for (const handler of undefinedHandlers) {
console.error(` ⚠️ Handler "${handler}" is referenced but not defined`);
}
console.error('');
}
}
if (hasErrors) {
console.error('\n❌ Validation failed: Found undefined handler functions\n');
console.error('Please ensure all handler functions are defined before use.\n');
process.exit(1);
} else {
console.log('✅ All handler functions are properly defined!\n');
process.exit(0);
}
}
if (require.main === module) {
main();
}
module.exports = { validateFile, extractHandlers, extractFunctions };