#include "CodeEngine.hpp" #include #include #include #include #include #include #include // ─── Swift keyword set ─────────────────────────────────────────── static const std::set& swift_keyword_set() { static const std::set 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 CodeEngine::tokenize(const std::string& code) { std::vector 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(i - start)}); col += static_cast(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(i - start)}); col += static_cast(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(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(i - start)}); col += static_cast(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(i - start)}); col += static_cast(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(i - start)}); col += static_cast(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(i - start)}); col += static_cast(i - start); continue; } // Unknown tokens.push_back({TokenType::Unknown, std::string(1, c), line, col, 1}); col++; i++; } return tokens; } // ─── Symbol Extraction ─────────────────────────────────────────── std::vector CodeEngine::extract_symbols(const std::string& code) { std::vector 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 CodeEngine::extract_imports(const std::string& code) { std::vector 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> CodeEngine::detect_duplicate_lines(const std::string& code) { std::vector> dupes; std::istringstream stream(code); std::string line; std::unordered_map 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> CodeEngine::extract_todo_comments(const std::string& code) { std::vector> 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(std::toupper(static_cast(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 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(code.size()); if (loc == 0 || volume == 0) return 100.0; double lnVol = std::log(static_cast(volume)); double lnLoc = std::log(static_cast(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(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(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 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(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(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(c); } return static_cast(hash & 0x7FFFFFFF); } // ─── Counting ──────────────────────────────────────────────────── int CodeEngine::count_lines(const std::string& code) { if (code.empty()) return 0; return static_cast(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 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 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(commentLines) / totalLines : 0.0; } // ─── Keyword Extraction ───────────────────────────────────────── std::vector CodeEngine::extract_keywords(const std::string& code) { const std::vector 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 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 CodeEngine::find_issues(const std::string& code) { std::vector 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(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(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(mi)) + "/100). Consider refactoring for readability."); } return issues; }