@csstools/css-parser-algorithms
Advanced tools
Comparing version
# Changes to CSS Parser Algorithms | ||
### 2.5.0 | ||
_December 31, 2023_ | ||
- Add a `forEach` and `walk` function. | ||
- Improve documentation. | ||
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`2.2.3`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#223) (patch) | ||
### 2.4.0 | ||
@@ -19,8 +27,2 @@ | ||
### 2.3.1 | ||
_July 24, 2023_ | ||
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`2.2.0`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#220) (minor) | ||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms/CHANGELOG.md) |
@@ -0,1 +1,78 @@ | ||
/** | ||
* 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'; | ||
* | ||
* 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'; | ||
* | ||
* 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'; | ||
* | ||
* 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'; | ||
* | ||
* 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'; | ||
@@ -6,7 +83,28 @@ import { ParseError } 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(): { | ||
@@ -16,3 +114,9 @@ type: ComponentValueType; | ||
}; | ||
/** | ||
* @internal | ||
*/ | ||
isCommentNode(): this is CommentNode; | ||
/** | ||
* @internal | ||
*/ | ||
static isCommentNode(x: unknown): x is CommentNode; | ||
@@ -62,5 +166,2 @@ } | ||
* @returns `false` if the iteration was halted, `undefined` otherwise. | ||
* | ||
* @template T - The type of the `state` object. | ||
* @template U - The type of the current node. | ||
*/ | ||
@@ -86,5 +187,2 @@ forEach<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: { | ||
* @returns `false` if the iteration was halted, `undefined` otherwise. | ||
* | ||
* @template T - The type of the `state` object. | ||
* @template U - The type of the current node. | ||
*/ | ||
@@ -98,6 +196,38 @@ walk<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: { | ||
/** | ||
* 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` | ||
* The node type, always `ComponentValueType.Function` | ||
*/ | ||
@@ -116,3 +246,3 @@ type: ComponentValueType; | ||
/** | ||
* Retrieve the name of the current Function. | ||
* Retrieve the name of the current function. | ||
* This is the parsed and unescaped name of the function. | ||
@@ -122,8 +252,8 @@ */ | ||
/** | ||
* Normalize the current Function: | ||
* - if the "endToken" is EOF, replace with a ")-token" | ||
* Normalize the current function: | ||
* 1. if the "endToken" is EOF, replace with a ")-token" | ||
*/ | ||
normalize(): void; | ||
/** | ||
* Retrieve the tokens for the current Function. | ||
* Retrieve the tokens for the current function. | ||
* This is the inverse of parsing from a list of tokens. | ||
@@ -133,3 +263,3 @@ */ | ||
/** | ||
* Convert the current Function to a string. | ||
* Convert the current function to a string. | ||
* This is not a true serialization. | ||
@@ -140,2 +270,4 @@ * It is purely a concatenation of the string representation of the tokens. | ||
/** | ||
* @internal | ||
* | ||
* A debug helper to convert the current object to a JSON representation. | ||
@@ -146,9 +278,7 @@ * This is useful in asserts and to store large ASTs in files. | ||
/** | ||
* Check if the current object is a FunctionNode. | ||
* This is a type guard to help with type narrowing. | ||
* @internal | ||
*/ | ||
isFunctionNode(): this is FunctionNode; | ||
/** | ||
* Check if the given object is a FunctionNode. | ||
* This is a type guard to help with type narrowing. | ||
* @internal | ||
*/ | ||
@@ -158,2 +288,28 @@ 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: { | ||
@@ -166,12 +322,43 @@ walk(cb: (entry: { | ||
/** | ||
* 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'; | ||
* | ||
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` })); | ||
* ``` | ||
*/ | ||
export declare function parseCommaSeparatedListOfComponentValues(tokens: Array<CSSToken>, options?: { | ||
@@ -181,2 +368,14 @@ onParseError?: (error: ParseError) => void; | ||
/** | ||
* Parse a single component value. | ||
* | ||
* @example | ||
* ```js | ||
* import { tokenize } from '@csstools/css-tokenizer'; | ||
* import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser'; | ||
* | ||
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `10px` })); | ||
* parseCommaSeparatedListOfComponentValues(tokenize({ css: `calc((10px + 1x) * 4)` })); | ||
* ``` | ||
*/ | ||
export declare function parseComponentValue(tokens: Array<CSSToken>, options?: { | ||
@@ -186,2 +385,13 @@ onParseError?: (error: ParseError) => void; | ||
/** | ||
* Parse a list of component values. | ||
* | ||
* @example | ||
* ```js | ||
* import { tokenize } from '@csstools/css-tokenizer'; | ||
* import { parseListOfComponentValues } from '@csstools/css-parser'; | ||
* | ||
* parseListOfComponentValues(tokenize({ css: `20deg 30%` })); | ||
* ``` | ||
*/ | ||
export declare function parseListOfComponentValues(tokens: Array<CSSToken>, options?: { | ||
@@ -191,20 +401,65 @@ onParseError?: (error: ParseError) => void; | ||
/** | ||
* 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) => ComponentValue | void): 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: | ||
* - if the "endToken" is EOF, replace with the mirror token of the "startToken" | ||
* 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; | ||
indexOf(item: ComponentValue): number | string; | ||
at(index: number | string): ComponentValue | undefined; | ||
/** | ||
* @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; | ||
@@ -222,10 +477,35 @@ } | ||
/** | ||
* 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(): Array<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(): { | ||
@@ -235,3 +515,9 @@ type: ComponentValueType; | ||
}; | ||
/** | ||
* @internal | ||
*/ | ||
isTokenNode(): this is TokenNode; | ||
/** | ||
* @internal | ||
*/ | ||
static isTokenNode(x: unknown): x is TokenNode; | ||
@@ -241,10 +527,55 @@ } | ||
/** | ||
* 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'; | ||
* | ||
* 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 : | ||
* - the previous iteration is used as a reference, so any checks are relative to the start of the current iteration. | ||
* - the next element always appears after the current index. | ||
* - the next element always exists in the list. | ||
* - replacing an element does not cause the replaced element to be visited. | ||
* - removing an element does not cause elements to be skipped. | ||
* - an element added later in the list will be visited. | ||
* 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. | ||
*/ | ||
@@ -254,7 +585,28 @@ 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(): { | ||
@@ -264,3 +616,9 @@ type: ComponentValueType; | ||
}; | ||
/** | ||
* @internal | ||
*/ | ||
isWhitespaceNode(): this is WhitespaceNode; | ||
/** | ||
* @internal | ||
*/ | ||
static isWhitespaceNode(x: unknown): x is WhitespaceNode; | ||
@@ -267,0 +625,0 @@ } |
{ | ||
"name": "@csstools/css-parser-algorithms", | ||
"description": "Algorithms to help you parse CSS from an array of tokens.", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"contributors": [ | ||
@@ -51,3 +51,3 @@ { | ||
"peerDependencies": { | ||
"@csstools/css-tokenizer": "^2.2.2" | ||
"@csstools/css-tokenizer": "^2.2.3" | ||
}, | ||
@@ -54,0 +54,0 @@ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms#readme", |
@@ -9,2 +9,6 @@ # CSS Parser Algorithms | ||
## API | ||
[Read the API docs](./docs/css-parser-algorithms.md) | ||
## Usage | ||
@@ -11,0 +15,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
45912
32.74%653
120.61%120
3.45%0
-100%