(input: string)
| 156 | const ASTERISK = 0x2a |
| 157 | |
| 158 | export function parse(input: string) { |
| 159 | input = input.replaceAll('\r\n', '\n') |
| 160 | |
| 161 | let ast: SelectorAstNode[] = [] |
| 162 | |
| 163 | let target = ast |
| 164 | |
| 165 | let containsCombinator = false |
| 166 | |
| 167 | let contextStack: { |
| 168 | target: SelectorAstNode[] |
| 169 | currentList: SelectorListNode | null |
| 170 | containsCombinator: boolean |
| 171 | }[] = [] |
| 172 | |
| 173 | let currentList: SelectorListNode | null = null |
| 174 | |
| 175 | let buffer = '' |
| 176 | |
| 177 | let peekChar |
| 178 | |
| 179 | function current(nodes = target): SelectorAstNode { |
| 180 | return nodes.length === 1 ? nodes[0] : containsCombinator ? complex(nodes) : compound(nodes) |
| 181 | } |
| 182 | |
| 183 | function append(node: SelectorAstNode) { |
| 184 | let existing = target[target.length - 1] |
| 185 | |
| 186 | if (existing?.kind === 'compound') { |
| 187 | existing.nodes.push(node) |
| 188 | } else if (existing && existing.kind !== 'list' && existing.kind !== 'combinator') { |
| 189 | target[target.length - 1] = compound([existing, node]) |
| 190 | } else { |
| 191 | target.push(node) |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | for (let i = 0; i < input.length; i++) { |
| 196 | let currentChar = input.charCodeAt(i) |
| 197 | |
| 198 | switch (currentChar) { |
| 199 | // E.g.: |
| 200 | // |
| 201 | // ```css |
| 202 | // .foo .bar |
| 203 | // ^ |
| 204 | // |
| 205 | // .foo > .bar |
| 206 | // ^^^ |
| 207 | // ``` |
| 208 | case COMMA: { |
| 209 | // Flush remaining buffer, mark it as a selector |
| 210 | // |
| 211 | // Combinators are handled separately, and functions end with `)` which |
| 212 | // means that the `buffer` will be empty at that point. |
| 213 | if (buffer.length > 0) { |
| 214 | append(selector(buffer)) |
| 215 | buffer = '' |
no test coverage detected