Skip to content

feat(editor): add syntax error detection for JS and Python code blocks#3407

Open
waleedlatif1 wants to merge 2 commits intostagingfrom
waleedlatif1/syntax-error-detection
Open

feat(editor): add syntax error detection for JS and Python code blocks#3407
waleedlatif1 wants to merge 2 commits intostagingfrom
waleedlatif1/syntax-error-detection

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • Add real-time syntax error detection for JavaScript (via acorn) and Python (bracket/string matching) in the code editor
  • Show errors as red warning icon with tooltip containing the error message (line, col, description)
  • Sanitize <Block.output> references and {{ENV_VAR}} placeholders before parsing to avoid false positives
  • Generalize existing JSON-only validation display to work for all code languages
  • Add 32 tests covering sanitization, JS validation, and Python validation

Type of Change

  • New feature

Testing

  • Added 32 unit tests for sanitizeForParsing, validateJavaScript, validatePython
  • Existing 20 sub-block tests still pass
  • Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 4, 2026 6:41am

Request Review

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

@cursor
Copy link

cursor bot commented Mar 4, 2026

Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR adds real-time syntax error detection for JavaScript (using acorn) and Python (bracket/string matching) to the code editor panel, generalising the previously JSON-only validation warning icon to all code languages. Block references and env-var placeholders are sanitised before parsing to prevent false positives.

Key changes:

  • New sanitizeForParsing, validateJavaScript, and validatePython utilities in utils.ts with 32 unit tests
  • Code component now computes a syntaxError via useMemo and passes it through the updated onValidationChange(isValid, errorMessage) signature
  • SubBlockComponent replaces isValidJson state with isValidCode + codeErrorMessage, showing a red AlertTriangle tooltip for any code language on validation failure
  • acorn@8.16.0 added as a runtime dependency (necessary for in-browser JS parsing)

Issues found:

  • handleValidationChange in sub-block.tsx is not wrapped in useCallback, so each state update creates a new function reference, causing the memoized Code child to re-render and re-fire its validation useEffect unnecessarily — one extra cycle before React's same-value bail-out stops it
  • validateJavaScript reports error line numbers based on where acorn is confused by the async IIFE wrapper, which for unclosed-delimiter errors can point to a line beyond the end of the user's actual code
  • validatePython does not increment the line counter when a backslash-continuation \ + \n is encountered inside a string, causing off-by-one line numbers for errors reported after such a continuation

Confidence Score: 4/5

  • Safe to merge with one recommended fix — the missing useCallback on handleValidationChange should be addressed to prevent a superfluous re-render cycle on every validation-state change.
  • The core feature is well-implemented and tested. The Python validator is intentionally heuristic (bracket/string only), clearly documented, and the test suite covers the important cases. The only functional concern is the missing useCallback that causes one unnecessary extra render+timer cycle when code transitions to an error state; it does not cause an infinite loop because React bails out on same-value setState, but it is still a correctness and performance concern. The line-number inaccuracies are minor UX issues.
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx — handleValidationChange needs useCallback

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils.ts Adds sanitizeForParsing, validateJavaScript (via acorn, dual-strategy parse), and validatePython (streaming bracket/string checker). Minor issues: JS error line numbers for unclosed delimiters can point past the last user line; Python line tracking skips increments on backslash-continuation newlines.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx Replaces isValidJson state with isValidCode+codeErrorMessage, generalising the warning icon to all code languages. handleValidationChange is not wrapped in useCallback, causing unnecessary re-renders of the memoized Code child and a spurious extra validation cycle on every error state change.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/code/code.tsx Adds a syntaxError useMemo that calls sanitizeForParsing + validateJavaScript/validatePython based on effectiveLanguage, and exposes the error message through the updated onValidationChange signature. Logic is correct and cleanly integrated.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils.test.ts Adds 32 focused unit tests for all three new utility functions covering happy paths, edge cases (escaped quotes, triple-quoted strings, comments, empty input, sanitized references). Coverage is thorough.
apps/sim/package.json Adds acorn@8.16.0 as a runtime dependency (correct, since it runs in the browser for real-time JS parsing).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User types code] --> B{effectiveLanguage}
    B -->|json| C[JSON.parse]
    C -->|valid| E[isValidJson = true]
    C -->|invalid| F[isValidJson = false]

    B -->|javascript or python| G[sanitizeForParsing\nReplace ENV_VAR and Block references\nwith placeholder identifiers]

    G -->|javascript| J[validateJavaScript via acorn\nTry module parse then async IIFE wrap]
    G -->|python| P[validatePython\nBracket stack and string scanner]

    J -->|no error| L[syntaxError = null]
    J -->|error| N[syntaxError = adjusted line/col message]
    P -->|no error| L
    P -->|error| N

    E & L --> R{isValid check}
    F & N --> R
    R -->|valid| S[onValidationChange true, null - immediate]
    R -->|invalid| T[onValidationChange false, errorMsg - 150ms debounce]

    T --> U[SubBlockComponent\nupdates isValidCode and codeErrorMessage]
    S --> U
    U --> V{code block and not valid}
    V -->|yes| W[AlertTriangle icon with tooltip]
    V -->|no| X[No icon shown]
