@bamboocss/generator 1.41.0 → 1.42.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.cjs CHANGED
@@ -2338,64 +2338,17 @@ const generateTypesEntry = (ctx) => {
2338
2338
  //#region src/artifacts/types/prop-types.ts
2339
2339
  function generatePropTypes(ctx) {
2340
2340
  const { utility } = ctx;
2341
- const result = [
2342
- outdent.outdent`
2341
+ const result = [outdent.outdent`
2343
2342
  ${ctx.file.importType("ConditionalValue", "./conditions")}
2344
2343
  ${ctx.file.importType("CssProperties", "./system-types")}
2345
2344
  ${ctx.file.importType("Tokens", "../tokens/index")}
2346
- `,
2347
- outdent.outdent`
2348
- /**
2349
- * A property's own keywords, without the open \`string\` csstype ends every property with.
2350
- *
2351
- * That trailing \`(string & {})\` is what makes \`color: 'mutedd'\` type-check: it is a
2352
- * string, so it is a colour. Removing it leaves what the property actually enumerates —
2353
- * \`transparent\`, \`currentColor\`, every named colour — which is what
2354
- * \`strictTokens: 'unknown-tokens'\` keeps.
2355
- *
2356
- * \`string extends T\` is the test, so the wide member goes and the literal ones stay. The
2357
- * second branch is for the *boxed* \`String\`, which \`Properties<String | Number>\` puts on
2358
- * every length-taking property and which is not assignable to \`string\` — so it survives the
2359
- * first test and admits every string on its own. \`Number\` is deliberately kept: a number
2360
- * cannot be a misspelled token path.
2361
- */
2362
- export type KnownKeywords<T> =
2363
- T extends string ? (string extends T ? never : T)
2364
- : T extends String ? never
2365
- : T
2366
- `,
2367
- "export interface UtilityValues {"
2368
- ];
2345
+ `, "export interface UtilityValues {"];
2369
2346
  const types = utility.getTypes();
2370
2347
  for (const [prop, values] of types.entries()) result.push(`\t${prop}: ${values.join(" | ")};`);
2371
2348
  result.push("}", "\n");
2372
2349
  return outdent.outdent`
2373
2350
  ${result.join("\n")}
2374
2351
 
2375
- /**
2376
- * Values whose *shape* says they are CSS rather than a token path.
2377
- *
2378
- * A token path is a bare identifier, possibly dotted. Anything that starts with a digit, a
2379
- * dot-digit, \`#\` or \`-\`, or that contains a space, a comma or a call, cannot be one — so
2380
- * these stay allowed under \`strictTokens: 'unknown-tokens'\` while \`'mutedd'\` does not.
2381
- *
2382
- * Constant, and not parameterised by the token union: a template literal distributes over a
2383
- * union in any placeholder, so a shape built from \`\${Token}\` would multiply the property's
2384
- * union by the size of the palette. These add seven members whatever the theme contains — see
2385
- * \`WithModifier\` below for what the other arrangement costs.
2386
- *
2387
- * The ambiguity this cannot resolve is a typo that is also a plausible value: \`'2xll'\` starts
2388
- * with a digit exactly as \`'2rem'\` does, and passes.
2389
- */
2390
- export type CssValueShape =
2391
- | \`\${number}\${string}\`
2392
- | \`.\${number}\${string}\`
2393
- | \`#\${string}\`
2394
- | \`-\${string}\`
2395
- | \`\${string} \${string}\`
2396
- | \`\${string},\${string}\`
2397
- | \`\${string}(\${string})\`
2398
-
2399
2352
  type ImportantMark = "!" | "!important"
2400
2353
  type WhitespaceImportant = \` \${ImportantMark}\`
2401
2354
  type Important = ImportantMark | WhitespaceImportant
@@ -2426,10 +2379,10 @@ function generatePropTypes(ctx) {
2426
2379
  type Modifier = "/" | "!" | " !"
2427
2380
  /**
2428
2381
  * \`Extract\`, not a \`[T] extends [string]\` test, which was the same thing until a utility
2429
- * carried keywords beside its tokens: \`KnownKeywords\` keeps \`Number\` deliberately, and that
2430
- * one non-string member turned every modifier form off for the whole property. It rejected
2431
- * \`roundedBottom: 'lg!'\` while \`rounded: 'lg!'\` passed, purely because the two utilities are
2432
- * declared differently — both emit \`var(--radii-lg) !important\`.
2382
+ * declared a non-string member. One of those turned every modifier form off for the whole
2383
+ * property: \`roundedBottom: 'lg!'\` was rejected while \`rounded: 'lg!'\` passed, decided by
2384
+ * nothing but how the two utilities happen to be declared — both emit
2385
+ * \`var(--radii-lg) !important\`.
2433
2386
  *
2434
2387
  * Filtering costs nothing the test did not: the same string members distribute either way,
2435
2388
  * and \`Extract\` of no strings is \`never\`, exactly what the false branch returned.
@@ -2492,7 +2445,7 @@ function generateStyleProps(ctx) {
2492
2445
  const cssVars = (0, _bamboocss_shared.unionType)(ctx.globalVars.vars);
2493
2446
  return outdent.default`
2494
2447
  ${ctx.file.importType("ConditionalValue", "./conditions")}
2495
- ${ctx.file.importType("CssValueShape, KnownKeywords, OnlyKnown, UtilityValues, WithEscapeHatch", "./prop-type")}
2448
+ ${ctx.file.importType("OnlyKnown, UtilityValues, WithEscapeHatch", "./prop-type")}
2496
2449
  ${ctx.file.importType("CssProperties", "./system-types")}
2497
2450
  ${ctx.file.importType("Token", "../tokens/index")}
2498
2451
 
@@ -2511,93 +2464,38 @@ function generateStyleProps(ctx) {
2511
2464
  const prop = ctx.utility.shorthands.get(key) ?? key;
2512
2465
  const union = [];
2513
2466
  const cssFallback = _bamboocss_is_valid_prop.allCssProperties.includes(prop) ? `CssProperties["${prop}"]` : "";
2514
- const knownFallback = ctx.config.strictTokens === "unknown-tokens" && !authorIdentProperties.has(prop) && cssFallback ? `KnownKeywords<${cssFallback}>` : "";
2515
- const gradedFallback = ctx.config.strictTokens === true ? "" : knownFallback || cssFallback;
2516
2467
  /**
2517
- * The token side, held out of the union under `'unknown-tokens'` so `restrict` can
2518
- * put it back inside `WithEscapeHatch`.
2468
+ * A utility's own values *extend* what the property accepts; they never replace it.
2469
+ *
2470
+ * Replacing is what `strictTokens` used to do, and it is why `transitionProperty` —
2471
+ * whose utility declares the sugar `common`, `colors`, `size`, `position` and
2472
+ * `background` — rejected `transitionProperty: 'color'`, a real css property name,
2473
+ * and suggested `'colors'`, which emits seven declarations instead of one. A utility
2474
+ * adds vocabulary to a property; it does not take the property's own away.
2519
2475
  *
2520
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of
2521
- * `T` — and csstype supplies `undefined` and a boxed `Number` — turns `'blue.300/40'`
2522
- * and `'blue.300!'` off for the whole property. Wrapping the tokens alone is what
2523
- * keeps those working, and listing them in both places instead would repeat one of
2524
- * the largest members a property has.
2476
+ * So nothing here narrows any more. Both questions the narrowing answered are the
2477
+ * build's now, asked of the css grammar rather than of a union: whether a name
2478
+ * resolves (`unresolvedToken`) and whether a raw value is allowed (`strictValues`).
2525
2479
  */
2526
- let heldOutTokens = "";
2527
- const separateTokens = ctx.config.strictTokens === "unknown-tokens";
2528
2480
  if (propTypes.has(prop)) {
2529
- const tokenValue = `UtilityValues["${prop}"]`;
2530
- if (separateTokens) heldOutTokens = tokenValue;
2531
- const own = separateTokens ? "" : tokenValue;
2532
- if (strictPropertyList.has(key)) union.push([
2533
- own,
2534
- "CssVars",
2535
- knownFallback
2536
- ].filter(Boolean).join(" | "));
2481
+ const own = `UtilityValues["${prop}"]`;
2482
+ if (strictPropertyList.has(key)) union.push([own, "CssVars"].filter(Boolean).join(" | "));
2537
2483
  else union.push([
2538
2484
  own,
2539
2485
  "CssVars",
2540
- gradedFallback
2486
+ cssFallback
2541
2487
  ].filter(Boolean).join(" | "));
2542
- } else union.push([strictPropertyList.has(key) ? "CssVars" : "", knownFallback || cssFallback].filter(Boolean).join(" | "));
2488
+ } else union.push([strictPropertyList.has(key) ? "CssVars" : "", cssFallback].filter(Boolean).join(" | "));
2543
2489
  const filtered = union.filter(Boolean);
2544
2490
  if (!filtered.length) filtered.push("string | number");
2545
2491
  let comment = comments?.[prop] || "";
2546
2492
  if (ctx.utility.isDeprecated(prop)) comment = comment ? comment.replace("@see", "@deprecated\n@see") : "/** @deprecated */";
2547
- const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config, heldOutTokens)}`;
2493
+ const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config)}`;
2548
2494
  return " " + [comment, line].filter(Boolean).join("\n");
