using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Environment = Jint.Runtime.Environments.Environment;
namespace Jint.Native.Function;
internal sealed class ClassDefinition
{
private static readonly MethodDefinition _superConstructor;
internal static CallExpression _defaultSuperCall;
internal static readonly MethodDefinition _emptyConstructor;
internal readonly string? _className;
private readonly Expression? _superClass;
private readonly ClassBody _body;
static ClassDefinition()
{
var parser = new Parser(Engine.BaseParserOptions);
// generate missing constructor AST only once
static MethodDefinition CreateConstructorMethodDefinition(Parser parser, string source)
{
var script = parser.ParseScriptGuarded(new Engine().Realm, source);
var classDeclaration = (ClassDeclaration) script.Body[0];
return (MethodDefinition) classDeclaration.Body.Body[0];
}
_superConstructor = CreateConstructorMethodDefinition(parser, "class temp extends X { constructor(...args) { super(...args); } }");
_defaultSuperCall = (CallExpression) ((NonSpecialExpressionStatement) _superConstructor.Value.Body.Body[0]).Expression;
_emptyConstructor = CreateConstructorMethodDefinition(parser, "class temp { constructor() {} }");
}
public ClassDefinition(
string? className,
Expression? superClass,
ClassBody body)
{
_className = className;
_superClass = superClass;
_body = body;
}
///
/// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
///
public JsValue BuildConstructor(EvaluationContext context, Environment env)
{
// A class definition is always strict mode code.
using var _ = new StrictModeScope(true, true);
var engine = context.Engine;
var classEnv = JintEnvironment.NewDeclarativeEnvironment(engine, env);
if (_className is not null)
{
classEnv.CreateImmutableBinding(_className, true);
}
var outerPrivateEnvironment = engine.ExecutionContext.PrivateEnvironment;
var classPrivateEnvironment = JintEnvironment.NewPrivateEnvironment(engine, outerPrivateEnvironment);
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(classEnv);
var superclass = JintExpression.Build(_superClass).GetValue(context);
engine.UpdateLexicalEnvironment(env);
if (superclass.IsNull())
{
protoParent = null;
constructorParent = engine.Realm.Intrinsics.Function.PrototypeObject;
}
else if (!superclass.IsConstructor)
{
Throw.TypeError(engine.Realm, "super class is not a constructor");
}
else
{
var temp = superclass.Get(CommonProperties.Prototype);
if (temp is ObjectInstance protoParentObject)
{
protoParent = protoParentObject;
}
else if (temp.IsNull())
{
// OK
}
else
{
Throw.TypeError(engine.Realm, "cannot resolve super class prototype chain");
return default;
}
constructorParent = (ObjectInstance) superclass;
}
}
ObjectInstance proto = new JsObject(engine) { _prototype = protoParent };
var privateBoundIdentifiers = new HashSet(PrivateIdentifierNameComparer._instance);
MethodDefinition? constructor = null;
ref readonly var elements = ref _body.Body;
var classBody = elements;
for (var i = 0; i < classBody.Count; ++i)
{
var element = classBody[i];
if (element is MethodDefinition { Kind: PropertyKind.Constructor } c)
{
constructor = c;
}
privateBoundIdentifiers.Clear();
element.PrivateBoundIdentifiers(privateBoundIdentifiers);
foreach (var name in privateBoundIdentifiers)
{
classPrivateEnvironment.Names.Add(name, new PrivateName(name));
}
}
constructor ??= _superClass != null
? _superConstructor
: _emptyConstructor;
engine.UpdateLexicalEnvironment(classEnv);
engine.UpdatePrivateEnvironment(classPrivateEnvironment);
ScriptFunction F;
try
{
var constructorInfo = constructor.DefineMethod(proto, constructorParent);
F = constructorInfo.Closure;
F.SetFunctionName(_className ?? "");
F.MakeConstructor(writableProperty: false, proto);
F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived;
F.MakeClassConstructor();
proto.CreateMethodProperty(CommonProperties.Constructor, F);
var instancePrivateMethods = new List();
var staticPrivateMethods = new List();
var instanceFields = new List();
var staticElements = new List