228 lines
5.7 KiB
JavaScript
228 lines
5.7 KiB
JavaScript
"use strict";
|
|
|
|
const createError = require("./parser-create-error");
|
|
|
|
function parseSelector(selector) {
|
|
const selectorParser = require("postcss-selector-parser");
|
|
let result;
|
|
selectorParser(result_ => {
|
|
result = result_;
|
|
}).process(selector);
|
|
return addTypePrefix(result, "selector-");
|
|
}
|
|
|
|
function parseValueNodes(nodes) {
|
|
let parenGroup = {
|
|
open: null,
|
|
close: null,
|
|
groups: [],
|
|
type: "paren_group"
|
|
};
|
|
const parenGroupStack = [parenGroup];
|
|
const rootParenGroup = parenGroup;
|
|
let commaGroup = {
|
|
groups: [],
|
|
type: "comma_group"
|
|
};
|
|
const commaGroupStack = [commaGroup];
|
|
|
|
for (let i = 0; i < nodes.length; ++i) {
|
|
if (nodes[i].type === "paren" && nodes[i].value === "(") {
|
|
parenGroup = {
|
|
open: nodes[i],
|
|
close: null,
|
|
groups: [],
|
|
type: "paren_group"
|
|
};
|
|
parenGroupStack.push(parenGroup);
|
|
|
|
commaGroup = {
|
|
groups: [],
|
|
type: "comma_group"
|
|
};
|
|
commaGroupStack.push(commaGroup);
|
|
} else if (nodes[i].type === "paren" && nodes[i].value === ")") {
|
|
if (commaGroup.groups.length) {
|
|
parenGroup.groups.push(commaGroup);
|
|
}
|
|
parenGroup.close = nodes[i];
|
|
|
|
if (commaGroupStack.length === 1) {
|
|
throw new Error("Unbalanced parenthesis");
|
|
}
|
|
|
|
commaGroupStack.pop();
|
|
commaGroup = commaGroupStack[commaGroupStack.length - 1];
|
|
commaGroup.groups.push(parenGroup);
|
|
|
|
parenGroupStack.pop();
|
|
parenGroup = parenGroupStack[parenGroupStack.length - 1];
|
|
} else if (nodes[i].type === "comma") {
|
|
parenGroup.groups.push(commaGroup);
|
|
commaGroup = {
|
|
groups: [],
|
|
type: "comma_group"
|
|
};
|
|
commaGroupStack[commaGroupStack.length - 1] = commaGroup;
|
|
} else {
|
|
commaGroup.groups.push(nodes[i]);
|
|
}
|
|
}
|
|
if (commaGroup.groups.length > 0) {
|
|
parenGroup.groups.push(commaGroup);
|
|
}
|
|
return rootParenGroup;
|
|
}
|
|
|
|
function flattenGroups(node) {
|
|
if (
|
|
node.type === "paren_group" &&
|
|
!node.open &&
|
|
!node.close &&
|
|
node.groups.length === 1
|
|
) {
|
|
return flattenGroups(node.groups[0]);
|
|
}
|
|
|
|
if (node.type === "comma_group" && node.groups.length === 1) {
|
|
return flattenGroups(node.groups[0]);
|
|
}
|
|
|
|
if (node.type === "paren_group" || node.type === "comma_group") {
|
|
return Object.assign({}, node, { groups: node.groups.map(flattenGroups) });
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function addTypePrefix(node, prefix) {
|
|
if (node && typeof node === "object") {
|
|
delete node.parent;
|
|
for (const key in node) {
|
|
addTypePrefix(node[key], prefix);
|
|
if (key === "type" && typeof node[key] === "string") {
|
|
if (!node[key].startsWith(prefix)) {
|
|
node[key] = prefix + node[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function addMissingType(node) {
|
|
if (node && typeof node === "object") {
|
|
delete node.parent;
|
|
for (const key in node) {
|
|
addMissingType(node[key]);
|
|
}
|
|
if (!Array.isArray(node) && node.value && !node.type) {
|
|
node.type = "unknown";
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function parseNestedValue(node) {
|
|
if (node && typeof node === "object") {
|
|
delete node.parent;
|
|
for (const key in node) {
|
|
parseNestedValue(node[key]);
|
|
if (key === "nodes") {
|
|
node.group = flattenGroups(parseValueNodes(node[key]));
|
|
delete node[key];
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function parseValue(value) {
|
|
const valueParser = require("postcss-values-parser");
|
|
const result = valueParser(value, { loose: true }).parse();
|
|
const parsedResult = parseNestedValue(result);
|
|
return addTypePrefix(parsedResult, "value-");
|
|
}
|
|
|
|
function parseMediaQuery(value) {
|
|
const mediaParser = require("postcss-media-query-parser").default;
|
|
const result = addMissingType(mediaParser(value));
|
|
return addTypePrefix(result, "media-");
|
|
}
|
|
|
|
function parseNestedCSS(node) {
|
|
if (node && typeof node === "object") {
|
|
delete node.parent;
|
|
for (const key in node) {
|
|
parseNestedCSS(node[key]);
|
|
}
|
|
if (typeof node.selector === "string") {
|
|
const selector = node.raws.selector
|
|
? node.raws.selector.raw
|
|
: node.selector;
|
|
|
|
try {
|
|
node.selector = parseSelector(selector);
|
|
} catch (e) {
|
|
// Fail silently. It's better to print it as is than to try and parse it
|
|
node.selector = selector;
|
|
}
|
|
}
|
|
if (node.type && typeof node.value === "string") {
|
|
try {
|
|
node.value = parseValue(node.value);
|
|
} catch (e) {
|
|
const line = +(e.toString().match(/line: ([0-9]+)/) || [1, 1])[1];
|
|
const column = +(e.toString().match(/column ([0-9]+)/) || [0, 0])[1];
|
|
throw createError(
|
|
"(postcss-values-parser) " + e.toString(),
|
|
node.source.start.line + line - 1,
|
|
node.source.start.column + column + node.prop.length
|
|
);
|
|
}
|
|
}
|
|
if (node.type === "css-atrule" && typeof node.params === "string") {
|
|
node.params = parseMediaQuery(node.params);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function parseWithParser(parser, text) {
|
|
let result;
|
|
try {
|
|
result = parser.parse(text);
|
|
} catch (e) {
|
|
if (typeof e.line !== "number") {
|
|
throw e;
|
|
}
|
|
throw createError("(postcss) " + e.name + " " + e.reason, e.line, e.column);
|
|
}
|
|
const prefixedResult = addTypePrefix(result, "css-");
|
|
const parsedResult = parseNestedCSS(prefixedResult);
|
|
return parsedResult;
|
|
}
|
|
|
|
function requireParser(isSCSS) {
|
|
if (isSCSS) {
|
|
return require("postcss-scss");
|
|
} else {
|
|
return require("postcss-less");
|
|
}
|
|
}
|
|
|
|
function parse(text) {
|
|
const isLikelySCSS = !!text.match(/(\w\s*: [^}:]+|#){/);
|
|
try {
|
|
return parseWithParser(requireParser(isLikelySCSS), text);
|
|
} catch (e) {
|
|
try {
|
|
return parseWithParser(requireParser(!isLikelySCSS), text);
|
|
} catch (e2) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = parse;
|