Skip to content

Commit

Permalink
Add basis comment and indent parser
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Feb 28, 2016
1 parent be694c5 commit 354aed6
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 38 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ npm-debug.log
.babelrc

test/
update-cases
.travis.yml
.editorconfig
.eslintrc
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"babel-preset-es2015-loose": "7.0.0",
"eslint-config-postcss": "2.0.0",
"babel-preset-stage-0": "6.5.0",
"postcss-parser-tests": "5.0.5",
"babel-eslint": "5.0.0",
"babel-core": "6.5.2",
"babel-cli": "6.5.1",
Expand Down
57 changes: 53 additions & 4 deletions parser.es6
Original file line number Diff line number Diff line change
@@ -1,22 +1,71 @@
import Root from 'postcss/lib/root';
import Comment from 'postcss/lib/comment';
import Root from 'postcss/lib/root';

export default class Parser {

constructor(input) {
this.input = input;

this.line = 0;
this.pos = 0;
this.root = new Root();
this.current = this.root;
this.spaces = '';
this.indent = false;

this.root.source = { input, start: { line: 1, column: 1 } };
}

loop() {
while ( this.line < this.lines.length ) {
this.line += 1;
let line;
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 ) {
this.atrule(line);
}

this.pos += 1;
}
}

comment(line) {
let token = line.tokens[0];
let node = new Comment();
this.init(node, token[2], token[3]);
node.source.end = { line: token[4], column: token[5] };

let text = token[1];
if ( token[6] === 'inline' ) {
text = text.slice(2);
} else {
text = text.slice(2, -2);
}

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

atrule() {
}

init(node, line, column) {
this.current.push(node);
node.source = { start: { line, column }, input: this.input };
}

}
74 changes: 40 additions & 34 deletions preprocess.es6
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,53 @@ export default function preprocess(input, lines) {
let indentType;
let number = 1;
return lines.map(line => {
let indent, tokens;
if ( line[0][0] === 'space' ) {
indent = line[0][1];
tokens = line.slice(1);
} else {
indent = '';
tokens = line;
}
let lastComma = false;
let comment = false;
let atrule = false;
let indent = '';
let tokens = [];
let colon = false;

if ( !indentType && indent.length ) {
indentType = indent[0] === ' ' ? 'space' : 'tab';
}
if ( indentType === 'space' ) {
if ( indent.indexOf('\t') !== -1 ) {
indentError(input, number, indent.indexOf('\t'));
if ( line.length > 0 ) {
if ( line[0][0] === 'space' ) {
indent = line[0][1];
tokens = line.slice(1);
} else {
indent = '';
tokens = line;
}
} else if ( indentType === 'tab' ) {
if ( indent.indexOf(' ') !== -1 ) {
indentError(input, number, indent.indexOf(' '));

if ( !indentType && indent.length ) {
indentType = indent[0] === ' ' ? 'space' : 'tab';
}
if ( indentType === 'space' ) {
if ( indent.indexOf('\t') !== -1 ) {
indentError(input, number, indent.indexOf('\t'));
}
} else if ( indentType === 'tab' ) {
if ( indent.indexOf(' ') !== -1 ) {
indentError(input, number, indent.indexOf(' '));
}
}
}

let lastComma = false;
let atrule = false;
let colon = false;
if ( tokens.length ) {
for ( let i = tokens.length - 1; i >= 0; i-- ) {
if ( tokens[i][0] === ',' ) {
lastComma = true;
break;
} else if ( tokens[i][0] !== 'space' ) {
break;
if ( tokens.length ) {
for ( let i = tokens.length - 1; i >= 0; i-- ) {
if ( tokens[i][0] === ',' ) {
lastComma = true;
break;
} else if ( tokens[i][0] !== 'space' ) {
break;
}
}
comment = tokens[0][0] === 'comment';
atrule = tokens[0][0] === 'at-word';
colon = tokens.some( j => j[0] === ':' );
}
atrule = tokens[0][0] === 'at-word';
colon = tokens.some( j => j[0] === ':' );
}

let last = tokens[tokens.length - 1];
if ( last && last[0] === 'newline' ) number = last[2] + 1;
let last = tokens[tokens.length - 1];
if ( last && last[0] === 'newline' ) number = last[2] + 1;
}

return { indent, tokens, atrule, colon, lastComma };
return { indent, tokens, atrule, colon, comment, lastComma };
});
}
57 changes: 57 additions & 0 deletions test/cases/comments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"raws": {},
"type": "root",
"nodes": [
{
"raws": {
"left": " ",
"right": " "
},
"type": "comment",
"source": {
"start": {
"line": 1,
"column": 1
},
"input": {
"file": "comments.sss"
},
"end": {
"line": 2,
"column": 10
}
},
"text": "multi\n line"
},
{
"raws": {
"left": " ",
"right": ""
},
"type": "comment",
"source": {
"start": {
"line": 3,
"column": 1
},
"input": {
"file": "comments.sss"
},
"end": {
"line": 3,
"column": 9
}
},
"text": "inline"
}
],
"source": {
"input": {
"file": "comments.sss"
},
"start": {
"line": 1,
"column": 1
}
}
}
3 changes: 3 additions & 0 deletions test/cases/comments.sss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* multi
line */
// inline
32 changes: 32 additions & 0 deletions test/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import parse from '../parse';

import { jsonify } from 'postcss-parser-tests';
import path from 'path';
import test from 'ava';
import fs from 'fs';

test('detects indent', t => {
let root = parse('@one\n @two\n @three');
t.same(root.raws.indent, ' ');
});

test('ignores comments in indent detection', t => {
let root = parse('@one\n // comment\n @two');
t.same(root.raws.indent, ' ');
});

let tests = fs.readdirSync(path.join(__dirname, 'cases'))
.filter(i => path.extname(i) === '.sss' );

function read(file) {
return fs.readFileSync(path.join(__dirname, 'cases', file)).toString();
}

for ( let name of tests ) {
test('parses ' + name, t => {
let sss = read(name).trim();
let json = read(name.replace(/\.sss/, '.json')).trim();
let root = parse(sss, { from: name });
t.same(jsonify(root), json);
});
}
20 changes: 20 additions & 0 deletions test/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ test('separates indent from other tokens', t => {
tokens: [['word', 'ab']],
colon: false,
atrule: false,
comment: false,
lastComma: false
}
]);
Expand All @@ -26,6 +27,7 @@ test('works with indentless strings', t => {
tokens: [['word', 'ab']],
colon: false,
atrule: false,
comment: false,
lastComma: false
}
]);
Expand All @@ -38,6 +40,7 @@ test('detects at-rules', t => {
tokens: [['at-word', '@ab'], ['space', ' ']],
colon: false,
atrule: true,
comment: false,
lastComma: false
}
]);
Expand All @@ -50,6 +53,7 @@ test('detects last comma', t => {
tokens: [['word', 'ab'], [',', ',']],
colon: false,
atrule: false,
comment: false,
lastComma: true
}
]);
Expand All @@ -62,6 +66,7 @@ test('detects last comma with trailing spaces', t => {
tokens: [['word', 'ab'], [',', ','], ['space', ' ']],
colon: false,
atrule: false,
comment: false,
lastComma: true
}
]);
Expand All @@ -74,6 +79,7 @@ test('ignore comma inside', t => {
tokens: [['word', 'ab'], [',', ','], ['word', 'ba']],
colon: false,
atrule: false,
comment: false,
lastComma: false
}
]);
Expand All @@ -86,6 +92,20 @@ test('detects colon', t => {
tokens: [['word', 'ab'], [':', ':'], ['word', 'ba']],
colon: true,
atrule: false,
comment: false,
lastComma: false
}
]);
});

test('detects comments', t => {
run(t, [[['comment', '// a']]], [
{
indent: '',
tokens: [['comment', '// a']],
colon: false,
atrule: false,
comment: true,
lastComma: false
}
]);
Expand Down
18 changes: 18 additions & 0 deletions update-cases
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

var parse = require('./parse');

var tests = require('postcss-parser-tests');
var path = require('path');
var fs = require('fs');

var dir = path.join(__dirname, 'test', 'cases');
fs.readdirSync(dir).filter(function (i) {
return path.extname(i) === '.sss';
}).forEach(function (name) {
var sssFile = path.join(dir, name);
var jsonFile = sssFile.replace(/\.sss/, '.json');
var sss = fs.readFileSync(sssFile).toString().trim();
var root = parse(sss, { from: name });
fs.writeFileSync(jsonFile, tests.jsonify(root) + '\n');
});

0 comments on commit 354aed6

Please sign in to comment.