Files
CxIDE/Core/CodeEngine.cpp
T
cx-git-agent c118996746 feat: CxIDE v1 — native macOS SwiftUI IDE with agentic AI assistant
- SwiftUI macOS app with C++17 code analysis engine (ObjC++ bridge)
- Agentic AI loop: LLM plans → tool calls → execution → feedback loop
- 15 agent tools: file ops, terminal, git, xcode build, code intel
- 7 persistent terminal tools with background session management
- Chat sidebar with agent step rendering and auto-apply
- NVIDIA NIM API integration (Llama 3.3 70B default)
- OpenAI tool_calls format with prompt-based fallback
- Code editor with syntax highlighting and multi-tab support
- File tree, console view, terminal view
- Git integration and workspace management
2026-04-21 16:05:52 -05:00

984 lines
39 KiB
C++

#include "CodeEngine.hpp"
#include <sstream>
#include <algorithm>
#include <stack>
#include <cmath>
#include <set>
#include <unordered_map>
#include <cctype>
// ─── Swift keyword set ───────────────────────────────────────────
static const std::set<std::string>& swift_keyword_set() {
static const std::set<std::string> kw = {
"actor", "any", "as", "associatedtype", "async", "await",
"break", "case", "catch", "class", "continue", "convenience",
"default", "defer", "deinit", "do", "dynamic",
"else", "enum", "extension",
"fallthrough", "false", "fileprivate", "final", "for", "func",
"get", "guard",
"if", "import", "in", "indirect", "infix", "init", "inout", "internal", "is",
"lazy", "let",
"mutating",
"nil", "nonisolated",
"open", "operator", "optional", "override",
"postfix", "precedencegroup", "prefix", "private", "protocol", "public",
"repeat", "required", "rethrows", "return",
"self", "Self", "Sendable", "set", "some", "static", "struct", "subscript", "super", "switch",
"throw", "throws", "true", "try", "typealias",
"unowned",
"var",
"weak", "where", "while",
"#available", "#colorLiteral", "#column", "#dsohandle", "#else", "#elseif",
"#endif", "#error", "#file", "#fileLiteral", "#function", "#if",
"#imageLiteral", "#line", "#selector", "#sourceLocation", "#warning",
"@available", "@discardableResult", "@dynamicCallable", "@dynamicMemberLookup",
"@escaping", "@frozen", "@inlinable", "@main", "@objc", "@objcMembers",
"@propertyWrapper", "@resultBuilder", "@Sendable", "@testable", "@unchecked", "@unknown"
};
return kw;
}
// ─── Construction ────────────────────────────────────────────────
CodeEngine::CodeEngine() : m_config() {}
CodeEngine::CodeEngine(const EngineConfig& config) : m_config(config) {}
CodeEngine::~CodeEngine() {}
void CodeEngine::set_config(const EngineConfig& config) {
m_config = config;
}
EngineConfig CodeEngine::get_config() const {
return m_config;
}
// ─── Helpers ─────────────────────────────────────────────────────
std::string CodeEngine::trim(const std::string& s) const {
size_t start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
size_t end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
bool CodeEngine::is_swift_keyword(const std::string& word) const {
return swift_keyword_set().count(word) > 0;
}
std::string CodeEngine::detect_access_level(const std::string& line) const {
std::string trimmed = trim(line);
if (trimmed.find("public ") == 0 || trimmed.find("open ") == 0) return "public";
if (trimmed.find("private ") == 0) return "private";
if (trimmed.find("fileprivate ") == 0) return "fileprivate";
if (trimmed.find("internal ") == 0) return "internal";
return "internal"; // Swift default
}
// ─── Tokenizer ───────────────────────────────────────────────────
std::vector<Token> CodeEngine::tokenize(const std::string& code) {
std::vector<Token> tokens;
int line = 1, col = 0;
size_t i = 0;
while (i < code.size()) {
char c = code[i];
// Newline
if (c == '\n') {
tokens.push_back({TokenType::Newline, "\n", line, col, 1});
line++;
col = 0;
i++;
continue;
}
// Whitespace
if (c == ' ' || c == '\t' || c == '\r') {
size_t start = i;
while (i < code.size() && (code[i] == ' ' || code[i] == '\t' || code[i] == '\r')) i++;
tokens.push_back({TokenType::Whitespace, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Line comment
if (c == '/' && i + 1 < code.size() && code[i + 1] == '/') {
size_t start = i;
while (i < code.size() && code[i] != '\n') i++;
tokens.push_back({TokenType::Comment, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Block comment
if (c == '/' && i + 1 < code.size() && code[i + 1] == '*') {
size_t start = i;
i += 2;
int depth = 1; // Support nested block comments (Swift allows them)
while (i < code.size() && depth > 0) {
if (code[i] == '/' && i + 1 < code.size() && code[i + 1] == '*') { depth++; i += 2; continue; }
if (code[i] == '*' && i + 1 < code.size() && code[i + 1] == '/') { depth--; i += 2; continue; }
if (code[i] == '\n') { line++; col = 0; }
i++;
}
tokens.push_back({TokenType::Comment, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
continue;
}
// String literal (handles multi-line """ and interpolation depth)
if (c == '"') {
size_t start = i;
bool multiline = (i + 2 < code.size() && code[i + 1] == '"' && code[i + 2] == '"');
if (multiline) {
i += 3;
while (i + 2 < code.size()) {
if (code[i] == '"' && code[i + 1] == '"' && code[i + 2] == '"') { i += 3; break; }
if (code[i] == '\n') { line++; col = 0; }
i++;
}
} else {
i++; // skip opening quote
while (i < code.size() && code[i] != '"' && code[i] != '\n') {
if (code[i] == '\\') i++; // skip escaped char
i++;
}
if (i < code.size() && code[i] == '"') i++; // skip closing quote
}
tokens.push_back({TokenType::StringLiteral, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Number literal
if (std::isdigit(c) || (c == '.' && i + 1 < code.size() && std::isdigit(code[i + 1]))) {
size_t start = i;
bool hasDecimal = false;
if (c == '0' && i + 1 < code.size() && (code[i + 1] == 'x' || code[i + 1] == 'b' || code[i + 1] == 'o')) {
i += 2; // hex/binary/octal prefix
while (i < code.size() && (std::isxdigit(code[i]) || code[i] == '_')) i++;
} else {
while (i < code.size() && (std::isdigit(code[i]) || code[i] == '_' || code[i] == '.')) {
if (code[i] == '.') {
if (hasDecimal) break;
hasDecimal = true;
}
i++;
}
// Scientific notation
if (i < code.size() && (code[i] == 'e' || code[i] == 'E')) {
i++;
if (i < code.size() && (code[i] == '+' || code[i] == '-')) i++;
while (i < code.size() && std::isdigit(code[i])) i++;
}
}
tokens.push_back({TokenType::NumberLiteral, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Identifier or keyword
if (std::isalpha(c) || c == '_' || c == '@' || c == '#') {
size_t start = i;
i++;
while (i < code.size() && (std::isalnum(code[i]) || code[i] == '_')) i++;
std::string word = code.substr(start, i - start);
TokenType type = is_swift_keyword(word) ? TokenType::Keyword : TokenType::Identifier;
tokens.push_back({type, word, line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Operators and punctuation
const std::string operators = "+-*/%=<>!&|^~?";
const std::string punctuation = "{}()[],:;.@#";
if (punctuation.find(c) != std::string::npos) {
tokens.push_back({TokenType::Punctuation, std::string(1, c), line, col, 1});
col++;
i++;
continue;
}
if (operators.find(c) != std::string::npos) {
// Consume multi-char operators
size_t start = i;
i++;
while (i < code.size() && operators.find(code[i]) != std::string::npos) i++;
tokens.push_back({TokenType::Operator, code.substr(start, i - start), line, col, static_cast<int>(i - start)});
col += static_cast<int>(i - start);
continue;
}
// Unknown
tokens.push_back({TokenType::Unknown, std::string(1, c), line, col, 1});
col++;
i++;
}
return tokens;
}
// ─── Symbol Extraction ───────────────────────────────────────────
std::vector<SymbolInfo> CodeEngine::extract_symbols(const std::string& code) {
std::vector<SymbolInfo> symbols;
std::istringstream stream(code);
std::string line;
int lineNum = 0;
while (std::getline(stream, line)) {
lineNum++;
std::string trimmed = trim(line);
if (trimmed.empty() || trimmed[0] == '/' || trimmed[0] == '*') continue;
// Strip access modifiers for parsing
std::string access = detect_access_level(trimmed);
std::string body = trimmed;
for (const auto& prefix : {"public ", "open ", "private ", "fileprivate ", "internal ", "static ", "final ", "override "}) {
auto pos = body.find(prefix);
if (pos == 0) body = body.substr(std::string(prefix).size());
}
auto extract_name = [&](const std::string& keyword) -> std::string {
auto pos = body.find(keyword);
if (pos != 0) return "";
std::string rest = body.substr(keyword.size());
// Trim leading whitespace
size_t nameStart = rest.find_first_not_of(" \t");
if (nameStart == std::string::npos) return "";
size_t nameEnd = rest.find_first_of(" \t({:<", nameStart);
if (nameEnd == std::string::npos) nameEnd = rest.size();
return rest.substr(nameStart, nameEnd - nameStart);
};
if (body.find("func ") == 0) {
std::string name = extract_name("func ");
if (!name.empty())
symbols.push_back({SymbolKind::Function, name, lineNum, access, trimmed});
} else if (body.find("class ") == 0) {
// Skip "class func" and "class var"
std::string after = body.substr(6);
std::string afterTrim = trim(after);
if (afterTrim.find("func") != 0 && afterTrim.find("var") != 0 && afterTrim.find("let") != 0) {
std::string name = extract_name("class ");
if (!name.empty())
symbols.push_back({SymbolKind::Class, name, lineNum, access, trimmed});
}
} else if (body.find("struct ") == 0) {
std::string name = extract_name("struct ");
if (!name.empty())
symbols.push_back({SymbolKind::Struct, name, lineNum, access, trimmed});
} else if (body.find("enum ") == 0) {
std::string name = extract_name("enum ");
if (!name.empty())
symbols.push_back({SymbolKind::Enum, name, lineNum, access, trimmed});
} else if (body.find("protocol ") == 0) {
std::string name = extract_name("protocol ");
if (!name.empty())
symbols.push_back({SymbolKind::Protocol, name, lineNum, access, trimmed});
} else if (body.find("typealias ") == 0) {
std::string name = extract_name("typealias ");
if (!name.empty())
symbols.push_back({SymbolKind::TypeAlias, name, lineNum, access, trimmed});
} else if (body.find("extension ") == 0) {
std::string name = extract_name("extension ");
if (!name.empty())
symbols.push_back({SymbolKind::Extension, name, lineNum, access, trimmed});
} else if (body.find("var ") == 0) {
std::string name = extract_name("var ");
if (!name.empty())
symbols.push_back({SymbolKind::Variable, name, lineNum, access, trimmed});
} else if (body.find("let ") == 0) {
std::string name = extract_name("let ");
if (!name.empty())
symbols.push_back({SymbolKind::Constant, name, lineNum, access, trimmed});
}
}
return symbols;
}
// ─── Import Tracking ─────────────────────────────────────────────
std::vector<std::string> CodeEngine::extract_imports(const std::string& code) {
std::vector<std::string> imports;
std::istringstream stream(code);
std::string line;
while (std::getline(stream, line)) {
std::string trimmed = trim(line);
if (trimmed.find("import ") == 0) {
std::string module = trim(trimmed.substr(7));
// Handle "@testable import X" or "import class X.Y"
if (module.find("class ") == 0 || module.find("struct ") == 0 ||
module.find("enum ") == 0 || module.find("func ") == 0 ||
module.find("var ") == 0 || module.find("let ") == 0 ||
module.find("protocol ") == 0 || module.find("typealias ") == 0) {
// Selective import: skip the keyword
auto spacePos = module.find(' ');
if (spacePos != std::string::npos) module = trim(module.substr(spacePos + 1));
}
if (!module.empty()) imports.push_back(module);
} else if (trimmed.find("@testable import ") == 0) {
std::string module = trim(trimmed.substr(17));
if (!module.empty()) imports.push_back("@testable " + module);
}
}
return imports;
}
// ─── Duplicate Line Detection ────────────────────────────────────
std::vector<std::pair<int, int>> CodeEngine::detect_duplicate_lines(const std::string& code) {
std::vector<std::pair<int, int>> dupes;
std::istringstream stream(code);
std::string line;
std::unordered_map<std::string, int> seen; // content -> first line number
int lineNum = 0;
while (std::getline(stream, line)) {
lineNum++;
std::string trimmed = trim(line);
// Skip blank, short, trivial lines
if (trimmed.size() < 10 || trimmed == "{" || trimmed == "}" ||
trimmed == "}" || trimmed == "return" || trimmed[0] == '/' ||
trimmed == "break" || trimmed == "default:" || trimmed == "else {") {
continue;
}
auto it = seen.find(trimmed);
if (it != seen.end()) {
dupes.push_back({it->second, lineNum});
} else {
seen[trimmed] = lineNum;
}
}
return dupes;
}
// ─── TODO/FIXME Extraction ───────────────────────────────────────
std::vector<std::pair<int, std::string>> CodeEngine::extract_todo_comments(const std::string& code) {
std::vector<std::pair<int, std::string>> todos;
std::istringstream stream(code);
std::string line;
int lineNum = 0;
while (std::getline(stream, line)) {
lineNum++;
// Look for TODO:, FIXME:, HACK:, XXX:, MARK: in comments
auto commentPos = line.find("//");
if (commentPos == std::string::npos) continue;
std::string comment = line.substr(commentPos + 2);
std::string upper;
upper.reserve(comment.size());
for (char ch : comment) upper += static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
for (const auto& tag : {"TODO", "FIXME", "HACK", "XXX", "WARNING"}) {
auto pos = upper.find(tag);
if (pos != std::string::npos) {
todos.push_back({lineNum, trim(comment)});
break;
}
}
}
return todos;
}
// ─── Indentation ─────────────────────────────────────────────────
int CodeEngine::suggest_indentation(const std::string& codeBefore, int tabWidth) {
// Count net brace depth from the code preceding the cursor
int depth = 0;
bool inString = false;
bool inLineComment = false;
bool inBlockComment = false;
for (size_t i = 0; i < codeBefore.size(); ++i) {
char c = codeBefore[i];
if (inBlockComment) {
if (c == '*' && i + 1 < codeBefore.size() && codeBefore[i + 1] == '/') { inBlockComment = false; ++i; }
continue;
}
if (inLineComment) {
if (c == '\n') inLineComment = false;
continue;
}
if (c == '/' && i + 1 < codeBefore.size()) {
if (codeBefore[i + 1] == '/') { inLineComment = true; continue; }
if (codeBefore[i + 1] == '*') { inBlockComment = true; ++i; continue; }
}
if (c == '"' && (i == 0 || codeBefore[i - 1] != '\\')) {
inString = !inString;
continue;
}
if (inString) continue;
if (c == '{') depth++;
else if (c == '}') depth = std::max(0, depth - 1);
}
return depth * tabWidth;
}
// ─── Cyclomatic Complexity ───────────────────────────────────────
int CodeEngine::cyclomatic_complexity(const std::string& code) {
// M = E - N + 2P simplified to: 1 + decision points
int cc = 1;
const std::vector<std::string> decisionPatterns = {
"if ", "else if ", "guard ", "for ", "while ", "repeat ",
"case ", "catch ", "where ", "&&", "||", "??"
};
for (const auto& pat : decisionPatterns) {
std::string::size_type pos = 0;
while ((pos = code.find(pat, pos)) != std::string::npos) {
cc++;
pos += pat.size();
}
}
return cc;
}
// ─── Maintainability Index ───────────────────────────────────────
double CodeEngine::maintainability_index(const std::string& code) {
// Simplified Microsoft-style MI formula:
// MI = max(0, (171 - 5.2*ln(HV) - 0.23*CC - 16.2*ln(LOC)) * 100/171)
// Using character count as Halstead Volume proxy
int loc = count_lines(code);
int cc = cyclomatic_complexity(code);
int volume = static_cast<int>(code.size());
if (loc == 0 || volume == 0) return 100.0;
double lnVol = std::log(static_cast<double>(volume));
double lnLoc = std::log(static_cast<double>(loc));
double mi = 171.0 - 5.2 * lnVol - 0.23 * cc - 16.2 * lnLoc;
mi = mi * 100.0 / 171.0;
return std::max(0.0, std::min(100.0, mi));
}
// ─── Full Analysis ───────────────────────────────────────────────
AnalysisReport CodeEngine::full_analysis(const std::string& code) {
AnalysisReport report;
report.lineCount = count_lines(code);
report.charCount = static_cast<int>(code.size());
report.blankLineCount = count_blank_lines(code);
report.commentLineCount = 0; // computed via comment_ratio
report.functionCount = count_functions(code);
report.classCount = count_classes(code);
report.structCount = count_structs(code);
report.enumCount = count_enums(code);
report.protocolCount = count_protocols(code);
report.complexity = estimate_complexity(code);
report.cyclomaticComplexity = cyclomatic_complexity(code);
report.maintainabilityIndex = maintainability_index(code);
report.bracesBalanced = check_balanced_braces(code);
report.keywords = extract_keywords(code);
report.issues = find_issues(code);
report.commentRatio = comment_ratio(code);
report.codeLineCount = report.lineCount - report.blankLineCount;
report.commentLineCount = static_cast<int>(report.commentRatio * report.lineCount);
report.symbols = extract_symbols(code);
report.imports = extract_imports(code);
report.duplicateLinePairs = detect_duplicate_lines(code);
report.todoComments = extract_todo_comments(code);
// Build keyword frequency map
const std::vector<std::string> freqKeywords = {
"import", "class", "struct", "enum", "protocol", "func",
"var", "let", "if", "else", "for", "while", "switch",
"return", "guard", "async", "await", "throws", "try",
"catch", "defer", "extension", "private", "public", "static",
"override", "final", "weak", "lazy", "mutating"
};
for (const auto& kw : freqKeywords) {
int count = count_pattern(code, kw + " ");
if (count > 0) {
report.keywordFrequency[kw] = count;
}
}
return report;
}
std::string CodeEngine::format_report(const AnalysisReport& report) {
std::ostringstream out;
out << "═══════════════════════════════════════════\n";
out << " CxIDE Analysis Report \n";
out << "═══════════════════════════════════════════\n\n";
// ── Metrics
out << "📊 Metrics\n";
out << " Total Lines: " << report.lineCount << "\n";
out << " Code Lines: " << report.codeLineCount << "\n";
out << " Blank Lines: " << report.blankLineCount << "\n";
out << " Comment Lines: " << report.commentLineCount << "\n";
out << " Characters: " << report.charCount << "\n";
out << " Comment Ratio: " << static_cast<int>(report.commentRatio * 100) << "%\n\n";
// ── Declarations
out << "📦 Declarations\n";
out << " Functions: " << report.functionCount << "\n";
out << " Classes: " << report.classCount << "\n";
out << " Structs: " << report.structCount << "\n";
out << " Enums: " << report.enumCount << "\n";
out << " Protocols: " << report.protocolCount << "\n\n";
// ── Complexity
out << "🧮 Complexity\n";
out << " Estimate: " << report.complexity << "\n";
out << " Cyclomatic: " << report.cyclomaticComplexity << "\n";
out << " Maintainability: " << static_cast<int>(report.maintainabilityIndex) << "/100";
if (report.maintainabilityIndex >= 80) out << " (Excellent)";
else if (report.maintainabilityIndex >= 60) out << " (Good)";
else if (report.maintainabilityIndex >= 40) out << " (Moderate)";
else out << " (Needs Improvement)";
out << "\n";
out << " Braces: " << (report.bracesBalanced ? "✓ Balanced" : "⚠ Unbalanced") << "\n\n";
// ── Imports
if (!report.imports.empty()) {
out << "📥 Imports (" << report.imports.size() << ")\n";
for (const auto& imp : report.imports) {
out << "" << imp << "\n";
}
out << "\n";
}
// ── Symbols
if (!report.symbols.empty()) {
out << "🔤 Symbols (" << report.symbols.size() << ")\n";
for (const auto& sym : report.symbols) {
const char* kindLabel = "";
switch (sym.kind) {
case SymbolKind::Function: kindLabel = "func"; break;
case SymbolKind::Class: kindLabel = "class"; break;
case SymbolKind::Struct: kindLabel = "struct"; break;
case SymbolKind::Enum: kindLabel = "enum"; break;
case SymbolKind::Protocol: kindLabel = "protocol"; break;
case SymbolKind::Variable: kindLabel = "var"; break;
case SymbolKind::Constant: kindLabel = "let"; break;
case SymbolKind::TypeAlias: kindLabel = "typealias"; break;
case SymbolKind::Extension: kindLabel = "extension"; break;
}
out << " L" << sym.line << " [" << kindLabel << "] "
<< sym.accessLevel << " " << sym.name << "\n";
}
out << "\n";
}
// ── Keywords
if (!report.keywords.empty()) {
out << "🔑 Keywords Found (" << report.keywords.size() << ")\n ";
for (size_t i = 0; i < report.keywords.size(); ++i) {
if (i > 0) out << ", ";
out << report.keywords[i];
}
out << "\n\n";
}
// ── Keyword Frequency
if (!report.keywordFrequency.empty()) {
out << "📈 Keyword Frequency\n";
for (const auto& pair : report.keywordFrequency) {
out << " " << pair.first << ": " << pair.second << "\n";
}
out << "\n";
}
// ── TODO/FIXME
if (!report.todoComments.empty()) {
out << "📝 TODO/FIXME (" << report.todoComments.size() << ")\n";
for (const auto& todo : report.todoComments) {
out << " L" << todo.first << ": " << todo.second << "\n";
}
out << "\n";
}
// ── Duplicates
if (!report.duplicateLinePairs.empty()) {
out << "🔁 Duplicate Lines (" << report.duplicateLinePairs.size() << " pairs)\n";
for (const auto& pair : report.duplicateLinePairs) {
out << " L" << pair.first << " ↔ L" << pair.second << "\n";
}
out << "\n";
}
// ── Issues
if (!report.issues.empty()) {
out << "⚠ Issues (" << report.issues.size() << ")\n";
for (const auto& issue : report.issues) {
out << "" << issue << "\n";
}
} else {
out << "✓ No issues detected.\n";
}
out << "\n═══════════════════════════════════════════\n";
return out.str();
}
std::string CodeEngine::analyze_syntax(const std::string& code) {
if (code.empty()) return "Empty file — nothing to analyze.";
auto report = full_analysis(code);
return format_report(report);
}
// ─── Checksum ────────────────────────────────────────────────────
int CodeEngine::calculate_checksum(const std::string& code) {
unsigned long hash = 5381;
for (char c : code) {
hash = ((hash << 5) + hash) + static_cast<unsigned char>(c);
}
return static_cast<int>(hash & 0x7FFFFFFF);
}
// ─── Counting ────────────────────────────────────────────────────
int CodeEngine::count_lines(const std::string& code) {
if (code.empty()) return 0;
return static_cast<int>(std::count(code.begin(), code.end(), '\n')) + 1;
}
int CodeEngine::count_blank_lines(const std::string& code) {
int count = 0;
std::istringstream stream(code);
std::string line;
while (std::getline(stream, line)) {
if (trim(line).empty()) count++;
}
return count;
}
int CodeEngine::count_code_lines(const std::string& code) {
return count_lines(code) - count_blank_lines(code);
}
int CodeEngine::count_functions(const std::string& code) {
return count_pattern(code, "func ");
}
int CodeEngine::count_classes(const std::string& code) {
// Avoid counting "class func" or "class var"
int total = count_pattern(code, "class ");
total -= count_pattern(code, "class func ");
total -= count_pattern(code, "class var ");
total -= count_pattern(code, "class let ");
return std::max(0, total);
}
int CodeEngine::count_structs(const std::string& code) {
return count_pattern(code, "struct ");
}
int CodeEngine::count_enums(const std::string& code) {
return count_pattern(code, "enum ");
}
int CodeEngine::count_protocols(const std::string& code) {
return count_pattern(code, "protocol ");
}
int CodeEngine::count_pattern(const std::string& code, const std::string& pattern) {
int count = 0;
std::string::size_type pos = 0;
while ((pos = code.find(pattern, pos)) != std::string::npos) {
count++;
pos += pattern.size();
}
return count;
}
// ─── Complexity ──────────────────────────────────────────────────
std::string CodeEngine::estimate_complexity(const std::string& code) {
int score = 0;
const std::vector<std::string> patterns = {
"if ", "else ", "for ", "while ", "switch ", "guard ", "catch ",
"case ", "where ", "repeat "
};
for (const auto& pat : patterns) {
std::string::size_type pos = 0;
while ((pos = code.find(pat, pos)) != std::string::npos) {
score++;
pos += pat.size();
}
}
// Nesting depth adds complexity
int maxDepth = 0, depth = 0;
for (char c : code) {
if (c == '{') { depth++; maxDepth = std::max(maxDepth, depth); }
if (c == '}') { depth = std::max(0, depth - 1); }
}
score += maxDepth * 2;
// Function count adds complexity
score += count_functions(code);
if (score <= 4) return "Low (" + std::to_string(score) + ")";
if (score <= 10) return "Moderate (" + std::to_string(score) + ")";
if (score <= 20) return "High (" + std::to_string(score) + ")";
return "Very High (" + std::to_string(score) + ")";
}
// ─── Brace Balancing ─────────────────────────────────────────────
bool CodeEngine::check_balanced_braces(const std::string& code) {
std::stack<char> stk;
bool inString = false;
bool inLineComment = false;
bool inBlockComment = false;
for (size_t i = 0; i < code.size(); ++i) {
char c = code[i];
if (inBlockComment) {
if (c == '*' && i + 1 < code.size() && code[i + 1] == '/') {
inBlockComment = false;
++i;
}
continue;
}
if (inLineComment) {
if (c == '\n') inLineComment = false;
continue;
}
if (c == '/' && i + 1 < code.size()) {
if (code[i + 1] == '/') { inLineComment = true; continue; }
if (code[i + 1] == '*') { inBlockComment = true; ++i; continue; }
}
if (c == '"' && (i == 0 || code[i - 1] != '\\')) {
inString = !inString;
continue;
}
if (inString) continue;
if (c == '{' || c == '(' || c == '[') {
stk.push(c);
} else if (c == '}' || c == ')' || c == ']') {
if (stk.empty()) return false;
char open = stk.top();
stk.pop();
if ((c == '}' && open != '{') ||
(c == ')' && open != '(') ||
(c == ']' && open != '[')) return false;
}
}
return stk.empty();
}
// ─── Comment Ratio ───────────────────────────────────────────────
double CodeEngine::comment_ratio(const std::string& code) {
if (code.empty()) return 0.0;
int commentLines = 0;
int totalLines = 0;
bool inBlockComment = false;
std::istringstream stream(code);
std::string line;
while (std::getline(stream, line)) {
totalLines++;
std::string trimmed = trim(line);
if (trimmed.empty()) continue;
if (inBlockComment) {
commentLines++;
if (trimmed.find("*/") != std::string::npos) {
inBlockComment = false;
}
continue;
}
if (trimmed.substr(0, 2) == "//") {
commentLines++;
}
if (trimmed.substr(0, 2) == "/*") {
commentLines++;
if (trimmed.find("*/") == std::string::npos) {
inBlockComment = true;
}
}
}
return totalLines > 0 ? static_cast<double>(commentLines) / totalLines : 0.0;
}
// ─── Keyword Extraction ─────────────────────────────────────────
std::vector<std::string> CodeEngine::extract_keywords(const std::string& code) {
const std::vector<std::string> swiftKeywords = {
"import", "class", "struct", "enum", "protocol", "func",
"var", "let", "if", "else", "for", "while", "switch",
"return", "guard", "async", "await", "throws", "try",
"catch", "defer", "extension", "private", "public", "static",
"override", "final", "weak", "lazy", "mutating", "typealias",
"init", "deinit", "subscript", "associatedtype", "where",
"inout", "some", "any", "actor", "nonisolated", "Sendable"
};
std::vector<std::string> found;
for (const auto& kw : swiftKeywords) {
std::string searchSpace = kw + " ";
std::string searchNewline = kw + "\n";
std::string searchParen = kw + "(";
std::string searchBrace = kw + "{";
std::string searchColon = kw + ":";
if (code.find(searchSpace) != std::string::npos ||
code.find(searchNewline) != std::string::npos ||
code.find(searchParen) != std::string::npos ||
code.find(searchBrace) != std::string::npos ||
code.find(searchColon) != std::string::npos) {
found.push_back(kw);
}
}
return found;
}
// ─── Issue Detection ─────────────────────────────────────────────
std::vector<std::string> CodeEngine::find_issues(const std::string& code) {
std::vector<std::string> issues;
// Force unwraps
if (m_config.detectForceUnwraps) {
int forceUnwraps = 0;
for (size_t i = 1; i < code.size(); ++i) {
if (code[i] == '!' && code[i - 1] != '=' && code[i - 1] != '<' &&
code[i - 1] != '>' && code[i - 1] != ' ' && code[i - 1] != '"') {
if (i + 1 >= code.size() || code[i + 1] != '=') {
forceUnwraps++;
}
}
}
if (forceUnwraps > 0) {
issues.push_back("Found " + std::to_string(forceUnwraps) +
" potential force unwrap(s). Consider guard/if-let.");
}
}
if (m_config.detectForceTry && code.find("try!") != std::string::npos) {
issues.push_back("Usage of try! detected. Consider try/catch for safety.");
}
if (m_config.detectForceCast && code.find("as!") != std::string::npos) {
issues.push_back("Force cast (as!) detected. Consider using as? instead.");
}
// Print statement count
int prints = count_pattern(code, "print(");
if (prints > m_config.maxPrintStatements) {
issues.push_back("Heavy use of print() (" + std::to_string(prints) +
" calls). Consider a logging framework.");
}
// Long lines
if (m_config.detectLongLines) {
int longLines = 0;
std::istringstream stream(code);
std::string line;
while (std::getline(stream, line)) {
if (static_cast<int>(line.size()) > m_config.maxLineLength) longLines++;
}
if (longLines > 0) {
issues.push_back(std::to_string(longLines) + " line(s) exceed " +
std::to_string(m_config.maxLineLength) + " characters.");
}
}
// Deeply nested code
if (m_config.detectDeepNesting) {
int maxDepth = 0, depth = 0;
for (char c : code) {
if (c == '{') { depth++; maxDepth = std::max(maxDepth, depth); }
if (c == '}') { depth = std::max(0, depth - 1); }
}
if (maxDepth > m_config.maxNestingDepth) {
issues.push_back("Deep nesting detected (depth: " + std::to_string(maxDepth) +
", max: " + std::to_string(m_config.maxNestingDepth) +
"). Consider refactoring.");
}
}
// Low comment ratio
if (m_config.detectLowComments) {
double ratio = comment_ratio(code);
if (count_lines(code) > m_config.minLinesForCommentCheck && ratio < m_config.minCommentRatio) {
issues.push_back("Low comment ratio (" + std::to_string(static_cast<int>(ratio * 100)) +
"%). Consider adding documentation.");
}
}
// Large functions
if (m_config.detectLargeFunctions) {
int funcCount = count_functions(code);
int lineCount = count_lines(code);
if (funcCount > 0 && lineCount / funcCount > m_config.maxFunctionLength) {
issues.push_back("Average function length is high (~" +
std::to_string(lineCount / funcCount) +
" lines, max: " + std::to_string(m_config.maxFunctionLength) +
"). Consider splitting into smaller functions.");
}
}
// Duplicate lines
if (m_config.detectDuplicateLines) {
auto dupes = detect_duplicate_lines(code);
if (dupes.size() > 3) {
issues.push_back(std::to_string(dupes.size()) +
" duplicate line pairs detected. Consider extracting shared logic.");
}
}
// TODO/FIXME reminders
if (m_config.detectTodoFixme) {
auto todos = extract_todo_comments(code);
if (!todos.empty()) {
issues.push_back(std::to_string(todos.size()) + " TODO/FIXME comment(s) found.");
}
}
// Retain cycle risk: closures capturing self without [weak self]
if (m_config.detectRetainCycles) {
int closuresWithSelf = 0;
std::string::size_type pos = 0;
while ((pos = code.find("{ [", pos)) != std::string::npos) {
pos += 3;
}
// Heuristic: look for ".self" or "self." inside closures without [weak self]
pos = 0;
while ((pos = code.find("self.", pos)) != std::string::npos) {
closuresWithSelf++;
pos += 5;
}
if (closuresWithSelf > 10) {
issues.push_back("High self. usage (" + std::to_string(closuresWithSelf) +
" refs). Verify no retain cycles in closures.");
}
}
// Maintainability warning
double mi = maintainability_index(code);
if (mi < 40 && count_lines(code) > 30) {
issues.push_back("Low maintainability index (" + std::to_string(static_cast<int>(mi)) +
"/100). Consider refactoring for readability.");
}
return issues;
}