2549
2495
  }).join("\n")}
2550
2496
  }
2551
2497
  `;
2552
2498
  }
2553
- /**
2554
- * Properties whose values are identifiers the author invents, not values anything enumerates.
2555
- *
2556
- * `strictTokens: 'unknown-tokens'` rejects a bare identifier that names no token and no keyword,
2557
- * on the reasoning that nothing else is shaped like one. That reasoning stops at a property
2558
- * whose values *are* bare identifiers by design: a `@keyframes` name written in CSS rather than
2559
- * in `theme.keyframes`, a grid area, a counter, a container, a view-transition name, a font
2560
- * family, a property name in `transitionProperty`. csstype types all of these as open strings
2561
- * for the same reason, so there is nothing to check against and everything to reject wrongly.
2562
- *
2563
- * Left alone rather than narrowed, so they behave under this setting exactly as they do under
2564
- * the default. The cost is that a typo in one of them is not caught — which is what
2565
- * `strictTokens: true` is for.
2566
- *
2567
- * `content` is here because its values are quoted strings, and `''""''` is neither a keyword nor
2568
- * a shape this can recognise.
2569
- */
2570
- const authorIdentProperties = new Set([
2571
- "anchorName",
2572
- "anchorScope",
2573
- "animationName",
2574
- "animationTimeline",
2575
- "containerName",
2576
- "content",
2577
- "counterIncrement",
2578
- "counterReset",
2579
- "counterSet",
2580
- "fontFamily",
2581
- "fontPalette",
2582
- "gridArea",
2583
- "gridColumn",
2584
- "gridColumnEnd",
2585
- "gridColumnStart",
2586
- "gridRow",
2587
- "gridRowEnd",
2588
- "gridRowStart",
2589
- "gridTemplateAreas",
2590
- "listStyleType",
2591
- "page",
2592
- "positionAnchor",
2593
- "positionTryFallbacks",
2594
- "scrollTimelineName",
2595
- "timelineScope",
2596
- "transitionProperty",
2597
- "viewTimelineName",
2598
- "viewTransitionName",
2599
- "willChange"
2600
- ]);
2601
2499
  const strictPropertyList = new Set([
2602
2500
  "alignContent",
2603
2501
  "alignItems",
@@ -2662,24 +2560,8 @@ const strictPropertyList = new Set([
2662
2560
  "wordBreak",
2663
2561
  "writingMode"
2664
2562
  ]);
2665
- const restrict = (key, value, config, heldOutTokens = "") => {
2666
- if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${[heldOutTokens, value].filter(Boolean).join(" | ")}>>>`;
2667
- /**
2668
- * The escape hatch wraps the *tokens*, not the whole value.
2669
- *
2670
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of `T`
2671
- * turns the modifier forms off for the property entirely — and under this setting `T`
2672
- * carries csstype's keywords, which include `undefined` and boxed `Number`. Wrapping the
2673
- * whole union that way silently rejected `color: 'blue.300/40'` and `'blue.300!'`, which
2674
- * decorate a token and have nothing to do with raw values.
2675
- *
2676
- * `CssValueShape` is what keeps raw values writable without an escape hatch: the shapes a
2677
- * token path cannot have — a leading digit, `#` or `-`, or a space, comma or call anywhere.
2678
- * A bare identifier that names no token and no keyword matches none of them, which is the
2679
- * mistake this setting exists to catch.
2680
- */
2681
- if (config.strictTokens === "unknown-tokens") return `ConditionalValue<WithEscapeHatch<${heldOutTokens || "never"}> | ${value} | CssValueShape>`;
2682
- if (config.strictTokens) return `ConditionalValue<WithEscapeHatch<${value}>>`;
2563
+ const restrict = (key, value, config) => {
2564
+ if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${value}>>>`;
2683
2565
  return `ConditionalValue<${value} | AnyString>`;
2684
2566
  };
2685
2567
  //#endregion
@@ -4230,6 +4112,7 @@ var Generator = class extends _bamboocss_core.Context {
4230
4112
  content: css
4231
4113
  }) ?? css;
4232
4114
  this.assertNoUnresolvedTokens();
4115
+ this.reportRawValues();
4233
4116
  return css;
4234
4117
  };
4235
4118
  /**
@@ -4276,6 +4159,44 @@ var Generator = class extends _bamboocss_core.Context {
4276
4159
  * exists: those all run during extraction, before anything has been decoded. Every path
4277
4160
  * that emits css comes through `getCss`.
4278
4161
  */
4162
+ /**
4163
+ * Report every raw CSS value in the source, under `strictValues`.
4164
+ *
4165
+ * The policy is "everything goes through the theme", and the brackets are what make reaching
4166
+ * outside it visible: `fontSize: '[14px]'` says so in the source, where `fontSize: '14px'`
4167
+ * reads exactly like using the scale.
4168
+ *
4169
+ * Read off `decoder.atomic` — the styles the *source* produced — so a preset's own reset and
4170
+ * a config recipe are not held to a project's policy about its own code. That is the whole
4171
+ * reason this is a separate pass rather than a branch in `transform`, which sees both.
4172
+ *
4173
+ * A keyword is not a raw value; see `isRawValue` for why that distinction needs the grammar
4174
+ * and is what the type-level version of this setting could never draw.
4175
+ */
4176
+ reportRawValues = () => {
4177
+ if (!this.config.strictValues) return;
4178
+ const found = /* @__PURE__ */ new Map();
4179
+ for (const atom of this.decoder.atomic) {
4180
+ const { prop, value } = atom.entry;
4181
+ if (typeof prop !== "string" || typeof value !== "string" && typeof value !== "number") continue;
4182
+ const written = String(value);
4183
+ if (!this.utility.isRawValue(prop, written)) continue;
4184
+ const key = this.utility.resolveShorthand(prop);
4185
+ found.set(`${key}:${written}`, {
4186
+ prop: key,
4187
+ value: written
4188
+ });
4189
+ }
4190
+ if (!found.size) return;
4191
+ const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), ({ prop, value }) => `- \`${prop}: ${value}\` — write \`[${value}]\` to mean it.`), {
4192
+ limit: 25,
4193
+ unit: "value",
4194
+ separator: "\n"
4195
+ });
4196
+ const message = `${found.size} style value(s) are raw css rather than tokens:\n\n${detail}\n\n\`strictValues\` asks every value to come from the theme, so reaching outside it is visible in the source. Write \`[value]\` to mean one literally, or add it to your tokens.`;
4197
+ if (this.config.validation === "error") throw new _bamboocss_shared.BambooError("STRICT_VALUES", message);
4198
+ _bamboocss_logger.logger.warn("strict-values", message);
4199
+ };
4279
4200
  assertNoUnresolvedTokens = () => {
4280
4201
  if (this.config.unresolvedToken !== "error") return;
4281
4202
  const found = new Map(this.utility.unresolvedTokens);
@@ -4285,16 +4206,10 @@ var Generator = class extends _bamboocss_core.Context {
4285
4206
  if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
4286
4207
  const key = this.utility.resolveShorthand(prop);
4287
4208
  const bare = this.utility.bareTokenPath(key, value);
4288
- found.set(`${key}:${bare}`, {
4289
- prop: key,
4290
- value: bare,
4291
- category: this.utility.getTokenCategory(key)
4292
- });
4209
+ found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
4293
4210
  }
4294
4211
  if (!found.size) return;
4295
- const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), ({ prop, value, category }) => {
4296
- return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
4297
- }), {
4212
+ const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
4298
4213
  limit: 25,
4299
4214
  unit: "value",
4300
4215
  separator: "\n"
package/dist/index.d.cts CHANGED
@@ -176,6 +176,21 @@ declare class Generator extends Context {
176
176
  * exists: those all run during extraction, before anything has been decoded. Every path
177
177
  * that emits css comes through `getCss`.
178
178
  */
179
+ /**
180
+ * Report every raw CSS value in the source, under `strictValues`.
181
+ *
182
+ * The policy is "everything goes through the theme", and the brackets are what make reaching
183
+ * outside it visible: `fontSize: '[14px]'` says so in the source, where `fontSize: '14px'`
184
+ * reads exactly like using the scale.
185
+ *
186
+ * Read off `decoder.atomic` — the styles the *source* produced — so a preset's own reset and
187
+ * a config recipe are not held to a project's policy about its own code. That is the whole
188
+ * reason this is a separate pass rather than a branch in `transform`, which sees both.
189
+ *
190
+ * A keyword is not a raw value; see `isRawValue` for why that distinction needs the grammar
191
+ * and is what the type-level version of this setting could never draw.
192
+ */
193
+ reportRawValues: () => void;
179
194
  assertNoUnresolvedTokens: () => void;
180
195
  /**
181
196
  * Get CSS for a specific layer from the stylesheet
package/dist/index.d.mts CHANGED
@@ -176,6 +176,21 @@ declare class Generator extends Context {
176
176
  * exists: those all run during extraction, before anything has been decoded. Every path
177
177
  * that emits css comes through `getCss`.
178
178
  */
179
+ /**
180
+ * Report every raw CSS value in the source, under `strictValues`.
181
+ *
182
+ * The policy is "everything goes through the theme", and the brackets are what make reaching
183
+ * outside it visible: `fontSize: '[14px]'` says so in the source, where `fontSize: '14px'`
184
+ * reads exactly like using the scale.
185
+ *
186
+ * Read off `decoder.atomic` — the styles the *source* produced — so a preset's own reset and
187
+ * a config recipe are not held to a project's policy about its own code. That is the whole
188
+ * reason this is a separate pass rather than a branch in `transform`, which sees both.
189
+ *
190
+ * A keyword is not a raw value; see `isRawValue` for why that distinction needs the grammar
191
+ * and is what the type-level version of this setting could never draw.
192
+ */
193
+ reportRawValues: () => void;
179
194
  assertNoUnresolvedTokens: () => void;
180
195
  /**
181
196
  * Get CSS for a specific layer from the stylesheet
package/dist/index.mjs CHANGED
@@ -2312,64 +2312,17 @@ const generateTypesEntry = (ctx) => {
2312
2312
  //#region src/artifacts/types/prop-types.ts
2313
2313
  function generatePropTypes(ctx) {
2314
2314
  const { utility } = ctx;
2315
- const result = [
2316
- outdent`
2315
+ const result = [outdent`
2317
2316
  ${ctx.file.importType("ConditionalValue", "./conditions")}
2318
2317
  ${ctx.file.importType("CssProperties", "./system-types")}
2319
2318
  ${ctx.file.importType("Tokens", "../tokens/index")}
2320
- `,
2321
- outdent`
2322
- /**
2323
- * A property's own keywords, without the open \`string\` csstype ends every property with.
2324
- *
2325
- * That trailing \`(string & {})\` is what makes \`color: 'mutedd'\` type-check: it is a
2326
- * string, so it is a colour. Removing it leaves what the property actually enumerates —
2327
- * \`transparent\`, \`currentColor\`, every named colour — which is what
2328
- * \`strictTokens: 'unknown-tokens'\` keeps.
2329
- *
2330
- * \`string extends T\` is the test, so the wide member goes and the literal ones stay. The
2331
- * second branch is for the *boxed* \`String\`, which \`Properties<String | Number>\` puts on
2332
- * every length-taking property and which is not assignable to \`string\` — so it survives the
2333
- * first test and admits every string on its own. \`Number\` is deliberately kept: a number
2334
- * cannot be a misspelled token path.
2335
- */
2336
- export type KnownKeywords<T> =
2337
- T extends string ? (string extends T ? never : T)
2338
- : T extends String ? never
2339
- : T
2340
- `,
2341
- "export interface UtilityValues {"
2342
- ];
2319
+ `, "export interface UtilityValues {"];
2343
2320
  const types = utility.getTypes();
2344
2321
  for (const [prop, values] of types.entries()) result.push(`\t${prop}: ${values.join(" | ")};`);
2345
2322
  result.push("}", "\n");
2346
2323
  return outdent`
2347
2324
  ${result.join("\n")}
2348
2325
 
2349
- /**
2350
- * Values whose *shape* says they are CSS rather than a token path.
2351
- *
2352
- * A token path is a bare identifier, possibly dotted. Anything that starts with a digit, a
2353
- * dot-digit, \`#\` or \`-\`, or that contains a space, a comma or a call, cannot be one — so
2354
- * these stay allowed under \`strictTokens: 'unknown-tokens'\` while \`'mutedd'\` does not.
2355
- *
2356
- * Constant, and not parameterised by the token union: a template literal distributes over a
2357
- * union in any placeholder, so a shape built from \`\${Token}\` would multiply the property's
2358
- * union by the size of the palette. These add seven members whatever the theme contains — see
2359
- * \`WithModifier\` below for what the other arrangement costs.
2360
- *
2361
- * The ambiguity this cannot resolve is a typo that is also a plausible value: \`'2xll'\` starts
2362
- * with a digit exactly as \`'2rem'\` does, and passes.
2363
- */
2364
- export type CssValueShape =
2365
- | \`\${number}\${string}\`
2366
- | \`.\${number}\${string}\`
2367
- | \`#\${string}\`
2368
- | \`-\${string}\`
2369
- | \`\${string} \${string}\`
2370
- | \`\${string},\${string}\`
2371
- | \`\${string}(\${string})\`
2372
-
2373
2326
  type ImportantMark = "!" | "!important"
2374
2327
  type WhitespaceImportant = \` \${ImportantMark}\`
2375
2328
  type Important = ImportantMark | WhitespaceImportant
@@ -2400,10 +2353,10 @@ function generatePropTypes(ctx) {
2400
2353
  type Modifier = "/" | "!" | " !"
2401
2354
  /**
2402
2355
  * \`Extract\`, not a \`[T] extends [string]\` test, which was the same thing until a utility
2403
- * carried keywords beside its tokens: \`KnownKeywords\` keeps \`Number\` deliberately, and that
2404
- * one non-string member turned every modifier form off for the whole property. It rejected
2405
- * \`roundedBottom: 'lg!'\` while \`rounded: 'lg!'\` passed, purely because the two utilities are
2406
- * declared differently — both emit \`var(--radii-lg) !important\`.
2356
+ * declared a non-string member. One of those turned every modifier form off for the whole
2357
+ * property: \`roundedBottom: 'lg!'\` was rejected while \`rounded: 'lg!'\` passed, decided by
2358
+ * nothing but how the two utilities happen to be declared — both emit
2359
+ * \`var(--radii-lg) !important\`.
2407
2360
  *
2408
2361
  * Filtering costs nothing the test did not: the same string members distribute either way,
2409
2362
  * and \`Extract\` of no strings is \`never\`, exactly what the false branch returned.
@@ -2466,7 +2419,7 @@ function generateStyleProps(ctx) {
2466
2419
  const cssVars = unionType(ctx.globalVars.vars);
2467
2420
  return outdent$1`
2468
2421
  ${ctx.file.importType("ConditionalValue", "./conditions")}
2469
- ${ctx.file.importType("CssValueShape, KnownKeywords, OnlyKnown, UtilityValues, WithEscapeHatch", "./prop-type")}
2422
+ ${ctx.file.importType("OnlyKnown, UtilityValues, WithEscapeHatch", "./prop-type")}
2470
2423
  ${ctx.file.importType("CssProperties", "./system-types")}
2471
2424
  ${ctx.file.importType("Token", "../tokens/index")}
2472
2425
 
@@ -2485,93 +2438,38 @@ function generateStyleProps(ctx) {
2485
2438
  const prop = ctx.utility.shorthands.get(key) ?? key;
2486
2439
  const union = [];
2487
2440
  const cssFallback = allCssProperties.includes(prop) ? `CssProperties["${prop}"]` : "";
2488
- const knownFallback = ctx.config.strictTokens === "unknown-tokens" && !authorIdentProperties.has(prop) && cssFallback ? `KnownKeywords<${cssFallback}>` : "";
2489
- const gradedFallback = ctx.config.strictTokens === true ? "" : knownFallback || cssFallback;
2490
2441
  /**
2491
- * The token side, held out of the union under `'unknown-tokens'` so `restrict` can
2492
- * put it back inside `WithEscapeHatch`.
2442
+ * A utility's own values *extend* what the property accepts; they never replace it.
2443
+ *
2444
+ * Replacing is what `strictTokens` used to do, and it is why `transitionProperty` —
2445
+ * whose utility declares the sugar `common`, `colors`, `size`, `position` and
2446
+ * `background` — rejected `transitionProperty: 'color'`, a real css property name,
2447
+ * and suggested `'colors'`, which emits seven declarations instead of one. A utility
2448
+ * adds vocabulary to a property; it does not take the property's own away.
2493
2449
  *
2494
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of
2495
- * `T` — and csstype supplies `undefined` and a boxed `Number` — turns `'blue.300/40'`
2496
- * and `'blue.300!'` off for the whole property. Wrapping the tokens alone is what
2497
- * keeps those working, and listing them in both places instead would repeat one of
2498
- * the largest members a property has.
2450
+ * So nothing here narrows any more. Both questions the narrowing answered are the
2451
+ * build's now, asked of the css grammar rather than of a union: whether a name
2452
+ * resolves (`unresolvedToken`) and whether a raw value is allowed (`strictValues`).
2499
2453
  */
2500
- let heldOutTokens = "";
2501
- const separateTokens = ctx.config.strictTokens === "unknown-tokens";
2502
2454
  if (propTypes.has(prop)) {
2503
- const tokenValue = `UtilityValues["${prop}"]`;
2504
- if (separateTokens) heldOutTokens = tokenValue;
2505
- const own = separateTokens ? "" : tokenValue;
2506
- if (strictPropertyList.has(key)) union.push([
2507
- own,
2508
- "CssVars",
2509
- knownFallback
2510
- ].filter(Boolean).join(" | "));
2455
+ const own = `UtilityValues["${prop}"]`;
2456
+ if (strictPropertyList.has(key)) union.push([own, "CssVars"].filter(Boolean).join(" | "));
2511
2457
  else union.push([
2512
2458
  own,
2513
2459
  "CssVars",
2514
- gradedFallback
2460
+ cssFallback
2515
2461
  ].filter(Boolean).join(" | "));
2516
- } else union.push([strictPropertyList.has(key) ? "CssVars" : "", knownFallback || cssFallback].filter(Boolean).join(" | "));
2462
+ } else union.push([strictPropertyList.has(key) ? "CssVars" : "", cssFallback].filter(Boolean).join(" | "));
2517
2463
  const filtered = union.filter(Boolean);
2518
2464
  if (!filtered.length) filtered.push("string | number");
2519
2465
  let comment = comments?.[prop] || "";
2520
2466
  if (ctx.utility.isDeprecated(prop)) comment = comment ? comment.replace("@see", "@deprecated\n@see") : "/** @deprecated */";
2521
- const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config, heldOutTokens)}`;
2467
+ const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config)}`;
2522
2468
  return " " + [comment, line].filter(Boolean).join("\n");
