Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@

## Why b_short?

- **📦 Tiny**: ~16KB compressed
- **📦 Tiny**: ~68KB minified + brotli (ESM), ~73KB (CJS) with all dependencies
- **⚡ Fast**: Optimized TypeScript with smart caching
- **🎯 Complete**: 35+ CSS shorthands including modern features
- **🔒 Type-Safe**: Full TypeScript support
- **✅ Tested**: 970 tests ensuring 100% accuracy
- **✅ Tested**: 973 tests ensuring 100% accuracy
- **🎨 Flexible**: CSS strings or JS objects (camelCase for React)
- **🔄 Bidirectional**: Both expand and collapse APIs

## Quick Start

```bash
npm install b_short css-tree
npm install b_short
```

```typescript
Expand Down Expand Up @@ -240,8 +240,8 @@ if (!result.ok) {
## Performance

- **Fast**: Optimized for performance with LRU caching
- **Small**: ~16KB compressed (brotli)
- **Efficient**: Handles 808 test cases in <1 second
- **Small**: 89KB unminified, ~68KB minified + brotli (ESM)
- **Efficient**: Handles 973 test cases in <2 seconds

## TypeScript Support

Expand Down Expand Up @@ -276,7 +276,8 @@ MIT © [alphabio](https://github.com/alphabio)
## Acknowledgments

- [TypeScript](https://www.typescriptlang.org/)
- [css-tree](https://github.com/csstree/csstree) - CSS parsing
- [@eslint/css-tree](https://github.com/eslint/css-tree) - CSS parsing
- [b_values](https://github.com/alphabio/b_values) - CSS value expansion and validation
- [Vitest](https://vitest.dev/) - Testing
- [Biome](https://biomejs.dev/) - Code quality

Expand Down
61 changes: 41 additions & 20 deletions docs.llm/llm_src.txt
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ export interface CollapseResult {
* @since 1.0.0
*/

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { BStyleWarning, StylesheetValidation } from "./schema";

// Constants
Expand Down Expand Up @@ -963,6 +963,21 @@ interface FormattedLine {
adjustedColumn: number;
}

/**
* Checks if a CSS value node contains var() function.
* CSS variables cannot be validated by @eslint/css-tree as they are runtime values.
*/
function containsVar(node: csstree.CssNode): boolean {
if (!node) return false;
if (node.type === "Function" && node.name === "var") return true;
if ("children" in node && node.children) {
for (const child of node.children) {
if (containsVar(child)) return true;
}
}
return false;
}

/**
* Validates a CSS stylesheet for syntax and property value errors.
*
Expand Down Expand Up @@ -1032,6 +1047,12 @@ export function validate(css: string): StylesheetValidation {
}

for (const decl of declarations) {
// Skip validation for declarations containing CSS variables (var())
// as they cannot be validated at parse time
if (containsVar(decl.value)) {
continue;
}

const match = syntax.matchProperty(decl.property, decl.value);
const error = match.error as csstree.SyntaxMatchError;

Expand Down Expand Up @@ -1311,7 +1332,7 @@ function formatErrorDisplay(
=== File: src/handlers/animation/animation-layers.ts ===
// b_path:: src/handlers/animation/animation-layers.ts

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { AnimationLayer, AnimationResult } from "@/core/schema";
import isTime from "@/internal/is-time";
import isTimingFunction from "@/internal/is-timing-function";
Expand Down Expand Up @@ -1357,8 +1378,8 @@ function parseSingleLayer(layerValue: string): AnimationLayer | undefined {
const children: csstree.CssNode[] = [];
csstree.walk(ast, {
visit: "Value",
enter: (node: csstree.Value) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
node.children.forEach((child) => {
children.push(child);
});
Expand Down Expand Up @@ -2148,7 +2169,7 @@ export { default } from "./expand";
=== File: src/handlers/background/background-layers.ts ===
// b_path:: src/handlers/background/background-layers.ts

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { BackgroundLayer, BackgroundResult } from "@/core/schema";
import isColor from "@/internal/is-color";
import { isPositionValueNode, isSizeValueNode } from "@/internal/is-value-node";
Expand Down Expand Up @@ -2252,8 +2273,8 @@ function parseSingleLayerWithCssTree(layerValue: string): BackgroundLayer & { co
const children: csstree.CssNode[] = [];
csstree.walk(ast, {
visit: "Value",
enter: (node: csstree.Value) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
node.children.forEach((child) => {
children.push(child);
});
Expand Down Expand Up @@ -5645,7 +5666,7 @@ export const gridCollapser: CollapseHandler = createCollapseHandler({
// named grid lines, track sizes, repeat() notation, area names, and multiple syntaxes
// (template form, explicit-rows, explicit-columns). The parsing logic is preserved as-is.

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import { matchesType } from "@/internal/is-value-node";
import { createPropertyHandler, type PropertyHandler } from "@/internal/property-handler";

Expand Down Expand Up @@ -5674,8 +5695,8 @@ function parseValueAndGetSegments(

csstree.walk(ast, {
visit: "Value",
enter: (node: csstree.Value) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
let index = 0;
node.children.forEach((child) => {
if (child.type === "Operator" && (child as csstree.Operator).value === "/") {
Expand Down Expand Up @@ -6715,7 +6736,7 @@ export { default } from "./expand";
=== File: src/handlers/mask/mask-layers.ts ===
// b_path:: src/handlers/mask/mask-layers.ts

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { MaskLayer, MaskResult } from "@/core/schema";
import { isPositionValueNode, isSizeValueNode } from "@/internal/is-value-node";
import { hasTopLevelCommas, parseLayersGeneric } from "@/internal/layer-parser-utils";
Expand Down Expand Up @@ -6766,8 +6787,8 @@ function parseSingleLayerWithCssTree(layerValue: string): MaskLayer | undefined
const children: csstree.CssNode[] = [];
csstree.walk(ast, {
visit: "Value",
enter: (node: csstree.Value) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
node.children.forEach((child) => {
children.push(child);
});
Expand Down Expand Up @@ -8944,7 +8965,7 @@ export { default } from "./expand";
=== File: src/handlers/transition/transition-layers.ts ===
// b_path:: src/handlers/transition/transition-layers.ts

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { TransitionLayer, TransitionResult } from "@/core/schema";
import isTime from "@/internal/is-time";
import isTimingFunction from "@/internal/is-timing-function";
Expand Down Expand Up @@ -8986,8 +9007,8 @@ function parseSingleLayer(layerValue: string): TransitionLayer | undefined {
const children: csstree.CssNode[] = [];
csstree.walk(ast, {
visit: "Value",
enter: (node: csstree.Value) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
node.children.forEach((child) => {
children.push(child);
});
Expand Down Expand Up @@ -10402,7 +10423,7 @@ export default function isTimingFunction(value: string): boolean {

=== File: src/internal/is-value-node.ts ===
// b_path:: src/internal/is-value-node.ts
import type * as csstree from "css-tree";
import type * as csstree from "@eslint/css-tree";

/**
* CSS functions that represent colors, not numeric values.
Expand Down Expand Up @@ -10532,7 +10553,7 @@ export function isSizeValueNode(node: csstree.CssNode, keywords?: string[]): boo

=== File: src/internal/layer-parser-utils.ts ===
// b_path:: src/internal/layer-parser-utils.ts
import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";

/**
* Shared utilities for parsing multi-layer CSS properties (background, mask, animation, transition).
Expand Down Expand Up @@ -10688,8 +10709,8 @@ export function collectCssTreeChildren(ast: csstree.CssNode): csstree.CssNode[]
// Walk the AST and collect children from Value nodes
csstree.walk(ast, {
visit: "Value",
enter: (node: { children?: Iterable<csstree.CssNode> }) => {
if (node.children) {
enter: (node: csstree.CssNode) => {
if (node.type === "Value" && node.children) {
for (const child of node.children) {
children.push(child);
}
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,12 @@
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"peerDependencies": {
"css-tree": "^3.1.0"
"dependencies": {
"@eslint/css-tree": "^3.6.6"
},
"devDependencies": {
"@biomejs/biome": "2.3.4",
"@size-limit/preset-small-lib": "^11.2.0",
"@types/css-tree": "^2.3.11",
"@types/node": "^24.10.0",
"@vitest/coverage-v8": "^4.0.8",
"husky": "^9.1.7",
Expand Down
38 changes: 15 additions & 23 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion src/core/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* @since 1.0.0
*/

import * as csstree from "css-tree";
import * as csstree from "@eslint/css-tree";
import type { BStyleWarning, StylesheetValidation } from "./schema";

// Constants
Expand Down Expand Up @@ -64,6 +64,21 @@ interface FormattedLine {
adjustedColumn: number;
}

/**
* Checks if a CSS value node contains var() function.
* CSS variables cannot be validated by @eslint/css-tree as they are runtime values.
*/
function containsVar(node: csstree.CssNode): boolean {
if (!node) return false;
if (node.type === "Function" && node.name === "var") return true;
if ("children" in node && node.children) {
for (const child of node.children) {
if (containsVar(child)) return true;
}
}
return false;
}

/**
* Validates a CSS stylesheet for syntax and property value errors.
*
Expand Down Expand Up @@ -133,6 +148,12 @@ export function validate(css: string): StylesheetValidation {
}

for (const decl of declarations) {
// Skip validation for declarations containing CSS variables (var())
// as they cannot be validated at parse time
if (containsVar(decl.value)) {
continue;
}

const match = syntax.matchProperty(decl.property, decl.value);
const error = match.error as csstree.SyntaxMatchError;

Expand Down
Loading
Loading