or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-postcss-safe-parser

Fault-tolerant CSS parser for PostCSS that can handle malformed or incomplete CSS syntax without throwing errors

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/postcss-safe-parser@7.0.x

To install, run

npx @tessl/cli install tessl/npm-postcss-safe-parser@7.0.0

index.mddocs/

PostCSS Safe Parser

PostCSS Safe Parser is a fault-tolerant CSS parser for PostCSS that can handle malformed or incomplete CSS syntax without throwing errors. It automatically finds and fixes syntax errors, making it capable of parsing any CSS input including legacy code with browser hacks and incomplete stylesheets.

Package Information

  • Package Name: postcss-safe-parser
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install postcss-safe-parser

Core Imports

const safe = require('postcss-safe-parser');

ESM (if using a build tool that supports it):

import safe from 'postcss-safe-parser';

Basic Usage

const postcss = require('postcss');
const safe = require('postcss-safe-parser');

// Parse malformed CSS that would break the standard parser
const badCss = 'a { color: black';
const result = await postcss().process(badCss, { parser: safe });
console.log(result.css); // 'a { color: black}'

// Direct parsing without PostCSS
const root = safe('a { /* unclosed comment');
console.log(root.toString()); // 'a { /* unclosed comment */}'

Architecture

PostCSS Safe Parser extends PostCSS's core parser with fault-tolerant parsing capabilities. The architecture consists of:

  • SafeParser Class: Extends PostCSS's Parser class, overriding key methods to handle malformed syntax
  • Error Recovery System: Custom tokenizer with ignoreErrors: true that continues parsing despite syntax errors
  • Graceful Degradation: Methods like checkMissedSemicolon(), unclosedBracket(), and unexpectedClose() are overridden to handle errors silently
  • PostCSS Integration: Seamlessly integrates with PostCSS's processing pipeline as a drop-in parser replacement

The safe parser creates the same PostCSS AST structure as the standard parser, ensuring full compatibility with all PostCSS plugins and tools.

Capabilities

Safe CSS Parsing

Parses CSS with automatic error recovery and syntax fixing.

/**
 * Parse CSS string with fault tolerance
 * @param css - CSS string to parse (can contain syntax errors)  
 * @param opts - Options object passed to PostCSS Input constructor
 * @param opts.from - Input file path for source map generation
 * @param opts.to - Output file path for source map generation  
 * @param opts.origin - Custom origin function for Input
 * @param opts.map - Source map options or boolean
 * @returns PostCSS Root AST node with fault-tolerant parsing applied
 */
function safeParse(css, opts = {});

Parameters:

  • css (string): CSS input to parse - can contain malformed syntax, unclosed blocks, missing semicolons, etc.
  • opts (object, optional): Options object passed to PostCSS Input constructor
    • from (string): Input file path for source map generation
    • origin (function): Custom origin function for Input
    • Any other options supported by PostCSS Input

Returns:

PostCSS Root node containing the parsed CSS AST with all syntax errors automatically fixed.

Error Recovery Features:

