@bamboocss/generator 1.41.1 → 1.43.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 +67 -163
- package/dist/index.d.cts +15 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +67 -163
- package/package.json +7 -7
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
|
-
*
|
|
2430
|
-
*
|
|
2431
|
-
*
|
|
2432
|
-
*
|
|
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("
|
|
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
|
-
*
|
|
2468
|
+
* A utility's own values *extend* what the property accepts; they never replace it.
|
|
2530
2469
|
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
* `
|
|
2534
|
-
*
|
|
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
|
-
*
|
|
2537
|
-
*
|
|
2538
|
-
*
|
|
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
|
|
2543
|
-
if (
|
|
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
|
-
|
|
2486
|
+
cssFallback
|
|
2554
2487
|
].filter(Boolean).join(" | "));
|
|
2555
|
-
} else union.push([strictPropertyList.has(key) ? "CssVars" : "",
|
|
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
|
|
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
|
|
2679
|
-
if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${
|
|
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,8 +4159,47 @@ 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
|
+
const severity = this.utility.unresolvedToken;
|
|
4202
|
+
if (severity.token !== "error" && severity.grammar !== "error") return;
|
|
4294
4203
|
const found = new Map(this.utility.unresolvedTokens);
|
|
4295
4204
|
for (const atom of this.decoder.atomic) {
|
|
4296
4205
|
const { prop, value } = atom.entry;
|
|
@@ -4298,16 +4207,11 @@ var Generator = class extends _bamboocss_core.Context {
|
|
|
4298
4207
|
if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
|
|
4299
4208
|
const key = this.utility.resolveShorthand(prop);
|
|
4300
4209
|
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
|
-
});
|
|
4210
|
+
found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
|
|
4306
4211
|
}
|
|
4212
|
+
for (const [id, ref] of found) if (severity[ref.kind] !== "error") found.delete(id);
|
|
4307
4213
|
if (!found.size) return;
|
|
4308
|
-
const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), (
|
|
4309
|
-
return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
|
|
4310
|
-
}), {
|
|
4214
|
+
const detail = (0, _bamboocss_shared.truncateList)(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
|
|
4311
4215
|
limit: 25,
|
|
4312
4216
|
unit: "value",
|
|
4313
4217
|
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
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
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("
|
|
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
|
-
*
|
|
2442
|
+
* A utility's own values *extend* what the property accepts; they never replace it.
|
|
2504
2443
|
*
|
|
2505
|
-
*
|
|
2506
|
-
*
|
|
2507
|
-
* `
|
|
2508
|
-
*
|
|
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
|
-
*
|
|
2511
|
-
*
|
|
2512
|
-
*
|
|
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
|
|
2517
|
-
if (
|
|
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
|
-
|
|
2460
|
+
cssFallback
|
|
2528
2461
|
].filter(Boolean).join(" | "));
|
|
2529
|
-
} else union.push([strictPropertyList.has(key) ? "CssVars" : "",
|
|
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
|
|
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
|
|
2653
|
-
if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${
|
|
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,8 +4133,47 @@ 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
|
+
const severity = this.utility.unresolvedToken;
|
|
4176
|
+
if (severity.token !== "error" && severity.grammar !== "error") return;
|
|
4268
4177
|
const found = new Map(this.utility.unresolvedTokens);
|
|
4269
4178
|
for (const atom of this.decoder.atomic) {
|
|
4270
4179
|
const { prop, value } = atom.entry;
|
|
@@ -4272,16 +4181,11 @@ var Generator = class extends Context {
|
|
|
4272
4181
|
if (!this.utility.isUnresolvedTokenValue(prop, value)) continue;
|
|
4273
4182
|
const key = this.utility.resolveShorthand(prop);
|
|
4274
4183
|
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
|
-
});
|
|
4184
|
+
found.set(`${key}:${bare}`, this.utility.unresolvedTokenRef(key, bare));
|
|
4280
4185
|
}
|
|
4186
|
+
for (const [id, ref] of found) if (severity[ref.kind] !== "error") found.delete(id);
|
|
4281
4187
|
if (!found.size) return;
|
|
4282
|
-
const detail = truncateList(Array.from(found.values(), (
|
|
4283
|
-
return `- \`${prop}: ${value}\`.${category ? ` Check the path against your \`${category}\` tokens.` : ""}`;
|
|
4284
|
-
}), {
|
|
4188
|
+
const detail = truncateList(Array.from(found.values(), (ref) => `- ${this.utility.explainUnresolvedToken(ref)}`), {
|
|
4285
4189
|
limit: 25,
|
|
4286
4190
|
unit: "value",
|
|
4287
4191
|
separator: "\n"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.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.
|
|
42
|
-
"@bamboocss/is-valid-prop": "^1.
|
|
43
|
-
"@bamboocss/logger": "1.
|
|
44
|
-
"@bamboocss/shared": "1.
|
|
45
|
-
"@bamboocss/token-dictionary": "1.
|
|
46
|
-
"@bamboocss/types": "1.
|
|
41
|
+
"@bamboocss/core": "1.43.0",
|
|
42
|
+
"@bamboocss/is-valid-prop": "^1.43.0",
|
|
43
|
+
"@bamboocss/logger": "1.43.0",
|
|
44
|
+
"@bamboocss/shared": "1.43.0",
|
|
45
|
+
"@bamboocss/token-dictionary": "1.43.0",
|
|
46
|
+
"@bamboocss/types": "1.43.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/pluralize": "0.0.33"
|