@homebound/truss 2.0.0-next.7 → 2.0.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.
@@ -5,8 +5,9 @@ import { resolve, dirname, isAbsolute } from "path";
5
5
  // src/plugin/transform.ts
6
6
  import { parse } from "@babel/parser";
7
7
  import _traverse2 from "@babel/traverse";
8
- import _generate from "@babel/generator";
8
+ import _generate2 from "@babel/generator";
9
9
  import * as t4 from "@babel/types";
10
+ import { basename } from "path";
10
11
 
11
12
  // src/plugin/resolve-chain.ts
12
13
  function resolveFullChain(chain, mapping) {
@@ -397,15 +398,32 @@ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
397
398
  pseudoElement,
398
399
  dynamicProps: targetEntry.props,
399
400
  incremented: false,
401
+ appendPx: true,
400
402
  dynamicExtraDefs: targetEntry.extraDefs,
401
403
  argNode: argAst
402
404
  };
403
405
  }
404
406
  }
405
407
  function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
408
+ if (node.args.length === 1) {
409
+ const styleArg = node.args[0];
410
+ if (styleArg.type === "SpreadElement") {
411
+ throw new UnsupportedPatternError(`add() does not support spread arguments`);
412
+ }
413
+ if (styleArg.type === "ObjectExpression") {
414
+ throw new UnsupportedPatternError(
415
+ `add(cssProp) does not accept object literals -- pass an existing CssProp expression instead`
416
+ );
417
+ }
418
+ return {
419
+ key: "__composed_css_prop",
420
+ defs: {},
421
+ styleArrayArg: styleArg
422
+ };
423
+ }
406
424
  if (node.args.length !== 2) {
407
425
  throw new UnsupportedPatternError(
408
- `add() requires exactly 2 arguments (property name and value), got ${node.args.length}. The add({...}) object overload is not supported -- use add("propName", value) instead`
426
+ `add() requires exactly 2 arguments (property name and value), got ${node.args.length}. Supported overloads are add(cssProp) and add("propName", value)`
409
427
  );
410
428
  }
411
429
  const propArg = node.args[0];
