Skip to content

Commit 851238c

Browse files
committed
adding more operator parsing conditions, limiting certain operator conditions to calc(), correcting operator parse errors
1 parent ae6c1b6 commit 851238c

File tree

4 files changed

+89
-14
lines changed

4 files changed

+89
-14
lines changed

lib/parser.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ module.exports = class Parser {
4242

4343
this.current = value;
4444
this.tokens = tokenize(input, this.options);
45-
// console.log(this.tokens);
4645
}
4746

4847
parse () {
@@ -140,23 +139,42 @@ module.exports = class Parser {
140139
node;
141140

142141
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);
142+
// only inspect if the operator is not the first token, and we're only
143+
// within a calc() function: the only spec-valid place for math expressions
144+
if (!this.options.loose) {
145+
if (this.position > 0) {
146+
if (this.current.type === 'func' && this.current.value === 'calc') {
147+
if (this.prevToken[0] !== 'space') {
148+
this.error('Syntax Error', this.currToken);
149+
}
150+
// valid: calc(1 - +2)
151+
// invalid: calc(1 -+2)
152+
else if (this.nextToken[0] !== 'space' && this.nextToken[0] !== 'word') {
153+
this.error('Syntax Error', this.currToken);
154+
}
155+
// valid: calc(1 - +2)
156+
// invalid: calc(1 -2)
157+
else if (this.nextToken[0] === 'word' && this.current.last.type !== 'operator') {
158+
this.error('Syntax Error', this.currToken);
159+
}
147160
}
148-
else if (this.nextToken[0] !== 'space') {
161+
// if we're not in a function and someone has doubled up on operators,
162+
// or they're trying to perform a calc outside of a calc
163+
// eg. +-4px or 5+ 5, throw an error
164+
else if (this.nextToken[0] === 'space'
165+
|| this.nextToken[0] === 'operator'
166+
|| this.prevToken[0] === 'operator') {
149167
this.error('Syntax Error', this.currToken);
150168
}
151169
}
152170
}
153171

154-
if ((!this.current.nodes.length || (this.current.last && this.current.last.type === 'operator')) && this.nextToken[0] === 'word') {
172+
// if ((!this.current.nodes.length || (this.current.last && this.current.last.type === 'operator')) && this.nextToken[0] === 'word') {
173+
if (this.nextToken[0] === 'word') {
155174
return this.word();
156175
}
157176
}
158177

159-
160178
node = new Operator({
161179
value: this.currToken[1],
162180
source: {

test/function.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ describe('Parser → Function', () => {
5858
{ type: 'paren', value: ')' }
5959
]
6060
},
61+
{
62+
it: 'should parse calc function with nutty numbers',
63+
test: 'calc(1px + -2vw - 4px)',
64+
expected: [
65+
{ type: 'func', value: 'calc' },
66+
{ type: 'paren', value: '(' },
67+
{ type: 'number', value: '1', unit: 'px' },
68+
{ type: 'operator', value: '+' },
69+
{ type: 'number', value: '-2', unit: 'vw' },
70+
{ type: 'operator', value: '-' },
71+
{ type: 'number', value: '4', unit: 'px' },
72+
{ type: 'paren', value: ')' }
73+
]
74+
},
6175
{
6276
it: 'should parse nested calc functions',
6377
test: 'calc(((768px - 100vw) / 2) - 15px)',
@@ -117,6 +131,8 @@ describe('Parser → Function', () => {
117131
let ast = new Parser(fixture.test).parse(),
118132
index = 0;
119133

134+
// console.log(ast.first.first.nodes);
135+
120136
ast.first.walk((node) => {
121137
let expected = fixture.expected[index];
122138
index ++;

test/number.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,33 @@ describe('Parser → Number', () => {
3939
test: '5+ 5',
4040
expected: { throw: true }
4141
},
42+
{
43+
test: 'calc(5+ 5)',
44+
expected: { throw: true }
45+
},
4246
{
4347
test: '5 +5',
48+
expected: { value: '+5', unit: '', length: 2 }
49+
},
50+
{
51+
test: 'calc(5 +5)',
4452
expected: { throw: true }
4553
},
4654
{
47-
test: '5+5',
55+
test: '5px+5px',
56+
expected: { value: '+5', unit: 'px', length: 2 }
57+
},
58+
{
59+
test: 'calc(5+5)',
4860
expected: { throw: true }
4961
},
5062
{
5163
test: '5 + 5',
52-
expected: { value: '5', unit: '', length: 3 }
64+
expected: { throw: true }
65+
},
66+
{
67+
test: 'calc(5 + 5)',
68+
expected: { value: ')', length: 5 }
5369
},
5470
{
5571
test: '.',
@@ -58,6 +74,18 @@ describe('Parser → Number', () => {
5874
{
5975
test: '.rem',
6076
expected: { fail: true, length: 1 }
77+
},
78+
{
79+
test: '-2px',
80+
expected: { value: '-2', unit: 'px', length: 1 }
81+
},
82+
{
83+
test: '-16px',
84+
expected: { value: '-16', unit: 'px', length: 1 }
85+
},
86+
{
87+
test: '-16px -1px -1px -16px',
88+
expected: { value: '-16', unit: 'px', length: 4 }
6189
}
6290
];
6391

@@ -77,7 +105,15 @@ describe('Parser → Number', () => {
77105
else {
78106
parse();
79107

80-
expect(ast.first.nodes.length).to.equal(fixture.expected.length);
108+
let targetNode = ast.first;
109+
110+
// support testing calc
111+
if (targetNode.first.nodes && targetNode.first.nodes.length) {
112+
targetNode = targetNode.first;
113+
node = targetNode.last;
114+
}
115+
116+
expect(targetNode.nodes.length).to.equal(fixture.expected.length);
81117

82118
if (fixture.expected.fail) {
83119
expect(node.value).to.equal(fixture.test);
@@ -117,11 +153,15 @@ describe('Parser → Number : Loose', () => {
117153
},
118154
{
119155
test: '5+5',
120-
expected: { value: '5', unit: '', length: 3 }
156+
expected: { value: '+5', unit: '', length: 2 }
121157
},
122158
{
123159
test: '5+-+-+-+5',
124160
expected: { value: '+5', unit: '', length: 8 }
161+
},
162+
{
163+
test: '-16px -1px -1px -16px',
164+
expected: { value: '-16', unit: 'px', length: 4 }
125165
}
126166
];
127167

test/tokenize.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ describe('Tokenize', () => {
1414
{ value: '#ffffff', expectedLength: 1 },
1515
{ value: 'Bond\\ 007', expectedLength: 4 },
1616
{ value: ' \\"word\\\'"\\ \\\t ', expectedLength: 7 },
17-
{ value: 'bar(baz(black, 10%), 10%)', expectedLength: 13 }
17+
{ value: 'bar(baz(black, 10%), 10%)', expectedLength: 13 },
18+
{ value: '-16px -1px -1px -16px', expectedLength: 11 }
1819
];
1920

2021
fixtures.forEach((fixture) => {
2122
it('should tokenize ' + fixture.value.replace(/\n/g, '\\n').replace(/\t/g, '\\t'), () => {
2223
let tokens = tokenize(fixture.value);
23-
24+
console.log(tokens);
2425
expect(tokens.length).to.equal(fixture.expectedLength);
2526
});
2627
});

0 commit comments

Comments
 (0)