The parser automatically handles and fixes:

  • Unclosed blocks: a { becomes a {}
  • Unclosed comments: /* comment becomes /* comment */
  • Missing semicolons: a { color: red font-size: 12px } becomes a { color: red; font-size: 12px }
  • Unclosed quotes: content: "text becomes content: "text"
  • Unclosed brackets: :not(input becomes :not(input)
  • Properties without values: a { color; } preserves structure
  • Nameless at-rules: @ becomes valid at-rule node
  • Double colons: a { prop:: value } becomes a { prop: : value }
  • Complex nested JSON-like properties: Handles CSS custom properties with JSON values

Usage Examples:

// Parse CSS with unclosed blocks
const root = safe('@media (screen) { a {\n');
console.log(root.toString()); // '@media (screen) { a {\n}}'

// Parse CSS with missing semicolons
const root = safe('a { color: red font-size: 12px }');
console.log(root.toString()); // 'a { color: red; font-size: 12px }'

// Parse CSS with unclosed comments
const root = safe('a { /* comment ');
console.log(root.toString()); // 'a { /* comment */}'

// Parse CSS with complex JSON-like custom properties
const root = safe(':root { --config: {"nested": {"key": "value"}}; }');
console.log(root.toString()); // Preserves the complex structure

Integration with PostCSS

Use as a parser option in PostCSS processing pipelines.

// Standard PostCSS integration
postcss(plugins).process(css, { parser: safeParse })

// With async/await
const result = await postcss([autoprefixer]).process(badCss, { 
  parser: safe,
  from: 'input.css'
});

Common Integration Patterns:

const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const safe = require('postcss-safe-parser');

// Live CSS editing tools - parse as user types
async function parseUserInput(userCss) {
  try {
    const result = await postcss([autoprefixer])
      .process(userCss, { parser: safe });
    return result.css;
  } catch (error) {
    // Safe parser won't throw, but plugins might
    return userCss;
  }
}

// Legacy CSS processing
async function processLegacyCSS(legacyCss) {
  const result = await postcss([
    // Your plugins here
  ]).process(legacyCss, {
    parser: safe,
    from: 'legacy.css'
  });
  return result;
}

// Browser hacks and malformed CSS
const browserHacksCSS = `
  .selector {
    property: value\\9; /* IE hack */
    *property: value; /* IE6/7 hack */
    _property: value /* IE6 hack
`;

const cleaned = safe(browserHacksCSS);
console.log(cleaned.toString()); // Properly closed and parsed

SafeParser Class

Direct access to the SafeParser class for advanced usage scenarios.

const SafeParser = require('postcss-safe-parser/lib/safe-parser');
const { Input } = require('postcss');

/**
 * SafeParser class extending PostCSS Parser
 * Provides fault-tolerant CSS parsing with error recovery
 */
class SafeParser extends Parser {
  constructor(input);
  parse(): void;
  createTokenizer(): void;
  comment(token): void;
  decl(tokens): void;
  endFile(): void;
  precheckMissedSemicolon(tokens): void;
  unclosedBracket(): void;
  unexpectedClose(): void;
  unknownWord(tokens): void;
  unnamedAtrule(node): void;
}

Direct SafeParser Usage:

const { Input } = require('postcss');
const SafeParser = require('postcss-safe-parser/lib/safe-parser');

// Create parser instance directly
const input = new Input(cssString, { from: 'input.css' });
const parser = new SafeParser(input);
parser.parse();

// Access the parsed root
const root = parser.root;
console.log(root.toString());

Key Method Behaviors:

  • comment(token): Handles unclosed comments by automatically closing them
  • decl(tokens): Validates declaration tokens before processing
  • endFile(): Ensures proper file closure even with unclosed blocks
  • unclosedBracket(): Silently handles unclosed brackets without throwing
  • unexpectedClose(): Handles extra closing braces by adding them to raws.after
  • unknownWord(tokens): Treats unknown tokens as whitespace instead of throwing errors

Advanced Usage Patterns:

// Custom input processing with source maps
const { Input } = require('postcss');
const SafeParser = require('postcss-safe-parser/lib/safe-parser');

function parseWithCustomInput(css, filename) {
  const input = new Input(css, { 
    from: filename,
    origin: (offset, file) => {
      // Custom source mapping logic
      const lines = css.substring(0, offset).split('\n');
      return { line: lines.length, col: lines[lines.length - 1].length + 1 };
    }
  });
  
  const parser = new SafeParser(input);
  parser.parse();
  return parser.root;
}

// Parser with custom error recovery tracking
class TrackedSafeParser extends SafeParser {
  constructor(input) {
    super(input);
    this.recoveredErrors = [];
  }
  
  unexpectedClose() {
    super.unexpectedClose();
    this.recoveredErrors.push({
      type: 'unexpected_close',
      position: this.tokenizer.position()
    });
  }
  
  unclosedBracket() {
    super.unclosedBracket();
    this.recoveredErrors.push({
      type: 'unclosed_bracket', 
      position: this.tokenizer.position()
    });
  }
}

Error Handling

The safe parser is designed to never throw parsing errors. It will always return a valid PostCSS AST, regardless of the input CSS quality. However, downstream PostCSS plugins may still throw errors during processing.

// Safe parser never throws
const root = safe('completely { malformed css {{{'); // Always succeeds

// But plugins might throw during processing
try {
  const result = await postcss([somePlugin])
    .process(malformedCss, { parser: safe });
} catch (pluginError) {
  // Handle plugin errors, not parser errors
}

Types

For TypeScript projects, the parser follows PostCSS type definitions:

import { Root, Input, Parser, Node, Comment, Rule, AtRule, Declaration } from 'postcss';

/**
 * Main safe parsing function
 * @param css - CSS string to parse with fault tolerance
 * @param opts - Options passed to PostCSS Input constructor
 * @returns PostCSS Root AST node
 */
declare function safeParse(css: string, opts?: ProcessOptions): Root;

interface ProcessOptions {
  from?: string;
  to?: string;
  origin?: (offset: number, file?: string) => { line: number; col: number };
  map?: SourceMapOptions | boolean;
}

interface SourceMapOptions {
  inline?: boolean;
  prev?: string | object | boolean;
  sourcesContent?: boolean;
  annotation?: boolean | string;
  from?: string;
  to?: string;
}

/**
 * SafeParser class extending PostCSS Parser
 */
declare class SafeParser extends Parser {
  constructor(input: Input);
  
  parse(): void;
  createTokenizer(): void;
  comment(token: [string, string, number, number]): void;
  decl(tokens: Array<[string, string, number, number]>): void;
  endFile(): void;
  precheckMissedSemicolon(tokens: Array<[string, string, number, number]>): void;
  unclosedBracket(): void;
  unexpectedClose(): void;
  unknownWord(tokens: Array<[string, string, number, number]>): void;
  unnamedAtrule(node: AtRule): void;
  
  // Inherited from Parser
  root: Root;
  input: Input;
  current: Node;
  spaces: string;
  semicolon: boolean;
  customProperty: boolean;
}

interface Root {
  type: 'root';
  nodes: Node[];
  source?: {
    input: Input;
    start?: { line: number; column: number; offset: number };
    end?: { line: number; column: number; offset: number };
  };
  raws: {
    before?: string;
    after?: string;
    semicolon?: boolean;
  };
  
  // Methods
  toString(stringifier?: any): string;
  toResult(opts?: any): any;
  append(...nodes: Node[]): Root;
  prepend(...nodes: Node[]): Root;
  insertAfter(exist: Node, add: Node): Root;
  insertBefore(exist: Node, add: Node): Root;
  removeAll(): Root;
  removeChild(child: Node): Root;
  each(callback: (node: Node, index: number) => void | false): void;
  walk(callback: (node: Node, index: number) => void | false): void;
  walkRules(callback: (rule: Rule, index: number) => void | false): void;
  walkAtRules(callback: (atrule: AtRule, index: number) => void | false): void;
  walkComments(callback: (comment: Comment, index: number) => void | false): void;
  walkDecls(callback: (decl: Declaration, index: number) => void | false): void;
}