UNPKG

frontend-standards-checker

Version:

A comprehensive frontend standards validation tool with TypeScript support

188 lines 7.77 kB
import path from 'path'; export const isConfigOrConstantsFile = (filePath) => { return /config|constants/i.test(filePath) && filePath.endsWith('.ts'); }; export function shouldProcessFile(filePath) { const fileName = path.basename(filePath); return fileName === 'index.tsx' && filePath.includes('/components/'); } export function findFunctionMatch(content) { const functionPatterns = [ // Función nombrada: export default function StoriesList() { ... } /export\s+default\s+function\s+(\w+)\s*\(/g, // Exportación directa con const: export const StoriesList = () => { ... } /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]{0,39})\s*=\s*\(([^)]{0,80})\)\s*=>\s*\{/g, // Función anónima asignada: const StoriesList = () => { ... }; export default StoriesList; /const\s+([a-zA-Z_$][a-zA-Z0-9_$]{0,39})\s*=\s*\(([^)]{0,80})\)\s*=>\s*\{/g, // Declaración de función: function StoriesList() { ... }; export default StoriesList; /function\s+(\w+)\s*\(/g, // React.FC con nombre: const StoriesList: React.FC = () => { ... } /const\s+(\w+)\s*:\s*React\.?FC</g, // TypeScript FC: const StoriesList: FC<Props> = () => { ... } /const\s+(\w+)\s*:\s*FC<?/g, ]; for (const pattern of functionPatterns) { const match = pattern.exec(content); if (match?.[1]) { return { functionName: match[1], lineNumber: content.substring(0, match.index).split('\n').length, }; } } return null; } export function validateFunctionName(functionName, dirName, isPascalCase, filePath, lineNumber) { if (!isPascalCase) { if (functionName.toLowerCase() !== dirName.toLowerCase()) { return createNameMismatchError(functionName, dirName, filePath, lineNumber); } } else if (functionName !== dirName) { return createNameMismatchError(functionName, dirName, filePath, lineNumber); } return null; } export function createNameMismatchError(functionName, dirName, filePath, lineNumber) { return { rule: 'Component function name match', message: `The function '${functionName}' (line ${lineNumber}) must have the same name as its containing folder '${dirName}'. ${dirName === dirName.toLowerCase() ? 'The folder must follow PascalCase and the function must have exactly the same name.' : `Found: function='${functionName}', folder='${dirName}'.`}`, filePath: filePath, line: lineNumber, severity: 'error', category: 'naming', }; } export function createNoFunctionError(dirName, filePath) { return { rule: 'Component function name match', message: `No main exported function found in index.tsx. The folder '${dirName}' must contain a function with the same name.`, filePath: filePath, line: 1, severity: 'error', category: 'naming', }; } export function shouldSkipLine(trimmedLine) { return (!trimmedLine || trimmedLine.startsWith('//') || trimmedLine.startsWith('*') || trimmedLine.startsWith('/*')); } export function detectFunctionDeclaration(trimmedLine) { // Split the complex regex into simpler ones const patterns = [ /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*=\s*\(.*\)\s*=>/, /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*=\s*async\s*\(.*\)\s*=>/, /const\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*=\s*async\s*\(.*\)\s*=>/, /export\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*\(/, /const\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*=\s*function\s*\(/, /function\s+([a-zA-Z_$][a-zA-Z0-9_$]{1,39})\s*\(/, ]; for (const pattern of patterns) { const match = pattern.exec(trimmedLine); if (match) { return match; } } return null; } export function getFunctionName(functionMatch) { return functionMatch[1] ?? null; } export function shouldSkipFunction(trimmedLine, _functionName) { if (trimmedLine.includes('interface ') || trimmedLine.includes('type ')) { return true; } return (trimmedLine.includes('=>') && trimmedLine.length < 80 && !trimmedLine.includes('async')); } export function analyzeFunctionComplexity(lines, startIndex, content) { let complexityScore = 0; let braceCount = 0; let inFunction = false; let linesInFunction = 0; const functionStartLine = lines[startIndex] ?? ''; // Fallback to empty string if undefined for (let j = startIndex; j < Math.min(startIndex + 30, lines.length); j++) { const bodyLine = lines[j]; if (!bodyLine) continue; braceCount = updateBraceCount(bodyLine, braceCount); inFunction = inFunction || braceCount > 0; if (inFunction) { linesInFunction++; complexityScore += calculateLineComplexity(bodyLine); } if (inFunction && braceCount === 0) { break; } } const functionContent = content.indexOf(functionStartLine) >= 0 ? content.substring(content.indexOf(functionStartLine)) : ''; const isComplex = determineComplexity(complexityScore, linesInFunction, functionContent); return { complexityScore, linesInFunction, isComplex }; } function updateBraceCount(line, currentCount) { const openBraces = (line.match(/\{/g) || []).length; const closeBraces = (line.match(/\}/g) || []).length; return currentCount + openBraces - closeBraces; } function calculateLineComplexity(line) { let score = 0; if (/\b(if|else if|switch|case)\b/.test(line)) score += 1; if (/\b(for|while|do)\b/.test(line)) score += 2; if (/\b(try|catch|finally)\b/.test(line)) score += 2; if (/\b(async|await|Promise\.all|Promise\.resolve|Promise\.reject|\.then|\.catch)\b/.test(line)) score += 2; if (/\.(map|filter|reduce|forEach|find|some|every)\s*\(/.test(line)) score += 1; if (/\?\s*[a-zA-Z0-9_$,\s=[]{}:.<>]{0,100}\s*:/.test(line)) score += 1; // Ternary operators if (/&&|\|\|/.test(line)) score += 0.5; // Logical operators return score; } function determineComplexity(score, lines, functionContent) { return (score >= 3 || lines > 8 || (score >= 2 && /async|await|Promise/.test(functionContent))); } export function hasProperComments(lines, functionLineIndex, _content) { for (let k = Math.max(0, functionLineIndex - 15); k < functionLineIndex; k++) { const commentLine = lines[k]; if (!commentLine) continue; const trimmedCommentLine = commentLine.trim(); if (isValidComment(trimmedCommentLine)) { return true; } } return false; } function isValidComment(commentLine) { return (commentLine.includes('/**') || commentLine.includes('*/') || (commentLine.startsWith('*') && commentLine.length > 5) || commentLine.includes('/*') || (commentLine.startsWith('//') && commentLine.length > 15 && !/^\s*\/\/\s*(TODO|FIXME|NOTE|HACK)/.test(commentLine))); } export function createCommentError(functionName, analysis, filePath, lineNumber) { return { rule: 'Missing comment in complex function', message: `Complex function '${functionName}' (complexity: ${analysis.complexityScore.toFixed(1)}, lines: ${analysis.linesInFunction}) must have comments explaining its behavior.`, filePath: `${filePath}:${lineNumber + 1}`, line: lineNumber + 1, severity: 'warning', category: 'documentation', }; } //# sourceMappingURL=additionalValidators.helper.js.map