Skip to content

Commit 5b12545

Browse files
author
Hannu Pelkonen
committed
PostCSS variable parsing support
1 parent 1b5960f commit 5b12545

18 files changed

+1424
-888
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,30 @@ The directory of of customColors file is included to SASS `includePaths` so it i
235235

236236
Internal styles could be overriden by defining new styles inside the `styleguide_custom_styles` mixin. This mixin is added to the end of the application stylesheet.
237237

238+
<a name="option-parsers"></a>
239+
**parsers** (object, optional)
240+
241+
default:
242+
243+
```
244+
{
245+
sass: 'scss',
246+
scss: 'scss',
247+
less: 'less',
248+
postcss: 'postcss'
249+
}
250+
```
251+
252+
Styleguide tries to guess which parser to use when parsing variable information from stylesheets. The object key defines the file extension and the value the parser name. Three are three parsers available: `scss`, `less` and `poscss`.
253+
254+
For example, to parse all .css files using postcss parser, following configuration could be used:
255+
256+
```
257+
{
258+
css: 'postcss'
259+
}
260+
```
261+
238262
<a name="option-filesConfig"></a>
239263
**filesConfig** (array, optional) **(Experimental feature)**
240264

lib/modules/common.js

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ function sanitizeOptions(opt) {
2020
server: opt.server || false,
2121
port: opt.port || 3000,
2222
rootPath: opt.rootPath,
23+
parsers: opt.parsers || {
24+
sass: 'scss',
25+
scss: 'scss',
26+
less: 'less',
27+
postcss: 'postcss'
28+
},
2329
filesConfig: opt.filesConfig
2430
};
2531
}

