@bamboocss/generator 1.41.1 → 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,106 +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
- /**
2517
- * The token side, held out of the union under `'unknown-tokens'` so `restrict` can
2518
- * put it back inside `WithEscapeHatch`.
2519
- *
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.
2525
- */
2526
- let heldOutTokens = "";
2527
- const separateTokens = ctx.config.strictTokens === "unknown-tokens";
2528
2467
  /**
2529
- * The keywords go with them, so a mark does not depend on being a token.
2468
+ * A utility's own values *extend* what the property accepts; they never replace it.
2530
2469
  *
2531
- * Only what `WithEscapeHatch` wraps carries `!` and `/`, and that used to be the
2532
- * tokens alone — so `shadow: 'none!'` was an error while `shadow: 'none'` and
2533
- * `color: 'red.300!'` were both fine, decided by whether the value happened to be a
2534
- * token rather than by anything the author can see.
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.
2535
2475
  *
2536
- * Held out only where there is a narrowed list to hold: an `authorIdentProperty`
2537
- * keeps csstype's open string, which must stay outside the wrapper or the property
2538
- * accepts nothing but marked values.
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`).
2539
2479
  */
2540
- const heldOutKeywords = separateTokens ? knownFallback : "";
2541
2480
  if (propTypes.has(prop)) {
2542
- const tokenValue = `UtilityValues["${prop}"]`;
2543
- if (separateTokens) heldOutTokens = tokenValue;
2544
- const own = separateTokens ? "" : tokenValue;
2545
- if (strictPropertyList.has(key)) union.push([
2546
- own,
2547
- "CssVars",
2548
- heldOutKeywords ? "" : knownFallback
2549
- ].filter(Boolean).join(" | "));
2481
+ const own = `UtilityValues["${prop}"]`;
2482
+ if (strictPropertyList.has(key)) union.push([own, "CssVars"].filter(Boolean).join(" | "));
2550
2483
  else union.push([
2551
2484
  own,
2552
2485
  "CssVars",
2553
- heldOutKeywords ? "" : gradedFallback
2486
+ cssFallback
2554
2487
  ].filter(Boolean).join(" | "));
2555
- } else union.push([strictPropertyList.has(key) ? "CssVars" : "", heldOutKeywords ? "" : knownFallback || cssFallback].filter(Boolean).join(" | "));
2488
+ } else union.push([strictPropertyList.has(key) ? "CssVars" : "", cssFallback].filter(Boolean).join(" | "));
2556
2489
  const filtered = union.filter(Boolean);
2557
2490
  if (!filtered.length) filtered.push("string | number");
2558
2491
  let comment = comments?.[prop] || "";
2559
2492
  if (ctx.utility.isDeprecated(prop)) comment = comment ? comment.replace("@see", "@deprecated\n@see") : "/** @deprecated */";
2560
- const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config, [heldOutTokens, heldOutKeywords].filter(Boolean).join(" | "))}`;
2493
+ const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config)}`;
2561
2494
  return " " + [comment, line].filter(Boolean).join("\n");
2562
2495
  }).join("\n")}
2563
2496
  }
2564
2497
  `;
2565
2498
  }
2566
- /**
2567
- * Properties whose values are identifiers the author invents, not values anything enumerates.
2568
- *
2569
- * `strictTokens: 'unknown-tokens'` rejects a bare identifier that names no token and no keyword,
2570
- * on the reasoning that nothing else is shaped like one. That reasoning stops at a property
2571
- * whose values *are* bare identifiers by design: a `@keyframes` name written in CSS rather than
2572
- * in `theme.keyframes`, a grid area, a counter, a container, a view-transition name, a font
2573
- * family, a property name in `transitionProperty`. csstype types all of these as open strings
2574
- * for the same reason, so there is nothing to check against and everything to reject wrongly.
2575
- *
2576
- * Left alone rather than narrowed, so they behave under this setting exactly as they do under
2577
- * the default. The cost is that a typo in one of them is not caught — which is what
2578
- * `strictTokens: true` is for.
2579
- *
2580
- * `content` is here because its values are quoted strings, and `''""''` is neither a keyword nor
2581
- * a shape this can recognise.
2582
- */
2583
- const authorIdentProperties = new Set([
2584
- "anchorName",
2585
- "anchorScope",
2586
- "animationName",
2587
- "animationTimeline",
2588
- "containerName",
2589
- "content",
2590
- "counterIncrement",
2591
- "counterReset",
2592
- "counterSet",
2593
- "fontFamily",
2594
- "fontPalette",
2595
- "gridArea",
2596
- "gridColumn",
2597
- "gridColumnEnd",
2598
- "gridColumnStart",
2599
- "gridRow",
2600
- "gridRowEnd",
2601
- "gridRowStart",
2602
- "gridTemplateAreas",
2603
- "listStyleType",
2604
- "page",
2605
- "positionAnchor",
2606
- "positionTryFallbacks",
2607
- "scrollTimelineName",
2608
- "timelineScope",
2609
- "transitionProperty",
2610
- "viewTimelineName",
2611
- "viewTransitionName",
2612
- "willChange"
2613
- ]);
2614
2499
  const strictPropertyList = new Set([
2615
2500
  "alignContent",
2616
2501
  "alignItems",
@@ -2675,24 +2560,8 @@ const strictPropertyList = new Set([
2675
2560
  "wordBreak",
2676
2561
  "writingMode"
2677
2562
  ]);
2678
- const restrict = (key, value, config, heldOutTokens = "") => {
2679
- if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${[heldOutTokens, value].filter(Boolean).join(" | ")}>>>`;
2680
- /**
2681
- * The escape hatch wraps the *tokens*, not the whole value.
2682
- *
2683
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of `T`
2684
- * turns the modifier forms off for the property entirely — and under this setting `T`
2685
- * carries csstype's keywords, which include `undefined` and boxed `Number`. Wrapping the
2686
- * whole union that way silently rejected `color: 'blue.300/40'` and `'blue.300!'`, which
2687
- * decorate a token and have nothing to do with raw values.
2688
- *
2689
- * `CssValueShape` is what keeps raw values writable without an escape hatch: the shapes a
2690
- * token path cannot have — a leading digit, `#` or `-`, or a space, comma or call anywhere.
2691
- * A bare identifier that names no token and no keyword matches none of them, which is the
2692
- * mistake this setting exists to catch.
2693
- */
2694
- if (config.strictTokens === "unknown-tokens") return `ConditionalValue<WithEscapeHatch<${heldOutTokens || "never"}> | ${value} | CssValueShape>`;
2695
- 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}>>>`;
2696
2565
  return `ConditionalValue<${value} | AnyString>`;
2697
2566
  };
2698
2567
  //#endregion
@@ -4243,6 +4112,7 @@ var Generator = class extends _bamboocss_core.Context {
4243
4112
  content: css
4244
4113
  }) ?? css;
4245
4114
  this.assertNoUnresolvedTokens();
4115
+ this.reportRawValues();
4246
4116
  return css;
4247
4117
  };
4248
4118
  /**
@@ -4289,6 +4159,44 @@ var Generator = class extends _bamboocss_core.Context {
4289
4159
  * exists: those all run during extraction, before anything has been decoded. Every path
4290
4160
  * that emits css comes through `getCss`.
4291
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
+ };
4292
4200
  assertNoUnresolvedTokens = () => {
4293
4201
  if (this.config.unresolvedToken !== "error") return;
4294
4202
  const found = new Map(this.utility.unresolvedTokens);
@@ -4298,16 +4206,10 @@ var Generator = class extends _bamboocss_core.Context {
4298
4206
  if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
4299
4207
  const key = this.utility.resolveShorthand(prop);
4300
4208
  const bare = this.utility.bareTokenPath(key, value);
4301
- found.set(`${key}:${bare}`, {
4302
- prop: key,
4303
- value: bare,
4304
- category: this.utility.getTokenCategory(key)
4305
- });
4209
+ found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
4306
4210
  }
4307
4211
  if (!found.size) return;
4308
- const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), ({ prop, value, category }) => {
4309
- return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
4310
- }), {
4212
+ const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
4311
4213
  limit: 25,
4312
4214
  unit: "value",
4313
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,106 +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
- /**
2491
- * The token side, held out of the union under `'unknown-tokens'` so `restrict` can
2492
- * put it back inside `WithEscapeHatch`.
2493
- *
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.
2499
- */
2500
- let heldOutTokens = "";
2501
- const separateTokens = ctx.config.strictTokens === "unknown-tokens";
2502
2441
  /**
2503
- * The keywords go with them, so a mark does not depend on being a token.
2442
+ * A utility's own values *extend* what the property accepts; they never replace it.
2504
2443
  *
2505
- * Only what `WithEscapeHatch` wraps carries `!` and `/`, and that used to be the
2506
- * tokens alone — so `shadow: 'none!'` was an error while `shadow: 'none'` and
2507
- * `color: 'red.300!'` were both fine, decided by whether the value happened to be a
2508
- * token rather than by anything the author can see.
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.
2509
2449
  *
2510
- * Held out only where there is a narrowed list to hold: an `authorIdentProperty`
2511
- * keeps csstype's open string, which must stay outside the wrapper or the property
2512
- * accepts nothing but marked values.
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`).
2513
2453
  */
2514
- const heldOutKeywords = separateTokens ? knownFallback : "";
2515
2454
  if (propTypes.has(prop)) {
2516
- const tokenValue = `UtilityValues["${prop}"]`;
2517
- if (separateTokens) heldOutTokens = tokenValue;
2518
- const own = separateTokens ? "" : tokenValue;
2519
- if (strictPropertyList.has(key)) union.push([
2520
- own,
2521
- "CssVars",
2522
- heldOutKeywords ? "" : knownFallback
2523
- ].filter(Boolean).join(" | "));
2455
+ const own = `UtilityValues["${prop}"]`;
2456
+ if (strictPropertyList.has(key)) union.push([own, "CssVars"].filter(Boolean).join(" | "));
2524
2457
  else union.push([
2525
2458
  own,
2526
2459
  "CssVars",
2527
- heldOutKeywords ? "" : gradedFallback
2460
+ cssFallback
2528
2461
  ].filter(Boolean).join(" | "));
2529
- } else union.push([strictPropertyList.has(key) ? "CssVars" : "", heldOutKeywords ? "" : knownFallback || cssFallback].filter(Boolean).join(" | "));
2462
+ } else union.push([strictPropertyList.has(key) ? "CssVars" : "", cssFallback].filter(Boolean).join(" | "));
2530
2463
  const filtered = union.filter(Boolean);
2531
2464
  if (!filtered.length) filtered.push("string | number");
2532
2465
  let comment = comments?.[prop] || "";
2533
2466
  if (ctx.utility.isDeprecated(prop)) comment = comment ? comment.replace("@see", "@deprecated\n@see") : "/** @deprecated */";
2534
- const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config, [heldOutTokens, heldOutKeywords].filter(Boolean).join(" | "))}`;
2467
+ const line = `${key}?: ${restrict(prop, filtered.filter(Boolean).join(" | "), ctx.config)}`;
2535
2468
  return " " + [comment, line].filter(Boolean).join("\n");
2536
2469
  }).join("\n")}
2537
2470
  }
2538
2471
  `;
2539
2472
  }
