diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 0a68b154c8..c57abaea10 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -715,6 +715,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob ASSERT(class_constructor_value.is_function() && class_constructor_value.as_function().is_script_function()); ScriptFunction* class_constructor = static_cast(&class_constructor_value.as_function()); + class_constructor->set_is_class_constructor(); Value super_constructor = js_undefined(); if (!m_super_class.is_null()) { super_constructor = m_super_class->execute(interpreter, global_object); diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h index 23792d643b..7192c80061 100644 --- a/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Libraries/LibJS/Runtime/ErrorTypes.h @@ -36,6 +36,7 @@ M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \ M(BigIntIntArgument, "BigInt argument must be an integer") \ M(BigIntInvalidValue, "Invalid value for BigInt: {}") \ + M(ClassConstructorWithoutNew, "Class constructor {} must be called with 'new'") \ M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null") \ M(Convert, "Cannot convert {} to {}") \ M(ConvertUndefinedToObject, "Cannot convert undefined to object") \ diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp index d3275425a1..4fd0ac399b 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -110,7 +110,7 @@ LexicalEnvironment* ScriptFunction::create_environment() return environment; } -Value ScriptFunction::call() +Value ScriptFunction::execute_function_body() { auto& vm = this->vm(); @@ -150,13 +150,22 @@ Value ScriptFunction::call() return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function); } +Value ScriptFunction::call() +{ + if (m_is_class_constructor) { + vm().throw_exception(global_object(), ErrorType::ClassConstructorWithoutNew, m_name); + return {}; + } + return execute_function_body(); +} + Value ScriptFunction::construct(Function&) { if (m_is_arrow_function) { vm().throw_exception(global_object(), ErrorType::NotAConstructor, m_name); return {}; } - return call(); + return execute_function_body(); } JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter) diff --git a/Libraries/LibJS/Runtime/ScriptFunction.h b/Libraries/LibJS/Runtime/ScriptFunction.h index 0bb2e0a279..9592b5ef35 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.h +++ b/Libraries/LibJS/Runtime/ScriptFunction.h @@ -50,6 +50,8 @@ public: virtual const FlyString& name() const override { return m_name; }; void set_name(const FlyString& name) { m_name = name; }; + void set_is_class_constructor() { m_is_class_constructor = true; }; + protected: virtual bool is_strict_mode() const final { return m_is_strict; } @@ -58,6 +60,8 @@ private: virtual LexicalEnvironment* create_environment() override; virtual void visit_children(Visitor&) override; + Value execute_function_body(); + JS_DECLARE_NATIVE_GETTER(length_getter); JS_DECLARE_NATIVE_GETTER(name_getter); @@ -68,6 +72,7 @@ private: i32 m_function_length { 0 }; bool m_is_strict { false }; bool m_is_arrow_function { false }; + bool m_is_class_constructor { false }; }; } diff --git a/Libraries/LibJS/Tests/classes/class-constructor.js b/Libraries/LibJS/Tests/classes/class-constructor.js index 9800ffabba..54a601e10c 100644 --- a/Libraries/LibJS/Tests/classes/class-constructor.js +++ b/Libraries/LibJS/Tests/classes/class-constructor.js @@ -46,18 +46,18 @@ test("constructor length affects class length", () => { expect(B).toHaveLength(2); }); -test.skip("must be invoked with 'new'", () => { +test("must be invoked with 'new'", () => { class A { constructor() {} } expect(() => { A(); - }).toThrow(TypeError); // FIXME: Add message when this test works + }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'"); expect(() => { A.prototype.constructor(); - }).toThrow(TypeError); // FIXME: Add message when this test works + }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'"); }); test("implicit constructor", () => {