@@ -512,7 +530,7 @@ function mergeOverlappingConditions(segments) {
512
530
  const propToIndices = /* @__PURE__ */ new Map();
513
531
  for (let i = 0; i < segments.length; i++) {
514
532
  const seg = segments[i];
515
- if (seg.dynamicProps || seg.whenPseudo || seg.error) continue;
533
+ if (seg.dynamicProps || seg.styleArrayArg || seg.whenPseudo || seg.error) continue;
516
534
  for (const prop of Object.keys(seg.defs)) {
517
535
  if (!propToIndices.has(prop)) propToIndices.set(prop, []);
518
536
  propToIndices.get(prop).push(i);
@@ -882,6 +900,39 @@ function insertStylexNamespaceImport(ast, localName) {
882
900
  const idx = findLastImportIndex(ast);
883
901
  ast.program.body.splice(idx + 1, 0, stylexImport);
884
902
  }
903
+ function findNamedImportBinding(ast, source, importedName) {
904
+ for (const node of ast.program.body) {
905
+ if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
906
+ for (const spec of node.specifiers) {
907
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: importedName })) {
908
+ return spec.local.name;
909
+ }
910
+ }
911
+ }
912
+ return null;
913
+ }
914
+ function upsertNamedImports(ast, source, imports) {
915
+ if (imports.length === 0) return;
916
+ for (const node of ast.program.body) {
917
+ if (!t.isImportDeclaration(node) || node.source.value !== source) continue;
918
+ for (const entry of imports) {
919
+ const exists = node.specifiers.some(function(spec) {
920
+ return t.isImportSpecifier(spec) && t.isIdentifier(spec.imported, { name: entry.importedName });
921
+ });
922
+ if (exists) continue;
923
+ node.specifiers.push(t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName)));
924
+ }
925
+ return;
926
+ }
927
+ const importDecl = t.importDeclaration(
928
+ imports.map(function(entry) {
929
+ return t.importSpecifier(t.identifier(entry.localName), t.identifier(entry.importedName));
930
+ }),
931
+ t.stringLiteral(source)
932
+ );
933
+ const idx = findLastImportIndex(ast);
934
+ ast.program.body.splice(idx + 1, 0, importDecl);
935
+ }
885
936
  function extractChain(node, cssBinding) {
886
937
  const chain = [];
887
938
  let current = node;
@@ -937,6 +988,9 @@ function collectCreateData(chains) {
937
988
  collectTypographyLookup(createEntries, runtimeLookups, seg);
938
989
  continue;
939
990
  }
991
+ if (seg.styleArrayArg) {
992
+ continue;
993
+ }
940
994
  if (seg.dynamicProps) {
941
995
  if (!createEntries.has(seg.key)) {
942
996
  createEntries.set(seg.key, {
@@ -1180,65 +1234,47 @@ function isValidIdentifier(s) {
1180
1234
 
1181
1235
  // src/plugin/rewrite-sites.ts
1182
1236
  import _traverse from "@babel/traverse";
1237
+ import _generate from "@babel/generator";
1183
1238
  import * as t3 from "@babel/types";
1239
+ var generate = _generate.default ?? _generate;
1184
1240
  var traverse = _traverse.default ?? _traverse;
1185
1241
  function rewriteExpressionSites(options) {
1186
1242
  for (const site of options.sites) {
1187
1243
  const propsArgs = buildPropsArgsFromChain(site.resolvedChain, options);
1188
1244
  const cssAttrPath = getCssAttributePath(site.path);
1189
1245
  if (cssAttrPath) {
1190
- const propsCall = t3.callExpression(
1191
- t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("props")),
1192
- propsArgs
1246
+ cssAttrPath.replaceWith(
1247
+ t3.jsxSpreadAttribute(
1248
+ buildCssSpreadExpression(
1249
+ cssAttrPath,
1250
+ propsArgs,
1251
+ site.path.node.loc?.start.line ?? null,
1252
+ options.mergePropsHelperName,
1253
+ options.needsMergePropsHelper,
1254
+ options
1255
+ )
1256
+ )
1193
1257
  );
1194
- const openingElement = cssAttrPath.parentPath;
1195
- let existingClassNameExpr = null;
1196
- if (openingElement && openingElement.isJSXOpeningElement()) {
1197
- const attrs = openingElement.node.attributes;
1198
- for (let i = 0; i < attrs.length; i++) {
1199
- const attr = attrs[i];
1200
- if (t3.isJSXAttribute(attr) && t3.isJSXIdentifier(attr.name, { name: "className" })) {
1201
- if (t3.isStringLiteral(attr.value)) {
1202
- existingClassNameExpr = attr.value;
1203
- } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1204
- existingClassNameExpr = attr.value.expression;
1205
- }
1206
- attrs.splice(i, 1);
1207
- break;
1208
- }
1209
- }
1210
- }
1211
- let spreadExpr;
1212
- if (existingClassNameExpr) {
1213
- const rId = t3.identifier("__r");
1214
- const mergedClassName = t3.callExpression(
1215
- t3.memberExpression(
1216
- t3.binaryExpression(
1217
- "+",
1218
- t3.binaryExpression("+", existingClassNameExpr, t3.stringLiteral(" ")),
1219
- t3.logicalExpression("||", t3.memberExpression(rId, t3.identifier("className")), t3.stringLiteral(""))
1220
- ),
1221
- t3.identifier("trim")
1222
- ),
1223
- []
1224
- );
1225
- spreadExpr = t3.callExpression(
1226
- t3.arrowFunctionExpression(
1227
- [rId],
1228
- t3.objectExpression([t3.spreadElement(rId), t3.objectProperty(t3.identifier("className"), mergedClassName)])
1229
- ),
1230
- [propsCall]
1231
- );
1232
- } else {
1233
- spreadExpr = propsCall;
1234
- }
1235
- cssAttrPath.replaceWith(t3.jsxSpreadAttribute(spreadExpr));
1236
1258
  continue;
1237
1259
  }
1238
- site.path.replaceWith(t3.arrayExpression(propsArgs));
1260
+ site.path.replaceWith(buildStyleArrayExpression(propsArgs, site.path.node.loc?.start.line ?? null, options));
1239
1261
  }
1240
1262
  rewriteStyleObjectExpressions(options.ast);
1241
- rewriteCssAttributeExpressions(options.ast, options.stylexNamespaceName);
1263
+ rewriteCssAttributeExpressions(
1264
+ options.ast,
1265
+ options.filename,
1266
+ options.debug,
1267
+ options.stylexNamespaceName,
1268
+ options.mergePropsHelperName,
1269
+ options.needsMergePropsHelper,
1270
+ options.trussPropsHelperName,
1271
+ options.needsTrussPropsHelper,
1272
+ options.trussDebugInfoName,
1273
+ options.needsTrussDebugInfo,
1274
+ options.asStyleArrayHelperName,
1275
+ options.needsAsStyleArrayHelper,
1276
+ options.skippedCssPropMessages
1277
+ );
1242
1278
  }
1243
1279
  function getCssAttributePath(path) {
1244
1280
  const parentPath = path.parentPath;
@@ -1298,6 +1334,18 @@ function buildPropsArgs(segments, options) {
1298
1334
  args.push(t3.spreadElement(t3.logicalExpression("??", lookupAccess, t3.arrayExpression([]))));
1299
1335
  continue;
1300
1336
  }
1337
+ if (seg.styleArrayArg) {
1338
+ args.push(
1339
+ t3.spreadElement(
1340
+ buildUnknownObjectSpreadFallback(
1341
+ seg.styleArrayArg,
1342
+ options.asStyleArrayHelperName,
1343
+ options.needsAsStyleArrayHelper
1344
+ )
1345
+ )
1346
+ );
1347
+ continue;
1348
+ }
1301
1349
  const ref = t3.memberExpression(t3.identifier(options.createVarName), t3.identifier(seg.key));
1302
1350
  if (seg.dynamicProps && seg.argNode) {
1303
1351
  let argExpr;
@@ -1305,6 +1353,12 @@ function buildPropsArgs(segments, options) {
1305
1353
  argExpr = t3.callExpression(t3.identifier(options.maybeIncHelperName), [seg.argNode]);
1306
1354
  } else if (seg.incremented) {
1307
1355
  argExpr = seg.argNode;
1356
+ } else if (seg.appendPx) {
1357
+ argExpr = t3.binaryExpression(
1358
+ "+",
1359
+ t3.callExpression(t3.identifier("String"), [seg.argNode]),
1360
+ t3.stringLiteral("px")
1361
+ );
1308
1362
  } else {
1309
1363
  argExpr = t3.callExpression(t3.identifier("String"), [seg.argNode]);
1310
1364
  }
@@ -1315,25 +1369,165 @@ function buildPropsArgs(segments, options) {
1315
1369
  }
1316
1370
  return args;
1317
1371
  }
1318
- function rewriteCssAttributeExpressions(ast, stylexNamespaceName) {
1372
+ function rewriteCssAttributeExpressions(ast, filename, debug, stylexNamespaceName, mergePropsHelperName, needsMergePropsHelper, trussPropsHelperName, needsTrussPropsHelper, trussDebugInfoName, needsTrussDebugInfo, asStyleArrayHelperName, needsAsStyleArrayHelper, skippedCssPropMessages) {
1319
1373
  traverse(ast, {
1320
1374
  JSXAttribute(path) {
1321
1375
  if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1322
1376
  const value = path.node.value;
1323
1377
  if (!t3.isJSXExpressionContainer(value)) return;
1324
1378
  if (!t3.isExpression(value.expression)) return;
1325
- const expr = normalizeStyleArrayLikeExpression(value.expression, path, /* @__PURE__ */ new Set());
1326
- if (!expr) return;
1327
- const propsArgs = buildStyleArrayLikePropsArgs(expr, path, /* @__PURE__ */ new Set());
1328
- if (!propsArgs) return;
1329
- const propsCall = t3.callExpression(
1330
- t3.memberExpression(t3.identifier(stylexNamespaceName), t3.identifier("props")),
1331
- propsArgs
1379
+ if (!isCssRewriteableExpression(value.expression, path)) {
1380
+ skippedCssPropMessages.push({
1381
+ message: explainSkippedCssRewrite(value.expression, path),
1382
+ line: path.node.loc?.start.line ?? null
1383
+ });
1384
+ return;
1385
+ }
1386
+ const propsArgs = lowerCssExpressionToPropsArgs(
1387
+ value.expression,
1388
+ path,
1389
+ asStyleArrayHelperName,
1390
+ needsAsStyleArrayHelper
1391
+ );
1392
+ if (!propsArgs) {
1393
+ skippedCssPropMessages.push({
1394
+ message: explainSkippedCssRewrite(value.expression, path),
1395
+ line: path.node.loc?.start.line ?? null
1396
+ });
1397
+ return;
1398
+ }
1399
+ path.replaceWith(
1400
+ t3.jsxSpreadAttribute(
1401
+ buildCssSpreadExpression(
1402
+ path,
1403
+ propsArgs,
1404
+ path.node.loc?.start.line ?? null,
1405
+ mergePropsHelperName,
1406
+ needsMergePropsHelper,
1407
+ {
1408
+ filename,
1409
+ debug,
1410
+ stylexNamespaceName,
1411
+ trussPropsHelperName,
1412
+ needsTrussPropsHelper,
1413
+ trussDebugInfoName,
1414
+ needsTrussDebugInfo
1415
+ }
1416
+ )
1417
+ )
1332
1418
  );
1333
- path.replaceWith(t3.jsxSpreadAttribute(propsCall));
1334
1419
  }
1335
1420
  });
1336
1421
  }
1422
+ function buildStyleArrayExpression(propsArgs, line, options) {
1423
+ const elements = buildDebugElements(line, options);
1424
+ elements.push(...propsArgs);
1425
+ return t3.arrayExpression(elements);
1426
+ }
1427
+ function buildPropsCall(propsArgs, line, options) {
1428
+ if (!options.debug) {
1429
+ return t3.callExpression(
1430
+ t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("props")),
1431
+ propsArgs
1432
+ );
1433
+ }
1434
+ options.needsTrussPropsHelper.current = true;
1435
+ const args = buildDebugElements(line, options);
1436
+ args.push(...propsArgs);
1437
+ return t3.callExpression(t3.identifier(options.trussPropsHelperName), [
1438
+ t3.identifier(options.stylexNamespaceName),
1439
+ ...args
1440
+ ]);
1441
+ }
1442
+ function buildDebugElements(line, options) {
1443
+ if (!options.debug || line === null) {
1444
+ return [];
1445
+ }
1446
+ options.needsTrussDebugInfo.current = true;
1447
+ return [t3.newExpression(t3.identifier(options.trussDebugInfoName), [t3.stringLiteral(`${options.filename}:${line}`)])];
1448
+ }
1449
+ function isCssRewriteableExpression(expr, path) {
1450
+ return !!lowerCssExpressionToPropsArgs(expr, path, "asStyleArray", { current: false });
1451
+ }
1452
+ function lowerCssExpressionToPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1453
+ return buildStyleObjectPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) ?? buildStyleArrayLikePropsArgsFromExpression(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) ?? buildUnknownCssValuePropsArgs(expr, asStyleArrayHelperName, needsAsStyleArrayHelper);
1454
+ }
1455
+ function explainSkippedCssRewrite(expr, path) {
1456
+ if (t3.isObjectExpression(expr)) {
1457
+ for (const prop of expr.properties) {
1458
+ if (!t3.isSpreadElement(prop)) {
1459
+ return `[truss] Unsupported pattern: Could not rewrite css prop: object contains a non-spread property (${formatNodeSnippet(expr)})`;
1460
+ }
1461
+ const normalizedArg = normalizeStyleArrayLikeExpression(prop.argument, path, /* @__PURE__ */ new Set());
1462
+ if (!normalizedArg) {
1463
+ return `[truss] Unsupported pattern: Could not rewrite css prop: spread argument is not style-array-like (${formatNodeSnippet(prop.argument)})`;
1464
+ }
1465
+ }
1466
+ return `[truss] Unsupported pattern: Could not rewrite css prop: object spread composition was not recognized (${formatNodeSnippet(expr)})`;
1467
+ }
1468
+ return `[truss] Unsupported pattern: Could not rewrite css prop: expression is not style-array-like (${formatNodeSnippet(expr)})`;
1469
+ }
1470
+ function formatNodeSnippet(node) {
1471
+ return generate(node, { compact: true, comments: true }).code;
1472
+ }
1473
+ function buildCssSpreadExpression(path, propsArgs, line, mergePropsHelperName, needsMergePropsHelper, options) {
1474
+ const existingClassNameExpr = removeExistingClassNameAttribute(path);
1475
+ if (!existingClassNameExpr) return buildPropsCall(propsArgs, line, options);
1476
+ needsMergePropsHelper.current = true;
1477
+ const args = buildDebugElements(line, options);
1478
+ args.push(...propsArgs);
1479
+ return t3.callExpression(t3.identifier(mergePropsHelperName), [
1480
+ t3.identifier(options.stylexNamespaceName),
1481
+ existingClassNameExpr,
1482
+ ...args
1483
+ ]);
1484
+ }
1485
+ function removeExistingClassNameAttribute(path) {
1486
+ const openingElement = path.parentPath;
1487
+ if (!openingElement || !openingElement.isJSXOpeningElement()) return null;
1488
+ const attrs = openingElement.node.attributes;
1489
+ for (let i = 0; i < attrs.length; i++) {
1490
+ const attr = attrs[i];
1491
+ if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: "className" })) continue;
1492
+ let classNameExpr = null;
1493
+ if (t3.isStringLiteral(attr.value)) {
1494
+ classNameExpr = attr.value;
1495
+ } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1496
+ classNameExpr = attr.value.expression;
1497
+ }
1498
+ attrs.splice(i, 1);
1499
+ return classNameExpr;
1500
+ }
1501
+ return null;
1502
+ }
1503
+ function buildStyleObjectPropsArgs(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1504
+ if (!t3.isObjectExpression(expr) || expr.properties.length === 0) return null;
1505
+ const propsArgs = [];
1506
+ for (const prop of expr.properties) {
1507
+ if (!t3.isSpreadElement(prop)) return null;
1508
+ const normalizedArg = normalizeStyleArrayLikeExpression(prop.argument, path, /* @__PURE__ */ new Set());
1509
+ if (!normalizedArg) {
1510
+ propsArgs.push(
1511
+ t3.spreadElement(
1512
+ buildUnknownObjectSpreadFallback(prop.argument, asStyleArrayHelperName, needsAsStyleArrayHelper)
1513
+ )
1514
+ );
1515
+ continue;
1516
+ }
1517
+ const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path, /* @__PURE__ */ new Set());
1518
+ if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1519
+ propsArgs.push(...nestedArgs);
1520
+ } else {
1521
+ propsArgs.push(t3.spreadElement(buildSafeSpreadArgument(normalizedArg)));
1522
+ }
1523
+ }
1524
+ return propsArgs.length > 0 ? propsArgs : null;
1525
+ }
1526
+ function buildStyleArrayLikePropsArgsFromExpression(expr, path, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1527
+ const normalizedExpr = normalizeStyleArrayLikeExpression(expr, path, /* @__PURE__ */ new Set());
1528
+ if (!normalizedExpr) return null;
1529
+ return buildStyleArrayLikePropsArgs(normalizedExpr, path, /* @__PURE__ */ new Set());
1530
+ }
1337
1531
  function buildStyleArrayLikePropsArgs(expr, path, seen) {
1338
1532
  if (seen.has(expr)) return null;
1339
1533
  seen.add(expr);
@@ -1351,7 +1545,7 @@ function buildStyleArrayLikePropsArgs(expr, path, seen) {
1351
1545
  if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1352
1546
  propsArgs.push(...nestedArgs);
1353
1547
  } else {
1354
- propsArgs.push(t3.spreadElement(normalizedArg));
1548
+ propsArgs.push(t3.spreadElement(buildSafeSpreadArgument(normalizedArg)));
1355
1549
  }
1356
1550
  continue;
1357
1551
  }
