UNPKG

6.06 kBJavaScriptView Raw
1/**
2 * Module dependencies
3 */
4
5var postcss = require('postcss');
6var sws = require('css-sws');
7var jade2ast = require('jade2ast');
8var processors = require('./processors');
9var pre = processors.pre;
10var post = processors.post;
11
12module.exports = Parser;
13
14function Parser(css, opts) {
15 this.source = css;
16 this.opts = opts || {};
17 this.compiler = postcss();
18}
19
20Parser.prototype.use = function(fn) {
21 this.compiler.use(fn);
22};
23
24Parser.prototype.parse = function(input) {
25 var type = input.type;
26 if (this['visit_' + type]) return this['visit_' + type](input);
27 return null;
28};
29
30Parser.prototype.parseChildren = function(children) {
31 if (!children || children.length === 0) return undefined;
32 return children.map(this.parse.bind(this));
33};
34
35Parser.prototype.visit_atrule = function(rule) {
36 if (rule.name === 'block') throw new Error('blocks can only be used as immediate children to constructors (line ' + rule.source.start.line + ')');
37 if (rule.name === 'constructor') return this.visit_constructor(rule);
38 if (rule.name === 'each') return this.visit_each(rule);
39 if (rule.name === 'else') return this.visit_else(rule);
40 if (rule.name === 'elseif') return this.visit_elseif(rule);
41 if (rule.name === 'expression') return this.visit_expression(rule);
42 if (rule.name === 'export') return this.visit_export(rule);
43 if (rule.name === 'function') return this.visit_function(rule);
44 if (rule.name === '_import') return this.visit_import(rule);
45 if (rule.name === 'if') return this.visit_if(rule);
46 if (rule.name === 'unbuffered') return this.visit_unbuffered(rule);
47 if (rule.name === 'var') return this.visit_var(rule);
48 if (rule.name === 'yield') return this.visit_yield(rule);
49 return this.visit_atrule_body(rule);
50};
51
52Parser.prototype.visit_atrule_body = function(rule) {
53 return {
54 type: 'tag',
55 name: JSON.stringify('@' + rule.name + ' ' + rule.params),
56 children: this.parseChildren(rule.nodes),
57 buffer: true
58 };
59};
60
61Parser.prototype.visit_comment = function(comment) {
62 return {
63 type: 'js_comment',
64 value: comment.text
65 };
66};
67
68Parser.prototype.visit_constructor = function(node) {
69 var self = this;
70 var call = jade2ast(JSON.parse(node.params))[0];
71 call.props = call.props || {};
72
73 var children = (node.nodes || []).filter(function(c) {
74 if (c.type !== 'atrule' && c.name !== 'block') return true;
75 var parts = c.params.trim().split(/([^\(]+)/);
76 var args = parts[3];
77 args = args ? '(' + args : undefined;
78
79 call.props[parts[1]] = {
80 block: true,
81 name: parts[1],
82 args: args,
83 expression: self.parseChildren(c.nodes)
84 };
85 return false;
86 });
87
88 call.children = this.parseChildren(children);
89 return call;
90};
91
92Parser.prototype.visit_decl = function(node) {
93 return this.visit_rule({
94 nodes: [
95 node
96 ],
97 selector: '&'
98 });
99};
100
101Parser.prototype.visit_else = function(node) {
102 return {
103 type: 'else',
104 children: this.parseChildren(node.nodes)
105 };
106};
107
108Parser.prototype.visit_each = function(node) {
109 var params = JSON.parse(node.params).trim();
110 var parts = params.split(/ *(?:in|of) */);
111 var target = parts[0].trim().split(/ *, */);
112
113 return {
114 type: 'each',
115 expression: parts[1],
116 value: target[0],
117 key: target[1] || '$index',
118 children: this.parseChildren(node.nodes),
119 buffer: true
120 };
121};
122
123Parser.prototype.visit_elseif = function(node) {
124 return {
125 type: 'elseif',
126 expression: JSON.parse(node.params),
127 children: this.parseChildren(node.nodes)
128 };
129};
130
131Parser.prototype.visit_expression = function(node) {
132 return {
133 type: 'expression',
134 expression: JSON.parse(node.params),
135 buffer: true
136 };
137};
138
139Parser.prototype.visit_export = function(node) {
140 return {
141 type: 'export',
142 expression: JSON.parse(node.params)
143 };
144};
145
146Parser.prototype.visit_function = function(node) {
147 return {
148 type: 'function',
149 expression: node.params,
150 children: this.parseChildren(node.nodes)
151 };
152};
153
154Parser.prototype.visit_if = function(node) {
155 return {
156 type: 'if',
157 expression: JSON.parse(node.params),
158 children: this.parseChildren(node.nodes)
159 };
160};
161
162Parser.prototype.visit_import = function(node) {
163 return {
164 type: 'import',
165 expression: JSON.parse(node.params)
166 };
167};
168
169Parser.prototype.visit_prop = function(prop) {
170 var expr = /^ *=/.test(prop.value) ?
171 prop.value.replace(/^ *=/, '') :
172 JSON.stringify(prop.value);
173 return {
174 expression: expr
175 };
176};
177
178Parser.prototype.visit_root = function(root) {
179 return this.parseChildren(root.nodes);
180};
181
182Parser.prototype.visit_rule = function(rule) {
183 var self = this;
184 var selectors = rule.selector.split(',').map(function(sel) {
185 return sel.trim();
186 });
187
188 var name = '[' + selectors.map(function(sel) {
189 return '"' + sel.replace(/\`([^\`]+)`/g, function(_, match) {
190 return '" + ' + match + ' + "';
191 }) + '"';
192 }).join(',') + ']';
193
194 var props = rule.nodes.reduce(function(acc, node) {
195 if (node.type !== 'decl') return acc;
196 acc.push([node.prop, self.visit_prop(node)]);
197 return acc;
198 }, []);
199
200 var children = rule.nodes.filter(function(node) {
201 return node.type !== 'decl';
202 });
203
204 return {
205 type: 'tag',
206 name: name,
207 props: props,
208 children: this.parseChildren(children),
209 buffer: true
210 };
211};
212
213Parser.prototype.visit_unbuffered = function(node) {
214 return {
215 type: 'expression',
216 expression: JSON.parse(node.params)
217 };
218};
219
220Parser.prototype.visit_var = function(node) {
221 return {
222 type: 'var',
223 expression: JSON.parse(node.params)
224 };
225};
226
227Parser.prototype.visit_yield = function(node) {
228 var params = JSON.parse(node.params).trim();
229 var parts = params.trim().split(/([^\(]+)/);
230 var args = parts[3];
231 args = args ? '(' + args : undefined;
232 return {
233 type: 'yield',
234 name: parts[1],
235 args: args
236 };
237};
238
239Parser.prototype.toAst = function(test) {
240 var css = post(sws(pre(this.source)));
241 var ast = this.compiler.process(css, {from: this.opts.filename});
242
243 var transformed = this.parse(ast.root);
244
245 return transformed;
246};