postcss-selector-parser

repository·main·Indexed 42 days ago

https://github.com/postcss/postcss-selector-parser

A tool for parsing, transforming, and serializing CSS selector strings. It provides an AST-based API to walk through and manipulate individual parts of a selector, supporting both synchronous and asynchronous processing. Version 7.1.6.

Tokens
6.6K
Snippets
23
Records
31
Agent score
52%

What's inside postcss-selector-parser

  1. Configure maxNestingDepth to prevent stack overflow

    main

    To protect against Denial-of-Service (DoS) attacks via deeply nested selectors (e.g., thousands of :not() pseudo-classes), the parser uses a nesting depth limit.

    • Default limit: 256.
    • Behavior: If nesting exceeds the limit, the parser throws a catchable Error instead of a RangeError: Maximum call stack size exceeded.
    • Customization: You can tighten this limit when processing untrusted input by passing the maxNestingDepth option to .processSync() or .process().

    Warning: Raising this value to an extremely high number in environments with small call stacks (like browser workers) may reintroduce stack overflow risks.

    // Tighten the limit when parsing untrusted input:
    parser().processSync(untrustedSelector, {maxNestingDepth: 128});
  2. Handle whitespace with `node.spaces`

    main

    Extra whitespaces around a node are moved into node.spaces.before and node.spaces.after.

    For descendant combinators, the value is always a single space (" "). Additional whitespace for descendant selectors is stored in node.spaces.before, node.raws.spaces.before, node.raws.spaces.after, or node.raws.value depending on the presence of comments.

  3. Handle errors within selector processors

    main

    When writing a custom selector processor, you should use the error(message, options) method available on the Root node passed to your callback. This ensures that errors are correctly associated with the selector's syntax and position, allowing PostCSS to report them accurately.

    • message: The error message string.
    • options: An object passed to the PostCSS error constructor. Common keys include index (the position in the string) and word (the specific text that caused the error).

    Use reject(root.error(...)) in asynchronous processors and throw root.error(...) in synchronous processors.

    // Asynchronous Error Example
    let processor = (root) => {
        return new Promise((resolve, reject) => {
            root.walkClasses((classNode) => {
                if (/^(.*)[-_]/.test(classNode.value)) {
                    let msg = "classes may not have underscores or dashes in them";
                    reject(root.error(msg, {
                        index: classNode.sourceIndex + RegExp.$1.length + 1,
                        word: classNode.value
                    }));
                }
            });
            resolve();
        });
    };
    
    // Synchronous Error Example
    let processor = (root) => {
        root.walkClasses((classNode) => {
            if (/.*[-_]/.test(classNode.value)) {
                let msg = "classes may not have underscores or dashes in them";
                throw root.error(msg, {
                    index: classNode.sourceIndex,
                    word: classNode.value
                });
            }
        });
    };
  4. Configure ProcessorOptions

    main

    When initializing a Processor, you can provide options to control how selectors are handled:

    • lossless (boolean): When true, whitespace is preserved. Defaults to true. Set to false to normalize whitespace (e.g., removing extra spaces).
    • updateSelector (boolean): When true, if a processor method is passed a PostCSS Rule node instead of a string, the Rule's selector is automatically updated with the processed result. Defaults to true.
  5. Quick Start: Transform selectors using a callback

    main

    You can use the parser to walk through a selector's Abstract Syntax Tree (AST) and perform transformations. Pass a function to the parser that receives the selectors object, then use .walk() to iterate over individual nodes.

    Note: parser(transform) creates a parser instance configured with your transformation logic. .processSync() then executes that logic on the provided selector string.

    const parser = require('postcss-selector-parser');
    const transform = selectors => {
        selectors.walk(selector => {
            // do something with the selector
            console.log(String(selector))
        });
    };
    
    const transformed = parser(transform).processSync('h1, h2, h3');
  6. Normalize selector whitespace

    main

    To remove unnecessary whitespace from a selector string, use .processSync() with the {lossless: false} option. This will return a minified version of the selector.

    Example: h1, h2, h3 becomes h1,h2,h3.

    const parser = require('postcss-selector-parser');
    const normalized = parser().processSync('h1, h2, h3', {lossless: false});
    // -> h1,h2,h3
  7. Initialize the parser with `parser()`

    main

    The parser function is the main entry point for the module. You can use it to create a new processor instance. You can optionally provide a transform function that operates on the parsed AST, or an options object to set default options for the returned Processor.

    const parser = require('postcss-selector-parser');
    
    // Create a processor without a transform function
    const processor = parser();
    
    // Create a processor with a transform function
    const transform = selectors => {
        selectors.walkUniversals(selector => {
            selector.remove();
        });
    };
    const processor = parser(transform);
    
    // Process a selector string
    const result = processor.processSync('*.class');
    // => .class
  8. Traverse the AST using `node.parent`, `next()`, and `prev()`

    main

    Nodes can be navigated within the tree structure:

    • node.parent: Returns the parent node.
    • node.next(): Returns the next child of the parent node.
    • node.prev(): Returns the previous child of the parent node.
    // Check parent relationship
    root.nodes[0].parent === root;
    
    // Check next sibling
    const next = id.next();
    if (next && next.type !== 'combinator') {
        throw new Error('Qualified IDs are not allowed!');
    }
  9. Split nodes into groups using split()

    main

    The container.split(callback) method allows you to divide a group of nodes into an array of arrays based on a condition. The callback receives a node and should return true to trigger a split.

    Example: Splitting a selector string by combinators.

    // (input) => h1 h2>>h3
    const list = selectors.first.split(selector => {
        return selector.type === 'combinator';
    });
    
    // (node values) => [['h1', ' '], ['h2', '>>'], ['h3']]
  10. Process selectors with process() and processSync()

    main

    Use process() (asynchronous) or processSync() (synchronous) to transform a selector string or a PostCSS Rule node into a processed string.

    If updateSelector: true is set in the options and you pass a PostCSS Rule node, the node's selector will be updated in place.

    To normalize whitespace (e.g., converting ' .class ' to '.class'), set { lossless: false } in the options.

    const parser = require("postcss-selector-parser");
    const processor = parser();
    
    // Synchronous string processing
    let result = processor.processSync(' .class');
    // => .class
    
    // Asynchronous string processing
    let promise = processor.process(' .class').then(result => {
        console.log(result)
        // => .class
    });
    
    // Normalizing whitespace
    result = processor.processSync('  .class  ', {lossless: false});
    // => .class
    
    // Updating a PostCSS Rule node
    const postcss = require('postcss');
    const rule = postcss.rule({selector: ' #foo    > a,  .class  '});
    processor.process(rule, {lossless: false, updateSelector: true}).then(result => {
        console.log(result); // => #foo>a,.class
        console.log("rule:", rule.selector); // => rule: #foo>a,.class
    });