CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-css-tree

A tool set for CSS: fast detailed parser (CSS → AST), walker (AST traversal), generator (AST → CSS) and lexer (validation and matching) based on specs and browser implementations

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

validation.mddocs/

CSS Syntax Validation

Advanced CSS syntax validation and matching against W3C specifications with support for properties, at-rules, and value types.

Capabilities

Lexer Instance

The default lexer provides CSS syntax validation based on W3C specifications and MDN data:

const lexer: Lexer;

class Lexer {
  /** CSS-wide keywords (inherit, initial, unset, revert) */
  readonly cssWideKeywords: string[];
  /** Whether generic type matching is enabled */
  readonly generic: boolean;
  /** Units configuration */
  readonly units: UnitsConfig;
  /** At-rules definitions */
  readonly atrules: AtRulesConfig;
  /** Properties definitions */
  readonly properties: PropertiesConfig;
  /** Type definitions */  
  readonly types: TypesConfig;
  /** Structure definitions for AST validation */
  readonly structure: StructureConfig;
}

Structure Validation

Validates AST node structure against expected schemas:

/**
 * Validates AST structure against expected schemas
 * @param ast - AST node to validate
 * @throws {Error} If structure is invalid
 */
checkStructure(ast: CssNode): void;

/**
 * Validates at-rule name
 * @param atruleName - Name of at-rule to validate
 * @throws {Error} If at-rule name is invalid
 */
checkAtruleName(atruleName: string): void;

/**
 * Validates property name
 * @param propertyName - Property name to validate
 * @throws {Error} If property name is invalid
 */
checkPropertyName(propertyName: string): void;

/**
 * Validates at-rule prelude syntax
 * @param atruleName - At-rule name
 * @param prelude - Prelude AST to validate
 * @throws {Error} If prelude is invalid for the at-rule
 */
checkAtrulePrelude(atruleName: string, prelude: CssNode): void;

/**
 * Validates at-rule descriptor name
 * @param atruleName - At-rule name
 * @param descriptorName - Descriptor name to validate
 * @throws {Error} If descriptor is invalid for the at-rule
 */
checkAtruleDescriptorName(atruleName: string, descriptorName: string): void;

Usage Examples:

import { lexer, parse } from 'css-tree';

// Validate AST structure
try {
  const ast = parse('.example { color: red; }');
  lexer.checkStructure(ast);
  console.log('AST structure is valid');
} catch (error) {
  console.error('Invalid AST structure:', error.message);
}

// Validate property names
try {
  lexer.checkPropertyName('color');
  lexer.checkPropertyName('invalid-property'); // throws error
} catch (error) {
  console.error('Invalid property:', error.message);
}

// Validate at-rule names
try {
  lexer.checkAtruleName('media');
  lexer.checkAtruleName('invalid-rule'); // throws error
} catch (error) {
  console.error('Invalid at-rule:', error.message);
}

Syntax Matching

Match CSS values against property and type definitions:

/**
 * Matches declaration against property syntax
 * @param declaration - Declaration AST node
 * @returns Match result with validation information
 */
matchDeclaration(declaration: CssNode): MatchResult;

/**
 * Matches value against property syntax
 * @param propertyName - CSS property name
 * @param value - Value AST to match
 * @returns Match result with validation information
 */
matchProperty(propertyName: string, value: CssNode): MatchResult;

/**
 * Matches value against type syntax
 * @param typeName - CSS type name (e.g., 'color', 'length')
 * @param value - Value AST to match
 * @returns Match result with validation information
 */
matchType(typeName: string, value: CssNode): MatchResult;

/**
 * Matches value against arbitrary syntax definition
 * @param syntax - CSS syntax definition string
 * @param value - Value AST to match
 * @returns Match result with validation information
 */
match(syntax: string, value: CssNode): MatchResult;

interface MatchResult {
  /** Successfully matched AST node or null */
  matched: CssNode | null;
  /** Error information if match failed */
  error: MatchError | null;
  
  /** Check if specific node matches a type */
  isType(node: CssNode, type: string): boolean;
  /** Get trace information for matched node */
  getTrace(node: CssNode): TraceNode[];
}

interface MatchError {
  message: string;
  syntax: string;
  css: string;
  mismatchOffset: number;
  mismatchLength: number;
}

interface TraceNode {
  type: 'Type' | 'Property' | 'Keyword' | 'Combinator';
  name: string;
}

Usage Examples:

import { lexer, parse } from 'css-tree';

// Match property values
const colorValue = parse('red', { context: 'value' });
const colorMatch = lexer.matchProperty('color', colorValue);

if (colorMatch.matched) {
  console.log('Valid color value');
  console.log('Is color type:', colorMatch.isType(colorValue.children.first, 'color'));
} else {
  console.error('Invalid color:', colorMatch.error.message);
}

// Match against specific types
const lengthValue = parse('10px', { context: 'value' });
const lengthMatch = lexer.matchType('length', lengthValue);

// Match complex properties
const borderValue = parse('1px solid red', { context: 'value' });
const borderMatch = lexer.matchProperty('border', borderValue);

// Get detailed trace
if (borderMatch.matched) {
  const trace = borderMatch.getTrace(borderValue.children.first);
  console.log('Match trace:', trace);
}

// Match declarations
const declaration = parse('color: red', { context: 'declaration' });
const declMatch = lexer.matchDeclaration(declaration);

Fragment Search

Find specific fragments within CSS values:

/**
 * Finds fragments of specific type in property value
 * @param propertyName - CSS property name
 * @param value - Value AST to search
 * @param type - Fragment type to find
 * @param name - Specific name to match (optional)
 * @returns Array of found fragments
 */