2540
- /**
2541
- * Properties whose values are identifiers the author invents, not values anything enumerates.
2542
- *
2543
- * `strictTokens: 'unknown-tokens'` rejects a bare identifier that names no token and no keyword,
2544
- * on the reasoning that nothing else is shaped like one. That reasoning stops at a property
2545
- * whose values *are* bare identifiers by design: a `@keyframes` name written in CSS rather than
2546
- * in `theme.keyframes`, a grid area, a counter, a container, a view-transition name, a font
2547
- * family, a property name in `transitionProperty`. csstype types all of these as open strings
2548
- * for the same reason, so there is nothing to check against and everything to reject wrongly.
2549
- *
2550
- * Left alone rather than narrowed, so they behave under this setting exactly as they do under
2551
- * the default. The cost is that a typo in one of them is not caught — which is what
2552
- * `strictTokens: true` is for.
2553
- *
2554
- * `content` is here because its values are quoted strings, and `''""''` is neither a keyword nor
2555
- * a shape this can recognise.
2556
- */
2557
- const authorIdentProperties = new Set([
2558
- "anchorName",
2559
- "anchorScope",
2560
- "animationName",
2561
- "animationTimeline",
2562
- "containerName",
2563
- "content",
2564
- "counterIncrement",
2565
- "counterReset",
2566
- "counterSet",
2567
- "fontFamily",
2568
- "fontPalette",
2569
- "gridArea",
2570
- "gridColumn",
2571
- "gridColumnEnd",
2572
- "gridColumnStart",
2573
- "gridRow",
2574
- "gridRowEnd",
2575
- "gridRowStart",
2576
- "gridTemplateAreas",
2577
- "listStyleType",
2578
- "page",
2579
- "positionAnchor",
2580
- "positionTryFallbacks",
2581
- "scrollTimelineName",
2582
- "timelineScope",
2583
- "transitionProperty",
2584
- "viewTimelineName",
2585
- "viewTransitionName",
2586
- "willChange"
2587
- ]);
2588
2473
  const strictPropertyList = new Set([
2589
2474
  "alignContent",
2590
2475
  "alignItems",
@@ -2649,24 +2534,8 @@ const strictPropertyList = new Set([
2649
2534
  "wordBreak",
2650
2535
  "writingMode"
2651
2536
  ]);
2652
- const restrict = (key, value, config, heldOutTokens = "") => {
2653
- if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${[heldOutTokens, value].filter(Boolean).join(" | ")}>>>`;
2654
- /**
2655
- * The escape hatch wraps the *tokens*, not the whole value.
2656
- *
2657
- * `WithModifier` is `[T] extends [string] ? … : never`, so one non-string member of `T`
2658
- * turns the modifier forms off for the property entirely — and under this setting `T`
2659
- * carries csstype's keywords, which include `undefined` and boxed `Number`. Wrapping the
2660
- * whole union that way silently rejected `color: 'blue.300/40'` and `'blue.300!'`, which
2661
- * decorate a token and have nothing to do with raw values.
2662
- *
2663
- * `CssValueShape` is what keeps raw values writable without an escape hatch: the shapes a
2664
- * token path cannot have — a leading digit, `#` or `-`, or a space, comma or call anywhere.
2665
- * A bare identifier that names no token and no keyword matches none of them, which is the
2666
- * mistake this setting exists to catch.
2667
- */
2668
- if (config.strictTokens === "unknown-tokens") return `ConditionalValue<WithEscapeHatch<${heldOutTokens || "never"}> | ${value} | CssValueShape>`;
2669
- 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}>>>`;
2670
2539
  return `ConditionalValue<${value} | AnyString>`;
2671
2540
  };
2672
2541
  //#endregion
@@ -4217,6 +4086,7 @@ var Generator = class extends Context {
4217
4086
  content: css
4218
4087
  }) ?? css;
4219
4088
  this.assertNoUnresolvedTokens();
4089
+ this.reportRawValues();
4220
4090
  return css;
4221
4091
  };
4222
4092
  /**
@@ -4263,6 +4133,44 @@ var Generator = class extends Context {
4263
4133
  * exists: those all run during extraction, before anything has been decoded. Every path
4264
4134
  * that emits css comes through `getCss`.
4265
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
+ };
4266
4174
  assertNoUnresolvedTokens = () => {
4267
4175
  if (this.config.unresolvedToken !== "error") return;
4268
4176
  const found = new Map(this.utility.unresolvedTokens);
@@ -4272,16 +4180,10 @@ var Generator = class extends Context {
4272
4180
  if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
4273
4181
  const key = this.utility.resolveShorthand(prop);
4274
4182
  const bare = this.utility.bareTokenPath(key, value);
4275
- found.set(`${key}:${bare}`, {
4276
- prop: key,
4277
- value: bare,
4278
- category: this.utility.getTokenCategory(key)
4279
- });
4183
+ found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
4280
4184
  }
4281
4185
  if (!found.size) return;
4282
- const detail = truncateList(Array.from(found.values(), ({ prop, value, category }) => {
4283
- return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
4284
- }), {
4186
+ const detail = truncateList(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
4285
4187
  limit: 25,
4286
4188
  unit: "value",
4287
4189
  separator: "\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/generator",
3
- "version": "1.41.1",
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.1",
42
- "@bamboocss/is-valid-prop": "^1.41.1",
43
- "@bamboocss/logger": "1.41.1",
44
- "@bamboocss/shared": "1.41.1",
45
- "@bamboocss/token-dictionary": "1.41.1",
46
- "@bamboocss/types": "1.41.1"
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"