unqommented
Version:
A Node.js utility that quickly identifies files with uncommented code in your codebase. Designed for developers who want to efficiently tell LLMs exactly which files need comments added.
113 lines (102 loc) • 5.08 kB
JavaScript
/**
* @file Command-line interface for the unqommented code analysis tool
* @description This script provides a user-friendly CLI for scanning directories to find files
* with uncommented code. It's specifically designed for LLM-assisted workflows where developers
* need to quickly identify which files need commenting before feeding them to AI assistants.
* @rationale The CLI uses yargs for robust argument parsing and structured output formatting
* that makes it easy to integrate into development workflows and automation scripts.
* @architectural_decision We chose yargs over minimist for better help generation and validation,
* and implemented batch output formatting to make results easier to process in chunks.
*
* @example
* // Scan the current directory
* npx unqommented
*
* @example
* // Scan a specific directory
* npx unqommented ./src
*/
const { findUncommentedFiles } = require('../lib/utils.js');
const localVars = require('../config/localVars');
const { qerrors } = require('qerrors'); // For structured error logging
const yargs =require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
/**
* @function main
* @description The main execution block implementing the CLI workflow
* @rationale Uses an Immediately Invoked Function Expression (IIFE) to enable async/await
* at the top level in Node.js versions before 14.8. This pattern ensures compatibility
* while providing clean async error handling. The function orchestrates argument parsing,
* file scanning, and result formatting in a single execution flow.
* @error_handling All errors are caught and logged with structured error information via
* qerrors for better debugging and user experience.
*/
(async () => {
let argv;
try {
// Use yargs to parse command-line arguments. This provides a robust and flexible
// way to define and handle CLI options, including the 'directory' argument.
// The .command() method defines the primary command, and .positional() specifies
// the 'directory' argument, its type, and a default value.
argv = yargs(hideBin(process.argv))
.usage('Usage: $0 [directory] [options]')
.command('$0 [directory]', 'Scan a directory for uncommented files', (yargs) => {
yargs.positional('directory', {
describe: 'The directory to scan for uncommented code',
type: 'string',
default: '.', // Default to the current directory if not specified
});
})
.help() // Automatically generate a help message
.argv;
// Execute the scan and await the results. The findUncommentedFiles function
// returns both the list of uncommented files and any errors that occurred.
const { uncommentedFiles, errors } = await findUncommentedFiles(argv.directory);
// Process and display the results. If no uncommented files are found, a
// success message is shown. Otherwise, the paths of the uncommented files are printed.
if (uncommentedFiles.length === 0) {
console.log(localVars.CLI_MESSAGES.NO_UNCOMMENTED_FILES);
} else {
// Split files into three evenly distributed batches
const totalFiles = uncommentedFiles.length;
const baseSize = Math.floor(totalFiles / 3);
const remainder = totalFiles % 3;
// Calculate batch sizes - distribute remainder evenly among first batches
const batchSizes = [
baseSize + (remainder > 0 ? 1 : 0), // First batch gets extra if remainder > 0
baseSize + (remainder > 1 ? 1 : 0), // Second batch gets extra if remainder > 1
baseSize // Third batch gets base size
];
// Create batches using calculated sizes
let startIndex = 0;
const batches = batchSizes.map(size => {
const batch = uncommentedFiles.slice(startIndex, startIndex + size);
startIndex += size;
return batch;
});
// Print each batch with a separator
batches.forEach((batch, index) => {
if (batch.length > 0) {
console.log(`\n--- Batch ${index + 1} ---`);
batch.forEach(f => console.log(f));
}
});
// Print total files found at the bottom
console.log(`\nTotal files found: ${uncommentedFiles.length}`);
}
// Display any errors that were collected during the scan. This ensures that
// file-specific issues are reported without halting the entire process.
errors.forEach(err => {
const fileInfo = err.file ? `${err.file}: ` : '';
console.error(`${localVars.CLI_MESSAGES.ERROR_PREFIX} ${fileInfo}${err.error}`);
});
} catch (error) {
// The catch block handles critical errors that prevent the CLI from running,
// such as an invalid directory. It logs the error using qerrors for structured
// logging and exits with a non-zero status code to indicate failure.
qerrors(error, 'cli', { directory: argv?.directory || 'unknown' });
console.error(`${localVars.CLI_MESSAGES.ERROR_PREFIX} ${error.message}`);
process.exit(1);
}
})();