You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
6.8 KiB
344 lines
6.8 KiB
'use strict'; |
|
|
|
const camelCase = require('./camel-case'); |
|
const getTemplate = require('./get-template'); |
|
const Literal = require('./literal'); |
|
const ObjectLiteral = require('./object'); |
|
const postcss = require('postcss'); |
|
const unCamelCase = require('./un-camel-case'); |
|
|
|
function forEach(arr, callback) { |
|
arr && arr.forEach(callback); |
|
} |
|
|
|
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, |
|
}, |
|
nodes: [], |
|
}); |
|
|
|
defineRaws(atRule, 'name', `${key.prefix}@`, params ? '' : key.suffix, { |
|
raw: 'camel', |
|
}); |
|
|
|
if (params) { |
|
atRule.params = params; |
|
defineRaws(atRule, 'params', '', key.suffix); |
|
} |
|
|
|
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; |
|
} |
|
|
|
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;
|
|
|