328 lines
7.3 KiB
JavaScript
328 lines
7.3 KiB
JavaScript
"use strict";
|
|
const getTemplate = require("./get-template");
|
|
const ObjectLiteral = require("./object");
|
|
const camelCase = require("./camel-case");
|
|
const unCamelCase = require("./un-camel-case");
|
|
const Literal = require("./literal");
|
|
const postcss = require("postcss");
|
|
|
|
function forEach (arr, callback) {
|
|
arr && arr.forEach(callback);
|
|
}
|
|
|
|
const replaceProp = (fn) => (value) => (
|
|
value.replace(/(\(\s*)(.*?)(\s*:)/g, (s, prefix, prop, suffix) => (
|
|
prefix + fn(prop) + suffix
|
|
))
|
|
);
|
|
const camelCaseProp = replaceProp(camelCase);
|
|
const unCamelCaseProp = replaceProp(unCamelCase);
|
|
|
|
function defineRaws (node, prop, prefix, suffix, props) {
|
|
if (!props) {
|
|
props = {};
|
|
}
|
|
const descriptor = {
|
|
enumerable: true,
|
|
get: () => (
|
|
node[prop]
|
|
),
|
|
set: (value) => {
|
|
node[prop] = value;
|
|
},
|
|
};
|
|
|
|
if (!props.raw) {
|
|
props.raw = descriptor;
|
|
} else if (props.raw === "camel") {
|
|
props.raw = {
|
|
enumerable: true,
|
|
get: () => (
|
|
camelCase(node[prop])
|
|
),
|
|
set: (value) => {
|
|
node[prop] = unCamelCase(value);
|
|
},
|
|
};
|
|
}
|
|
|
|
props.value = descriptor;
|
|
|
|
node.raws[prop] = Object.defineProperties({
|
|
prefix,
|
|
suffix,
|
|
}, props);
|
|
}
|
|
|
|
class objectParser {
|
|
constructor (input) {
|
|
this.input = input;
|
|
}
|
|
parse (node) {
|
|
const root = postcss.root({
|
|
source: {
|
|
input: this.input,
|
|
start: node.loc.start,
|
|
},
|
|
});
|
|
root.raws.node = node;
|
|
const obj = new ObjectLiteral({
|
|
raws: {
|
|
node,
|
|
},
|
|
});
|
|
root.push(obj);
|
|
this.process(node, obj);
|
|
this.sort(root);
|
|
this.raws(root);
|
|
|
|
const startNode = root.first.raws.node;
|
|
const endNode = root.last.raws.node;
|
|
|
|
const start = {
|
|
line: startNode.loc.start.line,
|
|
};
|
|
|
|
let before = root.source.input.css.slice(startNode.start - startNode.loc.start.column, startNode.start);
|
|
if (/^\s+$/.test(before)) {
|
|
start.column = 1;
|
|
} else {
|
|
before = "";
|
|
start.column = startNode.loc.start.column;
|
|
}
|
|
|
|
root.first.raws.before = before;
|
|
root.source.input.css = before + root.source.input.css.slice(startNode.start, endNode.end);
|
|
root.source.start = start;
|
|
|
|
this.root = root;
|
|
}
|
|
|
|
process (node, parent) {
|
|
[
|
|
"leadingComments",
|
|
"innerComments",
|
|
"trailingComments",
|
|
].forEach(prop => {
|
|
forEach(node[prop], child => {
|
|
this.source(child, this.comment(child, parent));
|
|
});
|
|
});
|
|
|
|
const child = (this[node.type] || this.literal).apply(this, [node, parent]);
|
|
this.source(node, child);
|
|
return child;
|
|
}
|
|
source (node, parent) {
|
|
parent.source = {
|
|
input: this.input,
|
|
start: node.loc.start,
|
|
end: node.loc.end,
|
|
};
|
|
return parent;
|
|
}
|
|
raws (parent, node) {
|
|
const source = this.input.css;
|
|
parent.nodes.forEach((child, i) => {
|
|
if (i) {
|
|
child.raws.before = source.slice(parent.nodes[i - 1].raws.node.end, child.raws.node.start).replace(/^\s*,+/, "");
|
|
} else if (node) {
|
|
child.raws.before = source.slice(node.start, child.raws.node.start).replace(/^\s*{+/, "");
|
|
}
|
|
});
|
|
if (node) {
|
|
let semicolon;
|
|
let after;
|
|
if (parent.nodes.length) {
|
|
after = source.slice(parent.last.raws.node.end, node.end).replace(/^\s*,+/, () => {
|
|
semicolon = true;
|
|
return "";
|
|
});
|
|
} else {
|
|
after = source.slice(node.start, node.end).replace(/^\s*{/, "");
|
|
}
|
|
parent.raws.after = after.replace(/}+\s*$/, "");
|
|
parent.raws.semicolon = semicolon || false;
|
|
}
|
|
}
|
|
|
|
sort (node) {
|
|
node.nodes = node.nodes.sort((a, b) => (
|
|
a.raws.node.start - b.raws.node.start
|
|
));
|
|
}
|
|
|
|
getNodeValue (node, wrappedValue) {
|
|
const source = this.input.css;
|
|
let rawValue;
|
|
let cookedValue;
|
|
switch (node.type) {
|
|
case "Identifier": {
|
|
const isCssFloat = node.name === "cssFloat";
|
|
return {
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: isCssFloat && node.name,
|
|
value: isCssFloat ? "float" : node.name,
|
|
};
|
|
}
|
|
case "StringLiteral": {
|
|
rawValue = node.extra.raw.slice(1, -1);
|
|
cookedValue = node.value;
|
|
break;
|
|
}
|
|
case "TemplateLiteral": {
|
|
rawValue = getTemplate(node, source);
|
|
break;
|
|
}
|
|
default: {
|
|
rawValue = source.slice(node.start, node.end);
|
|
break;
|
|
}
|
|
}
|
|
const valueWrap = wrappedValue.split(rawValue);
|
|
return {
|
|
prefix: valueWrap[0],
|
|
suffix: valueWrap[1],
|
|
value: cookedValue || rawValue,
|
|
};
|
|
}
|
|
|
|
ObjectExpression (node, parent) {
|
|
forEach(node.properties, child => {
|
|
this.process(child, parent);
|
|
});
|
|
this.sort(parent);
|
|
this.raws(parent, node);
|
|
return parent;
|
|
}
|
|
|
|
ObjectProperty (node, parent) {
|
|
const source = this.input.css;
|
|
let between = source.indexOf(":", node.key.end);
|
|
const rawKey = source.slice(node.start, between).trimRight();
|
|
const rawValue = source.slice(between + 1, node.end).trimLeft();
|
|
between = source.slice(node.start + rawKey.length, node.end - rawValue.length);
|
|
const key = this.getNodeValue(node.key, rawKey);
|
|
if (node.value.type === "ObjectExpression") {
|
|
let rule;
|
|
if (/^@(\S+)(\s*)(.*)$/.test(key.value)) {
|
|
const name = RegExp.$1;
|
|
const afterName = RegExp.$2;
|
|
const params = RegExp.$3;
|
|
const atRule = postcss.atRule({
|
|
name: unCamelCase(name),
|
|
raws: {
|
|
afterName: afterName,
|
|
},
|
|
nodes: [],
|
|
});
|
|
defineRaws(atRule, "name", key.prefix + "@", params ? "" : key.suffix, {
|
|
raw: "camel",
|
|
});
|
|
if (params) {
|
|
atRule.params = unCamelCaseProp(params);
|
|
defineRaws(atRule, "params", "", key.suffix, {
|
|
raw: {
|
|
enumerable: true,
|
|
get: () => (
|
|
camelCaseProp(atRule.params)
|
|
),
|
|
set: (value) => {
|
|
atRule.params = unCamelCaseProp(value);
|
|
},
|
|
},
|
|
});
|
|
}
|
|
rule = atRule;
|
|
} else {
|
|
// rule = this.rule(key, keyWrap, node.value, parent);
|
|
rule = postcss.rule({
|
|
selector: key.value,
|
|
});
|
|
defineRaws(rule, "selector", key.prefix, key.suffix);
|
|
}
|
|
raw(rule);
|
|
this.ObjectExpression(node.value, rule);
|
|
return rule;
|
|
}
|
|
|
|
const value = this.getNodeValue(node.value, rawValue);
|
|
|
|
if (key.value[0] === "@") {
|
|
const atRule = postcss.atRule({
|
|
name: unCamelCase(key.value),
|
|
params: value.value,
|
|
});
|
|
defineRaws(atRule, "name", key.prefix, key.suffix, {
|
|
raw: "camel",
|
|
});
|
|
|
|
defineRaws(atRule, "params", value.prefix, value.suffix);
|
|
raw(atRule);
|
|
return atRule;
|
|
} else {
|
|
let decl;
|
|
if (key.raw) {
|
|
decl = postcss.decl({
|
|
prop: key.value,
|
|
value: value.value,
|
|
raws: {
|
|
prop: key,
|
|
},
|
|
});
|
|
} else {
|
|
decl = postcss.decl({
|
|
prop: unCamelCase(key.value),
|
|
value: value.value,
|
|
});
|
|
|
|
defineRaws(decl, "prop", key.prefix, key.suffix, {
|
|
raw: "camel",
|
|
});
|
|
}
|
|
|
|
defineRaws(decl, "value", value.prefix, value.suffix);
|
|
raw(decl);
|
|
return decl;
|
|
}
|
|
|
|
function raw (postcssNode) {
|
|
postcssNode.raws.between = between;
|
|
postcssNode.raws.node = node;
|
|
parent.push(postcssNode);
|
|
}
|
|
}
|
|
|
|
literal (node, parent) {
|
|
const literal = new Literal({
|
|
text: this.input.css.slice(node.start, node.end),
|
|
raws: {
|
|
node,
|
|
},
|
|
});
|
|
parent.push(literal);
|
|
return literal;
|
|
}
|
|
|
|
comment (node, parent) {
|
|
if (!parent.nodes || (node.start < parent.raws.node.start && parent.type !== "root" && parent.parent)) {
|
|
return this.comment(node, parent.parent);
|
|
}
|
|
const text = node.value.match(/^(\s*)((?:\S[\s\S]*?)?)(\s*)$/);
|
|
const comment = postcss.comment({
|
|
text: text[2],
|
|
raws: {
|
|
node,
|
|
left: text[1],
|
|
right: text[3],
|
|
inline: node.type === "CommentLine",
|
|
},
|
|
});
|
|
|
|
parent.push(comment);
|
|
return comment;
|
|
}
|
|
}
|
|
module.exports = objectParser;
|