123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- /**
- * 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';
- *
- * parseListOfComponentValues(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 type { CSSToken } from '@csstools/css-tokenizer';
- import { ParseError } from '@csstools/css-tokenizer';
- import type { 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<CSSToken>;
- /**
- * 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<string, unknown>;
- /**
- * @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<ComponentValue>;
- /**
- * 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<T extends Record<string, unknown>, 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<T extends Record<string, unknown>, 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<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
- node: ComponentValue;
- parent: ContainerNode | {
- value: Array<ComponentValue>;
- };
- 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<ComponentValue>);
- /**
- * 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<CSSToken>;
- /**
- * 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<Child, Parent>` 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: unknown;
- parent: unknown;
- }, index: number | string) => boolean | void): false | undefined;
- }): Map<unknown, unknown>;
- /**
- * 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;
- /**
- * Check if the current object is a `WhiteSpaceNode` or a `CommentNode`.
- * This is a type guard.
- */
- export declare function isWhiteSpaceOrCommentNode(x: unknown): x is WhitespaceNode | CommentNode;
- /**
- * 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<CSSToken>, options?: {
- onParseError?: (error: ParseError) => void;
- }): Array<Array<ComponentValue>>;
- /**
- * 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<CSSToken>, 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<CSSToken>, options?: {
- onParseError?: (error: ParseError) => void;
- }): Array<ComponentValue>;
- /**
- * 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<Array<ComponentValue>>, replaceWith: (componentValue: ComponentValue) => Array<ComponentValue> | ComponentValue | void): Array<Array<ComponentValue>>;
- /**
- * 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<ComponentValue>);
- /**
- * 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<CSSToken>;
- /**
- * 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<CSSToken>;
- } | Array<{
- tokens(): Array<CSSToken>;
- }>): [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<Array<ComponentValue>>): 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(): [CSSToken];
- /**
- * 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<string, unknown>;
- /**
- * @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<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
- node: ComponentValue;
- parent: ContainerNode | {
- value: Array<ComponentValue>;
- };
- 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<T>(initialList: Array<T>): (list: Array<T>, 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<CSSToken>;
- constructor(value: Array<CSSToken>);
- /**
- * Retrieve the tokens for the current whitespace.
- * This is the inverse of parsing from a list of tokens.
- */
- tokens(): Array<CSSToken>;
- /**
- * 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<string, unknown>;
- /**
- * @internal
- */
- isWhitespaceNode(): this is WhitespaceNode;
- /**
- * @internal
- */
- static isWhitespaceNode(x: unknown): x is WhitespaceNode;
- }
- export { }
|