juxscript 1.1.281 → 1.1.282

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.
Files changed (2) hide show
  1. package/machinery/autowrap.js +45 -212
  2. package/package.json +1 -1
@@ -39,29 +39,6 @@ function containsPageStateRef(node) {
39
39
  return found;
40
40
  }
41
41
 
42
- function containsJuxCall(node) {
43
- let found = false;
44
- walkSimple(node, {
45
- CallExpression(n) { if (n.callee?.object?.name === 'jux') found = true; }
46
- });
47
- return found;
48
- }
49
-
50
- function getDeclaredVarNames(stmt) {
51
- if (stmt.type !== 'VariableDeclaration') return [];
52
- return stmt.declarations
53
- .filter(d => d.id.type === 'Identifier')
54
- .map(d => d.id.name);
55
- }
56
-
57
- function usesIdentifier(node, name) {
58
- let found = false;
59
- walkSimple(node, {
60
- Identifier(n) { if (n.name === name) found = true; }
61
- });
62
- return found;
63
- }
64
-
65
42
  function isAlreadyWrapped(stmt) {
66
43
  return stmt.type === 'ExpressionStatement' &&
67
44
  stmt.expression?.type === 'CallExpression' &&
@@ -69,36 +46,10 @@ function isAlreadyWrapped(stmt) {
69
46
  stmt.expression?.callee?.property?.name === '__watch';
70
47
  }
71
48
 
72
- /**
73
- * Extract the body statements from an existing __watch callback.
74
- * Returns null if the statement is not a __watch or has no block body.
75
- */
76
- function getWatchBody(stmt) {
77
- if (!isAlreadyWrapped(stmt)) return null;
78
- const arg = stmt.expression.arguments?.[0];
79
- if (!arg) return null;
80
- if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') {
81
- if (arg.body?.type === 'BlockStatement') {
82
- return arg.body.body; // array of statements
83
- }
84
- }
85
- return null;
86
- }
87
-
88
- function isSetupStatement(stmt) {
89
- // Never treat statements referencing pageState as setup
90
- if (containsPageStateRef(stmt)) return false;
91
-
49
+ function isSetupOnly(stmt) {
50
+ // Statements that should stay outside __watch
92
51
  if (stmt.type === 'ImportDeclaration') return true;
93
52
  if (stmt.type === 'FunctionDeclaration') return true;
94
- if (stmt.type === 'ExpressionStatement') {
95
- const expr = stmt.expression;
96
- if (expr.type === 'CallExpression' &&
97
- expr.callee?.object?.name === 'jux') return true;
98
- if (expr.type === 'AwaitExpression' &&
99
- expr.argument?.callee?.object?.name === 'jux') return true;
100
- }
101
- if (stmt.type === 'VariableDeclaration') return true;
102
53
  return false;
103
54
  }
104
55
 
@@ -124,186 +75,68 @@ export function autowrap(source, filename = '') {
124
75
  allowAwaitOutsideFunction: true,
125
76
  });
126
77
  } catch (err) {
127
- // Can't parse — return as-is
128
78
  return { code: source, wrappedCount: 0, details: [`parse error: ${err.message}`] };
129
79
  }
130
80
 
131
- const needsWatch = [];
132
- let i = 0;
81
+ // Check if there are any unwrapped pageState references at all
82
+ let hasUnwrappedPageState = false;
83
+ let firstUnwrappedIdx = -1;
84
+ let lastUnwrappedIdx = -1;
133
85
 
134
- while (i < ast.body.length) {
86
+ for (let i = 0; i < ast.body.length; i++) {
135
87
  const stmt = ast.body[i];
136
-
137
- if (isSetupStatement(stmt)) { i++; continue; }
138
-
139
- // Check if this is a single __watch that contains multiple independent concerns
140
- // If so, unwrap it and re-wrap each concern separately
141
- if (isAlreadyWrapped(stmt)) {
142
- const bodyStmts = getWatchBody(stmt);
143
- if (bodyStmts && bodyStmts.length > 1) {
144
- // Check if the body has multiple independent reactive groups
145
- const groups = groupBodyStatements(bodyStmts, source);
146
- if (groups.length > 1) {
147
- // Replace single watch with multiple watches
148
- // Record the original watch range for removal
149
- const watchStart = getLineNumber(source, stmt.start);
150
- const watchEnd = getLineNumber(source, stmt.end);
151
- needsWatch.push({
152
- line: watchStart,
153
- endLine: watchEnd,
154
- replace: true,
155
- groups: groups,
156
- });
157
- }
158
- }
159
- i++;
160
- continue;
161
- }
162
-
163
- // VariableDeclaration reading pageState — group with subsequent stmts that use declared vars
164
- if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
165
- const varNames = getDeclaredVarNames(stmt);
166
- const groupStmts = [stmt];
167
- let j = i + 1;
168
-
169
- // Greedily consume following statements that use any of the declared variable names
170
- while (j < ast.body.length) {
171
- const next = ast.body[j];
172
- if (isAlreadyWrapped(next) || isSetupStatement(next)) break;
173
- if (varNames.some(v => usesIdentifier(next, v))) {
174
- groupStmts.push(next);
175
- j++;
176
- } else {
177
- break;
178
- }
179
- }
180
-
181
- needsWatch.push({
182
- line: getLineNumber(source, groupStmts[0].start),
183
- endLine: getLineNumber(source, groupStmts[groupStmts.length - 1].end),
184
- });
185
- i = j;
186
- continue;
187
- }
188
-
189
- // Any other statement referencing pageState
88
+ if (isAlreadyWrapped(stmt)) continue;
89
+ if (isSetupOnly(stmt)) continue;
190
90
  if (containsPageStateRef(stmt)) {
191
- needsWatch.push({
192
- line: getLineNumber(source, stmt.start),
193
- endLine: getLineNumber(source, stmt.end),
194
- });
195
- i++;
196
- continue;
91
+ hasUnwrappedPageState = true;
92
+ if (firstUnwrappedIdx === -1) firstUnwrappedIdx = i;
93
+ lastUnwrappedIdx = i;
197
94
  }
198
-
199
- i++;
200
95
  }
201
96
 
202
- if (needsWatch.length === 0) {
97
+ if (!hasUnwrappedPageState) {
203
98
  return { code: source, wrappedCount: 0, details: [] };
204
99
  }
205
100
 
206
- // Apply wraps bottom-up to preserve line numbers
207
- const lines = source.split('\n');
208
- const sorted = [...needsWatch].sort((a, b) => b.line - a.line);
209
- const details = [];
101
+ // Find the contiguous range: from first unwrapped pageState ref
102
+ // to end of file (or last unwrapped ref), including any non-pageState
103
+ // statements in between (they may depend on reactive variables).
104
+ // But exclude leading setup-only statements (imports, function decls).
210
105
 
211
- for (const item of sorted) {
212
- const startIdx = item.line - 1;
213
- const endIdx = item.endLine - 1;
106
+ // Walk backwards from firstUnwrappedIdx to include any immediately
107
+ // preceding non-setup statements that declare variables used later
108
+ let rangeStart = firstUnwrappedIdx;
109
+ let rangeEnd = lastUnwrappedIdx;
214
110
 
215
- if (item.replace && item.groups) {
216
- // Replace a single big __watch with multiple smaller ones
217
- const indent = lines[startIdx].match(/^(\s*)/)[1];
218
- const newLines = [];
219
- for (const group of item.groups) {
220
- newLines.push(`${indent}pageState.__watch(() => {`);
221
- for (const gLine of group.lines) {
222
- newLines.push(`${indent} ${gLine.trim()}`);
223
- }
224
- newLines.push(`${indent}});`);
225
- }
226
- lines.splice(startIdx, endIdx - startIdx + 1, ...newLines);
227
- details.push(`L${item.line}-${item.endLine} (split ${item.groups.length})`);
228
- } else {
229
- const blockLines = lines.slice(startIdx, endIdx + 1);
230
- const indent = blockLines[0].match(/^(\s*)/)[1];
231
-
232
- const wrapped = [
233
- `${indent}pageState.__watch(() => {`,
234
- ...blockLines.map(l => `${indent} ${l.trim()}`),
235
- `${indent}});`,
236
- ];
111
+ // Collect all statements in the range
112
+ const lines = source.split('\n');
113
+ const stmtsInRange = ast.body.slice(rangeStart, rangeEnd + 1);
237
114
 
238
- lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
239
- details.push(`L${item.line}-${item.endLine}`);
240
- }
115
+ // Check if ALL pageState refs are already wrapped (nothing to do)
116
+ const unwrappedInRange = stmtsInRange.filter(s => !isAlreadyWrapped(s) && containsPageStateRef(s));
117
+ if (unwrappedInRange.length === 0) {
118
+ return { code: source, wrappedCount: 0, details: [] };
241
119
  }
242
120
 
243
- return {
244
- code: lines.join('\n'),
245
- wrappedCount: needsWatch.length,
246
- details
247
- };
248
- }
121
+ const startLine = getLineNumber(source, stmtsInRange[0].start);
122
+ const endLine = getLineNumber(source, stmtsInRange[stmtsInRange.length - 1].end);
249
123
 
250
- /**
251
- * Groups statements inside a __watch body into independent reactive groups.
252
- * Uses the same logic as the top-level grouping: VariableDeclarations that
253
- * read pageState get grouped with following statements that use those vars.
254
- * Standalone if-statements with pageState become their own group.
255
- */
256
- function groupBodyStatements(bodyStmts, source) {
257
- const groups = [];
258
- let i = 0;
124
+ const startIdx = startLine - 1;
125
+ const endIdx = endLine - 1;
126
+ const blockLines = lines.slice(startIdx, endIdx + 1);
127
+ const indent = blockLines[0].match(/^(\s*)/)[1];
259
128
 
260
- while (i < bodyStmts.length) {
261
- const stmt = bodyStmts[i];
129
+ const wrapped = [
130
+ `${indent}pageState.__watch(() => {`,
131
+ ...blockLines.map(l => `${indent} ${l.trimEnd()}`),
132
+ `${indent}});`,
133
+ ];
262
134
 
263
- // VariableDeclaration reading pageState group with dependents
264
- if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
265
- const varNames = getDeclaredVarNames(stmt);
266
- const groupStmts = [stmt];
267
- let j = i + 1;
135
+ lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
268
136
 
269
- while (j < bodyStmts.length) {
270
- const next = bodyStmts[j];
271
- if (varNames.some(v => usesIdentifier(next, v))) {
272
- groupStmts.push(next);
273
- j++;
274
- } else {
275
- break;
276
- }
277
- }
278
-
279
- groups.push({
280
- stmts: groupStmts,
281
- lines: extractSourceLines(groupStmts, source),
282
- });
283
- i = j;
284
- continue;
285
- }
286
-
287
- // Any statement referencing pageState — standalone group
288
- if (containsPageStateRef(stmt)) {
289
- groups.push({
290
- stmts: [stmt],
291
- lines: extractSourceLines([stmt], source),
292
- });
293
- i++;
294
- continue;
295
- }
296
-
297
- // Non-reactive statement — attach to next reactive group or skip
298
- i++;
299
- }
300
-
301
- return groups;
302
- }
303
-
304
- function extractSourceLines(stmts, source) {
305
- if (stmts.length === 0) return [];
306
- const start = stmts[0].start;
307
- const end = stmts[stmts.length - 1].end;
308
- return source.slice(start, end).split('\n');
137
+ return {
138
+ code: lines.join('\n'),
139
+ wrappedCount: 1,
140
+ details: [`L${startLine}-${endLine} (single watch block)`],
141
+ };
309
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.281",
3
+ "version": "1.1.282",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "./dist/lib/index.js",