/** * Parse CSS following the {@link https://drafts.csswg.org/css-syntax/#parsing | CSS Syntax Level 3 specification}. * * @remarks * The tokenizing and parsing tools provided by CSS Tools are designed to be low level and generic with strong ties to their respective specifications. * * Any analysis or mutation of CSS source code should be done with the least powerful tool that can accomplish the task. * For many applications it is sufficient to work with tokens. * For others you might need to use {@link https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms | @csstools/css-parser-algorithms} or a more specific parser. * * The implementation of the AST nodes is kept lightweight and simple. * Do not expect magic methods, instead assume that arrays and class instances behave like any other JavaScript. * * @example * Parse a string of CSS into a component value: * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseComponentValue } from '@csstools/css-parser-algorithms'; * * const myCSS = `calc(1px * 2)`; * * const componentValue = parseComponentValue(tokenize({ * css: myCSS, * })); * * console.log(componentValue); * ``` * * @example * Use the right algorithm for the job. * * Algorithms that can parse larger structures (comma-separated lists, ...) can also parse smaller structures. * However, the opposite is not true. * * If your context allows a list of component values, use {@link parseListOfComponentValues}: * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseListOfComponentValues } from '@csstools/css-parser-algorithms'; * * parseComponentValue(tokenize({ css: `10x 20px` })); * ``` * * If your context allows a comma-separated list of component values, use {@link parseCommaSeparatedListOfComponentValues}: * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; * * parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` })); * ``` * * @example * Use the stateful walkers to keep track of the context of a given component value. * * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseComponentValue, isSimpleBlockNode } from '@csstools/css-parser-algorithms'; * * const myCSS = `calc(1px * (5 / 2))`; * * const componentValue = parseComponentValue(tokenize({ css: myCSS })); * * let state = { inSimpleBlock: false }; * componentValue.walk((entry) => { * if (isSimpleBlockNode(entry)) { * entry.state.inSimpleBlock = true; * return; * } * * if (entry.state.inSimpleBlock) { * console.log(entry.node.toString()); // `5`, ... * } * }, state); * ``` * * @packageDocumentation */ import { CSSToken } from '@csstools/css-tokenizer'; import { ParseError } from '@csstools/css-tokenizer'; import { TokenFunction } from '@csstools/css-tokenizer'; export declare class CommentNode { /** * The node type, always `ComponentValueType.Comment` */ type: ComponentValueType; /** * The comment token. */ value: CSSToken; constructor(value: CSSToken); /** * Retrieve the tokens for the current comment. * This is the inverse of parsing from a list of tokens. */ tokens(): Array; /** * Convert the current comment to a string. * This is not a true serialization. * It is purely a concatenation of the string representation of the tokens. */ toString(): string; /** * @internal * * A debug helper to convert the current object to a JSON representation. * This is useful in asserts and to store large ASTs in files. */ toJSON(): Record; /** * @internal */ isCommentNode(): this is CommentNode; /** * @internal */ static isCommentNode(x: unknown): x is CommentNode; } export declare type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode; export declare enum ComponentValueType { Function = "function", SimpleBlock = "simple-block", Whitespace = "whitespace", Comment = "comment", Token = "token" } export declare type ContainerNode = FunctionNode | SimpleBlockNode; export declare abstract class ContainerNodeBaseClass { /** * The contents of the `Function` or `Simple Block`. * This is a list of component values. */ value: Array; /** * Retrieve the index of the given item in the current node. * For most node types this will be trivially implemented as `this.value.indexOf(item)`. */ indexOf(item: ComponentValue): number | string; /** * Retrieve the item at the given index in the current node. * For most node types this will be trivially implemented as `this.value[index]`. */ at(index: number | string): ComponentValue | undefined; /** * Iterates over each item in the `value` array of the current node. * * @param cb - The callback function to execute for each item. * The function receives an object containing the current node (`node`), its parent (`parent`), * and an optional `state` object. * A second parameter is the index of the current node. * The function can return `false` to stop the iteration. * * @param state - An optional state object that can be used to pass additional information to the callback function. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration. * * @returns `false` if the iteration was halted, `undefined` otherwise. */ forEach, U extends ContainerNode>(this: U, cb: (entry: { node: ComponentValue; parent: ContainerNode; state?: T; }, index: number | string) => boolean | void, state?: T): false | undefined; /** * Walks the current node and all its children. * * @param cb - The callback function to execute for each item. * The function receives an object containing the current node (`node`), its parent (`parent`), * and an optional `state` object. * A second parameter is the index of the current node. * The function can return `false` to stop the iteration. * * @param state - An optional state object that can be used to pass additional information to the callback function. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration. * However changes are passed down to child node iterations. * * @returns `false` if the iteration was halted, `undefined` otherwise. */ walk, U extends ContainerNode>(this: U, cb: (entry: { node: ComponentValue; parent: ContainerNode; state?: T; }, index: number | string) => boolean | void, state?: T): false | undefined; } /** * Iterates over each item in a list of component values. * * @param cb - The callback function to execute for each item. * The function receives an object containing the current node (`node`), its parent (`parent`), * and an optional `state` object. * A second parameter is the index of the current node. * The function can return `false` to stop the iteration. * * @param state - An optional state object that can be used to pass additional information to the callback function. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration. * * @returns `false` if the iteration was halted, `undefined` otherwise. */ export declare function forEach>(componentValues: Array, cb: (entry: { node: ComponentValue; parent: ContainerNode | { value: Array; }; state?: T; }, index: number | string) => boolean | void, state?: T): false | undefined; /** * A function node. * * @example * ```js * const node = parseComponentValue(tokenize('calc(1 + 1)')); * * isFunctionNode(node); // true * node.getName(); // 'calc' * ``` */ export declare class FunctionNode extends ContainerNodeBaseClass { /** * The node type, always `ComponentValueType.Function` */ type: ComponentValueType; /** * The token for the name of the function. */ name: TokenFunction; /** * The token for the closing parenthesis of the function. * If the function is unclosed, this will be an EOF token. */ endToken: CSSToken; constructor(name: TokenFunction, endToken: CSSToken, value: Array); /** * Retrieve the name of the current function. * This is the parsed and unescaped name of the function. */ getName(): string; /** * Normalize the current function: * 1. if the "endToken" is EOF, replace with a ")-token" */ normalize(): void; /** * Retrieve the tokens for the current function. * This is the inverse of parsing from a list of tokens. */ tokens(): Array; /** * Convert the current function to a string. * This is not a true serialization. * It is purely a concatenation of the string representation of the tokens. */ toString(): string; /** * @internal * * A debug helper to convert the current object to a JSON representation. * This is useful in asserts and to store large ASTs in files. */ toJSON(): unknown; /** * @internal */ isFunctionNode(): this is FunctionNode; /** * @internal */ static isFunctionNode(x: unknown): x is FunctionNode; } /** * AST nodes do not have a `parent` property or method. * This makes it harder to traverse the AST upwards. * This function builds a `Map` that can be used to lookup ancestors of a node. * * @remarks * There is no magic behind this or the map it returns. * Mutating the AST will not update the map. * * Types are erased and any content of the map has type `unknown`. * If someone knows a clever way to type this, please let us know. * * @example * ```js * const ancestry = gatherNodeAncestry(mediaQuery); * mediaQuery.walk((entry) => { * const node = entry.node; // directly exposed * const parent = entry.parent; // directly exposed * const grandParent: unknown = ancestry.get(parent); // lookup * * console.log('node', node); * console.log('parent', parent); * console.log('grandParent', grandParent); * }); * ``` */ export declare function gatherNodeAncestry(node: { walk(cb: (entry: { node: Array | unknown; parent: unknown; }, index: number | string) => boolean | void): false | undefined; }): Map; /** * Check if the current object is a `CommentNode`. * This is a type guard. */ export declare function isCommentNode(x: unknown): x is CommentNode; /** * Check if the current object is a `FunctionNode`. * This is a type guard. */ export declare function isFunctionNode(x: unknown): x is FunctionNode; /** * Check if the current object is a `SimpleBlockNode`. * This is a type guard. */ export declare function isSimpleBlockNode(x: unknown): x is SimpleBlockNode; /** * Check if the current object is a `TokenNode`. * This is a type guard. */ export declare function isTokenNode(x: unknown): x is TokenNode; /** * Check if the current object is a `WhitespaceNode`. * This is a type guard. */ export declare function isWhitespaceNode(x: unknown): x is WhitespaceNode; /** * Parse a comma-separated list of component values. * * @example * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; * * parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` })); * ``` */ export declare function parseCommaSeparatedListOfComponentValues(tokens: Array, options?: { onParseError?: (error: ParseError) => void; }): Array>; /** * Parse a single component value. * * @example * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms'; * * parseCommaSeparatedListOfComponentValues(tokenize({ css: `10px` })); * parseCommaSeparatedListOfComponentValues(tokenize({ css: `calc((10px + 1x) * 4)` })); * ``` */ export declare function parseComponentValue(tokens: Array, options?: { onParseError?: (error: ParseError) => void; }): ComponentValue | undefined; /** * Parse a list of component values. * * @example * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseListOfComponentValues } from '@csstools/css-parser-algorithms'; * * parseListOfComponentValues(tokenize({ css: `20deg 30%` })); * ``` */ export declare function parseListOfComponentValues(tokens: Array, options?: { onParseError?: (error: ParseError) => void; }): Array; /** * Replace specific component values in a list of component values. * A helper for the most common and simplistic cases when mutating an AST. */ export declare function replaceComponentValues(componentValuesList: Array>, replaceWith: (componentValue: ComponentValue) => Array | ComponentValue | void): Array>; /** * A simple block node. * * @example * ```js * const node = parseComponentValue(tokenize('[foo=bar]')); * * isSimpleBlockNode(node); // true * node.startToken; // [TokenType.OpenSquare, '[', 0, 0, undefined] * ``` */ export declare class SimpleBlockNode extends ContainerNodeBaseClass { /** * The node type, always `ComponentValueType.SimpleBlock` */ type: ComponentValueType; /** * The token for the opening token of the block. */ startToken: CSSToken; /** * The token for the closing token of the block. * If the block is closed it will be the mirror variant of the `startToken`. * If the block is unclosed, this will be an EOF token. */ endToken: CSSToken; constructor(startToken: CSSToken, endToken: CSSToken, value: Array); /** * Normalize the current simple block * 1. if the "endToken" is EOF, replace with the mirror token of the "startToken" */ normalize(): void; /** * Retrieve the tokens for the current simple block. * This is the inverse of parsing from a list of tokens. */ tokens(): Array; /** * Convert the current simple block to a string. * This is not a true serialization. * It is purely a concatenation of the string representation of the tokens. */ toString(): string; /** * @internal * * A debug helper to convert the current object to a JSON representation. * This is useful in asserts and to store large ASTs in files. */ toJSON(): unknown; /** * @internal */ isSimpleBlockNode(): this is SimpleBlockNode; /** * @internal */ static isSimpleBlockNode(x: unknown): x is SimpleBlockNode; } /** * Returns the start and end index of a node in the CSS source string. */ export declare function sourceIndices(x: { tokens(): Array; } | Array<{ tokens(): Array; }>): [number, number]; /** * Concatenate the string representation of a collection of component values. * This is not a proper serializer that will handle escaping and whitespace. * It only produces valid CSS for token lists that are also valid. */ export declare function stringify(componentValueLists: Array>): string; export declare class TokenNode { /** * The node type, always `ComponentValueType.Token` */ type: ComponentValueType; /** * The token. */ value: CSSToken; constructor(value: CSSToken); /** * This is the inverse of parsing from a list of tokens. */ tokens(): Array; /** * Convert the current token to a string. * This is not a true serialization. * It is purely the string representation of token. */ toString(): string; /** * @internal * * A debug helper to convert the current object to a JSON representation. * This is useful in asserts and to store large ASTs in files. */ toJSON(): Record; /** * @internal */ isTokenNode(): this is TokenNode; /** * @internal */ static isTokenNode(x: unknown): x is TokenNode; } /** * Walks each item in a list of component values all of their children. * * @param cb - The callback function to execute for each item. * The function receives an object containing the current node (`node`), its parent (`parent`), * and an optional `state` object. * A second parameter is the index of the current node. * The function can return `false` to stop the iteration. * * @param state - An optional state object that can be used to pass additional information to the callback function. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration. * However changes are passed down to child node iterations. * * @returns `false` if the iteration was halted, `undefined` otherwise. * * @example * ```js * import { tokenize } from '@csstools/css-tokenizer'; * import { parseListOfComponentValues, isSimpleBlockNode } from '@csstools/css-parser-algorithms'; * * const myCSS = `calc(1px * (5 / 2)) 10px`; * * const componentValues = parseListOfComponentValues(tokenize({ css: myCSS })); * * let state = { inSimpleBlock: false }; * walk(componentValues, (entry) => { * if (isSimpleBlockNode(entry)) { * entry.state.inSimpleBlock = true; * return; * } * * if (entry.state.inSimpleBlock) { * console.log(entry.node.toString()); // `5`, ... * } * }, state); * ``` */ export declare function walk>(componentValues: Array, cb: (entry: { node: ComponentValue; parent: ContainerNode | { value: Array; }; state?: T; }, index: number | string) => boolean | void, state?: T): false | undefined; /** * Generate a function that finds the next element that should be visited when walking an AST. * Rules : * 1. the previous iteration is used as a reference, so any checks are relative to the start of the current iteration. * 2. the next element always appears after the current index. * 3. the next element always exists in the list. * 4. replacing an element does not cause the replaced element to be visited. * 5. removing an element does not cause elements to be skipped. * 6. an element added later in the list will be visited. */ export declare function walkerIndexGenerator(initialList: Array): (list: Array, child: T, index: number) => number; export declare class WhitespaceNode { /** * The node type, always `ComponentValueType.WhiteSpace` */ type: ComponentValueType; /** * The list of consecutive whitespace tokens. */ value: Array; constructor(value: Array); /** * Retrieve the tokens for the current whitespace. * This is the inverse of parsing from a list of tokens. */ tokens(): Array; /** * Convert the current whitespace to a string. * This is not a true serialization. * It is purely a concatenation of the string representation of the tokens. */ toString(): string; /** * @internal * * A debug helper to convert the current object to a JSON representation. * This is useful in asserts and to store large ASTs in files. */ toJSON(): Record; /** * @internal */ isWhitespaceNode(): this is WhitespaceNode; /** * @internal */ static isWhitespaceNode(x: unknown): x is WhitespaceNode; } export { }