mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 08:25:07 +00:00
LibJS: Allow anonymous functions as default exports
This requires a special case with names as the default function is supposed to have a unique name ("*default*" in our case) but when checked should have name "default".
This commit is contained in:
parent
0fc67ffd62
commit
9f661d20f7
5 changed files with 79 additions and 19 deletions
|
@ -2534,7 +2534,7 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FunctionNodeType>
|
template<typename FunctionNodeType>
|
||||||
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Optional<Position> const& function_start)
|
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u16 parse_options, Optional<Position> const& function_start)
|
||||||
{
|
{
|
||||||
auto rule_start = function_start.has_value()
|
auto rule_start = function_start.has_value()
|
||||||
? RulePosition { *this, *function_start }
|
? RulePosition { *this, *function_start }
|
||||||
|
@ -2572,7 +2572,9 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Op
|
||||||
parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
|
parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FunctionNodeType::must_have_name() || match_identifier())
|
if (parse_options & FunctionNodeParseOptions::HasDefaultExportName)
|
||||||
|
name = ExportStatement::local_name_for_default;
|
||||||
|
else if (FunctionNodeType::must_have_name() || match_identifier())
|
||||||
name = consume_identifier().flystring_value();
|
name = consume_identifier().flystring_value();
|
||||||
else if (is_function_expression && (match(TokenType::Yield) || match(TokenType::Await)))
|
else if (is_function_expression && (match(TokenType::Yield) || match(TokenType::Await)))
|
||||||
name = consume().flystring_value();
|
name = consume().flystring_value();
|
||||||
|
@ -2624,7 +2626,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Op
|
||||||
contains_direct_call_to_eval);
|
contains_direct_call_to_eval);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_length, u8 parse_options)
|
Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_length, u16 parse_options)
|
||||||
{
|
{
|
||||||
auto rule_start = push_start();
|
auto rule_start = push_start();
|
||||||
bool has_default_parameter = false;
|
bool has_default_parameter = false;
|
||||||
|
@ -4327,6 +4329,12 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
|
||||||
|
|
||||||
auto lookahead_token = next_token();
|
auto lookahead_token = next_token();
|
||||||
|
|
||||||
|
enum class MatchesFunctionDeclaration {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
WithoutName,
|
||||||
|
};
|
||||||
|
|
||||||
// Note: For some reason the spec here has declaration which can have no name
|
// Note: For some reason the spec here has declaration which can have no name
|
||||||
// and the rest of the parser is just not setup for that. With these
|
// and the rest of the parser is just not setup for that. With these
|
||||||
// hacks below we get through most things but we should probably figure
|
// hacks below we get through most things but we should probably figure
|
||||||
|
@ -4337,6 +4345,14 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
|
||||||
// `export default function() {}()`
|
// `export default function() {}()`
|
||||||
// Since we parse this as an expression you are immediately allowed to call it
|
// Since we parse this as an expression you are immediately allowed to call it
|
||||||
// which is incorrect and this should give a SyntaxError.
|
// which is incorrect and this should give a SyntaxError.
|
||||||
|
|
||||||
|
auto has_name = [&](Token const& token) {
|
||||||
|
if (token.type() != TokenType::ParenOpen)
|
||||||
|
return MatchesFunctionDeclaration::Yes;
|
||||||
|
|
||||||
|
return MatchesFunctionDeclaration::WithoutName;
|
||||||
|
};
|
||||||
|
|
||||||
auto match_function_declaration = [&] {
|
auto match_function_declaration = [&] {
|
||||||
// Hack part 1.
|
// Hack part 1.
|
||||||
// Match a function declaration with a name, since we have async and generator
|
// Match a function declaration with a name, since we have async and generator
|
||||||
|
@ -4347,32 +4363,40 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
|
||||||
|
|
||||||
if (current_type == TokenType::Function) {
|
if (current_type == TokenType::Function) {
|
||||||
if (lookahead_token.type() == TokenType::Asterisk)
|
if (lookahead_token.type() == TokenType::Asterisk)
|
||||||
return lookahead_lexer.next().type() != TokenType::ParenOpen; // function * <name>
|
return has_name(lookahead_lexer.next()); // function * [name]
|
||||||
else
|
else
|
||||||
return lookahead_token.type() != TokenType::ParenOpen; // function <name>
|
return has_name(lookahead_token); // function [name]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_type == TokenType::Async) {
|
if (current_type == TokenType::Async) {
|
||||||
if (lookahead_token.type() != TokenType::Function)
|
if (lookahead_token.type() != TokenType::Function)
|
||||||
return false;
|
return MatchesFunctionDeclaration::No;
|
||||||
|
|
||||||
if (lookahead_token.trivia_contains_line_terminator())
|
if (lookahead_token.trivia_contains_line_terminator())
|
||||||
return false;
|
return MatchesFunctionDeclaration::No;
|
||||||
|
|
||||||
auto lookahead_two_token = lookahead_lexer.next();
|
auto lookahead_two_token = lookahead_lexer.next();
|
||||||
if (lookahead_two_token.type() == TokenType::Asterisk)
|
if (lookahead_two_token.type() == TokenType::Asterisk)
|
||||||
return lookahead_lexer.next().type() != TokenType::ParenOpen; // async function * <name>
|
return has_name(lookahead_lexer.next()); // async function * [name]
|
||||||
else
|
else
|
||||||
return lookahead_two_token.type() != TokenType::ParenOpen; // async function <name>
|
return has_name(lookahead_two_token); // async function [name]
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return MatchesFunctionDeclaration::No;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (match_function_declaration()) {
|
if (auto matches_function = match_function_declaration(); matches_function != MatchesFunctionDeclaration::No) {
|
||||||
auto function_declaration = parse_function_node<FunctionDeclaration>();
|
|
||||||
|
auto function_declaration = parse_function_node<FunctionDeclaration>(
|
||||||
|
(matches_function == MatchesFunctionDeclaration::WithoutName ? FunctionNodeParseOptions::HasDefaultExportName : 0)
|
||||||
|
| FunctionNodeParseOptions::CheckForFunctionAndName);
|
||||||
|
|
||||||
m_state.current_scope_pusher->add_declaration(function_declaration);
|
m_state.current_scope_pusher->add_declaration(function_declaration);
|
||||||
local_name = function_declaration->name();
|
if (matches_function == MatchesFunctionDeclaration::WithoutName)
|
||||||
|
local_name = ExportStatement::local_name_for_default;
|
||||||
|
else
|
||||||
|
local_name = function_declaration->name();
|
||||||
|
|
||||||
expression = move(function_declaration);
|
expression = move(function_declaration);
|
||||||
} else if (match(TokenType::Class) && lookahead_token.type() != TokenType::CurlyOpen && lookahead_token.type() != TokenType::Extends) {
|
} else if (match(TokenType::Class) && lookahead_token.type() != TokenType::CurlyOpen && lookahead_token.type() != TokenType::Extends) {
|
||||||
// Hack part 2.
|
// Hack part 2.
|
||||||
|
@ -4633,7 +4657,7 @@ Parser::ForbiddenTokens Parser::ForbiddenTokens::forbid(std::initializer_list<To
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u8, Optional<Position> const&);
|
template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u16, Optional<Position> const&);
|
||||||
template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u8, Optional<Position> const&);
|
template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u16, Optional<Position> const&);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ enum class Associativity {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FunctionNodeParseOptions {
|
struct FunctionNodeParseOptions {
|
||||||
enum {
|
enum : u16 {
|
||||||
CheckForFunctionAndName = 1 << 0,
|
CheckForFunctionAndName = 1 << 0,
|
||||||
AllowSuperPropertyLookup = 1 << 1,
|
AllowSuperPropertyLookup = 1 << 1,
|
||||||
AllowSuperConstructorCall = 1 << 2,
|
AllowSuperConstructorCall = 1 << 2,
|
||||||
|
@ -36,6 +36,7 @@ struct FunctionNodeParseOptions {
|
||||||
IsArrowFunction = 1 << 5,
|
IsArrowFunction = 1 << 5,
|
||||||
IsGeneratorFunction = 1 << 6,
|
IsGeneratorFunction = 1 << 6,
|
||||||
IsAsyncFunction = 1 << 7,
|
IsAsyncFunction = 1 << 7,
|
||||||
|
HasDefaultExportName = 1 << 8,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,8 +56,8 @@ public:
|
||||||
NonnullRefPtr<Program> parse_program(bool starts_in_strict_mode = false);
|
NonnullRefPtr<Program> parse_program(bool starts_in_strict_mode = false);
|
||||||
|
|
||||||
template<typename FunctionNodeType>
|
template<typename FunctionNodeType>
|
||||||
NonnullRefPtr<FunctionNodeType> parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName, Optional<Position> const& function_start = {});
|
NonnullRefPtr<FunctionNodeType> parse_function_node(u16 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName, Optional<Position> const& function_start = {});
|
||||||
Vector<FunctionNode::Parameter> parse_formal_parameters(int& function_length, u8 parse_options = 0);
|
Vector<FunctionNode::Parameter> parse_formal_parameters(int& function_length, u16 parse_options = 0);
|
||||||
|
|
||||||
enum class AllowDuplicates {
|
enum class AllowDuplicates {
|
||||||
Yes,
|
Yes,
|
||||||
|
|
|
@ -460,7 +460,12 @@ ThrowCompletionOr<void> SourceTextModule::initialize_environment(VM& vm)
|
||||||
auto const& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
auto const& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
||||||
|
|
||||||
// 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
|
// 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
|
||||||
auto* function = ECMAScriptFunctionObject::create(realm(), function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());
|
// NOTE: Special case if the function is a default export of an anonymous function
|
||||||
|
// it has name "*default*" but internally should have name "default".
|
||||||
|
FlyString function_name = function_declaration.name();
|
||||||
|
if (function_name == ExportStatement::local_name_for_default)
|
||||||
|
function_name = "default"sv;
|
||||||
|
auto* function = ECMAScriptFunctionObject::create(realm(), function_name, function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());
|
||||||
|
|
||||||
// 2. Perform ! env.InitializeBinding(dn, fo).
|
// 2. Perform ! env.InitializeBinding(dn, fo).
|
||||||
MUST(environment->initialize_binding(vm, name, function));
|
MUST(environment->initialize_binding(vm, name, function));
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
try {
|
||||||
|
f();
|
||||||
|
} catch (e) {
|
||||||
|
if (!(e instanceof ReferenceError)) throw e;
|
||||||
|
if (!e.message.includes("bindingUsedInFunction")) throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bindingUsedInFunction = 0;
|
||||||
|
|
||||||
|
const immediateResult = f();
|
||||||
|
const immediateName = f.name + "";
|
||||||
|
|
||||||
|
import f from "./anon-func-decl-default-export.mjs";
|
||||||
|
export default function () {
|
||||||
|
return bindingUsedInFunction++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const postImportResult = f();
|
||||||
|
const postImportName = f.name + "";
|
||||||
|
|
||||||
|
export const passed =
|
||||||
|
immediateResult === 0 &&
|
||||||
|
postImportResult === 1 &&
|
||||||
|
bindingUsedInFunction === 2 &&
|
||||||
|
immediateName === "default" &&
|
||||||
|
postImportName === "default";
|
|
@ -202,6 +202,10 @@ describe("in- and exports", () => {
|
||||||
test("import lexical binding before import statement behaves as initialized but non mutable binding", () => {
|
test("import lexical binding before import statement behaves as initialized but non mutable binding", () => {
|
||||||
expectModulePassed("./accessing-lex-import-before-decl.mjs");
|
expectModulePassed("./accessing-lex-import-before-decl.mjs");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("exporting anonymous function", () => {
|
||||||
|
expectModulePassed("./anon-func-decl-default-export.mjs");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("loops", () => {
|
describe("loops", () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue