@collie-lang/compiler 1.3.3 → 1.5.2

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/dist/index.cjs CHANGED
@@ -50,15 +50,15 @@ __export(index_exports, {
50
50
  module.exports = __toCommonJS(index_exports);
51
51
 
52
52
  // src/rewrite.ts
53
- function createTemplateEnv(propsDecls) {
54
- const propAliases = /* @__PURE__ */ new Map();
55
- if (propsDecls) {
56
- for (const decl of propsDecls) {
57
- propAliases.set(decl.name, decl.kind);
53
+ function createTemplateEnv(inputsDecls) {
54
+ const inputNames = /* @__PURE__ */ new Set();
55
+ if (inputsDecls) {
56
+ for (const decl of inputsDecls) {
57
+ inputNames.add(decl.name);
58
58
  }
59
59
  }
60
60
  return {
61
- propAliases,
61
+ inputNames,
62
62
  localsStack: []
63
63
  };
64
64
  }
@@ -77,12 +77,6 @@ function isLocal(env, name) {
77
77
  }
78
78
  return false;
79
79
  }
80
- function isPropAlias(env, name) {
81
- if (isLocal(env, name)) {
82
- return false;
83
- }
84
- return env.propAliases.has(name);
85
- }
86
80
  var IGNORED_IDENTIFIERS = /* @__PURE__ */ new Set([
87
81
  "null",
88
82
  "undefined",
@@ -90,10 +84,10 @@ var IGNORED_IDENTIFIERS = /* @__PURE__ */ new Set([
90
84
  "false",
91
85
  "NaN",
92
86
  "Infinity",
93
- "this",
94
- "props"
87
+ "this"
95
88
  ]);
96
89
  var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
90
+ "async",
97
91
  "await",
98
92
  "break",
99
93
  "case",
@@ -135,185 +129,33 @@ var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
135
129
  "yield"
136
130
  ]);
137
131
  function rewriteExpression(expression, env) {
138
- let i = 0;
139
- let state = "code";
140
- let output = "";
132
+ const tokens = tokenizeExpression(expression);
141
133
  const usedBare = /* @__PURE__ */ new Set();
142
- const usedPropsDot = /* @__PURE__ */ new Set();
143
134
  const callSitesBare = /* @__PURE__ */ new Set();
144
- const callSitesPropsDot = /* @__PURE__ */ new Set();
145
- const rewrittenAliases = /* @__PURE__ */ new Set();
146
- while (i < expression.length) {
147
- const ch = expression[i];
148
- if (state === "code") {
149
- if (ch === "'" || ch === '"') {
150
- state = ch === "'" ? "single" : "double";
151
- output += ch;
152
- i++;
153
- continue;
154
- }
155
- if (ch === "`") {
156
- state = "template";
157
- output += ch;
158
- i++;
159
- continue;
160
- }
161
- if (ch === "/" && expression[i + 1] === "/") {
162
- state = "line";
163
- output += ch;
164
- i++;
165
- continue;
166
- }
167
- if (ch === "/" && expression[i + 1] === "*") {
168
- state = "block";
169
- output += ch;
170
- i++;
171
- continue;
172
- }
173
- if (isIdentifierStart(ch)) {
174
- const start = i;
175
- i++;
176
- while (i < expression.length && isIdentifierPart(expression[i])) {
177
- i++;
178
- }
179
- const name = expression.slice(start, i);
180
- const prevNonSpace = findPreviousNonSpace(expression, start - 1);
181
- const nextNonSpace = findNextNonSpace(expression, i);
182
- const isMemberAccess = prevNonSpace === ".";
183
- const isObjectKey = nextNonSpace === ":" && (prevNonSpace === "{" || prevNonSpace === ",");
184
- const isCall = nextNonSpace === "(";
185
- if (prevNonSpace === "." && start >= 2) {
186
- const propsStart = findPreviousIdentifierStart(expression, start - 2);
187
- if (propsStart !== null) {
188
- const possibleProps = expression.slice(propsStart, start - 1).trim();
189
- if (possibleProps === "props") {
190
- usedPropsDot.add(name);
191
- if (isCall) {
192
- callSitesPropsDot.add(name);
193
- }
194
- }
195
- }
196
- }
197
- if (isMemberAccess || isObjectKey || isLocal(env, name) || shouldIgnoreIdentifier(name)) {
198
- output += name;
199
- continue;
200
- }
201
- if (isPropAlias(env, name)) {
202
- output += `props.${name}`;
203
- rewrittenAliases.add(name);
204
- if (isCall) {
205
- callSitesBare.add(name);
206
- }
207
- continue;
208
- }
209
- usedBare.add(name);
210
- if (isCall) {
211
- callSitesBare.add(name);
212
- }
213
- output += name;
214
- continue;
215
- }
216
- output += ch;
217
- i++;
218
- continue;
219
- }
220
- if (state === "line") {
221
- output += ch;
222
- if (ch === "\n") {
223
- state = "code";
224
- }
225
- i++;
226
- continue;
227
- }
228
- if (state === "block") {
229
- output += ch;
230
- if (ch === "*" && expression[i + 1] === "/") {
231
- output += "/";
232
- i += 2;
233
- state = "code";
234
- continue;
235
- }
236
- i++;
237
- continue;
238
- }
239
- if (state === "single") {
240
- output += ch;
241
- if (ch === "\\") {
242
- if (i + 1 < expression.length) {
243
- output += expression[i + 1];
244
- i += 2;
245
- continue;
246
- }
247
- }
248
- if (ch === "'") {
249
- state = "code";
250
- }
251
- i++;
252
- continue;
253
- }
254
- if (state === "double") {
255
- output += ch;
256
- if (ch === "\\") {
257
- if (i + 1 < expression.length) {
258
- output += expression[i + 1];
259
- i += 2;
260
- continue;
261
- }
262
- }
263
- if (ch === '"') {
264
- state = "code";
265
- }
266
- i++;
267
- continue;
268
- }
269
- if (state === "template") {
270
- output += ch;
271
- if (ch === "\\") {
272
- if (i + 1 < expression.length) {
273
- output += expression[i + 1];
274
- i += 2;
275
- continue;
276
- }
277
- }
278
- if (ch === "`") {
279
- state = "code";
280
- }
281
- i++;
282
- continue;
283
- }
284
- }
285
- return { code: output, usedBare, usedPropsDot, callSitesBare, callSitesPropsDot, rewrittenAliases };
135
+ const localScopes = [/* @__PURE__ */ new Set()];
136
+ analyzeTokens(tokens, 0, tokens.length, env, localScopes, usedBare, callSitesBare, false);
137
+ return { code: expression, usedBare, callSitesBare };
286
138
  }
287
139
  function rewriteJsxExpression(expression, env) {
288
- let output = "";
289
140
  let i = 0;
290
141
  const usedBare = /* @__PURE__ */ new Set();
291
- const usedPropsDot = /* @__PURE__ */ new Set();
292
142
  const callSitesBare = /* @__PURE__ */ new Set();
293
- const callSitesPropsDot = /* @__PURE__ */ new Set();
294
- const rewrittenAliases = /* @__PURE__ */ new Set();
295
143
  while (i < expression.length) {
296
144
  const ch = expression[i];
297
145
  if (ch === "{") {
298
146
  const braceResult = readBalancedBraces(expression, i + 1);
299
147
  if (!braceResult) {
300
- output += expression.slice(i);
301
148
  break;
302
149
  }
303
150
  const result = rewriteExpression(braceResult.content, env);
304
- output += `{${result.code}}`;
305
151
  for (const name of result.usedBare) usedBare.add(name);
306
- for (const name of result.usedPropsDot) usedPropsDot.add(name);
307
152
  for (const name of result.callSitesBare) callSitesBare.add(name);
308
- for (const name of result.callSitesPropsDot) callSitesPropsDot.add(name);
309
- for (const name of result.rewrittenAliases) rewrittenAliases.add(name);
310
153
  i = braceResult.endIndex + 1;
311
154
  continue;
312
155
  }
313
- output += ch;
314
156
  i++;
315
157
  }
316
- return { code: output, usedBare, usedPropsDot, callSitesBare, callSitesPropsDot, rewrittenAliases };
158
+ return { code: expression, usedBare, callSitesBare };
317
159
  }
318
160
  function readBalancedBraces(source, startIndex) {
319
161
  let i = startIndex;
@@ -405,38 +247,6 @@ function readBalancedBraces(source, startIndex) {
405
247
  }
406
248
  return null;
407
249
  }
408
- function findPreviousNonSpace(text, index) {
409
- for (let i = index; i >= 0; i--) {
410
- const ch = text[i];
411
- if (!/\s/.test(ch)) {
412
- return ch;
413
- }
414
- }
415
- return null;
416
- }
417
- function findNextNonSpace(text, index) {
418
- for (let i = index; i < text.length; i++) {
419
- const ch = text[i];
420
- if (!/\s/.test(ch)) {
421
- return ch;
422
- }
423
- }
424
- return null;
425
- }
426
- function findPreviousIdentifierStart(text, index) {
427
- let i = index;
428
- while (i >= 0 && /\s/.test(text[i])) {
429
- i--;
430
- }
431
- if (i < 0) return null;
432
- if (!isIdentifierPart(text[i])) {
433
- return null;
434
- }
435
- while (i > 0 && isIdentifierPart(text[i - 1])) {
436
- i--;
437
- }
438
- return i;
439
- }
440
250
  function isIdentifierStart(ch) {
441
251
  return /[A-Za-z_$]/.test(ch);
442
252
  }
@@ -446,96 +256,592 @@ function isIdentifierPart(ch) {
446
256
  function shouldIgnoreIdentifier(name) {
447
257
  return IGNORED_IDENTIFIERS.has(name) || RESERVED_KEYWORDS.has(name);
448
258
  }
449
-
450
- // src/codegen.ts
451
- function generateRenderModule(root, options) {
452
- const { prelude, propsType, propsDestructure, jsx, isTsx } = buildModuleParts(root, options);
453
- const parts = [...prelude, propsType];
454
- if (!isTsx) {
455
- parts.push(`/** @param {any} props */`);
456
- }
457
- const functionLines = [
458
- isTsx ? "export function render(props: any) {" : "export function render(props) {"
459
- ];
460
- if (propsDestructure) {
461
- functionLines.push(` ${propsDestructure}`);
259
+ function tokenizeExpression(expression) {
260
+ const tokens = [];
261
+ let i = 0;
262
+ while (i < expression.length) {
263
+ const ch = expression[i];
264
+ if (/\s/.test(ch)) {
265
+ i++;
266
+ continue;
267
+ }
268
+ if (ch === "/" && expression[i + 1] === "/") {
269
+ i += 2;
270
+ while (i < expression.length && expression[i] !== "\n") {
271
+ i++;
272
+ }
273
+ continue;
274
+ }
275
+ if (ch === "/" && expression[i + 1] === "*") {
276
+ i += 2;
277
+ while (i < expression.length && !(expression[i] === "*" && expression[i + 1] === "/")) {
278
+ i++;
279
+ }
280
+ i += 2;
281
+ continue;
282
+ }
283
+ if (ch === "'" || ch === '"') {
284
+ i = skipStringLiteral(expression, i, ch);
285
+ tokens.push({ type: "literal", value: "string" });
286
+ continue;
287
+ }
288
+ if (ch === "`") {
289
+ i = skipTemplateLiteral(expression, i);
290
+ tokens.push({ type: "literal", value: "template" });
291
+ continue;
292
+ }
293
+ if (isIdentifierStart(ch)) {
294
+ const start = i;
295
+ i++;
296
+ while (i < expression.length && isIdentifierPart(expression[i])) {
297
+ i++;
298
+ }
299
+ tokens.push({ type: "identifier", value: expression.slice(start, i) });
300
+ continue;
301
+ }
302
+ if (/[0-9]/.test(ch)) {
303
+ const start = i;
304
+ i++;
305
+ while (i < expression.length && /[0-9._]/.test(expression[i])) {
306
+ i++;
307
+ }
308
+ tokens.push({ type: "literal", value: expression.slice(start, i) });
309
+ continue;
310
+ }
311
+ if (expression.startsWith("...", i)) {
312
+ tokens.push({ type: "punctuator", value: "..." });
313
+ i += 3;
314
+ continue;
315
+ }
316
+ if (expression.startsWith("=>", i)) {
317
+ tokens.push({ type: "punctuator", value: "=>" });
318
+ i += 2;
319
+ continue;
320
+ }
321
+ if (expression.startsWith("?.", i)) {
322
+ tokens.push({ type: "punctuator", value: "?." });
323
+ i += 2;
324
+ continue;
325
+ }
326
+ if (expression.startsWith("??", i)) {
327
+ tokens.push({ type: "punctuator", value: "??" });
328
+ i += 2;
329
+ continue;
330
+ }
331
+ tokens.push({ type: "punctuator", value: ch });
332
+ i++;
462
333
  }
463
- functionLines.push(` return ${jsx};`, `}`);
464
- parts.push(functionLines.join("\n"));
465
- return parts.join("\n\n");
334
+ return tokens;
466
335
  }
467
- function buildModuleParts(root, options) {
468
- const { jsxRuntime, flavor } = options;
469
- const isTsx = flavor === "tsx";
470
- const aliasEnv = buildClassAliasEnvironment(root.classAliases);
471
- const env = createTemplateEnv(root.propsDecls);
472
- const jsx = renderRootChildren(root.children, aliasEnv, env);
473
- const propsDestructure = emitPropsDestructure(root.props);
474
- const prelude = [];
475
- if (root.clientComponent) {
476
- prelude.push(`"use client";`);
477
- }
478
- if (jsxRuntime === "classic" && templateUsesJsx(root)) {
479
- prelude.push(`import React from "react";`);
336
+ function skipStringLiteral(source, start, quote) {
337
+ let i = start + 1;
338
+ while (i < source.length) {
339
+ const ch = source[i];
340
+ if (ch === "\\") {
341
+ i += 2;
342
+ continue;
343
+ }
344
+ if (ch === quote) {
345
+ return i + 1;
346
+ }
347
+ i++;
480
348
  }
481
- const propsType = emitPropsType(root.props, flavor);
482
- return { prelude, propsType, propsDestructure, jsx, isTsx };
349
+ return source.length;
483
350
  }
484
- function buildClassAliasEnvironment(decl) {
485
- const env = /* @__PURE__ */ new Map();
486
- if (!decl) {
487
- return env;
351
+ function skipTemplateLiteral(source, start) {
352
+ let i = start + 1;
353
+ while (i < source.length) {
354
+ const ch = source[i];
355
+ if (ch === "\\") {
356
+ i += 2;
357
+ continue;
358
+ }
359
+ if (ch === "`") {
360
+ return i + 1;
361
+ }
362
+ i++;
488
363
  }
489
- for (const alias of decl.aliases) {
490
- env.set(alias.name, alias.classes);
364
+ return source.length;
365
+ }
366
+ function analyzeTokens(tokens, start, end, env, scopes, usedBare, callSitesBare, allowStatements) {
367
+ let i = start;
368
+ while (i < end) {
369
+ const token = tokens[i];
370
+ if (token.type === "identifier") {
371
+ if (token.value === "function") {
372
+ const fnResult = parseFunctionExpression(tokens, i, end, env, scopes, usedBare, callSitesBare);
373
+ if (fnResult > i) {
374
+ i = fnResult;
375
+ continue;
376
+ }
377
+ }
378
+ if (allowStatements && (token.value === "const" || token.value === "let" || token.value === "var")) {
379
+ const declResult = parseVariableDeclaration(tokens, i, end, env, scopes, usedBare, callSitesBare);
380
+ if (declResult > i) {
381
+ i = declResult;
382
+ continue;
383
+ }
384
+ }
385
+ if (allowStatements && token.value === "catch") {
386
+ const catchResult = parseCatchClause(tokens, i, end, env, scopes, usedBare, callSitesBare);
387
+ if (catchResult > i) {
388
+ i = catchResult;
389
+ continue;
390
+ }
391
+ }
392
+ const arrowResult = parseArrowFunction(tokens, i, end, env, scopes, usedBare, callSitesBare);
393
+ if (arrowResult > i) {
394
+ i = arrowResult;
395
+ continue;
396
+ }
397
+ if (shouldIgnoreIdentifier(token.value)) {
398
+ i++;
399
+ continue;
400
+ }
401
+ if (isShadowed(env, scopes, token.value)) {
402
+ i++;
403
+ continue;
404
+ }
405
+ if (isMemberAccess(tokens, i) || isObjectKey(tokens, i)) {
406
+ i++;
407
+ continue;
408
+ }
409
+ usedBare.add(token.value);
410
+ if (isCallSite(tokens, i)) {
411
+ callSitesBare.add(token.value);
412
+ }
413
+ i++;
414
+ continue;
415
+ }
416
+ if (token.type === "punctuator" && token.value === "(") {
417
+ const arrowResult = parseArrowFunction(tokens, i, end, env, scopes, usedBare, callSitesBare);
418
+ if (arrowResult > i) {
419
+ i = arrowResult;
420
+ continue;
421
+ }
422
+ }
423
+ i++;
491
424
  }
492
- return env;
493
425
  }
494
- function renderRootChildren(children, aliasEnv, env) {
495
- return emitNodesExpression(children, aliasEnv, env);
496
- }
497
- function templateUsesJsx(root) {
498
- if (root.children.length === 0) {
499
- return false;
426
+ function parseArrowFunction(tokens, index, end, env, scopes, usedBare, callSitesBare) {
427
+ const token = tokens[index];
428
+ if (!token) {
429
+ return index;
500
430
  }
501
- if (root.children.length > 1) {
502
- return true;
431
+ if (token.type === "identifier") {
432
+ const next = tokens[index + 1];
433
+ if (next && next.value === "=>") {
434
+ const params = /* @__PURE__ */ new Set([token.value]);
435
+ return analyzeArrowBody(tokens, index + 2, end, params, env, scopes, usedBare, callSitesBare);
436
+ }
503
437
  }
504
- return nodeUsesJsx(root.children[0]);
505
- }
506
- function nodeUsesJsx(node) {
507
- if (node.type === "Element" || node.type === "Text" || node.type === "Component") {
508
- return true;
438
+ if (token.type === "punctuator" && token.value === "(") {
439
+ const closeIndex = findMatchingToken(tokens, index, "(", ")");
440
+ if (closeIndex !== -1) {
441
+ const afterClose = tokens[closeIndex + 1];
442
+ if (afterClose && afterClose.value === "=>") {
443
+ const params = /* @__PURE__ */ new Set();
444
+ collectBindingNamesFromList(tokens, index + 1, closeIndex, params);
445
+ return analyzeArrowBody(tokens, closeIndex + 2, end, params, env, scopes, usedBare, callSitesBare);
446
+ }
447
+ }
509
448
  }
510
- if (node.type === "Expression" || node.type === "JSXPassthrough") {
511
- return false;
449
+ return index;
450
+ }
451
+ function analyzeArrowBody(tokens, start, end, params, env, scopes, usedBare, callSitesBare) {
452
+ const bodyToken = tokens[start];
453
+ if (!bodyToken) {
454
+ return start;
455
+ }
456
+ const scope = new Set(params);
457
+ scopes.push(scope);
458
+ if (bodyToken.type === "punctuator" && bodyToken.value === "{") {
459
+ const closeIndex = findMatchingToken(tokens, start, "{", "}");
460
+ const bodyEnd2 = closeIndex === -1 ? end : closeIndex;
461
+ analyzeTokens(tokens, start + 1, bodyEnd2, env, scopes, usedBare, callSitesBare, true);
462
+ scopes.pop();
463
+ return closeIndex === -1 ? end : closeIndex + 1;
464
+ }
465
+ const bodyEnd = findExpressionEnd(tokens, start, end, EXPRESSION_TERMINATORS);
466
+ analyzeTokens(tokens, start, bodyEnd, env, scopes, usedBare, callSitesBare, false);
467
+ scopes.pop();
468
+ return bodyEnd;
469
+ }
470
+ function parseFunctionExpression(tokens, index, end, env, scopes, usedBare, callSitesBare) {
471
+ let i = index + 1;
472
+ const nameToken = tokens[i];
473
+ let fnName;
474
+ if (nameToken && nameToken.type === "identifier" && tokens[i + 1]?.value === "(") {
475
+ fnName = nameToken.value;
476
+ i++;
512
477
  }
513
- if (node.type === "Conditional") {
514
- return node.branches.some((branch) => branchUsesJsx(branch));
478
+ if (!tokens[i] || tokens[i].value !== "(") {
479
+ return index;
515
480
  }
516
- if (node.type === "For") {
517
- return node.body.some((child) => nodeUsesJsx(child));
481
+ const closeIndex = findMatchingToken(tokens, i, "(", ")");
482
+ if (closeIndex === -1) {
483
+ return index;
484
+ }
485
+ const params = /* @__PURE__ */ new Set();
486
+ collectBindingNamesFromList(tokens, i + 1, closeIndex, params);
487
+ if (fnName) {
488
+ params.add(fnName);
489
+ }
490
+ const bodyStart = closeIndex + 1;
491
+ if (!tokens[bodyStart] || tokens[bodyStart].value !== "{") {
492
+ return index;
493
+ }
494
+ const closeBody = findMatchingToken(tokens, bodyStart, "{", "}");
495
+ const bodyEnd = closeBody === -1 ? end : closeBody;
496
+ scopes.push(params);
497
+ analyzeTokens(tokens, bodyStart + 1, bodyEnd, env, scopes, usedBare, callSitesBare, true);
498
+ scopes.pop();
499
+ return closeBody === -1 ? end : closeBody + 1;
500
+ }
501
+ function parseCatchClause(tokens, index, end, env, scopes, usedBare, callSitesBare) {
502
+ const next = tokens[index + 1];
503
+ if (!next || next.value !== "(") {
504
+ return index;
505
+ }
506
+ const closeIndex = findMatchingToken(tokens, index + 1, "(", ")");
507
+ if (closeIndex === -1) {
508
+ return index;
509
+ }
510
+ const params = /* @__PURE__ */ new Set();
511
+ collectBindingNamesFromList(tokens, index + 2, closeIndex, params);
512
+ const bodyStart = closeIndex + 1;
513
+ if (!tokens[bodyStart] || tokens[bodyStart].value !== "{") {
514
+ return index;
515
+ }
516
+ const closeBody = findMatchingToken(tokens, bodyStart, "{", "}");
517
+ const bodyEnd = closeBody === -1 ? end : closeBody;
518
+ scopes.push(params);
519
+ analyzeTokens(tokens, bodyStart + 1, bodyEnd, env, scopes, usedBare, callSitesBare, true);
520
+ scopes.pop();
521
+ return closeBody === -1 ? end : closeBody + 1;
522
+ }
523
+ function parseVariableDeclaration(tokens, index, end, env, scopes, usedBare, callSitesBare) {
524
+ let i = index + 1;
525
+ const scope = scopes[scopes.length - 1];
526
+ while (i < end) {
527
+ const names = /* @__PURE__ */ new Set();
528
+ const nextIndex = parseBindingPattern(tokens, i, end, names);
529
+ if (nextIndex === i) {
530
+ return index;
531
+ }
532
+ i = nextIndex;
533
+ if (tokens[i] && tokens[i].value === "=") {
534
+ const initStart = i + 1;
535
+ const initEnd = findExpressionEnd(tokens, initStart, end, DECLARATION_TERMINATORS);
536
+ analyzeTokens(tokens, initStart, initEnd, env, scopes, usedBare, callSitesBare, false);
537
+ i = initEnd;
538
+ }
539
+ for (const name of names) {
540
+ scope.add(name);
541
+ }
542
+ if (tokens[i] && tokens[i].value === ",") {
543
+ i++;
544
+ continue;
545
+ }
546
+ break;
518
547
  }
519
- return false;
548
+ return i;
520
549
  }
521
- function branchUsesJsx(branch) {
522
- if (!branch.body.length) {
523
- return false;
550
+ function parseBindingPattern(tokens, start, end, names) {
551
+ const token = tokens[start];
552
+ if (!token) {
553
+ return start;
524
554
  }
525
- return branch.body.some((child) => nodeUsesJsx(child));
526
- }
527
- function emitNodeInJsx(node, aliasEnv, env) {
528
- if (node.type === "Text") {
529
- return emitText(node, env);
555
+ if (token.type === "identifier") {
556
+ names.add(token.value);
557
+ return start + 1;
530
558
  }
531
- if (node.type === "Expression") {
532
- return `{${emitExpressionValue(node.value, env)}}`;
559
+ if (token.value === "{") {
560
+ return parseObjectPattern(tokens, start + 1, end, names);
533
561
  }
534
- if (node.type === "JSXPassthrough") {
535
- return `{${emitJsxExpression(node.expression, env)}}`;
562
+ if (token.value === "[") {
563
+ return parseArrayPattern(tokens, start + 1, end, names);
536
564
  }
537
- if (node.type === "Conditional") {
538
- return `{${emitConditionalExpression(node, aliasEnv, env)}}`;
565
+ return start + 1;
566
+ }
567
+ function parseObjectPattern(tokens, start, end, names) {
568
+ let i = start;
569
+ while (i < end) {
570
+ const token = tokens[i];
571
+ if (!token) {
572
+ return i;
573
+ }
574
+ if (token.value === "}") {
575
+ return i + 1;
576
+ }
577
+ if (token.value === ",") {
578
+ i++;
579
+ continue;
580
+ }
581
+ if (token.value === "...") {
582
+ i++;
583
+ i = parseBindingPattern(tokens, i, end, names);
584
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
585
+ continue;
586
+ }
587
+ if (token.value === "[") {
588
+ const closeIndex = findMatchingToken(tokens, i, "[", "]");
589
+ i = closeIndex === -1 ? end : closeIndex + 1;
590
+ if (tokens[i] && tokens[i].value === ":") {
591
+ i++;
592
+ i = parseBindingPattern(tokens, i, end, names);
593
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
594
+ }
595
+ continue;
596
+ }
597
+ if (token.type === "identifier" || token.type === "literal") {
598
+ const key = token.value;
599
+ i++;
600
+ if (tokens[i] && tokens[i].value === ":") {
601
+ i++;
602
+ i = parseBindingPattern(tokens, i, end, names);
603
+ } else if (token.type === "identifier") {
604
+ names.add(key);
605
+ }
606
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
607
+ continue;
608
+ }
609
+ i++;
610
+ }
611
+ return i;
612
+ }
613
+ function parseArrayPattern(tokens, start, end, names) {
614
+ let i = start;
615
+ while (i < end) {
616
+ const token = tokens[i];
617
+ if (!token) {
618
+ return i;
619
+ }
620
+ if (token.value === "]") {
621
+ return i + 1;
622
+ }
623
+ if (token.value === ",") {
624
+ i++;
625
+ continue;
626
+ }
627
+ if (token.value === "...") {
628
+ i++;
629
+ i = parseBindingPattern(tokens, i, end, names);
630
+ i = skipDefaultValue(tokens, i, end, ARRAY_PATTERN_TERMINATORS);
631
+ continue;
632
+ }
633
+ i = parseBindingPattern(tokens, i, end, names);
634
+ i = skipDefaultValue(tokens, i, end, ARRAY_PATTERN_TERMINATORS);
635
+ if (tokens[i] && tokens[i].value === ",") {
636
+ i++;
637
+ }
638
+ }
639
+ return i;
640
+ }
641
+ function collectBindingNamesFromList(tokens, start, end, names) {
642
+ let i = start;
643
+ while (i < end) {
644
+ if (tokens[i] && tokens[i].value === ",") {
645
+ i++;
646
+ continue;
647
+ }
648
+ const nextIndex = parseBindingPattern(tokens, i, end, names);
649
+ if (nextIndex === i) {
650
+ i++;
651
+ continue;
652
+ }
653
+ i = skipParameterSuffix(tokens, nextIndex, end);
654
+ if (tokens[i] && tokens[i].value === ",") {
655
+ i++;
656
+ }
657
+ }
658
+ }
659
+ function skipParameterSuffix(tokens, start, end) {
660
+ if (!tokens[start]) {
661
+ return start;
662
+ }
663
+ if (tokens[start].value === "=" || tokens[start].value === ":") {
664
+ return findExpressionEnd(tokens, start + 1, end, PARAMETER_TERMINATORS);
665
+ }
666
+ return start;
667
+ }
668
+ function skipDefaultValue(tokens, start, end, terminators) {
669
+ if (!tokens[start] || tokens[start].value !== "=") {
670
+ return start;
671
+ }
672
+ return findExpressionEnd(tokens, start + 1, end, terminators);
673
+ }
674
+ function findMatchingToken(tokens, start, open, close) {
675
+ let depth = 0;
676
+ for (let i = start; i < tokens.length; i++) {
677
+ const value = tokens[i].value;
678
+ if (value === open) {
679
+ depth++;
680
+ } else if (value === close) {
681
+ depth--;
682
+ if (depth === 0) {
683
+ return i;
684
+ }
685
+ }
686
+ }
687
+ return -1;
688
+ }
689
+ function findExpressionEnd(tokens, start, end, terminators) {
690
+ let depthParen = 0;
691
+ let depthBracket = 0;
692
+ let depthBrace = 0;
693
+ for (let i = start; i < end; i++) {
694
+ const value = tokens[i].value;
695
+ if (value === "(") {
696
+ depthParen++;
697
+ } else if (value === ")") {
698
+ if (depthParen === 0 && terminators.has(value)) {
699
+ return i;
700
+ }
701
+ depthParen = Math.max(0, depthParen - 1);
702
+ } else if (value === "[") {
703
+ depthBracket++;
704
+ } else if (value === "]") {
705
+ if (depthBracket === 0 && terminators.has(value)) {
706
+ return i;
707
+ }
708
+ depthBracket = Math.max(0, depthBracket - 1);
709
+ } else if (value === "{") {
710
+ depthBrace++;
711
+ } else if (value === "}") {
712
+ if (depthBrace === 0 && terminators.has(value)) {
713
+ return i;
714
+ }
715
+ depthBrace = Math.max(0, depthBrace - 1);
716
+ }
717
+ if (depthParen === 0 && depthBracket === 0 && depthBrace === 0 && terminators.has(value)) {
718
+ return i;
719
+ }
720
+ }
721
+ return end;
722
+ }
723
+ function isMemberAccess(tokens, index) {
724
+ const prev = tokens[index - 1];
725
+ return prev?.value === "." || prev?.value === "?.";
726
+ }
727
+ function isObjectKey(tokens, index) {
728
+ const next = tokens[index + 1];
729
+ if (!next || next.value !== ":") {
730
+ return false;
731
+ }
732
+ const prev = tokens[index - 1];
733
+ return prev?.value === "{" || prev?.value === ",";
734
+ }
735
+ function isCallSite(tokens, index) {
736
+ const next = tokens[index + 1];
737
+ return next?.value === "(";
738
+ }
739
+ function isShadowed(env, scopes, name) {
740
+ if (isLocal(env, name)) {
741
+ return true;
742
+ }
743
+ for (let i = scopes.length - 1; i >= 0; i--) {
744
+ if (scopes[i].has(name)) {
745
+ return true;
746
+ }
747
+ }
748
+ return false;
749
+ }
750
+ var PARAMETER_TERMINATORS = /* @__PURE__ */ new Set([","]);
751
+ var OBJECT_PATTERN_TERMINATORS = /* @__PURE__ */ new Set([",", "}"]);
752
+ var ARRAY_PATTERN_TERMINATORS = /* @__PURE__ */ new Set([",", "]"]);
753
+ var EXPRESSION_TERMINATORS = /* @__PURE__ */ new Set([",", ")", "]", "}", ";"]);
754
+ var DECLARATION_TERMINATORS = /* @__PURE__ */ new Set([",", ")", "]", "}", ";"]);
755
+
756
+ // src/codegen.ts
757
+ function generateRenderModule(root, options) {
758
+ const { prelude, inputsType, inputsPrelude, jsx, isTsx } = buildModuleParts(root, options);
759
+ const parts = [...prelude, inputsType];
760
+ if (!isTsx) {
761
+ parts.push(`/** @param {any} __inputs */`);
762
+ }
763
+ const functionLines = [
764
+ isTsx ? "export function render(__inputs: any) {" : "export function render(__inputs) {"
765
+ ];
766
+ if (inputsPrelude) {
767
+ functionLines.push(` ${inputsPrelude}`);
768
+ }
769
+ functionLines.push(` return ${jsx};`, `}`);
770
+ parts.push(functionLines.join("\n"));
771
+ return parts.join("\n\n");
772
+ }
773
+ function buildModuleParts(root, options) {
774
+ const { jsxRuntime, flavor } = options;
775
+ const isTsx = flavor === "tsx";
776
+ const aliasEnv = buildClassAliasEnvironment(root.classAliases);
777
+ const env = createTemplateEnv(root.inputsDecls);
778
+ const jsx = renderRootChildren(root.children, aliasEnv, env);
779
+ const inputsPrelude = emitInputsPrelude(root.inputsDecls);
780
+ const prelude = [];
781
+ if (root.clientComponent) {
782
+ prelude.push(`"use client";`);
783
+ }
784
+ if (jsxRuntime === "classic" && templateUsesJsx(root)) {
785
+ prelude.push(`import React from "react";`);
786
+ }
787
+ const inputsType = emitInputsType(root.inputs, flavor);
788
+ return { prelude, inputsType, inputsPrelude, jsx, isTsx };
789
+ }
790
+ function buildClassAliasEnvironment(decl) {
791
+ const env = /* @__PURE__ */ new Map();
792
+ if (!decl) {
793
+ return env;
794
+ }
795
+ for (const alias of decl.aliases) {
796
+ env.set(alias.name, alias.classes);
797
+ }
798
+ return env;
799
+ }
800
+ function renderRootChildren(children, aliasEnv, env) {
801
+ return emitNodesExpression(children, aliasEnv, env);
802
+ }
803
+ function templateUsesJsx(root) {
804
+ if (root.children.length === 0) {
805
+ return false;
806
+ }
807
+ if (root.children.length > 1) {
808
+ return true;
809
+ }
810
+ return nodeUsesJsx(root.children[0]);
811
+ }
812
+ function nodeUsesJsx(node) {
813
+ if (node.type === "Element" || node.type === "Text" || node.type === "Component") {
814
+ return true;
815
+ }
816
+ if (node.type === "Expression" || node.type === "JSXPassthrough") {
817
+ return false;
818
+ }
819
+ if (node.type === "Conditional") {
820
+ return node.branches.some((branch) => branchUsesJsx(branch));
821
+ }
822
+ if (node.type === "For") {
823
+ return node.body.some((child) => nodeUsesJsx(child));
824
+ }
825
+ return false;
826
+ }
827
+ function branchUsesJsx(branch) {
828
+ if (!branch.body.length) {
829
+ return false;
830
+ }
831
+ return branch.body.some((child) => nodeUsesJsx(child));
832
+ }
833
+ function emitNodeInJsx(node, aliasEnv, env) {
834
+ if (node.type === "Text") {
835
+ return emitText(node, env);
836
+ }
837
+ if (node.type === "Expression") {
838
+ return `{${emitExpressionValue(node.value, env)}}`;
839
+ }
840
+ if (node.type === "JSXPassthrough") {
841
+ return `{${emitJsxExpression(node.expression, env)}}`;
842
+ }
843
+ if (node.type === "Conditional") {
844
+ return `{${emitConditionalExpression(node, aliasEnv, env)}}`;
539
845
  }
540
846
  if (node.type === "For") {
541
847
  return `{${emitForExpression(node, aliasEnv, env)}}`;
@@ -559,8 +865,8 @@ function emitElement(node, aliasEnv, env) {
559
865
  }
560
866
  function emitComponent(node, aliasEnv, env) {
561
867
  const attrs = emitAttributes(node.attributes, aliasEnv, env);
562
- const slotProps = emitSlotProps(node, aliasEnv, env);
563
- const allAttrs = `${attrs}${slotProps}`;
868
+ const slotBindings = emitSlotBindings(node, aliasEnv, env);
869
+ const allAttrs = `${attrs}${slotBindings}`;
564
870
  const children = emitChildrenWithSpacing(node.children, aliasEnv, env);
565
871
  if (children.length > 0) {
566
872
  return `<${node.name}${allAttrs}>${children}</${node.name}>`;
@@ -598,7 +904,7 @@ function emitAttributes(attributes, aliasEnv, env) {
598
904
  return ` ${attr.name}=${emitAttributeValue(attr.value, env)}`;
599
905
  }).join("");
600
906
  }
601
- function emitSlotProps(node, aliasEnv, env) {
907
+ function emitSlotBindings(node, aliasEnv, env) {
602
908
  if (!node.slots || node.slots.length === 0) {
603
909
  return "";
604
910
  }
@@ -729,41 +1035,41 @@ function emitSingleNodeExpression(node, aliasEnv, env) {
729
1035
  }
730
1036
  return emitNodeInJsx(node, aliasEnv, env);
731
1037
  }
732
- function emitPropsType(props, flavor) {
1038
+ function emitInputsType(inputs, flavor) {
733
1039
  if (flavor === "tsx") {
734
- return emitTsPropsType(props);
1040
+ return emitTsInputsType(inputs);
735
1041
  }
736
- return emitJsDocPropsType(props);
1042
+ return emitJsDocInputsType(inputs);
737
1043
  }
738
- function emitJsDocPropsType(props) {
739
- if (!props) {
740
- return "/** @typedef {any} Props */";
1044
+ function emitJsDocInputsType(inputs) {
1045
+ if (!inputs) {
1046
+ return "/** @typedef {any} Inputs */";
741
1047
  }
742
- if (!props.fields.length) {
743
- return "/** @typedef {{}} Props */";
1048
+ if (!inputs.fields.length) {
1049
+ return "/** @typedef {{}} Inputs */";
744
1050
  }
745
- const fields = props.fields.map((field) => {
1051
+ const fields = inputs.fields.map((field) => {
746
1052
  const optional = field.optional ? "?" : "";
747
1053
  return `${field.name}${optional}: ${field.typeText}`;
748
1054
  }).join("; ");
749
- return `/** @typedef {{ ${fields} }} Props */`;
1055
+ return `/** @typedef {{ ${fields} }} Inputs */`;
750
1056
  }
751
- function emitTsPropsType(props) {
752
- if (!props || props.fields.length === 0) {
753
- return "export type Props = Record<string, never>;";
1057
+ function emitTsInputsType(inputs) {
1058
+ if (!inputs || inputs.fields.length === 0) {
1059
+ return "export type Inputs = Record<string, never>;";
754
1060
  }
755
- const lines = props.fields.map((field) => {
1061
+ const lines = inputs.fields.map((field) => {
756
1062
  const optional = field.optional ? "?" : "";
757
1063
  return ` ${field.name}${optional}: ${field.typeText};`;
758
1064
  });
759
- return ["export interface Props {", ...lines, "}"].join("\n");
1065
+ return ["export interface Inputs {", ...lines, "}"].join("\n");
760
1066
  }
761
- function emitPropsDestructure(props) {
762
- if (!props || props.fields.length === 0) {
1067
+ function emitInputsPrelude(inputsDecls) {
1068
+ if (!inputsDecls || inputsDecls.length === 0) {
763
1069
  return null;
764
1070
  }
765
- const names = props.fields.map((field) => field.name);
766
- return `const { ${names.join(", ")} } = props ?? {};`;
1071
+ const names = inputsDecls.map((decl) => decl.name);
1072
+ return `const { ${names.join(", ")} } = __inputs ?? {};`;
767
1073
  }
768
1074
  function escapeText(value) {
769
1075
  return value.replace(/[&<>{}]/g, (char) => {
@@ -925,836 +1231,196 @@ function unescapeChar(char, quote) {
925
1231
  default:
926
1232
  if (char === quote) {
927
1233
  return quote;
928
- }
929
- return char;
930
- }
931
- }
932
- function buildClassAliasEnvironment2(decl) {
933
- const env = /* @__PURE__ */ new Map();
934
- if (!decl) {
935
- return env;
936
- }
937
- for (const alias of decl.aliases) {
938
- env.set(alias.name, alias.classes);
939
- }
940
- return env;
941
- }
942
- function expandClasses2(classes, aliasEnv) {
943
- const result = [];
944
- for (const cls of classes) {
945
- const match = cls.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/);
946
- if (!match) {
947
- result.push(cls);
948
- continue;
949
- }
950
- const aliasClasses = aliasEnv.get(match[1]);
951
- if (!aliasClasses) {
952
- continue;
953
- }
954
- result.push(...aliasClasses);
955
- }
956
- return result;
957
- }
958
- function escapeStaticText(value) {
959
- return value.replace(/[&<>{}]/g, (char) => {
960
- switch (char) {
961
- case "&":
962
- return "&amp;";
963
- case "<":
964
- return "&lt;";
965
- case ">":
966
- return "&gt;";
967
- case "{":
968
- return "&#123;";
969
- case "}":
970
- return "&#125;";
971
- default:
972
- return char;
973
- }
974
- });
975
- }
976
- function escapeAttributeValue(value) {
977
- return value.replace(/["&<>]/g, (char) => {
978
- switch (char) {
979
- case "&":
980
- return "&amp;";
981
- case "<":
982
- return "&lt;";
983
- case ">":
984
- return "&gt;";
985
- case '"':
986
- return "&quot;";
987
- default:
988
- return char;
989
- }
990
- });
991
- }
992
-
993
- // src/diagnostics.ts
994
- function createSpan(line, col, length, lineOffset) {
995
- const startOffset = lineOffset + col - 1;
996
- return {
997
- start: { line, col, offset: startOffset },
998
- end: { line, col: col + length, offset: startOffset + length }
999
- };
1000
- }
1001
-
1002
- // src/dialect.ts
1003
- function enforceDialect(root, config) {
1004
- const diagnostics = [];
1005
- if (root.idToken) {
1006
- diagnostics.push(
1007
- ...evaluateToken(
1008
- { kind: "id", token: root.idToken, span: root.idTokenSpan },
1009
- config.tokens.id
1010
- )
1011
- );
1012
- }
1013
- walkNodes(root.children, (occurrence) => {
1014
- const rule = config.tokens[occurrence.kind];
1015
- diagnostics.push(...evaluateToken(occurrence, rule));
1016
- });
1017
- return diagnostics;
1018
- }
1019
- function walkNodes(nodes, onToken) {
1020
- for (const node of nodes) {
1021
- if (node.type === "For") {
1022
- onFor(node, onToken);
1023
- walkNodes(node.body, onToken);
1024
- continue;
1025
- }
1026
- if (node.type === "Conditional") {
1027
- onConditional(node, onToken);
1028
- continue;
1029
- }
1030
- if (node.type === "Element" || node.type === "Component") {
1031
- walkNodes(node.children, onToken);
1032
- if (node.type === "Component" && node.slots) {
1033
- for (const slot of node.slots) {
1034
- walkNodes(slot.children, onToken);
1035
- }
1036
- }
1037
- continue;
1038
- }
1039
- }
1040
- }
1041
- function onFor(node, onToken) {
1042
- if (!node.token) {
1043
- return;
1044
- }
1045
- onToken({ kind: "for", token: node.token, span: node.tokenSpan });
1046
- }
1047
- function onConditional(node, onToken) {
1048
- for (const branch of node.branches) {
1049
- onBranch(branch, onToken);
1050
- walkNodes(branch.body, onToken);
1051
- }
1052
- }
1053
- function onBranch(branch, onToken) {
1054
- if (!branch.token || !branch.kind) {
1055
- return;
1056
- }
1057
- onToken({ kind: branch.kind, token: branch.token, span: branch.tokenSpan });
1058
- }
1059
- function evaluateToken(occurrence, rule) {
1060
- const diagnostics = [];
1061
- const used = occurrence.token;
1062
- const preferred = rule.preferred;
1063
- const isAllowed = rule.allow.includes(used);
1064
- if (!isAllowed) {
1065
- const severity = levelToSeverity(rule.onDisallowed);
1066
- if (severity) {
1067
- diagnostics.push(
1068
- createDialectDiagnostic(
1069
- "dialect.token.disallowed",
1070
- severity,
1071
- used,
1072
- preferred,
1073
- occurrence.span,
1074
- `Token "${used}" is not allowed for ${occurrence.kind}. Preferred: "${preferred}".`
1075
- )
1076
- );
1077
- }
1078
- return diagnostics;
1079
- }
1080
- if (used !== preferred) {
1081
- const severity = levelToSeverity(rule.onDisallowed);
1082
- if (severity) {
1083
- diagnostics.push(
1084
- createDialectDiagnostic(
1085
- "dialect.token.nonPreferred",
1086
- severity,
1087
- used,
1088
- preferred,
1089
- occurrence.span,
1090
- `Token "${used}" is allowed but not preferred for ${occurrence.kind}. Preferred: "${preferred}".`
1091
- )
1092
- );
1093
- }
1094
- }
1095
- return diagnostics;
1096
- }
1097
- function createDialectDiagnostic(code, severity, used, preferred, span, message) {
1098
- const fix = span ? {
1099
- range: span,
1100
- replacementText: preferred
1101
- } : void 0;
1102
- return {
1103
- severity,
1104
- code,
1105
- message: message.replace(/\\s+/g, " "),
1106
- span,
1107
- fix
1108
- };
1109
- }
1110
- function levelToSeverity(level) {
1111
- if (level === "off") {
1112
- return null;
1113
- }
1114
- if (level === "error") {
1115
- return "error";
1116
- }
1117
- return "warning";
1118
- }
1119
-
1120
- // src/props.ts
1121
- var IGNORED_IDENTIFIERS2 = /* @__PURE__ */ new Set([
1122
- "null",
1123
- "undefined",
1124
- "true",
1125
- "false",
1126
- "NaN",
1127
- "Infinity",
1128
- "this",
1129
- "props"
1130
- ]);
1131
- var RESERVED_KEYWORDS2 = /* @__PURE__ */ new Set([
1132
- "await",
1133
- "break",
1134
- "case",
1135
- "catch",
1136
- "class",
1137
- "const",
1138
- "continue",
1139
- "debugger",
1140
- "default",
1141
- "delete",
1142
- "do",
1143
- "else",
1144
- "enum",
1145
- "export",
1146
- "extends",
1147
- "false",
1148
- "finally",
1149
- "for",
1150
- "function",
1151
- "if",
1152
- "import",
1153
- "in",
1154
- "instanceof",
1155
- "let",
1156
- "new",
1157
- "null",
1158
- "return",
1159
- "super",
1160
- "switch",
1161
- "this",
1162
- "throw",
1163
- "true",
1164
- "try",
1165
- "typeof",
1166
- "var",
1167
- "void",
1168
- "while",
1169
- "with",
1170
- "yield"
1171
- ]);
1172
- function enforceProps(root, propsConfig) {
1173
- const diagnostics = [];
1174
- const declaredProps = /* @__PURE__ */ new Map();
1175
- const usedLocal = /* @__PURE__ */ new Map();
1176
- const usedNamespace = /* @__PURE__ */ new Map();
1177
- const usedAny = /* @__PURE__ */ new Set();
1178
- const missingReported = /* @__PURE__ */ new Set();
1179
- const localStyleReported = /* @__PURE__ */ new Set();
1180
- const namespaceStyleReported = /* @__PURE__ */ new Set();
1181
- if (root.props?.fields) {
1182
- for (const field of root.props.fields) {
1183
- declaredProps.set(field.name, field);
1184
- }
1185
- }
1186
- const preferStyle = propsConfig.preferAccessStyle;
1187
- const flagLocalStyle = !propsConfig.allowDeclaredLocals || preferStyle === "namespace";
1188
- const flagNamespaceStyle = !propsConfig.allowPropsNamespace || preferStyle === "locals";
1189
- const walkNodes2 = (nodes, locals) => {
1190
- for (const node of nodes) {
1191
- if (node.type === "Conditional") {
1192
- handleConditional(node, locals);
1193
- continue;
1194
- }
1195
- if (node.type === "For") {
1196
- handleFor(node, locals);
1197
- continue;
1198
- }
1199
- if (node.type === "Expression") {
1200
- handleExpression(node.value, node.span, locals);
1201
- continue;
1202
- }
1203
- if (node.type === "JSXPassthrough") {
1204
- handleExpression(node.expression, node.span, locals);
1205
- continue;
1206
- }
1207
- if (node.type === "Text") {
1208
- handleText(node.parts, locals);
1209
- continue;
1210
- }
1211
- if (node.type === "Element") {
1212
- handleElement(node, locals);
1213
- continue;
1214
- }
1215
- if (node.type === "Component") {
1216
- handleComponent(node, locals);
1217
- continue;
1218
- }
1219
- }
1220
- };
1221
- const handleConditional = (node, locals) => {
1222
- for (const branch of node.branches) {
1223
- if (branch.test) {
1224
- handleExpression(branch.test, branch.testSpan, locals);
1225
- }
1226
- walkNodes2(branch.body, locals);
1227
- }
1228
- };
1229
- const handleFor = (node, locals) => {
1230
- handleExpression(node.arrayExpr, node.arrayExprSpan, locals);
1231
- const nextLocals = new Set(locals);
1232
- nextLocals.add(node.itemName);
1233
- walkNodes2(node.body, nextLocals);
1234
- };
1235
- const handleElement = (node, locals) => {
1236
- if (node.guard) {
1237
- handleExpression(node.guard, node.guardSpan, locals);
1238
- }
1239
- handleAttributes(node.attributes, locals);
1240
- walkNodes2(node.children, locals);
1241
- };
1242
- const handleComponent = (node, locals) => {
1243
- if (node.guard) {
1244
- handleExpression(node.guard, node.guardSpan, locals);
1245
- }
1246
- handleAttributes(node.attributes, locals);
1247
- if (node.slots) {
1248
- for (const slot of node.slots) {
1249
- walkNodes2(slot.children, locals);
1250
- }
1251
- }
1252
- walkNodes2(node.children, locals);
1253
- };
1254
- const handleText = (parts, locals) => {
1255
- for (const part of parts) {
1256
- if (part.type === "expr") {
1257
- handleExpression(part.value, part.span, locals);
1258
- }
1259
- }
1260
- };
1261
- const handleAttributes = (attributes, locals) => {
1262
- for (const attr of attributes) {
1263
- if (!attr.value) continue;
1264
- const trimmed = attr.value.trim();
1265
- if (!trimmed || trimmed.startsWith("'") || trimmed.startsWith('"')) {
1266
- continue;
1267
- }
1268
- handleExpression(trimmed, void 0, locals);
1269
- }
1270
- };
1271
- const handleExpression = (expression, span, locals) => {
1272
- const occurrences = scanExpression(expression);
1273
- for (const occurrence of occurrences) {
1274
- const name = occurrence.name;
1275
- if (occurrence.kind === "local" && locals.has(name)) {
1276
- continue;
1277
- }
1278
- if (shouldIgnoreIdentifier2(name)) {
1279
- continue;
1280
- }
1281
- const usageSpan = span ? offsetSpan(span, occurrence.index, occurrence.length) : void 0;
1282
- if (occurrence.kind === "namespace") {
1283
- recordUsage(usedNamespace, name, usageSpan);
1284
- usedAny.add(name);
1285
- if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
1286
- const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
1287
- if (severity) {
1288
- diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
1289
- missingReported.add(name);
1290
- }
1291
- }
1292
- if (flagNamespaceStyle && !namespaceStyleReported.has(name)) {
1293
- const severity = levelToSeverity2(propsConfig.diagnostics.style);
1294
- if (severity) {
1295
- diagnostics.push(
1296
- createStyleDiagnostic(
1297
- name,
1298
- "namespace",
1299
- severity,
1300
- usageSpan,
1301
- propsConfig.allowPropsNamespace
1302
- )
1303
- );
1304
- namespaceStyleReported.add(name);
1305
- }
1306
- }
1307
- continue;
1308
- }
1309
- recordUsage(usedLocal, name, usageSpan);
1310
- usedAny.add(name);
1311
- if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
1312
- const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
1313
- if (severity) {
1314
- diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
1315
- missingReported.add(name);
1316
- }
1317
- }
1318
- if (flagLocalStyle && !localStyleReported.has(name)) {
1319
- const severity = levelToSeverity2(propsConfig.diagnostics.style);
1320
- if (severity) {
1321
- diagnostics.push(
1322
- createStyleDiagnostic(name, "local", severity, usageSpan, propsConfig.allowDeclaredLocals)
1323
- );
1324
- localStyleReported.add(name);
1325
- }
1326
- }
1327
- }
1328
- };
1329
- walkNodes2(root.children, /* @__PURE__ */ new Set());
1330
- if (root.props?.fields) {
1331
- for (const field of root.props.fields) {
1332
- if (!usedAny.has(field.name)) {
1333
- const severity = levelToSeverity2(propsConfig.diagnostics.unusedDeclaration);
1334
- if (severity) {
1335
- diagnostics.push({
1336
- severity,
1337
- code: "props.unusedDeclaration",
1338
- message: `Prop "${field.name}" is declared but never used.`,
1339
- span: field.span
1340
- });
1341
- }
1342
- }
1343
- }
1344
- }
1345
- if (propsConfig.requirePropsBlockWhen.enabled && !root.props && usedAny.size >= propsConfig.requirePropsBlockWhen.minUniquePropsUsed) {
1346
- const severity = levelToSeverity2(propsConfig.requirePropsBlockWhen.severity);
1347
- if (severity) {
1348
- diagnostics.push({
1349
- severity,
1350
- code: "props.block.recommendedOrRequired",
1351
- message: `Props block recommended: ${usedAny.size} unique prop${usedAny.size === 1 ? "" : "s"} used.`
1352
- });
1353
- }
1354
- }
1355
- return diagnostics;
1356
- }
1357
- function createMissingDeclarationDiagnostic(name, severity, span) {
1358
- return {
1359
- severity,
1360
- code: "props.missingDeclaration",
1361
- message: `Prop \`${name}\` is used but not declared in \`#props\`.`,
1362
- span,
1363
- data: {
1364
- kind: "addPropDeclaration",
1365
- propName: name
1366
- }
1367
- };
1368
- }
1369
- function createStyleDiagnostic(name, kind, severity, span, allowed) {
1370
- if (kind === "namespace") {
1371
- const message2 = allowed ? `props.${name} is allowed but not preferred; use "${name}" instead.` : `props.${name} is disabled; use "${name}" instead.`;
1372
- return {
1373
- severity,
1374
- code: "props.style.nonPreferred",
1375
- message: message2,
1376
- span
1377
- };
1378
- }
1379
- const message = allowed ? `"${name}" is allowed but not preferred; use props.${name} instead.` : `"${name}" is disabled; use props.${name} instead.`;
1380
- return {
1381
- severity,
1382
- code: "props.style.nonPreferred",
1383
- message,
1384
- span
1385
- };
1386
- }
1387
- function recordUsage(map, name, span) {
1388
- const existing = map.get(name);
1389
- if (existing) {
1390
- existing.count += 1;
1391
- return;
1392
- }
1393
- map.set(name, { count: 1, span });
1394
- }
1395
- function scanExpression(expression) {
1396
- const occurrences = [];
1397
- let i = 0;
1398
- let state = "code";
1399
- while (i < expression.length) {
1400
- const ch = expression[i];
1401
- if (state === "code") {
1402
- if (ch === "'" || ch === '"') {
1403
- state = ch === "'" ? "single" : "double";
1404
- i++;
1405
- continue;
1406
- }
1407
- if (ch === "`") {
1408
- state = "template";
1409
- i++;
1410
- continue;
1411
- }
1412
- if (ch === "/" && expression[i + 1] === "/") {
1413
- state = "line";
1414
- i += 2;
1415
- continue;
1416
- }
1417
- if (ch === "/" && expression[i + 1] === "*") {
1418
- state = "block";
1419
- i += 2;
1420
- continue;
1421
- }
1422
- if (isIdentifierStart2(ch)) {
1423
- const start = i;
1424
- i++;
1425
- while (i < expression.length && isIdentifierPart2(expression[i])) {
1426
- i++;
1427
- }
1428
- const name = expression.slice(start, i);
1429
- const prevNonSpace = findPreviousNonSpace2(expression, start - 1);
1430
- if (name === "props" && prevNonSpace !== ".") {
1431
- const namespace = readNamespaceAccess(expression, i);
1432
- if (namespace) {
1433
- occurrences.push({
1434
- name: namespace.name,
1435
- kind: "namespace",
1436
- index: namespace.index,
1437
- length: namespace.name.length
1438
- });
1439
- i = namespace.endIndex;
1440
- continue;
1441
- }
1442
- }
1443
- if (prevNonSpace !== ".") {
1444
- occurrences.push({ name, kind: "local", index: start, length: name.length });
1445
- }
1446
- continue;
1447
- }
1448
- i++;
1449
- continue;
1450
- }
1451
- if (state === "line") {
1452
- if (ch === "\n") {
1453
- state = "code";
1454
- }
1455
- i++;
1456
- continue;
1457
- }
1458
- if (state === "block") {
1459
- if (ch === "*" && expression[i + 1] === "/") {
1460
- state = "code";
1461
- i += 2;
1462
- continue;
1463
- }
1464
- i++;
1465
- continue;
1466
- }
1467
- if (state === "single") {
1468
- if (ch === "\\") {
1469
- i += 2;
1470
- continue;
1471
- }
1472
- if (ch === "'") {
1473
- state = "code";
1474
- }
1475
- i++;
1476
- continue;
1477
- }
1478
- if (state === "double") {
1479
- if (ch === "\\") {
1480
- i += 2;
1481
- continue;
1482
- }
1483
- if (ch === '"') {
1484
- state = "code";
1485
- }
1486
- i++;
1487
- continue;
1488
- }
1489
- if (state === "template") {
1490
- if (ch === "\\") {
1491
- i += 2;
1492
- continue;
1493
- }
1494
- if (ch === "`") {
1495
- state = "code";
1496
- i++;
1497
- continue;
1498
- }
1499
- i++;
1500
- continue;
1501
- }
1234
+ }
1235
+ return char;
1502
1236
  }
1503
- return occurrences;
1504
1237
  }
1505
- function readNamespaceAccess(expression, startIndex) {
1506
- let i = startIndex;
1507
- while (i < expression.length && /\s/.test(expression[i])) {
1508
- i++;
1509
- }
1510
- if (expression[i] === "?") {
1511
- if (expression[i + 1] !== ".") {
1512
- return null;
1513
- }
1514
- i += 2;
1515
- } else if (expression[i] === ".") {
1516
- i++;
1517
- } else {
1518
- return null;
1519
- }
1520
- while (i < expression.length && /\s/.test(expression[i])) {
1521
- i++;
1522
- }
1523
- if (!isIdentifierStart2(expression[i])) {
1524
- return null;
1238
+ function buildClassAliasEnvironment2(decl) {
1239
+ const env = /* @__PURE__ */ new Map();
1240
+ if (!decl) {
1241
+ return env;
1525
1242
  }
1526
- const propStart = i;
1527
- i++;
1528
- while (i < expression.length && isIdentifierPart2(expression[i])) {
1529
- i++;
1243
+ for (const alias of decl.aliases) {
1244
+ env.set(alias.name, alias.classes);
1530
1245
  }
1531
- return {
1532
- name: expression.slice(propStart, i),
1533
- index: propStart,
1534
- endIndex: i
1535
- };
1246
+ return env;
1536
1247
  }
1537
- function findPreviousNonSpace2(text, index) {
1538
- for (let i = index; i >= 0; i--) {
1539
- const ch = text[i];
1540
- if (!/\s/.test(ch)) {
1541
- return ch;
1248
+ function expandClasses2(classes, aliasEnv) {
1249
+ const result = [];
1250
+ for (const cls of classes) {
1251
+ const match = cls.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/);
1252
+ if (!match) {
1253
+ result.push(cls);
1254
+ continue;
1255
+ }
1256
+ const aliasClasses = aliasEnv.get(match[1]);
1257
+ if (!aliasClasses) {
1258
+ continue;
1542
1259
  }
1260
+ result.push(...aliasClasses);
1543
1261
  }
1544
- return null;
1545
- }
1546
- function isIdentifierStart2(ch) {
1547
- return /[A-Za-z_$]/.test(ch);
1548
- }
1549
- function isIdentifierPart2(ch) {
1550
- return /[A-Za-z0-9_$]/.test(ch);
1262
+ return result;
1551
1263
  }
1552
- function shouldIgnoreIdentifier2(name) {
1553
- return IGNORED_IDENTIFIERS2.has(name) || RESERVED_KEYWORDS2.has(name);
1264
+ function escapeStaticText(value) {
1265
+ return value.replace(/[&<>{}]/g, (char) => {
1266
+ switch (char) {
1267
+ case "&":
1268
+ return "&amp;";
1269
+ case "<":
1270
+ return "&lt;";
1271
+ case ">":
1272
+ return "&gt;";
1273
+ case "{":
1274
+ return "&#123;";
1275
+ case "}":
1276
+ return "&#125;";
1277
+ default:
1278
+ return char;
1279
+ }
1280
+ });
1554
1281
  }
1555
- function levelToSeverity2(level) {
1556
- if (level === "off") {
1557
- return null;
1558
- }
1559
- if (level === "error") {
1560
- return "error";
1561
- }
1562
- return "warning";
1282
+ function escapeAttributeValue(value) {
1283
+ return value.replace(/["&<>]/g, (char) => {
1284
+ switch (char) {
1285
+ case "&":
1286
+ return "&amp;";
1287
+ case "<":
1288
+ return "&lt;";
1289
+ case ">":
1290
+ return "&gt;";
1291
+ case '"':
1292
+ return "&quot;";
1293
+ default:
1294
+ return char;
1295
+ }
1296
+ });
1563
1297
  }
1564
- function offsetSpan(base, index, length) {
1565
- const startOffset = base.start.offset + index;
1566
- const startCol = base.start.col + index;
1298
+
1299
+ // src/diagnostics.ts
1300
+ function createSpan(line, col, length, lineOffset) {
1301
+ const startOffset = lineOffset + col - 1;
1567
1302
  return {
1568
- start: {
1569
- line: base.start.line,
1570
- col: startCol,
1571
- offset: startOffset
1572
- },
1573
- end: {
1574
- line: base.start.line,
1575
- col: startCol + length,
1576
- offset: startOffset + length
1577
- }
1303
+ start: { line, col, offset: startOffset },
1304
+ end: { line, col: col + length, offset: startOffset + length }
1578
1305
  };
1579
1306
  }
1580
- function enforcePropAliases(root) {
1581
- if (!root.propsDecls || root.propsDecls.length === 0) {
1582
- return [];
1583
- }
1307
+
1308
+ // src/dialect.ts
1309
+ function enforceDialect(root, config) {
1584
1310
  const diagnostics = [];
1585
- const declaredProps = new Map(root.propsDecls.map((d) => [d.name, d]));
1586
- const allUsage = collectTemplateUsage(root);
1587
- for (const name of allUsage.usedBare) {
1588
- if (!declaredProps.has(name) && !shouldIgnoreForDiagnostics(name)) {
1589
- diagnostics.push({
1590
- severity: "warning",
1591
- code: "props.missingDeclaration",
1592
- message: `Identifier "${name}" is used without "props." but is not declared in #props. Declare "${name}" in #props or use "props.${name}".`
1593
- });
1594
- }
1595
- }
1596
- for (const [name, decl] of declaredProps) {
1597
- const usedAsBare = allUsage.usedBareAliases.has(name);
1598
- const usedAsProps = allUsage.usedPropsDot.has(name);
1599
- if (!usedAsBare && !usedAsProps) {
1600
- diagnostics.push({
1601
- severity: "warning",
1602
- code: "props.unusedDeclaration",
1603
- message: `Prop "${name}" is declared in #props but never used in this template.`,
1604
- span: decl.span
1605
- });
1606
- }
1607
- }
1608
- for (const name of allUsage.usedPropsDot) {
1609
- if (declaredProps.has(name)) {
1610
- diagnostics.push({
1611
- severity: "warning",
1612
- code: "props.style.nonPreferred",
1613
- message: `"props.${name}" is unnecessary because "${name}" is declared in #props. Use "{${name}}" instead.`
1614
- });
1615
- }
1616
- }
1617
- for (const [name, decl] of declaredProps) {
1618
- const isCallable = decl.kind === "callable";
1619
- const usedAsCall = allUsage.callSitesBare.has(name);
1620
- const usedAsValue = allUsage.usedBareAliases.has(name) && !usedAsCall;
1621
- if (isCallable && usedAsValue) {
1622
- diagnostics.push({
1623
- severity: "warning",
1624
- code: "props.style.nonPreferred",
1625
- message: `"${name}" is declared as callable in #props (${name}()) but used as a value.`
1626
- });
1627
- } else if (!isCallable && usedAsCall) {
1628
- diagnostics.push({
1629
- severity: "warning",
1630
- code: "props.style.nonPreferred",
1631
- message: `"${name}" is declared as a value in #props but used as a function call.`
1632
- });
1633
- }
1311
+ if (root.idToken) {
1312
+ diagnostics.push(
1313
+ ...evaluateToken(
1314
+ { kind: "id", token: root.idToken, span: root.idTokenSpan },
1315
+ config.tokens.id
1316
+ )
1317
+ );
1634
1318
  }
1319
+ walkNodes(root.children, (occurrence) => {
1320
+ const rule = config.tokens[occurrence.kind];
1321
+ diagnostics.push(...evaluateToken(occurrence, rule));
1322
+ });
1635
1323
  return diagnostics;
1636
1324
  }
1637
- function collectTemplateUsage(root) {
1638
- const usage = {
1639
- usedBare: /* @__PURE__ */ new Set(),
1640
- usedBareAliases: /* @__PURE__ */ new Set(),
1641
- usedPropsDot: /* @__PURE__ */ new Set(),
1642
- callSitesBare: /* @__PURE__ */ new Set(),
1643
- callSitesPropsDot: /* @__PURE__ */ new Set()
1644
- };
1645
- const env = createTemplateEnv(root.propsDecls);
1646
- function mergeResult(result) {
1647
- for (const name of result.usedBare) {
1648
- usage.usedBare.add(name);
1649
- }
1650
- for (const name of result.rewrittenAliases) {
1651
- usage.usedBareAliases.add(name);
1652
- }
1653
- for (const name of result.usedPropsDot) {
1654
- usage.usedPropsDot.add(name);
1325
+ function walkNodes(nodes, onToken) {
1326
+ for (const node of nodes) {
1327
+ if (node.type === "For") {
1328
+ onFor(node, onToken);
1329
+ walkNodes(node.body, onToken);
1330
+ continue;
1655
1331
  }
1656
- for (const name of result.callSitesBare) {
1657
- usage.callSitesBare.add(name);
1332
+ if (node.type === "Conditional") {
1333
+ onConditional(node, onToken);
1334
+ continue;
1658
1335
  }
1659
- for (const name of result.callSitesPropsDot) {
1660
- usage.callSitesPropsDot.add(name);
1336
+ if (node.type === "Element" || node.type === "Component") {
1337
+ walkNodes(node.children, onToken);
1338
+ if (node.type === "Component" && node.slots) {
1339
+ for (const slot of node.slots) {
1340
+ walkNodes(slot.children, onToken);
1341
+ }
1342
+ }
1343
+ continue;
1661
1344
  }
1662
1345
  }
1663
- function analyzeExpression(expr) {
1664
- if (!expr) return;
1665
- const result = rewriteExpression(expr, env);
1666
- mergeResult(result);
1346
+ }
1347
+ function onFor(node, onToken) {
1348
+ if (!node.token) {
1349
+ return;
1667
1350
  }
1668
- function analyzeJsxExpression(expr) {
1669
- if (!expr) return;
1670
- const result = rewriteJsxExpression(expr, env);
1671
- mergeResult(result);
1351
+ onToken({ kind: "for", token: node.token, span: node.tokenSpan });
1352
+ }
1353
+ function onConditional(node, onToken) {
1354
+ for (const branch of node.branches) {
1355
+ onBranch(branch, onToken);
1356
+ walkNodes(branch.body, onToken);
1672
1357
  }
1673
- function walkNode(node) {
1674
- switch (node.type) {
1675
- case "Text":
1676
- for (const part of node.parts) {
1677
- if (part.type === "expr") {
1678
- analyzeExpression(part.value);
1679
- }
1680
- }
1681
- break;
1682
- case "Expression":
1683
- analyzeExpression(node.value);
1684
- break;
1685
- case "JSXPassthrough":
1686
- analyzeJsxExpression(node.expression);
1687
- break;
1688
- case "Element":
1689
- if (node.guard) {
1690
- analyzeExpression(node.guard);
1691
- }
1692
- for (const attr of node.attributes) {
1693
- if (attr.value) {
1694
- analyzeAttributeValue(attr.value);
1695
- }
1696
- }
1697
- for (const child of node.children) {
1698
- walkNode(child);
1699
- }
1700
- break;
1701
- case "Component":
1702
- if (node.guard) {
1703
- analyzeExpression(node.guard);
1704
- }
1705
- for (const attr of node.attributes) {
1706
- if (attr.value) {
1707
- analyzeAttributeValue(attr.value);
1708
- }
1709
- }
1710
- if (node.slots) {
1711
- for (const slot of node.slots) {
1712
- for (const child of slot.children) {
1713
- walkNode(child);
1714
- }
1715
- }
1716
- }
1717
- for (const child of node.children) {
1718
- walkNode(child);
1719
- }
1720
- break;
1721
- case "Conditional":
1722
- for (const branch of node.branches) {
1723
- if (branch.test) {
1724
- analyzeExpression(branch.test);
1725
- }
1726
- for (const child of branch.body) {
1727
- walkNode(child);
1728
- }
1729
- }
1730
- break;
1731
- case "For":
1732
- analyzeExpression(node.arrayExpr);
1733
- for (const child of node.body) {
1734
- walkNode(child);
1735
- }
1736
- break;
1737
- }
1358
+ }
1359
+ function onBranch(branch, onToken) {
1360
+ if (!branch.token || !branch.kind) {
1361
+ return;
1738
1362
  }
1739
- function analyzeAttributeValue(value) {
1740
- const trimmed = value.trim();
1741
- if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
1742
- return;
1743
- }
1744
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1745
- const inner = trimmed.slice(1, -1);
1746
- analyzeExpression(inner);
1747
- } else {
1748
- analyzeExpression(trimmed);
1363
+ onToken({ kind: branch.kind, token: branch.token, span: branch.tokenSpan });
1364
+ }
1365
+ function evaluateToken(occurrence, rule) {
1366
+ const diagnostics = [];
1367
+ const used = occurrence.token;
1368
+ const preferred = rule.preferred;
1369
+ const isAllowed = rule.allow.includes(used);
1370
+ if (!isAllowed) {
1371
+ const severity = levelToSeverity(rule.onDisallowed);
1372
+ if (severity) {
1373
+ diagnostics.push(
1374
+ createDialectDiagnostic(
1375
+ "dialect.token.disallowed",
1376
+ severity,
1377
+ used,
1378
+ preferred,
1379
+ occurrence.span,
1380
+ `Token "${used}" is not allowed for ${occurrence.kind}. Preferred: "${preferred}".`
1381
+ )
1382
+ );
1749
1383
  }
1384
+ return diagnostics;
1750
1385
  }
1751
- for (const child of root.children) {
1752
- walkNode(child);
1386
+ if (used !== preferred) {
1387
+ const severity = levelToSeverity(rule.onDisallowed);
1388
+ if (severity) {
1389
+ diagnostics.push(
1390
+ createDialectDiagnostic(
1391
+ "dialect.token.nonPreferred",
1392
+ severity,
1393
+ used,
1394
+ preferred,
1395
+ occurrence.span,
1396
+ `Token "${used}" is allowed but not preferred for ${occurrence.kind}. Preferred: "${preferred}".`
1397
+ )
1398
+ );
1399
+ }
1753
1400
  }
1754
- return usage;
1401
+ return diagnostics;
1402
+ }
1403
+ function createDialectDiagnostic(code, severity, used, preferred, span, message) {
1404
+ const fix = span ? {
1405
+ range: span,
1406
+ replacementText: preferred
1407
+ } : void 0;
1408
+ return {
1409
+ severity,
1410
+ code,
1411
+ message: message.replace(/\\s+/g, " "),
1412
+ span,
1413
+ fix
1414
+ };
1755
1415
  }
1756
- function shouldIgnoreForDiagnostics(name) {
1757
- return IGNORED_IDENTIFIERS2.has(name) || RESERVED_KEYWORDS2.has(name);
1416
+ function levelToSeverity(level) {
1417
+ if (level === "off") {
1418
+ return null;
1419
+ }
1420
+ if (level === "error") {
1421
+ return "error";
1422
+ }
1423
+ return "warning";
1758
1424
  }
1759
1425
 
1760
1426
  // src/parser.ts
@@ -1934,7 +1600,7 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
1934
1600
  const diagnostics = [];
1935
1601
  const root = { type: "Root", children: [] };
1936
1602
  const stack = [{ node: root, level: -1 }];
1937
- let propsBlockLevel = null;
1603
+ let inputsBlockLevel = null;
1938
1604
  let classesBlockLevel = null;
1939
1605
  let sawTopLevelTemplateNode = false;
1940
1606
  const conditionalChains = /* @__PURE__ */ new Map();
@@ -1976,19 +1642,19 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
1976
1642
  continue;
1977
1643
  }
1978
1644
  let level = indent / 2;
1979
- if (propsBlockLevel !== null && level <= propsBlockLevel) {
1980
- propsBlockLevel = null;
1645
+ if (inputsBlockLevel !== null && level <= inputsBlockLevel) {
1646
+ inputsBlockLevel = null;
1981
1647
  }
1982
1648
  if (classesBlockLevel !== null && level <= classesBlockLevel) {
1983
1649
  classesBlockLevel = null;
1984
1650
  }
1985
- const isInPropsBlock = propsBlockLevel !== null && level > propsBlockLevel;
1651
+ const isInInputsBlock = inputsBlockLevel !== null && level > inputsBlockLevel;
1986
1652
  const isInClassesBlock = classesBlockLevel !== null && level > classesBlockLevel;
1987
1653
  while (stack.length > 1 && stack[stack.length - 1].level >= level) {
1988
1654
  stack.pop();
1989
1655
  }
1990
1656
  const parentLevel = stack[stack.length - 1].level;
1991
- if (level > parentLevel + 1 && !isInPropsBlock && !isInClassesBlock) {
1657
+ if (level > parentLevel + 1 && !isInInputsBlock && !isInClassesBlock) {
1992
1658
  pushDiag(
1993
1659
  diagnostics,
1994
1660
  "COLLIE003",
@@ -2034,48 +1700,35 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
2034
1700
  }
2035
1701
  continue;
2036
1702
  }
2037
- if (trimmed === "props") {
2038
- pushDiag(
2039
- diagnostics,
2040
- "COLLIE103",
2041
- "`props` must be declared using `#props`.",
2042
- lineNumber,
2043
- indent + 1,
2044
- lineOffset,
2045
- trimmed.length
2046
- );
2047
- if (level === 0) {
2048
- propsBlockLevel = level;
2049
- }
2050
- continue;
2051
- }
2052
- if (trimmed === "#props") {
1703
+ if (trimmed === "#inputs") {
2053
1704
  if (level !== 0) {
2054
1705
  pushDiag(
2055
1706
  diagnostics,
2056
1707
  "COLLIE102",
2057
- "#props block must be at the top level.",
1708
+ "#inputs block must be at the top level.",
2058
1709
  lineNumber,
2059
1710
  indent + 1,
2060
1711
  lineOffset,
2061
1712
  trimmed.length
2062
1713
  );
2063
- } else if (root.props) {
1714
+ } else if (root.inputs) {
2064
1715
  pushDiag(
2065
1716
  diagnostics,
2066
1717
  "COLLIE101",
2067
- "Only one #props block is allowed per #id.",
1718
+ "Only one #inputs block is allowed per #id.",
2068
1719
  lineNumber,
2069
1720
  indent + 1,
2070
1721
  lineOffset,
2071
1722
  trimmed.length
2072
1723
  );
2073
1724
  } else {
2074
- root.props = { fields: [] };
2075
- root.propsDecls = [];
1725
+ root.inputs = { fields: [] };
2076
1726
  }
2077
1727
  if (level === 0) {
2078
- propsBlockLevel = level;
1728
+ if (!root.inputsDecls) {
1729
+ root.inputsDecls = [];
1730
+ }
1731
+ inputsBlockLevel = level;
2079
1732
  }
2080
1733
  continue;
2081
1734
  }
@@ -2115,33 +1768,33 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
2115
1768
  }
2116
1769
  continue;
2117
1770
  }
2118
- if (propsBlockLevel !== null && level > propsBlockLevel) {
2119
- if (level !== propsBlockLevel + 1) {
1771
+ if (inputsBlockLevel !== null && level > inputsBlockLevel) {
1772
+ if (level !== inputsBlockLevel + 1) {
2120
1773
  pushDiag(
2121
1774
  diagnostics,
2122
1775
  "COLLIE102",
2123
- "#props lines must be indented two spaces under the #props header.",
1776
+ "#inputs lines must be indented two spaces under the #inputs header.",
2124
1777
  lineNumber,
2125
1778
  indent + 1,
2126
1779
  lineOffset
2127
1780
  );
2128
1781
  continue;
2129
1782
  }
2130
- const decl = parsePropDecl(lineContent, lineNumber, indent + 1, lineOffset, diagnostics);
2131
- if (decl && root.propsDecls) {
2132
- const existing = root.propsDecls.find((d) => d.name === decl.name);
1783
+ const decl = parseInputDecl(lineContent, lineNumber, indent + 1, lineOffset, diagnostics);
1784
+ if (decl && root.inputsDecls) {
1785
+ const existing = root.inputsDecls.find((d) => d.name === decl.name);
2133
1786
  if (existing) {
2134
1787
  pushDiag(
2135
1788
  diagnostics,
2136
1789
  "COLLIE106",
2137
- `Duplicate prop declaration "${decl.name}".`,
1790
+ `Duplicate input declaration "${decl.name}".`,
2138
1791
  lineNumber,
2139
1792
  indent + 1,
2140
1793
  lineOffset,
2141
1794
  trimmed.length
2142
1795
  );
2143
1796
  } else {
2144
- root.propsDecls.push(decl);
1797
+ root.inputsDecls.push(decl);
2145
1798
  }
2146
1799
  }
2147
1800
  continue;
@@ -2554,9 +2207,7 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
2554
2207
  }
2555
2208
  if (options.dialect) {
2556
2209
  diagnostics.push(...enforceDialect(root, options.dialect));
2557
- diagnostics.push(...enforceProps(root, options.dialect.props));
2558
2210
  }
2559
- diagnostics.push(...enforcePropAliases(root));
2560
2211
  return { root, diagnostics };
2561
2212
  }
2562
2213
  function buildLineOffsets(lines) {
@@ -3716,13 +3367,13 @@ function parseAndAddAttribute(attrStr, attributes, diagnostics, lineNumber, colu
3716
3367
  }
3717
3368
  }
3718
3369
  }
3719
- function parsePropDecl(line, lineNumber, column, lineOffset, diagnostics) {
3370
+ function parseInputDecl(line, lineNumber, column, lineOffset, diagnostics) {
3720
3371
  const trimmed = line.trim();
3721
3372
  if (trimmed.includes(":") || trimmed.includes("<") || trimmed.includes("?")) {
3722
3373
  pushDiag(
3723
3374
  diagnostics,
3724
3375
  "COLLIE104",
3725
- 'Types are not supported in #props yet. Use "name" or "name()".',
3376
+ 'Types are not supported in #inputs yet. Use "name" or "name()".',
3726
3377
  lineNumber,
3727
3378
  column,
3728
3379
  lineOffset,
@@ -3730,32 +3381,32 @@ function parsePropDecl(line, lineNumber, column, lineOffset, diagnostics) {
3730
3381
  );
3731
3382
  return null;
3732
3383
  }
3733
- const callableMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\(\)$/);
3734
- if (callableMatch) {
3735
- const name = callableMatch[1];
3384
+ const valueMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)$/);
3385
+ if (valueMatch) {
3386
+ const name = valueMatch[1];
3736
3387
  const nameStart = line.indexOf(name);
3737
3388
  const nameColumn = column + nameStart;
3738
3389
  return {
3739
3390
  name,
3740
- kind: "callable",
3391
+ kind: "value",
3741
3392
  span: createSpan(lineNumber, nameColumn, name.length, lineOffset)
3742
3393
  };
3743
3394
  }
3744
- const valueMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)$/);
3745
- if (valueMatch) {
3746
- const name = valueMatch[1];
3395
+ const fnMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\(\)$/);
3396
+ if (fnMatch) {
3397
+ const name = fnMatch[1];
3747
3398
  const nameStart = line.indexOf(name);
3748
3399
  const nameColumn = column + nameStart;
3749
3400
  return {
3750
3401
  name,
3751
- kind: "value",
3402
+ kind: "fn",
3752
3403
  span: createSpan(lineNumber, nameColumn, name.length, lineOffset)
3753
3404
  };
3754
3405
  }
3755
3406
  pushDiag(
3756
3407
  diagnostics,
3757
3408
  "COLLIE105",
3758
- 'Invalid #props declaration. Use "name" or "name()".',
3409
+ 'Invalid #inputs declaration. Use "name" or "name()".',
3759
3410
  lineNumber,
3760
3411
  column,
3761
3412
  lineOffset,
@@ -3890,8 +3541,8 @@ function serializeRoot(root, indentSize) {
3890
3541
  if (root.classAliases && root.classAliases.aliases.length > 0) {
3891
3542
  sections.push(formatClassAliases(root.classAliases, indentSize));
3892
3543
  }
3893
- if (root.props && root.props.fields.length > 0) {
3894
- sections.push(formatProps(root.props, indentSize));
3544
+ if (root.inputs && root.inputs.fields.length > 0) {
3545
+ sections.push(formatInputs(root.inputs, indentSize));
3895
3546
  }
3896
3547
  if (root.children.length > 0) {
3897
3548
  sections.push(formatNodes(root.children, 0, indentSize));
@@ -3920,10 +3571,10 @@ function formatClassAliases(decl, indentSize) {
3920
3571
  }
3921
3572
  return lines;
3922
3573
  }
3923
- function formatProps(props, indentSize) {
3574
+ function formatInputs(inputs, indentSize) {
3924
3575
  const indent = indentString(1, indentSize);
3925
- const lines = ["props"];
3926
- for (const field of props.fields) {
3576
+ const lines = ["#inputs"];
3577
+ for (const field of inputs.fields) {
3927
3578
  const optionalFlag = field.optional ? "?" : "";
3928
3579
  lines.push(cleanLine(`${indent}${field.name}${optionalFlag}: ${field.typeText.trim()}`));
3929
3580
  }
@@ -4113,19 +3764,19 @@ function convertTsxToCollie(source, options = {}) {
4113
3764
  );
4114
3765
  const warnings = [];
4115
3766
  const ctx = { sourceFile, warnings };
4116
- const propDeclarations = collectPropDeclarations(sourceFile);
4117
- const component = findComponentInfo(sourceFile, propDeclarations, ctx);
3767
+ const inputDeclarations = collectInputDeclarations(sourceFile);
3768
+ const component = findComponentInfo(sourceFile, inputDeclarations, ctx);
4118
3769
  if (!component) {
4119
3770
  throw new Error("Could not find a component that returns JSX in this file.");
4120
3771
  }
4121
- const propsLines = buildPropsBlock(component, propDeclarations, ctx);
3772
+ const inputsLines = buildInputsBlock(component, inputDeclarations, ctx);
4122
3773
  const templateLines = convertJsxNode(component.jsxRoot, ctx, 0);
4123
3774
  if (!templateLines.length) {
4124
3775
  throw new Error("Unable to convert JSX tree to Collie template.");
4125
3776
  }
4126
3777
  const sections = [];
4127
- if (propsLines.length) {
4128
- sections.push(propsLines.join("\n"));
3778
+ if (inputsLines.length) {
3779
+ sections.push(inputsLines.join("\n"));
4129
3780
  }
4130
3781
  sections.push(templateLines.join("\n"));
4131
3782
  const collie = `${sections.join("\n\n").trimEnd()}
@@ -4140,18 +3791,18 @@ function inferScriptKind(filename) {
4140
3791
  if (ext === ".ts") return import_typescript.default.ScriptKind.TS;
4141
3792
  return import_typescript.default.ScriptKind.JS;
4142
3793
  }
4143
- function collectPropDeclarations(sourceFile) {
3794
+ function collectInputDeclarations(sourceFile) {
4144
3795
  const map = /* @__PURE__ */ new Map();
4145
3796
  for (const statement of sourceFile.statements) {
4146
3797
  if (import_typescript.default.isInterfaceDeclaration(statement) && statement.name) {
4147
- map.set(statement.name.text, extractPropsFromMembers(statement.members, sourceFile));
3798
+ map.set(statement.name.text, extractInputsFromMembers(statement.members, sourceFile));
4148
3799
  } else if (import_typescript.default.isTypeAliasDeclaration(statement) && import_typescript.default.isTypeLiteralNode(statement.type)) {
4149
- map.set(statement.name.text, extractPropsFromMembers(statement.type.members, sourceFile));
3800
+ map.set(statement.name.text, extractInputsFromMembers(statement.type.members, sourceFile));
4150
3801
  }
4151
3802
  }
4152
3803
  return map;
4153
3804
  }
4154
- function extractPropsFromMembers(members, sourceFile) {
3805
+ function extractInputsFromMembers(members, sourceFile) {
4155
3806
  const fields = [];
4156
3807
  for (const member of members) {
4157
3808
  if (!import_typescript.default.isPropertySignature(member) || member.name === void 0) {
@@ -4176,11 +3827,11 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4176
3827
  const jsx = findJsxReturn(statement.body);
4177
3828
  if (jsx) {
4178
3829
  const defaults = extractDefaultsFromParameters(statement.parameters, ctx);
4179
- const propsInfo = resolvePropsFromParameters(statement.parameters, declarations, ctx);
3830
+ const inputsInfo = resolveInputsFromParameters(statement.parameters, declarations, ctx);
4180
3831
  return {
4181
3832
  jsxRoot: jsx,
4182
- propsTypeName: propsInfo.typeName,
4183
- inlineProps: propsInfo.inline,
3833
+ inputsTypeName: inputsInfo.typeName,
3834
+ inlineInputs: inputsInfo.inline,
4184
3835
  defaults
4185
3836
  };
4186
3837
  }
@@ -4194,20 +3845,20 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4194
3845
  continue;
4195
3846
  }
4196
3847
  const defaults = extractDefaultsFromParameters(init.parameters, ctx);
4197
- const propsInfo = resolvePropsFromParameters(init.parameters, declarations, ctx);
4198
- if (!propsInfo.typeName && !propsInfo.inline && decl.type) {
4199
- const inferred = resolvePropsFromTypeAnnotation(decl.type, sourceFile, declarations);
4200
- if (inferred.typeName && !propsInfo.typeName) {
4201
- propsInfo.typeName = inferred.typeName;
3848
+ const inputsInfo = resolveInputsFromParameters(init.parameters, declarations, ctx);
3849
+ if (!inputsInfo.typeName && !inputsInfo.inline && decl.type) {
3850
+ const inferred = resolveInputsFromTypeAnnotation(decl.type, sourceFile, declarations);
3851
+ if (inferred.typeName && !inputsInfo.typeName) {
3852
+ inputsInfo.typeName = inferred.typeName;
4202
3853
  }
4203
- if (inferred.inline && !propsInfo.inline) {
4204
- propsInfo.inline = inferred.inline;
3854
+ if (inferred.inline && !inputsInfo.inline) {
3855
+ inputsInfo.inline = inferred.inline;
4205
3856
  }
4206
3857
  }
4207
3858
  return {
4208
3859
  jsxRoot: jsx,
4209
- propsTypeName: propsInfo.typeName,
4210
- inlineProps: propsInfo.inline,
3860
+ inputsTypeName: inputsInfo.typeName,
3861
+ inlineInputs: inputsInfo.inline,
4211
3862
  defaults
4212
3863
  };
4213
3864
  }
@@ -4216,13 +3867,13 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4216
3867
  }
4217
3868
  return null;
4218
3869
  }
4219
- function resolvePropsFromParameters(parameters, declarations, ctx) {
3870
+ function resolveInputsFromParameters(parameters, declarations, ctx) {
4220
3871
  if (!parameters.length) {
4221
3872
  return {};
4222
3873
  }
4223
3874
  const param = parameters[0];
4224
3875
  if (param.type) {
4225
- const inferred = resolvePropsFromTypeAnnotation(param.type, ctx.sourceFile, declarations);
3876
+ const inferred = resolveInputsFromTypeAnnotation(param.type, ctx.sourceFile, declarations);
4226
3877
  if (inferred.inline) {
4227
3878
  return inferred;
4228
3879
  }
@@ -4232,7 +3883,7 @@ function resolvePropsFromParameters(parameters, declarations, ctx) {
4232
3883
  }
4233
3884
  return {};
4234
3885
  }
4235
- function resolvePropsFromTypeAnnotation(typeNode, sourceFile, declarations) {
3886
+ function resolveInputsFromTypeAnnotation(typeNode, sourceFile, declarations) {
4236
3887
  if (import_typescript.default.isTypeReferenceNode(typeNode)) {
4237
3888
  const referenced = getTypeReferenceName(typeNode.typeName);
4238
3889
  if (referenced && declarations.has(referenced)) {
@@ -4246,12 +3897,12 @@ function resolvePropsFromTypeAnnotation(typeNode, sourceFile, declarations) {
4246
3897
  return { typeName: nested };
4247
3898
  }
4248
3899
  } else if (import_typescript.default.isTypeLiteralNode(typeArg)) {
4249
- return { inline: extractPropsFromMembers(typeArg.members, sourceFile) };
3900
+ return { inline: extractInputsFromMembers(typeArg.members, sourceFile) };
4250
3901
  }
4251
3902
  }
4252
3903
  }
4253
3904
  if (import_typescript.default.isTypeLiteralNode(typeNode)) {
4254
- return { inline: extractPropsFromMembers(typeNode.members, sourceFile) };
3905
+ return { inline: extractInputsFromMembers(typeNode.members, sourceFile) };
4255
3906
  }
4256
3907
  return {};
4257
3908
  }
@@ -4307,16 +3958,16 @@ function extractDefaultsFromParameters(parameters, ctx) {
4307
3958
  if (!element.initializer) {
4308
3959
  continue;
4309
3960
  }
4310
- const propName = getBindingElementPropName(element, ctx.sourceFile);
4311
- if (!propName) {
3961
+ const inputName = getBindingElementInputName(element, ctx.sourceFile);
3962
+ if (!inputName) {
4312
3963
  ctx.warnings.push("Skipping complex destructured default value.");
4313
3964
  continue;
4314
3965
  }
4315
- defaults.set(propName, element.initializer.getText(ctx.sourceFile).trim());
3966
+ defaults.set(inputName, element.initializer.getText(ctx.sourceFile).trim());
4316
3967
  }
4317
3968
  return defaults;
4318
3969
  }
4319
- function getBindingElementPropName(element, sourceFile) {
3970
+ function getBindingElementInputName(element, sourceFile) {
4320
3971
  const prop = element.propertyName;
4321
3972
  if (prop) {
4322
3973
  if (import_typescript.default.isIdentifier(prop) || import_typescript.default.isStringLiteral(prop) || import_typescript.default.isNumericLiteral(prop)) {
@@ -4338,24 +3989,25 @@ function getPropertyName(name, sourceFile) {
4338
3989
  }
4339
3990
  return name.getText(sourceFile);
4340
3991
  }
4341
- function buildPropsBlock(info, propDeclarations, ctx) {
4342
- const fields = info.inlineProps ?? (info.propsTypeName ? propDeclarations.get(info.propsTypeName) ?? [] : void 0) ?? [];
3992
+ function buildInputsBlock(info, inputDeclarations, ctx) {
3993
+ const fields = info.inlineInputs ?? (info.inputsTypeName ? inputDeclarations.get(info.inputsTypeName) ?? [] : void 0) ?? [];
4343
3994
  if (!fields.length && !info.defaults.size) {
4344
3995
  return [];
4345
3996
  }
4346
- const lines = ["props"];
3997
+ const lines = ["#inputs"];
4347
3998
  if (fields.length) {
4348
3999
  for (const field of fields) {
4349
4000
  const def = info.defaults.get(field.name);
4350
- let line = ` ${field.name}${field.optional ? "?" : ""}: ${field.typeText}`;
4001
+ let line = ` ${field.name}`;
4351
4002
  if (def) {
4352
- line += ` = ${def}`;
4003
+ ctx.warnings.push(`Default value for "${field.name}" cannot be preserved in Collie #inputs.`);
4353
4004
  }
4354
4005
  lines.push(line);
4355
4006
  }
4356
4007
  } else {
4357
4008
  for (const [name, defValue] of info.defaults.entries()) {
4358
- lines.push(` ${name}: any = ${defValue}`);
4009
+ ctx.warnings.push(`Default value for "${name}" cannot be preserved in Collie #inputs.`);
4010
+ lines.push(` ${name}`);
4359
4011
  }
4360
4012
  }
4361
4013
  return lines;
@@ -4636,28 +4288,28 @@ function hasErrors(diagnostics) {
4636
4288
  function createStubComponent(name, flavor) {
4637
4289
  if (flavor === "tsx") {
4638
4290
  return [
4639
- "export type Props = Record<string, never>;",
4640
- `export default function ${name}(props: Props) {`,
4291
+ "export type Inputs = Record<string, never>;",
4292
+ `export default function ${name}(__inputs: Inputs) {`,
4641
4293
  " return null;",
4642
4294
  "}"
4643
4295
  ].join("\n");
4644
4296
  }
4645
- return [`export default function ${name}(props) {`, " return null;", "}"].join("\n");
4297
+ return [`export default function ${name}(__inputs) {`, " return null;", "}"].join("\n");
4646
4298
  }
4647
4299
  function createStubRender(flavor) {
4648
4300
  if (flavor === "tsx") {
4649
4301
  return [
4650
- "export type Props = Record<string, never>;",
4651
- "export function render(props: any) {",
4302
+ "export type Inputs = Record<string, never>;",
4303
+ "export function render(__inputs: any) {",
4652
4304
  " return null;",
4653
4305
  "}"
4654
4306
  ].join("\n");
4655
4307
  }
4656
- return ["export function render(props) {", " return null;", "}"].join("\n");
4308
+ return ["export function render(__inputs) {", " return null;", "}"].join("\n");
4657
4309
  }
4658
4310
  function wrapRenderModuleAsComponent(renderModule, name, flavor) {
4659
- const signature = flavor === "tsx" ? `export default function ${name}(props: Props) {` : `export default function ${name}(props) {`;
4660
- const wrapper = [signature, " return render(props);", "}"].join("\n");
4311
+ const signature = flavor === "tsx" ? `export default function ${name}(__inputs: Inputs) {` : `export default function ${name}(__inputs) {`;
4312
+ const wrapper = [signature, " return render(__inputs);", "}"].join("\n");
4661
4313
  return `${renderModule}
4662
4314
 
4663
4315
  ${wrapper}`;