Skip to content

Commit

Permalink
Allow usage of this and arguments in nested function expression (#…
Browse files Browse the repository at this point in the history
…74179)

In #73059 we added build-time checks for the forbidden usage of `this`
and `arguments` in server functions. A nested function expression is
however allowed to use these expressions.

fixes #74181

To add a bit more context: `this` is forbidden in server actions because
the Next.js compiler hoists inline server actions out of their original
location into the module scope, so that they can be imported and invoked
by the action handler. Due to this hoisting, the `this` context gets
lost and is not available to the server action.

To prevent surprising runtime errors for such cases, we emit a build
error to provide feedback to developers as early as possible.

However, nested function declarations or function expressions create a
new `this` context, and in those it's allowed to access `this` and
`arguments`.

For consistency, and to prevent surprises when refactoring server
actions, we apply the same rules for module-level server actions.
  • Loading branch information
unstubbable authored Dec 20, 2024
1 parent c004041 commit 6fa146a
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -932,11 +932,13 @@ impl<C: Comments> VisitMut for ServerActions<C> {
}

fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
if let Some(ident) = &f.ident {
self.arrow_or_fn_expr_ident = Some(ident.clone());
}
f.visit_mut_children_with(self);
self.this_status = old_this_status;
self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
}

Expand Down Expand Up @@ -1264,6 +1266,12 @@ impl<C: Comments> VisitMut for ServerActions<C> {
self.in_exported_expr = old_in_exported_expr;
}

fn visit_mut_class(&mut self, n: &mut Class) {
let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
n.visit_mut_children_with(self);
self.this_status = old_this_status;
}

fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
if let ClassMember::Method(ClassMethod {
is_abstract: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use server'

import { db } from './database'

export const createItem = async (title) => {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run(
`INSERT INTO items (title) VALUES ($title)`,
{ $title: title },
function () {
// arguments is allowed here
const [err] = arguments

if (err) {
reject(err)
}

// this is allowed here
resolve(this.lastID)
}
)
})
})
}

export async function test() {
const MyClass = class {
x = 1
foo() {
// this is allowed here
return this.x
}
bar = () => {
// this is allowed here
return this.x
}
}
const myObj = new MyClass()
return myObj.foo() + myObj.bar()
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6fa146a

Please sign in to comment.