Skip to content

Conversation

@tt-a1i
Copy link

@tt-a1i tt-a1i commented Dec 14, 2025

Summary

  • Fix hasReturn function to correctly detect arrow function expressions in minified code
  • Add test cases for beforeHandle with various arrow function formats

Problem

The hasReturn function in compose.ts used hardcoded character positions to detect arrow function expressions:

// Old code assumed format: `) => expr`
fnLiteral.charCodeAt(parenthesisEnd + 2) === 61 &&  // '=' at position +2
fnLiteral.charCodeAt(parenthesisEnd + 5) !== 123    // not '{' at position +5

This broke when code was minified (e.g., by tsx) to )=>expr (no spaces), because:

  • In ) => expr: position +2 is =
  • In )=>expr: position +2 is >

This caused beforeHandle and other lifecycle hooks to be incorrectly identified as "not having a return", leading to them being silently removed during AOT compilation.

Solution

Use indexOf('=>') to find the arrow operator regardless of spacing:

const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd)
if (arrowIndex !== -1) {
    // Skip whitespace after `=>`
    let afterArrow = arrowIndex + 2
    while (fnLiteral.charCodeAt(afterArrow) === 32) afterArrow++
    
    // Check if not a block
    if (fnLiteral.charCodeAt(afterArrow) !== 123) {
        return true  // Direct return expression
    }
}

Test Plan

  • Added test file test/core/before-handle-arrow.test.ts with 7 test cases
  • All existing tests pass (1423 pass, 2 unrelated flaky failures)
  • Manually verified with minified function strings

Fixes #1617

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests for arrow-function beforeHandle hooks, covering async, block-body, expression returns, multiple hooks, minified variants, and end-to-end server behavior.
  • Refactor

    • Improved detection and handling of direct-return arrow functions in beforeHandle hooks and more robust input coercion for non-object inputs.

✏️ Tip: You can customize this high-level summary in your review settings.

The `hasReturn` function used hardcoded character positions to detect
arrow function expressions, assuming the format `) => expr`. This broke
when code was minified to `)=>expr` (no spaces), causing beforeHandle
and other lifecycle hooks to be incorrectly removed during AOT compilation.

The fix:
- Use `indexOf('=>')` to find the arrow operator regardless of spacing
- Skip whitespace after `=>` before checking for block braces
- Correctly identify direct return expressions in both minified and
  unminified code

Fixes elysiajs#1617
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

Walkthrough

hasReturn was changed to coerce non-object inputs to string via toString() and to detect direct-arrow-function returns by searching for the arrow token (=>) then checking the first non-whitespace character after it; tests for many arrow-function forms in beforeHandle hooks were added.

Changes

