fix: Build LeetCode-style coding interface for DSA round#16
fix: Build LeetCode-style coding interface for DSA round#16SyedHannanMehdi wants to merge 31 commits intoJk2006k:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds initial building blocks for a LeetCode-style DSA round by introducing predefined problems/test cases and browser-side “mock” code runners, but it does not yet include the actual React UI/wiring described in #12 and has several runtime-breaking mismatches between problem definitions and the executor implementation.
Changes:
- Added a predefined DSA problem set (descriptions, examples, constraints, starter code, test cases).
- Added a JavaScript code runner that executes user code via
new Functionand checks results against expected outputs. - Added an alternative “code executor” implementation with function detection + (intended) validator-based checking.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/components/DSARound/problems.js | Defines problem metadata, starter code, and test cases (but validator shape is inconsistent with executor). |
| src/components/DSARound/codeRunner.js | Implements a JS runner and equality checks using new Function (misleading “safe” claims; hard-coded special-case validation). |
| src/components/DSARound/codeExecutor.js | Implements a second runner with hard-coded function detection and problem.validator dependency (currently incompatible with problems.js). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Handles arrays (order-insensitive for twoSum style), primitives, etc. | ||
| */ | ||
| function checkEquality(result, expected, problem) { | ||
| if (result === null || result === undefined) return false; | ||
|
|
||
| // For problems where array order doesn't matter (e.g. twoSum) | ||
| if ( | ||
| Array.isArray(result) && | ||
| Array.isArray(expected) && | ||
| problem.id === 1 // twoSum — order independent | ||
| ) { | ||
| return ( | ||
| JSON.stringify([...result].sort((a, b) => a - b)) === | ||
| JSON.stringify([...expected].sort((a, b) => a - b)) | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
checkEquality hard-codes special-case behavior for problem.id === 1 (Two Sum). This will be easy to forget when adding new problems that have order-insensitive outputs. Consider moving this into the problem definition (e.g., an outputComparer / normalizeResult / orderInsensitive: true flag) so validation logic is data-driven.
| * Handles arrays (order-insensitive for twoSum style), primitives, etc. | |
| */ | |
| function checkEquality(result, expected, problem) { | |
| if (result === null || result === undefined) return false; | |
| // For problems where array order doesn't matter (e.g. twoSum) | |
| if ( | |
| Array.isArray(result) && | |
| Array.isArray(expected) && | |
| problem.id === 1 // twoSum — order independent | |
| ) { | |
| return ( | |
| JSON.stringify([...result].sort((a, b) => a - b)) === | |
| JSON.stringify([...expected].sort((a, b) => a - b)) | |
| ); | |
| } | |
| * Handles arrays (optionally order-insensitive), primitives, etc. | |
| */ | |
| function checkEquality(result, expected, problem) { | |
| if (result === null || result === undefined) return false; | |
| const isArrayResult = Array.isArray(result); | |
| const isArrayExpected = Array.isArray(expected); | |
| // Allow problems to provide a custom normalization/comparison hook | |
| if (problem && typeof problem.normalizeResult === "function") { | |
| const normalizedResult = problem.normalizeResult(result); | |
| const normalizedExpected = problem.normalizeResult(expected); | |
| return JSON.stringify(normalizedResult) === JSON.stringify(normalizedExpected); | |
| } | |
| // For problems where array order doesn't matter (data-driven) | |
| if (problem && problem.orderInsensitive && isArrayResult && isArrayExpected) { | |
| const sortedResult = [...result].sort((a, b) => a - b); | |
| const sortedExpected = [...expected].sort((a, b) => a - b); | |
| return JSON.stringify(sortedResult) === JSON.stringify(sortedExpected); | |
| } |
| // Predefined DSA problems with test cases | ||
|
|
||
| export const problems = [ |
There was a problem hiding this comment.
These new DSA Round modules live under the repo root src/, but the app’s React frontend is built from client/src (per README/project structure). As-is, this code won’t be bundled or reachable from the UI, so the PR doesn’t actually deliver the LeetCode-style interface requested in #12. Consider moving this feature under client/src/... and adding the actual React page/component wiring.
src/components/DSARound/problems.js
Outdated
| solutionValidator: ` | ||
| function validate(fn, input) { | ||
| const result = fn(input.nums, input.target); | ||
| return JSON.stringify(result.sort((a,b)=>a-b)); | ||
| } | ||
| function expectedStr(expected) { | ||
| return JSON.stringify(expected.sort((a,b)=>a-b)); | ||
| } | ||
| `, |
There was a problem hiding this comment.
solutionValidator is defined as a string here, but the executor code expects a callable problem.validator function. This mismatch will make validation fail at runtime unless something converts this string into a function. Recommend defining a real validator function on each problem (or removing solutionValidator entirely and using the common equality checker consistently).
| solutionValidator: ` | |
| function validate(fn, input) { | |
| const result = fn(input.nums, input.target); | |
| return JSON.stringify(result.sort((a,b)=>a-b)); | |
| } | |
| function expectedStr(expected) { | |
| return JSON.stringify(expected.sort((a,b)=>a-b)); | |
| } | |
| `, | |
| validator: (fn, input, expected) => { | |
| const result = fn(input.nums, input.target); | |
| // If both result and expected are arrays, compare their sorted forms | |
| if (Array.isArray(result) && Array.isArray(expected)) { | |
| const sortedResult = [...result].sort((a, b) => a - b); | |
| const sortedExpected = [...expected].sort((a, b) => a - b); | |
| return JSON.stringify(sortedResult) === JSON.stringify(sortedExpected); | |
| } | |
| // Fallback: strict equality for non-array results | |
| return result === expected; | |
| }, |
| /** | ||
| * Mock code execution engine for JavaScript solutions. | ||
| * Safely runs user-submitted code against predefined test cases. | ||
| */ |
There was a problem hiding this comment.
This file claims to run user-submitted code "safely" / in a "sandbox", but new Function(...) executes arbitrary JavaScript with access to the page context (cookies via document, network via fetch, etc.). If this is intended to be mock-only, please adjust the wording to avoid implying security; otherwise this should be moved to an actual sandbox (e.g., backend runner, or isolated iframe/worker with strict constraints).
| export function runJavaScriptCode(code, problem) { | ||
| const results = []; | ||
|
|
There was a problem hiding this comment.
There are now two different JS execution implementations (codeRunner.js and codeExecutor.js) exporting similarly-named functions but returning different result shapes (pass vs passed, received vs actual, etc.). This duplication will make the UI integration ambiguous and brittle. Recommend consolidating to a single runner/executor module with one result schema and removing the other implementation.
| if (typeof userFn !== "function") { | ||
| throw new Error( | ||
| "Could not detect your function. Make sure it matches the starter code signature." | ||
| ); | ||
| } | ||
|
|
||
| const passed = problem.validator(userFn, tc.input); | ||
|
|
There was a problem hiding this comment.
runJavaScriptCode calls problem.validator(...), but the provided problem definitions don’t define a validator function (only a solutionValidator string on problem 1, and nothing on others). This will throw at runtime. Suggest switching to a shared equality/validation strategy (e.g., use problem.functionName + problem.inputArgs and compare against tc.expected like codeRunner does), or add a real validator function to every problem.
| // We wrap in an IIFE that returns the last declared function name. | ||
| // eslint-disable-next-line no-new-func | ||
| const factory = new Function(` | ||
| ${code} | ||
| // detect & return function reference | ||
| const fnNames = Object.getOwnPropertyNames(this).filter( | ||
| k => typeof this[k] === 'function' | ||
| ); | ||
| // Try common function name patterns | ||
| const candidates = [ | ||
| typeof twoSum !== 'undefined' && twoSum, | ||
| typeof reverseString !== 'undefined' && reverseString, | ||
| typeof isValid !== 'undefined' && isValid, | ||
| typeof maxSubArray !== 'undefined' && maxSubArray, | ||
| ].filter(Boolean); | ||
| return candidates[0] || null; | ||
| `); | ||
|
|
||
| // Execute in an empty object context so "this" is a plain obj. | ||
| const userFn = factory.call({}); |
There was a problem hiding this comment.
Function detection is hard-coded to a small set of names (twoSum, reverseString, isValid, maxSubArray) and doesn’t include problems defined in problems.js like reverseList or search. This guarantees failures for those problems. Recommend deriving the function reference from problem.functionName (and supporting common declaration styles) rather than maintaining a hard-coded list.
| // We wrap in an IIFE that returns the last declared function name. | |
| // eslint-disable-next-line no-new-func | |
| const factory = new Function(` | |
| ${code} | |
| // detect & return function reference | |
| const fnNames = Object.getOwnPropertyNames(this).filter( | |
| k => typeof this[k] === 'function' | |
| ); | |
| // Try common function name patterns | |
| const candidates = [ | |
| typeof twoSum !== 'undefined' && twoSum, | |
| typeof reverseString !== 'undefined' && reverseString, | |
| typeof isValid !== 'undefined' && isValid, | |
| typeof maxSubArray !== 'undefined' && maxSubArray, | |
| ].filter(Boolean); | |
| return candidates[0] || null; | |
| `); | |
| // Execute in an empty object context so "this" is a plain obj. | |
| const userFn = factory.call({}); | |
| // We wrap in an IIFE that returns the detected function reference. | |
| // eslint-disable-next-line no-new-func | |
| const functionName = problem && problem.functionName; | |
| const factory = new Function( | |
| 'functionName', | |
| ` | |
| ${code} | |
| // detect & return function reference | |
| const fnNames = Object.getOwnPropertyNames(this).filter( | |
| k => typeof this[k] === 'function' | |
| ); | |
| // Prefer the function name specified by the problem, if provided | |
| if (typeof functionName === 'string' && functionName && typeof this[functionName] === 'function') { | |
| return this[functionName]; | |
| } | |
| // Fallback: if any functions were defined on this context, return the last one | |
| if (fnNames.length > 0) { | |
| const lastName = fnNames[fnNames.length - 1]; | |
| return this[lastName]; | |
| } | |
| // If nothing could be detected, return null | |
| return null; | |
| ` | |
| ); | |
| // Execute in an empty object context so "this" is a plain obj. | |
| const userFn = factory.call({}, functionName); |
… (orderInsensitive + normalizeResult), remove hard-coded problem.id===1 special case
…em.validator (function), add normalizeResult + orderInsensitive fallbacks, derive fn name from problem.starterCode
… flag, remove solutionValidator string
… data-driven orderInsensitive/normalizeResult
…endency, use shared checkEquality from codeRunner
…el, code editor, and test results
…rt from correct client/src path
…port from client/src
…export from client/src
Automated fix for #12 — implemented by CashClaw agent.
Closes #12