From e88e55b100de7f42cf4ad398a131d9182e98a5d0 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:07 +0530 Subject: [PATCH 01/11] feat: add AST-based complexity analyzer --- frontend/src/utils/complexityAnalyzer.js | 225 +++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 frontend/src/utils/complexityAnalyzer.js diff --git a/frontend/src/utils/complexityAnalyzer.js b/frontend/src/utils/complexityAnalyzer.js new file mode 100644 index 00000000..de85407b --- /dev/null +++ b/frontend/src/utils/complexityAnalyzer.js @@ -0,0 +1,225 @@ +import { parse } from "@babel/parser"; + +const LOOP_TYPES = new Set(["ForStatement", "WhileStatement", "DoWhileStatement"]); +const LINEAR_ARRAY_METHODS = new Set([ + "forEach", + "map", + "filter", + "find", + "some", + "every", + "reduce", +]); + +const COMPLEXITY_BY_DEPTH = ["O(1)", "O(n)", "O(n²)", "O(n³)", "O(n⁴)", "O(n^k)"]; + +function complexityForDepth(depth) { + return COMPLEXITY_BY_DEPTH[Math.min(depth, COMPLEXITY_BY_DEPTH.length - 1)]; +} + +function walk(node, visitor, state) { + if (!node || typeof node !== "object") return; + + visitor(node, state); + + for (const [key, value] of Object.entries(node)) { + if (key === "loc" || key === "start" || key === "end") continue; + + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child.type === "string") walk(child, visitor, state); + }); + } else if (value && typeof value.type === "string") { + walk(value, visitor, state); + } + } +} + +function containsIdentifier(node, name) { + let found = false; + walk(node, (current) => { + if (current.type === "Identifier" && current.name === name) found = true; + }, {}); + return found; +} + +function getLoopDepth(node, currentDepth = 0) { + if (!node || typeof node !== "object") return currentDepth; + + let maxDepth = currentDepth; + const nextDepth = LOOP_TYPES.has(node.type) ? currentDepth + 1 : currentDepth; + maxDepth = Math.max(maxDepth, nextDepth); + + for (const [key, value] of Object.entries(node)) { + if (key === "loc" || key === "start" || key === "end") continue; + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child.type === "string") { + maxDepth = Math.max(maxDepth, getLoopDepth(child, nextDepth)); + } + }); + } else if (value && typeof value.type === "string") { + maxDepth = Math.max(maxDepth, getLoopDepth(value, nextDepth)); + } + } + + return maxDepth; +} + +function analyzeSpace(ast) { + let dynamicAllocation = false; + let recursion = false; + const functionNames = new Set(); + + walk(ast, (node) => { + if (node.type === "FunctionDeclaration" && node.id?.name) { + functionNames.add(node.id.name); + } + + if ( + node.type === "ArrayExpression" && + node.elements?.length > 0 + ) { + dynamicAllocation = true; + } + + if ( + node.type === "NewExpression" || + (node.type === "CallExpression" && + node.callee?.type === "MemberExpression" && + node.callee.property?.name === "push") + ) { + dynamicAllocation = true; + } + }, {}); + + walk(ast, (node) => { + if ( + node.type === "CallExpression" && + node.callee?.type === "Identifier" && + functionNames.has(node.callee.name) + ) { + recursion = true; + } + }, {}); + + if (recursion) return "O(n) or O(depth)"; + if (dynamicAllocation) return "O(n)"; + return "O(1)"; +} + +function analyzeLoops(ast) { + let maxLoopDepth = 0; + let loopCount = 0; + let recursiveFunctionCount = 0; + const functionNames = new Set(); + + walk(ast, (node) => { + if (LOOP_TYPES.has(node.type)) { + loopCount += 1; + } + if (node.type === "FunctionDeclaration" && node.id?.name) { + functionNames.add(node.id.name); + } + }, {}); + + maxLoopDepth = getLoopDepth(ast); + + walk(ast, (node) => { + if ( + node.type === "CallExpression" && + node.callee?.type === "Identifier" && + functionNames.has(node.callee.name) + ) { + recursiveFunctionCount += 1; + } + }, {}); + + const arrayMethodLoops = []; + walk(ast, (node) => { + if ( + node.type === "CallExpression" && + node.callee?.type === "MemberExpression" && + !node.callee.computed && + LINEAR_ARRAY_METHODS.has(node.callee.property?.name) + ) { + arrayMethodLoops.push(node.callee.property.name); + } + }, {}); + + return { + maxLoopDepth, + loopCount, + recursiveFunctionCount, + arrayMethodLoops, + }; +} + +export function analyzeJavaScriptComplexity(code) { + if (!code?.trim()) { + return { + status: "empty", + timeComplexity: "O(1)", + spaceComplexity: "O(1)", + explanation: "Write some JavaScript code to analyze its complexity.", + warnings: [], + }; + } + + try { + const ast = parse(code, { + sourceType: "unambiguous", + plugins: ["jsx", "typescript"], + }); + + const loops = analyzeLoops(ast); + const timeComplexity = complexityForDepth( + loops.maxLoopDepth + (loops.recursiveFunctionCount > 0 ? 0 : 0) + ); + + const warnings = []; + if (loops.maxLoopDepth >= 2) { + warnings.push( + `${loops.maxLoopDepth} nested loop levels detected; the dominant loop structure is approximately ${timeComplexity}.` + ); + } else if (loops.loopCount === 1 || loops.arrayMethodLoops.length > 0) { + warnings.push("A linear traversal was detected; review the input size to confirm O(n) behavior."); + } + + if (loops.recursiveFunctionCount > 0) { + warnings.push( + "Recursive function calls were detected. Recursion depth and branching determine the final complexity." + ); + } + + if (loops.arrayMethodLoops.length > 0) { + warnings.push( + `Array traversal method(s) detected: ${[...new Set(loops.arrayMethodLoops)].join(", ")}.` + ); + } + + return { + status: "success", + timeComplexity, + spaceComplexity: analyzeSpace(ast), + explanation: + loops.loopCount === 0 && loops.recursiveFunctionCount === 0 + ? "No loop or recursive traversal was detected. The analyzed operations are treated as constant-time by this heuristic." + : "This is a static heuristic based on the parsed AST. It estimates dominant loop nesting and common allocation patterns; it is not a formal proof of Big-O complexity.", + warnings, + metrics: { + loopCount: loops.loopCount, + maxLoopDepth: loops.maxLoopDepth, + recursiveCalls: loops.recursiveFunctionCount, + }, + }; + } catch (error) { + return { + status: "error", + timeComplexity: "—", + spaceComplexity: "—", + explanation: "The code is incomplete or contains a syntax error, so it cannot be analyzed yet.", + warnings: [error.message?.split("\n")[0] || "Unable to parse the code."], + }; + } +} From 7e28b99b92e7b84b6b50c4da7f4b2dfbaffdda00 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:15 +0530 Subject: [PATCH 02/11] feat: add complexity profiler panel --- .../src/components/ComplexityProfiler.jsx | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 frontend/src/components/ComplexityProfiler.jsx diff --git a/frontend/src/components/ComplexityProfiler.jsx b/frontend/src/components/ComplexityProfiler.jsx new file mode 100644 index 00000000..14aa8df1 --- /dev/null +++ b/frontend/src/components/ComplexityProfiler.jsx @@ -0,0 +1,68 @@ +import React from "react"; + +const ComplexityProfiler = ({ analysis, supported = true }) => { + if (!supported) { + return ( +
+
+

Complexity Analysis

+ JavaScript only +
+

+ AST analysis is currently available for JavaScript and TypeScript. Run or edit Java, Python, or C++ code normally. +

+
+ ); + } + + const isError = analysis?.status === "error"; + const isEmpty = analysis?.status === "empty"; + + return ( +
+
+

Complexity Analysis

+ + AST heuristic + +
+ +
+
+

Time Complexity

+

{analysis?.timeComplexity || "—"}

+
+
+

Space Complexity

+

{analysis?.spaceComplexity || "—"}

+
+
+ + {analysis?.metrics && !isError && !isEmpty && ( +
+ Loops: {analysis.metrics.loopCount} + Max nesting: {analysis.metrics.maxLoopDepth} + Recursive calls: {analysis.metrics.recursiveCalls} +
+ )} + +

{analysis?.explanation}

+ + {analysis?.warnings?.length > 0 && ( +
    + {analysis.warnings.map((warning, index) => ( +
  • + {warning} +
  • + ))} +
+ )} + + {isError && ( +

Fix the syntax error above and the profiler will analyze the code automatically.

+ )} +
+ ); +}; + +export default ComplexityProfiler; From 9c90d0b40a21f3435c1e0a8565ee789d55cf3622 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:25 +0530 Subject: [PATCH 03/11] feat: integrate AST complexity profiler into compiler --- frontend/src/components/Compiler.jsx | 70 ++++++++++++++++------------ 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/Compiler.jsx b/frontend/src/components/Compiler.jsx index 0ee46ef2..97919d7a 100644 --- a/frontend/src/components/Compiler.jsx +++ b/frontend/src/components/Compiler.jsx @@ -1,7 +1,9 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Editor from "@monaco-editor/react"; import { Play } from "lucide-react"; import DashboardLayout from "./Layouts/DashboardLayout"; +import ComplexityProfiler from "./ComplexityProfiler"; +import { analyzeJavaScriptComplexity } from "../utils/complexityAnalyzer"; const RAPIDAPI_KEY = import.meta.env.VITE_REACT_APP_RAPIDAPI_KEY; @@ -15,28 +17,32 @@ const LANGUAGE_MAP = { const Compiler = () => { const [language, setLanguage] = useState("62"); // Default Java const codeTemplates = { - "54": `#include -using namespace std; -int main() { - cout << "Hello World" << endl; - return 0; -}`, - "62": `public class Main { - public static void main(String[] args) { - System.out.println("Hello World"); - } -}`, + "54": `#include \nusing namespace std;\nint main() {\n cout << "Hello World" << endl;\n return 0;\n}`, + "62": `public class Main {\n public static void main(String[] args) {\n System.out.println("Hello World");\n }\n}`, "71": `print("Hello World")`, - "63": `console.log("Hello World");` + "63": `console.log("Hello World");`, }; const [code, setCode] = useState(codeTemplates[language]); + const [output, setOutput] = useState("No output"); + const [complexity, setComplexity] = useState(() => + analyzeJavaScriptComplexity(codeTemplates["63"]) + ); const handleLanguageChange = (e) => { const lang = e.target.value; setLanguage(lang); setCode(codeTemplates[lang] || "// Select a supported language"); }; - const [output, setOutput] = useState("No output"); + + useEffect(() => { + if (language !== "63") return; + + const timer = setTimeout(() => { + setComplexity(analyzeJavaScriptComplexity(code)); + }, 300); + + return () => clearTimeout(timer); + }, [code, language]); const handleRun = async () => { setOutput("Running..."); @@ -76,16 +82,16 @@ int main() { return ( <> -
-
-

Instant Code Compiler

-

+

+
+

Instant Code Compiler

+

Instantly write, run, and test your code in multiple languages. No setup required—just code and see results!

-
-
-
+
+
+
-
+
-
-

+
+

Output

               {output}
             

+ +
+ +
); From 8bd918a16f5e0b808095656243e45d3238e4fce7 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:36 +0530 Subject: [PATCH 04/11] feat: add AST parser dependency --- frontend/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/package.json b/frontend/package.json index 3c1e0738..79ef7015 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "test:watch": "vitest watch" }, "dependencies": { + "@babel/parser": "^7.28.3", "@monaco-editor/react": "^4.7.0", "@tailwindcss/vite": "^4.1.12", "axios": "^1.11.0", From 9209dc61bbe917a1c9dfb47f8a4f7eddecd3f5aa Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:49 +0530 Subject: [PATCH 05/11] fix: simplify AST complexity heuristics --- frontend/src/utils/complexityAnalyzer.js | 70 +++++++----------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/frontend/src/utils/complexityAnalyzer.js b/frontend/src/utils/complexityAnalyzer.js index de85407b..4ce2b49f 100644 --- a/frontend/src/utils/complexityAnalyzer.js +++ b/frontend/src/utils/complexityAnalyzer.js @@ -10,7 +10,6 @@ const LINEAR_ARRAY_METHODS = new Set([ "every", "reduce", ]); - const COMPLEXITY_BY_DEPTH = ["O(1)", "O(n)", "O(n²)", "O(n³)", "O(n⁴)", "O(n^k)"]; function complexityForDepth(depth) { @@ -19,12 +18,10 @@ function complexityForDepth(depth) { function walk(node, visitor, state) { if (!node || typeof node !== "object") return; - visitor(node, state); for (const [key, value] of Object.entries(node)) { if (key === "loc" || key === "start" || key === "end") continue; - if (Array.isArray(value)) { value.forEach((child) => { if (child && typeof child.type === "string") walk(child, visitor, state); @@ -35,20 +32,10 @@ function walk(node, visitor, state) { } } -function containsIdentifier(node, name) { - let found = false; - walk(node, (current) => { - if (current.type === "Identifier" && current.name === name) found = true; - }, {}); - return found; -} - function getLoopDepth(node, currentDepth = 0) { if (!node || typeof node !== "object") return currentDepth; - - let maxDepth = currentDepth; const nextDepth = LOOP_TYPES.has(node.type) ? currentDepth + 1 : currentDepth; - maxDepth = Math.max(maxDepth, nextDepth); + let maxDepth = nextDepth; for (const [key, value] of Object.entries(node)) { if (key === "loc" || key === "start" || key === "end") continue; @@ -68,26 +55,20 @@ function getLoopDepth(node, currentDepth = 0) { function analyzeSpace(ast) { let dynamicAllocation = false; - let recursion = false; + let recursiveCall = false; const functionNames = new Set(); walk(ast, (node) => { if (node.type === "FunctionDeclaration" && node.id?.name) { functionNames.add(node.id.name); } - - if ( - node.type === "ArrayExpression" && - node.elements?.length > 0 - ) { + if (node.type === "ArrayExpression" || node.type === "NewExpression") { dynamicAllocation = true; } - if ( - node.type === "NewExpression" || - (node.type === "CallExpression" && - node.callee?.type === "MemberExpression" && - node.callee.property?.name === "push") + node.type === "CallExpression" && + node.callee?.type === "MemberExpression" && + node.callee.property?.name === "push" ) { dynamicAllocation = true; } @@ -99,56 +80,48 @@ function analyzeSpace(ast) { node.callee?.type === "Identifier" && functionNames.has(node.callee.name) ) { - recursion = true; + recursiveCall = true; } }, {}); - if (recursion) return "O(n) or O(depth)"; + if (recursiveCall) return "O(n) or O(depth)"; if (dynamicAllocation) return "O(n)"; return "O(1)"; } function analyzeLoops(ast) { - let maxLoopDepth = 0; let loopCount = 0; let recursiveFunctionCount = 0; const functionNames = new Set(); + const arrayMethodLoops = []; walk(ast, (node) => { - if (LOOP_TYPES.has(node.type)) { - loopCount += 1; - } + if (LOOP_TYPES.has(node.type)) loopCount += 1; if (node.type === "FunctionDeclaration" && node.id?.name) { functionNames.add(node.id.name); } - }, {}); - - maxLoopDepth = getLoopDepth(ast); - - walk(ast, (node) => { if ( node.type === "CallExpression" && - node.callee?.type === "Identifier" && - functionNames.has(node.callee.name) + node.callee?.type === "MemberExpression" && + !node.callee.computed && + LINEAR_ARRAY_METHODS.has(node.callee.property?.name) ) { - recursiveFunctionCount += 1; + arrayMethodLoops.push(node.callee.property.name); } }, {}); - const arrayMethodLoops = []; walk(ast, (node) => { if ( node.type === "CallExpression" && - node.callee?.type === "MemberExpression" && - !node.callee.computed && - LINEAR_ARRAY_METHODS.has(node.callee.property?.name) + node.callee?.type === "Identifier" && + functionNames.has(node.callee.name) ) { - arrayMethodLoops.push(node.callee.property.name); + recursiveFunctionCount += 1; } }, {}); return { - maxLoopDepth, + maxLoopDepth: getLoopDepth(ast), loopCount, recursiveFunctionCount, arrayMethodLoops, @@ -171,13 +144,10 @@ export function analyzeJavaScriptComplexity(code) { sourceType: "unambiguous", plugins: ["jsx", "typescript"], }); - const loops = analyzeLoops(ast); - const timeComplexity = complexityForDepth( - loops.maxLoopDepth + (loops.recursiveFunctionCount > 0 ? 0 : 0) - ); - + const timeComplexity = complexityForDepth(loops.maxLoopDepth); const warnings = []; + if (loops.maxLoopDepth >= 2) { warnings.push( `${loops.maxLoopDepth} nested loop levels detected; the dominant loop structure is approximately ${timeComplexity}.` From f132d42617595531cce12037c1a3bab9b2c96a64 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:31:55 +0530 Subject: [PATCH 06/11] test: cover AST complexity analysis --- frontend/src/utils/complexityAnalyzer.test.js | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 frontend/src/utils/complexityAnalyzer.test.js diff --git a/frontend/src/utils/complexityAnalyzer.test.js b/frontend/src/utils/complexityAnalyzer.test.js new file mode 100644 index 00000000..7cb7d5a5 --- /dev/null +++ b/frontend/src/utils/complexityAnalyzer.test.js @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; +import { analyzeJavaScriptComplexity } from "./complexityAnalyzer"; + +describe("analyzeJavaScriptComplexity", () => { + it("detects a single loop as O(n)", () => { + const result = analyzeJavaScriptComplexity(` + for (let i = 0; i < n; i += 1) { + console.log(i); + } + `); + + expect(result.status).toBe("success"); + expect(result.timeComplexity).toBe("O(n)"); + expect(result.metrics.loopCount).toBe(1); + expect(result.metrics.maxLoopDepth).toBe(1); + }); + + it("detects nested loops as O(n²)", () => { + const result = analyzeJavaScriptComplexity(` + for (let i = 0; i < n; i += 1) { + for (let j = 0; j < n; j += 1) { + console.log(i, j); + } + } + `); + + expect(result.status).toBe("success"); + expect(result.timeComplexity).toBe("O(n²)"); + expect(result.metrics.loopCount).toBe(2); + expect(result.metrics.maxLoopDepth).toBe(2); + }); + + it("handles code with no loops", () => { + const result = analyzeJavaScriptComplexity("const answer = 42;"); + + expect(result.status).toBe("success"); + expect(result.timeComplexity).toBe("O(1)"); + expect(result.spaceComplexity).toBe("O(1)"); + }); + + it("returns a parse error for invalid JavaScript", () => { + const result = analyzeJavaScriptComplexity("for (let i = 0; i < n; i++ {"); + + expect(result.status).toBe("error"); + expect(result.timeComplexity).toBe("—"); + }); +}); From 84d7b59cd1b70b4894ebbb9acc2a39e4ca0b7745 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 13:32:09 +0530 Subject: [PATCH 07/11] fix: report linear array traversals as O(n) --- frontend/src/utils/complexityAnalyzer.js | 67 +++++++----------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/frontend/src/utils/complexityAnalyzer.js b/frontend/src/utils/complexityAnalyzer.js index 4ce2b49f..c9666255 100644 --- a/frontend/src/utils/complexityAnalyzer.js +++ b/frontend/src/utils/complexityAnalyzer.js @@ -1,15 +1,7 @@ import { parse } from "@babel/parser"; const LOOP_TYPES = new Set(["ForStatement", "WhileStatement", "DoWhileStatement"]); -const LINEAR_ARRAY_METHODS = new Set([ - "forEach", - "map", - "filter", - "find", - "some", - "every", - "reduce", -]); +const LINEAR_ARRAY_METHODS = new Set(["forEach", "map", "filter", "find", "some", "every", "reduce"]); const COMPLEXITY_BY_DEPTH = ["O(1)", "O(n)", "O(n²)", "O(n³)", "O(n⁴)", "O(n^k)"]; function complexityForDepth(depth) { @@ -59,27 +51,15 @@ function analyzeSpace(ast) { const functionNames = new Set(); walk(ast, (node) => { - if (node.type === "FunctionDeclaration" && node.id?.name) { - functionNames.add(node.id.name); - } - if (node.type === "ArrayExpression" || node.type === "NewExpression") { - dynamicAllocation = true; - } - if ( - node.type === "CallExpression" && - node.callee?.type === "MemberExpression" && - node.callee.property?.name === "push" - ) { + if (node.type === "FunctionDeclaration" && node.id?.name) functionNames.add(node.id.name); + if (node.type === "ArrayExpression" || node.type === "NewExpression") dynamicAllocation = true; + if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.property?.name === "push") { dynamicAllocation = true; } }, {}); walk(ast, (node) => { - if ( - node.type === "CallExpression" && - node.callee?.type === "Identifier" && - functionNames.has(node.callee.name) - ) { + if (node.type === "CallExpression" && node.callee?.type === "Identifier" && functionNames.has(node.callee.name)) { recursiveCall = true; } }, {}); @@ -97,9 +77,7 @@ function analyzeLoops(ast) { walk(ast, (node) => { if (LOOP_TYPES.has(node.type)) loopCount += 1; - if (node.type === "FunctionDeclaration" && node.id?.name) { - functionNames.add(node.id.name); - } + if (node.type === "FunctionDeclaration" && node.id?.name) functionNames.add(node.id.name); if ( node.type === "CallExpression" && node.callee?.type === "MemberExpression" && @@ -111,11 +89,7 @@ function analyzeLoops(ast) { }, {}); walk(ast, (node) => { - if ( - node.type === "CallExpression" && - node.callee?.type === "Identifier" && - functionNames.has(node.callee.name) - ) { + if (node.type === "CallExpression" && node.callee?.type === "Identifier" && functionNames.has(node.callee.name)) { recursiveFunctionCount += 1; } }, {}); @@ -140,32 +114,29 @@ export function analyzeJavaScriptComplexity(code) { } try { - const ast = parse(code, { - sourceType: "unambiguous", - plugins: ["jsx", "typescript"], - }); + const ast = parse(code, { sourceType: "unambiguous", plugins: ["jsx", "typescript"] }); const loops = analyzeLoops(ast); - const timeComplexity = complexityForDepth(loops.maxLoopDepth); + const timeComplexity = loops.maxLoopDepth > 0 + ? complexityForDepth(loops.maxLoopDepth) + : loops.arrayMethodLoops.length > 0 + ? "O(n)" + : loops.recursiveFunctionCount > 0 + ? "O(n) or O(branching^depth)" + : "O(1)"; const warnings = []; if (loops.maxLoopDepth >= 2) { - warnings.push( - `${loops.maxLoopDepth} nested loop levels detected; the dominant loop structure is approximately ${timeComplexity}.` - ); + warnings.push(`${loops.maxLoopDepth} nested loop levels detected; the dominant loop structure is approximately ${timeComplexity}.`); } else if (loops.loopCount === 1 || loops.arrayMethodLoops.length > 0) { warnings.push("A linear traversal was detected; review the input size to confirm O(n) behavior."); } if (loops.recursiveFunctionCount > 0) { - warnings.push( - "Recursive function calls were detected. Recursion depth and branching determine the final complexity." - ); + warnings.push("Recursive function calls were detected. Recursion depth and branching determine the final complexity."); } if (loops.arrayMethodLoops.length > 0) { - warnings.push( - `Array traversal method(s) detected: ${[...new Set(loops.arrayMethodLoops)].join(", ")}.` - ); + warnings.push(`Array traversal method(s) detected: ${[...new Set(loops.arrayMethodLoops)].join(", ")}.`); } return { @@ -173,7 +144,7 @@ export function analyzeJavaScriptComplexity(code) { timeComplexity, spaceComplexity: analyzeSpace(ast), explanation: - loops.loopCount === 0 && loops.recursiveFunctionCount === 0 + loops.loopCount === 0 && loops.recursiveFunctionCount === 0 && loops.arrayMethodLoops.length === 0 ? "No loop or recursive traversal was detected. The analyzed operations are treated as constant-time by this heuristic." : "This is a static heuristic based on the parsed AST. It estimates dominant loop nesting and common allocation patterns; it is not a formal proof of Big-O complexity.", warnings, From dd077126adb24911540363a76facc49e99bd0c47 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 18:39:39 +0530 Subject: [PATCH 08/11] fix: address AST complexity profiler review findings --- frontend/src/utils/complexityAnalyzer.js | 141 ++++++++++++++++++----- 1 file changed, 110 insertions(+), 31 deletions(-) diff --git a/frontend/src/utils/complexityAnalyzer.js b/frontend/src/utils/complexityAnalyzer.js index c9666255..ffd038c4 100644 --- a/frontend/src/utils/complexityAnalyzer.js +++ b/frontend/src/utils/complexityAnalyzer.js @@ -1,14 +1,21 @@ import { parse } from "@babel/parser"; -const LOOP_TYPES = new Set(["ForStatement", "WhileStatement", "DoWhileStatement"]); +const LOOP_TYPES = new Set([ + "ForStatement", + "ForInStatement", + "ForOfStatement", + "WhileStatement", + "DoWhileStatement", +]); const LINEAR_ARRAY_METHODS = new Set(["forEach", "map", "filter", "find", "some", "every", "reduce"]); +const ALLOCATING_ARRAY_METHODS = new Set(["map", "filter"]); const COMPLEXITY_BY_DEPTH = ["O(1)", "O(n)", "O(n²)", "O(n³)", "O(n⁴)", "O(n^k)"]; function complexityForDepth(depth) { return COMPLEXITY_BY_DEPTH[Math.min(depth, COMPLEXITY_BY_DEPTH.length - 1)]; } -function walk(node, visitor, state) { +function walk(node, visitor, state = {}) { if (!node || typeof node !== "object") return; visitor(node, state); @@ -45,39 +52,110 @@ function getLoopDepth(node, currentDepth = 0) { return maxDepth; } -function analyzeSpace(ast) { - let dynamicAllocation = false; - let recursiveCall = false; - const functionNames = new Set(); +function isCallable(node) { + return node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression"; +} - walk(ast, (node) => { - if (node.type === "FunctionDeclaration" && node.id?.name) functionNames.add(node.id.name); - if (node.type === "ArrayExpression" || node.type === "NewExpression") dynamicAllocation = true; - if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.property?.name === "push") { - dynamicAllocation = true; +function getCallableName(node, parent) { + if (node?.id?.name) return node.id.name; + if (parent?.type === "VariableDeclarator" && parent.id?.type === "Identifier") return parent.id.name; + if (parent?.type === "AssignmentExpression" && parent.left?.type === "Identifier") return parent.left.name; + return null; +} + +function collectCallables(ast) { + const callables = []; + + function visit(node, parent = null) { + if (!node || typeof node !== "object") return; + if (isCallable(node)) { + const name = getCallableName(node, parent); + if (name && node.body) callables.push({ name, body: node.body }); + } + + for (const [key, value] of Object.entries(node)) { + if (key === "loc" || key === "start" || key === "end") continue; + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child.type === "string") visit(child, node); + }); + } else if (value && typeof value.type === "string") { + visit(value, node); + } + } + } + + visit(ast); + return callables; +} + +function hasRecursiveCall(callable) { + let recursive = false; + + function visit(node, parent = null) { + if (!node || typeof node !== "object" || recursive) return; + if (isCallable(node) && node !== callable.body) return; + + if ( + node.type === "CallExpression" && + node.callee?.type === "Identifier" && + node.callee.name === callable.name + ) { + recursive = true; + return; } - }, {}); + + for (const [key, value] of Object.entries(node)) { + if (key === "loc" || key === "start" || key === "end") continue; + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child.type === "string") visit(child, node); + }); + } else if (value && typeof value.type === "string") { + visit(value, node); + } + } + } + + visit(callable.body); + return recursive; +} + +function analyzeRecursion(ast) { + const callables = collectCallables(ast); + const recursiveCallables = callables.filter(hasRecursiveCall); + return { + recursiveFunctionCount: recursiveCallables.length, + hasRecursion: recursiveCallables.length > 0, + }; +} + +function analyzeSpace(ast, recursion) { + let dynamicAllocation = false; walk(ast, (node) => { - if (node.type === "CallExpression" && node.callee?.type === "Identifier" && functionNames.has(node.callee.name)) { - recursiveCall = true; + if (node.type === "ArrayExpression" || node.type === "NewExpression") dynamicAllocation = true; + if ( + node.type === "CallExpression" && + node.callee?.type === "MemberExpression" && + !node.callee.computed + ) { + const method = node.callee.property?.name; + if (method === "push" || ALLOCATING_ARRAY_METHODS.has(method)) dynamicAllocation = true; } - }, {}); + }); - if (recursiveCall) return "O(n) or O(depth)"; + if (recursion.hasRecursion) return "O(n) or O(depth)"; if (dynamicAllocation) return "O(n)"; return "O(1)"; } -function analyzeLoops(ast) { +function analyzeLoops(ast, recursion) { let loopCount = 0; - let recursiveFunctionCount = 0; - const functionNames = new Set(); const arrayMethodLoops = []; walk(ast, (node) => { if (LOOP_TYPES.has(node.type)) loopCount += 1; - if (node.type === "FunctionDeclaration" && node.id?.name) functionNames.add(node.id.name); if ( node.type === "CallExpression" && node.callee?.type === "MemberExpression" && @@ -86,18 +164,12 @@ function analyzeLoops(ast) { ) { arrayMethodLoops.push(node.callee.property.name); } - }, {}); - - walk(ast, (node) => { - if (node.type === "CallExpression" && node.callee?.type === "Identifier" && functionNames.has(node.callee.name)) { - recursiveFunctionCount += 1; - } - }, {}); + }); return { maxLoopDepth: getLoopDepth(ast), loopCount, - recursiveFunctionCount, + recursiveFunctionCount: recursion.recursiveFunctionCount, arrayMethodLoops, }; } @@ -114,8 +186,15 @@ export function analyzeJavaScriptComplexity(code) { } try { - const ast = parse(code, { sourceType: "unambiguous", plugins: ["jsx", "typescript"] }); - const loops = analyzeLoops(ast); + const ast = parse(code, { + sourceType: "unambiguous", + plugins: ["jsx", "typescript"], + }); + const recursion = analyzeRecursion(ast); + const loops = analyzeLoops(ast, recursion); + + // Array callbacks represent one traversal of their input. They should not + // be multiplied with one another by this lightweight heuristic. const timeComplexity = loops.maxLoopDepth > 0 ? complexityForDepth(loops.maxLoopDepth) : loops.arrayMethodLoops.length > 0 @@ -142,7 +221,7 @@ export function analyzeJavaScriptComplexity(code) { return { status: "success", timeComplexity, - spaceComplexity: analyzeSpace(ast), + spaceComplexity: analyzeSpace(ast, recursion), explanation: loops.loopCount === 0 && loops.recursiveFunctionCount === 0 && loops.arrayMethodLoops.length === 0 ? "No loop or recursive traversal was detected. The analyzed operations are treated as constant-time by this heuristic." From cd4aab21ebfc42f08dc85a419b7ccda5e4ea9234 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 18:39:51 +0530 Subject: [PATCH 09/11] fix: enable TypeScript complexity analysis --- frontend/src/components/Compiler.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Compiler.jsx b/frontend/src/components/Compiler.jsx index 97919d7a..6dec3370 100644 --- a/frontend/src/components/Compiler.jsx +++ b/frontend/src/components/Compiler.jsx @@ -12,6 +12,7 @@ const LANGUAGE_MAP = { "62": "java", "71": "python", "63": "javascript", + "74": "typescript", }; const Compiler = () => { @@ -21,6 +22,7 @@ const Compiler = () => { "62": `public class Main {\n public static void main(String[] args) {\n System.out.println("Hello World");\n }\n}`, "71": `print("Hello World")`, "63": `console.log("Hello World");`, + "74": `const message: string = "Hello World";\nconsole.log(message);`, }; const [code, setCode] = useState(codeTemplates[language]); const [output, setOutput] = useState("No output"); @@ -35,7 +37,7 @@ const Compiler = () => { }; useEffect(() => { - if (language !== "63") return; + if (language !== "63" && language !== "74") return; const timer = setTimeout(() => { setComplexity(analyzeJavaScriptComplexity(code)); @@ -106,6 +108,7 @@ const Compiler = () => { +
From b624d269579aacfd7f8e1c7137c3669b994d81fc Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 18:39:59 +0530 Subject: [PATCH 10/11] fix: clarify complexity profiler language support --- frontend/src/components/ComplexityProfiler.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ComplexityProfiler.jsx b/frontend/src/components/ComplexityProfiler.jsx index 14aa8df1..a4cc40d6 100644 --- a/frontend/src/components/ComplexityProfiler.jsx +++ b/frontend/src/components/ComplexityProfiler.jsx @@ -6,7 +6,7 @@ const ComplexityProfiler = ({ analysis, supported = true }) => {

Complexity Analysis

- JavaScript only + JavaScript / TypeScript only

AST analysis is currently available for JavaScript and TypeScript. Run or edit Java, Python, or C++ code normally. From a3c6ced46f4e54f5a3a2c889121088dfd94bf044 Mon Sep 17 00:00:00 2001 From: bindusreeseetha Date: Wed, 12 Aug 2026 18:40:08 +0530 Subject: [PATCH 11/11] test: cover AST complexity review cases --- frontend/src/utils/complexityAnalyzer.test.js | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/frontend/src/utils/complexityAnalyzer.test.js b/frontend/src/utils/complexityAnalyzer.test.js index 7cb7d5a5..eefb4035 100644 --- a/frontend/src/utils/complexityAnalyzer.test.js +++ b/frontend/src/utils/complexityAnalyzer.test.js @@ -15,6 +15,24 @@ describe("analyzeJavaScriptComplexity", () => { expect(result.metrics.maxLoopDepth).toBe(1); }); + it("detects for...in and for...of as linear loops", () => { + const forIn = analyzeJavaScriptComplexity(` + for (const key in values) { + console.log(key); + } + `); + const forOf = analyzeJavaScriptComplexity(` + for (const value of values) { + console.log(value); + } + `); + + expect(forIn.timeComplexity).toBe("O(n)"); + expect(forIn.metrics.loopCount).toBe(1); + expect(forOf.timeComplexity).toBe("O(n)"); + expect(forOf.metrics.loopCount).toBe(1); + }); + it("detects nested loops as O(n²)", () => { const result = analyzeJavaScriptComplexity(` for (let i = 0; i < n; i += 1) { @@ -30,6 +48,59 @@ describe("analyzeJavaScriptComplexity", () => { expect(result.metrics.maxLoopDepth).toBe(2); }); + it("treats nested map callbacks as one linear traversal", () => { + const result = analyzeJavaScriptComplexity(` + rows.map((row) => row.map((value) => value * 2)); + `); + + expect(result.timeComplexity).toBe("O(n)"); + expect(result.metrics.loopCount).toBe(0); + }); + + it("treats a map inside a loop as linear under this heuristic", () => { + const result = analyzeJavaScriptComplexity(` + for (const row of rows) { + row.map((value) => value * 2); + } + `); + + expect(result.timeComplexity).toBe("O(n)"); + expect(result.metrics.loopCount).toBe(1); + }); + + it("reports O(n) space for map and filter allocations", () => { + const mapResult = analyzeJavaScriptComplexity("const doubled = values.map((value) => value * 2);"); + const filterResult = analyzeJavaScriptComplexity("const active = values.filter((value) => value.active);"); + + expect(mapResult.spaceComplexity).toBe("O(n)"); + expect(filterResult.spaceComplexity).toBe("O(n)"); + }); + + it("does not treat ordinary helper calls as recursion", () => { + const result = analyzeJavaScriptComplexity(` + function helper(value) { + return value + 1; + } + function main(value) { + return helper(value); + } + `); + + expect(result.metrics.recursiveCalls).toBe(0); + expect(result.timeComplexity).toBe("O(1)"); + expect(result.spaceComplexity).toBe("O(1)"); + }); + + it("detects recursive arrow functions", () => { + const result = analyzeJavaScriptComplexity(` + const factorial = (n) => n <= 1 ? 1 : n * factorial(n - 1); + `); + + expect(result.metrics.recursiveCalls).toBe(1); + expect(result.timeComplexity).toBe("O(n) or O(branching^depth)"); + expect(result.spaceComplexity).toBe("O(n) or O(depth)"); + }); + it("handles code with no loops", () => { const result = analyzeJavaScriptComplexity("const answer = 42;");