Loading

Last reviewed commit: 136d5b5

Comment on lines +485 to 488
const handleValidationChange = (isValid: boolean, errorMessage?: string | null): void => {
setIsValidCode(isValid)
setCodeErrorMessage(errorMessage ?? null)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleValidationChange not memoized — triggers unnecessary re-renders

handleValidationChange is a plain function created on every render. Because it is passed as the onValidationChange prop to Code (which is a memo component with default shallow comparison), every state update inside SubBlockComponent — including those caused by handleValidationChange itself — creates a new function reference, which then causes Code to re-render and re-fire the useEffect that has onValidationChange in its dependency array. This sets a fresh 150 ms timer for invalid code, and the cycle repeats once more before React's bail-out for equal state values finally stops it.

Wrapping the callback in useCallback with an empty dependency array (the setState functions from useState are stable) fixes this:

Suggested change
const handleValidationChange = (isValid: boolean, errorMessage?: string | null): void => {
setIsValidCode(isValid)
setCodeErrorMessage(errorMessage ?? null)
}
const handleValidationChange = useCallback((isValid: boolean, errorMessage?: string | null): void => {
setIsValidCode(isValid)
setCodeErrorMessage(errorMessage ?? null)
}, [])

Comment on lines +74 to +78
const match = msg.match(/\((\d+):(\d+)\)/)
if (match) {
const adjustedLine = Number(match[1]) - 1
if (adjustedLine < 1) return null
return `Syntax error at line ${adjustedLine}, col ${match[2]}: ${msg.replace(/\s*\(\d+:\d+\)/, '')}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inaccurate line numbers for unclosed-delimiter errors

When the parser fails because a delimiter is never closed (e.g. function foo() { with no closing }), acorn reports the error at the point where it is surprised — typically the wrapper's })() closing line, which may be two or more lines after the user's actual last line. For example, the 1-line snippet function foo() { produces a wrapped 4-line program; acorn reports the error somewhere around line 4, so adjustedLine becomes 3, pointing the user to a non-existent line 3. The column is also taken from the wrapper's close, not from the unmatched opener.

This is a known limitation of the wrapping approach, but it may confuse users who see "line 3" for a 1-line snippet. A best-effort mitigation is to clamp the reported line to the actual number of user code lines before surfacing it:

const userLineCount = code.split('\n').length
const clampedLine = Math.min(adjustedLine, userLineCount)
return `Syntax error at line ${clampedLine}, col ${match[2]}: ${msg.replace(/\s*\(\d+:\d+\)/, '')}`

Comment on lines +135 to +138
while (i < code.length && code[i] !== ch && code[i] !== '\n') {
if (code[i] === '\\') i++
i++
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\ line-continuation in strings skips line counter increment

When the single-line string scanner encounters \ followed by \n (a backslash-continuation), it advances i twice and skips the newline without incrementing line. Any bracket-mismatch reported on a subsequent code line will have its line number off by one for each backslash-continuation encountered earlier. This is a minor inaccuracy in error reporting.

To fix, increment line when the skipped character is \n:

while (i < code.length && code[i] !== ch && code[i] !== '\n') {
  if (code[i] === '\\') {
    i++
    if (i < code.length && code[i] === '\n') line++
  }
  i++
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant