Skip to content

Commit 2c4773b

Browse files
authored
implement loose option. fixes #15
1 parent 45ddeeb commit 2c4773b

File tree

5 files changed

+116
-38
lines changed

5 files changed

+116
-38
lines changed

lib/errors/ParserError.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
class ParserError extends Error {
4+
constructor(message) {
5+
super(message);
6+
7+
this.name = this.constructor.name;
8+
this.message = message || 'An error ocurred while parsing.';
9+
10+
if (typeof Error.captureStackTrace === 'function') {
11+
Error.captureStackTrace(this, this.constructor);
12+
}
13+
else {
14+
this.stack = (new Error(message)).stack;
15+
}
16+
}
17+
}
18+
19+
module.exports = ParserError;

lib/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const Str = require('./string');
1313
const Value = require('./value');
1414
const Word = require('./word');
1515

16-
let parser = function (source) {
17-
return new Parser(source);
16+
let parser = function (source, options) {
17+
return new Parser(source, options);
1818
};
1919

2020
parser.atword = function (opts) {

lib/parser.js

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ const tokenize = require('./tokenize');
1919
const flatten = require('flatten');
2020
const indexesOf = require('indexes-of');
2121
const uniq = require('uniq');
22+
const ParserError = require('./errors/ParserError');
2223

2324
function sortAscending (list) {
2425
return list.sort((a, b) => a - b);
2526
}
2627

2728
module.exports = class Parser {
28-
constructor (input) {
29+
constructor (input, options) {
30+
const defaults = { loose: false };
31+
2932
this.input = input;
33+
this.options = Object.assign({}, defaults, options);
3034
this.position = 0;
3135
// we'll use this to keep track of the paren balance
3236
this.unbalanced = 0;
@@ -37,7 +41,8 @@ module.exports = class Parser {
3741
this.root.append(value);
3842

3943
this.current = value;
40-
this.tokens = tokenize(input);
44+
this.tokens = tokenize(input, this.options);
45+
// console.log(this.tokens);
4146
}
4247

4348
parse () {
@@ -106,8 +111,8 @@ module.exports = class Parser {
106111
this.position++;
107112
}
108113

109-
error (message) {
110-
throw new Error(message); // eslint-disable-line new-cap
114+
error (message, token) {
115+
throw new ParserError(message + ` at line: ${token[2]}, column ${token[3]}`);
111116
}
112117

113118
loop () {
@@ -129,7 +134,30 @@ module.exports = class Parser {
129134

130135
operator () {
131136

132-
let node = new Operator({
137+
// if a +|- operator is followed by a non-word character (. is allowed) and
138+
// is preceded by a non-word character. (5+5)
139+
let char = this.currToken[1],
140+
node;
141+
142+
if (char === '+' || char === '-') {
143+
if (this.position > 0) {
144+
if (!this.options.loose) {
145+
if (this.prevToken[0] !== 'space') {
146+
this.error('Syntax Error', this.currToken);
147+
}
148+
else if (this.nextToken[0] !== 'space') {
149+
this.error('Syntax Error', this.currToken);
150+
}
151+
}
152+
}
153+
154+
if ((!this.current.nodes.length || (this.current.last && this.current.last.type === 'operator')) && this.nextToken[0] === 'word') {
155+
return this.word();
156+
}
157+
}
158+
159+
160+
node = new Operator({
133161
value: this.currToken[1],
134162
source: {
135163
start: {
@@ -205,7 +233,7 @@ module.exports = class Parser {
205233
}
206234

207235
if (unbalanced) {
208-
this.error('Expected closing parenthesis.');
236+
this.error('Expected closing parenthesis', token);
209237
}
210238

211239
// ok, all parens are balanced. continue on
@@ -300,7 +328,7 @@ module.exports = class Parser {
300328
this.current.unbalanced --;
301329

302330
if (this.current.unbalanced < 0) {
303-
this.error('Expected opening parenthesis.');
331+
this.error('Expected opening parenthesis', token);
304332
}
305333

306334
if (!this.current.unbalanced && this.cache) {
@@ -450,10 +478,6 @@ module.exports = class Parser {
450478
return this.splitWord();
451479
}
452480

453-
missingParenthesis () {
454-
return this.error('Expected opening parenthesis.');
455-
}
456-
457481
newNode (node) {
458482
if (this.spaces) {
459483
node.raws.before += this.spaces;

lib/tokenize.js

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ const wordEndNum = /[ \n\t\r\(\)\*:;@!&'"\-\+\|~>,\[\]\\]|\//g;
2424
const util = require('util');
2525
const TokenizeError = require('./errors/TokenizeError');
2626

27-
module.exports = function tokenize (input) {
27+
module.exports = function tokenize (input, options) {
28+
29+
options = options || {};
30+
2831
let tokens = [],
2932
css = input.valueOf(),
3033
length = css.length,
@@ -37,7 +40,7 @@ module.exports = function tokenize (input) {
3740

3841
function unclosed (what) {
3942
let message = util.format('Unclosed %s at line: %d, column: %d, token: %d', what, line, pos - offset, pos);
40-
throw new Error(message);
43+
throw new TokenizeError(message);
4144
}
4245

4346
function tokenizeError () {
@@ -208,28 +211,14 @@ module.exports = function tokenize (input) {
208211
break;
209212
}
210213

211-
// if a +|- operator is followed by a non-word character (. is allowed) and
212-
// is preceded by a non-word character. (5+5)
213-
if (pos > 0 && (code === plus || code === minus)) {
214-
if (prevChar.charCodeAt(0) !== space) {
215-
tokenizeError();
216-
}
217-
else if (nextChar.charCodeAt(0) !== space) {
218-
tokenizeError();
219-
}
220-
}
221-
222-
/* eslint no-fallthrough: 0 */
223-
if (!/[a-z0-9\.]/gi.test(nextChar)) {
224-
tokens.push(['operator', css.slice(pos, next),
225-
line, pos - offset,
226-
line, next - offset,
227-
pos
228-
]);
214+
tokens.push(['operator', css.slice(pos, next),
215+
line, pos - offset,
216+
line, next - offset,
217+
pos
218+
]);
229219

230-
pos = next - 1;
231-
break;
232-
}
220+
pos = next - 1;
221+
break;
233222

234223
default:
235224
if (code === slash && css.charCodeAt(pos + 1) === asterisk) {

test/number.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const expect = require('chai').expect;
44
const Parser = require('../lib/parser');
5-
const TokenizeError = require('../lib/errors/TokenizeError');
5+
const ParserError = require('../lib/errors/ParserError');
66

77
describe('Parser → Number', () => {
88

@@ -72,7 +72,7 @@ describe('Parser → Number', () => {
7272
}
7373

7474
if (fixture.expected.throw) {
75-
expect(parse).to.throw(TokenizeError);
75+
expect(parse).to.throw(ParserError);
7676
}
7777
else {
7878
parse();
@@ -91,3 +91,49 @@ describe('Parser → Number', () => {
9191
});
9292

9393
});
94+
95+
describe('Parser → Number : Loose', () => {
96+
97+
let fixtures = [
98+
{
99+
test: '-2',
100+
expected: { value: '-2', unit: '', length: 1 }
101+
},
102+
{
103+
test: ' -2',
104+
expected: { value: '-2', unit: '', length: 1 }
105+
},
106+
{
107+
test: '+-2.',
108+
expected: { value: '-2.', unit: '', length: 2 }
109+
},
110+
{
111+
test: '5+ 5',
112+
expected: { value: '5', unit: '', length: 3 }
113+
},
114+
{
115+
test: '1+ 5 + +5',
116+
expected: { value: '+5', unit: '', length: 5 }
117+
},
118+
{
119+
test: '5+5',
120+
expected: { value: '5', unit: '', length: 3 }
121+
},
122+
{
123+
test: '5+-+-+-+5',
124+
expected: { value: '+5', unit: '', length: 8 }
125+
}
126+
];
127+
128+
fixtures.forEach((fixture) => {
129+
it('should ' + (fixture.expected.throw ? 'not ' : '') + 'parse ' + fixture.test, () => {
130+
let ast = new Parser(fixture.test, { loose: true }).parse(),
131+
node = ast.first.last;
132+
133+
expect(ast.first.nodes.length).to.equal(fixture.expected.length);
134+
expect(node.value).to.equal(fixture.expected.value);
135+
expect(node.unit).to.equal(fixture.expected.unit);
136+
});
137+
});
138+
139+
});

0 commit comments

Comments
 (0)