Quality: Update exercise examples to use expression-based evaluation instead of const declarations#1157
Conversation
… instead of `const` declarations The issue arises because `const` declarations in JavaScript are statements, not expressions. When evaluated in a REPL or the Source Academy inline IDE, statements return `undefined`. To show the expected output (e.g., `3` or `4`), the examples should either use simple variable assignments (if the variable is already declared) or, more idiomatically for SICP JS, use expressions that evaluate to the desired value without relying on declaration side-effects. Affected files: processExerciseJson.js Signed-off-by: toanmap <174589430+maptoan@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the processExerciseJson function to handle SNIPPET and TEXT nodes, but it introduces several critical breaking changes. The removal of the getIdForExerciseJson function and the modification of the processExerciseJson signature (changing the second argument from an object to an array) will likely cause runtime errors in dependent files like parseXmlJson.js. Additionally, the regex used to transform const declarations is fragile and may incorrectly modify string literals or fail on destructuring. There is also an unused import that should be removed.
| const processExerciseJson = (node, writeTo) => { | ||
| const exercise = { | ||
| type: "exercise", | ||
| content: [] | ||
| }; | ||
| const children = Array.from(node.childNodes); | ||
| for (const child of children) { | ||
| if (child.nodeName === "SNIPPET") { | ||
| const snippet = []; | ||
| recursivelyProcessTextSnippetJson(child.firstChild, snippet); | ||
| const code = snippet.join("").trim(); | ||
| exercise.content.push({ | ||
| type: "snippet", | ||
| code: code.replace(/const\s+(\w+)\s*=\s*/g, "$1 = ") | ||
| }); | ||
| } else if (child.nodeName === "TEXT") { | ||
| const text = []; | ||
| recursivelyProcessTextSnippetJson(child.firstChild, text); | ||
| exercise.content.push({ | ||
| type: "text", | ||
| content: text.join("").trim() | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| const displayName = referenceStore[labelName].displayName; | ||
| return `#ex-${displayName}`; | ||
| writeTo.push(exercise); | ||
| }; |
There was a problem hiding this comment.
This pull request introduces breaking changes that will likely cause the application to crash at runtime.
- Removed
getIdForExerciseJson: The functiongetIdForExerciseJsonhas been removed from this file, but it is still imported and called injavascript/parseXmlJson.js(e.g., on line 219). This will cause aReferenceError. - Incompatible
processExerciseJsonsignature: The signature and implementation ofprocessExerciseJsonhave been completely changed.- It is called from
parseXmlJson.jswith an object as the second argument, but the new implementation expects an array and calls.push()on it, which will result in aTypeError. - The old function was called with more arguments which are now ignored.
- It is called from
- Loss of functionality: The previous implementation of
processExerciseJsonhandled exercise labels, solutions, and references. This logic has been removed entirely. This is a significant change that seems to go beyond the scope of replacingconstdeclarations and may have unintended side effects on how exercises are processed.
It appears that javascript/parseXmlJson.js and potentially other files need to be updated to be compatible with these changes. Due to the high risk of breaking the application, this is a critical issue.
| const code = snippet.join("").trim(); | ||
| exercise.content.push({ | ||
| type: "snippet", | ||
| code: code.replace(/const\s+(\w+)\s*=\s*/g, "$1 = ") |
There was a problem hiding this comment.
The regular expression /const\s+(\w+)\s*=\s*/g is not robust enough. It has two main issues:
- It only supports simple variable declarations (e.g.,
const x = ...) and will not work for declarations that use destructuring, such asconst { a, b } = .... - It can incorrectly modify occurrences of "const" inside string literals, as it is not anchored.
A safer and more robust approach is to anchor the regex to the beginning of a line and preserve leading whitespace. This will correctly handle all forms of const declarations without affecting string contents.
| code: code.replace(/const\s+(\w+)\s*=\s*/g, "$1 = ") | |
| code: code.replace(/^(\s*)const\s+/gm, "$1") |
| if (!referenceStore[labelName]) { | ||
| missingExerciseWarning(labelName); | ||
| return undefined; | ||
| import { getChildrenByTagName } from "../utilityFunctions.js"; |
Problem
The issue arises because
constdeclarations in JavaScript are statements, not expressions. When evaluated in a REPL or the Source Academy inline IDE, statements returnundefined. To show the expected output (e.g.,3or4), the examples should either use simple variable assignments (if the variable is already declared) or, more idiomatically for SICP JS, use expressions that evaluate to the desired value without relying on declaration side-effects.Severity:
lowFile:
The source XML/Markdown files containing the exercise content (likely located in thesourcedirectory or referenced by theprocessExercise*functions).Solution
The issue arises because
constdeclarations in JavaScript are statements, not expressions. When evaluated in a REPL or the Source Academy inline IDE, statements returnundefined. To show the expected output (e.g.,3or4), the examples should either use simple variable assignments (if the variable is already declared) or, more idiomatically for SICP JS, use expressions that evaluate to the desired value without relying on declaration side-effects.Changes
javascript/processingFunctions/processExerciseJson.js(modified)Testing
Closes #1072