2523
2469
  }).join("\n")}
2524
2470
  }
2525
2471
  `;
2526
2472
  }
2527
- /**
2528
- * Properties whose values are identifiers the author invents, not values anything enumerates.
2529
- *
2530
- * `strictTokens: 'unknown-tokens'` rejects a bare identifier that names no token and no keyword,
2531
- * on the reasoning that nothing else is shaped like one. That reasoning stops at a property
2532
- * whose values *are* bare identifiers by design: a `@keyframes` name written in CSS rather than
2533
- * in `theme.keyframes`, a grid area, a counter, a container, a view-transition name, a font
2534
- * family, a property name in `transitionProperty`. csstype types all of these as open strings
2535
- * for the same reason, so there is nothing to check against and everything to reject wrongly.
2536
- *
2537
- * Left alone rather than narrowed, so they behave under this setting exactly as they do under
2538
- * the default. The cost is that a typo in one of them is not caught — which is what
2539
- * `strictTokens: true` is for.
2540
- *
2541
- * `content` is here because its values are quoted strings, and `''""''` is neither a keyword nor
2542
- * a shape this can recognise.
2543
- */
2544
- const authorIdentProperties = new Set([
2545
- "anchorName",
2546
- "anchorScope",
2547
- "animationName",
2548
- "animationTimeline",
2549
- "containerName",
2550
- "content",
2551
- "counterIncrement",
2552
- "counterReset",
2553
- "counterSet",
2554
- "fontFamily",
2555
- "fontPalette",
2556
- "gridArea",
2557
- "gridColumn",
2558
- "gridColumnEnd",
2559
- "gridColumnStart",
2560
- "gridRow",
2561
- "gridRowEnd",
2562
- "gridRowStart",
2563
- "gridTemplateAreas",
2564
- "listStyleType",
2565
- "page",
2566
- "positionAnchor",
2567
- "positionTryFallbacks",
2568
- "scrollTimelineName",
2569
- "timelineScope",
2570
- "transitionProperty",
2571
- "viewTimelineName",
2572
- "viewTransitionName",
2573
- "willChange"
2574
- ]);
2575
2473
  const strictPropertyList = new Set([
2576
2474
  "alignContent",
2577
2475
  "alignItems",
@@ -2636,24 +2534,8 @@ const strictPropertyList = new Set([
2636
2534
  "wordBreak",
2637
2535
  "writingMode"
2638
2536
  ]);
2639
- const restrict = (key, value, config, heldOutTokens = "") => {
2640
- if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${[heldOutTokens, value].filter(Boolean).join(" | ")}>>>`;
2641
- /**
2642
- * The escape hatch wraps the *tokens*, not the whole value.
2643
- *
2644
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of `T`
2645
- * turns the modifier forms off for the property entirely — and under this setting `T`
2646
- * carries csstype's keywords, which include `undefined` and boxed `Number`. Wrapping the
2647
- * whole union that way silently rejected `color: 'blue.300/40'` and `'blue.300!'`, which
2648
- * decorate a token and have nothing to do with raw values.
2649
- *
2650
- * `CssValueShape` is what keeps raw values writable without an escape hatch: the shapes a
2651
- * token path cannot have — a leading digit, `#` or `-`, or a space, comma or call anywhere.
2652
- * A bare identifier that names no token and no keyword matches none of them, which is the
2653
- * mistake this setting exists to catch.
2654
- */
2655
- if (config.strictTokens === "unknown-tokens") return `ConditionalValue<WithEscapeHatch<${heldOutTokens || "never"}> | ${value} | CssValueShape>`;
2656
- if (config.strictTokens) return `ConditionalValue<WithEscapeHatch<${value}>>`;
2537
+ const restrict = (key, value, config) => {
2538
+ if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${value}>>>`;
2657
2539
  return `ConditionalValue<${value} | AnyString>`;
2658
2540
  };
2659
2541
  //#endregion
@@ -4204,6 +4086,7 @@ var Generator = class extends Context {
4204
4086
  content: css
4205
4087
  }) ?? css;
4206
4088
  this.assertNoUnresolvedTokens();
4089
+ this.reportRawValues();
4207
4090
  return css;
4208
4091
  };
4209
4092
  /**
@@ -4250,6 +4133,44 @@ var Generator = class extends Context {
4250
4133
  * exists: those all run during extraction, before anything has been decoded. Every path
4251
4134
  * that emits css comes through `getCss`.
4252
4135
  */
4136
+ /**
4137
+ * Report every raw CSS value in the source, under `strictValues`.
4138
+ *
4139
+ * The policy is "everything goes through the theme", and the brackets are what make reaching
4140
+ * outside it visible: `fontSize: '[14px]'` says so in the source, where `fontSize: '14px'`
4141
+ * reads exactly like using the scale.
4142
+ *
4143
+ * Read off `decoder.atomic` — the styles the *source* produced — so a preset's own reset and
4144
+ * a config recipe are not held to a project's policy about its own code. That is the whole
4145
+ * reason this is a separate pass rather than a branch in `transform`, which sees both.
4146
+ *
4147
+ * A keyword is not a raw value; see `isRawValue` for why that distinction needs the grammar
4148
+ * and is what the type-level version of this setting could never draw.
4149
+ */
4150
+ reportRawValues = () => {
4151
+ if (!this.config.strictValues) return;
4152
+ const found = /* @__PURE__ */ new Map();
4153
+ for (const atom of this.decoder.atomic) {
4154
+ const { prop, value } = atom.entry;
4155
+ if (typeof prop !== "string" || typeof value !== "string" && typeof value !== "number") continue;
4156
+ const written = String(value);
4157
+ if (!this.utility.isRawValue(prop, written)) continue;
4158
+ const key = this.utility.resolveShorthand(prop);
4159
+ found.set(`${key}:${written}`, {
4160
+ prop: key,
4161
+ value: written
4162
+ });
4163
+ }
4164
+ if (!found.size) return;
4165
+ const detail = truncateList(Array.from(found.values(), ({ prop, value }) => `- \`${prop}: ${value}\` — write \`[${value}]\` to mean it.`), {
4166
+ limit: 25,
4167
+ unit: "value",
4168
+ separator: "\n"
4169
+ });
4170
+ const message = `${found.size} style value(s) are raw css rather than tokens:\n\n${detail}\n\n\`strictValues\` asks every value to come from the theme, so reaching outside it is visible in the source. Write \`[value]\` to mean one literally, or add it to your tokens.`;
4171
+ if (this.config.validation === "error") throw new BambooError("STRICT_VALUES", message);
4172
+ logger.warn("strict-values", message);
4173
+ };
4253
4174
  assertNoUnresolvedTokens = () => {
4254
4175
  if (this.config.unresolvedToken !== "error") return;
4255
4176
  const found = new Map(this.utility.unresolvedTokens);
@@ -4259,16 +4180,10 @@ var Generator = class extends Context {
4259
4180
  if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
4260
4181
  const key = this.utility.resolveShorthand(prop);
4261
4182
  const bare = this.utility.bareTokenPath(key, value);
4262
- found.set(`${key}:${bare}`, {
4263
- prop: key,
4264
- value: bare,
4265
- category: this.utility.getTokenCategory(key)
4266
- });
4183
+ found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
4267
4184
  }
4268
4185
  if (!found.size) return;
4269
- const detail = truncateList(Array.from(found.values(), ({ prop, value, category }) => {
4270
- return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
4271
- }), {
4186
+ const detail = truncateList(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
4272
4187
  limit: 25,
4273
4188
  unit: "value",
4274
4189
  separator: "\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/generator",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "The css generator for css bamboo",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -38,12 +38,12 @@
38
38
  "pluralize": "8.0.0",
39
39
  "postcss": "8.5.26",
40
40
  "ts-pattern": "5.9.0",
41
- "@bamboocss/core": "1.41.0",
42
- "@bamboocss/logger": "1.41.0",
43
- "@bamboocss/token-dictionary": "1.41.0",
44
- "@bamboocss/is-valid-prop": "^1.41.0",
45
- "@bamboocss/types": "1.41.0",
46
- "@bamboocss/shared": "1.41.0"
41
+ "@bamboocss/core": "1.42.0",
42
+ "@bamboocss/is-valid-prop": "^1.42.0",
43
+ "@bamboocss/logger": "1.42.0",
44
+ "@bamboocss/shared": "1.42.0",
45
+ "@bamboocss/token-dictionary": "1.42.0",
46
+ "@bamboocss/types": "1.42.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/pluralize": "0.0.33"