import { parse } from "css-tree"; function mapSelectors(selector: string): string[] { if (!selector) { return []; } return selector.split(/\s*(?![^(]*\)),\s*/).map(s => s.replace(/\u200C/g, ",")); } function mapPosition(node, css) { let res: any = { start: { line: node.loc.start.line, column: node.loc.start.column }, end: { line: node.loc.end.line, column: node.loc.end.column }, content: css }; if (node.loc.source && node.loc.source !== "") { res.source = node.loc.source; } return res; } function transformAst(node, css, type = null) { if (!node) { return; } if (node.type === "StyleSheet") { return { type: "stylesheet", stylesheet: { rules: node.children.map(child => transformAst(child, css)).filter(child => child !== null).toArray(), parsingErrors: [] } }; } if (node.type === "Atrule") { let atrule: any = { type: node.name, }; if (node.name === "supports" || node.name === "media") { atrule[node.name] = node.prelude.value; atrule.rules = transformAst(node.block, css); } else if (node.name === "page") { atrule.selectors = node.prelude ? mapSelectors(node.prelude.value) : []; atrule.declarations = transformAst(node.block, css); } else if (node.name === "document") { atrule.document = node.prelude ? node.prelude.value : ""; atrule.vendor = ""; atrule.rules = transformAst(node.block, css); } else if (node.name === "font-face") { atrule.declarations = transformAst(node.block, css); } else if (node.name === "import" || node.name === "charset" || node.name === "namespace") { atrule[node.name] = node.prelude ? node.prelude.value : ""; } else if (node.name === "keyframes") { atrule.name = node.prelude ? node.prelude.value : ""; atrule.keyframes = transformAst(node.block, css, "keyframe"); atrule.vendor = undefined; } else { atrule.rules = transformAst(node.block, css); } atrule.position = mapPosition(node, css); return atrule; } if (node.type === "Block") { return node.children.map(child => transformAst(child, css, type)).filter(child => child !== null).toArray(); } if (node.type === "Rule") { let value = node.prelude.value; let res: any = { type: type != null ? type : "rule", declarations: transformAst(node.block, css), position: mapPosition(node, css) }; if (type === "keyframe") { res.values = mapSelectors(value); } else { res.selectors = mapSelectors(value); } return res; } if (node.type === "Comment") { return { type: "comment", comment: node.value, position: mapPosition(node, css) }; } if (node.type === "Declaration") { return { type: "declaration", property: node.property, value: node.value.value ? node.value.value.trim() : "", position: mapPosition(node, css) }; } if (node.type === "Raw") { return null; } throw Error(`Unknown node type ${node.type}`); } export function cssTreeParse(css, source): any { let errors = []; let ast = parse(css, { parseValue: false, parseAtrulePrelude: false, parseRulePrelude: false, positions: true, filename: source, onParseError: error => { errors.push(`${source}:${error.line}:${error.column}: ${error.formattedMessage}`); } }); if (errors.length > 0) { throw new Error(errors[0]); } return transformAst(ast, css); }