diff --git a/backend/app/services/code_assistant.py b/backend/app/services/code_assistant.py index 951853c6..23133262 100644 --- a/backend/app/services/code_assistant.py +++ b/backend/app/services/code_assistant.py @@ -1097,6 +1097,152 @@ def run_suggestions(code: str, language: str) -> dict: } ) + # ───────────────────────────────────────────────────────────── + # PHP-specific suggestions + # ───────────────────────────────────────────────────────────── + if language == "PHP": + # Detect deprecated mysql_* functions + mysql_lines = find_lines_matching_pattern( + code, r"\bmysql_(real_)?escape_string\b|\bmysql_connect\b|\bmysql_query\b" + ) + if mysql_lines: + sample_mysql = mysql_lines[:3] + suggestions.append({ + "category": "Security", + "description": f"Deprecated `mysql_*` functions detected ({len(mysql_lines)} line(s)). Use mysqli or PDO with prepared statements instead.", + "line_number": mysql_lines[0], + "line_range": sample_mysql, + "code_context": format_code_snippet(code, sample_mysql), + "example": "$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');\n$stmt->execute([$id]);", + "priority": "high", + }) + + # Detect unsanitized output of superglobals + unsanitized_output = find_lines_matching_pattern( + code, r"echo\s+\$(GET|POST|REQUEST|COOKIE|SERVER)\b" + ) + if unsanitized_output: + sample_unsan = unsanitized_output[:3] + suggestions.append({ + "category": "Security", + "description": f"Unsanitized superglobal output detected ({len(unsanitized_output)} line(s)). Escape output with htmlspecialchars().", + "line_number": unsanitized_output[0], + "line_range": sample_unsan, + "code_context": format_code_snippet(code, sample_unsan), + "example": "echo htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8');", + "priority": "high", + }) + + # Detect missing namespace/use in PHP files that likely need autoloading + has_namespace = bool(re.search(r"\bnamespace\s+[\w\\]+;", code)) + has_use = bool(re.search(r"\buse\s+[\w\\]+;", code)) + has_class = bool(re.search(r"\bclass\s+\w+", code)) + if has_class and not (has_namespace or has_use): + suggestions.append({ + "category": "Architecture", + "description": "PHP file defines a class but lacks `namespace` or `use` statements. Adopt PSR-4 autoloading for maintainability.", + "line_number": None, + "line_range": None, + "code_context": None, + "example": "namespace App\\Models;\nuse Illuminate\\Database\\Eloquent\\Model;", + "priority": "medium", + }) + + # ───────────────────────────────────────────────────────────── + # Rust-specific suggestions + # ───────────────────────────────────────────────────────────── + if language == "Rust": + # Detect .unwrap() in library/production code paths + unwrap_lines = find_lines_matching_pattern(code, r"\.\s*unwrap\s*\(") + if unwrap_lines: + sample_unwrap = unwrap_lines[:3] + suggestions.append({ + "category": "Error Handling", + "description": f"`.unwrap()` detected ({len(unwrap_lines)} occurrence(s)). In production code, propagate errors with `?` or handle them explicitly.", + "line_number": unwrap_lines[0], + "line_range": sample_unwrap, + "code_context": format_code_snippet(code, sample_unwrap), + "example": "let value = some_result?;\n// or\nlet value = match some_result {\n Ok(v) => v,\n Err(e) => return Err(e),\n};", + "priority": "high", + }) + + # Detect functions returning Result without ? operator usage + fn_returning_result = find_lines_matching_pattern( + code, r"fn\s+\w+[^{]*\)\s*->\s*Result\b" + ) + if fn_returning_result: + suggestions.append({ + "category": "Error Handling", + "description": f"Function(s) returning `Result` detected. Consider using the `?` operator for concise error propagation.", + "line_number": fn_returning_result[0], + "line_range": fn_returning_result[:3], + "code_context": format_code_snippet(code, fn_returning_result[:2]), + "example": "fn read_file(path: &str) -> Result {\n std::fs::read_to_string(path)?\n}", + "priority": "medium", + }) + + # Detect unsafe blocks + unsafe_lines = find_lines_matching_pattern(code, r"\bunsafe\s*\{") + if unsafe_lines: + sample_unsafe = unsafe_lines[:3] + suggestions.append({ + "category": "Safety", + "description": f"`unsafe` block detected ({len(unsafe_lines)} occurrence(s)). Ensure all invariants are documented and the block is as small as possible.", + "line_number": unsafe_lines[0], + "line_range": sample_unsafe, + "code_context": format_code_snippet(code, sample_unsafe), + "example": "// Document WHY this block is unsafe and what invariants it maintains", + "priority": "medium", + }) + + # ───────────────────────────────────────────────────────────── + # Kotlin-specific suggestions + # ───────────────────────────────────────────────────────────── + if language == "Kotlin": + # Detect !! non-null assertions + not_null_assert_lines = find_lines_matching_pattern(code, r"\b\w+\s*!!\s*\(?") + if not_null_assert_lines: + sample_not_null = not_null_assert_lines[:3] + suggestions.append({ + "category": "Type Safety", + "description": f"`!!` non-null assertion detected ({len(not_null_assert_lines)} occurrence(s)). Use safe calls (`?.`) or Elvis operator (`?:`) instead.", + "line_number": not_null_assert_lines[0], + "line_range": sample_not_null, + "code_context": format_code_snippet(code, sample_not_null), + "example": "val len = name?.length ?: 0", + "priority": "high", + }) + + # Detect nested if-else chains that could be when expressions + nested_if_pattern = r"if\s*\([^)]+\)\s*\{[^}]*\n[^}]*if\s*\([^)]+\)\s*\{" + nested_ifs = find_lines_matching_pattern(code, nested_if_pattern) + if nested_ifs: + suggestions.append({ + "category": "Readability", + "description": "Nested if-else chain detected. Consider refactoring to a `when` expression for cleaner control flow.", + "line_number": nested_ifs[0], + "line_range": nested_ifs[:3], + "code_context": format_code_snippet(code, nested_ifs[:2]), + "example": "when (x) {\n 1 -> \"one\"\n 2 -> \"two\"\n else -> \"other\"\n}", + "priority": "medium", + }) + + # Detect mutable collections used where immutable would suffice + mutable_collection_lines = find_lines_matching_pattern( + code, r"\bmutableListOf\b|\bmutableMapOf\b|\bmutableSetOf\b" + ) + if mutable_collection_lines: + sample_mut = mutable_collection_lines[:3] + suggestions.append({ + "category": "Immutability", + "description": f"Mutable collection constructors detected ({len(mutable_collection_lines)} occurrence(s)). Prefer immutable collections for safer, more predictable code.", + "line_number": mutable_collection_lines[0], + "line_range": sample_mut, + "code_context": format_code_snippet(code, sample_mut), + "example": "listOf(1, 2, 3) // immutable\nmutableListOf(1, 2, 3) // mutable — only use if modification is needed", + "priority": "low", + }) + # Score # Score calculation deductions = sum( diff --git a/frontend/index.html b/frontend/index.html index 839f8095..486a4323 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3391,6 +3391,7 @@

Debug. Understand.
Ship faster.

analyzeBtn.classList.add('loading'); analyzeBtn.disabled = true; showShimmers(); + await new Promise(r => requestAnimationFrame(r)); try { if (selectedZipFile) {