1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-23 18:55:08 +00:00

LibJS: Rewrite Parser.parse_object_expression()

This rewrite drastically increases the accuracy of object literals.
Additionally, an "assertIsSyntaxError" function has been added to
test-common.js to assist in testing syntax errors.
This commit is contained in:
Matthew Olsson 2020-05-28 22:50:06 -07:00 committed by Andreas Kling
parent 05b7fec517
commit ab576e610c
5 changed files with 138 additions and 59 deletions

View file

@ -796,7 +796,7 @@ public:
Spread, Spread,
}; };
ObjectProperty(NonnullRefPtr<Expression> key, NonnullRefPtr<Expression> value, Type property_type) ObjectProperty(NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type)
: m_key(move(key)) : m_key(move(key))
, m_value(move(value)) , m_value(move(value))
, m_property_type(property_type) , m_property_type(property_type)
@ -804,7 +804,11 @@ public:
} }
const Expression& key() const { return m_key; } const Expression& key() const { return m_key; }
const Expression& value() const { return m_value; } const Expression& value() const
{
ASSERT(m_value);
return *m_value;
}
Type type() const { return m_property_type; } Type type() const { return m_property_type; }
@ -815,7 +819,7 @@ private:
virtual const char* class_name() const override { return "ObjectProperty"; } virtual const char* class_name() const override { return "ObjectProperty"; }
NonnullRefPtr<Expression> m_key; NonnullRefPtr<Expression> m_key;
NonnullRefPtr<Expression> m_value; RefPtr<Expression> m_value;
Type m_property_type; Type m_property_type;
}; };

View file

