From ad366540121acc51ad6383ca7ce291bf9946703c Mon Sep 17 00:00:00 2001 From: OpenCode Agent Date: Fri, 12 Jun 2026 00:55:46 +0530 Subject: [PATCH 1/2] feat: add language-specific suggestion rules for PHP, Rust, and Kotlin (#702) --- backend/app/services/code_assistant.py | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) 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( From 36c57d727a66476c9eaf6aeeec668333b61d38db Mon Sep 17 00:00:00 2001 From: OpenCode Agent Date: Fri, 12 Jun 2026 01:20:56 +0530 Subject: [PATCH 2/2] fix(frontend): ensure shimmer loading animation renders before analysis fetch Before the fetch starts, showShimmers() is called but the browser may not get a paint cycle before the API responds and renderResults() overwrites the shimmer DOM. Add await requestAnimationFrame(...) after showShimmers() to guarantee the browser paints the shimmer placeholder before the fetch begins. This ensures the loading animation is visible even for fast API responses, fixing the UX gap where the results panel stayed blank during analysis. Fixes #700 --- frontend/index.html | 1 + 1 file changed, 1 insertion(+) 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) {