@collie-lang/compiler 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // src/rewrite.ts
2
- function createTemplateEnv(propsDecls) {
3
- const propAliases = /* @__PURE__ */ new Map();
4
- if (propsDecls) {
5
- for (const decl of propsDecls) {
6
- propAliases.set(decl.name, decl.kind);
2
+ function createTemplateEnv(inputsDecls) {
3
+ const inputNames = /* @__PURE__ */ new Set();
4
+ if (inputsDecls) {
5
+ for (const decl of inputsDecls) {
6
+ inputNames.add(decl.name);
7
7
  }
8
8
  }
9
9
  return {
10
- propAliases,
10
+ inputNames,
11
11
  localsStack: []
12
12
  };
13
13
  }
@@ -26,12 +26,6 @@ function isLocal(env, name) {
26
26
  }
27
27
  return false;
28
28
  }
29
- function isPropAlias(env, name) {
30
- if (isLocal(env, name)) {
31
- return false;
32
- }
33
- return env.propAliases.has(name);
34
- }
35
29
  var IGNORED_IDENTIFIERS = /* @__PURE__ */ new Set([
36
30
  "null",
37
31
  "undefined",
@@ -39,10 +33,10 @@ var IGNORED_IDENTIFIERS = /* @__PURE__ */ new Set([
39
33
  "false",
40
34
  "NaN",
41
35
  "Infinity",
42
- "this",
43
- "props"
36
+ "this"
44
37
  ]);
45
38
  var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
39
+ "async",
46
40
  "await",
47
41
  "break",
48
42
  "case",
@@ -84,185 +78,33 @@ var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
84
78
  "yield"
85
79
  ]);
86
80
  function rewriteExpression(expression, env) {
87
- let i = 0;
88
- let state = "code";
89
- let output = "";
81
+ const tokens = tokenizeExpression(expression);
90
82
  const usedBare = /* @__PURE__ */ new Set();
91
- const usedPropsDot = /* @__PURE__ */ new Set();
92
83
  const callSitesBare = /* @__PURE__ */ new Set();
93
- const callSitesPropsDot = /* @__PURE__ */ new Set();
94
- const rewrittenAliases = /* @__PURE__ */ new Set();
95
- while (i < expression.length) {
96
- const ch = expression[i];
97
- if (state === "code") {
98
- if (ch === "'" || ch === '"') {
99
- state = ch === "'" ? "single" : "double";
100
- output += ch;
101
- i++;
102
- continue;
103
- }
104
- if (ch === "`") {
105
- state = "template";
106
- output += ch;
107
- i++;
108
- continue;
109
- }
110
- if (ch === "/" && expression[i + 1] === "/") {
111
- state = "line";
112
- output += ch;
113
- i++;
114
- continue;
115
- }
116
- if (ch === "/" && expression[i + 1] === "*") {
117
- state = "block";
118
- output += ch;
119
- i++;
120
- continue;
121
- }
122
- if (isIdentifierStart(ch)) {
123
- const start = i;
124
- i++;
125
- while (i < expression.length && isIdentifierPart(expression[i])) {
126
- i++;
127
- }
128
- const name = expression.slice(start, i);
129
- const prevNonSpace = findPreviousNonSpace(expression, start - 1);
130
- const nextNonSpace = findNextNonSpace(expression, i);
131
- const isMemberAccess = prevNonSpace === ".";
132
- const isObjectKey = nextNonSpace === ":" && (prevNonSpace === "{" || prevNonSpace === ",");
133
- const isCall = nextNonSpace === "(";
134
- if (prevNonSpace === "." && start >= 2) {
135
- const propsStart = findPreviousIdentifierStart(expression, start - 2);
136
- if (propsStart !== null) {
137
- const possibleProps = expression.slice(propsStart, start - 1).trim();
138
- if (possibleProps === "props") {
139
- usedPropsDot.add(name);
140
- if (isCall) {
141
- callSitesPropsDot.add(name);
142
- }
143
- }
144
- }
145
- }
146
- if (isMemberAccess || isObjectKey || isLocal(env, name) || shouldIgnoreIdentifier(name)) {
147
- output += name;
148
- continue;
149
- }
150
- if (isPropAlias(env, name)) {
151
- output += `props.${name}`;
152
- rewrittenAliases.add(name);
153
- if (isCall) {
154
- callSitesBare.add(name);
155
- }
156
- continue;
157
- }
158
- usedBare.add(name);
159
- if (isCall) {
160
- callSitesBare.add(name);
161
- }
162
- output += name;
163
- continue;
164
- }
165
- output += ch;
166
- i++;
167
- continue;
168
- }
169
- if (state === "line") {
170
- output += ch;
171
- if (ch === "\n") {
172
- state = "code";
173
- }
174
- i++;
175
- continue;
176
- }
177
- if (state === "block") {
178
- output += ch;
179
- if (ch === "*" && expression[i + 1] === "/") {
180
- output += "/";
181
- i += 2;
182
- state = "code";
183
- continue;
184
- }
185
- i++;
186
- continue;
187
- }
188
- if (state === "single") {
189
- output += ch;
190
- if (ch === "\\") {
191
- if (i + 1 < expression.length) {
192
- output += expression[i + 1];
193
- i += 2;
194
- continue;
195
- }
196
- }
197
- if (ch === "'") {
198
- state = "code";
199
- }
200
- i++;
201
- continue;
202
- }
203
- if (state === "double") {
204
- output += ch;
205
- if (ch === "\\") {
206
- if (i + 1 < expression.length) {
207
- output += expression[i + 1];
208
- i += 2;
209
- continue;
210
- }
211
- }
212
- if (ch === '"') {
213
- state = "code";
214
- }
215
- i++;
216
- continue;
217
- }
218
- if (state === "template") {
219
- output += ch;
220
- if (ch === "\\") {
221
- if (i + 1 < expression.length) {
222
- output += expression[i + 1];
223
- i += 2;
224
- continue;
225
- }
226
- }
227
- if (ch === "`") {
228
- state = "code";
229
- }
230
- i++;
231
- continue;
232
- }
233
- }
234
- return { code: output, usedBare, usedPropsDot, callSitesBare, callSitesPropsDot, rewrittenAliases };
84
+ const localScopes = [/* @__PURE__ */ new Set()];
85
+ analyzeTokens(tokens, 0, tokens.length, env, localScopes, usedBare, callSitesBare, false);
86
+ return { code: expression, usedBare, callSitesBare };
235
87
  }
236
88
  function rewriteJsxExpression(expression, env) {
237
- let output = "";
238
89
  let i = 0;
239
90
  const usedBare = /* @__PURE__ */ new Set();
240
- const usedPropsDot = /* @__PURE__ */ new Set();
241
91
  const callSitesBare = /* @__PURE__ */ new Set();
242
- const callSitesPropsDot = /* @__PURE__ */ new Set();
243
- const rewrittenAliases = /* @__PURE__ */ new Set();
244
92
  while (i < expression.length) {
245
93
  const ch = expression[i];
246
94
  if (ch === "{") {
247
95
  const braceResult = readBalancedBraces(expression, i + 1);
248
96
  if (!braceResult) {
249
- output += expression.slice(i);
250
97
  break;
251
98
  }
252
99
  const result = rewriteExpression(braceResult.content, env);
253
- output += `{${result.code}}`;
254
100
  for (const name of result.usedBare) usedBare.add(name);
255
- for (const name of result.usedPropsDot) usedPropsDot.add(name);
256
101
  for (const name of result.callSitesBare) callSitesBare.add(name);
257
- for (const name of result.callSitesPropsDot) callSitesPropsDot.add(name);
258
- for (const name of result.rewrittenAliases) rewrittenAliases.add(name);
259
102
  i = braceResult.endIndex + 1;
260
103
  continue;
261
104
  }
262
- output += ch;
263
105
  i++;
264
106
  }
265
- return { code: output, usedBare, usedPropsDot, callSitesBare, callSitesPropsDot, rewrittenAliases };
107
+ return { code: expression, usedBare, callSitesBare };
266
108
  }
267
109
  function readBalancedBraces(source, startIndex) {
268
110
  let i = startIndex;
@@ -354,38 +196,6 @@ function readBalancedBraces(source, startIndex) {
354
196
  }
355
197
  return null;
356
198
  }
357
- function findPreviousNonSpace(text, index) {
358
- for (let i = index; i >= 0; i--) {
359
- const ch = text[i];
360
- if (!/\s/.test(ch)) {
361
- return ch;
362
- }
363
- }
364
- return null;
365
- }
366
- function findNextNonSpace(text, index) {
367
- for (let i = index; i < text.length; i++) {
368
- const ch = text[i];
369
- if (!/\s/.test(ch)) {
370
- return ch;
371
- }
372
- }
373
- return null;
374
- }
375
- function findPreviousIdentifierStart(text, index) {
376
- let i = index;
377
- while (i >= 0 && /\s/.test(text[i])) {
378
- i--;
379
- }
380
- if (i < 0) return null;
381
- if (!isIdentifierPart(text[i])) {
382
- return null;
383
- }
384
- while (i > 0 && isIdentifierPart(text[i - 1])) {
385
- i--;
386
- }
387
- return i;
388
- }
389
199
  function isIdentifierStart(ch) {
390
200
  return /[A-Za-z_$]/.test(ch);
391
201
  }
@@ -395,96 +205,592 @@ function isIdentifierPart(ch) {
395
205
  function shouldIgnoreIdentifier(name) {
396
206
  return IGNORED_IDENTIFIERS.has(name) || RESERVED_KEYWORDS.has(name);
397
207
  }
398
-
399
- // src/codegen.ts
400
- function generateRenderModule(root, options) {
401
- const { prelude, propsType, propsDestructure, jsx, isTsx } = buildModuleParts(root, options);
402
- const parts = [...prelude, propsType];
403
- if (!isTsx) {
404
- parts.push(`/** @param {any} props */`);
405
- }
406
- const functionLines = [
407
- isTsx ? "export function render(props: any) {" : "export function render(props) {"
408
- ];
409
- if (propsDestructure) {
410
- functionLines.push(` ${propsDestructure}`);
208
+ function tokenizeExpression(expression) {
209
+ const tokens = [];
210
+ let i = 0;
211
+ while (i < expression.length) {
212
+ const ch = expression[i];
213
+ if (/\s/.test(ch)) {
214
+ i++;
215
+ continue;
216
+ }
217
+ if (ch === "/" && expression[i + 1] === "/") {
218
+ i += 2;
219
+ while (i < expression.length && expression[i] !== "\n") {
220
+ i++;
221
+ }
222
+ continue;
223
+ }
224
+ if (ch === "/" && expression[i + 1] === "*") {
225
+ i += 2;
226
+ while (i < expression.length && !(expression[i] === "*" && expression[i + 1] === "/")) {
227
+ i++;
228
+ }
229
+ i += 2;
230
+ continue;
231
+ }
232
+ if (ch === "'" || ch === '"') {
233
+ i = skipStringLiteral(expression, i, ch);
234
+ tokens.push({ type: "literal", value: "string" });
235
+ continue;
236
+ }
237
+ if (ch === "`") {
238
+ i = skipTemplateLiteral(expression, i);
239
+ tokens.push({ type: "literal", value: "template" });
240
+ continue;
241
+ }
242
+ if (isIdentifierStart(ch)) {
243
+ const start = i;
244
+ i++;
245
+ while (i < expression.length && isIdentifierPart(expression[i])) {
246
+ i++;
247
+ }
248
+ tokens.push({ type: "identifier", value: expression.slice(start, i) });
249
+ continue;
250
+ }
251
+ if (/[0-9]/.test(ch)) {
252
+ const start = i;
253
+ i++;
254
+ while (i < expression.length && /[0-9._]/.test(expression[i])) {
255
+ i++;
256
+ }
257
+ tokens.push({ type: "literal", value: expression.slice(start, i) });
258
+ continue;
259
+ }
260
+ if (expression.startsWith("...", i)) {
261
+ tokens.push({ type: "punctuator", value: "..." });
262
+ i += 3;
263
+ continue;
264
+ }
265
+ if (expression.startsWith("=>", i)) {
266
+ tokens.push({ type: "punctuator", value: "=>" });
267
+ i += 2;
268
+ continue;
269
+ }
270
+ if (expression.startsWith("?.", i)) {
271
+ tokens.push({ type: "punctuator", value: "?." });
272
+ i += 2;
273
+ continue;
274
+ }
275
+ if (expression.startsWith("??", i)) {
276
+ tokens.push({ type: "punctuator", value: "??" });
277
+ i += 2;
278
+ continue;
279
+ }
280
+ tokens.push({ type: "punctuator", value: ch });
281
+ i++;
411
282
  }
412
- functionLines.push(` return ${jsx};`, `}`);
413
- parts.push(functionLines.join("\n"));
414
- return parts.join("\n\n");
283
+ return tokens;
415
284
  }
416
- function buildModuleParts(root, options) {
417
- const { jsxRuntime, flavor } = options;
418
- const isTsx = flavor === "tsx";
419
- const aliasEnv = buildClassAliasEnvironment(root.classAliases);
420
- const env = createTemplateEnv(root.propsDecls);
421
- const jsx = renderRootChildren(root.children, aliasEnv, env);
422
- const propsDestructure = emitPropsDestructure(root.props);
423
- const prelude = [];
424
- if (root.clientComponent) {
425
- prelude.push(`"use client";`);
426
- }
427
- if (jsxRuntime === "classic" && templateUsesJsx(root)) {
428
- prelude.push(`import React from "react";`);
285
+ function skipStringLiteral(source, start, quote) {
286
+ let i = start + 1;
287
+ while (i < source.length) {
288
+ const ch = source[i];
289
+ if (ch === "\\") {
290
+ i += 2;
291
+ continue;
292
+ }
293
+ if (ch === quote) {
294
+ return i + 1;
295
+ }
296
+ i++;
429
297
  }
430
- const propsType = emitPropsType(root.props, flavor);
431
- return { prelude, propsType, propsDestructure, jsx, isTsx };
298
+ return source.length;
432
299
  }
433
- function buildClassAliasEnvironment(decl) {
434
- const env = /* @__PURE__ */ new Map();
435
- if (!decl) {
436
- return env;
300
+ function skipTemplateLiteral(source, start) {
301
+ let i = start + 1;
302
+ while (i < source.length) {
303
+ const ch = source[i];
304
+ if (ch === "\\") {
305
+ i += 2;
306
+ continue;
307
+ }
308
+ if (ch === "`") {
309
+ return i + 1;
310
+ }
311
+ i++;
437
312
  }
438
- for (const alias of decl.aliases) {
439
- env.set(alias.name, alias.classes);
313
+ return source.length;
314
+ }
315
+ function analyzeTokens(tokens, start, end, env, scopes, usedBare, callSitesBare, allowStatements) {
316
+ let i = start;
317
+ while (i < end) {
318
+ const token = tokens[i];
319
+ if (token.type === "identifier") {
320
+ if (token.value === "function") {
321
+ const fnResult = parseFunctionExpression(tokens, i, end, env, scopes, usedBare, callSitesBare);
322
+ if (fnResult > i) {
323
+ i = fnResult;
324
+ continue;
325
+ }
326
+ }
327
+ if (allowStatements && (token.value === "const" || token.value === "let" || token.value === "var")) {
328
+ const declResult = parseVariableDeclaration(tokens, i, end, env, scopes, usedBare, callSitesBare);
329
+ if (declResult > i) {
330
+ i = declResult;
331
+ continue;
332
+ }
333
+ }
334
+ if (allowStatements && token.value === "catch") {
335
+ const catchResult = parseCatchClause(tokens, i, end, env, scopes, usedBare, callSitesBare);
336
+ if (catchResult > i) {
337
+ i = catchResult;
338
+ continue;
339
+ }
340
+ }
341
+ const arrowResult = parseArrowFunction(tokens, i, end, env, scopes, usedBare, callSitesBare);
342
+ if (arrowResult > i) {
343
+ i = arrowResult;
344
+ continue;
345
+ }
346
+ if (shouldIgnoreIdentifier(token.value)) {
347
+ i++;
348
+ continue;
349
+ }
350
+ if (isShadowed(env, scopes, token.value)) {
351
+ i++;
352
+ continue;
353
+ }
354
+ if (isMemberAccess(tokens, i) || isObjectKey(tokens, i)) {
355
+ i++;
356
+ continue;
357
+ }
358
+ usedBare.add(token.value);
359
+ if (isCallSite(tokens, i)) {
360
+ callSitesBare.add(token.value);
361
+ }
362
+ i++;
363
+ continue;
364
+ }
365
+ if (token.type === "punctuator" && token.value === "(") {
366
+ const arrowResult = parseArrowFunction(tokens, i, end, env, scopes, usedBare, callSitesBare);
367
+ if (arrowResult > i) {
368
+ i = arrowResult;
369
+ continue;
370
+ }
371
+ }
372
+ i++;
440
373
  }
441
- return env;
442
374
  }
443
- function renderRootChildren(children, aliasEnv, env) {
444
- return emitNodesExpression(children, aliasEnv, env);
445
- }
446
- function templateUsesJsx(root) {
447
- if (root.children.length === 0) {
448
- return false;
375
+ function parseArrowFunction(tokens, index, end, env, scopes, usedBare, callSitesBare) {
376
+ const token = tokens[index];
377
+ if (!token) {
378
+ return index;
449
379
  }
450
- if (root.children.length > 1) {
451
- return true;
380
+ if (token.type === "identifier") {
381
+ const next = tokens[index + 1];
382
+ if (next && next.value === "=>") {
383
+ const params = /* @__PURE__ */ new Set([token.value]);
384
+ return analyzeArrowBody(tokens, index + 2, end, params, env, scopes, usedBare, callSitesBare);
385
+ }
452
386
  }
453
- return nodeUsesJsx(root.children[0]);
454
- }
455
- function nodeUsesJsx(node) {
456
- if (node.type === "Element" || node.type === "Text" || node.type === "Component") {
457
- return true;
387
+ if (token.type === "punctuator" && token.value === "(") {
388
+ const closeIndex = findMatchingToken(tokens, index, "(", ")");
389
+ if (closeIndex !== -1) {
390
+ const afterClose = tokens[closeIndex + 1];
391
+ if (afterClose && afterClose.value === "=>") {
392
+ const params = /* @__PURE__ */ new Set();
393
+ collectBindingNamesFromList(tokens, index + 1, closeIndex, params);
394
+ return analyzeArrowBody(tokens, closeIndex + 2, end, params, env, scopes, usedBare, callSitesBare);
395
+ }
396
+ }
458
397
  }
459
- if (node.type === "Expression" || node.type === "JSXPassthrough") {
460
- return false;
398
+ return index;
399
+ }
400
+ function analyzeArrowBody(tokens, start, end, params, env, scopes, usedBare, callSitesBare) {
401
+ const bodyToken = tokens[start];
402
+ if (!bodyToken) {
403
+ return start;
404
+ }
405
+ const scope = new Set(params);
406
+ scopes.push(scope);
407
+ if (bodyToken.type === "punctuator" && bodyToken.value === "{") {
408
+ const closeIndex = findMatchingToken(tokens, start, "{", "}");
409
+ const bodyEnd2 = closeIndex === -1 ? end : closeIndex;
410
+ analyzeTokens(tokens, start + 1, bodyEnd2, env, scopes, usedBare, callSitesBare, true);
411
+ scopes.pop();
412
+ return closeIndex === -1 ? end : closeIndex + 1;
413
+ }
414
+ const bodyEnd = findExpressionEnd(tokens, start, end, EXPRESSION_TERMINATORS);
415
+ analyzeTokens(tokens, start, bodyEnd, env, scopes, usedBare, callSitesBare, false);
416
+ scopes.pop();
417
+ return bodyEnd;
418
+ }
419
+ function parseFunctionExpression(tokens, index, end, env, scopes, usedBare, callSitesBare) {
420
+ let i = index + 1;
421
+ const nameToken = tokens[i];
422
+ let fnName;
423
+ if (nameToken && nameToken.type === "identifier" && tokens[i + 1]?.value === "(") {
424
+ fnName = nameToken.value;
425
+ i++;
461
426
  }
462
- if (node.type === "Conditional") {
463
- return node.branches.some((branch) => branchUsesJsx(branch));
427
+ if (!tokens[i] || tokens[i].value !== "(") {
428
+ return index;
464
429
  }
465
- if (node.type === "For") {
466
- return node.body.some((child) => nodeUsesJsx(child));
430
+ const closeIndex = findMatchingToken(tokens, i, "(", ")");
431
+ if (closeIndex === -1) {
432
+ return index;
433
+ }
434
+ const params = /* @__PURE__ */ new Set();
435
+ collectBindingNamesFromList(tokens, i + 1, closeIndex, params);
436
+ if (fnName) {
437
+ params.add(fnName);
438
+ }
439
+ const bodyStart = closeIndex + 1;
440
+ if (!tokens[bodyStart] || tokens[bodyStart].value !== "{") {
441
+ return index;
442
+ }
443
+ const closeBody = findMatchingToken(tokens, bodyStart, "{", "}");
444
+ const bodyEnd = closeBody === -1 ? end : closeBody;
445
+ scopes.push(params);
446
+ analyzeTokens(tokens, bodyStart + 1, bodyEnd, env, scopes, usedBare, callSitesBare, true);
447
+ scopes.pop();
448
+ return closeBody === -1 ? end : closeBody + 1;
449
+ }
450
+ function parseCatchClause(tokens, index, end, env, scopes, usedBare, callSitesBare) {
451
+ const next = tokens[index + 1];
452
+ if (!next || next.value !== "(") {
453
+ return index;
454
+ }
455
+ const closeIndex = findMatchingToken(tokens, index + 1, "(", ")");
456
+ if (closeIndex === -1) {
457
+ return index;
458
+ }
459
+ const params = /* @__PURE__ */ new Set();
460
+ collectBindingNamesFromList(tokens, index + 2, closeIndex, params);
461
+ const bodyStart = closeIndex + 1;
462
+ if (!tokens[bodyStart] || tokens[bodyStart].value !== "{") {
463
+ return index;
464
+ }
465
+ const closeBody = findMatchingToken(tokens, bodyStart, "{", "}");
466
+ const bodyEnd = closeBody === -1 ? end : closeBody;
467
+ scopes.push(params);
468
+ analyzeTokens(tokens, bodyStart + 1, bodyEnd, env, scopes, usedBare, callSitesBare, true);
469
+ scopes.pop();
470
+ return closeBody === -1 ? end : closeBody + 1;
471
+ }
472
+ function parseVariableDeclaration(tokens, index, end, env, scopes, usedBare, callSitesBare) {
473
+ let i = index + 1;
474
+ const scope = scopes[scopes.length - 1];
475
+ while (i < end) {
476
+ const names = /* @__PURE__ */ new Set();
477
+ const nextIndex = parseBindingPattern(tokens, i, end, names);
478
+ if (nextIndex === i) {
479
+ return index;
480
+ }
481
+ i = nextIndex;
482
+ if (tokens[i] && tokens[i].value === "=") {
483
+ const initStart = i + 1;
484
+ const initEnd = findExpressionEnd(tokens, initStart, end, DECLARATION_TERMINATORS);
485
+ analyzeTokens(tokens, initStart, initEnd, env, scopes, usedBare, callSitesBare, false);
486
+ i = initEnd;
487
+ }
488
+ for (const name of names) {
489
+ scope.add(name);
490
+ }
491
+ if (tokens[i] && tokens[i].value === ",") {
492
+ i++;
493
+ continue;
494
+ }
495
+ break;
467
496
  }
468
- return false;
497
+ return i;
469
498
  }
470
- function branchUsesJsx(branch) {
471
- if (!branch.body.length) {
472
- return false;
499
+ function parseBindingPattern(tokens, start, end, names) {
500
+ const token = tokens[start];
501
+ if (!token) {
502
+ return start;
473
503
  }
474
- return branch.body.some((child) => nodeUsesJsx(child));
475
- }
476
- function emitNodeInJsx(node, aliasEnv, env) {
477
- if (node.type === "Text") {
478
- return emitText(node, env);
504
+ if (token.type === "identifier") {
505
+ names.add(token.value);
506
+ return start + 1;
479
507
  }
480
- if (node.type === "Expression") {
481
- return `{${emitExpressionValue(node.value, env)}}`;
508
+ if (token.value === "{") {
509
+ return parseObjectPattern(tokens, start + 1, end, names);
482
510
  }
483
- if (node.type === "JSXPassthrough") {
484
- return `{${emitJsxExpression(node.expression, env)}}`;
511
+ if (token.value === "[") {
512
+ return parseArrayPattern(tokens, start + 1, end, names);
485
513
  }
486
- if (node.type === "Conditional") {
487
- return `{${emitConditionalExpression(node, aliasEnv, env)}}`;
514
+ return start + 1;
515
+ }
516
+ function parseObjectPattern(tokens, start, end, names) {
517
+ let i = start;
518
+ while (i < end) {
519
+ const token = tokens[i];
520
+ if (!token) {
521
+ return i;
522
+ }
523
+ if (token.value === "}") {
524
+ return i + 1;
525
+ }
526
+ if (token.value === ",") {
527
+ i++;
528
+ continue;
529
+ }
530
+ if (token.value === "...") {
531
+ i++;
532
+ i = parseBindingPattern(tokens, i, end, names);
533
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
534
+ continue;
535
+ }
536
+ if (token.value === "[") {
537
+ const closeIndex = findMatchingToken(tokens, i, "[", "]");
538
+ i = closeIndex === -1 ? end : closeIndex + 1;
539
+ if (tokens[i] && tokens[i].value === ":") {
540
+ i++;
541
+ i = parseBindingPattern(tokens, i, end, names);
542
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
543
+ }
544
+ continue;
545
+ }
546
+ if (token.type === "identifier" || token.type === "literal") {
547
+ const key = token.value;
548
+ i++;
549
+ if (tokens[i] && tokens[i].value === ":") {
550
+ i++;
551
+ i = parseBindingPattern(tokens, i, end, names);
552
+ } else if (token.type === "identifier") {
553
+ names.add(key);
554
+ }
555
+ i = skipDefaultValue(tokens, i, end, OBJECT_PATTERN_TERMINATORS);
556
+ continue;
557
+ }
558
+ i++;
559
+ }
560
+ return i;
561
+ }
562
+ function parseArrayPattern(tokens, start, end, names) {
563
+ let i = start;
564
+ while (i < end) {
565
+ const token = tokens[i];
566
+ if (!token) {
567
+ return i;
568
+ }
569
+ if (token.value === "]") {
570
+ return i + 1;
571
+ }
572
+ if (token.value === ",") {
573
+ i++;
574
+ continue;
575
+ }
576
+ if (token.value === "...") {
577
+ i++;
578
+ i = parseBindingPattern(tokens, i, end, names);
579
+ i = skipDefaultValue(tokens, i, end, ARRAY_PATTERN_TERMINATORS);
580
+ continue;
581
+ }
582
+ i = parseBindingPattern(tokens, i, end, names);
583
+ i = skipDefaultValue(tokens, i, end, ARRAY_PATTERN_TERMINATORS);
584
+ if (tokens[i] && tokens[i].value === ",") {
585
+ i++;
586
+ }
587
+ }
588
+ return i;
589
+ }
590
+ function collectBindingNamesFromList(tokens, start, end, names) {
591
+ let i = start;
592
+ while (i < end) {
593
+ if (tokens[i] && tokens[i].value === ",") {
594
+ i++;
595
+ continue;
596
+ }
597
+ const nextIndex = parseBindingPattern(tokens, i, end, names);
598
+ if (nextIndex === i) {
599
+ i++;
600
+ continue;
601
+ }
602
+ i = skipParameterSuffix(tokens, nextIndex, end);
603
+ if (tokens[i] && tokens[i].value === ",") {
604
+ i++;
605
+ }
606
+ }
607
+ }
608
+ function skipParameterSuffix(tokens, start, end) {
609
+ if (!tokens[start]) {
610
+ return start;
611
+ }
612
+ if (tokens[start].value === "=" || tokens[start].value === ":") {
613
+ return findExpressionEnd(tokens, start + 1, end, PARAMETER_TERMINATORS);
614
+ }
615
+ return start;
616
+ }
617
+ function skipDefaultValue(tokens, start, end, terminators) {
618
+ if (!tokens[start] || tokens[start].value !== "=") {
619
+ return start;
620
+ }
621
+ return findExpressionEnd(tokens, start + 1, end, terminators);
622
+ }
623
+ function findMatchingToken(tokens, start, open, close) {
624
+ let depth = 0;
625
+ for (let i = start; i < tokens.length; i++) {
626
+ const value = tokens[i].value;
627
+ if (value === open) {
628
+ depth++;
629
+ } else if (value === close) {
630
+ depth--;
631
+ if (depth === 0) {
632
+ return i;
633
+ }
634
+ }
635
+ }
636
+ return -1;
637
+ }
638
+ function findExpressionEnd(tokens, start, end, terminators) {
639
+ let depthParen = 0;
640
+ let depthBracket = 0;
641
+ let depthBrace = 0;
642
+ for (let i = start; i < end; i++) {
643
+ const value = tokens[i].value;
644
+ if (value === "(") {
645
+ depthParen++;
646
+ } else if (value === ")") {
647
+ if (depthParen === 0 && terminators.has(value)) {
648
+ return i;
649
+ }
650
+ depthParen = Math.max(0, depthParen - 1);
651
+ } else if (value === "[") {
652
+ depthBracket++;
653
+ } else if (value === "]") {
654
+ if (depthBracket === 0 && terminators.has(value)) {
655
+ return i;
656
+ }
657
+ depthBracket = Math.max(0, depthBracket - 1);
658
+ } else if (value === "{") {
659
+ depthBrace++;
660
+ } else if (value === "}") {
661
+ if (depthBrace === 0 && terminators.has(value)) {
662
+ return i;
663
+ }
664
+ depthBrace = Math.max(0, depthBrace - 1);
665
+ }
666
+ if (depthParen === 0 && depthBracket === 0 && depthBrace === 0 && terminators.has(value)) {
667
+ return i;
668
+ }
669
+ }
670
+ return end;
671
+ }
672
+ function isMemberAccess(tokens, index) {
673
+ const prev = tokens[index - 1];
674
+ return prev?.value === "." || prev?.value === "?.";
675
+ }
676
+ function isObjectKey(tokens, index) {
677
+ const next = tokens[index + 1];
678
+ if (!next || next.value !== ":") {
679
+ return false;
680
+ }
681
+ const prev = tokens[index - 1];
682
+ return prev?.value === "{" || prev?.value === ",";
683
+ }
684
+ function isCallSite(tokens, index) {
685
+ const next = tokens[index + 1];
686
+ return next?.value === "(";
687
+ }
688
+ function isShadowed(env, scopes, name) {
689
+ if (isLocal(env, name)) {
690
+ return true;
691
+ }
692
+ for (let i = scopes.length - 1; i >= 0; i--) {
693
+ if (scopes[i].has(name)) {
694
+ return true;
695
+ }
696
+ }
697
+ return false;
698
+ }
699
+ var PARAMETER_TERMINATORS = /* @__PURE__ */ new Set([","]);
700
+ var OBJECT_PATTERN_TERMINATORS = /* @__PURE__ */ new Set([",", "}"]);
701
+ var ARRAY_PATTERN_TERMINATORS = /* @__PURE__ */ new Set([",", "]"]);
702
+ var EXPRESSION_TERMINATORS = /* @__PURE__ */ new Set([",", ")", "]", "}", ";"]);
703
+ var DECLARATION_TERMINATORS = /* @__PURE__ */ new Set([",", ")", "]", "}", ";"]);
704
+
705
+ // src/codegen.ts
706
+ function generateRenderModule(root, options) {
707
+ const { prelude, inputsType, inputsPrelude, jsx, isTsx } = buildModuleParts(root, options);
708
+ const parts = [...prelude, inputsType];
709
+ if (!isTsx) {
710
+ parts.push(`/** @param {any} __inputs */`);
711
+ }
712
+ const functionLines = [
713
+ isTsx ? "export function render(__inputs: any) {" : "export function render(__inputs) {"
714
+ ];
715
+ if (inputsPrelude) {
716
+ functionLines.push(` ${inputsPrelude}`);
717
+ }
718
+ functionLines.push(` return ${jsx};`, `}`);
719
+ parts.push(functionLines.join("\n"));
720
+ return parts.join("\n\n");
721
+ }
722
+ function buildModuleParts(root, options) {
723
+ const { jsxRuntime, flavor } = options;
724
+ const isTsx = flavor === "tsx";
725
+ const aliasEnv = buildClassAliasEnvironment(root.classAliases);
726
+ const env = createTemplateEnv(root.inputsDecls);
727
+ const jsx = renderRootChildren(root.children, aliasEnv, env);
728
+ const inputsPrelude = emitInputsPrelude(root.inputsDecls);
729
+ const prelude = [];
730
+ if (root.clientComponent) {
731
+ prelude.push(`"use client";`);
732
+ }
733
+ if (jsxRuntime === "classic" && templateUsesJsx(root)) {
734
+ prelude.push(`import React from "react";`);
735
+ }
736
+ const inputsType = emitInputsType(root.inputs, flavor);
737
+ return { prelude, inputsType, inputsPrelude, jsx, isTsx };
738
+ }
739
+ function buildClassAliasEnvironment(decl) {
740
+ const env = /* @__PURE__ */ new Map();
741
+ if (!decl) {
742
+ return env;
743
+ }
744
+ for (const alias of decl.aliases) {
745
+ env.set(alias.name, alias.classes);
746
+ }
747
+ return env;
748
+ }
749
+ function renderRootChildren(children, aliasEnv, env) {
750
+ return emitNodesExpression(children, aliasEnv, env);
751
+ }
752
+ function templateUsesJsx(root) {
753
+ if (root.children.length === 0) {
754
+ return false;
755
+ }
756
+ if (root.children.length > 1) {
757
+ return true;
758
+ }
759
+ return nodeUsesJsx(root.children[0]);
760
+ }
761
+ function nodeUsesJsx(node) {
762
+ if (node.type === "Element" || node.type === "Text" || node.type === "Component") {
763
+ return true;
764
+ }
765
+ if (node.type === "Expression" || node.type === "JSXPassthrough") {
766
+ return false;
767
+ }
768
+ if (node.type === "Conditional") {
769
+ return node.branches.some((branch) => branchUsesJsx(branch));
770
+ }
771
+ if (node.type === "For") {
772
+ return node.body.some((child) => nodeUsesJsx(child));
773
+ }
774
+ return false;
775
+ }
776
+ function branchUsesJsx(branch) {
777
+ if (!branch.body.length) {
778
+ return false;
779
+ }
780
+ return branch.body.some((child) => nodeUsesJsx(child));
781
+ }
782
+ function emitNodeInJsx(node, aliasEnv, env) {
783
+ if (node.type === "Text") {
784
+ return emitText(node, env);
785
+ }
786
+ if (node.type === "Expression") {
787
+ return `{${emitExpressionValue(node.value, env)}}`;
788
+ }
789
+ if (node.type === "JSXPassthrough") {
790
+ return `{${emitJsxExpression(node.expression, env)}}`;
791
+ }
792
+ if (node.type === "Conditional") {
793
+ return `{${emitConditionalExpression(node, aliasEnv, env)}}`;
488
794
  }
489
795
  if (node.type === "For") {
490
796
  return `{${emitForExpression(node, aliasEnv, env)}}`;
@@ -508,8 +814,8 @@ function emitElement(node, aliasEnv, env) {
508
814
  }
509
815
  function emitComponent(node, aliasEnv, env) {
510
816
  const attrs = emitAttributes(node.attributes, aliasEnv, env);
511
- const slotProps = emitSlotProps(node, aliasEnv, env);
512
- const allAttrs = `${attrs}${slotProps}`;
817
+ const slotBindings = emitSlotBindings(node, aliasEnv, env);
818
+ const allAttrs = `${attrs}${slotBindings}`;
513
819
  const children = emitChildrenWithSpacing(node.children, aliasEnv, env);
514
820
  if (children.length > 0) {
515
821
  return `<${node.name}${allAttrs}>${children}</${node.name}>`;
@@ -547,7 +853,7 @@ function emitAttributes(attributes, aliasEnv, env) {
547
853
  return ` ${attr.name}=${emitAttributeValue(attr.value, env)}`;
548
854
  }).join("");
549
855
  }
550
- function emitSlotProps(node, aliasEnv, env) {
856
+ function emitSlotBindings(node, aliasEnv, env) {
551
857
  if (!node.slots || node.slots.length === 0) {
552
858
  return "";
553
859
  }
@@ -678,41 +984,41 @@ function emitSingleNodeExpression(node, aliasEnv, env) {
678
984
  }
679
985
  return emitNodeInJsx(node, aliasEnv, env);
680
986
  }
681
- function emitPropsType(props, flavor) {
987
+ function emitInputsType(inputs, flavor) {
682
988
  if (flavor === "tsx") {
683
- return emitTsPropsType(props);
989
+ return emitTsInputsType(inputs);
684
990
  }
685
- return emitJsDocPropsType(props);
991
+ return emitJsDocInputsType(inputs);
686
992
  }
687
- function emitJsDocPropsType(props) {
688
- if (!props) {
689
- return "/** @typedef {any} Props */";
993
+ function emitJsDocInputsType(inputs) {
994
+ if (!inputs) {
995
+ return "/** @typedef {any} Inputs */";
690
996
  }
691
- if (!props.fields.length) {
692
- return "/** @typedef {{}} Props */";
997
+ if (!inputs.fields.length) {
998
+ return "/** @typedef {{}} Inputs */";
693
999
  }
694
- const fields = props.fields.map((field) => {
1000
+ const fields = inputs.fields.map((field) => {
695
1001
  const optional = field.optional ? "?" : "";
696
1002
  return `${field.name}${optional}: ${field.typeText}`;
697
1003
  }).join("; ");
698
- return `/** @typedef {{ ${fields} }} Props */`;
1004
+ return `/** @typedef {{ ${fields} }} Inputs */`;
699
1005
  }
700
- function emitTsPropsType(props) {
701
- if (!props || props.fields.length === 0) {
702
- return "export type Props = Record<string, never>;";
1006
+ function emitTsInputsType(inputs) {
1007
+ if (!inputs || inputs.fields.length === 0) {
1008
+ return "export type Inputs = Record<string, never>;";
703
1009
  }
704
- const lines = props.fields.map((field) => {
1010
+ const lines = inputs.fields.map((field) => {
705
1011
  const optional = field.optional ? "?" : "";
706
1012
  return ` ${field.name}${optional}: ${field.typeText};`;
707
1013
  });
708
- return ["export interface Props {", ...lines, "}"].join("\n");
1014
+ return ["export interface Inputs {", ...lines, "}"].join("\n");
709
1015
  }
710
- function emitPropsDestructure(props) {
711
- if (!props || props.fields.length === 0) {
1016
+ function emitInputsPrelude(inputsDecls) {
1017
+ if (!inputsDecls || inputsDecls.length === 0) {
712
1018
  return null;
713
1019
  }
714
- const names = props.fields.map((field) => field.name);
715
- return `const { ${names.join(", ")} } = props ?? {};`;
1020
+ const names = inputsDecls.map((decl) => decl.name);
1021
+ return `const { ${names.join(", ")} } = __inputs ?? {};`;
716
1022
  }
717
1023
  function escapeText(value) {
718
1024
  return value.replace(/[&<>{}]/g, (char) => {
@@ -873,837 +1179,197 @@ function unescapeChar(char, quote) {
873
1179
  return "'";
874
1180
  default:
875
1181
  if (char === quote) {
876
- return quote;
877
- }
878
- return char;
879
- }
880
- }
881
- function buildClassAliasEnvironment2(decl) {
882
- const env = /* @__PURE__ */ new Map();
883
- if (!decl) {
884
- return env;
885
- }
886
- for (const alias of decl.aliases) {
887
- env.set(alias.name, alias.classes);
888
- }
889
- return env;
890
- }
891
- function expandClasses2(classes, aliasEnv) {
892
- const result = [];
893
- for (const cls of classes) {
894
- const match = cls.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/);
895
- if (!match) {
896
- result.push(cls);
897
- continue;
898
- }
899
- const aliasClasses = aliasEnv.get(match[1]);
900
- if (!aliasClasses) {
901
- continue;
902
- }
903
- result.push(...aliasClasses);
904
- }
905
- return result;
906
- }
907
- function escapeStaticText(value) {
908
- return value.replace(/[&<>{}]/g, (char) => {
909
- switch (char) {
910
- case "&":
911
- return "&amp;";
912
- case "<":
913
- return "&lt;";
914
- case ">":
915
- return "&gt;";
916
- case "{":
917
- return "&#123;";
918
- case "}":
919
- return "&#125;";
920
- default:
921
- return char;
922
- }
923
- });
924
- }
925
- function escapeAttributeValue(value) {
926
- return value.replace(/["&<>]/g, (char) => {
927
- switch (char) {
928
- case "&":
929
- return "&amp;";
930
- case "<":
931
- return "&lt;";
932
- case ">":
933
- return "&gt;";
934
- case '"':
935
- return "&quot;";
936
- default:
937
- return char;
938
- }
939
- });
940
- }
941
-
942
- // src/diagnostics.ts
943
- function createSpan(line, col, length, lineOffset) {
944
- const startOffset = lineOffset + col - 1;
945
- return {
946
- start: { line, col, offset: startOffset },
947
- end: { line, col: col + length, offset: startOffset + length }
948
- };
949
- }
950
-
951
- // src/dialect.ts
952
- function enforceDialect(root, config) {
953
- const diagnostics = [];
954
- if (root.idToken) {
955
- diagnostics.push(
956
- ...evaluateToken(
957
- { kind: "id", token: root.idToken, span: root.idTokenSpan },
958
- config.tokens.id
959
- )
960
- );
961
- }
962
- walkNodes(root.children, (occurrence) => {
963
- const rule = config.tokens[occurrence.kind];
964
- diagnostics.push(...evaluateToken(occurrence, rule));
965
- });
966
- return diagnostics;
967
- }
968
- function walkNodes(nodes, onToken) {
969
- for (const node of nodes) {
970
- if (node.type === "For") {
971
- onFor(node, onToken);
972
- walkNodes(node.body, onToken);
973
- continue;
974
- }
975
- if (node.type === "Conditional") {
976
- onConditional(node, onToken);
977
- continue;
978
- }
979
- if (node.type === "Element" || node.type === "Component") {
980
- walkNodes(node.children, onToken);
981
- if (node.type === "Component" && node.slots) {
982
- for (const slot of node.slots) {
983
- walkNodes(slot.children, onToken);
984
- }
985
- }
986
- continue;
987
- }
988
- }
989
- }
990
- function onFor(node, onToken) {
991
- if (!node.token) {
992
- return;
993
- }
994
- onToken({ kind: "for", token: node.token, span: node.tokenSpan });
995
- }
996
- function onConditional(node, onToken) {
997
- for (const branch of node.branches) {
998
- onBranch(branch, onToken);
999
- walkNodes(branch.body, onToken);
1000
- }
1001
- }
1002
- function onBranch(branch, onToken) {
1003
- if (!branch.token || !branch.kind) {
1004
- return;
1005
- }
1006
- onToken({ kind: branch.kind, token: branch.token, span: branch.tokenSpan });
1007
- }
1008
- function evaluateToken(occurrence, rule) {
1009
- const diagnostics = [];
1010
- const used = occurrence.token;
1011
- const preferred = rule.preferred;
1012
- const isAllowed = rule.allow.includes(used);
1013
- if (!isAllowed) {
1014
- const severity = levelToSeverity(rule.onDisallowed);
1015
- if (severity) {
1016
- diagnostics.push(
1017
- createDialectDiagnostic(
1018
- "dialect.token.disallowed",
1019
- severity,
1020
- used,
1021
- preferred,
1022
- occurrence.span,
1023
- `Token "${used}" is not allowed for ${occurrence.kind}. Preferred: "${preferred}".`
1024
- )
1025
- );
1026
- }
1027
- return diagnostics;
1028
- }
1029
- if (used !== preferred) {
1030
- const severity = levelToSeverity(rule.onDisallowed);
1031
- if (severity) {
1032
- diagnostics.push(
1033
- createDialectDiagnostic(
1034
- "dialect.token.nonPreferred",
1035
- severity,
1036
- used,
1037
- preferred,
1038
- occurrence.span,
1039
- `Token "${used}" is allowed but not preferred for ${occurrence.kind}. Preferred: "${preferred}".`
1040
- )
1041
- );
1042
- }
1043
- }
1044
- return diagnostics;
1045
- }
1046
- function createDialectDiagnostic(code, severity, used, preferred, span, message) {
1047
- const fix = span ? {
1048
- range: span,
1049
- replacementText: preferred
1050
- } : void 0;
1051
- return {
1052
- severity,
1053
- code,
1054
- message: message.replace(/\\s+/g, " "),
1055
- span,
1056
- fix
1057
- };
1058
- }
1059
- function levelToSeverity(level) {
1060
- if (level === "off") {
1061
- return null;
1062
- }
1063
- if (level === "error") {
1064
- return "error";
1065
- }
1066
- return "warning";
1067
- }
1068
-
1069
- // src/props.ts
1070
- var IGNORED_IDENTIFIERS2 = /* @__PURE__ */ new Set([
1071
- "null",
1072
- "undefined",
1073
- "true",
1074
- "false",
1075
- "NaN",
1076
- "Infinity",
1077
- "this",
1078
- "props"
1079
- ]);
1080
- var RESERVED_KEYWORDS2 = /* @__PURE__ */ new Set([
1081
- "await",
1082
- "break",
1083
- "case",
1084
- "catch",
1085
- "class",
1086
- "const",
1087
- "continue",
1088
- "debugger",
1089
- "default",
1090
- "delete",
1091
- "do",
1092
- "else",
1093
- "enum",
1094
- "export",
1095
- "extends",
1096
- "false",
1097
- "finally",
1098
- "for",
1099
- "function",
1100
- "if",
1101
- "import",
1102
- "in",
1103
- "instanceof",
1104
- "let",
1105
- "new",
1106
- "null",
1107
- "return",
1108
- "super",
1109
- "switch",
1110
- "this",
1111
- "throw",
1112
- "true",
1113
- "try",
1114
- "typeof",
1115
- "var",
1116
- "void",
1117
- "while",
1118
- "with",
1119
- "yield"
1120
- ]);
1121
- function enforceProps(root, propsConfig) {
1122
- const diagnostics = [];
1123
- const declaredProps = /* @__PURE__ */ new Map();
1124
- const usedLocal = /* @__PURE__ */ new Map();
1125
- const usedNamespace = /* @__PURE__ */ new Map();
1126
- const usedAny = /* @__PURE__ */ new Set();
1127
- const missingReported = /* @__PURE__ */ new Set();
1128
- const localStyleReported = /* @__PURE__ */ new Set();
1129
- const namespaceStyleReported = /* @__PURE__ */ new Set();
1130
- if (root.props?.fields) {
1131
- for (const field of root.props.fields) {
1132
- declaredProps.set(field.name, field);
1133
- }
1134
- }
1135
- const preferStyle = propsConfig.preferAccessStyle;
1136
- const flagLocalStyle = !propsConfig.allowDeclaredLocals || preferStyle === "namespace";
1137
- const flagNamespaceStyle = !propsConfig.allowPropsNamespace || preferStyle === "locals";
1138
- const walkNodes2 = (nodes, locals) => {
1139
- for (const node of nodes) {
1140
- if (node.type === "Conditional") {
1141
- handleConditional(node, locals);
1142
- continue;
1143
- }
1144
- if (node.type === "For") {
1145
- handleFor(node, locals);
1146
- continue;
1147
- }
1148
- if (node.type === "Expression") {
1149
- handleExpression(node.value, node.span, locals);
1150
- continue;
1151
- }
1152
- if (node.type === "JSXPassthrough") {
1153
- handleExpression(node.expression, node.span, locals);
1154
- continue;
1155
- }
1156
- if (node.type === "Text") {
1157
- handleText(node.parts, locals);
1158
- continue;
1159
- }
1160
- if (node.type === "Element") {
1161
- handleElement(node, locals);
1162
- continue;
1163
- }
1164
- if (node.type === "Component") {
1165
- handleComponent(node, locals);
1166
- continue;
1167
- }
1168
- }
1169
- };
1170
- const handleConditional = (node, locals) => {
1171
- for (const branch of node.branches) {
1172
- if (branch.test) {
1173
- handleExpression(branch.test, branch.testSpan, locals);
1174
- }
1175
- walkNodes2(branch.body, locals);
1176
- }
1177
- };
1178
- const handleFor = (node, locals) => {
1179
- handleExpression(node.arrayExpr, node.arrayExprSpan, locals);
1180
- const nextLocals = new Set(locals);
1181
- nextLocals.add(node.itemName);
1182
- walkNodes2(node.body, nextLocals);
1183
- };
1184
- const handleElement = (node, locals) => {
1185
- if (node.guard) {
1186
- handleExpression(node.guard, node.guardSpan, locals);
1187
- }
1188
- handleAttributes(node.attributes, locals);
1189
- walkNodes2(node.children, locals);
1190
- };
1191
- const handleComponent = (node, locals) => {
1192
- if (node.guard) {
1193
- handleExpression(node.guard, node.guardSpan, locals);
1194
- }
1195
- handleAttributes(node.attributes, locals);
1196
- if (node.slots) {
1197
- for (const slot of node.slots) {
1198
- walkNodes2(slot.children, locals);
1199
- }
1200
- }
1201
- walkNodes2(node.children, locals);
1202
- };
1203
- const handleText = (parts, locals) => {
1204
- for (const part of parts) {
1205
- if (part.type === "expr") {
1206
- handleExpression(part.value, part.span, locals);
1207
- }
1208
- }
1209
- };
1210
- const handleAttributes = (attributes, locals) => {
1211
- for (const attr of attributes) {
1212
- if (!attr.value) continue;
1213
- const trimmed = attr.value.trim();
1214
- if (!trimmed || trimmed.startsWith("'") || trimmed.startsWith('"')) {
1215
- continue;
1216
- }
1217
- handleExpression(trimmed, void 0, locals);
1218
- }
1219
- };
1220
- const handleExpression = (expression, span, locals) => {
1221
- const occurrences = scanExpression(expression);
1222
- for (const occurrence of occurrences) {
1223
- const name = occurrence.name;
1224
- if (occurrence.kind === "local" && locals.has(name)) {
1225
- continue;
1226
- }
1227
- if (shouldIgnoreIdentifier2(name)) {
1228
- continue;
1229
- }
1230
- const usageSpan = span ? offsetSpan(span, occurrence.index, occurrence.length) : void 0;
1231
- if (occurrence.kind === "namespace") {
1232
- recordUsage(usedNamespace, name, usageSpan);
1233
- usedAny.add(name);
1234
- if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
1235
- const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
1236
- if (severity) {
1237
- diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
1238
- missingReported.add(name);
1239
- }
1240
- }
1241
- if (flagNamespaceStyle && !namespaceStyleReported.has(name)) {
1242
- const severity = levelToSeverity2(propsConfig.diagnostics.style);
1243
- if (severity) {
1244
- diagnostics.push(
1245
- createStyleDiagnostic(
1246
- name,
1247
- "namespace",
1248
- severity,
1249
- usageSpan,
1250
- propsConfig.allowPropsNamespace
1251
- )
1252
- );
1253
- namespaceStyleReported.add(name);
1254
- }
1255
- }
1256
- continue;
1257
- }
1258
- recordUsage(usedLocal, name, usageSpan);
1259
- usedAny.add(name);
1260
- if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
1261
- const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
1262
- if (severity) {
1263
- diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
1264
- missingReported.add(name);
1265
- }
1266
- }
1267
- if (flagLocalStyle && !localStyleReported.has(name)) {
1268
- const severity = levelToSeverity2(propsConfig.diagnostics.style);
1269
- if (severity) {
1270
- diagnostics.push(
1271
- createStyleDiagnostic(name, "local", severity, usageSpan, propsConfig.allowDeclaredLocals)
1272
- );
1273
- localStyleReported.add(name);
1274
- }
1275
- }
1276
- }
1277
- };
1278
- walkNodes2(root.children, /* @__PURE__ */ new Set());
1279
- if (root.props?.fields) {
1280
- for (const field of root.props.fields) {
1281
- if (!usedAny.has(field.name)) {
1282
- const severity = levelToSeverity2(propsConfig.diagnostics.unusedDeclaration);
1283
- if (severity) {
1284
- diagnostics.push({
1285
- severity,
1286
- code: "props.unusedDeclaration",
1287
- message: `Prop "${field.name}" is declared but never used.`,
1288
- span: field.span
1289
- });
1290
- }
1291
- }
1292
- }
1293
- }
1294
- if (propsConfig.requirePropsBlockWhen.enabled && !root.props && usedAny.size >= propsConfig.requirePropsBlockWhen.minUniquePropsUsed) {
1295
- const severity = levelToSeverity2(propsConfig.requirePropsBlockWhen.severity);
1296
- if (severity) {
1297
- diagnostics.push({
1298
- severity,
1299
- code: "props.block.recommendedOrRequired",
1300
- message: `Props block recommended: ${usedAny.size} unique prop${usedAny.size === 1 ? "" : "s"} used.`
1301
- });
1302
- }
1303
- }
1304
- return diagnostics;
1305
- }
1306
- function createMissingDeclarationDiagnostic(name, severity, span) {
1307
- return {
1308
- severity,
1309
- code: "props.missingDeclaration",
1310
- message: `Prop \`${name}\` is used but not declared in \`#props\`.`,
1311
- span,
1312
- data: {
1313
- kind: "addPropDeclaration",
1314
- propName: name
1315
- }
1316
- };
1317
- }
1318
- function createStyleDiagnostic(name, kind, severity, span, allowed) {
1319
- if (kind === "namespace") {
1320
- const message2 = allowed ? `props.${name} is allowed but not preferred; use "${name}" instead.` : `props.${name} is disabled; use "${name}" instead.`;
1321
- return {
1322
- severity,
1323
- code: "props.style.nonPreferred",
1324
- message: message2,
1325
- span
1326
- };
1327
- }
1328
- const message = allowed ? `"${name}" is allowed but not preferred; use props.${name} instead.` : `"${name}" is disabled; use props.${name} instead.`;
1329
- return {
1330
- severity,
1331
- code: "props.style.nonPreferred",
1332
- message,
1333
- span
1334
- };
1335
- }
1336
- function recordUsage(map, name, span) {
1337
- const existing = map.get(name);
1338
- if (existing) {
1339
- existing.count += 1;
1340
- return;
1341
- }
1342
- map.set(name, { count: 1, span });
1343
- }
1344
- function scanExpression(expression) {
1345
- const occurrences = [];
1346
- let i = 0;
1347
- let state = "code";
1348
- while (i < expression.length) {
1349
- const ch = expression[i];
1350
- if (state === "code") {
1351
- if (ch === "'" || ch === '"') {
1352
- state = ch === "'" ? "single" : "double";
1353
- i++;
1354
- continue;
1355
- }
1356
- if (ch === "`") {
1357
- state = "template";
1358
- i++;
1359
- continue;
1360
- }
1361
- if (ch === "/" && expression[i + 1] === "/") {
1362
- state = "line";
1363
- i += 2;
1364
- continue;
1365
- }
1366
- if (ch === "/" && expression[i + 1] === "*") {
1367
- state = "block";
1368
- i += 2;
1369
- continue;
1370
- }
1371
- if (isIdentifierStart2(ch)) {
1372
- const start = i;
1373
- i++;
1374
- while (i < expression.length && isIdentifierPart2(expression[i])) {
1375
- i++;
1376
- }
1377
- const name = expression.slice(start, i);
1378
- const prevNonSpace = findPreviousNonSpace2(expression, start - 1);
1379
- if (name === "props" && prevNonSpace !== ".") {
1380
- const namespace = readNamespaceAccess(expression, i);
1381
- if (namespace) {
1382
- occurrences.push({
1383
- name: namespace.name,
1384
- kind: "namespace",
1385
- index: namespace.index,
1386
- length: namespace.name.length
1387
- });
1388
- i = namespace.endIndex;
1389
- continue;
1390
- }
1391
- }
1392
- if (prevNonSpace !== ".") {
1393
- occurrences.push({ name, kind: "local", index: start, length: name.length });
1394
- }
1395
- continue;
1396
- }
1397
- i++;
1398
- continue;
1399
- }
1400
- if (state === "line") {
1401
- if (ch === "\n") {
1402
- state = "code";
1403
- }
1404
- i++;
1405
- continue;
1406
- }
1407
- if (state === "block") {
1408
- if (ch === "*" && expression[i + 1] === "/") {
1409
- state = "code";
1410
- i += 2;
1411
- continue;
1412
- }
1413
- i++;
1414
- continue;
1415
- }
1416
- if (state === "single") {
1417
- if (ch === "\\") {
1418
- i += 2;
1419
- continue;
1420
- }
1421
- if (ch === "'") {
1422
- state = "code";
1423
- }
1424
- i++;
1425
- continue;
1426
- }
1427
- if (state === "double") {
1428
- if (ch === "\\") {
1429
- i += 2;
1430
- continue;
1431
- }
1432
- if (ch === '"') {
1433
- state = "code";
1434
- }
1435
- i++;
1436
- continue;
1437
- }
1438
- if (state === "template") {
1439
- if (ch === "\\") {
1440
- i += 2;
1441
- continue;
1442
- }
1443
- if (ch === "`") {
1444
- state = "code";
1445
- i++;
1446
- continue;
1447
- }
1448
- i++;
1449
- continue;
1450
- }
1182
+ return quote;
1183
+ }
1184
+ return char;
1451
1185
  }
1452
- return occurrences;
1453
1186
  }
1454
- function readNamespaceAccess(expression, startIndex) {
1455
- let i = startIndex;
1456
- while (i < expression.length && /\s/.test(expression[i])) {
1457
- i++;
1458
- }
1459
- if (expression[i] === "?") {
1460
- if (expression[i + 1] !== ".") {
1461
- return null;
1462
- }
1463
- i += 2;
1464
- } else if (expression[i] === ".") {
1465
- i++;
1466
- } else {
1467
- return null;
1468
- }
1469
- while (i < expression.length && /\s/.test(expression[i])) {
1470
- i++;
1471
- }
1472
- if (!isIdentifierStart2(expression[i])) {
1473
- return null;
1187
+ function buildClassAliasEnvironment2(decl) {
1188
+ const env = /* @__PURE__ */ new Map();
1189
+ if (!decl) {
1190
+ return env;
1474
1191
  }
1475
- const propStart = i;
1476
- i++;
1477
- while (i < expression.length && isIdentifierPart2(expression[i])) {
1478
- i++;
1192
+ for (const alias of decl.aliases) {
1193
+ env.set(alias.name, alias.classes);
1479
1194
  }
1480
- return {
1481
- name: expression.slice(propStart, i),
1482
- index: propStart,
1483
- endIndex: i
1484
- };
1195
+ return env;
1485
1196
  }
1486
- function findPreviousNonSpace2(text, index) {
1487
- for (let i = index; i >= 0; i--) {
1488
- const ch = text[i];
1489
- if (!/\s/.test(ch)) {
1490
- return ch;
1197
+ function expandClasses2(classes, aliasEnv) {
1198
+ const result = [];
1199
+ for (const cls of classes) {
1200
+ const match = cls.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/);
1201
+ if (!match) {
1202
+ result.push(cls);
1203
+ continue;
1491
1204
  }
1205
+ const aliasClasses = aliasEnv.get(match[1]);
1206
+ if (!aliasClasses) {
1207
+ continue;
1208
+ }
1209
+ result.push(...aliasClasses);
1492
1210
  }
1493
- return null;
1494
- }
1495
- function isIdentifierStart2(ch) {
1496
- return /[A-Za-z_$]/.test(ch);
1497
- }
1498
- function isIdentifierPart2(ch) {
1499
- return /[A-Za-z0-9_$]/.test(ch);
1211
+ return result;
1500
1212
  }
1501
- function shouldIgnoreIdentifier2(name) {
1502
- return IGNORED_IDENTIFIERS2.has(name) || RESERVED_KEYWORDS2.has(name);
1213
+ function escapeStaticText(value) {
1214
+ return value.replace(/[&<>{}]/g, (char) => {
1215
+ switch (char) {
1216
+ case "&":
1217
+ return "&amp;";
1218
+ case "<":
1219
+ return "&lt;";
1220
+ case ">":
1221
+ return "&gt;";
1222
+ case "{":
1223
+ return "&#123;";
1224
+ case "}":
1225
+ return "&#125;";
1226
+ default:
1227
+ return char;
1228
+ }
1229
+ });
1503
1230
  }
1504
- function levelToSeverity2(level) {
1505
- if (level === "off") {
1506
- return null;
1507
- }
1508
- if (level === "error") {
1509
- return "error";
1510
- }
1511
- return "warning";
1231
+ function escapeAttributeValue(value) {
1232
+ return value.replace(/["&<>]/g, (char) => {
1233
+ switch (char) {
1234
+ case "&":
1235
+ return "&amp;";
1236
+ case "<":
1237
+ return "&lt;";
1238
+ case ">":
1239
+ return "&gt;";
1240
+ case '"':
1241
+ return "&quot;";
1242
+ default:
1243
+ return char;
1244
+ }
1245
+ });
1512
1246
  }
1513
- function offsetSpan(base, index, length) {
1514
- const startOffset = base.start.offset + index;
1515
- const startCol = base.start.col + index;
1247
+
1248
+ // src/diagnostics.ts
1249
+ function createSpan(line, col, length, lineOffset) {
1250
+ const startOffset = lineOffset + col - 1;
1516
1251
  return {
1517
- start: {
1518
- line: base.start.line,
1519
- col: startCol,
1520
- offset: startOffset
1521
- },
1522
- end: {
1523
- line: base.start.line,
1524
- col: startCol + length,
1525
- offset: startOffset + length
1526
- }
1252
+ start: { line, col, offset: startOffset },
1253
+ end: { line, col: col + length, offset: startOffset + length }
1527
1254
  };
1528
1255
  }
1529
- function enforcePropAliases(root) {
1530
- if (!root.propsDecls || root.propsDecls.length === 0) {
1531
- return [];
1532
- }
1256
+
1257
+ // src/dialect.ts
1258
+ function enforceDialect(root, config) {
1533
1259
  const diagnostics = [];
1534
- const declaredProps = new Map(root.propsDecls.map((d) => [d.name, d]));
1535
- const allUsage = collectTemplateUsage(root);
1536
- for (const name of allUsage.usedBare) {
1537
- if (!declaredProps.has(name) && !shouldIgnoreForDiagnostics(name)) {
1538
- diagnostics.push({
1539
- severity: "warning",
1540
- code: "props.missingDeclaration",
1541
- message: `Identifier "${name}" is used without "props." but is not declared in #props. Declare "${name}" in #props or use "props.${name}".`
1542
- });
1543
- }
1544
- }
1545
- for (const [name, decl] of declaredProps) {
1546
- const usedAsBare = allUsage.usedBareAliases.has(name);
1547
- const usedAsProps = allUsage.usedPropsDot.has(name);
1548
- if (!usedAsBare && !usedAsProps) {
1549
- diagnostics.push({
1550
- severity: "warning",
1551
- code: "props.unusedDeclaration",
1552
- message: `Prop "${name}" is declared in #props but never used in this template.`,
1553
- span: decl.span
1554
- });
1555
- }
1556
- }
1557
- for (const name of allUsage.usedPropsDot) {
1558
- if (declaredProps.has(name)) {
1559
- diagnostics.push({
1560
- severity: "warning",
1561
- code: "props.style.nonPreferred",
1562
- message: `"props.${name}" is unnecessary because "${name}" is declared in #props. Use "{${name}}" instead.`
1563
- });
1564
- }
1565
- }
1566
- for (const [name, decl] of declaredProps) {
1567
- const isCallable = decl.kind === "callable";
1568
- const usedAsCall = allUsage.callSitesBare.has(name);
1569
- const usedAsValue = allUsage.usedBareAliases.has(name) && !usedAsCall;
1570
- if (isCallable && usedAsValue) {
1571
- diagnostics.push({
1572
- severity: "warning",
1573
- code: "props.style.nonPreferred",
1574
- message: `"${name}" is declared as callable in #props (${name}()) but used as a value.`
1575
- });
1576
- } else if (!isCallable && usedAsCall) {
1577
- diagnostics.push({
1578
- severity: "warning",
1579
- code: "props.style.nonPreferred",
1580
- message: `"${name}" is declared as a value in #props but used as a function call.`
1581
- });
1582
- }
1260
+ if (root.idToken) {
1261
+ diagnostics.push(
1262
+ ...evaluateToken(
1263
+ { kind: "id", token: root.idToken, span: root.idTokenSpan },
1264
+ config.tokens.id
1265
+ )
1266
+ );
1583
1267
  }
1268
+ walkNodes(root.children, (occurrence) => {
1269
+ const rule = config.tokens[occurrence.kind];
1270
+ diagnostics.push(...evaluateToken(occurrence, rule));
1271
+ });
1584
1272
  return diagnostics;
1585
1273
  }
1586
- function collectTemplateUsage(root) {
1587
- const usage = {
1588
- usedBare: /* @__PURE__ */ new Set(),
1589
- usedBareAliases: /* @__PURE__ */ new Set(),
1590
- usedPropsDot: /* @__PURE__ */ new Set(),
1591
- callSitesBare: /* @__PURE__ */ new Set(),
1592
- callSitesPropsDot: /* @__PURE__ */ new Set()
1593
- };
1594
- const env = createTemplateEnv(root.propsDecls);
1595
- function mergeResult(result) {
1596
- for (const name of result.usedBare) {
1597
- usage.usedBare.add(name);
1598
- }
1599
- for (const name of result.rewrittenAliases) {
1600
- usage.usedBareAliases.add(name);
1601
- }
1602
- for (const name of result.usedPropsDot) {
1603
- usage.usedPropsDot.add(name);
1274
+ function walkNodes(nodes, onToken) {
1275
+ for (const node of nodes) {
1276
+ if (node.type === "For") {
1277
+ onFor(node, onToken);
1278
+ walkNodes(node.body, onToken);
1279
+ continue;
1604
1280
  }
1605
- for (const name of result.callSitesBare) {
1606
- usage.callSitesBare.add(name);
1281
+ if (node.type === "Conditional") {
1282
+ onConditional(node, onToken);
1283
+ continue;
1607
1284
  }
1608
- for (const name of result.callSitesPropsDot) {
1609
- usage.callSitesPropsDot.add(name);
1285
+ if (node.type === "Element" || node.type === "Component") {
1286
+ walkNodes(node.children, onToken);
1287
+ if (node.type === "Component" && node.slots) {
1288
+ for (const slot of node.slots) {
1289
+ walkNodes(slot.children, onToken);
1290
+ }
1291
+ }
1292
+ continue;
1610
1293
  }
1611
1294
  }
1612
- function analyzeExpression(expr) {
1613
- if (!expr) return;
1614
- const result = rewriteExpression(expr, env);
1615
- mergeResult(result);
1295
+ }
1296
+ function onFor(node, onToken) {
1297
+ if (!node.token) {
1298
+ return;
1616
1299
  }
1617
- function analyzeJsxExpression(expr) {
1618
- if (!expr) return;
1619
- const result = rewriteJsxExpression(expr, env);
1620
- mergeResult(result);
1300
+ onToken({ kind: "for", token: node.token, span: node.tokenSpan });
1301
+ }
1302
+ function onConditional(node, onToken) {
1303
+ for (const branch of node.branches) {
1304
+ onBranch(branch, onToken);
1305
+ walkNodes(branch.body, onToken);
1621
1306
  }
1622
- function walkNode(node) {
1623
- switch (node.type) {
1624
- case "Text":
1625
- for (const part of node.parts) {
1626
- if (part.type === "expr") {
1627
- analyzeExpression(part.value);
1628
- }
1629
- }
1630
- break;
1631
- case "Expression":
1632
- analyzeExpression(node.value);
1633
- break;
1634
- case "JSXPassthrough":
1635
- analyzeJsxExpression(node.expression);
1636
- break;
1637
- case "Element":
1638
- if (node.guard) {
1639
- analyzeExpression(node.guard);
1640
- }
1641
- for (const attr of node.attributes) {
1642
- if (attr.value) {
1643
- analyzeAttributeValue(attr.value);
1644
- }
1645
- }
1646
- for (const child of node.children) {
1647
- walkNode(child);
1648
- }
1649
- break;
1650
- case "Component":
1651
- if (node.guard) {
1652
- analyzeExpression(node.guard);
1653
- }
1654
- for (const attr of node.attributes) {
1655
- if (attr.value) {
1656
- analyzeAttributeValue(attr.value);
1657
- }
1658
- }
1659
- if (node.slots) {
1660
- for (const slot of node.slots) {
1661
- for (const child of slot.children) {
1662
- walkNode(child);
1663
- }
1664
- }
1665
- }
1666
- for (const child of node.children) {
1667
- walkNode(child);
1668
- }
1669
- break;
1670
- case "Conditional":
1671
- for (const branch of node.branches) {
1672
- if (branch.test) {
1673
- analyzeExpression(branch.test);
1674
- }
1675
- for (const child of branch.body) {
1676
- walkNode(child);
1677
- }
1678
- }
1679
- break;
1680
- case "For":
1681
- analyzeExpression(node.arrayExpr);
1682
- for (const child of node.body) {
1683
- walkNode(child);
1684
- }
1685
- break;
1686
- }
1307
+ }
1308
+ function onBranch(branch, onToken) {
1309
+ if (!branch.token || !branch.kind) {
1310
+ return;
1687
1311
  }
1688
- function analyzeAttributeValue(value) {
1689
- const trimmed = value.trim();
1690
- if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
1691
- return;
1692
- }
1693
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1694
- const inner = trimmed.slice(1, -1);
1695
- analyzeExpression(inner);
1696
- } else {
1697
- analyzeExpression(trimmed);
1312
+ onToken({ kind: branch.kind, token: branch.token, span: branch.tokenSpan });
1313
+ }
1314
+ function evaluateToken(occurrence, rule) {
1315
+ const diagnostics = [];
1316
+ const used = occurrence.token;
1317
+ const preferred = rule.preferred;
1318
+ const isAllowed = rule.allow.includes(used);
1319
+ if (!isAllowed) {
1320
+ const severity = levelToSeverity(rule.onDisallowed);
1321
+ if (severity) {
1322
+ diagnostics.push(
1323
+ createDialectDiagnostic(
1324
+ "dialect.token.disallowed",
1325
+ severity,
1326
+ used,
1327
+ preferred,
1328
+ occurrence.span,
1329
+ `Token "${used}" is not allowed for ${occurrence.kind}. Preferred: "${preferred}".`
1330
+ )
1331
+ );
1698
1332
  }
1333
+ return diagnostics;
1699
1334
  }
1700
- for (const child of root.children) {
1701
- walkNode(child);
1335
+ if (used !== preferred) {
1336
+ const severity = levelToSeverity(rule.onDisallowed);
1337
+ if (severity) {
1338
+ diagnostics.push(
1339
+ createDialectDiagnostic(
1340
+ "dialect.token.nonPreferred",
1341
+ severity,
1342
+ used,
1343
+ preferred,
1344
+ occurrence.span,
1345
+ `Token "${used}" is allowed but not preferred for ${occurrence.kind}. Preferred: "${preferred}".`
1346
+ )
1347
+ );
1348
+ }
1702
1349
  }
1703
- return usage;
1350
+ return diagnostics;
1351
+ }
1352
+ function createDialectDiagnostic(code, severity, used, preferred, span, message) {
1353
+ const fix = span ? {
1354
+ range: span,
1355
+ replacementText: preferred
1356
+ } : void 0;
1357
+ return {
1358
+ severity,
1359
+ code,
1360
+ message: message.replace(/\\s+/g, " "),
1361
+ span,
1362
+ fix
1363
+ };
1704
1364
  }
1705
- function shouldIgnoreForDiagnostics(name) {
1706
- return IGNORED_IDENTIFIERS2.has(name) || RESERVED_KEYWORDS2.has(name);
1365
+ function levelToSeverity(level) {
1366
+ if (level === "off") {
1367
+ return null;
1368
+ }
1369
+ if (level === "error") {
1370
+ return "error";
1371
+ }
1372
+ return "warning";
1707
1373
  }
1708
1374
 
1709
1375
  // src/parser.ts
@@ -1883,7 +1549,7 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
1883
1549
  const diagnostics = [];
1884
1550
  const root = { type: "Root", children: [] };
1885
1551
  const stack = [{ node: root, level: -1 }];
1886
- let propsBlockLevel = null;
1552
+ let inputsBlockLevel = null;
1887
1553
  let classesBlockLevel = null;
1888
1554
  let sawTopLevelTemplateNode = false;
1889
1555
  const conditionalChains = /* @__PURE__ */ new Map();
@@ -1925,19 +1591,19 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
1925
1591
  continue;
1926
1592
  }
1927
1593
  let level = indent / 2;
1928
- if (propsBlockLevel !== null && level <= propsBlockLevel) {
1929
- propsBlockLevel = null;
1594
+ if (inputsBlockLevel !== null && level <= inputsBlockLevel) {
1595
+ inputsBlockLevel = null;
1930
1596
  }
1931
1597
  if (classesBlockLevel !== null && level <= classesBlockLevel) {
1932
1598
  classesBlockLevel = null;
1933
1599
  }
1934
- const isInPropsBlock = propsBlockLevel !== null && level > propsBlockLevel;
1600
+ const isInInputsBlock = inputsBlockLevel !== null && level > inputsBlockLevel;
1935
1601
  const isInClassesBlock = classesBlockLevel !== null && level > classesBlockLevel;
1936
1602
  while (stack.length > 1 && stack[stack.length - 1].level >= level) {
1937
1603
  stack.pop();
1938
1604
  }
1939
1605
  const parentLevel = stack[stack.length - 1].level;
1940
- if (level > parentLevel + 1 && !isInPropsBlock && !isInClassesBlock) {
1606
+ if (level > parentLevel + 1 && !isInInputsBlock && !isInClassesBlock) {
1941
1607
  pushDiag(
1942
1608
  diagnostics,
1943
1609
  "COLLIE003",
@@ -1983,48 +1649,35 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
1983
1649
  }
1984
1650
  continue;
1985
1651
  }
1986
- if (trimmed === "props") {
1987
- pushDiag(
1988
- diagnostics,
1989
- "COLLIE103",
1990
- "`props` must be declared using `#props`.",
1991
- lineNumber,
1992
- indent + 1,
1993
- lineOffset,
1994
- trimmed.length
1995
- );
1996
- if (level === 0) {
1997
- propsBlockLevel = level;
1998
- }
1999
- continue;
2000
- }
2001
- if (trimmed === "#props") {
1652
+ if (trimmed === "#inputs") {
2002
1653
  if (level !== 0) {
2003
1654
  pushDiag(
2004
1655
  diagnostics,
2005
1656
  "COLLIE102",
2006
- "#props block must be at the top level.",
1657
+ "#inputs block must be at the top level.",
2007
1658
  lineNumber,
2008
1659
  indent + 1,
2009
1660
  lineOffset,
2010
1661
  trimmed.length
2011
1662
  );
2012
- } else if (root.props) {
1663
+ } else if (root.inputs) {
2013
1664
  pushDiag(
2014
1665
  diagnostics,
2015
1666
  "COLLIE101",
2016
- "Only one #props block is allowed per #id.",
1667
+ "Only one #inputs block is allowed per #id.",
2017
1668
  lineNumber,
2018
1669
  indent + 1,
2019
1670
  lineOffset,
2020
1671
  trimmed.length
2021
1672
  );
2022
1673
  } else {
2023
- root.props = { fields: [] };
2024
- root.propsDecls = [];
1674
+ root.inputs = { fields: [] };
2025
1675
  }
2026
1676
  if (level === 0) {
2027
- propsBlockLevel = level;
1677
+ if (!root.inputsDecls) {
1678
+ root.inputsDecls = [];
1679
+ }
1680
+ inputsBlockLevel = level;
2028
1681
  }
2029
1682
  continue;
2030
1683
  }
@@ -2064,33 +1717,33 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
2064
1717
  }
2065
1718
  continue;
2066
1719
  }
2067
- if (propsBlockLevel !== null && level > propsBlockLevel) {
2068
- if (level !== propsBlockLevel + 1) {
1720
+ if (inputsBlockLevel !== null && level > inputsBlockLevel) {
1721
+ if (level !== inputsBlockLevel + 1) {
2069
1722
  pushDiag(
2070
1723
  diagnostics,
2071
1724
  "COLLIE102",
2072
- "#props lines must be indented two spaces under the #props header.",
1725
+ "#inputs lines must be indented two spaces under the #inputs header.",
2073
1726
  lineNumber,
2074
1727
  indent + 1,
2075
1728
  lineOffset
2076
1729
  );
2077
1730
  continue;
2078
1731
  }
2079
- const decl = parsePropDecl(lineContent, lineNumber, indent + 1, lineOffset, diagnostics);
2080
- if (decl && root.propsDecls) {
2081
- const existing = root.propsDecls.find((d) => d.name === decl.name);
1732
+ const decl = parseInputDecl(lineContent, lineNumber, indent + 1, lineOffset, diagnostics);
1733
+ if (decl && root.inputsDecls) {
1734
+ const existing = root.inputsDecls.find((d) => d.name === decl.name);
2082
1735
  if (existing) {
2083
1736
  pushDiag(
2084
1737
  diagnostics,
2085
1738
  "COLLIE106",
2086
- `Duplicate prop declaration "${decl.name}".`,
1739
+ `Duplicate input declaration "${decl.name}".`,
2087
1740
  lineNumber,
2088
1741
  indent + 1,
2089
1742
  lineOffset,
2090
1743
  trimmed.length
2091
1744
  );
2092
1745
  } else {
2093
- root.propsDecls.push(decl);
1746
+ root.inputsDecls.push(decl);
2094
1747
  }
2095
1748
  }
2096
1749
  continue;
@@ -2503,9 +2156,7 @@ function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
2503
2156
  }
2504
2157
  if (options.dialect) {
2505
2158
  diagnostics.push(...enforceDialect(root, options.dialect));
2506
- diagnostics.push(...enforceProps(root, options.dialect.props));
2507
2159
  }
2508
- diagnostics.push(...enforcePropAliases(root));
2509
2160
  return { root, diagnostics };
2510
2161
  }
2511
2162
  function buildLineOffsets(lines) {
@@ -3665,13 +3316,13 @@ function parseAndAddAttribute(attrStr, attributes, diagnostics, lineNumber, colu
3665
3316
  }
3666
3317
  }
3667
3318
  }
3668
- function parsePropDecl(line, lineNumber, column, lineOffset, diagnostics) {
3319
+ function parseInputDecl(line, lineNumber, column, lineOffset, diagnostics) {
3669
3320
  const trimmed = line.trim();
3670
3321
  if (trimmed.includes(":") || trimmed.includes("<") || trimmed.includes("?")) {
3671
3322
  pushDiag(
3672
3323
  diagnostics,
3673
3324
  "COLLIE104",
3674
- 'Types are not supported in #props yet. Use "name" or "name()".',
3325
+ 'Types are not supported in #inputs yet. Use "name".',
3675
3326
  lineNumber,
3676
3327
  column,
3677
3328
  lineOffset,
@@ -3679,17 +3330,6 @@ function parsePropDecl(line, lineNumber, column, lineOffset, diagnostics) {
3679
3330
  );
3680
3331
  return null;
3681
3332
  }
3682
- const callableMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\(\)$/);
3683
- if (callableMatch) {
3684
- const name = callableMatch[1];
3685
- const nameStart = line.indexOf(name);
3686
- const nameColumn = column + nameStart;
3687
- return {
3688
- name,
3689
- kind: "callable",
3690
- span: createSpan(lineNumber, nameColumn, name.length, lineOffset)
3691
- };
3692
- }
3693
3333
  const valueMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)$/);
3694
3334
  if (valueMatch) {
3695
3335
  const name = valueMatch[1];
@@ -3704,7 +3344,7 @@ function parsePropDecl(line, lineNumber, column, lineOffset, diagnostics) {
3704
3344
  pushDiag(
3705
3345
  diagnostics,
3706
3346
  "COLLIE105",
3707
- 'Invalid #props declaration. Use "name" or "name()".',
3347
+ 'Invalid #inputs declaration. Use "name".',
3708
3348
  lineNumber,
3709
3349
  column,
3710
3350
  lineOffset,
@@ -3844,8 +3484,8 @@ function serializeRoot(root, indentSize) {
3844
3484
  if (root.classAliases && root.classAliases.aliases.length > 0) {
3845
3485
  sections.push(formatClassAliases(root.classAliases, indentSize));
3846
3486
  }
3847
- if (root.props && root.props.fields.length > 0) {
3848
- sections.push(formatProps(root.props, indentSize));
3487
+ if (root.inputs && root.inputs.fields.length > 0) {
3488
+ sections.push(formatInputs(root.inputs, indentSize));
3849
3489
  }
3850
3490
  if (root.children.length > 0) {
3851
3491
  sections.push(formatNodes(root.children, 0, indentSize));
@@ -3874,10 +3514,10 @@ function formatClassAliases(decl, indentSize) {
3874
3514
  }
3875
3515
  return lines;
3876
3516
  }
3877
- function formatProps(props, indentSize) {
3517
+ function formatInputs(inputs, indentSize) {
3878
3518
  const indent = indentString(1, indentSize);
3879
- const lines = ["props"];
3880
- for (const field of props.fields) {
3519
+ const lines = ["#inputs"];
3520
+ for (const field of inputs.fields) {
3881
3521
  const optionalFlag = field.optional ? "?" : "";
3882
3522
  lines.push(cleanLine(`${indent}${field.name}${optionalFlag}: ${field.typeText.trim()}`));
3883
3523
  }
@@ -4067,19 +3707,19 @@ function convertTsxToCollie(source, options = {}) {
4067
3707
  );
4068
3708
  const warnings = [];
4069
3709
  const ctx = { sourceFile, warnings };
4070
- const propDeclarations = collectPropDeclarations(sourceFile);
4071
- const component = findComponentInfo(sourceFile, propDeclarations, ctx);
3710
+ const inputDeclarations = collectInputDeclarations(sourceFile);
3711
+ const component = findComponentInfo(sourceFile, inputDeclarations, ctx);
4072
3712
  if (!component) {
4073
3713
  throw new Error("Could not find a component that returns JSX in this file.");
4074
3714
  }
4075
- const propsLines = buildPropsBlock(component, propDeclarations, ctx);
3715
+ const inputsLines = buildInputsBlock(component, inputDeclarations, ctx);
4076
3716
  const templateLines = convertJsxNode(component.jsxRoot, ctx, 0);
4077
3717
  if (!templateLines.length) {
4078
3718
  throw new Error("Unable to convert JSX tree to Collie template.");
4079
3719
  }
4080
3720
  const sections = [];
4081
- if (propsLines.length) {
4082
- sections.push(propsLines.join("\n"));
3721
+ if (inputsLines.length) {
3722
+ sections.push(inputsLines.join("\n"));
4083
3723
  }
4084
3724
  sections.push(templateLines.join("\n"));
4085
3725
  const collie = `${sections.join("\n\n").trimEnd()}
@@ -4094,18 +3734,18 @@ function inferScriptKind(filename) {
4094
3734
  if (ext === ".ts") return ts.ScriptKind.TS;
4095
3735
  return ts.ScriptKind.JS;
4096
3736
  }
4097
- function collectPropDeclarations(sourceFile) {
3737
+ function collectInputDeclarations(sourceFile) {
4098
3738
  const map = /* @__PURE__ */ new Map();
4099
3739
  for (const statement of sourceFile.statements) {
4100
3740
  if (ts.isInterfaceDeclaration(statement) && statement.name) {
4101
- map.set(statement.name.text, extractPropsFromMembers(statement.members, sourceFile));
3741
+ map.set(statement.name.text, extractInputsFromMembers(statement.members, sourceFile));
4102
3742
  } else if (ts.isTypeAliasDeclaration(statement) && ts.isTypeLiteralNode(statement.type)) {
4103
- map.set(statement.name.text, extractPropsFromMembers(statement.type.members, sourceFile));
3743
+ map.set(statement.name.text, extractInputsFromMembers(statement.type.members, sourceFile));
4104
3744
  }
4105
3745
  }
4106
3746
  return map;
4107
3747
  }
4108
- function extractPropsFromMembers(members, sourceFile) {
3748
+ function extractInputsFromMembers(members, sourceFile) {
4109
3749
  const fields = [];
4110
3750
  for (const member of members) {
4111
3751
  if (!ts.isPropertySignature(member) || member.name === void 0) {
@@ -4130,11 +3770,11 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4130
3770
  const jsx = findJsxReturn(statement.body);
4131
3771
  if (jsx) {
4132
3772
  const defaults = extractDefaultsFromParameters(statement.parameters, ctx);
4133
- const propsInfo = resolvePropsFromParameters(statement.parameters, declarations, ctx);
3773
+ const inputsInfo = resolveInputsFromParameters(statement.parameters, declarations, ctx);
4134
3774
  return {
4135
3775
  jsxRoot: jsx,
4136
- propsTypeName: propsInfo.typeName,
4137
- inlineProps: propsInfo.inline,
3776
+ inputsTypeName: inputsInfo.typeName,
3777
+ inlineInputs: inputsInfo.inline,
4138
3778
  defaults
4139
3779
  };
4140
3780
  }
@@ -4148,20 +3788,20 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4148
3788
  continue;
4149
3789
  }
4150
3790
  const defaults = extractDefaultsFromParameters(init.parameters, ctx);
4151
- const propsInfo = resolvePropsFromParameters(init.parameters, declarations, ctx);
4152
- if (!propsInfo.typeName && !propsInfo.inline && decl.type) {
4153
- const inferred = resolvePropsFromTypeAnnotation(decl.type, sourceFile, declarations);
4154
- if (inferred.typeName && !propsInfo.typeName) {
4155
- propsInfo.typeName = inferred.typeName;
3791
+ const inputsInfo = resolveInputsFromParameters(init.parameters, declarations, ctx);
3792
+ if (!inputsInfo.typeName && !inputsInfo.inline && decl.type) {
3793
+ const inferred = resolveInputsFromTypeAnnotation(decl.type, sourceFile, declarations);
3794
+ if (inferred.typeName && !inputsInfo.typeName) {
3795
+ inputsInfo.typeName = inferred.typeName;
4156
3796
  }
4157
- if (inferred.inline && !propsInfo.inline) {
4158
- propsInfo.inline = inferred.inline;
3797
+ if (inferred.inline && !inputsInfo.inline) {
3798
+ inputsInfo.inline = inferred.inline;
4159
3799
  }
4160
3800
  }
4161
3801
  return {
4162
3802
  jsxRoot: jsx,
4163
- propsTypeName: propsInfo.typeName,
4164
- inlineProps: propsInfo.inline,
3803
+ inputsTypeName: inputsInfo.typeName,
3804
+ inlineInputs: inputsInfo.inline,
4165
3805
  defaults
4166
3806
  };
4167
3807
  }
@@ -4170,13 +3810,13 @@ function findComponentInfo(sourceFile, declarations, ctx) {
4170
3810
  }
4171
3811
  return null;
4172
3812
  }
4173
- function resolvePropsFromParameters(parameters, declarations, ctx) {
3813
+ function resolveInputsFromParameters(parameters, declarations, ctx) {
4174
3814
  if (!parameters.length) {
4175
3815
  return {};
4176
3816
  }
4177
3817
  const param = parameters[0];
4178
3818
  if (param.type) {
4179
- const inferred = resolvePropsFromTypeAnnotation(param.type, ctx.sourceFile, declarations);
3819
+ const inferred = resolveInputsFromTypeAnnotation(param.type, ctx.sourceFile, declarations);
4180
3820
  if (inferred.inline) {
4181
3821
  return inferred;
4182
3822
  }
@@ -4186,7 +3826,7 @@ function resolvePropsFromParameters(parameters, declarations, ctx) {
4186
3826
  }
4187
3827
  return {};
4188
3828
  }
4189
- function resolvePropsFromTypeAnnotation(typeNode, sourceFile, declarations) {
3829
+ function resolveInputsFromTypeAnnotation(typeNode, sourceFile, declarations) {
4190
3830
  if (ts.isTypeReferenceNode(typeNode)) {
4191
3831
  const referenced = getTypeReferenceName(typeNode.typeName);
4192
3832
  if (referenced && declarations.has(referenced)) {
@@ -4200,12 +3840,12 @@ function resolvePropsFromTypeAnnotation(typeNode, sourceFile, declarations) {
4200
3840
  return { typeName: nested };
4201
3841
  }
4202
3842
  } else if (ts.isTypeLiteralNode(typeArg)) {
4203
- return { inline: extractPropsFromMembers(typeArg.members, sourceFile) };
3843
+ return { inline: extractInputsFromMembers(typeArg.members, sourceFile) };
4204
3844
  }
4205
3845
  }
4206
3846
  }
4207
3847
  if (ts.isTypeLiteralNode(typeNode)) {
4208
- return { inline: extractPropsFromMembers(typeNode.members, sourceFile) };
3848
+ return { inline: extractInputsFromMembers(typeNode.members, sourceFile) };
4209
3849
  }
4210
3850
  return {};
4211
3851
  }
@@ -4261,16 +3901,16 @@ function extractDefaultsFromParameters(parameters, ctx) {
4261
3901
  if (!element.initializer) {
4262
3902
  continue;
4263
3903
  }
4264
- const propName = getBindingElementPropName(element, ctx.sourceFile);
4265
- if (!propName) {
3904
+ const inputName = getBindingElementInputName(element, ctx.sourceFile);
3905
+ if (!inputName) {
4266
3906
  ctx.warnings.push("Skipping complex destructured default value.");
4267
3907
  continue;
4268
3908
  }
4269
- defaults.set(propName, element.initializer.getText(ctx.sourceFile).trim());
3909
+ defaults.set(inputName, element.initializer.getText(ctx.sourceFile).trim());
4270
3910
  }
4271
3911
  return defaults;
4272
3912
  }
4273
- function getBindingElementPropName(element, sourceFile) {
3913
+ function getBindingElementInputName(element, sourceFile) {
4274
3914
  const prop = element.propertyName;
4275
3915
  if (prop) {
4276
3916
  if (ts.isIdentifier(prop) || ts.isStringLiteral(prop) || ts.isNumericLiteral(prop)) {
@@ -4292,24 +3932,25 @@ function getPropertyName(name, sourceFile) {
4292
3932
  }
4293
3933
  return name.getText(sourceFile);
4294
3934
  }
4295
- function buildPropsBlock(info, propDeclarations, ctx) {
4296
- const fields = info.inlineProps ?? (info.propsTypeName ? propDeclarations.get(info.propsTypeName) ?? [] : void 0) ?? [];
3935
+ function buildInputsBlock(info, inputDeclarations, ctx) {
3936
+ const fields = info.inlineInputs ?? (info.inputsTypeName ? inputDeclarations.get(info.inputsTypeName) ?? [] : void 0) ?? [];
4297
3937
  if (!fields.length && !info.defaults.size) {
4298
3938
  return [];
4299
3939
  }
4300
- const lines = ["props"];
3940
+ const lines = ["#inputs"];
4301
3941
  if (fields.length) {
4302
3942
  for (const field of fields) {
4303
3943
  const def = info.defaults.get(field.name);
4304
- let line = ` ${field.name}${field.optional ? "?" : ""}: ${field.typeText}`;
3944
+ let line = ` ${field.name}`;
4305
3945
  if (def) {
4306
- line += ` = ${def}`;
3946
+ ctx.warnings.push(`Default value for "${field.name}" cannot be preserved in Collie #inputs.`);
4307
3947
  }
4308
3948
  lines.push(line);
4309
3949
  }
4310
3950
  } else {
4311
3951
  for (const [name, defValue] of info.defaults.entries()) {
4312
- lines.push(` ${name}: any = ${defValue}`);
3952
+ ctx.warnings.push(`Default value for "${name}" cannot be preserved in Collie #inputs.`);
3953
+ lines.push(` ${name}`);
4313
3954
  }
4314
3955
  }
4315
3956
  return lines;
@@ -4590,28 +4231,28 @@ function hasErrors(diagnostics) {
4590
4231
  function createStubComponent(name, flavor) {
4591
4232
  if (flavor === "tsx") {
4592
4233
  return [
4593
- "export type Props = Record<string, never>;",
4594
- `export default function ${name}(props: Props) {`,
4234
+ "export type Inputs = Record<string, never>;",
4235
+ `export default function ${name}(__inputs: Inputs) {`,
4595
4236
  " return null;",
4596
4237
  "}"
4597
4238
  ].join("\n");
4598
4239
  }
4599
- return [`export default function ${name}(props) {`, " return null;", "}"].join("\n");
4240
+ return [`export default function ${name}(__inputs) {`, " return null;", "}"].join("\n");
4600
4241
  }
4601
4242
  function createStubRender(flavor) {
4602
4243
  if (flavor === "tsx") {
4603
4244
  return [
4604
- "export type Props = Record<string, never>;",
4605
- "export function render(props: any) {",
4245
+ "export type Inputs = Record<string, never>;",
4246
+ "export function render(__inputs: any) {",
4606
4247
  " return null;",
4607
4248
  "}"
4608
4249
  ].join("\n");
4609
4250
  }
4610
- return ["export function render(props) {", " return null;", "}"].join("\n");
4251
+ return ["export function render(__inputs) {", " return null;", "}"].join("\n");
4611
4252
  }
4612
4253
  function wrapRenderModuleAsComponent(renderModule, name, flavor) {
4613
- const signature = flavor === "tsx" ? `export default function ${name}(props: Props) {` : `export default function ${name}(props) {`;
4614
- const wrapper = [signature, " return render(props);", "}"].join("\n");
4254
+ const signature = flavor === "tsx" ? `export default function ${name}(__inputs: Inputs) {` : `export default function ${name}(__inputs) {`;
4255
+ const wrapper = [signature, " return render(__inputs);", "}"].join("\n");
4615
4256
  return `${renderModule}
4616
4257
 
4617
4258
  ${wrapper}`;