Skip to content

Commit

Permalink
Add at-rules support to parser
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Mar 1, 2016
1 parent 81461f9 commit 78d3934
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 26 deletions.
154 changes: 138 additions & 16 deletions parser.es6
@@ -1,4 +1,5 @@
import Comment from 'postcss/lib/comment';
import AtRule from 'postcss/lib/at-rule';
import Root from 'postcss/lib/root';

export default class Parser {
Expand All @@ -10,7 +11,9 @@ export default class Parser {
this.root = new Root();
this.current = this.root;
this.spaces = '';
this.indent = false;

this.prevIndent = undefined;
this.step = undefined;

this.root.source = { input, start: { line: 1, column: 1 } };
}
Expand All @@ -20,11 +23,6 @@ export default class Parser {
while ( this.pos < this.lines.length ) {
line = this.lines[this.pos];

if ( !this.indent && line.indent.length && !line.comment ) {
this.indent = line.indent;
this.root.raws.indent = this.indent;
}

if ( line.comment ) {
this.comment(line);
} else if ( line.atrule ) {
Expand All @@ -38,7 +36,7 @@ export default class Parser {
comment(line) {
let token = line.tokens[0];
let node = new Comment();
this.init(node, token[2], token[3]);
this.initComment(node, line, token[2], token[3]);
node.source.end = { line: token[4], column: token[5] };

let text = token[1];
Expand All @@ -47,25 +45,149 @@ export default class Parser {
} else {
text = text.slice(2, -2);
}
node.text = text.trim();
}

atrule(line) {
this.indent(line);
let atword = line.tokens[0];
let params = line.tokens.slice(1);

let node = new AtRule();
node.name = atword[1].slice(1);
this.init(node, atword[2], atword[3]);

if ( node.name === '' ) this.unnamedAtrule(atword);

while ( line && line.lastComma ) {
this.pos += 1;
line = this.lines[this.pos];
params.push(['space', line.indent]);
params = params.concat(line.tokens);
}

if ( /^\s*$/.test(text) ) {
node.text = '';
node.raws.left = text;
node.raws.right = '';
params = this.trim(params);
if ( params.length ) {
this.raw(node, 'params', params);
} else {
let match = text.match(/^(\s*)([^]*[^\s])(\s*)$/);
node.text = match[2];
node.raws.left = match[1];
node.raws.right = match[3];
node.raws.afterName = '';
node.params = '';
}
}

atrule() {
/* Helpers */

indent(line) {
let indent = line.indent.length;
let isPrev = typeof this.prevIndent !== 'undefined';

if ( !isPrev && indent ) this.indentedFirstLine(line);

if ( !this.step && indent ) {
this.step = indent;
this.root.raws.indent = line.indent;
}

if ( isPrev && this.prevIndent !== indent ) {
let diff = indent - this.prevIndent;
if ( diff > 0 ) {
if ( diff !== this.step ) {
this.wrongIndent(this.prevIndent + this.step, indent, line);
} else {
this.current = this.current.last;
}
} else if ( diff % this.step !== 0 ) {
let m = indent + diff % this.step;
this.wrongIndent(`${ m } or ${ m + this.step }`, indent, line);
} else {
for ( let i = 0; i < diff / this.step; i++ ) {
this.current = this.current.parent;
}
}
}

this.prevIndent = indent;
}

init(node, line, column) {
if ( !this.current.nodes ) this.current.nodes = [];
this.current.push(node);
node.source = { start: { line, column }, input: this.input };
}

initComment(node, line, lineNumber, column) {
let isPrev = typeof this.prevIndent !== 'undefined';
if ( isPrev && this.prevIndent < line.indent.length ) {
this.current = this.current.last;
this.init(node, lineNumber, column);
this.current = this.current.parent;
} else {
this.init(node, lineNumber, column);
}
}

raw(node, prop, tokens) {
let token, type;
let length = tokens.length;
let value = '';
let clean = true;
for ( let i = 0; i < length; i += 1 ) {
token = tokens[i];
type = token[0];
if ( type === 'comment' || type === 'space' && i === length - 1 ) {
clean = false;
} else {
value += token[1];
}
}
if ( !clean ) {
let raw = tokens.reduce( (all, i) => {
if ( i[0] === 'comment' && i[6] === 'inline' ) {
return all + '/* ' + i[1].slice(2).trim() + ' */';
} else {
return all + i[1];
}
}, '');
node.raws[prop] = { value, raw };
}
node[prop] = value;
}

trim(tokens) {
let start, end;
for ( let i = 0; i < tokens.length; i++ ) {
if ( tokens[i][0] !== 'newline' && tokens[i][0] !== 'space' ) {
start = i;
break;
}
}
if ( typeof start === 'undefined' ) return [];
for ( let i = tokens.length - 1; i >= 0; i-- ) {
if ( tokens[i][0] !== 'newline' && tokens[i][0] !== 'space' ) {
end = i;
break;
}
}
return tokens.slice(start, end + 1);
}

// Errors

error(msg, line, column) {
throw this.input.error(msg, line, column);
}

unnamedAtrule(token) {
this.error('At-rule without name', token[2], token[3]);
}

indentedFirstLine(line) {
this.error('First line should not have indent', line.number, 1);
}

wrongIndent(expected, real, line) {
let msg = `Expected ${ expected } indent, but get ${ real }`;
this.error(msg, line.number, 1);
}

}
8 changes: 8 additions & 0 deletions test/cases/atrules.css
@@ -0,0 +1,8 @@
@test;
@charset "UTF-8";
@supports (color: black),
(background: black) {
@media (max-width: /*<=*/ 400px) /* mobile */ {
/* write code here */
}
}
148 changes: 148 additions & 0 deletions test/cases/atrules.json
@@ -0,0 +1,148 @@
{
"raws": {
"indent": " "
},
"type": "root",
"nodes": [
{
"raws": {
"afterName": ""
},
"type": "atrule",
"name": "test",
"source": {
"start": {
"line": 1,
"column": 1
},
"input": {
"file": "atrules.sss"
}
},
"params": "",
"lastEach": 10,
"indexes": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"7": 0,
"8": 0,
"9": 0,
"10": 0
}
},
{
"raws": {},
"type": "atrule",
"name": "charset",
"source": {
"start": {
"line": 2,
"column": 1
},
"input": {
"file": "atrules.sss"
}
},
"params": "\"UTF-8\"",
"lastEach": 10,
"indexes": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"7": 0,
"8": 0,
"9": 0,
"10": 0
}
},
{
"raws": {},
"type": "atrule",
"name": "supports",
"source": {
"start": {
"line": 3,
"column": 1
},
"input": {
"file": "atrules.sss"
}
},
"params": "(color: black),\n (background: black)",
"nodes": [
{
"raws": {
"params": {
"value": "(max-width: 400px) ",
"raw": "(max-width: /*<=*/ 400px) /* mobile */"
}
},
"type": "atrule",
"name": "media",
"source": {
"start": {
"line": 5,
"column": 3
},
"input": {
"file": "atrules.sss"
}
},
"params": "(max-width: 400px) ",
"nodes": [
{
"raws": {},
"type": "comment",
"source": {
"start": {
"line": 6,
"column": 5
},
"input": {
"file": "atrules.sss"
},
"end": {
"line": 6,
"column": 22
}
},
"text": "write code here"
}
],
"lastEach": 10,
"indexes": {}
}
],
"lastEach": 10,
"indexes": {}
}
],
"source": {
"input": {
"file": "atrules.sss"
},
"start": {
"line": 1,
"column": 1
}
},
"rawCache": {
"beforeRule": "\n",
"indent": " ",
"beforeOpen": " ",
"beforeDecl": "\n",
"beforeComment": "\n",
"commentLeft": " ",
"commentRight": " ",
"beforeClose": "\n"
},
"lastEach": 10,
"indexes": {}
}
6 changes: 6 additions & 0 deletions test/cases/atrules.sss
@@ -0,0 +1,6 @@
@test
@charset "UTF-8"
@supports (color: black),
(background: black)
@media (max-width: /*<=*/ 400px) // mobile
// write code here
2 changes: 1 addition & 1 deletion test/cases/comments.css
@@ -1,3 +1,3 @@
/* multi
line */
/* inline*/
/* inline */

0 comments on commit 78d3934

Please sign in to comment.