EsprimaExtensions.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Runtime.CompilerServices;
  5. using Esprima;
  6. using Esprima.Ast;
  7. using Jint.Native;
  8. using Jint.Native.Function;
  9. using Jint.Native.Object;
  10. using Jint.Runtime;
  11. using Jint.Runtime.Environments;
  12. using Jint.Runtime.Interpreter;
  13. using Jint.Runtime.Interpreter.Expressions;
  14. using Jint.Runtime.Modules;
  15. namespace Jint
  16. {
  17. public static class EsprimaExtensions
  18. {
  19. public static JsValue GetKey<T>(this T property, Engine engine) where T : ClassProperty => GetKey(property.Key, engine, property.Computed);
  20. public static JsValue GetKey(this Expression expression, Engine engine, bool resolveComputed = false)
  21. {
  22. if (expression is Literal literal)
  23. {
  24. if (literal.TokenType == TokenType.NullLiteral)
  25. {
  26. return JsValue.Null;
  27. }
  28. return LiteralKeyToString(literal);
  29. }
  30. if (!resolveComputed && expression is Identifier identifier)
  31. {
  32. return identifier.Name;
  33. }
  34. if (!resolveComputed || !TryGetComputedPropertyKey(expression, engine, out var propertyKey))
  35. {
  36. ExceptionHelper.ThrowArgumentException("Unable to extract correct key, node type: " + expression.Type);
  37. return null;
  38. }
  39. return propertyKey;
  40. }
  41. private static bool TryGetComputedPropertyKey<T>(T expression, Engine engine, out JsValue propertyKey)
  42. where T : Expression
  43. {
  44. if (expression.Type is Nodes.Identifier
  45. or Nodes.CallExpression
  46. or Nodes.BinaryExpression
  47. or Nodes.UpdateExpression
  48. or Nodes.AssignmentExpression
  49. or Nodes.UnaryExpression
  50. or Nodes.MemberExpression
  51. or Nodes.LogicalExpression
  52. or Nodes.ConditionalExpression
  53. or Nodes.ArrowFunctionExpression
  54. or Nodes.FunctionExpression)
  55. {
  56. var context = engine._activeEvaluationContext;
  57. propertyKey = TypeConverter.ToPropertyKey(JintExpression.Build(engine, expression).GetValue(context).Value);
  58. return true;
  59. }
  60. propertyKey = string.Empty;
  61. return false;
  62. }
  63. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  64. internal static bool IsFunctionDefinition<T>(this T node) where T : Node
  65. {
  66. var type = node.Type;
  67. return type
  68. is Nodes.FunctionExpression
  69. or Nodes.ArrowFunctionExpression
  70. or Nodes.ArrowParameterPlaceHolder
  71. or Nodes.ClassExpression;
  72. }
  73. /// <summary>
  74. /// https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration
  75. /// </summary>
  76. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  77. internal static bool IsConstantDeclaration(this Declaration d)
  78. {
  79. return d is VariableDeclaration { Kind: VariableDeclarationKind.Const };
  80. }
  81. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  82. internal static bool HasName<T>(this T node) where T : Node
  83. {
  84. if (!node.IsFunctionDefinition())
  85. {
  86. return false;
  87. }
  88. if ((node as IFunction)?.Id is not null)
  89. {
  90. return true;
  91. }
  92. if ((node as ClassExpression)?.Id is not null)
  93. {
  94. return true;
  95. }
  96. return false;
  97. }
  98. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  99. internal static bool IsAnonymousFunctionDefinition<T>(this T node) where T : Node
  100. {
  101. if (!node.IsFunctionDefinition())
  102. {
  103. return false;
  104. }
  105. if (node.HasName())
  106. {
  107. return false;
  108. }
  109. return true;
  110. }
  111. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  112. internal static bool IsOptional<T>(this T node) where T : Expression
  113. {
  114. switch (node)
  115. {
  116. case MemberExpression { Optional: true }:
  117. case CallExpression { Optional: true }:
  118. return true;
  119. default:
  120. return false;
  121. }
  122. }
  123. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  124. internal static string LiteralKeyToString(Literal literal)
  125. {
  126. // prevent conversion to scientific notation
  127. if (literal.Value is double d)
  128. {
  129. return TypeConverter.ToString(d);
  130. }
  131. return literal.Value as string ?? Convert.ToString(literal.Value, provider: null);
  132. }
  133. internal static void GetBoundNames(this VariableDeclaration variableDeclaration, List<string> target)
  134. {
  135. ref readonly var declarations = ref variableDeclaration.Declarations;
  136. for (var i = 0; i < declarations.Count; i++)
  137. {
  138. var declaration = declarations[i];
  139. GetBoundNames(declaration.Id, target);
  140. }
  141. }
  142. internal static void GetBoundNames(this Node? parameter, List<string> target)
  143. {
  144. if (parameter is null || parameter.Type == Nodes.Literal)
  145. {
  146. return;
  147. }
  148. // try to get away without a loop
  149. if (parameter is Identifier id)
  150. {
  151. target.Add(id.Name!);
  152. return;
  153. }
  154. if (parameter is VariableDeclaration variableDeclaration)
  155. {
  156. variableDeclaration.GetBoundNames(target);
  157. return;
  158. }
  159. while (true)
  160. {
  161. if (parameter is Identifier identifier)
  162. {
  163. target.Add(identifier.Name!);
  164. return;
  165. }
  166. if (parameter is RestElement restElement)
  167. {
  168. parameter = restElement.Argument;
  169. continue;
  170. }
  171. if (parameter is ArrayPattern arrayPattern)
  172. {
  173. ref readonly var arrayPatternElements = ref arrayPattern.Elements;
  174. for (var i = 0; i < arrayPatternElements.Count; i++)
  175. {
  176. var expression = arrayPatternElements[i];
  177. GetBoundNames(expression, target);
  178. }
  179. }
  180. else if (parameter is ObjectPattern objectPattern)
  181. {
  182. ref readonly var objectPatternProperties = ref objectPattern.Properties;
  183. for (var i = 0; i < objectPatternProperties.Count; i++)
  184. {
  185. var property = objectPatternProperties[i];
  186. if (property is Property p)
  187. {
  188. GetBoundNames(p.Value, target);
  189. }
  190. else
  191. {
  192. GetBoundNames((RestElement) property, target);
  193. }
  194. }
  195. }
  196. else if (parameter is AssignmentPattern assignmentPattern)
  197. {
  198. parameter = assignmentPattern.Left;
  199. continue;
  200. }
  201. else if (parameter is ClassDeclaration classDeclaration)
  202. {
  203. var name = classDeclaration.Id?.Name;
  204. if (name != null)
  205. {
  206. target.Add(name);
  207. }
  208. }
  209. break;
  210. }
  211. }
  212. internal static void BindingInitialization(
  213. this Expression? expression,
  214. EvaluationContext context,
  215. JsValue value,
  216. EnvironmentRecord env)
  217. {
  218. if (expression is Identifier identifier)
  219. {
  220. var catchEnvRecord = (DeclarativeEnvironmentRecord) env;
  221. catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value);
  222. }
  223. else if (expression is BindingPattern bindingPattern)
  224. {
  225. BindingPatternAssignmentExpression.ProcessPatterns(
  226. context,
  227. bindingPattern,
  228. value,
  229. env);
  230. }
  231. }
  232. /// <summary>
  233. /// https://tc39.es/ecma262/#sec-runtime-semantics-definemethod
  234. /// </summary>
  235. internal static Record DefineMethod(this ClassProperty m, ObjectInstance obj, ObjectInstance? functionPrototype = null)
  236. {
  237. var engine = obj.Engine;
  238. var property = TypeConverter.ToPropertyKey(m.GetKey(engine));
  239. var prototype = functionPrototype ?? engine.Realm.Intrinsics.Function.PrototypeObject;
  240. var function = m.Value as IFunction;
  241. if (function is null)
  242. {
  243. ExceptionHelper.ThrowSyntaxError(engine.Realm);
  244. }
  245. var functionDefinition = new JintFunctionDefinition(engine, function);
  246. var closure = new ScriptFunctionInstance(
  247. engine,
  248. functionDefinition,
  249. engine.ExecutionContext.LexicalEnvironment,
  250. functionDefinition.ThisMode,
  251. prototype);
  252. closure.MakeMethod(obj);
  253. return new Record(property, closure);
  254. }
  255. internal static void GetImportEntries(this ImportDeclaration import, List<ImportEntry> importEntries, HashSet<string> requestedModules)
  256. {
  257. var source = import.Source.StringValue!;
  258. var specifiers = import.Specifiers;
  259. requestedModules.Add(source!);
  260. foreach (var specifier in specifiers)
  261. {
  262. switch (specifier)
  263. {
  264. case ImportNamespaceSpecifier namespaceSpecifier:
  265. importEntries.Add(new ImportEntry(source, "*", namespaceSpecifier.Local.GetModuleKey()));
  266. break;
  267. case ImportSpecifier importSpecifier:
  268. importEntries.Add(new ImportEntry(source, importSpecifier.Imported.GetModuleKey(), importSpecifier.Local.GetModuleKey()));
  269. break;
  270. case ImportDefaultSpecifier defaultSpecifier:
  271. importEntries.Add(new ImportEntry(source, "default", defaultSpecifier.Local.GetModuleKey()));
  272. break;
  273. }
  274. }
  275. }
  276. internal static void GetExportEntries(this ExportDeclaration export, List<ExportEntry> exportEntries, HashSet<string> requestedModules)
  277. {
  278. switch (export)
  279. {
  280. case ExportDefaultDeclaration defaultDeclaration:
  281. GetExportEntries(true, defaultDeclaration.Declaration, exportEntries);
  282. break;
  283. case ExportAllDeclaration allDeclaration:
  284. //Note: there is a pending PR for Esprima to support exporting an imported modules content as a namespace i.e. 'export * as ns from "mod"'
  285. requestedModules.Add(allDeclaration.Source.StringValue!);
  286. exportEntries.Add(new(allDeclaration.Exported?.GetModuleKey(), allDeclaration.Source.StringValue, "*", null));
  287. break;
  288. case ExportNamedDeclaration namedDeclaration:
  289. ref readonly var specifiers = ref namedDeclaration.Specifiers;
  290. if (specifiers.Count == 0)
  291. {
  292. GetExportEntries(false, namedDeclaration.Declaration!, exportEntries, namedDeclaration.Source?.StringValue);
  293. }
  294. else
  295. {
  296. for (var i = 0; i < specifiers.Count; i++)
  297. {
  298. var specifier = specifiers[i];
  299. if (namedDeclaration.Source != null)
  300. {
  301. exportEntries.Add(new(specifier.Exported.GetModuleKey(), namedDeclaration.Source.StringValue, specifier.Local.GetModuleKey(), null));
  302. }
  303. else
  304. {
  305. exportEntries.Add(new(specifier.Exported.GetModuleKey(), null, null, specifier.Local.GetModuleKey()));
  306. }
  307. }
  308. }
  309. if (namedDeclaration.Source is not null)
  310. {
  311. requestedModules.Add(namedDeclaration.Source.StringValue!);
  312. }
  313. break;
  314. }
  315. }
  316. private static void GetExportEntries(bool defaultExport, StatementListItem declaration, List<ExportEntry> exportEntries, string? moduleRequest = null)
  317. {
  318. var names = GetExportNames(declaration);
  319. if (names.Count == 0)
  320. {
  321. if (defaultExport)
  322. {
  323. exportEntries.Add(new("default", null, null, "*default*"));
  324. }
  325. }
  326. else
  327. {
  328. for (var i = 0; i < names.Count; i++)
  329. {
  330. var name = names[i];
  331. var exportName = defaultExport ? "default" : name;
  332. exportEntries.Add(new(exportName, moduleRequest, null, name));
  333. }
  334. }
  335. }
  336. private static List<string> GetExportNames(StatementListItem declaration)
  337. {
  338. var result = new List<string>();
  339. switch (declaration)
  340. {
  341. case FunctionDeclaration functionDeclaration:
  342. var funcName = functionDeclaration.Id?.Name;
  343. if (funcName is not null)
  344. {
  345. result.Add(funcName);
  346. }
  347. break;
  348. case ClassDeclaration classDeclaration:
  349. var className = classDeclaration.Id?.Name;
  350. if (className is not null)
  351. {
  352. result.Add(className);
  353. }
  354. break;
  355. case VariableDeclaration variableDeclaration:
  356. ref readonly var declarators = ref variableDeclaration.Declarations;
  357. for (var i = 0; i < declarators.Count; i++)
  358. {
  359. var declarator = declarators[i];
  360. var varName = declarator.Id.As<Identifier>()?.Name;
  361. if (varName is not null)
  362. {
  363. result.Add(varName);
  364. }
  365. }
  366. break;
  367. }
  368. return result;
  369. }
  370. private static string? GetModuleKey(this Expression expression)
  371. {
  372. return (expression as Identifier)?.Name ?? (expression as Literal)?.StringValue;
  373. }
  374. internal readonly record struct Record(JsValue Key, ScriptFunctionInstance Closure);
  375. }
  376. }