Cohort / File(s) Summary
Core logic update
src/compose.ts
Coerce non-object inputs to string with toString() and replace prior char-code based direct-arrow return check with string-based detection: find =>, skip whitespace, and treat as direct return if the next non-whitespace char is not {.
New tests for arrow beforeHandle
test/core/before-handle-arrow.test.ts
Add test suite covering many arrow-function variants in beforeHandle (expression, async, block body, minified/no-space, multiple hooks, complex expressions) including an end-to-end HTTP listen/stop scenario.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review focus:
    • src/compose.ts: validate string-based parsing handles edge cases (comments, line breaks, default params) and doesn't regress other function shapes.
    • test/core/before-handle-arrow.test.ts: confirm tests accurately exercise AOT-relevant code paths and server lifecycle cleanup.

Poem

🐰
I chewed the code through night and day,
Found arrows hiding far away.
A little toString(), sniff and peek,
Now beforeHandle won't go meek.
Hoppity hop — the tests all say hooray! ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: fixing hasReturn detection to handle minified arrow functions, which is the primary objective of the PR.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #1617: replaces brittle character-position checks with indexOf-based arrow operator detection, handles both minified and spaced arrow functions, preserves beforeHandle hooks during AOT compilation, and includes comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives: the hasReturn detection fix in src/compose.ts and the new test suite in test/core/before-handle-arrow.test.ts address the bug described in issue #1617 without introducing unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/compose.ts (1)

409-432: Consider supporting tabs and newlines after arrow operator.

The whitespace-skipping logic only checks for spaces (char code 32) but not tabs (9) or newlines (10, 13). While minified code typically uses spaces, formatted code with tabs or newlines after => would not be detected correctly.

Apply this diff to handle all whitespace characters:

 	// Check for arrow function expression (direct return without braces)
 	// Handle both `) => x` and `)=>x` formats (with or without spaces)
 	const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd)
 
 	if (arrowIndex !== -1) {
-		// Skip any whitespace after `=>`
+		// Skip any whitespace after `=>` (space, tab, newline, carriage return)
 		let afterArrow = arrowIndex + 2
 		while (
 			afterArrow < fnLiteral.length &&
-			fnLiteral.charCodeAt(afterArrow) === 32
+			(fnLiteral.charCodeAt(afterArrow) === 32 ||  // space
+			 fnLiteral.charCodeAt(afterArrow) === 9 ||   // tab
+			 fnLiteral.charCodeAt(afterArrow) === 10 ||  // newline
+			 fnLiteral.charCodeAt(afterArrow) === 13)    // carriage return
 		) {
 			afterArrow++
 		}

Alternatively, use a regex-based approach:

 	const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd)
 
 	if (arrowIndex !== -1) {
-		// Skip any whitespace after `=>`
-		let afterArrow = arrowIndex + 2
-		while (
-			afterArrow < fnLiteral.length &&
-			fnLiteral.charCodeAt(afterArrow) === 32
-		) {
-			afterArrow++
-		}
+		// Skip any whitespace after `=>` using regex
+		const afterArrowMatch = fnLiteral.slice(arrowIndex + 2).match(/^\s*/)
+		const afterArrow = arrowIndex + 2 + (afterArrowMatch ? afterArrowMatch[0].length : 0)
 
 		// If the first non-whitespace char after `=>` is not `{`, it's a direct return
 		if (
test/core/before-handle-arrow.test.ts (1)

1-132: Add explicit test for minified arrow function format.

While the test suite is comprehensive, it would benefit from an explicit test case that reproduces the exact minified format mentioned in the issue: ()=>expr without spaces. This would serve as a regression test for the specific bug that was fixed.

Add this test case:

it('should handle minified arrow function without spaces', async () => {
	let beforeHandleCalled = false

	// Simulate minified code: ()=>expr with no spaces
	const minifiedBeforeHandle = new Function(
		'beforeHandleCalled',
		'return ()=>(beforeHandleCalled.value=true,undefined)'
	)({ value: false })

	const app = new Elysia()
		.get('/test', () => 'ok', {
			beforeHandle: minifiedBeforeHandle
		})

	const response = await app.handle(new Request('http://localhost/test'))
	expect(response.status).toBe(200)
	// Note: This test structure needs adjustment to check the flag properly
	// as the closure won't work as written above. Consider using context instead.
})

Or simpler, using the context pattern from line 62:

it('should handle minified arrow expression format', async () => {
	const state = { called: false }
	
	const app = new Elysia()
		.get('/test', () => 'ok', {
			// Minified format without spaces: ()=>expr
			beforeHandle: ()=>(state.called=true)
		})

	const response = await app.handle(new Request('http://localhost/test'))
	expect(response.status).toBe(200)
	expect(state.called).toBe(true)
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 218c12a and 1349e7f.

📒 Files selected for processing (2)
  • src/compose.ts (1 hunks)
  • test/core/before-handle-arrow.test.ts (1 hunks)
🔇 Additional comments (2)
src/compose.ts (1)

405-405: LGTM: String coercion is correct.

The change to call v.toString() for the non-object, non-string branch is correct—the function needs the string representation to analyze arrow function syntax.

test/core/before-handle-arrow.test.ts (1)

5-132: LGTM: Comprehensive test coverage for arrow function variants.

The test suite thoroughly validates the fix across multiple scenarios: sync/async arrows, early returns, complex expressions, multiple hooks, and live server integration. This provides strong regression protection.

- Support tab, newline, and carriage return in whitespace skip logic
- Add regression test with truly minified arrow function (no spaces)
- Clean up unused variables in test file
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/compose.ts (2)

401-406: Simplify fnLiteral coercion (redundant ternary).

Both branches call toString(), so the typeof v === 'string' check no longer does anything.

- const fnLiteral = isObject
-   ? v.fn.toString()
-   : typeof v === 'string'
-     ? v.toString()
-     : v.toString()
+ const fnLiteral = isObject ? v.fn.toString() : v.toString()

407-436: Tighten arrow detection to avoid matching => inside non-arrow function bodies.

As-is, any => after the first ) (including nested callbacks) can flip hasReturn to true. This is usually benign, but you can cheaply reduce false positives by ensuring the => appears before the first { (i.e., in the “signature” region).

  const parenthesisEnd = fnLiteral.indexOf(')')
+ const scanStart = parenthesisEnd === -1 ? 0 : parenthesisEnd
+ const braceIndex = fnLiteral.indexOf('{', scanStart)

  // Check for arrow function expression (direct return without braces)
  // Handle both `) => x` and `)=>x` formats (with or without spaces)
- const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd)
+ const arrowIndex = fnLiteral.indexOf('=>', scanStart)

- if (arrowIndex !== -1) {
+ if (arrowIndex !== -1 && (braceIndex === -1 || arrowIndex < braceIndex)) {
     // Skip any whitespace after `=>` (space, tab, newline, carriage return)
     let afterArrow = arrowIndex + 2
     let charCode: number
     while (
       afterArrow < fnLiteral.length &&
       ((charCode = fnLiteral.charCodeAt(afterArrow)) === 32 || // space
         charCode === 9 || // tab
         charCode === 10 || // newline
         charCode === 13) // carriage return
     ) {
       afterArrow++
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1349e7f and 7a9761b.

📒 Files selected for processing (2)
  • src/compose.ts (1 hunks)
  • test/core/before-handle-arrow.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/core/before-handle-arrow.test.ts

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.

AOT compilation removes beforeHandle when using arrow function expression

1 participant