@ -516,71 +516,121 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression() NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
{ {
NonnullRefPtrVector<ObjectProperty> properties;
consume(TokenType::CurlyOpen); consume(TokenType::CurlyOpen);
auto property_type = ObjectProperty::Type::KeyValue; NonnullRefPtrVector<ObjectProperty> properties;
ObjectProperty::Type property_type;
while (!done() && !match(TokenType::CurlyClose)) { auto match_property_key = [&]() -> bool {
RefPtr<Expression> property_key; auto type = m_parser_state.m_current_token.type();
RefPtr<Expression> property_value; return match_identifier_name()
auto need_colon = true; || type == TokenType::BracketOpen
|| type == TokenType::StringLiteral
|| type == TokenType::NumericLiteral;
};
if (match_identifier_name()) { auto parse_property_key = [&]() -> NonnullRefPtr<Expression> {
auto identifier = consume().value(); if (match(TokenType::StringLiteral)) {
if (property_type == ObjectProperty::Type::KeyValue) { return parse_string_literal(consume());
if (identifier == "get" && !match(TokenType::ParenOpen)) {
property_type = ObjectProperty::Type::Getter;
continue;
}
if (identifier == "set" && !match(TokenType::ParenOpen)) {
property_type = ObjectProperty::Type::Setter;
continue;
}
}
property_key = create_ast_node<StringLiteral>(identifier);
property_value = create_ast_node<Identifier>(identifier);
need_colon = false;
} else if (match(TokenType::StringLiteral)) {
property_key = parse_string_literal(consume());
} else if (match(TokenType::NumericLiteral)) { } else if (match(TokenType::NumericLiteral)) {
property_key = create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value()); return create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
} else if (match(TokenType::BracketOpen)) { } else if (match(TokenType::BracketOpen)) {
consume(TokenType::BracketOpen); consume(TokenType::BracketOpen);
property_key = parse_expression(0); auto result = parse_expression(0);
consume(TokenType::BracketClose); consume(TokenType::BracketClose);
} else if (match(TokenType::TripleDot)) { return result;
consume(TokenType::TripleDot);
property_key = create_ast_node<SpreadExpression>(parse_expression(2));
property_value = property_key;
need_colon = false;
property_type = ObjectProperty::Type::Spread;
} else { } else {
if (property_type != ObjectProperty::Type::Getter && property_type != ObjectProperty::Type::Setter) { if (!match_identifier_name())
syntax_error(String::format("Unexpected token %s as member in object initialization. Expected a numeric literal, string literal or identifier", m_parser_state.m_current_token.name())); expected("IdentifierName");
consume(); return create_ast_node<StringLiteral>(consume().value());
}
};
auto skip_to_next_property = [&] {
while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen))
consume();
};
while (!done() && !match(TokenType::CurlyClose)) {
property_type = ObjectProperty::Type::KeyValue;
RefPtr<Expression> property_name;
RefPtr<Expression> property_value;
if (match(TokenType::TripleDot)) {
consume();
property_name = parse_expression(4);
properties.append(create_ast_node<ObjectProperty>(*property_name, nullptr, ObjectProperty::Type::Spread));
if (!match(TokenType::Comma))
break;
consume(TokenType::Comma);
continue;
}
if (match(TokenType::Identifier)) {
auto identifier = consume().value();
if (identifier == "get" && match_property_key()) {
property_type = ObjectProperty::Type::Getter;
property_name = parse_property_key();
} else if (identifier == "set" && match_property_key()) {
property_type = ObjectProperty::Type::Setter;
property_name = parse_property_key();
} else {
property_name = create_ast_node<StringLiteral>(identifier);
property_value = create_ast_node<Identifier>(identifier);
}
} else {
property_name = parse_property_key();
}
if (property_type == ObjectProperty::Type::Getter || property_type == ObjectProperty::Type::Setter) {
if (!match(TokenType::ParenOpen)) {
syntax_error(
"Expected '(' for object getter or setter property",
m_parser_state.m_current_token.line_number(),
m_parser_state.m_current_token.line_column()
);
skip_to_next_property();
continue;
}
}
if (match(TokenType::ParenOpen)) {
ASSERT(property_name);
auto function = parse_function_node<FunctionExpression>(false);
auto arg_count = function->parameters().size();
if (property_type == ObjectProperty::Type::Getter && arg_count != 0) {
syntax_error(
"Object getter property must have no arguments",
m_parser_state.m_current_token.line_number(),
m_parser_state.m_current_token.line_column()
);
skip_to_next_property();
continue;
}
if (property_type == ObjectProperty::Type::Setter && arg_count != 1) {
syntax_error(
"Object setter property must have one argument",
m_parser_state.m_current_token.line_number(),
m_parser_state.m_current_token.line_column()
);
skip_to_next_property();
continue; continue;
} }
auto name = property_type == ObjectProperty::Type::Getter ? "get" : "set"; properties.append(create_ast_node<ObjectProperty>(*property_name, function, property_type));
property_key = create_ast_node<StringLiteral>(name); } else if (match(TokenType::Colon)) {
property_value = create_ast_node<Identifier>(name); consume();
need_colon = false; ASSERT(property_name);
properties.append(create_ast_node<ObjectProperty>(*property_name, parse_expression(2), property_type));
} else {
ASSERT(property_name);
ASSERT(property_value);
properties.append(create_ast_node<ObjectProperty>(*property_name, *property_value, property_type));
} }
if (property_type != ObjectProperty::Type::Spread && match(TokenType::ParenOpen)) {
property_value = parse_function_node<FunctionExpression>(false);
} else if (need_colon || match(TokenType::Colon)) {
consume(TokenType::Colon);
property_value = parse_expression(2);
}
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value, property_type);
properties.append(property);
property_type = ObjectProperty::Type::KeyValue;
if (!match(TokenType::Comma)) if (!match(TokenType::Comma))
break; break;
consume(TokenType::Comma); consume(TokenType::Comma);
} }
@ -1001,19 +1051,21 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
} }
template<typename FunctionNodeType> template<typename FunctionNodeType>
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_keyword) NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_function_and_name)
{ {
ScopePusher scope(*this, ScopePusher::Var); ScopePusher scope(*this, ScopePusher::Var);
if (needs_function_keyword) if (check_for_function_and_name)
consume(TokenType::Function); consume(TokenType::Function);
String name; String name;
if (FunctionNodeType::must_have_name()) { if (check_for_function_and_name) {
name = consume(TokenType::Identifier).value(); if (FunctionNodeType::must_have_name()) {
} else {
if (match(TokenType::Identifier))
name = consume(TokenType::Identifier).value(); name = consume(TokenType::Identifier).value();
} else {
if (match(TokenType::Identifier))
name = consume(TokenType::Identifier).value();
}
} }
consume(TokenType::ParenOpen); consume(TokenType::ParenOpen);
Vector<FunctionNode::Parameter> parameters; Vector<FunctionNode::Parameter> parameters;

View file

@ -46,7 +46,7 @@ public:
NonnullRefPtr<Program> parse_program(); NonnullRefPtr<Program> parse_program();
template<typename FunctionNodeType> template<typename FunctionNodeType>
NonnullRefPtr<FunctionNodeType> parse_function_node(bool need_function_keyword = true); NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true);
NonnullRefPtr<Statement> parse_statement(); NonnullRefPtr<Statement> parse_statement();
NonnullRefPtr<BlockStatement> parse_block_statement(); NonnullRefPtr<BlockStatement> parse_block_statement();

View file

@ -66,6 +66,17 @@ try {
assert(a[2] === 3); assert(a[2] === 3);
assert(o4.test === undefined); assert(o4.test === undefined);
assertIsSyntaxError("({ get ...foo })");
assertIsSyntaxError("({ get... foo })");
assertIsSyntaxError("({ get foo })");
assertIsSyntaxError("({ get foo: bar })");
assertIsSyntaxError("({ get [foo]: bar })");
assertIsSyntaxError("({ get ...[foo] })");
assertIsSyntaxError("({ get foo(bar) {} })");
assertIsSyntaxError("({ set foo() {} })");
assertIsSyntaxError("({ set foo(bar, baz) {} })");
assertIsSyntaxError("({ ...foo: bar })");
console.log("PASS"); console.log("PASS");
} catch (e) { } catch (e) {
console.log("FAIL: " + e); console.log("FAIL: " + e);

View file

@ -50,6 +50,18 @@ function assertThrowsError(testFunction, options) {
} }
} }
/**
* Ensures the provided JavaScript source code results in a SyntaxError
* @param {string} source The JavaScript source code to compile
*/
function assertIsSyntaxError(source) {
assertThrowsError(() => {
new Function(source)();
}, {
error: SyntaxError,
});
}
/** /**
* Ensures the provided arrays contain exactly the same items. * Ensures the provided arrays contain exactly the same items.
* @param {Array} a First array * @param {Array} a First array