UNPKG

meca-code-quality

Version:
460 lines (459 loc) 18.2 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); // import * as ts from 'typescript'; // import * as stylelint from 'stylelint'; // import { ESLint } from 'eslint'; /** Parse the command line */ const args = process.argv.slice(2); // Validate input if (args.length > 1) { console.log("Warning: Accommodates at most one argument (full path to desired root dir)"); process.exit(); } const src = args[0]; if (!fs.existsSync(src)) { console.log("Error: Source file doesn't exist. Given: ", src); process.exit(); } /** COUNTER OBJECTS **/ const fileCounter = { '.js': 0, '.jsx': 0, '.ts': 0, '.tsx': 0, '.md': 0, '.css': 0, '.scss': 0, '.sass': 0, '.less': 0, '.py': 0, 'other_style_files': 0 }; const filesLinesCounter = { js_lines: 0, ts_lines: 0, css_lines: 0, other_style_lines: 0 }; const filesContentCounter = { '!important': 0, '= useRef': 0, '= React.useRef': 0, 'style={': 0, 'margin:': 0, '.subscribe(': 0, }; const problemIssues = { strict_mode: 0, // styleLint: 0 // TODO: add later }; /** VARIABLES **/ const fileCounterKeys = Object.keys(fileCounter); let ROOT_DIR = '.'; let markdownContent = '# JS / TS Repo Analysis Report\n\n'; const styleRegex = /\.style\.(js|jsx|ts|tsx)$/; /** METHODS **/ /** * Counts the number of StyleLint problems/issues. * TODO: Get this working GENERALLY, or add a flag to enable. * TODO: change this so it runs once for the whole repo. */ // async function checkStyleLint(): Promise<void> { // // at end, make the rest a scoped function? // const strictModeOutput: string = execSync(`cd ${ROOT_DIR} && npx eslint . --config ./.eslintrc --format json`, { encoding: 'utf-8' }); // // const result = await stylelint.lint({ files: ROOT_DIR, formatter: 'string' }); // console.log('checkStyleLint', result); // if (result.errored) { // // problemIssues.styleLint += result.report.split('\n').length - 1; // Subtract 1 for the final newline // } // // // problemIssues.styleLint += result.results.length; // } /** * Counts the number of strict mode problems/issues. * TODO: Get this working GENERALLY, or add a flag to enable. * TODO: change this so it runs once for the whole repo. */ // function checkStrictMode(): void { // // at end, make the rest a scoped function? // const strictModeOutput: string = execSync(`cd ${ROOT_DIR} && npx eslint . --config ./.eslintrc --format json`, { encoding: 'utf-8' }); // // try { // const sourceFile = ts.createSourceFile( // ROOT_DIR, // fs.readFileSync(ROOT_DIR, 'utf8'), // ts.ScriptTarget.Latest, // true // ); // // if (!sourceFile) { // console.error(`Failed to create source file for ${ROOT_DIR}`); // problemIssues.strict_mode += 0; // return; // } // // const program = ts.createProgram([ROOT_DIR], {}); // const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile); // // problemIssues.strict_mode += diagnostics.filter(({ category, code }) => category === ts.DiagnosticCategory.Error && code >= 2300 && code <= 2700).length; // } catch (error) { // console.error(`Error checking Strict Mode for ${ROOT_DIR}: ${error}`); // } // } /** * Counts number of non-empty lines in a file. * @param filePath */ function countNonEmptyLines(filePath) { const fileContent = fs.readFileSync(filePath, 'utf-8'); const lines = fileContent.split('\n'); let lineCount = 0; for (const line of lines) { if (line.trim() !== '') { lineCount++; } } return lineCount; } /** * Counts occurrences of certain code uses that should be minimized * Currently counted: !important, useRef, inline styles, margins, and RxJS subscriptions * @param filePath * @param counter */ function countOccurrencesToMinimize(filePath, counter) { const fileContent = fs.readFileSync(filePath, 'utf-8'); const lines = fileContent.split('\n'); for (const line of lines) { for (const key in counter) { if (line.includes(key)) { counter[key]++; } } } } function traverseDirectory(currentPath) { const fileNames = fs.readdirSync(currentPath); fileNames.forEach((file) => { const fullPath = path.join(currentPath, file); const stats = fs.statSync(fullPath); const extension = path.extname(file); if (fullPath.includes('node_modules') || fullPath.includes('dist') || fullPath.includes('build')) { return; } else if (stats.isDirectory()) { traverseDirectory(fullPath); } else if (stats.isFile() && fileCounterKeys.includes(extension)) { fileCounter[extension]++; // Count lines if (['.js', '.jsx'].includes(extension)) { filesLinesCounter.js_lines += countNonEmptyLines(fullPath); } else if (['.ts', '.tsx'].includes(extension)) { filesLinesCounter.ts_lines += countNonEmptyLines(fullPath); } else if (['.css', '.scss', '.sass', '.less'].includes(extension)) { filesLinesCounter.css_lines += countNonEmptyLines(fullPath); } // count instances of unwanted occurrences countOccurrencesToMinimize(fullPath, filesContentCounter); } if (fullPath.match(styleRegex)) { fileCounter.other_style_files++; filesLinesCounter.other_style_lines += countNonEmptyLines(fullPath); } }); } function traverseFilesAndFoldersForIssues(directoryPath) { console.log('Traverse Files and Folders...'); traverseDirectory(directoryPath); formatFileIssuesToMarkdown({ fileCounter, filesLinesCounter, filesContentCounter, problemIssues }); } /** * Formats the ESLint results, and adds it to the markdown file * @param eslintJson */ // function formatEslintOutputToMarkdown(eslintJson: string): void { // try { // const parsedOutput: Array<IFile> = JSON.parse(eslintJson); // // markdownContent += '## ESLint Summary\n\n'; // const totalFiles = parsedOutput.length; // const filesWithIssues = parsedOutput.filter((file) => file.messages.length > 0).length; // const totalIssues = parsedOutput.reduce((sum, file) => sum + file.messages.length, 0); // // markdownContent += `- **Total Files Analyzed**: ${totalFiles}\n`; // markdownContent += `- **Files with Issues**: ${filesWithIssues}\n`; // markdownContent += `- **Total Issues**: ${totalIssues}\n\n`; // } catch (error) { // console.error('Error formatting ESLint output:', error); // markdownContent += '## ESLint Analysis Report\n\nError parsing ESLint output.\n\n'; // } // } /** * Formats the code quality results, and adds it to the markdown file * @param codeQualityJson */ function formatFileIssuesToMarkdown(codeQualityJson) { try { const { fileCounter, filesLinesCounter, filesContentCounter, problemIssues } = codeQualityJson; markdownContent += '## Code Quality Summary\n\n'; markdownContent += `- **Filename**: ${ROOT_DIR}\n`; markdownContent += '### File Counts\n\n'; // js markdownContent += `- **Total .js & .jsx Files**: ${fileCounter['.jsx'] + fileCounter['.js']}\n`; markdownContent += `- **Total .js & .jsx File Lines**: ${filesLinesCounter.js_lines}\n`; // ts markdownContent += `- **Total .ts & .tsx Files**: ${fileCounter['.tsx'] + fileCounter['.ts']}\n`; markdownContent += `- **Total .ts & .tsx File Lines**: ${filesLinesCounter.ts_lines}\n`; // css, sass, scss, less markdownContent += `- **Total Styling Files**: ${fileCounter['.css'] + fileCounter['.less'] + fileCounter['.sass'] + fileCounter['.scss']}\n`; markdownContent += `- **Total Styling Lines**: ${filesLinesCounter.css_lines}\n`; // other styling markdownContent += `- **Total Other Styling Files**: ${fileCounter.other_style_files}\n`; markdownContent += `- **Total Other Styling Lines**: ${filesLinesCounter.other_style_lines}\n\n`; markdownContent += '### General Issues Count\n\n'; // "!important"s markdownContent += `- **Total !important Counts**: ${filesContentCounter['!important']}\n`; // Ref Counts markdownContent += `- **Total Ref Counts**: ${filesContentCounter['= useRef'] + filesContentCounter['= React.useRef']}\n`; // Margin Counts markdownContent += `- **Total Margin Counts**: ${filesContentCounter['margin:']}\n`; // RxJS Subscriptions markdownContent += `- **Total RxJS Subscriptions**: ${filesContentCounter['.subscribe(']}\n`; // Inline Styles markdownContent += `- **Total Inline Styles**: ${filesContentCounter['style={']}\n`; // Strict Mode Issues // markdownContent += `- **Total Files with Strict Mode Issues**: ${problemIssues.strict_mode}\n\n`; // Style Lint Issues - TODO } catch (error) { console.error('Error formatting Code Quality output:', error); markdownContent += '## Code Quality Analysis Report\n\nError parsing Code Quality output.\n\n'; } } /** * Runs eslint on the repo and adds results to the markdown results report. * TODO: Get this working GENERALLY, or add a flag to enable. */ // function eslintReporter(): void { // console.log('Running ESLint...'); // // const eslintOutput: string = execSync(`cd ${ROOT_DIR} && npx eslint . --format json`, { encoding: 'utf-8' }); // // const eslintOutput: string = execSync('npm run lint', { encoding: 'utf-8' }); // // try { // console.log(execSync(`pwd`, { encoding: 'utf-8' })); // const eslintOutput = execSync('npm run lint', { encoding: 'utf-8' }).toString(); // // ... use eslintOutput // // console.log('ESLint Output', eslintOutput); // formatEslintOutputToMarkdown(eslintOutput); // } catch (error) { // if (error instanceof Error) { // console.error("ESLint execution failed:", error.message); // // Handle the error, e.g., exit the script, log the error to a file, etc. // } else { // console.error("Unexpected error occurred during ESLint execution."); // } // } // // try { // // } catch (error: any) { // // console.log('No ESLint config found.'); // // try { // // console.log('Re-Running ESLint...'); // // const eslintOutput: string = execSync(`cd ${ROOT_DIR} && touch .eslintrc.js && npx eslint . --config ./.eslintrc.js --format json`, { encoding: 'utf-8' }); // // // // console.log('ESLint Output 2', eslintOutput); // // formatEslintOutputToMarkdown(eslintOutput); // // } catch (error: any) { // // console.error('An error occurred:', error.message); // // process.exit(1); // // } // // } // } // function eslintReporter(): void { // console.log('Running ESLint...'); // // const currentDirectory = execSync(`pwd`, { encoding: 'utf-8' }).toString(); // console.log('Current Directory:', currentDirectory); // console.log('ROOT_DIR:', ROOT_DIR); // // try { // const eslintOutput = execFileSync('npm', ['run', 'lint'], { // cwd: currentDirectory, // encoding: 'utf-8' // }).toString(); // // console.log('ESLint Output:\n', eslintOutput); // formatEslintOutputToMarkdown(eslintOutput); // } catch (error) { // if (error instanceof Error) { // console.error("ESLint execution failed in directory:", currentDirectory); // console.error("Error Message:", error.message); // } else { // console.error("Unexpected error occurred during ESLint execution."); // } // } // // // try { // // // const eslintOutput = execSync('npm run lint', { // // const eslintOutput = execSync(`cd ${ROOT_DIR} && npx eslint .`, { // // // const eslintOutput = execSync(`cd ${currentDirectory} && npx eslint .`, { // // // cwd: ROOT_DIR, // // encoding: 'utf-8', // // // shell: 'cmd.exe' // // }).toString(); // // // // // const eslintOutput = execSync('npm run lint', { encoding: 'utf-8', cwd: currentDirectory }).toString(); // // console.log('ESLint Output:\n', eslintOutput); // // formatEslintOutputToMarkdown(eslintOutput); // // } catch (error) { // // if (error instanceof Error) { // // console.error("ESLint execution failed in directory:", currentDirectory); // // console.error("Error Message:", error.message); // // } else { // // console.error("Unexpected error occurred during ESLint execution."); // // } // // } // // // exec('npm run lint', { cwd: currentDirectory, shell: 'cmd.exe' }, (error, stdout, stderr) => { // // if (error) { // // console.error("ESLint execution failed:", error); // // console.error("Stderr:", stderr); // // } else { // // console.log("ESLint Output:\n", stdout); // // formatEslintOutputToMarkdown(stdout); // // } // // }); // } // function eslintReporter(): void { // console.log('Running ESLint...'); // // const currentDirectory = execSync(`pwd`, { encoding: 'utf-8' }).toString(); // console.log('Current Directory:', currentDirectory); // // let filesToLint = []; // const filesPaths = ['./src/**/*.js', './tests/**/*.js', './src/**/*.ts', './tests/**/*.ts', './src/**/*.jsx', './tests/**/*.jsx', './src/**/*.tsx', './tests/**/*.tsx']; // Adjust paths as needed // // const existingFiles = filesPaths.filter((file) => fs.existsSync(file)); // // // const filesToLint = ['./src/**/*.ts', './tests/**/*.ts', './src/**/*.tsx', './tests/**/*.tsx']; // Adjust paths as needed // // const filesToLint = ['./src/**/*.js', './tests/**/*.js', './src/**/*.ts', './tests/**/*.ts']; // Adjust paths as needed // filesToLint = ['./**/*.ts']; // Adjust paths as needed // // getESLintReport(filesToLint) // .then((report) => { // console.log(JSON.stringify(report, null, 2)); // // You can further process the report here, such as: // // - Generating a summary of errors and warnings // // - Writing the report to a file // // - Integrating with a CI/CD pipeline // }); // } // async function getESLintReport(files: string | string[]) { // try { // const eslint = new ESLint({ // fix: false, // Set to true to automatically fix fixable violations // cwd: ROOT_DIR, // }); // // const results = await eslint.lintFiles(files); // // // Format results for better readability // const formattedResults = results.map((result) => ({ // filePath: result.filePath, // errorCount: result.errorCount, // warningCount: result.warningCount, // messages: result.messages.map((message) => ({ // message: message.message, // line: message.line, // column: message.column, // severity: message.severity, // ruleId: message.ruleId, // })), // })); // // return formattedResults; // } catch (error) { // console.error('Error generating ESLint report:', error); // return []; // } // } /** * Traverses all the files and folders recursively to check the current folder, and outputs a report on the code quality */ function generateFullReport() { const reportsDir = path.join(process.cwd(), 'code-quality-reports'); if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir); } const timestamp = new Date().toISOString().replace(/:/g, '-'); const reportFilename = `code-quality-report-${timestamp}.md`; const reportPath = path.join(reportsDir, reportFilename); fs.writeFileSync(reportPath, markdownContent); console.log(`Code quality report generated: ${reportPath}`); } /** * Traverses all the files and folders recursively to check the current folder, and outputs a report on the code quality. * @param fullPathRootDir */ function codeQualityRunner(fullPathRootDir) { try { ROOT_DIR = fullPathRootDir ?? ROOT_DIR; // console.log('Running npm install...'); // execSync(`cd ${ROOT_DIR} && npm install`, { stdio: 'inherit' }); (0, child_process_1.execSync)(`cd ${ROOT_DIR}`, { stdio: 'inherit' }); console.log('ESLint check skipped.'); // eslintReporter(); console.log('StyleLint check skipped.'); // checkStyleLint(); console.log('Strict Mode check skipped.'); // checkStrictMode(); traverseFilesAndFoldersForIssues(ROOT_DIR); generateFullReport(); } catch (error) { console.error('An error occurred:', error.message); process.exit(1); } } codeQualityRunner(src);