jslike 1.2.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jslike",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Production-ready JavaScript interpreter with full ES6+ support using Acorn parser",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -3,7 +3,7 @@
3
3
  import { readFile } from 'fs/promises';
4
4
  import { stdin } from 'process';
5
5
  import { parse } from 'acorn';
6
- import { preprocessCode, isTopLevelAwait } from '../index.js';
6
+ import { isTopLevelAwait } from '../index.js';
7
7
 
8
8
  const args = process.argv.slice(2);
9
9
 
@@ -59,12 +59,11 @@ async function validate() {
59
59
  process.exit(1);
60
60
  }
61
61
 
62
- // Preprocess the code
63
- const processedCode = preprocessCode(code);
64
- const hasAwait = isTopLevelAwait(processedCode);
62
+ // Check for top-level await
63
+ const hasAwait = isTopLevelAwait(code);
65
64
 
66
65
  // Parse the code to validate syntax
67
- const ast = parse(processedCode, {
66
+ const ast = parse(code, {
68
67
  ecmaVersion: 'latest',
69
68
  sourceType: hasAwait ? 'module' : 'script',
70
69
  locations: true
package/src/index.js CHANGED
@@ -23,212 +23,18 @@ function containsModuleSyntax(code) {
23
23
  return false;
24
24
  }
25
25
 
26
- // Pre-process code to fix common patterns that fail in strict mode
27
- function preprocessCode(code) {
28
- // Pattern: object literal at statement level with commas
29
- // Example: let arr = [1,2,3]\n{ first: first(arr), last: last(arr) }
30
- // This fails to parse because labeled statements can't have commas
31
- // We need to wrap it in parentheses to make it an expression: ({ ... })
32
-
33
- const lines = code.split('\n');
34
- const result = [];
35
-
36
- for (let i = 0; i < lines.length; i++) {
37
- let line = lines[i];
38
- const trimmed = line.trim();
39
-
40
- // Handle array literals and parenthesized expressions at statement level
41
- // Add semicolon to previous line to prevent ASI issues
42
- // Example: let x = 10\n[a, b] parses as: let x = 10[a, b] without semicolon
43
- // Example: let x = 2\n({ y: 3 }) parses as: let x = 2({ y: 3 }) without semicolon
44
- // But don't add if previous line ends with an operator (continuing expression)
45
- // Note: We DON'T handle standalone `{` here because it's too ambiguous (could be a block)
46
- if ((trimmed.startsWith('[') || trimmed.startsWith('(')) && result.length > 0) {
47
- // Skip back over empty lines and comments to find the last real statement
48
- let targetIdx = result.length - 1;
49
- while (targetIdx >= 0) {
50
- const checkLine = result[targetIdx].trim();
51
- if (checkLine && !checkLine.startsWith('//') && !checkLine.startsWith('/*')) {
52
- break;
53
- }
54
- targetIdx--;
55
- }
56
-
57
- if (targetIdx >= 0) {
58
- const prevLine = result[targetIdx];
59
- const prevTrimmed = prevLine.trim();
60
- // Don't add semicolon if previous line ends with an operator, block opener, or already has semicolon
61
- // But / at the end might be a regex literal, not division operator
62
- const endsWithOperator = /[+\-*%&|^<>=!?:]$/.test(prevTrimmed);
63
- // Check for regex literal at end: /pattern/flags
64
- const endsWithRegex = /\/[gims]*$/.test(prevTrimmed);
65
- // Don't add if previous line looks like a control statement (if, while, etc.)
66
- const isControlStatement = /^\s*(if|else|for|while|do|try|catch|finally|switch|function|class)\b/.test(prevTrimmed);
67
- if (prevTrimmed && !prevTrimmed.endsWith(';') && !prevTrimmed.endsWith('{') &&
68
- !prevTrimmed.endsWith('(') && !prevTrimmed.endsWith(',') && !endsWithOperator && !isControlStatement) {
69
- result[targetIdx] = prevLine + ';';
70
- }
71
- }
72
- }
73
-
74
- // Handle inline object literals at statement level: { key: value, key2: value2 }
75
- // Pattern: line has {...} with comma and/or colon inside (object-like)
76
- // This needs to be wrapped in parens to avoid being parsed as a block
77
- const hasInlineBlock = trimmed.match(/^(.*?)\s*\{([^{}]+)\}\s*$/);
78
- if (hasInlineBlock && !trimmed.match(/^\s*(if|else|for|while|do|try|catch|finally|switch|function|class)\b/)) {
79
- const before = hasInlineBlock[1].trim();
80
- const inside = hasInlineBlock[2];
81
-
82
- // Check if inside looks like object literal (has comma and/or colon)
83
- const hasComma = inside.includes(',');
84
- const hasColon = inside.includes(':');
85
-
86
- // If nothing before or just await/return, and inside has object-like syntax, wrap it
87
- if ((!before || before.match(/^(await|return)$/)) && (hasComma || hasColon)) {
88
- // Check if previous line indicates we're inside an array/object (don't wrap in that case)
89
- if (result.length > 0) {
90
- const prevLine = result[result.length - 1];
91
- const prevTrimmed = prevLine.trim();
92
- // Skip wrapping if inside array or object literal
93
- if (prevTrimmed.endsWith(',') || prevTrimmed.endsWith('[') || prevTrimmed.endsWith('{')) {
94
- result.push(line);
95
- continue;
96
- }
97
- }
98
-
99
- // Add semicolon to previous line to avoid ASI issues
100
- // Skip back over empty lines and comments
101
- if (result.length > 0) {
102
- let targetIdx = result.length - 1;
103
- while (targetIdx >= 0) {
104
- const checkLine = result[targetIdx].trim();
105
- if (checkLine && !checkLine.startsWith('//') && !checkLine.startsWith('/*')) {
106
- break;
107
- }
108
- targetIdx--;
109
- }
110
-
111
- if (targetIdx >= 0) {
112
- const prevLine = result[targetIdx];
113
- const prevTrimmed = prevLine.trim();
114
- if (prevTrimmed && !prevTrimmed.endsWith(';') && !prevTrimmed.endsWith('{')) {
115
- result[targetIdx] = prevLine + ';';
116
- }
117
- }
118
- }
119
-
120
- // Wrap just the {...} part
121
- const objPart = trimmed.substring(trimmed.indexOf('{'));
122
- const prefix = trimmed.substring(0, trimmed.indexOf('{'));
123
- line = prefix + '(' + objPart + ')';
124
- result.push(line);
125
- continue;
126
- }
127
- }
128
-
129
- // Check if this line starts a block that looks like an object literal
130
- // BUT: don't wrap if previous line is a function declaration
131
- if (trimmed === '{' && i < lines.length - 1) {
132
- // Check if previous line is a function declaration
133
- if (result.length > 0) {
134
- const prevTrimmed = result[result.length - 1].trim();
135
- if (prevTrimmed.match(/^\s*(async\s+)?function\s*\w*\s*\([^)]*\)\s*$/) ||
136
- prevTrimmed.match(/\)\s*=>\s*$/) ||
137
- prevTrimmed.match(/\s+(if|else|for|while|do|try|catch|finally|switch)\s*\([^)]*\)\s*$/)) {
138
- // This is a function/control structure body, not an object literal
139
- result.push(line);
140
- continue;
141
- }
142
- }
143
-
144
- // Look ahead to see if block contains colons and commas (object literal pattern)
145
- let blockLines = [line];
146
- let j = i + 1;
147
- let braceCount = 1;
148
- let hasColon = false;
149
- let hasComma = false;
150
-
151
- // Collect the block
152
- while (j < lines.length && braceCount > 0) {
153
- const nextLine = lines[j];
154
- blockLines.push(nextLine);
155
-
156
- // Count braces (simple heuristic, not perfect but good enough)
157
- for (const char of nextLine) {
158
- if (char === '{') braceCount++;
159
- if (char === '}') braceCount--;
160
- }
161
-
162
- // Check for object literal indicators
163
- if (nextLine.includes(':')) hasColon = true;
164
- if (nextLine.includes(',')) hasComma = true;
165
-
166
- j++;
167
- if (braceCount === 0) break;
168
- }
169
-
170
- // If block looks like object literal (has both : and ,), wrap in parens
171
- if (hasColon && hasComma) {
172
- // Check if previous line needs a semicolon to avoid ASI issues
173
- // (wrapping in parens can cause previous expression to be treated as function call)
174
- // Skip back over empty lines and comments to find the last real statement
175
- if (result.length > 0) {
176
- let targetIdx = result.length - 1;
177
- while (targetIdx >= 0) {
178
- const checkLine = result[targetIdx].trim();
179
- if (checkLine && !checkLine.startsWith('//') && !checkLine.startsWith('/*')) {
180
- // Found a non-empty, non-comment line
181
- break;
182
- }
183
- targetIdx--;
184
- }
185
-
186
- if (targetIdx >= 0) {
187
- const targetLine = result[targetIdx];
188
- const targetTrimmed = targetLine.trim();
189
- // Add semicolon if line doesn't end with one
190
- // Note: Even if it ends with }, we need semicolon (could be object literal)
191
- // Only skip if it ends with ; or { (block statement opener)
192
- if (targetTrimmed && !targetTrimmed.endsWith(';') && !targetTrimmed.endsWith('{')) {
193
- result[targetIdx] = targetLine + ';';
194
- }
195
- }
196
- }
197
-
198
- result.push('(' + blockLines[0]);
199
- for (let k = 1; k < blockLines.length - 1; k++) {
200
- result.push(blockLines[k]);
201
- }
202
- // Add closing paren after closing brace
203
- const lastLine = blockLines[blockLines.length - 1];
204
- result.push(lastLine.replace('}', '})'));
205
-
206
- // Skip the lines we already processed
207
- i = j - 1;
208
- continue;
209
- }
210
- }
211
-
212
- result.push(line);
213
- }
214
-
215
- return result.join('\n');
216
- }
217
26
 
218
27
  export function parse(code, options = {}) {
219
- // Pre-process code to handle patterns that fail in strict mode
220
- const processedCode = preprocessCode(code);
221
-
222
28
  // Determine sourceType: use 'module' ONLY if explicitly requested or if code has imports/exports
223
29
  // Default to 'script' for better compatibility with labeled statements
224
30
  let sourceType = options.sourceType || 'script';
225
- if (!options.sourceType && containsModuleSyntax(processedCode)) {
31
+ if (!options.sourceType && containsModuleSyntax(code)) {
226
32
  sourceType = 'module';
227
33
  }
228
34
 
229
35
  // Parse with Acorn
230
36
  try {
231
- return acornParse(processedCode, {
37
+ return acornParse(code, {
232
38
  ecmaVersion: 2022, // Support ES2022 features (including top-level await)
233
39
  sourceType: sourceType,
234
40
  locations: true, // Track source locations for better error messages
@@ -331,7 +137,6 @@ export function createEnvironment() {
331
137
  }
332
138
 
333
139
  // Export utility functions for CLI tools
334
- export { preprocessCode };
335
140
  export const isTopLevelAwait = containsModuleSyntax;
336
141
 
337
142
  export { Interpreter } from './interpreter/interpreter.js';
@@ -2,7 +2,7 @@
2
2
  * WangValidator - Syntax validation for Wang/JSLike code
3
3
  */
4
4
 
5
- import { parse, preprocessCode } from '../index.js';
5
+ import { parse } from '../index.js';
6
6
 
7
7
  export class WangValidator {
8
8
  /**
@@ -14,11 +14,8 @@ export class WangValidator {
14
14
  */
15
15
  validate(code, options = {}) {
16
16
  try {
17
- // Preprocess code (handles ASI, etc.)
18
- const processed = preprocessCode(code);
19
-
20
17
  // Parse the code
21
- const ast = parse(processed);
18
+ const ast = parse(code);
22
19
 
23
20
  return {
24
21
  valid: true,