#nullable enable using Esprima; using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions; namespace Jint.Native.Function { internal class ClassDefinition { private static readonly MethodDefinition _superConstructor; private static readonly MethodDefinition _emptyConstructor; internal readonly string? _className; private readonly Expression? _superClass; private readonly ClassBody _body; static ClassDefinition() { // generate missing constructor AST only once static MethodDefinition CreateConstructorMethodDefinition(string source) { var parser = new JavaScriptParser(source); var script = parser.ParseScript(); return (MethodDefinition) script.Body[0].ChildNodes[2].ChildNodes[0]; } _superConstructor = CreateConstructorMethodDefinition("class temp { constructor(...args) { super(...args); } }"); _emptyConstructor = CreateConstructorMethodDefinition("class temp { constructor() {} }"); } public ClassDefinition( Identifier? className, Expression? superClass, ClassBody body) { _className = className?.Name; _superClass = superClass; _body = body; } /// /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation /// public ScriptFunctionInstance BuildConstructor( Engine engine, EnvironmentRecord env) { // A class definition is always strict mode code. using var _ = (new StrictModeScope(true, true)); var classScope = JintEnvironment.NewDeclarativeEnvironment(engine, env); if (_className is not null) { classScope.CreateImmutableBinding(_className, true); } ObjectInstance? protoParent = null; ObjectInstance? constructorParent = null; if (_superClass is null) { protoParent = engine.Realm.Intrinsics.Object.PrototypeObject; constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject; } else { engine.UpdateLexicalEnvironment(classScope); var superclass = JintExpression.Build(engine, _superClass).GetValue(); engine.UpdateLexicalEnvironment(env); if (superclass.IsNull()) { protoParent = null; constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject; } else if (!superclass.IsConstructor) { ExceptionHelper.ThrowTypeError(engine.Realm, "super class is not a constructor"); } else { var temp = superclass.Get("prototype"); if (temp is ObjectInstance protoParentObject) { protoParent = protoParentObject; } else if (temp._type == InternalTypes.Null) { // OK } else { ExceptionHelper.ThrowTypeError(engine.Realm); return null!; } constructorParent = (ObjectInstance) superclass; } } var proto = new ObjectInstance(engine) { _prototype = protoParent }; MethodDefinition? constructor = null; var classBody = _body.Body; for (var i = 0; i < classBody.Count; ++i) { if (classBody[i].Kind == PropertyKind.Constructor) { constructor = (MethodDefinition) classBody[i]; break; } } constructor ??= _superClass != null ? _superConstructor : _emptyConstructor; engine.UpdateLexicalEnvironment(classScope); ScriptFunctionInstance F; try { var constructorInfo = constructor.DefineMethod(proto, constructorParent); F = constructorInfo.Closure; if (_className is not null) { F.SetFunctionName(_className); } F.MakeConstructor(false, proto); F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived; F.MakeClassConstructor(); proto.CreateMethodProperty(CommonProperties.Constructor, F); foreach (var classProperty in _body.Body) { if (classProperty is not MethodDefinition m || m.Kind == PropertyKind.Constructor) { continue; } var target = !m.Static ? proto : F; PropertyDefinitionEvaluation(engine, target, m); } } finally { engine.UpdateLexicalEnvironment(env); } if (_className is not null) { classScope.InitializeBinding(_className, F); } return F; } /// /// https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation /// private static void PropertyDefinitionEvaluation( Engine engine, ObjectInstance obj, MethodDefinition method) { if (method.Kind != PropertyKind.Get && method.Kind != PropertyKind.Set) { var methodDef = method.DefineMethod(obj); methodDef.Closure.SetFunctionName(methodDef.Key); var desc = new PropertyDescriptor(methodDef.Closure, PropertyFlag.NonEnumerable); obj.DefinePropertyOrThrow(methodDef.Key, desc); } else { var propKey = TypeConverter.ToPropertyKey(method.GetKey(engine)); var function = method.Value as IFunction; if (function is null) { ExceptionHelper.ThrowSyntaxError(obj.Engine.Realm); } var closure = new ScriptFunctionInstance( obj.Engine, function, obj.Engine.ExecutionContext.LexicalEnvironment, true); closure.SetFunctionName(propKey, method.Kind == PropertyKind.Get ? "get" : "set"); closure.MakeMethod(obj); var propDesc = new GetSetPropertyDescriptor( method.Kind == PropertyKind.Get ? closure : null, method.Kind == PropertyKind.Set ? closure : null, PropertyFlag.Configurable); obj.DefinePropertyOrThrow(propKey, propDesc); } } } }