jslike 1.7.3 → 1.8.1

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
@@ -7,6 +7,7 @@
7
7
  - **Production-Ready** - Handles files of any size, tested with 1000+ tests
8
8
  - **Full ES6+ JavaScript Support** - Classes, destructuring, template literals, spread operator, arrow functions
9
9
  - **Native JSX Support** - Parse and execute JSX without pre-transformation
10
+ - **TypeScript & TSX Module Support** - Execute JS-compatible TypeScript syntax directly from `.ts`, `.tsx`, `.mts`, and `.cts` modules
10
11
  - **React Integration** - Import React hooks and components via moduleResolver
11
12
  - **CSP-Safe** - Tree-walking interpreter, no eval() or new Function()
12
13
  - **ASI (Automatic Semicolon Insertion)** - Write JavaScript naturally without mandatory semicolons
@@ -143,9 +144,10 @@ const moduleResolver = {
143
144
 
144
145
  ```javascript
145
146
  const moduleResolver = {
146
- async resolve(modulePath) {
147
+ async resolve(modulePath, fromPath) {
147
148
  if (modulePath === './utils') {
148
149
  return {
150
+ path: '/virtual/project/utils.js',
149
151
  code: `
150
152
  export function double(x) { return x * 2; }
151
153
  export const PI = 3.14159;
@@ -157,6 +159,40 @@ const moduleResolver = {
157
159
  };
158
160
  ```
159
161
 
162
+ `fromPath` is the path of the importing module. For top-level code, pass `sourcePath` to `execute()` so relative imports have a deterministic root. For nested imports, JSLike passes the resolved module `path` returned by the resolver.
163
+
164
+ ```javascript
165
+ const files = {
166
+ '/virtual/project/main.ts': `
167
+ import { user } from './user.ts';
168
+ user.name
169
+ `,
170
+ '/virtual/project/user.ts': `
171
+ type User = { name: string };
172
+ export const user: User = { name: 'Ada' };
173
+ `
174
+ };
175
+
176
+ const moduleResolver = {
177
+ async resolve(modulePath, fromPath) {
178
+ const base = new URL('.', `file://${fromPath}`).pathname;
179
+ const resolvedPath = new URL(modulePath, `file://${base}`).pathname;
180
+
181
+ if (!files[resolvedPath]) return null;
182
+ return {
183
+ path: resolvedPath,
184
+ code: files[resolvedPath]
185
+ };
186
+ }
187
+ };
188
+
189
+ const result = await execute(files['/virtual/project/main.ts'], null, {
190
+ moduleResolver,
191
+ sourcePath: '/virtual/project/main.ts'
192
+ });
193
+ // "Ada"
194
+ ```
195
+
160
196
  ### Import Styles Supported
161
197
 
162
198
  ```javascript
@@ -165,6 +201,44 @@ import React from 'react'; // Default import
165
201
  import * as Utils from './utils'; // Namespace import
166
202
  ```
167
203
 
204
+ ## TypeScript Support
205
+
206
+ JSLike parses TypeScript and TSX with a bundled `@sveltejs/acorn-typescript` parser. TypeScript syntax is enabled automatically for `sourcePath` and resolved module paths ending in `.ts`, `.tsx`, `.mts`, or `.cts`. You can also force it with `typescript: true` or `tsx: true`.
207
+
208
+ ```javascript
209
+ const result = await execute(`
210
+ interface User {
211
+ name: string;
212
+ }
213
+
214
+ enum Role {
215
+ Admin,
216
+ Member
217
+ }
218
+
219
+ class Account {
220
+ constructor(public user: User, readonly role: Role) {}
221
+ }
222
+
223
+ const account = new Account({ name: 'Ada' }, Role.Admin);
224
+ account.user.name + ':' + account.role
225
+ `, null, {
226
+ sourcePath: '/virtual/account.ts'
227
+ });
228
+ // "Ada:0"
229
+ ```
230
+
231
+ Supported TypeScript runtime behavior:
232
+
233
+ - Type-only declarations and annotations are erased: `type`, `interface`, `declare`, parameter/return annotations, tuple/readonly annotations.
234
+ - Type-only imports and exports do not trigger runtime module resolution.
235
+ - Type wrappers evaluate their inner expression: `as`, `<T>value`, `satisfies`, non-null `!`, generic call instantiation.
236
+ - Enums execute as runtime enum objects, including numeric reverse mappings and string enum members.
237
+ - Constructor parameter properties assign to `this`, including `public`, `private`, and `readonly`.
238
+ - TSX parses and executes JSX in `.tsx` files or with `tsx: true`.
239
+
240
+ Unsupported runtime TypeScript constructs throw explicit errors instead of executing incorrectly. This currently includes `namespace`, `export =`, and `import x = require(...)`.
241
+
168
242
  ## JSX Features
169
243
 
170
244
  ### Basic Elements
@@ -473,7 +547,10 @@ Execute JavaScript code and return the result.
473
547
  const result = await execute(code, env, {
474
548
  moduleResolver, // For import statements
475
549
  executionController, // For pause/resume/abort
476
- abortSignal // For cancellation
550
+ abortSignal, // For cancellation
551
+ sourcePath, // Optional importer path for resolving top-level imports
552
+ typescript, // Parse TypeScript syntax
553
+ tsx // Parse TypeScript + JSX syntax
477
554
  });
478
555
  ```
479
556
 
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,9 @@ 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,
134
+ isTypeScriptModule: options.typescript || options.tsx || isTypeScriptPath(options.sourcePath)
123
135
  });
124
136
 
125
137
  // 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