@csszyx/compiler 0.10.6 → 0.10.8

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.
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  function __szColorVar(v) {
4
+ if (typeof v !== "string" || v === "") {
5
+ return void 0;
6
+ }
4
7
  if (v.startsWith("#") || v.startsWith("rgb") || v.startsWith("hsl") || v.startsWith("oklch")) {
5
8
  return v;
6
9
  }
@@ -6,13 +6,14 @@
6
6
  * The runtime/lite inlines this implementation at build time (zero runtime dep).
7
7
  *
8
8
  * Resolution rules (Tailwind v4 conventions):
9
+ * - undefined/null/empty/non-string → undefined ("no colour", omit the CSS var)
9
10
  * - Raw CSS values (#hex, rgb, hsl, oklch) → pass through unchanged
10
11
  * - CSS custom properties (--*) → wrapped in var()
11
12
  * - Tailwind color names (blue-500, etc.) → var(--color-<name>)
12
13
  *
13
14
  * @param v - Color value: Tailwind name, hex, rgb/hsl/oklch, or CSS variable
14
- * @returns CSS-compatible color string
15
+ * @returns CSS-compatible color string, or undefined when there is no colour
15
16
  */
16
- declare function __szColorVar(v: string): string;
17
+ declare function __szColorVar(v: string | null | undefined): string | undefined;
17
18
 
18
19
  export { __szColorVar };
@@ -6,13 +6,14 @@
6
6
  * The runtime/lite inlines this implementation at build time (zero runtime dep).
7
7
  *
8
8
  * Resolution rules (Tailwind v4 conventions):
9
+ * - undefined/null/empty/non-string → undefined ("no colour", omit the CSS var)
9
10
  * - Raw CSS values (#hex, rgb, hsl, oklch) → pass through unchanged
10
11
  * - CSS custom properties (--*) → wrapped in var()
11
12
  * - Tailwind color names (blue-500, etc.) → var(--color-<name>)
12
13
  *
13
14
  * @param v - Color value: Tailwind name, hex, rgb/hsl/oklch, or CSS variable
14
- * @returns CSS-compatible color string
15
+ * @returns CSS-compatible color string, or undefined when there is no colour
15
16
  */
16
- declare function __szColorVar(v: string): string;
17
+ declare function __szColorVar(v: string | null | undefined): string | undefined;
17
18
 
18
19
  export { __szColorVar };
@@ -1,4 +1,7 @@
1
1
  function __szColorVar(v) {
2
+ if (typeof v !== "string" || v === "") {
3
+ return void 0;
4
+ }
2
5
  if (v.startsWith("#") || v.startsWith("rgb") || v.startsWith("hsl") || v.startsWith("oklch")) {
3
6
  return v;
4
7
  }
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('@csszyx/core');
4
- const transformCore = require('./shared/compiler.BCOOXYBh.cjs');
4
+ const transformCore = require('./shared/compiler.mibv6qPF.cjs');
5
5
  const oxcParser = require('oxc-parser');
6
6
  const t = require('@babel/types');
7
7
  const node_crypto = require('node:crypto');
@@ -117,6 +117,7 @@ function transformSourceCode(source, filename, options) {
117
117
  // before any sz transform work begins, and doesn't
118
118
  // interfere with the JSXAttribute handler below.
119
119
  pre(file) {
120
+ transformCore.setSzWarnLocation(void 0);
120
121
  let nodeCount = 0;
121
122
  babel__namespace.traverse(file.ast, {
122
123
  enter() {
@@ -131,6 +132,11 @@ function transformSourceCode(source, filename, options) {
131
132
  }
132
133
  });
133
134
  },
135
+ // Drop the warn location after the file's visitor pass so it
136
+ // never leaks to an unrelated later transform or the runtime path.
137
+ post() {
138
+ transformCore.setSzWarnLocation(void 0);
139
+ },
134
140
  visitor: {
135
141
  JSXAttribute(path) {
136
142
  const attrName = t__namespace.isJSXIdentifier(path.node.name) ? path.node.name.name : "";
@@ -197,6 +203,13 @@ function transformSourceCode(source, filename, options) {
197
203
  if (attrName !== "sz") {
198
204
  return;
199
205
  }
206
+ transformCore.setSzWarnLocation(
207
+ transformCore.formatSzWarnLocation(
208
+ filename ?? "file.tsx",
209
+ path.node.loc?.start.line,
210
+ options?.rootDir
211
+ )
212
+ );
200
213
  const value = path.node.value;
201
214
  let existingClassNameNode = null;
202
215
  let existingClassExpr = null;
@@ -232,6 +245,9 @@ function transformSourceCode(source, filename, options) {
232
245
  }
233
246
  const createMergedClassNameValue = (szExpr) => {
234
247
  if (!existingClassExpr) {
248
+ if (t__namespace.isStringLiteral(szExpr) && szExpr.value === "") {
249
+ return t__namespace.jsxExpressionContainer(t__namespace.identifier("undefined"));
250
+ }
235
251
  return t__namespace.isStringLiteral(szExpr) ? szExpr : t__namespace.jsxExpressionContainer(szExpr);
236
252
  }
237
253
  if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
@@ -813,6 +829,9 @@ function parseStyleStringToObjectExpr(styleStr) {
813
829
  }
814
830
  return t__namespace.objectExpression(objProps);
815
831
  }
832
+ function emptyClassToUndefined(node) {
833
+ return t__namespace.isStringLiteral(node) && node.value === "" ? t__namespace.identifier("undefined") : node;
834
+ }
816
835
  function tryStaticTransformNode(node, getBinding) {
817
836
  if (t__namespace.isTSAsExpression(node) || t__namespace.isTSSatisfiesExpression(node)) {
818
837
  return tryStaticTransformNode(node.expression, getBinding);
@@ -849,7 +868,11 @@ function tryStaticTransformNode(node, getBinding) {
849
868
  const consequent = tryStaticTransformNode(node.consequent, getBinding);
850
869
  const alternate = tryStaticTransformNode(node.alternate, getBinding);
851
870
  if (consequent !== null && alternate !== null) {
852
- return t__namespace.conditionalExpression(node.test, consequent, alternate);
871
+ return t__namespace.conditionalExpression(
872
+ node.test,
873
+ emptyClassToUndefined(consequent),
874
+ emptyClassToUndefined(alternate)
875
+ );
853
876
  }
854
877
  return null;
855
878
  }
@@ -886,7 +909,11 @@ function tryHoistConditionalSpread(node, getBinding) {
886
909
  if (!t__namespace.isStringLiteral(resolvedA) || !t__namespace.isStringLiteral(resolvedB)) {
887
910
  return null;
888
911
  }
889
- return t__namespace.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
912
+ return t__namespace.conditionalExpression(
913
+ conditionalExpr.test,
914
+ emptyClassToUndefined(resolvedA),
915
+ emptyClassToUndefined(resolvedB)
916
+ );
890
917
  }
891
918
  function readStaticConfigObject(configExpr, key, scope) {
892
919
  for (const prop of configExpr.properties) {
@@ -1016,20 +1043,20 @@ function buildConditionalClassExpr(baseClasses, conditionalClasses) {
1016
1043
  if (conditionalClasses.length === 0) {
1017
1044
  return t__namespace.stringLiteral(baseClasses);
1018
1045
  }
1019
- const makeCondExpr = (cc) => t__namespace.conditionalExpression(
1046
+ const makeCondExpr = (cc, bare) => t__namespace.conditionalExpression(
1020
1047
  cc.test,
1021
- t__namespace.stringLiteral(cc.consequent),
1022
- t__namespace.stringLiteral(cc.alternate)
1048
+ bare && cc.consequent === "" ? t__namespace.identifier("undefined") : t__namespace.stringLiteral(cc.consequent),
1049
+ bare && cc.alternate === "" ? t__namespace.identifier("undefined") : t__namespace.stringLiteral(cc.alternate)
1023
1050
  );
1024
1051
  if (conditionalClasses.length === 1 && !baseClasses) {
1025
- return makeCondExpr(conditionalClasses[0]);
1052
+ return makeCondExpr(conditionalClasses[0], true);
1026
1053
  }
1027
1054
  const quasis = [];
1028
1055
  const exprs = [];
1029
1056
  for (let i = 0; i < conditionalClasses.length; i++) {
1030
1057
  const prefix = i === 0 ? baseClasses ? `${baseClasses} ` : "" : " ";
1031
1058
  quasis.push(t__namespace.templateElement({ raw: prefix, cooked: prefix }, false));
1032
- exprs.push(makeCondExpr(conditionalClasses[i]));
1059
+ exprs.push(makeCondExpr(conditionalClasses[i], false));
1033
1060
  }
1034
1061
  quasis.push(t__namespace.templateElement({ raw: "", cooked: "" }, true));
1035
1062
  return t__namespace.templateLiteral(quasis, exprs);
@@ -2288,6 +2315,7 @@ function transformOxc(source, filename, options) {
2288
2315
  };
2289
2316
  }
2290
2317
  const effectiveFilename = filename ?? "file.tsx";
2318
+ transformCore.setSzWarnLocation(void 0);
2291
2319
  const astBudget = options?.astBudget ?? AST_BUDGET;
2292
2320
  const parsed = oxcParser.parseSync(effectiveFilename, source);
2293
2321
  if (parsed.errors.length > 0) {
@@ -2428,6 +2456,10 @@ function transformOxc(source, filename, options) {
2428
2456
  let runtimeFallbackExpr = null;
2429
2457
  let runtimeFallbackAttr = null;
2430
2458
  for (const szAttr of szAttrs) {
2459
+ const { line: szWarnLine } = offsetToLineColumn(source, szAttr.start);
2460
+ transformCore.setSzWarnLocation(
2461
+ transformCore.formatSzWarnLocation(effectiveFilename, szWarnLine, options?.rootDir)
2462
+ );
2431
2463
  const value = szAttr.value;
2432
2464
  if (!value) {
2433
2465
  throw new OxcNotImplementedError(
@@ -2704,6 +2736,7 @@ function transformOxc(source, filename, options) {
2704
2736
  }
2705
2737
  }
2706
2738
  }
2739
+ transformCore.setSzWarnLocation(void 0);
2707
2740
  if (runtimeFallbackExpr && runtimeFallbackAttr) {
2708
2741
  if (classNameAttr) {
2709
2742
  throw new OxcNotImplementedError(
@@ -2750,7 +2783,7 @@ function transformOxc(source, filename, options) {
2750
2783
  ...existingRaw ? existingRaw.split(/\s+/).filter(Boolean) : [],
2751
2784
  ...szDerived
2752
2785
  ];
2753
- const mergedAttr = `className="${mergedClasses.join(" ")}"`;
2786
+ const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2754
2787
  if (classNameAttr) {
2755
2788
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2756
2789
  for (const szAttr of szAttrs) {
@@ -3362,7 +3395,8 @@ function buildConditionalSpreadClassExpression(node, filename, bindings, source,
3362
3395
  }
3363
3396
  }
3364
3397
  const testSource = source.slice(conditionalSpread.test.start, conditionalSpread.test.end);
3365
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
3398
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3399
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3366
3400
  }
3367
3401
  function compileConditionalSpreadBranch(branch, otherProps, sourceNode, filename, bindings, globalVarAliases, cssVariableMap) {
3368
3402
  const branchObject = resolveObjectExpression(branch, bindings);
@@ -3425,7 +3459,11 @@ function buildPartialObjectTransform(node, filename, bindings, source, options,
3425
3459
  classParts.push(entry.consequent, entry.alternate);
3426
3460
  }
3427
3461
  const className = classParts.filter(Boolean).join(" ");
3428
- const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : `className="${className}"`;
3462
+ const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : className === "" ? (
3463
+ // An sz that lowers to zero classes emits `className={undefined}` so the
3464
+ // DOM has no `class` attribute, instead of the noisy `class=""`.
3465
+ "className={undefined}"
3466
+ ) : `className="${className}"`;
3429
3467
  const styleProps = [...partial.dynamicProps.entries()].filter(([id]) => !hoistedNames?.has(id)).map(
3430
3468
  ([, info]) => `${JSON.stringify(info.varName)}: ${generateStyleValueSource(info, source)}`
3431
3469
  );
@@ -3999,9 +4037,11 @@ function buildCSSVarClassName(info) {
3999
4037
  function buildConditionalClassSource(classParts, conditionals, source) {
4000
4038
  if (conditionals.length === 1) {
4001
4039
  const [entry] = conditionals;
4002
- const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${JSON.stringify(entry.consequent)} : ${JSON.stringify(entry.alternate)}`;
4003
4040
  const staticParts = classParts.slice(0, -2).filter(Boolean);
4004
- if (staticParts.length === 0) {
4041
+ const bare = staticParts.length === 0;
4042
+ const branch = (cls) => bare && cls === "" ? "undefined" : JSON.stringify(cls);
4043
+ const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${branch(entry.consequent)} : ${branch(entry.alternate)}`;
4044
+ if (bare) {
4005
4045
  return ternary;
4006
4046
  }
4007
4047
  return `\`${staticParts.join(" ")} \${${ternary}}\``;
@@ -4053,7 +4093,8 @@ function buildStaticConditionalClassExpression(node, filename, bindings, source,
4053
4093
  }
4054
4094
  }
4055
4095
  const testSource = source.slice(node.test.start, node.test.end);
4056
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
4096
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
4097
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
4057
4098
  }
4058
4099
  function resolveStaticClassString(node, filename, bindings, globalVarAliases, cssVariableMap) {
4059
4100
  const unwrapped = unwrapExpression(node);
@@ -4283,7 +4324,8 @@ function transformRustBatch(files, options) {
4283
4324
  {
4284
4325
  mangleVars: options?.mangleVars === true,
4285
4326
  mangleVarHoistMaxDepth: options?.mangleVarHoistMaxDepth,
4286
- globalVarAliases: normalizeGlobalVarAliases(options?.globalVarAliases)
4327
+ globalVarAliases: normalizeGlobalVarAliases(options?.globalVarAliases),
4328
+ rootDir: options?.rootDir
4287
4329
  }
4288
4330
  ).map(fromNativeResult);
4289
4331
  } catch (err) {
package/dist/index.d.cts CHANGED
@@ -329,6 +329,12 @@ interface TransformSourceCodeOptions {
329
329
  * token names to aliases, for example `--brand-primary` -> `---gz`.
330
330
  */
331
331
  globalVarAliases?: GlobalVarAliasTableInput;
332
+ /**
333
+ * Project root used only to render diagnostic file paths relative to it (so a
334
+ * dev-mode "Unknown property" warning reads `src/Foo.tsx:12`, not an absolute
335
+ * path). When omitted, diagnostics fall back to the filename as given.
336
+ */
337
+ rootDir?: string;
332
338
  }
333
339
  /**
334
340
  * Accepted input shapes for global CSS custom-property alias tables.
package/dist/index.d.mts CHANGED
@@ -329,6 +329,12 @@ interface TransformSourceCodeOptions {
329
329
  * token names to aliases, for example `--brand-primary` -> `---gz`.
330
330
  */
331
331
  globalVarAliases?: GlobalVarAliasTableInput;
332
+ /**
333
+ * Project root used only to render diagnostic file paths relative to it (so a
334
+ * dev-mode "Unknown property" warning reads `src/Foo.tsx:12`, not an absolute
335
+ * path). When omitted, diagnostics fall back to the filename as given.
336
+ */
337
+ rootDir?: string;
332
338
  }
333
339
  /**
334
340
  * Accepted input shapes for global CSS custom-property alias tables.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { init, version, transform_sz, encode } from '@csszyx/core';
2
- import { t as transform, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, s as stripInvalidColorStrings } from './shared/compiler.DGInsDP8.mjs';
3
- export { B as BOOLEAN_SHORTHANDS, d as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, e as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.DGInsDP8.mjs';
2
+ import { t as transform, s as setSzWarnLocation, f as formatSzWarnLocation, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, d as stripInvalidColorStrings } from './shared/compiler.CghwJ6p5.mjs';
3
+ export { B as BOOLEAN_SHORTHANDS, e as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.CghwJ6p5.mjs';
4
4
  import { parseSync } from 'oxc-parser';
5
5
  import * as t from '@babel/types';
6
6
  import { createHash } from 'node:crypto';
@@ -98,6 +98,7 @@ function transformSourceCode(source, filename, options) {
98
98
  // before any sz transform work begins, and doesn't
99
99
  // interfere with the JSXAttribute handler below.
100
100
  pre(file) {
101
+ setSzWarnLocation(void 0);
101
102
  let nodeCount = 0;
102
103
  babel.traverse(file.ast, {
103
104
  enter() {
@@ -112,6 +113,11 @@ function transformSourceCode(source, filename, options) {
112
113
  }
113
114
  });
114
115
  },
116
+ // Drop the warn location after the file's visitor pass so it
117
+ // never leaks to an unrelated later transform or the runtime path.
118
+ post() {
119
+ setSzWarnLocation(void 0);
120
+ },
115
121
  visitor: {
116
122
  JSXAttribute(path) {
117
123
  const attrName = t.isJSXIdentifier(path.node.name) ? path.node.name.name : "";
@@ -178,6 +184,13 @@ function transformSourceCode(source, filename, options) {
178
184
  if (attrName !== "sz") {
179
185
  return;
180
186
  }
187
+ setSzWarnLocation(
188
+ formatSzWarnLocation(
189
+ filename ?? "file.tsx",
190
+ path.node.loc?.start.line,
191
+ options?.rootDir
192
+ )
193
+ );
181
194
  const value = path.node.value;
182
195
  let existingClassNameNode = null;
183
196
  let existingClassExpr = null;
@@ -213,6 +226,9 @@ function transformSourceCode(source, filename, options) {
213
226
  }
214
227
  const createMergedClassNameValue = (szExpr) => {
215
228
  if (!existingClassExpr) {
229
+ if (t.isStringLiteral(szExpr) && szExpr.value === "") {
230
+ return t.jsxExpressionContainer(t.identifier("undefined"));
231
+ }
216
232
  return t.isStringLiteral(szExpr) ? szExpr : t.jsxExpressionContainer(szExpr);
217
233
  }
218
234
  if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
@@ -794,6 +810,9 @@ function parseStyleStringToObjectExpr(styleStr) {
794
810
  }
795
811
  return t.objectExpression(objProps);
796
812
  }
813
+ function emptyClassToUndefined(node) {
814
+ return t.isStringLiteral(node) && node.value === "" ? t.identifier("undefined") : node;
815
+ }
797
816
  function tryStaticTransformNode(node, getBinding) {
798
817
  if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {
799
818
  return tryStaticTransformNode(node.expression, getBinding);
@@ -830,7 +849,11 @@ function tryStaticTransformNode(node, getBinding) {
830
849
  const consequent = tryStaticTransformNode(node.consequent, getBinding);
831
850
  const alternate = tryStaticTransformNode(node.alternate, getBinding);
832
851
  if (consequent !== null && alternate !== null) {
833
- return t.conditionalExpression(node.test, consequent, alternate);
852
+ return t.conditionalExpression(
853
+ node.test,
854
+ emptyClassToUndefined(consequent),
855
+ emptyClassToUndefined(alternate)
856
+ );
834
857
  }
835
858
  return null;
836
859
  }
@@ -867,7 +890,11 @@ function tryHoistConditionalSpread(node, getBinding) {
867
890
  if (!t.isStringLiteral(resolvedA) || !t.isStringLiteral(resolvedB)) {
868
891
  return null;
869
892
  }
870
- return t.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
893
+ return t.conditionalExpression(
894
+ conditionalExpr.test,
895
+ emptyClassToUndefined(resolvedA),
896
+ emptyClassToUndefined(resolvedB)
897
+ );
871
898
  }
872
899
  function readStaticConfigObject(configExpr, key, scope) {
873
900
  for (const prop of configExpr.properties) {
@@ -997,20 +1024,20 @@ function buildConditionalClassExpr(baseClasses, conditionalClasses) {
997
1024
  if (conditionalClasses.length === 0) {
998
1025
  return t.stringLiteral(baseClasses);
999
1026
  }
1000
- const makeCondExpr = (cc) => t.conditionalExpression(
1027
+ const makeCondExpr = (cc, bare) => t.conditionalExpression(
1001
1028
  cc.test,
1002
- t.stringLiteral(cc.consequent),
1003
- t.stringLiteral(cc.alternate)
1029
+ bare && cc.consequent === "" ? t.identifier("undefined") : t.stringLiteral(cc.consequent),
1030
+ bare && cc.alternate === "" ? t.identifier("undefined") : t.stringLiteral(cc.alternate)
1004
1031
  );
1005
1032
  if (conditionalClasses.length === 1 && !baseClasses) {
1006
- return makeCondExpr(conditionalClasses[0]);
1033
+ return makeCondExpr(conditionalClasses[0], true);
1007
1034
  }
1008
1035
  const quasis = [];
1009
1036
  const exprs = [];
1010
1037
  for (let i = 0; i < conditionalClasses.length; i++) {
1011
1038
  const prefix = i === 0 ? baseClasses ? `${baseClasses} ` : "" : " ";
1012
1039
  quasis.push(t.templateElement({ raw: prefix, cooked: prefix }, false));
1013
- exprs.push(makeCondExpr(conditionalClasses[i]));
1040
+ exprs.push(makeCondExpr(conditionalClasses[i], false));
1014
1041
  }
1015
1042
  quasis.push(t.templateElement({ raw: "", cooked: "" }, true));
1016
1043
  return t.templateLiteral(quasis, exprs);
@@ -2269,6 +2296,7 @@ function transformOxc(source, filename, options) {
2269
2296
  };
2270
2297
  }
2271
2298
  const effectiveFilename = filename ?? "file.tsx";
2299
+ setSzWarnLocation(void 0);
2272
2300
  const astBudget = options?.astBudget ?? AST_BUDGET;
2273
2301
  const parsed = parseSync(effectiveFilename, source);
2274
2302
  if (parsed.errors.length > 0) {
@@ -2409,6 +2437,10 @@ function transformOxc(source, filename, options) {
2409
2437
  let runtimeFallbackExpr = null;
2410
2438
  let runtimeFallbackAttr = null;
2411
2439
  for (const szAttr of szAttrs) {
2440
+ const { line: szWarnLine } = offsetToLineColumn(source, szAttr.start);
2441
+ setSzWarnLocation(
2442
+ formatSzWarnLocation(effectiveFilename, szWarnLine, options?.rootDir)
2443
+ );
2412
2444
  const value = szAttr.value;
2413
2445
  if (!value) {
2414
2446
  throw new OxcNotImplementedError(
@@ -2685,6 +2717,7 @@ function transformOxc(source, filename, options) {
2685
2717
  }
2686
2718
  }
2687
2719
  }
2720
+ setSzWarnLocation(void 0);
2688
2721
  if (runtimeFallbackExpr && runtimeFallbackAttr) {
2689
2722
  if (classNameAttr) {
2690
2723
  throw new OxcNotImplementedError(
@@ -2731,7 +2764,7 @@ function transformOxc(source, filename, options) {
2731
2764
  ...existingRaw ? existingRaw.split(/\s+/).filter(Boolean) : [],
2732
2765
  ...szDerived
2733
2766
  ];
2734
- const mergedAttr = `className="${mergedClasses.join(" ")}"`;
2767
+ const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2735
2768
  if (classNameAttr) {
2736
2769
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2737
2770
  for (const szAttr of szAttrs) {
@@ -3343,7 +3376,8 @@ function buildConditionalSpreadClassExpression(node, filename, bindings, source,
3343
3376
  }
3344
3377
  }
3345
3378
  const testSource = source.slice(conditionalSpread.test.start, conditionalSpread.test.end);
3346
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
3379
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3380
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3347
3381
  }
3348
3382
  function compileConditionalSpreadBranch(branch, otherProps, sourceNode, filename, bindings, globalVarAliases, cssVariableMap) {
3349
3383
  const branchObject = resolveObjectExpression(branch, bindings);
@@ -3406,7 +3440,11 @@ function buildPartialObjectTransform(node, filename, bindings, source, options,
3406
3440
  classParts.push(entry.consequent, entry.alternate);
3407
3441
  }
3408
3442
  const className = classParts.filter(Boolean).join(" ");
3409
- const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : `className="${className}"`;
3443
+ const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : className === "" ? (
3444
+ // An sz that lowers to zero classes emits `className={undefined}` so the
3445
+ // DOM has no `class` attribute, instead of the noisy `class=""`.
3446
+ "className={undefined}"
3447
+ ) : `className="${className}"`;
3410
3448
  const styleProps = [...partial.dynamicProps.entries()].filter(([id]) => !hoistedNames?.has(id)).map(
3411
3449
  ([, info]) => `${JSON.stringify(info.varName)}: ${generateStyleValueSource(info, source)}`
3412
3450
  );
@@ -3980,9 +4018,11 @@ function buildCSSVarClassName(info) {
3980
4018
  function buildConditionalClassSource(classParts, conditionals, source) {
3981
4019
  if (conditionals.length === 1) {
3982
4020
  const [entry] = conditionals;
3983
- const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${JSON.stringify(entry.consequent)} : ${JSON.stringify(entry.alternate)}`;
3984
4021
  const staticParts = classParts.slice(0, -2).filter(Boolean);
3985
- if (staticParts.length === 0) {
4022
+ const bare = staticParts.length === 0;
4023
+ const branch = (cls) => bare && cls === "" ? "undefined" : JSON.stringify(cls);
4024
+ const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${branch(entry.consequent)} : ${branch(entry.alternate)}`;
4025
+ if (bare) {
3986
4026
  return ternary;
3987
4027
  }
3988
4028
  return `\`${staticParts.join(" ")} \${${ternary}}\``;
@@ -4034,7 +4074,8 @@ function buildStaticConditionalClassExpression(node, filename, bindings, source,
4034
4074
  }
4035
4075
  }
4036
4076
  const testSource = source.slice(node.test.start, node.test.end);
4037
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
4077
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
4078
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
4038
4079
  }
4039
4080
  function resolveStaticClassString(node, filename, bindings, globalVarAliases, cssVariableMap) {
4040
4081
  const unwrapped = unwrapExpression(node);
@@ -4264,7 +4305,8 @@ function transformRustBatch(files, options) {
4264
4305
  {
4265
4306
  mangleVars: options?.mangleVars === true,
4266
4307
  mangleVarHoistMaxDepth: options?.mangleVarHoistMaxDepth,
4267
- globalVarAliases: normalizeGlobalVarAliases(options?.globalVarAliases)
4308
+ globalVarAliases: normalizeGlobalVarAliases(options?.globalVarAliases),
4309
+ rootDir: options?.rootDir
4268
4310
  }
4269
4311
  ).map(fromNativeResult);
4270
4312
  } catch (err) {
@@ -1495,6 +1495,36 @@ function handleSupports(supportsObj, prefix) {
1495
1495
  return classes;
1496
1496
  }
1497
1497
  let szTransformDepth = 0;
1498
+ let szWarnLocation;
1499
+ let szHintedProjectScan = false;
1500
+ function hintProjectScanOnce(location) {
1501
+ if (szHintedProjectScan || !location || process.env.CSSZYX_NO_PROJECT_SCAN_HINT === "1") {
1502
+ return;
1503
+ }
1504
+ szHintedProjectScan = true;
1505
+ console.warn(
1506
+ "[csszyx] Tip: run `npx @csszyx/cli check` to scan every file for sz key issues at once (dev warnings only surface files as you open them)."
1507
+ );
1508
+ }
1509
+ function setSzWarnLocation(location) {
1510
+ szWarnLocation = location;
1511
+ }
1512
+ function formatSzWarnLocation(file, line, rootDir) {
1513
+ let rel = file;
1514
+ if (rootDir) {
1515
+ let end = rootDir.length;
1516
+ while (end > 0 && (rootDir[end - 1] === "/" || rootDir[end - 1] === "\\")) {
1517
+ end--;
1518
+ }
1519
+ const root = rootDir.slice(0, end);
1520
+ if (file === root) {
1521
+ rel = file;
1522
+ } else if (file.startsWith(`${root}/`) || file.startsWith(`${root}\\`)) {
1523
+ rel = file.slice(root.length + 1);
1524
+ }
1525
+ }
1526
+ return line === void 0 ? rel : `${rel}:${line}`;
1527
+ }
1498
1528
  function transform(szProp, prefix = "", mangleMap) {
1499
1529
  if (!szProp || typeof szProp !== "object") {
1500
1530
  return { className: "", attributes: {} };
@@ -2339,16 +2369,18 @@ function transformImpl(szProp, prefix, mangleMap) {
2339
2369
  KNOWN_VARIANTS.has(rawKey) || // Parametric/scope variants (group, peer, has, not, data, aria, supports)
2340
2370
  SPECIAL_VARIANTS.has(rawKey) || rawKey === "min" || rawKey === "max";
2341
2371
  if (!isKnown) {
2372
+ const at = szWarnLocation ? ` at ${szWarnLocation}` : "";
2342
2373
  const suggestion = SUGGESTION_MAP[rawKey];
2343
2374
  if (suggestion) {
2344
2375
  console.warn(
2345
- `[csszyx] Use the canonical key "${suggestion}" instead of "${rawKey}".`
2376
+ `[csszyx] Use the canonical key "${suggestion}" instead of "${rawKey}"${at}.`
2346
2377
  );
2347
2378
  } else {
2348
2379
  console.warn(
2349
- `[csszyx] Unknown property "${rawKey}" in sz prop. This will be ignored. Check for typos.`
2380
+ `[csszyx] Unknown property "${rawKey}" in sz prop${at}. This will be ignored. Check for typos.`
2350
2381
  );
2351
2382
  }
2383
+ hintProjectScanOnce(szWarnLocation);
2352
2384
  }
2353
2385
  }
2354
2386
  if (value === true) {
@@ -2465,4 +2497,4 @@ function normalizeClassName(className) {
2465
2497
  return className.split(/\s+/).filter(Boolean).join(" ");
2466
2498
  }
2467
2499
 
2468
- export { BOOLEAN_SHORTHANDS as B, COLOR_PROPERTIES as C, KNOWN_VARIANTS as K, MAX_SZ_DEPTH as M, PROPERTY_MAP as P, REMOVED_BOOLEAN_SUGAR as R, SPECIAL_VARIANTS as S, VARIANT_MAP as V, PropertyCategory as a, getVariantPrefix as b, getPropertyCategory as c, PROPERTY_CATEGORY_MAP as d, SUGGESTION_MAP as e, SzDepthError as f, getCSSVariableName as g, isForbiddenSzKey as h, isValidSzProp as i, normalizeArbitraryValue as j, normalizeArbitraryVariant as k, normalizeClassName as n, stripInvalidColorStrings as s, transform as t };
2500
+ export { BOOLEAN_SHORTHANDS as B, COLOR_PROPERTIES as C, KNOWN_VARIANTS as K, MAX_SZ_DEPTH as M, PROPERTY_MAP as P, REMOVED_BOOLEAN_SUGAR as R, SPECIAL_VARIANTS as S, VARIANT_MAP as V, PropertyCategory as a, getVariantPrefix as b, getPropertyCategory as c, stripInvalidColorStrings as d, PROPERTY_CATEGORY_MAP as e, formatSzWarnLocation as f, getCSSVariableName as g, SUGGESTION_MAP as h, isValidSzProp as i, SzDepthError as j, isForbiddenSzKey as k, normalizeArbitraryValue as l, normalizeArbitraryVariant as m, normalizeClassName as n, setSzWarnLocation as s, transform as t };
@@ -1497,6 +1497,36 @@ function handleSupports(supportsObj, prefix) {
1497
1497
  return classes;
1498
1498
  }
1499
1499
  let szTransformDepth = 0;
1500
+ let szWarnLocation;
1501
+ let szHintedProjectScan = false;
1502
+ function hintProjectScanOnce(location) {
1503
+ if (szHintedProjectScan || !location || process.env.CSSZYX_NO_PROJECT_SCAN_HINT === "1") {
1504
+ return;
1505
+ }
1506
+ szHintedProjectScan = true;
1507
+ console.warn(
1508
+ "[csszyx] Tip: run `npx @csszyx/cli check` to scan every file for sz key issues at once (dev warnings only surface files as you open them)."
1509
+ );
1510
+ }
1511
+ function setSzWarnLocation(location) {
1512
+ szWarnLocation = location;
1513
+ }
1514
+ function formatSzWarnLocation(file, line, rootDir) {
1515
+ let rel = file;
1516
+ if (rootDir) {
1517
+ let end = rootDir.length;
1518
+ while (end > 0 && (rootDir[end - 1] === "/" || rootDir[end - 1] === "\\")) {
1519
+ end--;
1520
+ }
1521
+ const root = rootDir.slice(0, end);
1522
+ if (file === root) {
1523
+ rel = file;
1524
+ } else if (file.startsWith(`${root}/`) || file.startsWith(`${root}\\`)) {
1525
+ rel = file.slice(root.length + 1);
1526
+ }
1527
+ }
1528
+ return line === void 0 ? rel : `${rel}:${line}`;
1529
+ }
1500
1530
  function transform(szProp, prefix = "", mangleMap) {
1501
1531
  if (!szProp || typeof szProp !== "object") {
1502
1532
  return { className: "", attributes: {} };
@@ -2341,16 +2371,18 @@ function transformImpl(szProp, prefix, mangleMap) {
2341
2371
  KNOWN_VARIANTS.has(rawKey) || // Parametric/scope variants (group, peer, has, not, data, aria, supports)
2342
2372
  SPECIAL_VARIANTS.has(rawKey) || rawKey === "min" || rawKey === "max";
2343
2373
  if (!isKnown) {
2374
+ const at = szWarnLocation ? ` at ${szWarnLocation}` : "";
2344
2375
  const suggestion = SUGGESTION_MAP[rawKey];
2345
2376
  if (suggestion) {
2346
2377
  console.warn(
2347
- `[csszyx] Use the canonical key "${suggestion}" instead of "${rawKey}".`
2378
+ `[csszyx] Use the canonical key "${suggestion}" instead of "${rawKey}"${at}.`
2348
2379
  );
2349
2380
  } else {
2350
2381
  console.warn(
2351
- `[csszyx] Unknown property "${rawKey}" in sz prop. This will be ignored. Check for typos.`
2382
+ `[csszyx] Unknown property "${rawKey}" in sz prop${at}. This will be ignored. Check for typos.`
2352
2383
  );
2353
2384
  }
2385
+ hintProjectScanOnce(szWarnLocation);
2354
2386
  }
2355
2387
  }
2356
2388
  if (value === true) {
@@ -2479,6 +2511,7 @@ exports.SPECIAL_VARIANTS = SPECIAL_VARIANTS;
2479
2511
  exports.SUGGESTION_MAP = SUGGESTION_MAP;
2480
2512
  exports.SzDepthError = SzDepthError;
2481
2513
  exports.VARIANT_MAP = VARIANT_MAP;
2514
+ exports.formatSzWarnLocation = formatSzWarnLocation;
2482
2515
  exports.getCSSVariableName = getCSSVariableName;
2483
2516
  exports.getPropertyCategory = getPropertyCategory;
2484
2517
  exports.getVariantPrefix = getVariantPrefix;
@@ -2487,5 +2520,6 @@ exports.isValidSzProp = isValidSzProp;
2487
2520
  exports.normalizeArbitraryValue = normalizeArbitraryValue;
2488
2521
  exports.normalizeArbitraryVariant = normalizeArbitraryVariant;
2489
2522
  exports.normalizeClassName = normalizeClassName;
2523
+ exports.setSzWarnLocation = setSzWarnLocation;
2490
2524
  exports.stripInvalidColorStrings = stripInvalidColorStrings;
2491
2525
  exports.transform = transform;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const transformCore = require('./shared/compiler.BCOOXYBh.cjs');
3
+ const transformCore = require('./shared/compiler.mibv6qPF.cjs');
4
4
 
5
5
 
6
6
 
@@ -13,10 +13,12 @@ exports.SPECIAL_VARIANTS = transformCore.SPECIAL_VARIANTS;
13
13
  exports.SUGGESTION_MAP = transformCore.SUGGESTION_MAP;
14
14
  exports.SzDepthError = transformCore.SzDepthError;
15
15
  exports.VARIANT_MAP = transformCore.VARIANT_MAP;
16
+ exports.formatSzWarnLocation = transformCore.formatSzWarnLocation;
16
17
  exports.getVariantPrefix = transformCore.getVariantPrefix;
17
18
  exports.isForbiddenSzKey = transformCore.isForbiddenSzKey;
18
19
  exports.isValidSzProp = transformCore.isValidSzProp;
19
20
  exports.normalizeArbitraryValue = transformCore.normalizeArbitraryValue;
20
21
  exports.normalizeArbitraryVariant = transformCore.normalizeArbitraryVariant;
21
22
  exports.normalizeClassName = transformCore.normalizeClassName;
23
+ exports.setSzWarnLocation = transformCore.setSzWarnLocation;
22
24
  exports.transform = transformCore.transform;
@@ -104,6 +104,28 @@ declare function normalizeArbitraryValue(value: string): string;
104
104
  * @returns the mapped or kebab-cased variant prefix
105
105
  */
106
106
  declare function getVariantPrefix(key: string): string;
107
+ /**
108
+ * Set (or clear, with `undefined`) the source location appended to the dev-mode
109
+ * unknown-property warning. Called by the build engines (oxc/babel) around each
110
+ * sz attribute; a balanced clear MUST follow so the location never leaks to an
111
+ * unrelated later transform.
112
+ *
113
+ * @param location - `relativePath:line` (or `relativePath`) to attribute the
114
+ * warning to, or `undefined` to clear.
115
+ */
116
+ declare function setSzWarnLocation(location: string | undefined): void;
117
+ /**
118
+ * Render a `relativePath:line` location string for the unknown-property warning,
119
+ * relative to the project root when one is known. Avoids a `node:path` dependency
120
+ * (this module is also browser-bundled) with a plain prefix strip — good enough
121
+ * for a human-facing diagnostic.
122
+ *
123
+ * @param file - the source filename (typically absolute, as the bundler gives it).
124
+ * @param line - 1-based line of the sz prop, or undefined to omit it.
125
+ * @param rootDir - project root to relativize against, or undefined to keep `file`.
126
+ * @returns `relativePath:line`, `relativePath`, or the raw filename.
127
+ */
128
+ declare function formatSzWarnLocation(file: string, line: number | undefined, rootDir: string | undefined): string;
107
129
  /**
108
130
  * Transform an sz object into a className string plus any attributes, bounding
109
131
  * recursion depth via {@link szTransformDepth}.
@@ -129,5 +151,5 @@ declare function isValidSzProp(szProp: unknown): szProp is SzObject;
129
151
  */
130
152
  declare function normalizeClassName(className: string): string;
131
153
 
132
- export { BOOLEAN_SHORTHANDS, KNOWN_VARIANTS, MAX_SZ_DEPTH, PROPERTY_MAP, REMOVED_BOOLEAN_SUGAR, SPECIAL_VARIANTS, SUGGESTION_MAP, SzDepthError, VARIANT_MAP, getVariantPrefix, isForbiddenSzKey, isValidSzProp, normalizeArbitraryValue, normalizeArbitraryVariant, normalizeClassName, transform };
154
+ export { BOOLEAN_SHORTHANDS, KNOWN_VARIANTS, MAX_SZ_DEPTH, PROPERTY_MAP, REMOVED_BOOLEAN_SUGAR, SPECIAL_VARIANTS, SUGGESTION_MAP, SzDepthError, VARIANT_MAP, formatSzWarnLocation, getVariantPrefix, isForbiddenSzKey, isValidSzProp, normalizeArbitraryValue, normalizeArbitraryVariant, normalizeClassName, setSzWarnLocation, transform };
133
155
  export type { ReadonlySzObject, ReadonlySzValue, SzObject, SzValue, TransformResult };
@@ -104,6 +104,28 @@ declare function normalizeArbitraryValue(value: string): string;
104
104
  * @returns the mapped or kebab-cased variant prefix
105
105
  */
106
106
  declare function getVariantPrefix(key: string): string;
107
+ /**
108
+ * Set (or clear, with `undefined`) the source location appended to the dev-mode
109
+ * unknown-property warning. Called by the build engines (oxc/babel) around each
110
+ * sz attribute; a balanced clear MUST follow so the location never leaks to an
111
+ * unrelated later transform.
112
+ *
113
+ * @param location - `relativePath:line` (or `relativePath`) to attribute the
114
+ * warning to, or `undefined` to clear.
115
+ */
116
+ declare function setSzWarnLocation(location: string | undefined): void;
117
+ /**
118
+ * Render a `relativePath:line` location string for the unknown-property warning,
119
+ * relative to the project root when one is known. Avoids a `node:path` dependency
120
+ * (this module is also browser-bundled) with a plain prefix strip — good enough
121
+ * for a human-facing diagnostic.
122
+ *
123
+ * @param file - the source filename (typically absolute, as the bundler gives it).
124
+ * @param line - 1-based line of the sz prop, or undefined to omit it.
125
+ * @param rootDir - project root to relativize against, or undefined to keep `file`.
126
+ * @returns `relativePath:line`, `relativePath`, or the raw filename.
127
+ */
128
+ declare function formatSzWarnLocation(file: string, line: number | undefined, rootDir: string | undefined): string;
107
129
  /**
108
130
  * Transform an sz object into a className string plus any attributes, bounding
109
131
  * recursion depth via {@link szTransformDepth}.
@@ -129,5 +151,5 @@ declare function isValidSzProp(szProp: unknown): szProp is SzObject;
129
151
  */
130
152
  declare function normalizeClassName(className: string): string;
131
153
 
132
- export { BOOLEAN_SHORTHANDS, KNOWN_VARIANTS, MAX_SZ_DEPTH, PROPERTY_MAP, REMOVED_BOOLEAN_SUGAR, SPECIAL_VARIANTS, SUGGESTION_MAP, SzDepthError, VARIANT_MAP, getVariantPrefix, isForbiddenSzKey, isValidSzProp, normalizeArbitraryValue, normalizeArbitraryVariant, normalizeClassName, transform };
154
+ export { BOOLEAN_SHORTHANDS, KNOWN_VARIANTS, MAX_SZ_DEPTH, PROPERTY_MAP, REMOVED_BOOLEAN_SUGAR, SPECIAL_VARIANTS, SUGGESTION_MAP, SzDepthError, VARIANT_MAP, formatSzWarnLocation, getVariantPrefix, isForbiddenSzKey, isValidSzProp, normalizeArbitraryValue, normalizeArbitraryVariant, normalizeClassName, setSzWarnLocation, transform };
133
155
  export type { ReadonlySzObject, ReadonlySzValue, SzObject, SzValue, TransformResult };
@@ -1 +1 @@
1
- export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, e as SUGGESTION_MAP, f as SzDepthError, V as VARIANT_MAP, b as getVariantPrefix, h as isForbiddenSzKey, i as isValidSzProp, j as normalizeArbitraryValue, k as normalizeArbitraryVariant, n as normalizeClassName, t as transform } from './shared/compiler.DGInsDP8.mjs';
1
+ export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, j as SzDepthError, V as VARIANT_MAP, f as formatSzWarnLocation, b as getVariantPrefix, k as isForbiddenSzKey, i as isValidSzProp, l as normalizeArbitraryValue, m as normalizeArbitraryVariant, n as normalizeClassName, s as setSzWarnLocation, t as transform } from './shared/compiler.CghwJ6p5.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/compiler",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "Core compiler and transformation logic for csszyx",
5
5
  "keywords": [
6
6
  "csszyx",
@@ -65,7 +65,7 @@
65
65
  "@babel/types": "^7.23.6",
66
66
  "magic-string": "0.30.21",
67
67
  "oxc-parser": "0.131.0",
68
- "@csszyx/core": "0.10.6"
68
+ "@csszyx/core": "0.10.8"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__core": "^7.20.5",