* CSS 块解析(review-lint 主题 token 检查专用)。
*
* 为什么手写而不用正则:候选正则如 `([^{}]+)\{([^{}]*)\}` 在「整段文本不含左花括号」时
* 会从每个起点一路扫到结尾才失败,复杂度 O(n²);而且它每次只能匹配到嵌套结构中的一层。
* 这里用花括号栈逐字符推进,复杂度 O(n),@media 等嵌套规则里的内层块也会逐个产出。
*
* 已知边界:不解析字符串和声明值里的花括号,未闭合的块不产出。tokens.css 是扁平的
* token 声明文件,不涉及这些情况;若将来放宽到任意 CSS,请换正式解析器。
*/
export function stripCssComments(css) {
let out = ''
let cursor = 0
while (cursor < css.length) {
const start = css.indexOf('/*', cursor)
if (start === -1) {
out += css.slice(cursor)
break
}
out += css.slice(cursor, start)
const end = css.indexOf('*/', start + 2)
if (end === -1)
break
cursor = end + 2
}
return out
}
* 解析出每个「选择器 { 声明 }」块。嵌套块按闭合顺序逐个产出(内层先于外层),
* 外层 body 保留其内部的嵌套文本——主题 token 检查用正则扫 body,两层都能覆盖。
*/
export function parseCssBlocks(css) {
const source = stripCssComments(css)
const blocks = []
const stack = []
let segmentStart = 0
for (let i = 0; i < source.length; i++) {
const char = source[i]
if (char === '{') {
stack.push({ selector: source.slice(segmentStart, i).trim(), bodyStart: i + 1 })
segmentStart = i + 1
}
else if (char === '}') {
const open = stack.pop()
if (!open)
continue
blocks.push({ selector: open.selector, body: source.slice(open.bodyStart, i) })
segmentStart = i + 1
}
}
return blocks
}