index.d.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. /**
  2. * Parse CSS following the {@link https://drafts.csswg.org/css-syntax/#parsing | CSS Syntax Level 3 specification}.
  3. *
  4. * @remarks
  5. * The tokenizing and parsing tools provided by CSS Tools are designed to be low level and generic with strong ties to their respective specifications.
  6. *
  7. * Any analysis or mutation of CSS source code should be done with the least powerful tool that can accomplish the task.
  8. * For many applications it is sufficient to work with tokens.
  9. * 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.
  10. *
  11. * The implementation of the AST nodes is kept lightweight and simple.
  12. * Do not expect magic methods, instead assume that arrays and class instances behave like any other JavaScript.
  13. *
  14. * @example
  15. * Parse a string of CSS into a component value:
  16. * ```js
  17. * import { tokenize } from '@csstools/css-tokenizer';
  18. * import { parseComponentValue } from '@csstools/css-parser-algorithms';
  19. *
  20. * const myCSS = `calc(1px * 2)`;
  21. *
  22. * const componentValue = parseComponentValue(tokenize({
  23. * css: myCSS,
  24. * }));
  25. *
  26. * console.log(componentValue);
  27. * ```
  28. *
  29. * @example
  30. * Use the right algorithm for the job.
  31. *
  32. * Algorithms that can parse larger structures (comma-separated lists, ...) can also parse smaller structures.
  33. * However, the opposite is not true.
  34. *
  35. * If your context allows a list of component values, use {@link parseListOfComponentValues}:
  36. * ```js
  37. * import { tokenize } from '@csstools/css-tokenizer';
  38. * import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
  39. *
  40. * parseListOfComponentValues(tokenize({ css: `10x 20px` }));
  41. * ```
  42. *
  43. * If your context allows a comma-separated list of component values, use {@link parseCommaSeparatedListOfComponentValues}:
  44. * ```js
  45. * import { tokenize } from '@csstools/css-tokenizer';
  46. * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
  47. *
  48. * parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
  49. * ```
  50. *
  51. * @example
  52. * Use the stateful walkers to keep track of the context of a given component value.
  53. *
  54. * ```js
  55. * import { tokenize } from '@csstools/css-tokenizer';
  56. * import { parseComponentValue, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
  57. *
  58. * const myCSS = `calc(1px * (5 / 2))`;
  59. *
  60. * const componentValue = parseComponentValue(tokenize({ css: myCSS }));
  61. *
  62. * let state = { inSimpleBlock: false };
  63. * componentValue.walk((entry) => {
  64. * if (isSimpleBlockNode(entry)) {
  65. * entry.state.inSimpleBlock = true;
  66. * return;
  67. * }
  68. *
  69. * if (entry.state.inSimpleBlock) {
  70. * console.log(entry.node.toString()); // `5`, ...
  71. * }
  72. * }, state);
  73. * ```
  74. *
  75. * @packageDocumentation
  76. */
  77. import type { CSSToken } from '@csstools/css-tokenizer';
  78. import { ParseError } from '@csstools/css-tokenizer';
  79. import type { TokenFunction } from '@csstools/css-tokenizer';
  80. export declare class CommentNode {
  81. /**
  82. * The node type, always `ComponentValueType.Comment`
  83. */
  84. type: ComponentValueType;
  85. /**
  86. * The comment token.
  87. */
  88. value: CSSToken;
  89. constructor(value: CSSToken);
  90. /**
  91. * Retrieve the tokens for the current comment.
  92. * This is the inverse of parsing from a list of tokens.
  93. */
  94. tokens(): Array<CSSToken>;
  95. /**
  96. * Convert the current comment to a string.
  97. * This is not a true serialization.
  98. * It is purely a concatenation of the string representation of the tokens.
  99. */
  100. toString(): string;
  101. /**
  102. * @internal
  103. *
  104. * A debug helper to convert the current object to a JSON representation.
  105. * This is useful in asserts and to store large ASTs in files.
  106. */
  107. toJSON(): Record<string, unknown>;
  108. /**
  109. * @internal
  110. */
  111. isCommentNode(): this is CommentNode;
  112. /**
  113. * @internal
  114. */
  115. static isCommentNode(x: unknown): x is CommentNode;
  116. }
  117. export declare type ComponentValue = FunctionNode | SimpleBlockNode | WhitespaceNode | CommentNode | TokenNode;
  118. export declare enum ComponentValueType {
  119. Function = "function",
  120. SimpleBlock = "simple-block",
  121. Whitespace = "whitespace",
  122. Comment = "comment",
  123. Token = "token"
  124. }
  125. export declare type ContainerNode = FunctionNode | SimpleBlockNode;
  126. export declare abstract class ContainerNodeBaseClass {
  127. /**
  128. * The contents of the `Function` or `Simple Block`.
  129. * This is a list of component values.
  130. */
  131. value: Array<ComponentValue>;
  132. /**
  133. * Retrieve the index of the given item in the current node.
  134. * For most node types this will be trivially implemented as `this.value.indexOf(item)`.
  135. */
  136. indexOf(item: ComponentValue): number | string;
  137. /**
  138. * Retrieve the item at the given index in the current node.
  139. * For most node types this will be trivially implemented as `this.value[index]`.
  140. */
  141. at(index: number | string): ComponentValue | undefined;
  142. /**
  143. * Iterates over each item in the `value` array of the current node.
  144. *
  145. * @param cb - The callback function to execute for each item.
  146. * The function receives an object containing the current node (`node`), its parent (`parent`),
  147. * and an optional `state` object.
  148. * A second parameter is the index of the current node.
  149. * The function can return `false` to stop the iteration.
  150. *
  151. * @param state - An optional state object that can be used to pass additional information to the callback function.
  152. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
  153. *
  154. * @returns `false` if the iteration was halted, `undefined` otherwise.
  155. */
  156. forEach<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
  157. node: ComponentValue;
  158. parent: ContainerNode;
  159. state?: T;
  160. }, index: number | string) => boolean | void, state?: T): false | undefined;
  161. /**
  162. * Walks the current node and all its children.
  163. *
  164. * @param cb - The callback function to execute for each item.
  165. * The function receives an object containing the current node (`node`), its parent (`parent`),
  166. * and an optional `state` object.
  167. * A second parameter is the index of the current node.
  168. * The function can return `false` to stop the iteration.
  169. *
  170. * @param state - An optional state object that can be used to pass additional information to the callback function.
  171. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
  172. * However changes are passed down to child node iterations.
  173. *
  174. * @returns `false` if the iteration was halted, `undefined` otherwise.
  175. */
  176. walk<T extends Record<string, unknown>, U extends ContainerNode>(this: U, cb: (entry: {
  177. node: ComponentValue;
  178. parent: ContainerNode;
  179. state?: T;
  180. }, index: number | string) => boolean | void, state?: T): false | undefined;
  181. }
  182. /**
  183. * Iterates over each item in a list of component values.
  184. *
  185. * @param cb - The callback function to execute for each item.
  186. * The function receives an object containing the current node (`node`), its parent (`parent`),
  187. * and an optional `state` object.
  188. * A second parameter is the index of the current node.
  189. * The function can return `false` to stop the iteration.
  190. *
  191. * @param state - An optional state object that can be used to pass additional information to the callback function.
  192. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
  193. *
  194. * @returns `false` if the iteration was halted, `undefined` otherwise.
  195. */
  196. export declare function forEach<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
  197. node: ComponentValue;
  198. parent: ContainerNode | {
  199. value: Array<ComponentValue>;
  200. };
  201. state?: T;
  202. }, index: number | string) => boolean | void, state?: T): false | undefined;
  203. /**
  204. * A function node.
  205. *
  206. * @example
  207. * ```js
  208. * const node = parseComponentValue(tokenize('calc(1 + 1)'));
  209. *
  210. * isFunctionNode(node); // true
  211. * node.getName(); // 'calc'
  212. * ```
  213. */
  214. export declare class FunctionNode extends ContainerNodeBaseClass {
  215. /**
  216. * The node type, always `ComponentValueType.Function`
  217. */
  218. type: ComponentValueType;
  219. /**
  220. * The token for the name of the function.
  221. */
  222. name: TokenFunction;
  223. /**
  224. * The token for the closing parenthesis of the function.
  225. * If the function is unclosed, this will be an EOF token.
  226. */
  227. endToken: CSSToken;
  228. constructor(name: TokenFunction, endToken: CSSToken, value: Array<ComponentValue>);
  229. /**
  230. * Retrieve the name of the current function.
  231. * This is the parsed and unescaped name of the function.
  232. */
  233. getName(): string;
  234. /**
  235. * Normalize the current function:
  236. * 1. if the "endToken" is EOF, replace with a ")-token"
  237. */
  238. normalize(): void;
  239. /**
  240. * Retrieve the tokens for the current function.
  241. * This is the inverse of parsing from a list of tokens.
  242. */
  243. tokens(): Array<CSSToken>;
  244. /**
  245. * Convert the current function to a string.
  246. * This is not a true serialization.
  247. * It is purely a concatenation of the string representation of the tokens.
  248. */
  249. toString(): string;
  250. /**
  251. * @internal
  252. *
  253. * A debug helper to convert the current object to a JSON representation.
  254. * This is useful in asserts and to store large ASTs in files.
  255. */
  256. toJSON(): unknown;
  257. /**
  258. * @internal
  259. */
  260. isFunctionNode(): this is FunctionNode;
  261. /**
  262. * @internal
  263. */
  264. static isFunctionNode(x: unknown): x is FunctionNode;
  265. }
  266. /**
  267. * AST nodes do not have a `parent` property or method.
  268. * This makes it harder to traverse the AST upwards.
  269. * This function builds a `Map<Child, Parent>` that can be used to lookup ancestors of a node.
  270. *
  271. * @remarks
  272. * There is no magic behind this or the map it returns.
  273. * Mutating the AST will not update the map.
  274. *
  275. * Types are erased and any content of the map has type `unknown`.
  276. * If someone knows a clever way to type this, please let us know.
  277. *
  278. * @example
  279. * ```js
  280. * const ancestry = gatherNodeAncestry(mediaQuery);
  281. * mediaQuery.walk((entry) => {
  282. * const node = entry.node; // directly exposed
  283. * const parent = entry.parent; // directly exposed
  284. * const grandParent: unknown = ancestry.get(parent); // lookup
  285. *
  286. * console.log('node', node);
  287. * console.log('parent', parent);
  288. * console.log('grandParent', grandParent);
  289. * });
  290. * ```
  291. */
  292. export declare function gatherNodeAncestry(node: {
  293. walk(cb: (entry: {
  294. node: unknown;
  295. parent: unknown;
  296. }, index: number | string) => boolean | void): false | undefined;
  297. }): Map<unknown, unknown>;
  298. /**
  299. * Check if the current object is a `CommentNode`.
  300. * This is a type guard.
  301. */
  302. export declare function isCommentNode(x: unknown): x is CommentNode;
  303. /**
  304. * Check if the current object is a `FunctionNode`.
  305. * This is a type guard.
  306. */
  307. export declare function isFunctionNode(x: unknown): x is FunctionNode;
  308. /**
  309. * Check if the current object is a `SimpleBlockNode`.
  310. * This is a type guard.
  311. */
  312. export declare function isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
  313. /**
  314. * Check if the current object is a `TokenNode`.
  315. * This is a type guard.
  316. */
  317. export declare function isTokenNode(x: unknown): x is TokenNode;
  318. /**
  319. * Check if the current object is a `WhitespaceNode`.
  320. * This is a type guard.
  321. */
  322. export declare function isWhitespaceNode(x: unknown): x is WhitespaceNode;
  323. /**
  324. * Check if the current object is a `WhiteSpaceNode` or a `CommentNode`.
  325. * This is a type guard.
  326. */
  327. export declare function isWhiteSpaceOrCommentNode(x: unknown): x is WhitespaceNode | CommentNode;
  328. /**
  329. * Parse a comma-separated list of component values.
  330. *
  331. * @example
  332. * ```js
  333. * import { tokenize } from '@csstools/css-tokenizer';
  334. * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
  335. *
  336. * parseCommaSeparatedListOfComponentValues(tokenize({ css: `20deg, 50%, 30%` }));
  337. * ```
  338. */
  339. export declare function parseCommaSeparatedListOfComponentValues(tokens: Array<CSSToken>, options?: {
  340. onParseError?: (error: ParseError) => void;
  341. }): Array<Array<ComponentValue>>;
  342. /**
  343. * Parse a single component value.
  344. *
  345. * @example
  346. * ```js
  347. * import { tokenize } from '@csstools/css-tokenizer';
  348. * import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
  349. *
  350. * parseCommaSeparatedListOfComponentValues(tokenize({ css: `10px` }));
  351. * parseCommaSeparatedListOfComponentValues(tokenize({ css: `calc((10px + 1x) * 4)` }));
  352. * ```
  353. */
  354. export declare function parseComponentValue(tokens: Array<CSSToken>, options?: {
  355. onParseError?: (error: ParseError) => void;
  356. }): ComponentValue | undefined;
  357. /**
  358. * Parse a list of component values.
  359. *
  360. * @example
  361. * ```js
  362. * import { tokenize } from '@csstools/css-tokenizer';
  363. * import { parseListOfComponentValues } from '@csstools/css-parser-algorithms';
  364. *
  365. * parseListOfComponentValues(tokenize({ css: `20deg 30%` }));
  366. * ```
  367. */
  368. export declare function parseListOfComponentValues(tokens: Array<CSSToken>, options?: {
  369. onParseError?: (error: ParseError) => void;
  370. }): Array<ComponentValue>;
  371. /**
  372. * Replace specific component values in a list of component values.
  373. * A helper for the most common and simplistic cases when mutating an AST.
  374. */
  375. export declare function replaceComponentValues(componentValuesList: Array<Array<ComponentValue>>, replaceWith: (componentValue: ComponentValue) => Array<ComponentValue> | ComponentValue | void): Array<Array<ComponentValue>>;
  376. /**
  377. * A simple block node.
  378. *
  379. * @example
  380. * ```js
  381. * const node = parseComponentValue(tokenize('[foo=bar]'));
  382. *
  383. * isSimpleBlockNode(node); // true
  384. * node.startToken; // [TokenType.OpenSquare, '[', 0, 0, undefined]
  385. * ```
  386. */
  387. export declare class SimpleBlockNode extends ContainerNodeBaseClass {
  388. /**
  389. * The node type, always `ComponentValueType.SimpleBlock`
  390. */
  391. type: ComponentValueType;
  392. /**
  393. * The token for the opening token of the block.
  394. */
  395. startToken: CSSToken;
  396. /**
  397. * The token for the closing token of the block.
  398. * If the block is closed it will be the mirror variant of the `startToken`.
  399. * If the block is unclosed, this will be an EOF token.
  400. */
  401. endToken: CSSToken;
  402. constructor(startToken: CSSToken, endToken: CSSToken, value: Array<ComponentValue>);
  403. /**
  404. * Normalize the current simple block
  405. * 1. if the "endToken" is EOF, replace with the mirror token of the "startToken"
  406. */
  407. normalize(): void;
  408. /**
  409. * Retrieve the tokens for the current simple block.
  410. * This is the inverse of parsing from a list of tokens.
  411. */
  412. tokens(): Array<CSSToken>;
  413. /**
  414. * Convert the current simple block to a string.
  415. * This is not a true serialization.
  416. * It is purely a concatenation of the string representation of the tokens.
  417. */
  418. toString(): string;
  419. /**
  420. * @internal
  421. *
  422. * A debug helper to convert the current object to a JSON representation.
  423. * This is useful in asserts and to store large ASTs in files.
  424. */
  425. toJSON(): unknown;
  426. /**
  427. * @internal
  428. */
  429. isSimpleBlockNode(): this is SimpleBlockNode;
  430. /**
  431. * @internal
  432. */
  433. static isSimpleBlockNode(x: unknown): x is SimpleBlockNode;
  434. }
  435. /**
  436. * Returns the start and end index of a node in the CSS source string.
  437. */
  438. export declare function sourceIndices(x: {
  439. tokens(): Array<CSSToken>;
  440. } | Array<{
  441. tokens(): Array<CSSToken>;
  442. }>): [number, number];
  443. /**
  444. * Concatenate the string representation of a collection of component values.
  445. * This is not a proper serializer that will handle escaping and whitespace.
  446. * It only produces valid CSS for token lists that are also valid.
  447. */
  448. export declare function stringify(componentValueLists: Array<Array<ComponentValue>>): string;
  449. export declare class TokenNode {
  450. /**
  451. * The node type, always `ComponentValueType.Token`
  452. */
  453. type: ComponentValueType;
  454. /**
  455. * The token.
  456. */
  457. value: CSSToken;
  458. constructor(value: CSSToken);
  459. /**
  460. * This is the inverse of parsing from a list of tokens.
  461. */
  462. tokens(): [CSSToken];
  463. /**
  464. * Convert the current token to a string.
  465. * This is not a true serialization.
  466. * It is purely the string representation of token.
  467. */
  468. toString(): string;
  469. /**
  470. * @internal
  471. *
  472. * A debug helper to convert the current object to a JSON representation.
  473. * This is useful in asserts and to store large ASTs in files.
  474. */
  475. toJSON(): Record<string, unknown>;
  476. /**
  477. * @internal
  478. */
  479. isTokenNode(): this is TokenNode;
  480. /**
  481. * @internal
  482. */
  483. static isTokenNode(x: unknown): x is TokenNode;
  484. }
  485. /**
  486. * Walks each item in a list of component values all of their children.
  487. *
  488. * @param cb - The callback function to execute for each item.
  489. * The function receives an object containing the current node (`node`), its parent (`parent`),
  490. * and an optional `state` object.
  491. * A second parameter is the index of the current node.
  492. * The function can return `false` to stop the iteration.
  493. *
  494. * @param state - An optional state object that can be used to pass additional information to the callback function.
  495. * The state object is cloned for each iteration. This means that changes to the state object are not reflected in the next iteration.
  496. * However changes are passed down to child node iterations.
  497. *
  498. * @returns `false` if the iteration was halted, `undefined` otherwise.
  499. *
  500. * @example
  501. * ```js
  502. * import { tokenize } from '@csstools/css-tokenizer';
  503. * import { parseListOfComponentValues, isSimpleBlockNode } from '@csstools/css-parser-algorithms';
  504. *
  505. * const myCSS = `calc(1px * (5 / 2)) 10px`;
  506. *
  507. * const componentValues = parseListOfComponentValues(tokenize({ css: myCSS }));
  508. *
  509. * let state = { inSimpleBlock: false };
  510. * walk(componentValues, (entry) => {
  511. * if (isSimpleBlockNode(entry)) {
  512. * entry.state.inSimpleBlock = true;
  513. * return;
  514. * }
  515. *
  516. * if (entry.state.inSimpleBlock) {
  517. * console.log(entry.node.toString()); // `5`, ...
  518. * }
  519. * }, state);
  520. * ```
  521. */
  522. export declare function walk<T extends Record<string, unknown>>(componentValues: Array<ComponentValue>, cb: (entry: {
  523. node: ComponentValue;
  524. parent: ContainerNode | {
  525. value: Array<ComponentValue>;
  526. };
  527. state?: T;
  528. }, index: number | string) => boolean | void, state?: T): false | undefined;
  529. /**
  530. * Generate a function that finds the next element that should be visited when walking an AST.
  531. * Rules :
  532. * 1. the previous iteration is used as a reference, so any checks are relative to the start of the current iteration.
  533. * 2. the next element always appears after the current index.
  534. * 3. the next element always exists in the list.
  535. * 4. replacing an element does not cause the replaced element to be visited.
  536. * 5. removing an element does not cause elements to be skipped.
  537. * 6. an element added later in the list will be visited.
  538. */
  539. export declare function walkerIndexGenerator<T>(initialList: Array<T>): (list: Array<T>, child: T, index: number) => number;
  540. export declare class WhitespaceNode {
  541. /**
  542. * The node type, always `ComponentValueType.WhiteSpace`
  543. */
  544. type: ComponentValueType;
  545. /**
  546. * The list of consecutive whitespace tokens.
  547. */
  548. value: Array<CSSToken>;
  549. constructor(value: Array<CSSToken>);
  550. /**
  551. * Retrieve the tokens for the current whitespace.
  552. * This is the inverse of parsing from a list of tokens.
  553. */
  554. tokens(): Array<CSSToken>;
  555. /**
  556. * Convert the current whitespace to a string.
  557. * This is not a true serialization.
  558. * It is purely a concatenation of the string representation of the tokens.
  559. */
  560. toString(): string;
  561. /**
  562. * @internal
  563. *
  564. * A debug helper to convert the current object to a JSON representation.
  565. * This is useful in asserts and to store large ASTs in files.
  566. */
  567. toJSON(): Record<string, unknown>;
  568. /**
  569. * @internal
  570. */
  571. isWhitespaceNode(): this is WhitespaceNode;
  572. /**
  573. * @internal
  574. */
  575. static isWhitespaceNode(x: unknown): x is WhitespaceNode;
  576. }
  577. export { }