lib/modules/io.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ var fs = require('fs'),
33
Q = require('q'),
44
_ = require('lodash'),
55
parser = require('./variable-parser'),
6-
writer = require('./variable-writer'),
76
events = {
87
connection: 'connection',
98
progress: {
@@ -63,11 +62,11 @@ module.exports = function(ioServer, options) {
6362
}
6463

6564
function updateVariableValues(file) {
66-
file.contents = writer.setVariables(file.contents, file.syntax, file.variables);
65+
file.contents = parser.setVariables(file.contents, file.syntax, file.variables, options);
6766
}
6867

6968
function checkSyntax(file) {
70-
parser.parseVariableDeclarations(file.contents, file.syntax);
69+
parser.parseVariableDeclarations(file.contents, file.syntax, options);
7170
}
7271

7372
function writeFileContents(file) {

lib/modules/parsers/ast-traverser.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
function tree(node, visitor) {
2+
if (typeof node !== 'object') {
3+
return node;
4+
}
5+
//console.log('Accessing node', visitor)
6+
if (visitor && visitor.test && visitor.test(node.type, node)) {
7+
node = visitor.process(node);
8+
if (!node) {
9+
return;
10+
}
11+
}
12+
var res = [node.type];
13+
//console.log("-- child length", node.content.length)
14+
if (node.content) {
15+
for (var i = 0; i <= node.content.length; i++) {
16+
var n = tree(node.content[i], visitor);
17+
if (n) {
18+
res.push(n);
19+
}
20+
}
21+
}
22+
return res;
23+
}
24+
25+
module.exports = {
26+
traverse: function traverse(ast, visitor) {
27+
return tree(ast, visitor);
28+
}
29+
};

lib/modules/parsers/less.js

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
var gonzales = require('gonzales-pe'),
2+
traverser = require('./ast-traverser');
3+
4+
// Parse Style variables to object
5+
function parseVariableDeclarations(string) {
6+
// Do not parse empty files. Otherwise gonzales.parse will fail
7+
if (!string) {
8+
return [];
9+
}
10+
11+
var out = [],
12+
ast = gonzales.parse(string, {
13+
syntax: 'less'
14+
}),
15+
visitor = {
16+
// Visitor for SASS, SCSS and plain CSS syntaxes
17+
test: function(name) {
18+
return name === 'atrules';
19+
},
20+
process: function(nodes) {
21+
var varName = nodes.content[0].content[0].content,
22+
varVal = '';
23+
24+
// Skip at-keywords that do not decrade variable (Ex. @imports)
25+
if (nodes.content[1].type === 'operator' && nodes.content[1].content === ':') {
26+
/* Grabs all the listed values
27+
* Fix then https://github.com/tonyganch/gonzales-pe/issues/17 is fixed */
28+
29+
nodes.content.forEach(function(element) {
30+
if (element.type === 'atrules' || element.type === 'atkeyword') {
31+
return;
32+
}
33+
if (element.type === 'operator' && element.content === ':') {
34+
return;
35+
}
36+
varVal += element.toCSS('less'); // Syntax is always less as this visitor is only for LESS
37+
});
38+
39+
out.push({
40+
name: varName,
41+
value: varVal.trim(),
42+
line: nodes.content[1].start.line
43+
});
44+
}
45+
}
46+
};
47+
48+
traverser.traverse(ast, visitor);
49+
return out;
50+
}
51+
52+
// Parse Style variables to object
53+
function findVariables(string) {
54+
// Do not parse empty files. Otherwise gonzales.parse will fail
55+
if (!string) {
56+
return [];
57+
}
58+
59+
var out = [],
60+
ast = gonzales.parse(string, {
61+
syntax: 'less'
62+
}),
63+
visitor = {
64+
// Visitor for SASS, SCSS and plain CSS syntaxes
65+
test: function(name, nodes) {
66+
return (name === 'declaration' && nodes.content[0].content[0].type === 'variable') || (name === 'variable' && nodes.content[0].type === 'ident');
67+
},
68+
process: function(nodes) {
69+
if (nodes.type !== 'declaration') {
70+
out.push(nodes.content[0].content);
71+
}
72+
}
73+
};
74+
75+
traverser.traverse(ast, visitor);
76+
return out;
77+
}
78+
79+
function setVariables(string, variables) {
80+
81+
var ast = gonzales.parse(string, {
82+
syntax: 'less'
83+
});
84+
85+
variables.forEach(function(variable) {
86+
ast.map(function(node) {
87+
if (node.type === 'atrules') {
88+
var hasOperator = false,
89+
isThisVariable = false,
90+
metOperator = false,
91+
metFirstSpace = false,
92+
newContent = [];
93+
node.map(function(nnode) {
94+
if (nnode.type === 'ident' && nnode.content === variable.name) {
95+
isThisVariable = true;
96+
}
97+
if (nnode.type === 'operator' && nnode.content === ':') {
98+
metOperator = true;
99+
hasOperator = true;
100+
}
101+
});
102+
// Take only at-rules with operators
103+
if (!hasOperator) {
104+
return;
105+
}
106+
// For given variable only
107+
if (!isThisVariable) {
108+
return;
109+
}
110+
node.content.forEach(function(node) {
111+
if (metOperator && metFirstSpace) {
112+
return;
113+
}
114+
if (node.type === 'operator') {
115+
metOperator = true;
116+
}
117+
newContent.push(node);
118+
if (node.type === 'space' && metOperator) {
119+
metFirstSpace = true;
120+
}
121+
});
122+
node.content = newContent;
123+
node.content.push({
124+
type: 'string',
125+
content: variable.value
126+
});
127+
}
128+
});
129+
});
130+
return ast.toCSS('less');
131+
}
132+
133+
module.exports = {
134+
parseVariableDeclarations: parseVariableDeclarations,
135+
findVariables: findVariables,
136+
setVariables: setVariables
137+
};

lib/modules/parsers/postcss.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
var postcss = require('postcss'),
2+
// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS)
3+
// `--foo`
4+
// See: http://dev.w3.org/csswg/css-variables/#custom-property
5+
RE_VAR_PROP = (/(--(.+))/),
6+
RE_VAR_FUNC = (/var\((--[^,\s]+?)(?:\s*,\s*(.+))?\)/);
7+
8+
function cleanVariableName(name) {
9+
return name.replace(/^\-\-/, '');
10+
}
11+
12+
function parseVariableDeclarations(string) {
13+
if (!string) {
14+
return [];
15+
}
16+
17+
var out = [],
18+
ast = postcss.parse(string);
19+
// Loop through all of the declarations and grab the variables and put them in the map
20+
ast.eachDecl(function(decl) {
21+
// If declaration is a variable
22+
if (RE_VAR_PROP.test(decl.prop)) {
23+
out.push({
24+
name: cleanVariableName(decl.prop),
25+
value: decl.value,
26+
line: decl.source.start.line
27+
});
28+
}
29+
});
30+
return out;
31+
}
32+
33+
function findVariables(string) {
34+
if (!string) {
35+
return [];
36+
}
37+
38+
var out = [],
39+
ast = postcss.parse(string);
40+
41+
ast.eachRule(function(rule) {
42+
rule.nodes.forEach(function(node) {
43+
if (node.type === 'decl') {
44+
var decl = node;
45+
if (RE_VAR_FUNC.test(decl.value) && !RE_VAR_PROP.test(decl.prop)) {
46+
out.push(cleanVariableName(decl.value.match(RE_VAR_FUNC)[1]));
47+
}
48+
}
49+
});
50+
});
51+
return out;
52+
}
53+
54+
function setVariables(string, variables) {
55+
var ast = postcss.parse(string);
56+
// Loop through all of the declarations and change variable values
57+
variables.forEach(function(variable) {
58+
ast.eachDecl(function(decl) {
59+
// If declaration is a variable
60+
if (RE_VAR_PROP.test(decl.prop) && decl.prop === '--' + variable.name) {
61+
decl.value = variable.value;
62+
}
63+
});
64+
});
65+
return ast.toString();
66+
}
67+
68+
module.exports = {
69+
parseVariableDeclarations: parseVariableDeclarations,
70+
findVariables: findVariables,
71+
setVariables: setVariables
72+
};

0 commit comments

Comments
 (0)