UNPKG

arela

Version:

AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.

351 lines 14.7 kB
/** * Standards Module * Defines and checks standards for: * - Security (input validation, auth, crypto) * - UX (accessibility, performance, responsiveness) * - Architecture (modularity, dependencies, coupling) * - Performance (caching, optimization, memory) */ /** * Define all standards */ const SECURITY_STANDARDS = [ { id: 'SEC_001', name: 'Input Validation', description: 'All user inputs must be validated and sanitized', patterns: [ /(?:req\.|params\.|query\.|body\.)/, /innerHTML|dangerouslySetInnerHTML/, ], check: (content) => { const hasValidation = /validate|sanitize|trim|parseInt|Number\(|zod|yup|joi/i.test(content); const hasDangerousPatterns = /innerHTML|dangerouslySetInnerHTML|eval\(|Function\(/i.test(content); return hasValidation && !hasDangerousPatterns; }, }, { id: 'SEC_002', name: 'Authentication Check', description: 'All protected routes must have authentication checks', patterns: [/middleware|auth|JWT|token|session/i], check: (content) => { return /(?:auth|middleware|JWT|token|session|isAuth|requireAuth)/i.test(content); }, }, { id: 'SEC_003', name: 'Error Handling', description: 'Errors must be properly caught and handled without exposing sensitive info', patterns: [/try|catch|error|Error/], check: (content) => { const hasTryCatch = /try\s*{[\s\S]*?}\s*catch/i.test(content); const hasErrorHandling = /\.catch\(|error\s*=>|throw\s+(?:new\s+)?Error/i.test(content); return hasTryCatch || hasErrorHandling; }, }, { id: 'SEC_004', name: 'No Hardcoded Secrets', description: 'API keys, passwords, and secrets must not be hardcoded', patterns: [/password|secret|api_key|apiKey|token/i], check: (content) => { const hasHardcodedSecrets = /(?:password|secret|api_?key|token)\s*[:=]\s*['"`][\w\-]/i.test(content); const usesEnvVars = /process\.env|import\.meta\.env/.test(content); return !hasHardcodedSecrets || usesEnvVars; }, }, { id: 'SEC_005', name: 'SQL Injection Prevention', description: 'Use parameterized queries to prevent SQL injection', patterns: [/query|sql|database/i], check: (content) => { const hasRawSQL = /`SELECT|'SELECT|\`INSERT|'INSERT|query\s*\(\s*['"`]/i.test(content); const usesParameterized = /\?|\$1|\${|parameterized|prepared/i.test(content); return !hasRawSQL || usesParameterized; }, }, ]; const UX_STANDARDS = [ { id: 'UX_001', name: 'Loading States', description: 'All async operations should show loading states', patterns: [/fetch|async|await|promise/i], check: (content) => { const hasAsync = /(?:fetch|axios|async|await)/.test(content); const hasLoadingState = /loading|isLoading|pending|isPending|state\s*=\s*['"`]loading/i.test(content); return !hasAsync || hasLoadingState; }, }, { id: 'UX_002', name: 'Error Messages', description: 'User-friendly error messages should be displayed', patterns: [/error|catch|reject/i], check: (content) => { const hasErrorHandling = /error|catch|\.catch/i.test(content); const hasUserMessage = /message|notification|toast|alert|display.*error/i.test(content); return !hasErrorHandling || hasUserMessage; }, }, { id: 'UX_003', name: 'Accessibility (WCAG)', description: 'Components should follow WCAG accessibility guidelines', patterns: [/button|input|form|link|image/i], check: (content) => { const hasAlt = /alt=|aria-label|role=/.test(content); const hasSemanticHTML = /<(button|input|label|nav|main|article|section)/.test(content); return hasAlt || hasSemanticHTML; }, }, { id: 'UX_004', name: 'Mobile Responsive', description: 'Design should be responsive on mobile devices', patterns: [/style|css|media|viewport/i], check: (content) => { const hasMediaQueries = /@media|mobile|responsive|breakpoint/.test(content); const hasMobileClasses = /sm:|md:|lg:|mobile|tablet|desktop/.test(content); return hasMediaQueries || hasMobileClasses; }, }, { id: 'UX_005', name: 'Keyboard Navigation', description: 'All interactive elements should be keyboard accessible', patterns: [/onClick|button|link|form/i], check: (content) => { const hasKeyboardHandlers = /onKeyDown|onKeyUp|onKeyPress|tabIndex|role=/.test(content); const hasSemanticElements = /<(button|a|input)/.test(content); return hasKeyboardHandlers || hasSemanticElements; }, }, ]; const ARCHITECTURE_STANDARDS = [ { id: 'ARCH_001', name: 'Module Cohesion', description: 'Each module should have a single, well-defined responsibility', patterns: [/export|function|class/], check: (content) => { const exportCount = (content.match(/export\s+(?:default\s+)?(?:function|const|class)/g) || []).length; // Good cohesion if 1-3 exports return exportCount <= 3; }, }, { id: 'ARCH_002', name: 'Dependency Injection', description: 'Dependencies should be injected, not hardcoded', patterns: [/import|require|new\s+/], check: (content) => { const hasConstructor = /constructor\s*\(/.test(content); const hasFunctionParams = /function\s+\w+\s*\([^)]+\)/.test(content); return hasConstructor || hasFunctionParams; }, }, { id: 'ARCH_003', name: 'Circular Dependencies', description: 'Should not have circular dependencies between modules', patterns: [/import|require/], check: (content) => { // This is checked during tracing, not in static content return true; }, }, { id: 'ARCH_004', name: 'Code Reusability', description: 'Duplicate code should be extracted into reusable functions', patterns: [/function|const/], check: (content) => { const lines = content.split('\n'); // If file is > 300 lines, consider breaking it up return lines.length <= 300; }, }, { id: 'ARCH_005', name: 'Type Safety', description: 'Use TypeScript for type safety and better IDE support', patterns: [/function|const|class/], check: (content) => { // Check for TypeScript type annotations const hasTypes = /:\s*(?:string|number|boolean|any|void|{|<|Promise)/.test(content); return hasTypes; }, }, ]; const PERFORMANCE_STANDARDS = [ { id: 'PERF_001', name: 'Memoization', description: 'Expensive computations should be memoized or cached', patterns: [/calculate|compute|filter|map|reduce|sort/i], check: (content) => { const hasExpensiveOp = /(?:filter|map|reduce|sort|findIndex|JSON\.stringify|calculate|compute)/i.test(content); const hasMemoization = /useMemo|useCallback|memo|cache|memoize|memoized/i.test(content); return !hasExpensiveOp || hasMemoization; }, }, { id: 'PERF_002', name: 'Lazy Loading', description: 'Large components/data should be lazy loaded', patterns: [/import|component|React/], check: (content) => { const hasLargeComponents = /const|function/.test(content); const hasLazyLoading = /lazy|dynamic|code.split|import\s*\(/i.test(content); return true; // Hard to check statically }, }, { id: 'PERF_003', name: 'Debouncing/Throttling', description: 'Frequent events should be debounced or throttled', patterns: [/onChange|onScroll|onResize|onMouseMove/], check: (content) => { const hasFrequentEvents = /onChange|onScroll|onResize|onMouseMove/i.test(content); const hasDebounce = /debounce|throttle|useCallback|useMemo/i.test(content); return !hasFrequentEvents || hasDebounce; }, }, { id: 'PERF_004', name: 'Bundle Size', description: 'Keep bundle size minimal - use tree shaking and code splitting', patterns: [/import/], check: (content) => { const importCount = (content.match(/import\s+(?:{[^}]*}|[^;\n]*)\s+from/g) || []).length; // Reasonable import count return importCount <= 10; }, }, { id: 'PERF_005', name: 'Memory Leaks Prevention', description: 'Event listeners and timers should be cleaned up', patterns: [/addEventListener|setTimeout|setInterval|useEffect/], check: (content) => { const hasTimers = /addEventListener|setTimeout|setInterval/.test(content); const hasCleanup = /removeEventListener|clearTimeout|clearInterval|return\s*\(\s*\)\s*=>|cleanup/i.test(content); return !hasTimers || hasCleanup; }, }, ]; /** * Check code against all standards */ export function checkStandards(content, filePath) { const violations = []; const passedChecks = []; const allStandards = [ ...SECURITY_STANDARDS.map(s => ({ ...s, category: 'security' })), ...UX_STANDARDS.map(s => ({ ...s, category: 'ux' })), ...ARCHITECTURE_STANDARDS.map(s => ({ ...s, category: 'architecture' })), ...PERFORMANCE_STANDARDS.map(s => ({ ...s, category: 'performance' })), ]; for (const standard of allStandards) { try { const passed = standard.check(content); if (passed) { passedChecks.push(standard.id); } else { const violation = { id: standard.id, standard: standard.name, category: standard.category, severity: standard.category === 'security' ? 'critical' : 'warning', location: filePath, description: standard.description, refactorProposal: generateRefactorProposal(standard), priority: standard.category === 'security' ? 10 : 5, }; violations.push(violation); } } catch (error) { // Skip on error } } // Calculate scores const totalStandards = allStandards.length; const getScore = (category) => { const categoryStandards = allStandards.filter(s => s.category === category); const passed = categoryStandards.filter(s => passedChecks.includes(s.id)).length; return categoryStandards.length > 0 ? Math.round((passed / categoryStandards.length) * 100) : 100; }; const scores = { security: getScore('security'), ux: getScore('ux'), architecture: getScore('architecture'), performance: getScore('performance'), }; return { violations, passedChecks, score: { ...scores, overall: Math.round((scores.security + scores.ux + scores.architecture + scores.performance) / 4), }, }; } /** * Generate actionable refactor proposal for a standard violation */ function generateRefactorProposal(standard) { const proposals = { SEC_001: 'Add input validation using a library like Zod or Yup. Example: const validated = schema.parse(userInput);', SEC_002: 'Add authentication middleware. Example: app.use(requireAuth); or if (!user) return redirect("/login");', SEC_003: 'Wrap async operations in try/catch. Example: try { await fetch(...) } catch (err) { handleError(err); }', SEC_004: 'Move secrets to environment variables. Use process.env.API_KEY instead of hardcoded values.', SEC_005: 'Use parameterized queries: db.query("SELECT * FROM users WHERE id = ?", [userId]);', UX_001: 'Add loading state tracking: const [loading, setLoading] = useState(false); Set during fetch operations.', UX_002: 'Display user-friendly error: <div>{error && <p>{error.message}</p>}</div>', UX_003: 'Add accessibility attributes: <img alt="description" />, <button aria-label="..." />', UX_004: 'Use responsive design: Add media queries or use Tailwind breakpoints (sm:, md:, lg:)', UX_005: 'Ensure keyboard navigation: Use semantic HTML (<button>, <a>) or add tabIndex and key handlers.', ARCH_001: 'Each file should export 1-2 related functions/classes. Split large modules into separate files.', ARCH_002: 'Pass dependencies as constructor parameters or function arguments instead of importing directly.', ARCH_003: 'Refactor circular dependencies by extracting common logic to a shared module.', ARCH_004: 'Break large files (>300 lines) into smaller, focused modules.', ARCH_005: 'Add TypeScript types: type User = { name: string; id: number }; function getUser(id: number): User', PERF_001: 'Memoize expensive operations: const memoized = useMemo(() => expensiveCalc(), [dependencies]);', PERF_002: 'Use lazy loading: const Component = lazy(() => import("./Heavy.js"));', PERF_003: 'Debounce frequent events: const debouncedSearch = debounce(handleSearch, 300);', PERF_004: 'Use dynamic imports for large dependencies to enable code splitting.', PERF_005: 'Clean up in useEffect return: useEffect(() => { /* setup */ return () => { /* cleanup */ }; }, [])', }; return proposals[standard.id] || 'Review standard and refactor accordingly.'; } /** * Get all standards for a category */ export function getStandardsForCategory(category) { const standards = { security: SECURITY_STANDARDS, ux: UX_STANDARDS, architecture: ARCHITECTURE_STANDARDS, performance: PERFORMANCE_STANDARDS, }; return standards[category]; } /** * Generate severity color for CLI output */ export function getSeverityIcon(severity) { const icons = { critical: '🔴', warning: '🟡', info: '🔵', }; return icons[severity]; } //# sourceMappingURL=standards.js.map