@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 +65 -150
- package/dist/index.d.cts +15 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +65 -150
- 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,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
|
-
*
|
|
2518
|
-
*
|
|
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
|
-
*
|
|
2521
|
-
*
|
|
2522
|
-
*
|
|
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
|
|
2530
|
-
if (
|
|
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
|
-
|
|
2486
|
+
cssFallback
|
|
2541
2487
|
].filter(Boolean).join(" | "));
|
|
2542
|
-
} else union.push([strictPropertyList.has(key) ? "CssVars" : "",
|
|
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
|
|
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
|
|
2666
|
-
if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${
|
|
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(), (
|
|
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
|
-
*
|
|
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,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
|
-
*
|
|
2492
|
-
*
|
|
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
|
-
*
|
|
2495
|
-
*
|
|
2496
|
-
*
|
|
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
|
|
2504
|
-
if (
|
|
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
|
-
|
|
2460
|
+
cssFallback
|
|
2515
2461
|
].filter(Boolean).join(" | "));
|
|
2516
|
-
} else union.push([strictPropertyList.has(key) ? "CssVars" : "",
|
|
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
|
|
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
|
|
2640
|
-
if (config.strictPropertyValues && strictPropertyList.has(key)) return `ConditionalValue<WithEscapeHatch<OnlyKnown<"${key}", ${
|
|
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(), (
|
|
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.
|
|
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.
|
|
42
|
-
"@bamboocss/
|
|
43
|
-
"@bamboocss/
|
|
44
|
-
"@bamboocss/
|
|
45
|
-
"@bamboocss/
|
|
46
|
-
"@bamboocss/
|
|
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"
|