findValueFragments(
  propertyName: string, 
  value: CssNode, 
  type: string, 
  name?: string
): Fragment[];

/**
 * Finds fragments in declaration value
 * @param declaration - Declaration AST node
 * @param type - Fragment type to find
 * @param name - Specific name to match (optional)
 * @returns Array of found fragments
 */
findDeclarationValueFragments(
  declaration: CssNode, 
  type: string, 
  name?: string
): Fragment[];

/**
 * Finds all fragments of type in entire AST
 * @param ast - AST to search
 * @param type - Fragment type to find
 * @param name - Specific name to match (optional)
 * @returns Array of found fragments
 */
findAllFragments(
  ast: CssNode, 
  type: string, 
  name?: string
): Fragment[];

interface Fragment {
  node: CssNode;
  property: string;
  value: CssNode;
}

Usage Examples:

import { lexer, parse } from 'css-tree';

const ast = parse(`
  .example {
    color: red;
    background: url(image.png) no-repeat;
    border: 1px solid #ff0000;
  }
`);

// Find all color fragments
const colorFragments = lexer.findAllFragments(ast, 'color');
colorFragments.forEach(fragment => {
  console.log(`Color in ${fragment.property}:`, generate(fragment.node));
});

// Find URLs
const urlFragments = lexer.findAllFragments(ast, 'url');
urlFragments.forEach(fragment => {
  console.log('Found URL:', generate(fragment.node));
});

// Find specific colors
const redFragments = lexer.findAllFragments(ast, 'color', 'red');

// Find fragments in specific property
const declaration = parse('margin: 10px 20px', { context: 'declaration' });
const lengthFragments = lexer.findDeclarationValueFragments(declaration, 'length');

Configuration Access

Access lexer configuration and definitions:

/**
 * Gets at-rule definition
 * @param atruleName - At-rule name
 * @param fallbackBasename - Fallback name if not found
 * @returns At-rule definition or null
 */
getAtrule(atruleName: string, fallbackBasename?: string): AtRuleDefinition | null;

/**
 * Gets property definition
 * @param propertyName - Property name
 * @param fallbackBasename - Fallback name if not found
 * @returns Property definition or null
 */
getProperty(propertyName: string, fallbackBasename?: string): PropertyDefinition | null;

/**
 * Gets type definition
 * @param typeName - Type name
 * @returns Type definition or null
 */
getType(typeName: string): TypeDefinition | null;

interface PropertyDefinition {
  syntax: string;
  media?: string;
  inherited?: boolean;
  animationType?: string;
  percentages?: string;
  groups?: string[];
  initial?: string;
  appliesto?: string;
  computed?: string;
  order?: string;
  status?: string;
}

Usage Examples:

// Get property information
const colorDef = lexer.getProperty('color');
console.log('Color syntax:', colorDef.syntax);
console.log('Color inherited:', colorDef.inherited);

// Check property support
const customProp = lexer.getProperty('custom-property');
if (!customProp) {
  console.log('Custom property not defined');
}

// Get type information
const lengthType = lexer.getType('length');
console.log('Length type definition:', lengthType);

// Get at-rule information
const mediaDef = lexer.getAtrule('media');
console.log('Media rule syntax:', mediaDef.prelude);

Custom Lexer Creation

Create custom lexers with extended or modified syntax definitions:

/**
 * Creates new lexer with custom configuration
 * @param config - Custom lexer configuration
 * @returns New lexer instance
 */
function createLexer(config: LexerConfig): Lexer;

interface LexerConfig {
  /** Custom property definitions */
  properties?: { [propertyName: string]: PropertyDefinition };
  /** Custom type definitions */
  types?: { [typeName: string]: string };
  /** Custom at-rule definitions */
  atrules?: { [atruleName: string]: AtRuleDefinition };
  /** Enable/disable generic type matching */
  generic?: boolean;
  /** Units configuration */
  units?: string[];
}

Usage Examples:

import { createLexer } from 'css-tree';

// Create lexer with custom properties
const customLexer = createLexer({
  properties: {
    'custom-color': {
      syntax: '<color>',
      inherited: false
    },
    'custom-size': {
      syntax: '<length> | <percentage>',
      inherited: false
    }
  },
  types: {
    'custom-keyword': 'small | medium | large'
  }
});

// Use custom lexer
const value = parse('small', { context: 'value' });
const match = customLexer.matchType('custom-keyword', value);

// Extend existing lexer
const extendedLexer = lexer.fork({
  properties: {
    'new-property': { syntax: '<number>' }
  }
});

Validation Error Handling

Handle validation errors gracefully:

function validateCSS(css) {
  const errors = [];
  
  try {
    const ast = parse(css);
    
    walk(ast, {
      Declaration: (node) => {
        try {
          const match = lexer.matchDeclaration(node);
          if (!match.matched) {
            errors.push({
              type: 'InvalidValue',
              property: node.property.name,
              value: generate(node.value),
              message: match.error.message
            });
          }
        } catch (error) {
          errors.push({
            type: 'ValidationError',
            property: node.property.name,
            message: error.message
          });
        }
      }
    });
  } catch (error) {
    errors.push({
      type: 'ParseError',
      message: error.message
    });
  }
  
  return errors;
}

// Usage
const errors = validateCSS('.example { color: invalid; width: -10px; }');
errors.forEach(error => {
  console.error(`${error.type}: ${error.message}`);
});

Install with Tessl CLI

npx tessl i tessl/npm-css-tree

docs

data-structures.md

generation.md

index.md

parsing.md

tokenization.md

traversal.md

utilities.md

validation.md

tile.json