Skip to content

Instantly share code, notes, and snippets.

@amponce
Created April 15, 2025 21:52
Show Gist options
  • Save amponce/30349b2b69433edb25b5fe27d53302b1 to your computer and use it in GitHub Desktop.
Save amponce/30349b2b69433edb25b5fe27d53302b1 to your computer and use it in GitHub Desktop.
Scripts
#!/usr/bin/env node
/**
* Claims Status App Code Analyzer
*
* This script analyzes the claims-status application for:
* - Class-based React components
* - Performance red flags
* - Functions with excessive lines
* - Expensive compute logic
*/
const fs = require('fs');
const path = require('path');
const util = require('util');
const readFile = util.promisify(fs.readFile);
const readDir = util.promisify(fs.readdir);
const stat = util.promisify(fs.stat);
// Configuration
const APP_DIR = path.join(process.cwd(), 'src/applications/claims-status');
const LINE_THRESHOLD = 100; // Functions with more than this many lines are flagged
const MAX_FUNCTION_PARAMS = 4; // Flag functions with more parameters than this
// Patterns to search for
const PATTERNS = {
CLASS_COMPONENT: /class\s+\w+\s+extends\s+(React\.)?Component/g,
INLINE_FUNCTION: /{[\s\n]*\([^)]*\)[\s\n]*=>[\s\n]*{/g,
EXPENSIVE_ARRAY_METHODS: /\.(map|filter|reduce|forEach|some|every|find|findIndex)\(/g,
NESTED_LOOPS: /for\s*\([^{]*\)\s*{[^}]*for\s*\([^{]*\)/g,
EXPENSIVE_STATE_UPDATE: /setState\(\{\s*.*,.*,.*,.*,/g, // Multiple state updates at once
RENDER_METHOD: /render\(\)\s*{/g,
FUNCTION_DECLARATION: /function\s+([A-Za-z0-9_$]+)\s*\(([^)]*)\)/g,
ARROW_FUNCTION: /const\s+([A-Za-z0-9_$]+)\s*=\s*(\([^)]*\)|[A-Za-z0-9_$]+)\s*=>/g,
USE_EFFECT_EMPTY_DEPS: /useEffect\(\(\)\s*=>\s*{[^}]*},\s*\[\]\)/g,
INLINE_OBJECT_CREATION: /<[A-Za-z0-9]+[^>]*\{[\s\n]*\{[^}]*\}[\s\n]*\}/g,
};
// Results storage
const results = {
classComponents: [],
largeFunctions: [],
performanceIssues: [],
expensiveCompute: [],
};
/**
* Checks if a file is a JavaScript or JSX file
*/
const isJsFile = (filePath) => {
return /\.(js|jsx)$/.test(filePath);
};
/**
* Counts parameters in a function signature
*/
const countParameters = (params) => {
if (!params || params.trim() === '') return 0;
return params.split(',').length;
};
/**
* Find all JS/JSX files in a directory and its subdirectories
*/
async function findJsFiles(dir) {
const files = [];
async function traverse(currentDir) {
const entries = await readDir(currentDir);
for (const entry of entries) {
const fullPath = path.join(currentDir, entry);
const stats = await stat(fullPath);
if (stats.isDirectory() && entry !== 'node_modules') {
await traverse(fullPath);
} else if (stats.isFile() && isJsFile(fullPath)) {
files.push(fullPath);
}
}
}
await traverse(dir);
return files;
}
/**
* Analyze a single file for code quality issues
*/
async function analyzeFile(filePath) {
try {
const content = await readFile(filePath, 'utf8');
const lines = content.split('\n');
const relativePath = path.relative(process.cwd(), filePath);
// Check for class components
const classMatches = content.match(PATTERNS.CLASS_COMPONENT);
if (classMatches) {
results.classComponents.push({
file: relativePath,
count: classMatches.length,
});
}
// Check for inline functions in JSX
const inlineFunctionMatches = content.match(PATTERNS.INLINE_FUNCTION);
if (inlineFunctionMatches) {
results.performanceIssues.push({
file: relativePath,
issue: 'Inline function in JSX',
count: inlineFunctionMatches.length,
});
}
// Check for inline object creation in JSX
const inlineObjectMatches = content.match(PATTERNS.INLINE_OBJECT_CREATION);
if (inlineObjectMatches) {
results.performanceIssues.push({
file: relativePath,
issue: 'Inline object creation in JSX',
count: inlineObjectMatches.length,
});
}
// Check for expensive array methods
const arrayMethodMatches = content.match(PATTERNS.EXPENSIVE_ARRAY_METHODS);
if (arrayMethodMatches && arrayMethodMatches.length > 5) {
results.expensiveCompute.push({
file: relativePath,
issue: 'Heavy use of array methods',
count: arrayMethodMatches.length,
});
}
// Check for nested loops
const nestedLoopMatches = content.match(PATTERNS.NESTED_LOOPS);
if (nestedLoopMatches) {
results.expensiveCompute.push({
file: relativePath,
issue: 'Nested loops detected',
count: nestedLoopMatches.length,
});
}
// Check for large state updates
const stateUpdateMatches = content.match(PATTERNS.EXPENSIVE_STATE_UPDATE);
if (stateUpdateMatches) {
results.performanceIssues.push({
file: relativePath,
issue: 'Large state update',
count: stateUpdateMatches.length,
});
}
// Check for useEffect with empty deps array
const emptyDepsMatches = content.match(PATTERNS.USE_EFFECT_EMPTY_DEPS);
if (emptyDepsMatches) {
results.performanceIssues.push({
file: relativePath,
issue: 'useEffect with empty deps array',
count: emptyDepsMatches.length,
});
}
// Find function declarations and check their length and parameter count
let match;
while ((match = PATTERNS.FUNCTION_DECLARATION.exec(content)) !== null) {
const functionName = match[1];
const params = match[2];
const startLine = content.substring(0, match.index).split('\n').length;
// Find the function body and count its lines
const bodyStartIndex = content.indexOf('{', match.index + match[0].length);
if (bodyStartIndex !== -1) {
let openBraces = 1;
let currentIndex = bodyStartIndex + 1;
while (openBraces > 0 && currentIndex < content.length) {
if (content[currentIndex] === '{') openBraces++;
if (content[currentIndex] === '}') openBraces--;
currentIndex++;
}
if (openBraces === 0) {
const functionBody = content.substring(bodyStartIndex, currentIndex);
const lineCount = functionBody.split('\n').length;
if (lineCount > LINE_THRESHOLD) {
results.largeFunctions.push({
file: relativePath,
function: functionName,
lines: lineCount,
startLine,
});
}
if (countParameters(params) > MAX_FUNCTION_PARAMS) {
results.performanceIssues.push({
file: relativePath,
issue: `Function ${functionName} has too many parameters`,
count: countParameters(params),
});
}
}
}
}
// Find arrow functions and check their length
while ((match = PATTERNS.ARROW_FUNCTION.exec(content)) !== null) {
const functionName = match[1];
const params = match[2];
const startLine = content.substring(0, match.index).split('\n').length;
// Find the function body (more complex for arrow functions)
const afterArrow = content.indexOf('=>', match.index);
if (afterArrow !== -1) {
const bodyStartIndex = content.indexOf('{', afterArrow);
if (bodyStartIndex !== -1) {
let openBraces = 1;
let currentIndex = bodyStartIndex + 1;
while (openBraces > 0 && currentIndex < content.length) {
if (content[currentIndex] === '{') openBraces++;
if (content[currentIndex] === '}') openBraces--;
currentIndex++;
}
if (openBraces === 0) {
const functionBody = content.substring(bodyStartIndex, currentIndex);
const lineCount = functionBody.split('\n').length;
if (lineCount > LINE_THRESHOLD) {
results.largeFunctions.push({
file: relativePath,
function: `${functionName} (arrow)`,
lines: lineCount,
startLine,
});
}
if (params && countParameters(params.replace(/[\(\)]/g, '')) > MAX_FUNCTION_PARAMS) {
results.performanceIssues.push({
file: relativePath,
issue: `Arrow function ${functionName} has too many parameters`,
count: countParameters(params.replace(/[\(\)]/g, '')),
});
}
}
}
}
}
// Find render methods in class components and check their length
if (classMatches) {
let renderMatch;
const renderMatches = [...content.matchAll(PATTERNS.RENDER_METHOD)];
for (renderMatch of renderMatches) {
const startLine = content.substring(0, renderMatch.index).split('\n').length;
// Find the render method body
const bodyStartIndex = content.indexOf('{', renderMatch.index);
if (bodyStartIndex !== -1) {
let openBraces = 1;
let currentIndex = bodyStartIndex + 1;
while (openBraces > 0 && currentIndex < content.length) {
if (content[currentIndex] === '{') openBraces++;
if (content[currentIndex] === '}') openBraces--;
currentIndex++;
}
if (openBraces === 0) {
const methodBody = content.substring(bodyStartIndex, currentIndex);
const lineCount = methodBody.split('\n').length;
if (lineCount > LINE_THRESHOLD) {
results.largeFunctions.push({
file: relativePath,
function: 'render method',
lines: lineCount,
startLine,
});
}
}
}
}
}
} catch (error) {
console.error(`Error analyzing ${filePath}:`, error);
}
}
/**
* Format and output results
*/
function outputResults() {
console.log('\n= Claims Status App Code Analysis =\n');
console.log('== Class-based Components ==');
if (results.classComponents.length === 0) {
console.log('No class-based components found.');
} else {
results.classComponents.forEach(item => {
console.log(`${item.file}: ${item.count} class component(s)`);
});
}
console.log('\n== Large Functions (> 100 lines) ==');
if (results.largeFunctions.length === 0) {
console.log('No large functions found.');
} else {
results.largeFunctions
.sort((a, b) => b.lines - a.lines)
.forEach(item => {
console.log(`${item.file}:${item.startLine} - ${item.function} (${item.lines} lines)`);
});
}
console.log('\n== Performance Issues ==');
if (results.performanceIssues.length === 0) {
console.log('No performance issues found.');
} else {
results.performanceIssues.forEach(item => {
console.log(`${item.file}: ${item.issue} (${item.count} occurrence(s))`);
});
}
console.log('\n== Expensive Compute Logic ==');
if (results.expensiveCompute.length === 0) {
console.log('No expensive compute logic found.');
} else {
results.expensiveCompute.forEach(item => {
console.log(`${item.file}: ${item.issue} (${item.count} occurrence(s))`);
});
}
// Summary statistics
const totalClassComponents = results.classComponents.reduce((sum, item) => sum + item.count, 0);
const totalLargeFunctions = results.largeFunctions.length;
const totalPerformanceIssues = results.performanceIssues.reduce((sum, item) => sum + item.count, 0);
const totalExpensiveCompute = results.expensiveCompute.reduce((sum, item) => sum + item.count, 0);
console.log('\n== Summary ==');
console.log(`Total class components: ${totalClassComponents}`);
console.log(`Total large functions: ${totalLargeFunctions}`);
console.log(`Total performance issues: ${totalPerformanceIssues}`);
console.log(`Total expensive compute patterns: ${totalExpensiveCompute}`);
}
/**
* Main function to run the analysis
*/
async function main() {
console.log('Analyzing claims-status application code...');
try {
const jsFiles = await findJsFiles(APP_DIR);
console.log(`Found ${jsFiles.length} JavaScript/JSX files to analyze.`);
for (const file of jsFiles) {
await analyzeFile(file);
}
outputResults();
} catch (error) {
console.error('Error during analysis:', error);
}
}
// Run the script
main();
@amponce
Copy link
Author

amponce commented Apr 16, 2025

Analyzing claims-status application code...
Found 226 JavaScript/JSX files to analyze.

= Claims Status App Code Analysis =

== Class-based Components ==
src/applications/claims-status/components/appeals-v2/Timeline.jsx: 1 class component(s)
src/applications/claims-status/components/claim-files-tab/AddFilesForm.jsx: 1 class component(s)
src/applications/claims-status/components/claim-files-tab/AdditionalEvidencePage.jsx: 1 class component(s)
src/applications/claims-status/containers/AppealInfo.jsx: 1 class component(s)
src/applications/claims-status/containers/AskVAPage.jsx: 1 class component(s)
src/applications/claims-status/containers/ClaimEstimationPage.jsx: 1 class component(s)
src/applications/claims-status/containers/ClaimStatusPage.jsx: 1 class component(s)
src/applications/claims-status/containers/DocumentRequestPage.jsx: 1 class component(s)
src/applications/claims-status/containers/FilesPage.jsx: 1 class component(s)
src/applications/claims-status/containers/OverviewPage.jsx: 1 class component(s)
src/applications/claims-status/containers/StemClaimStatusPage.jsx: 1 class component(s)
src/applications/claims-status/containers/YourClaimsPageV2.jsx: 1 class component(s)

== Large Functions (> 100 lines) ==
src/applications/claims-status/utils/appeals-v2-helpers.jsx:281 - getStatusContents (617 lines)
src/applications/claims-status/utils/appeals-v2-helpers.jsx:1182 - getNextEvents (431 lines)
src/applications/claims-status/utils/appeals-v2-helpers.jsx:1637 - getAlertContent (356 lines)
src/applications/claims-status/components/claim-status-tab/RecentActivity.jsx:17 - RecentActivity (252 lines)
src/applications/claims-status/components/claim-overview-tab/ClaimPhaseStepper.jsx:7 - ClaimPhaseStepper (230 lines)
src/applications/claims-status/components/StemDeniedDetails.jsx:9 - StemDeniedDetails (arrow) (229 lines)
src/applications/claims-status/components/appeals-v2/Docket.jsx:28 - Docket (208 lines)
src/applications/claims-status/actions/index.js:313 - submitFiles (179 lines)
src/applications/claims-status/utils/appeals-v2-helpers.jsx:942 - getEventContent (179 lines)
src/applications/claims-status/tests/e2e/claims-status-helpers.js:74 - initClaimDetailMocks (169 lines)
src/applications/claims-status/components/claim-files-tab/AddFilesForm.jsx:161 - render method (159 lines)
src/applications/claims-status/actions/index.js:494 - submitFilesLighthouse (146 lines)
src/applications/claims-status/containers/YourClaimLetters/index.jsx:24 - YourClaimLetters (arrow) (134 lines)
src/applications/claims-status/components/claim-overview-tab/MobileClaimPhaseDiagram.jsx:4 - MobileClaimPhaseDiagram (133 lines)
src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx:24 - Default5103EvidenceNotice (131 lines)
src/applications/claims-status/components/claim-files-tab/DocumentsFiled.jsx:81 - DocumentsFiled (126 lines)
src/applications/claims-status/components/ClaimDetailLayout.jsx:27 - ClaimDetailLayout (112 lines)
src/applications/claims-status/components/appeals-v2/Issues.jsx:4 - Issues (arrow) (107 lines)
src/applications/claims-status/components/claim-overview-tab/DesktopClaimPhaseDiagram.jsx:4 - DesktopClaimPhaseDiagram (107 lines)
src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx:9 - DefaultPage (101 lines)
src/applications/claims-status/containers/OverviewPage.jsx:27 - getPhaseFromStatus (arrow) (101 lines)
src/applications/claims-status/containers/YourClaimsPageV2.jsx:173 - render method (101 lines)

== Performance Issues ==
src/applications/claims-status/components/ClaimCard/ClaimCardLink.jsx: Function ClaimCardLink has too many parameters (5 occurrence(s))
src/applications/claims-status/components/Notification.jsx: Function Notification has too many parameters (6 occurrence(s))
src/applications/claims-status/components/StemDeniedDetails.jsx: Inline function in JSX (3 occurrence(s))
src/applications/claims-status/components/TabItem.jsx: Function TabItem has too many parameters (5 occurrence(s))
src/applications/claims-status/components/appeals-v2/Docket.jsx: Function Docket has too many parameters (13 occurrence(s))
src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx: useEffect with empty deps array (1 occurrence(s))
src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx: Function Default5103EvidenceNotice has too many parameters (8 occurrence(s))
src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx: Function DefaultPage has too many parameters (13 occurrence(s))
src/applications/claims-status/components/claim-files-tab/AddFilesForm.jsx: Inline function in JSX (3 occurrence(s))
src/applications/claims-status/components/claim-files-tab/AdditionalEvidencePage.jsx: Inline function in JSX (1 occurrence(s))
src/applications/claims-status/components/claim-files-tab/FilesNeeded.jsx: Inline function in JSX (1 occurrence(s))
src/applications/claims-status/components/claim-files-tab/RemoveFileModal.jsx: Inline function in JSX (3 occurrence(s))
src/applications/claims-status/components/claim-files-tab/RemoveFileModal.jsx: Function RemoveFileModal has too many parameters (5 occurrence(s))
src/applications/claims-status/components/claim-files-tab/Standard5103Alert.jsx: Inline function in JSX (1 occurrence(s))
src/applications/claims-status/components/claim-overview-tab/ClaimPhaseStepper.jsx: Function ClaimPhaseStepper has too many parameters (5 occurrence(s))
src/applications/claims-status/components/claim-status-tab/WhatWeAreDoing.jsx: Function WhatWeAreDoing has too many parameters (6 occurrence(s))
src/applications/claims-status/containers/ClaimPage.jsx: useEffect with empty deps array (1 occurrence(s))
src/applications/claims-status/containers/ClaimsStatusApp.jsx: Function ClaimsStatusApp has too many parameters (5 occurrence(s))
src/applications/claims-status/containers/DocumentRequestPage.jsx: Inline function in JSX (1 occurrence(s))
src/applications/claims-status/tests/components/AskVAPage.unit.spec.jsx: Inline object creation in JSX (5 occurrence(s))
src/applications/claims-status/tests/components/ClaimStatusPage.unit.spec.jsx: Inline function in JSX (4 occurrence(s))
src/applications/claims-status/tests/components/ClaimStatusPage.unit.spec.jsx: Inline object creation in JSX (1 occurrence(s))
src/applications/claims-status/tests/components/FilesPage.unit.spec.jsx: Inline function in JSX (5 occurrence(s))
src/applications/claims-status/tests/components/FilesPage.unit.spec.jsx: Inline object creation in JSX (12 occurrence(s))
src/applications/claims-status/tests/components/OverviewPage.unit.spec.jsx: Inline object creation in JSX (1 occurrence(s))
src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx: Inline function in JSX (1 occurrence(s))
src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx: Inline object creation in JSX (3 occurrence(s))
src/applications/claims-status/tests/components/claim-files-tab/AdditionalEvidencePage.unit.spec.jsx: Inline object creation in JSX (1 occurrence(s))
src/applications/claims-status/tests/e2e/claims-status-helpers.js: Function initClaimDetailMocks has too many parameters (7 occurrence(s))
src/applications/claims-status/utils/helpers.js: Function makeAuthRequest has too many parameters (6 occurrence(s))

== Expensive Compute Logic ==
src/applications/claims-status/actions/index.js: Heavy use of array methods (9 occurrence(s))
src/applications/claims-status/components/appeals-v2/Decision.jsx: Heavy use of array methods (6 occurrence(s))
src/applications/claims-status/components/appeals-v2/Issues.jsx: Heavy use of array methods (10 occurrence(s))
src/applications/claims-status/components/claim-files-tab/AddFilesForm.jsx: Heavy use of array methods (6 occurrence(s))
src/applications/claims-status/reducers/serialize.js: Heavy use of array methods (6 occurrence(s))
src/applications/claims-status/tests/components/YourClaimsPageV2.unit.spec.jsx: Heavy use of array methods (18 occurrence(s))
src/applications/claims-status/tests/components/appeals-v2/AppealInfo.unit.spec.jsx: Heavy use of array methods (13 occurrence(s))
src/applications/claims-status/tests/components/appeals-v2/AppealListItem.unit.spec.jsx: Heavy use of array methods (11 occurrence(s))
src/applications/claims-status/tests/components/appeals-v2/Decision.unit.spec.jsx: Heavy use of array methods (12 occurrence(s))
src/applications/claims-status/tests/components/appeals-v2/Issues.unit.spec.jsx: Heavy use of array methods (25 occurrence(s))
src/applications/claims-status/tests/components/appeals-v2/Timeline.unit.spec.jsx: Heavy use of array methods (12 occurrence(s))
src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPage.js: Heavy use of array methods (10 occurrence(s))
src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js: Heavy use of array methods (24 occurrence(s))
src/applications/claims-status/tests/utils/helpers.unit.spec.jsx: Heavy use of array methods (9 occurrence(s))
src/applications/claims-status/utils/helpers.js: Heavy use of array methods (12 occurrence(s))

== Summary ==
Total class components: 12
Total large functions: 22
Total performance issues: 132
Total expensive compute patterns: 183

Report
Component Refactoring Analysis

1. Class-based Components (12 total)

  • Several components currently use older class-based React syntax rather than functional components with hooks.
  • These components represent high-priority targets for modernization.

2. Large Functions (22 total)

  • Multiple functions have excessive length, notably:
    • Largest: 617 lines in appeals-v2-helpers.jsx.
    • Problematic functions: getStatusContents, getNextEvents, and getAlertContent.
  • Numerous render methods surpass 100 lines, indicating overly complex UI logic needing decomposition.

3. Performance Issues (132 total)

  • Frequent occurrence of functions exceeding the recommended 4-parameter limit.
  • Inline functions within JSX leading to unnecessary component re-renders.
  • Misuse of empty dependency arrays in useEffect causing unintended side effects.
  • Inline object creation in JSX triggering new object creation on every render.

4. Expensive Compute Logic (183 total)

  • Extensive use of array methods (map, filter, reduce) across the codebase, notably in test files.
  • Potential for significant performance gains by optimizing these array operations.

Key Recommendations for Refactoring:

  • Convert all class components to functional components leveraging React hooks.
  • Segment large functions into smaller, maintainable pieces for improved readability and debugging.
  • Remove or refactor inline JSX functions using useCallback or external declarations.
  • Employ object parameters or React context to minimize excessive function parameters.
  • Optimize or reduce complex array operations, particularly within critical utility files like appeals-v2-helpers.jsx.

Analysis Script:

  • To conduct this detailed analysis, execute:
node scripts/analyze-claims-status.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment