jslike 1.7.3 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -473,7 +473,10 @@ Execute JavaScript code and return the result.
473
473
  const result = await execute(code, env, {
474
474
  moduleResolver, // For import statements
475
475
  executionController, // For pause/resume/abort
476
- abortSignal // For cancellation
476
+ abortSignal, // For cancellation
477
+ sourcePath, // Optional importer path for resolving top-level imports
478
+ typescript, // Parse TypeScript syntax
479
+ tsx // Parse TypeScript + JSX syntax
477
480
  });
478
481
  ```
479
482
 
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Use bundled Acorn parser for zero runtime dependencies
2
- import { parse as acornParse } from './parser.js';
2
+ import { parse as acornParse, tsParse, tsxParse } from './parser.js';
3
3
  import { Interpreter } from './interpreter/interpreter.js';
4
4
  import { Environment, ReturnValue } from './runtime/environment.js';
5
5
  import { createGlobalEnvironment } from './runtime/builtins.js';
@@ -9,7 +9,7 @@ function containsModuleSyntax(code) {
9
9
  // Trigger module mode for:
10
10
  // 1. import/export statements
11
11
  // 2. Top-level await (await not inside a function)
12
- if (/^\s*(import|export)\s+/m.test(code)) {
12
+ if (/(^|[;{\n])\s*(import|export)\s+/m.test(code)) {
13
13
  return true;
14
14
  }
15
15
 
@@ -23,6 +23,13 @@ function containsModuleSyntax(code) {
23
23
  return false;
24
24
  }
25
25
 
26
+ function isTypeScriptPath(sourcePath) {
27
+ return typeof sourcePath === 'string' && /\.(ts|tsx|mts|cts)$/i.test(sourcePath);
28
+ }
29
+
30
+ function isTSXPath(sourcePath) {
31
+ return typeof sourcePath === 'string' && /\.tsx$/i.test(sourcePath);
32
+ }
26
33
 
27
34
  export function parse(code, options = {}) {
28
35
  // Determine sourceType: use 'module' ONLY if explicitly requested or if code has imports/exports
@@ -32,10 +39,13 @@ export function parse(code, options = {}) {
32
39
  sourceType = 'module';
33
40
  }
34
41
 
42
+ const shouldParseTypeScript = options.typescript || options.tsx || isTypeScriptPath(options.sourcePath);
43
+ const parser = options.tsx || isTSXPath(options.sourcePath) ? tsxParse : shouldParseTypeScript ? tsParse : acornParse;
44
+
35
45
  // Parse with Acorn
36
46
  try {
37
- return acornParse(code, {
38
- ecmaVersion: 2022, // Support ES2022 features (including top-level await)
47
+ return parser(code, {
48
+ ecmaVersion: shouldParseTypeScript ? 'latest' : 2022, // Support ES2022 features (including top-level await)
39
49
  sourceType: sourceType,
40
50
  locations: true, // Track source locations for better error messages
41
51
  allowReturnOutsideFunction: true, // Allow top-level return statements
@@ -119,7 +129,8 @@ export async function execute(code, env = null, options = {}) {
119
129
  const interpreter = new Interpreter(env, {
120
130
  moduleResolver: options.moduleResolver,
121
131
  abortSignal: options.abortSignal,
122
- executionController: controller
132
+ executionController: controller,
133
+ currentModulePath: options.sourcePath
123
134
  });
124
135
 
125
136
  // Use async evaluation if:
@@ -103,7 +103,8 @@ export class WangInterpreter {
103
103
  const options = {
104
104
  moduleResolver: this.moduleResolver,
105
105
  executionController: userOptions.executionController,
106
- abortSignal: userOptions.abortSignal // Pass abort signal to interpreter for cancellation support
106
+ abortSignal: userOptions.abortSignal, // Pass abort signal to interpreter for cancellation support
107
+ sourcePath: userOptions.sourcePath ?? this.options.sourcePath
107
108
  // sourceType will be auto-detected from code
108
109
  };
109
110
 
@@ -1,13 +1,70 @@
1
1
  import { Environment, ReturnValue, BreakSignal, ContinueSignal, ThrowSignal } from '../runtime/environment.js';
2
- import { parse as acornParse } from '../parser.js';
2
+ import { parse as acornParse, tsParse, tsxParse } from '../parser.js';
3
3
  import { createMethodNotFoundError } from '../errors/enhanced-error.js';
4
4
 
5
+ function isTypeScriptPath(sourcePath) {
6
+ return typeof sourcePath === 'string' && /\.(ts|tsx|mts|cts)$/i.test(sourcePath);
7
+ }
8
+
9
+ function isTSXPath(sourcePath) {
10
+ return typeof sourcePath === 'string' && /\.tsx$/i.test(sourcePath);
11
+ }
12
+
13
+ function parseModuleCode(code, sourcePath) {
14
+ const parser = isTSXPath(sourcePath) ? tsxParse : isTypeScriptPath(sourcePath) ? tsParse : acornParse;
15
+ return parser(code, {
16
+ ecmaVersion: isTypeScriptPath(sourcePath) ? 'latest' : 2020,
17
+ sourceType: 'module',
18
+ locations: isTypeScriptPath(sourcePath)
19
+ });
20
+ }
21
+
22
+ const TYPE_ONLY_DECLARATIONS = new Set([
23
+ 'TSTypeAliasDeclaration',
24
+ 'TSInterfaceDeclaration',
25
+ 'TSDeclareFunction'
26
+ ]);
27
+
28
+ const TYPE_WRAPPER_EXPRESSIONS = new Set([
29
+ 'TSAsExpression',
30
+ 'TSTypeAssertion',
31
+ 'TSNonNullExpression',
32
+ 'TSSatisfiesExpression',
33
+ 'TSInstantiationExpression'
34
+ ]);
35
+
36
+ function isTypeOnlyDeclaration(node) {
37
+ return TYPE_ONLY_DECLARATIONS.has(node?.type) || node?.declare === true;
38
+ }
39
+
40
+ function isTypeWrapperExpression(node) {
41
+ return TYPE_WRAPPER_EXPRESSIONS.has(node?.type);
42
+ }
43
+
44
+ function getTypeWrapperInnerExpression(node) {
45
+ return node.expression;
46
+ }
47
+
48
+ function createUnsupportedTypeScriptRuntimeError(node) {
49
+ return new Error(`Unsupported runtime TypeScript syntax: ${node.type}`);
50
+ }
51
+
52
+ function getPatternName(pattern) {
53
+ if (!pattern) return undefined;
54
+ if (pattern.type === 'Identifier') return pattern.name;
55
+ if (pattern.type === 'AssignmentPattern') return getPatternName(pattern.left);
56
+ if (pattern.type === 'TSParameterProperty') return getPatternName(pattern.parameter);
57
+ return pattern.name;
58
+ }
59
+
5
60
  export class Interpreter {
6
61
  constructor(globalEnv, options = {}) {
7
62
  this.globalEnv = globalEnv;
8
63
  this.moduleResolver = options.moduleResolver;
9
64
  this.moduleCache = new Map(); // Cache loaded modules
65
+ this.moduleResolutionCache = options.moduleResolutionCache || new Map();
10
66
  this.moduleExports = {}; // Track exports in current module
67
+ this.currentModulePath = options.currentModulePath;
11
68
  this.abortSignal = options.abortSignal;
12
69
  this.executionController = options.executionController;
13
70
  }
@@ -163,6 +220,27 @@ export class Interpreter {
163
220
  const checkpointPromise = this._getCheckpointPromise(node, env);
164
221
  if (checkpointPromise) await checkpointPromise;
165
222
 
223
+ if (isTypeOnlyDeclaration(node)) {
224
+ return undefined;
225
+ }
226
+
227
+ if (node.type === 'TSExportAssignment' || node.type === 'TSImportEqualsDeclaration') {
228
+ throw createUnsupportedTypeScriptRuntimeError(node);
229
+ }
230
+
231
+ if (node.type === 'TSEnumDeclaration') {
232
+ return this.evaluateTSEnumDeclaration(node, env);
233
+ }
234
+
235
+ if (node.type === 'TSModuleDeclaration') {
236
+ if (node.declare) return undefined;
237
+ throw createUnsupportedTypeScriptRuntimeError(node);
238
+ }
239
+
240
+ if (isTypeWrapperExpression(node)) {
241
+ return await this.evaluateAsync(getTypeWrapperInnerExpression(node), env);
242
+ }
243
+
166
244
  // Handle await expressions by actually awaiting the promise
167
245
  if (node.type === 'AwaitExpression') {
168
246
  const promise = await this.evaluateAsync(node.argument, env);
@@ -814,6 +892,27 @@ export class Interpreter {
814
892
  // Check for abort signal before evaluating
815
893
  this.checkAbortSignal();
816
894
 
895
+ if (isTypeOnlyDeclaration(node)) {
896
+ return undefined;
897
+ }
898
+
899
+ if (node.type === 'TSExportAssignment' || node.type === 'TSImportEqualsDeclaration') {
900
+ throw createUnsupportedTypeScriptRuntimeError(node);
901
+ }
902
+
903
+ if (node.type === 'TSEnumDeclaration') {
904
+ return this.evaluateTSEnumDeclaration(node, env);
905
+ }
906
+
907
+ if (node.type === 'TSModuleDeclaration') {
908
+ if (node.declare) return undefined;
909
+ throw createUnsupportedTypeScriptRuntimeError(node);
910
+ }
911
+
912
+ if (isTypeWrapperExpression(node)) {
913
+ return this.evaluate(getTypeWrapperInnerExpression(node), env);
914
+ }
915
+
817
916
  switch (node.type) {
818
917
  case 'Program':
819
918
  return this.evaluateProgram(node, env);
@@ -974,6 +1073,9 @@ export class Interpreter {
974
1073
  case 'Property':
975
1074
  return this.evaluateProperty(node, env);
976
1075
 
1076
+ case 'TSEnumDeclaration':
1077
+ return this.evaluateTSEnumDeclaration(node, env);
1078
+
977
1079
  // JSX Support
978
1080
  case 'JSXElement':
979
1081
  return this.evaluateJSXElement(node, env);
@@ -1396,7 +1498,9 @@ export class Interpreter {
1396
1498
 
1397
1499
  // Bind parameters
1398
1500
  for (let i = 0; i < metadata.params.length; i++) {
1399
- const param = metadata.params[i];
1501
+ const param = metadata.params[i].type === 'TSParameterProperty'
1502
+ ? metadata.params[i].parameter
1503
+ : metadata.params[i];
1400
1504
 
1401
1505
  if (param.type === 'Identifier') {
1402
1506
  // Simple parameter: function(x)
@@ -1719,6 +1823,34 @@ export class Interpreter {
1719
1823
  return undefined;
1720
1824
  }
1721
1825
 
1826
+ evaluateTSEnumDeclaration(node, env) {
1827
+ const enumObject = {};
1828
+ let nextNumericValue = 0;
1829
+
1830
+ for (const member of node.members) {
1831
+ const memberName = member.id.name ?? member.id.value;
1832
+ let value;
1833
+
1834
+ if (member.initializer) {
1835
+ value = this.evaluate(member.initializer, env);
1836
+ } else {
1837
+ value = nextNumericValue;
1838
+ }
1839
+
1840
+ enumObject[memberName] = value;
1841
+
1842
+ if (typeof value === 'number') {
1843
+ enumObject[value] = memberName;
1844
+ nextNumericValue = value + 1;
1845
+ } else {
1846
+ nextNumericValue = undefined;
1847
+ }
1848
+ }
1849
+
1850
+ env.define(node.id.name, enumObject, false);
1851
+ return undefined;
1852
+ }
1853
+
1722
1854
  bindObjectPattern(pattern, value, env, isConst = false) {
1723
1855
  if (value === null || value === undefined) {
1724
1856
  throw new TypeError('Cannot destructure undefined or null');
@@ -1823,42 +1955,65 @@ export class Interpreter {
1823
1955
  // Get module path from import source
1824
1956
  const modulePath = node.source.value;
1825
1957
 
1958
+ if (node.importKind === 'type' ||
1959
+ (node.specifiers.length > 0 && node.specifiers.every(specifier => specifier.importKind === 'type'))) {
1960
+ return undefined;
1961
+ }
1962
+
1826
1963
  // Check if module resolver is configured
1827
1964
  if (!this.moduleResolver) {
1828
1965
  throw new Error('Module resolver not configured - cannot import modules');
1829
1966
  }
1830
1967
 
1968
+ const fromPath = this.currentModulePath;
1969
+
1970
+ const resolutionCacheKey = `${fromPath || ''}\0${modulePath}`;
1971
+ let resolution;
1972
+ let resolvedPath = this.moduleResolutionCache.get(resolutionCacheKey);
1973
+ if (!resolvedPath && !modulePath.startsWith('.') && this.moduleCache.has(modulePath)) {
1974
+ resolvedPath = modulePath;
1975
+ }
1976
+
1831
1977
  // Check if module is already cached
1832
1978
  let moduleExports;
1833
- if (this.moduleCache.has(modulePath)) {
1834
- moduleExports = this.moduleCache.get(modulePath);
1979
+ if (resolvedPath && this.moduleCache.has(resolvedPath)) {
1980
+ moduleExports = this.moduleCache.get(resolvedPath);
1835
1981
  } else {
1836
- // Resolve and load module code
1837
- const resolution = await this.moduleResolver.resolve(modulePath);
1982
+ // Resolve first so relative imports can use importer context and cache by resolved path.
1983
+ resolution = await this.moduleResolver.resolve(modulePath, fromPath);
1838
1984
  if (!resolution) {
1839
1985
  throw new Error(`Cannot find module '${modulePath}'`);
1840
1986
  }
1841
1987
 
1988
+ resolvedPath = typeof resolution === 'string'
1989
+ ? modulePath
1990
+ : resolution.path || modulePath;
1991
+ this.moduleResolutionCache.set(resolutionCacheKey, resolvedPath);
1992
+ if (this.moduleCache.has(resolvedPath)) {
1993
+ moduleExports = this.moduleCache.get(resolvedPath);
1994
+ return this.bindImportSpecifiers(node, env, modulePath, moduleExports);
1995
+ }
1996
+
1842
1997
  // Handle native module exports (for libraries like React)
1843
1998
  // If resolution has 'exports' property, use it directly without parsing
1844
1999
  if (resolution.exports) {
1845
2000
  moduleExports = resolution.exports;
1846
- this.moduleCache.set(modulePath, moduleExports);
2001
+ this.moduleCache.set(resolvedPath, moduleExports);
1847
2002
  } else {
1848
2003
  // Handle both old (string) and new (ModuleResolution) formats
1849
2004
  const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1850
2005
 
1851
2006
  // Parse and execute module in its own environment
1852
- const moduleAst = acornParse(moduleCode, {
1853
- ecmaVersion: 2020,
1854
- sourceType: 'module',
1855
- locations: false
1856
- });
2007
+ const moduleAst = parseModuleCode(moduleCode, resolvedPath);
1857
2008
  const moduleEnv = new Environment(this.globalEnv);
1858
2009
 
1859
2010
  // Create a new interpreter for the module with shared module cache
1860
2011
  const moduleInterpreter = new Interpreter(this.globalEnv, {
1861
- moduleResolver: this.moduleResolver
2012
+ moduleResolver: this.moduleResolver,
2013
+ moduleResolutionCache: this.moduleResolutionCache,
2014
+ currentModulePath: resolvedPath,
2015
+ abortSignal: this.abortSignal,
2016
+ executionController: this.executionController
1862
2017
  });
1863
2018
  moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1864
2019
 
@@ -1867,12 +2022,21 @@ export class Interpreter {
1867
2022
 
1868
2023
  // Cache the module exports
1869
2024
  moduleExports = moduleInterpreter.moduleExports;
1870
- this.moduleCache.set(modulePath, moduleExports);
2025
+ this.moduleCache.set(resolvedPath, moduleExports);
1871
2026
  }
1872
2027
  }
1873
2028
 
2029
+ this.bindImportSpecifiers(node, env, modulePath, moduleExports);
2030
+ return undefined;
2031
+ }
2032
+
2033
+ bindImportSpecifiers(node, env, modulePath, moduleExports) {
1874
2034
  // Import specified bindings into current environment
1875
2035
  for (const specifier of node.specifiers) {
2036
+ if (specifier.importKind === 'type') {
2037
+ continue;
2038
+ }
2039
+
1876
2040
  if (specifier.type === 'ImportSpecifier') {
1877
2041
  // Named import: import { foo, bar } from "module"
1878
2042
  const importedName = specifier.imported.name;
@@ -1903,6 +2067,10 @@ export class Interpreter {
1903
2067
  }
1904
2068
 
1905
2069
  evaluateExportNamedDeclaration(node, env) {
2070
+ if (node.exportKind === 'type' || isTypeOnlyDeclaration(node.declaration)) {
2071
+ return undefined;
2072
+ }
2073
+
1906
2074
  // Handle export with declaration: export function foo() {} or export const x = 42
1907
2075
  if (node.declaration) {
1908
2076
  const result = this.evaluate(node.declaration, env);
@@ -1922,6 +2090,9 @@ export class Interpreter {
1922
2090
  // export class Foo {}
1923
2091
  const name = node.declaration.id.name;
1924
2092
  this.moduleExports[name] = env.get(name);
2093
+ } else if (node.declaration.type === 'TSEnumDeclaration') {
2094
+ const name = node.declaration.id.name;
2095
+ this.moduleExports[name] = env.get(name);
1925
2096
  }
1926
2097
 
1927
2098
  return result;
@@ -1930,6 +2101,10 @@ export class Interpreter {
1930
2101
  // Handle export list: export { foo, bar }
1931
2102
  if (node.specifiers && node.specifiers.length > 0) {
1932
2103
  for (const specifier of node.specifiers) {
2104
+ if (specifier.exportKind === 'type') {
2105
+ continue;
2106
+ }
2107
+
1933
2108
  const exportedName = specifier.exported.name;
1934
2109
  const localName = specifier.local.name;
1935
2110
  this.moduleExports[exportedName] = env.get(localName);
@@ -2406,7 +2581,10 @@ export class Interpreter {
2406
2581
 
2407
2582
  // Bind parameters
2408
2583
  for (let i = 0; i < methodFunc.__params.length; i++) {
2409
- const param = methodFunc.__params[i];
2584
+ const originalParam = methodFunc.__params[i];
2585
+ const param = originalParam.type === 'TSParameterProperty'
2586
+ ? originalParam.parameter
2587
+ : originalParam;
2410
2588
 
2411
2589
  if (param.type === 'Identifier') {
2412
2590
  // Simple parameter: function(x)
@@ -2429,6 +2607,13 @@ export class Interpreter {
2429
2607
  // Fallback for simple parameter names
2430
2608
  funcEnv.define(param.name, args[i]);
2431
2609
  }
2610
+
2611
+ if (originalParam.type === 'TSParameterProperty') {
2612
+ const propertyName = getPatternName(originalParam.parameter);
2613
+ if (propertyName) {
2614
+ thisContext[propertyName] = funcEnv.get(propertyName);
2615
+ }
2616
+ }
2432
2617
  }
2433
2618
 
2434
2619
  const result = this.evaluate(methodFunc.__body, funcEnv);