@@ -1359,11 +1553,15 @@ function buildStyleArrayLikePropsArgs(expr, path, seen) {
1359
1553
  }
1360
1554
  return propsArgs;
1361
1555
  }
1362
- if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isConditionalExpression(expr)) {
1363
- return [t3.spreadElement(expr)];
1556
+ if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isConditionalExpression(expr) || t3.isLogicalExpression(expr) || t3.isCallExpression(expr)) {
1557
+ return [t3.spreadElement(buildSafeSpreadArgument(expr))];
1364
1558
  }
1365
1559
  return null;
1366
1560
  }
1561
+ function buildUnknownCssValuePropsArgs(expr, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1562
+ if (!(t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isCallExpression(expr))) return null;
1563
+ return [t3.spreadElement(buildUnknownObjectSpreadFallback(expr, asStyleArrayHelperName, needsAsStyleArrayHelper))];
1564
+ }
1367
1565
  function rewriteStyleObjectExpressions(ast) {
1368
1566
  traverse(ast, {
1369
1567
  ObjectExpression(path) {
@@ -1388,16 +1586,17 @@ function tryBuildStyleArrayFromObject(path) {
1388
1586
  // I.e. `...Css.df.$`, `...(cond ? Css.df.$ : {})`, or `...styles.wrapper`
1389
1587
  );
1390
1588
  if (!normalizedArg) {
1391
- return null;
1589
+ elements.push(t3.spreadElement(buildInlineAsStyleArrayExpression(prop.argument)));
1590
+ continue;
1392
1591
  }
1393
- if (isStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1592
+ if (isKnownStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1394
1593
  sawStyleArray = true;
1395
1594
  }
1396
1595
  if (t3.isArrayExpression(normalizedArg)) {
1397
1596
  elements.push(...normalizedArg.elements);
1398
1597
  continue;
1399
1598
  }
1400
- elements.push(t3.spreadElement(normalizedArg));
1599
+ elements.push(t3.spreadElement(buildSafeSpreadArgument(normalizedArg)));
1401
1600
  }
1402
1601
  if (!sawStyleArray) return null;
1403
1602
  return t3.arrayExpression(elements);
@@ -1406,13 +1605,24 @@ function normalizeStyleArrayLikeExpression(expr, path, seen) {
1406
1605
  if (seen.has(expr)) return null;
1407
1606
  seen.add(expr);
1408
1607
  if (t3.isArrayExpression(expr)) return expr;
1608
+ if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1609
+ const consequent = normalizeStyleArrayLikeExpression(expr.right, path, seen);
1610
+ if (!consequent) return null;
1611
+ return t3.conditionalExpression(expr.left, consequent, t3.arrayExpression([]));
1612
+ }
1613
+ if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1614
+ const left = normalizeStyleArrayLikeExpression(expr.left, path, seen);
1615
+ const right = normalizeStyleArrayLikeBranch(expr.right, path, seen);
1616
+ if (!left || !right) return null;
1617
+ return t3.logicalExpression(expr.operator, left, right);
1618
+ }
1409
1619
  if (t3.isConditionalExpression(expr)) {
1410
1620
  const consequent = normalizeStyleArrayLikeBranch(expr.consequent, path, seen);
1411
1621
  const alternate = normalizeStyleArrayLikeBranch(expr.alternate, path, seen);
1412
1622
  if (!consequent || !alternate) return null;
1413
1623
  return t3.conditionalExpression(expr.test, consequent, alternate);
1414
1624
  }
1415
- if (t3.isIdentifier(expr) || t3.isMemberExpression(expr)) {
1625
+ if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isCallExpression(expr)) {
1416
1626
  const nestedSeen = new Set(seen);
1417
1627
  nestedSeen.delete(expr);
1418
1628
  if (isStyleArrayLike(expr, path, nestedSeen)) return expr;
@@ -1429,6 +1639,12 @@ function isStyleArrayLike(expr, path, seen) {
1429
1639
  if (seen.has(expr)) return false;
1430
1640
  seen.add(expr);
1431
1641
  if (t3.isArrayExpression(expr)) return true;
1642
+ if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1643
+ return isStyleArrayLike(expr.right, path, seen);
1644
+ }
1645
+ if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1646
+ return isStyleArrayLike(expr.left, path, seen) && isStyleArrayLikeBranch(expr.right, path, seen);
1647
+ }
1432
1648
  if (t3.isConditionalExpression(expr)) {
1433
1649
  return isStyleArrayLikeBranch(expr.consequent, path, seen) && isStyleArrayLikeBranch(expr.alternate, path, seen);
1434
1650
  }
@@ -1439,7 +1655,11 @@ function isStyleArrayLike(expr, path, seen) {
1439
1655
  const init = bindingPath.node.init;
1440
1656
  return !!(init && isStyleArrayLike(init, bindingPath, seen));
1441
1657
  }
1442
- if (t3.isMemberExpression(expr) && !expr.computed && t3.isIdentifier(expr.property)) {
1658
+ if (t3.isCallExpression(expr)) {
1659
+ const returnExpr = getCallStyleArrayLikeExpression(expr, path);
1660
+ return returnExpr ? isStyleArrayLike(returnExpr, path, seen) : true;
1661
+ }
1662
+ if (t3.isMemberExpression(expr)) {
1443
1663
  const object = expr.object;
1444
1664
  if (!t3.isIdentifier(object)) return false;
1445
1665
  const binding = path.scope.getBinding(object.name);
@@ -1447,7 +1667,8 @@ function isStyleArrayLike(expr, path, seen) {
1447
1667
  if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1448
1668
  const init = bindingPath.node.init;
1449
1669
  if (!init || !t3.isObjectExpression(init)) return false;
1450
- const propertyName = expr.property.name;
1670
+ const propertyName = getStaticMemberPropertyName(expr, path);
1671
+ if (!propertyName) return false;
1451
1672
  for (const prop of init.properties) {
1452
1673
  if (!t3.isObjectProperty(prop) || prop.computed) continue;
1453
1674
  if (!isMatchingPropertyName(prop.key, propertyName)) continue;
@@ -1457,6 +1678,49 @@ function isStyleArrayLike(expr, path, seen) {
1457
1678
  }
1458
1679
  return false;
1459
1680
  }
1681
+ function isKnownStyleArrayLike(expr, path, seen) {
1682
+ if (seen.has(expr)) return false;
1683
+ seen.add(expr);
1684
+ if (t3.isArrayExpression(expr)) return true;
1685
+ if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1686
+ return isKnownStyleArrayLike(expr.right, path, seen);
1687
+ }
1688
+ if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1689
+ return isKnownStyleArrayLike(expr.left, path, seen) && isStyleArrayLikeBranch(expr.right, path, seen);
1690
+ }
1691
+ if (t3.isConditionalExpression(expr)) {
1692
+ return isStyleArrayLikeBranch(expr.consequent, path, seen) && isStyleArrayLikeBranch(expr.alternate, path, seen);
1693
+ }
1694
+ if (t3.isIdentifier(expr)) {
1695
+ const binding = path.scope.getBinding(expr.name);
1696
+ const bindingPath = binding?.path;
1697
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1698
+ const init = bindingPath.node.init;
1699
+ return !!(init && isKnownStyleArrayLike(init, bindingPath, seen));
1700
+ }
1701
+ if (t3.isCallExpression(expr)) {
1702
+ const returnExpr = getCallStyleArrayLikeExpression(expr, path);
1703
+ return !!(returnExpr && isKnownStyleArrayLike(returnExpr, path, seen));
1704
+ }
1705
+ if (t3.isMemberExpression(expr)) {
1706
+ const object = expr.object;
1707
+ if (!t3.isIdentifier(object)) return false;
1708
+ const binding = path.scope.getBinding(object.name);
1709
+ const bindingPath = binding?.path;
1710
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1711
+ const init = bindingPath.node.init;
1712
+ if (!init || !t3.isObjectExpression(init)) return false;
1713
+ const propertyName = getStaticMemberPropertyName(expr, path);
1714
+ if (!propertyName) return false;
1715
+ for (const prop of init.properties) {
1716
+ if (!t3.isObjectProperty(prop) || prop.computed) continue;
1717
+ if (!isMatchingPropertyName(prop.key, propertyName)) continue;
1718
+ const value = prop.value;
1719
+ return t3.isExpression(value) && isKnownStyleArrayLike(value, bindingPath, seen);
1720
+ }
1721
+ }
1722
+ return false;
1723
+ }
1460
1724
  function isStyleArrayLikeBranch(expr, path, seen) {
1461
1725
  return isEmptyObjectExpression(expr) || isStyleArrayLike(expr, path, seen);
1462
1726
  }
@@ -1466,11 +1730,74 @@ function isMatchingPropertyName(key, name) {
1466
1730
  function isEmptyObjectExpression(expr) {
1467
1731
  return t3.isObjectExpression(expr) && expr.properties.length === 0;
1468
1732
  }
1733
+ function buildUnknownObjectSpreadFallback(expr, asStyleArrayHelperName, needsAsStyleArrayHelper) {
1734
+ needsAsStyleArrayHelper.current = true;
1735
+ return t3.callExpression(t3.identifier(asStyleArrayHelperName), [expr]);
1736
+ }
1737
+ function buildInlineAsStyleArrayExpression(expr) {
1738
+ return t3.conditionalExpression(
1739
+ t3.callExpression(t3.memberExpression(t3.identifier("Array"), t3.identifier("isArray")), [expr]),
1740
+ expr,
1741
+ t3.conditionalExpression(expr, t3.arrayExpression([expr]), t3.arrayExpression([]))
1742
+ );
1743
+ }
1744
+ function buildSafeSpreadArgument(expr) {
1745
+ return t3.isConditionalExpression(expr) || t3.isLogicalExpression(expr) ? t3.parenthesizedExpression(expr) : expr;
1746
+ }
1747
+ function getStaticMemberPropertyName(expr, path) {
1748
+ if (!expr.computed && t3.isIdentifier(expr.property)) {
1749
+ return expr.property.name;
1750
+ }
1751
+ if (t3.isStringLiteral(expr.property)) {
1752
+ return expr.property.value;
1753
+ }
1754
+ if (t3.isIdentifier(expr.property)) {
1755
+ const binding = path.scope.getBinding(expr.property.name);
1756
+ const bindingPath = binding?.path;
1757
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return null;
1758
+ const init = bindingPath.node.init;
1759
+ return t3.isStringLiteral(init) ? init.value : null;
1760
+ }
1761
+ return null;
1762
+ }
1763
+ function getCallStyleArrayLikeExpression(expr, path) {
1764
+ const localReturnExpr = getLocalFunctionReturnExpression(expr, path);
1765
+ if (localReturnExpr) return localReturnExpr;
1766
+ const firstArg = expr.arguments[0];
1767
+ if (firstArg && !t3.isSpreadElement(firstArg) && (t3.isArrowFunctionExpression(firstArg) || t3.isFunctionExpression(firstArg))) {
1768
+ return getFunctionLikeReturnExpression(firstArg);
1769
+ }
1770
+ return null;
1771
+ }
1772
+ function getLocalFunctionReturnExpression(expr, path) {
1773
+ if (!t3.isIdentifier(expr.callee)) return null;
1774
+ const binding = path.scope.getBinding(expr.callee.name);
1775
+ const bindingPath = binding?.path;
1776
+ if (!bindingPath) return null;
1777
+ if (bindingPath.isFunctionDeclaration()) {
1778
+ return getFunctionLikeReturnExpression(bindingPath.node);
1779
+ }
1780
+ if (bindingPath.isVariableDeclarator()) {
1781
+ const init = bindingPath.node.init;
1782
+ if (init && (t3.isArrowFunctionExpression(init) || t3.isFunctionExpression(init))) {
1783
+ return getFunctionLikeReturnExpression(init);
1784
+ }
1785
+ }
1786
+ return null;
1787
+ }
1788
+ function getFunctionLikeReturnExpression(fn) {
1789
+ if (t3.isExpression(fn.body)) {
1790
+ return fn.body;
1791
+ }
1792
+ if (fn.body.body.length !== 1) return null;
1793
+ const stmt = fn.body.body[0];
1794
+ return t3.isReturnStatement(stmt) && stmt.argument && t3.isExpression(stmt.argument) ? stmt.argument : null;
1795
+ }
1469
1796
 
1470
1797
  // src/plugin/transform.ts
1471
1798
  var traverse2 = _traverse2.default ?? _traverse2;
1472
- var generate = _generate.default ?? _generate;
1473
- function transformTruss(code, filename, mapping) {
1799
+ var generate2 = _generate2.default ?? _generate2;
1800
+ function transformTruss(code, filename, mapping, options = {}) {
1474
1801
  if (!code.includes("Css")) return null;
1475
1802
  const ast = parse(code, {
1476
1803
  sourceType: "module",
@@ -1506,6 +1833,18 @@ function transformTruss(code, filename, mapping) {
1506
1833
  const stylexNamespaceName = existingStylexNamespace ?? reservePreferredName(usedTopLevelNames, "stylex");
1507
1834
  const createVarName = reservePreferredName(usedTopLevelNames, "css", "css_");
1508
1835
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
1836
+ const existingMergePropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "mergeProps");
1837
+ const mergePropsHelperName = existingMergePropsHelperName ?? reservePreferredName(usedTopLevelNames, "mergeProps");
1838
+ const needsMergePropsHelper = { current: false };
1839
+ const existingAsStyleArrayHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "asStyleArray");
1840
+ const asStyleArrayHelperName = existingAsStyleArrayHelperName ?? reservePreferredName(usedTopLevelNames, "asStyleArray");
1841
+ const needsAsStyleArrayHelper = { current: false };
1842
+ const existingTrussPropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "trussProps");
1843
+ const trussPropsHelperName = existingTrussPropsHelperName ?? reservePreferredName(usedTopLevelNames, "trussProps");
1844
+ const needsTrussPropsHelper = { current: false };
1845
+ const existingTrussDebugInfoName = findNamedImportBinding(ast, "@homebound/truss/runtime", "TrussDebugInfo");
1846
+ const trussDebugInfoName = existingTrussDebugInfoName ?? reservePreferredName(usedTopLevelNames, "TrussDebugInfo");
1847
+ const needsTrussDebugInfo = { current: false };
1509
1848
  const runtimeLookupNames = /* @__PURE__ */ new Map();
1510
1849
  for (const [lookupKey] of runtimeLookups) {
1511
1850
  runtimeLookupNames.set(lookupKey, reservePreferredName(usedTopLevelNames, `__${lookupKey}`));
@@ -1514,15 +1853,42 @@ function transformTruss(code, filename, mapping) {
1514
1853
  rewriteExpressionSites({
1515
1854
  ast,
1516
1855
  sites,
1856
+ filename: basename(filename),
1857
+ debug: options.debug ?? false,
1517
1858
  createVarName,
1518
1859
  stylexNamespaceName,
1519
1860
  maybeIncHelperName,
1861
+ mergePropsHelperName,
1862
+ needsMergePropsHelper,
1863
+ trussPropsHelperName,
1864
+ needsTrussPropsHelper,
1865
+ trussDebugInfoName,
1866
+ needsTrussDebugInfo,
1867
+ asStyleArrayHelperName,
1868
+ needsAsStyleArrayHelper,
1869
+ skippedCssPropMessages: errorMessages,
1520
1870
  runtimeLookupNames
1521
1871
  });
1522
1872
  removeCssImport(ast, cssBindingName);
1523
1873
  if (!findStylexNamespaceImport(ast)) {
1524
1874
  insertStylexNamespaceImport(ast, stylexNamespaceName);
1525
1875
  }
1876
+ if (needsMergePropsHelper.current || needsAsStyleArrayHelper.current || needsTrussPropsHelper.current || needsTrussDebugInfo.current) {
1877
+ const runtimeImports = [];
1878
+ if (needsMergePropsHelper.current) {
1879
+ runtimeImports.push({ importedName: "mergeProps", localName: mergePropsHelperName });
1880
+ }
1881
+ if (needsAsStyleArrayHelper.current) {
1882
+ runtimeImports.push({ importedName: "asStyleArray", localName: asStyleArrayHelperName });
1883
+ }
1884
+ if (needsTrussPropsHelper.current) {
1885
+ runtimeImports.push({ importedName: "trussProps", localName: trussPropsHelperName });
1886
+ }
1887
+ if (needsTrussDebugInfo.current) {
1888
+ runtimeImports.push({ importedName: "TrussDebugInfo", localName: trussDebugInfoName });
1889
+ }
1890
+ upsertNamedImports(ast, "@homebound/truss/runtime", runtimeImports);
1891
+ }
1526
1892
  const markerVarNames = collectReferencedMarkerNames(createEntries);
1527
1893
  const hoistedMarkerDecls = hoistMarkerDeclarations(ast, markerVarNames);
1528
1894
  const declarationsToInsert = [];
@@ -1549,10 +1915,12 @@ function transformTruss(code, filename, mapping) {
1549
1915
  declarationsToInsert.push(consoleError);
1550
1916
  }
1551
1917
  if (declarationsToInsert.length > 0) {
1552
- const insertIndex = findLastImportIndex(ast) + 1;
1553
- ast.program.body.splice(insertIndex, 0, ...declarationsToInsert);
1918
+ const insertIndex = ast.program.body.findIndex(function(node) {
1919
+ return !t4.isImportDeclaration(node);
1920
+ });
1921
+ ast.program.body.splice(insertIndex === -1 ? ast.program.body.length : insertIndex, 0, ...declarationsToInsert);
1554
1922
  }
1555
- const output = generate(ast, {
1923
+ const output = generate2(ast, {
1556
1924
  sourceFileName: filename,
1557
1925
  retainLines: false
1558
1926
  });
@@ -1770,6 +2138,9 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
1770
2138
  if (seg.typographyLookup) {
1771
2139
  return { error: `typography() with a runtime key is not supported in .css.ts files` };
1772
2140
  }
2141
+ if (seg.styleArrayArg) {
2142
+ return { error: `add(cssProp) is not supported in .css.ts files` };
2143
+ }
1773
2144
  if (seg.mediaQuery) {
1774
2145
  return { error: `media query modifiers (ifSm, ifMd, etc.) are not supported in .css.ts files` };
1775
2146
  }
@@ -1808,9 +2179,9 @@ ${body}
1808
2179
 
1809
2180
  // src/plugin/rewrite-css-ts-imports.ts
1810
2181
  import { parse as parse3 } from "@babel/parser";
1811
- import _generate2 from "@babel/generator";
2182
+ import _generate3 from "@babel/generator";
1812
2183
  import * as t7 from "@babel/types";
1813
- var generate2 = _generate2.default ?? _generate2;
2184
+ var generate3 = _generate3.default ?? _generate3;
1814
2185
  function rewriteCssTsImports(code, filename) {
1815
2186
  if (!code.includes(".css.ts")) {
1816
2187
  return { code, changed: false };
@@ -1848,7 +2219,7 @@ function rewriteCssTsImports(code, filename) {
1848
2219
  const insertIndex = findLastImportIndex(ast) + 1;
1849
2220
  ast.program.body.splice(insertIndex, 0, ...sideEffectImports);
1850
2221
  }
1851
- const output = generate2(ast, {
2222
+ const output = generate3(ast, {
1852
2223
  sourceFileName: filename,
1853
2224
  retainLines: false
1854
2225
  });
@@ -1864,6 +2235,7 @@ var CSS_TS_QUERY = "?truss-css";
1864
2235
  function trussPlugin(opts) {
1865
2236
  let mapping = null;
1866
2237
  let projectRoot;
2238
+ let debug = false;
1867
2239
  const externalPackages = opts.externalPackages ?? [];
1868
2240
  function mappingPath() {
1869
2241
  return resolve(projectRoot || process.cwd(), opts.mapping);
@@ -1879,6 +2251,7 @@ function trussPlugin(opts) {
1879
2251
  enforce: "pre",
1880
2252
  configResolved(config) {
1881
2253
  projectRoot = config.root;
2254
+ debug = config.command === "serve" || config.mode === "development" || config.mode === "test";
1882
2255
  },
1883
2256
  buildStart() {
1884
2257
  ensureMapping();
@@ -1911,7 +2284,7 @@ function trussPlugin(opts) {
1911
2284
  if (!hasCssDsl) {
1912
2285
  return { code: rewrittenCode, map: null };
1913
2286
  }
1914
- const result = transformTruss(rewrittenCode, id, ensureMapping());
2287
+ const result = transformTruss(rewrittenCode, fileId, ensureMapping(), { debug });
1915
2288
  if (!result) {
1916
2289
  if (!rewrittenImports.changed) return null;
1917
2290
  return { code: rewrittenCode, map: null };