summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibJS/Parser.cpp')
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp87
1 files changed, 84 insertions, 3 deletions
diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp
index 03fecfd13b..b00f119e6b 100644
--- a/Userland/Libraries/LibJS/Parser.cpp
+++ b/Userland/Libraries/LibJS/Parser.cpp
@@ -850,6 +850,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
NonnullRefPtrVector<ClassElement> elements;
RefPtr<Expression> super_class;
RefPtr<FunctionExpression> constructor;
+ HashTable<FlyString> found_private_names;
String class_name = expect_class_name || match_identifier() || match(TokenType::Yield) || match(TokenType::Await)
? consume_identifier_reference().value().to_string()
@@ -881,6 +882,13 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
consume(TokenType::CurlyOpen);
+ HashTable<StringView> referenced_private_names;
+ HashTable<StringView>* outer_referenced_private_names = m_state.referenced_private_names;
+ m_state.referenced_private_names = &referenced_private_names;
+ ScopeGuard restore_private_name_table = [&] {
+ m_state.referenced_private_names = outer_referenced_private_names;
+ };
+
while (!done() && !match(TokenType::CurlyClose)) {
RefPtr<Expression> property_key;
bool is_static = false;
@@ -899,7 +907,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
StringView name;
- if (match_property_key()) {
+ if (match_property_key() || match(TokenType::PrivateIdentifier)) {
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
if (match(TokenType::Identifier)) {
consume();
@@ -923,12 +931,49 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
}
- if (match_property_key()) {
+ if (match_property_key() || match(TokenType::PrivateIdentifier)) {
switch (m_state.current_token.type()) {
case TokenType::Identifier:
name = consume().value();
property_key = create_ast_node<StringLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
break;
+ case TokenType::PrivateIdentifier:
+ name = consume().value();
+ if (name == "#constructor")
+ syntax_error("Private property with name '#constructor' is not allowed");
+
+ if (method_kind != ClassMethod::Kind::Method) {
+ // It is a Syntax Error if PrivateBoundIdentifiers of ClassElementList contains any duplicate entries,
+ // unless the name is used once for a getter and once for a setter and in no other entries,
+ // and the getter and setter are either both static or both non-static.
+
+ for (auto& element : elements) {
+ auto private_name = element.private_bound_identifier();
+ if (!private_name.has_value() || private_name.value() != name)
+ continue;
+
+ if (element.class_element_kind() != ClassElement::ElementKind::Method
+ || element.is_static() != is_static) {
+ syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
+ break;
+ }
+
+ VERIFY(is<ClassMethod>(element));
+ auto& class_method_element = static_cast<ClassMethod const&>(element);
+
+ if (class_method_element.kind() == ClassMethod::Kind::Method || class_method_element.kind() == method_kind) {
+ syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
+ break;
+ }
+ }
+
+ found_private_names.set(name);
+ } else if (found_private_names.set(name) != AK::HashSetResult::InsertedNewEntry) {
+ syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
+ }
+
+ property_key = create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
+ break;
case TokenType::StringLiteral: {
auto string_literal = parse_string_literal(consume());
name = string_literal->value();
@@ -1064,6 +1109,16 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
}
+ // We could be in a subclass defined within the main class so must move all non declared private names to outer.
+ for (auto& private_name : referenced_private_names) {
+ if (found_private_names.contains(private_name))
+ continue;
+ if (outer_referenced_private_names)
+ outer_referenced_private_names->set(private_name);
+ else // FIXME: Make these error appear in the appropriate places.
+ syntax_error(String::formatted("Reference to undeclared private field or method '{}'", private_name));
+ }
+
return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements));
}
@@ -1264,6 +1319,11 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
if (is<Identifier>(*rhs) && m_state.strict_mode) {
syntax_error("Delete of an unqualified identifier in strict mode.", rhs_start);
}
+ if (is<MemberExpression>(*rhs)) {
+ auto& member_expression = static_cast<MemberExpression const&>(*rhs);
+ if (member_expression.ends_in_private_name())
+ syntax_error("Private fields cannot be deleted");
+ }
return create_ast_node<UnaryExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UnaryOp::Delete, move(rhs));
}
default:
@@ -1711,8 +1771,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity);
case TokenType::Period:
consume();
- if (!match_identifier_name())
+ if (match(TokenType::PrivateIdentifier)) {
+ if (!is_private_identifier_valid())
+ syntax_error(String::formatted("Reference to undeclared private field or method '{}'", m_state.current_token.value()));
+ else if (is<SuperExpression>(*lhs))
+ syntax_error(String::formatted("Cannot access private field or method '{}' on super", m_state.current_token.value()));
+
+ return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
+ } else if (!match_identifier_name()) {
expected("IdentifierName");
+ }
+
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
case TokenType::BracketOpen: {
consume(TokenType::BracketOpen);
@@ -1780,6 +1849,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
}
}
+bool Parser::is_private_identifier_valid() const
+{
+ VERIFY(match(TokenType::PrivateIdentifier));
+ if (!m_state.referenced_private_names)
+ return false;
+
+ // We might not have hit the declaration yet so class will check this in the end
+ m_state.referenced_private_names->set(m_state.current_token.value());
+ return true;
+}
+
RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expression)
{
VERIFY(is<ArrayExpression>(expression) || is<ObjectExpression>(expression));
@@ -3042,6 +3122,7 @@ bool Parser::match_expression() const
|| type == TokenType::TemplateLiteralStart
|| type == TokenType::NullLiteral
|| match_identifier()
+ || (type == TokenType::PrivateIdentifier && next_token().type() == TokenType::In)
|| type == TokenType::Await
|| type == TokenType::New
|| type == TokenType::Class