ccguard
Version:
Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code
163 lines • 5.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BashCommandParser = void 0;
class BashCommandParser {
/**
* Parse a bash command to identify file operations
*/
parseCommand(input) {
const command = input.command.trim();
const operations = [];
// Handle file deletion commands
if (this.isDeleteCommand(command)) {
const paths = this.extractPathsFromDelete(command);
if (paths.length > 0) {
operations.push({ type: 'delete', paths });
}
}
// Handle file creation commands
if (this.isCreateCommand(command)) {
const paths = this.extractPathsFromCreate(command);
if (paths.length > 0) {
operations.push({ type: 'create', paths });
}
}
// Handle file overwrite commands (echo >, cat >)
if (this.isOverwriteCommand(command)) {
const paths = this.extractPathsFromOverwrite(command);
if (paths.length > 0) {
operations.push({ type: 'overwrite', paths });
}
}
// Handle file append commands (echo >>, cat >>)
if (this.isAppendCommand(command)) {
const paths = this.extractPathsFromAppend(command);
if (paths.length > 0) {
operations.push({ type: 'modify', paths });
}
}
return operations;
}
isDeleteCommand(command) {
// Match rm command with various flags
return /^\s*rm\s+/.test(command);
}
isCreateCommand(command) {
// Match touch command
return /^\s*touch\s+/.test(command);
}
isOverwriteCommand(command) {
// Match commands that overwrite files with single >
// Avoid matching >> (append)
return /(?:echo|cat|printf)\s+.*?>\s*(?!>)/.test(command) ||
/>\s*(?!>)[^\s|;&]+/.test(command);
}
isAppendCommand(command) {
// Match commands that append to files with >>
return />>\s*[^\s|;&]+/.test(command);
}
extractPathsFromDelete(command) {
const paths = [];
// Remove rm command and flags
let args = command.replace(/^\s*rm\s+/, '');
// Remove common flags (-r, -f, -rf, etc.)
args = args.replace(/^(-[rfiv]+\s+)+/, '');
// Extract paths (simplified - doesn't handle all edge cases)
const tokens = this.tokenizeCommand(args);
for (const token of tokens) {
// Skip if it's a flag
if (token.startsWith('-'))
continue;
// Add as path if it doesn't contain special characters
if (!this.isSpecialToken(token)) {
paths.push(token);
}
}
return paths;
}
extractPathsFromCreate(command) {
const paths = [];
// Remove touch command
let args = command.replace(/^\s*touch\s+/, '');
// Extract paths
const tokens = this.tokenizeCommand(args);
for (const token of tokens) {
// Skip if it's a flag
if (token.startsWith('-'))
continue;
// Add as path
if (!this.isSpecialToken(token)) {
paths.push(token);
}
}
return paths;
}
extractPathsFromOverwrite(command) {
const paths = [];
// Match pattern: > filename
const match = command.match(/>\s*([^\s|;&]+)/);
if (match && match[1]) {
paths.push(match[1]);
}
return paths;
}
extractPathsFromAppend(command) {
const paths = [];
// Match pattern: >> filename
const match = command.match(/>>\s*([^\s|;&]+)/);
if (match && match[1]) {
paths.push(match[1]);
}
return paths;
}
/**
* Simple command tokenizer (doesn't handle all shell quoting rules)
*/
tokenizeCommand(command) {
const tokens = [];
let current = '';
let inQuote = false;
let quoteChar = '';
for (let i = 0; i < command.length; i++) {
const char = command[i];
if (!inQuote && (char === '"' || char === "'")) {
inQuote = true;
quoteChar = char;
}
else if (inQuote && char === quoteChar) {
inQuote = false;
if (current) {
tokens.push(current);
current = '';
}
}
else if (!inQuote && /\s/.test(char)) {
if (current) {
tokens.push(current);
current = '';
}
}
else {
current += char;
}
}
if (current) {
tokens.push(current);
}
return tokens;
}
isSpecialToken(token) {
// Check if token contains shell special characters
return /[|;&<>]/.test(token);
}
/**
* Check if a command would affect files
*/
static wouldAffectFiles(input) {
const parser = new BashCommandParser();
const operations = parser.parseCommand(input);
return operations.length > 0;
}
}
exports.BashCommandParser = BashCommandParser;
//# sourceMappingURL=bashCommandParser.js.map