Skip to content

Commit 5f9bf17

Browse files
author
Brian Mock
authored
Merge pull request #188 from jneen/topic/test-coverage
Fixes #187 regexp group out of range; 100% tests
2 parents bff2ae1 + c9fb9de commit 5f9bf17

16 files changed

+181
-43
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules
33
npm-*.log
44
.nyc_output
55
coverage
6+
.DS_Store

API.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ Parsimmon.test(function(c) {
142142
});
143143
```
144144

145-
## Parsimmon.regexp(regexp, group=0)
145+
## Parsimmon.regexp(regexp)
146146

147147
Returns a parser that looks for a match to the regexp and yields the entire text matched. The regexp will always match starting at the current parse location. The regexp may only use the following flags: `imu`. Any other flag will result in an error being thrown.
148148

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## version 1.6.1 (2017-07-01)
2+
3+
* **100% unit test coverage!** This does not mean bugs won't exist, but it keeps us much safer against regressions in the future.
4+
* **BUGFIX:** `Parsimmon.regexp(regexp, group)` will now correctly fail to parse if the `group` number is out of range for the `regexp` number of groups. This worked correctly in the past, but was broken during a minor code clean up due to missing tests.
5+
16
## version 1.6.0 (2017-06-26)
27

38
* Adds `Parsimmon.seqObj(...args)`

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parsimmon",
3-
"version": "1.6.0",
3+
"version": "1.6.1",
44
"description": "A monadic LL(infinity) parser combinator library",
55
"keywords": [
66
"parsing",
@@ -38,7 +38,8 @@
3838
"precoverage": "npm run test",
3939
"coverage": "nyc report --reporter=text-lcov | coveralls",
4040
"pretest": "npm run lint",
41-
"test": "nyc node_modules/.bin/mocha -i src --ui tdd --reporter dot test/setup.js test/core/*.test.js test/laws/*.test.js",
41+
"test": "nyc --reporter=lcov npm run test:mocha",
42+
"test:mocha": "mocha -i src --ui tdd --reporter dot test/setup.js test/core/*.test.js test/laws/*.test.js",
4243
"watch:test": "mocha --ui tdd --reporter min --watch test/setup.js test/core/*.test.js test/laws/*.test.js"
4344
}
4445
}

src/parsimmon.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -590,11 +590,14 @@ function regexp(re, group) {
590590
return Parsimmon(function(input, i) {
591591
var match = anchored.exec(input.slice(i));
592592
if (match) {
593-
var fullMatch = match[0];
594-
var groupMatch = match[group];
595-
if (groupMatch !== null) {
593+
if (0 <= group && group <= match.length) {
594+
var fullMatch = match[0];
595+
var groupMatch = match[group];
596596
return makeSuccess(i + fullMatch.length, groupMatch);
597597
}
598+
return makeFailure(
599+
'valid match group (0 to ' + match.length + ') in ' + expected
600+
);
598601
}
599602
return makeFailure(i, expected);
600603
});

test/.eslintrc

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"Parsimmon": true,
77
"assert": true,
88
"suite": true,
9+
"setup": true,
10+
"teardown": true,
911
"test": true
1012
}
1113
}

test/core/alt.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@ test('Parsimmon.alt', function(){
3838
assert.throws(function() {
3939
Parsimmon.alt('not a parser');
4040
});
41+
42+
43+
assert.strictEqual(Parsimmon.alt().parse('').status, false);
4144
});

test/core/constructor.test.js

+43-23
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,48 @@
11
'use strict';
22

3-
test('Parsimmon()', function() {
4-
var good = 'just a Q';
5-
var bad = 'all I wanted was a Q';
6-
var justQ = Parsimmon(function(str, i) {
7-
if (str.charAt(i) === 'Q') {
8-
return Parsimmon.makeSuccess(i + 1, good);
9-
} else {
10-
return Parsimmon.makeFailure(i, bad);
11-
}
12-
});
13-
var result1 = justQ.parse('Q');
14-
var result2 = justQ.parse('x');
15-
assert.deepEqual(result1, {
16-
status: true,
17-
value: good,
3+
suite('Parsimmon()', function() {
4+
5+
test('should work in general', function() {
6+
var good = 'just a Q';
7+
var bad = 'all I wanted was a Q';
8+
var justQ = Parsimmon(function(str, i) {
9+
if (str.charAt(i) === 'Q') {
10+
return Parsimmon.makeSuccess(i + 1, good);
11+
} else {
12+
return Parsimmon.makeFailure(i, bad);
13+
}
14+
});
15+
var result1 = justQ.parse('Q');
16+
var result2 = justQ.parse('x');
17+
assert.deepEqual(result1, {
18+
status: true,
19+
value: good,
20+
});
21+
assert.deepEqual(result2, {
22+
status: false,
23+
index: {
24+
offset: 0,
25+
line: 1,
26+
column: 1
27+
},
28+
expected: [bad]
29+
});
1830
});
19-
assert.deepEqual(result2, {
20-
status: false,
21-
index: {
22-
offset: 0,
23-
line: 1,
24-
column: 1
25-
},
26-
expected: [bad]
31+
32+
test('unsafeUnion works on poorly formatted custom parser', function() {
33+
var p1 = Parsimmon.string('a').or(Parsimmon.string('b'));
34+
var p2 = Parsimmon(function(str, i) {
35+
return {
36+
status: false,
37+
index: -1,
38+
value: null,
39+
furthest: i,
40+
expected: []
41+
};
42+
});
43+
var p3 = Parsimmon.alt(p2, p1);
44+
var result = p3.parse('x');
45+
assert.deepStrictEqual(result.expected, ['\'a\'', '\'b\'']);
2746
});
47+
2848
});

test/core/createLanguage.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
suite('Parsimmon.createLanguage', function() {
44

5+
setup(function() {
6+
Object.prototype.NASTY = 'dont extend Object.prototype please';
7+
});
8+
9+
teardown(function() {
10+
delete Object.prototype.NASTY;
11+
});
12+
513
test('should return an object of parsers', function() {
614
var lang = Parsimmon.createLanguage({
715
a: function() {
@@ -29,6 +37,16 @@ suite('Parsimmon.createLanguage', function() {
2937
lang.Parentheses.tryParse('(((())))');
3038
});
3139

40+
test('should ignore non-own properties', function() {
41+
var obj = Object.create({
42+
foo: function() {
43+
return Parsimmon.of(1);
44+
}
45+
});
46+
var lang = Parsimmon.createLanguage(obj);
47+
assert.strictEqual(lang.foo, undefined);
48+
});
49+
3250
test('should allow indirect recursion in parsers', function() {
3351
var lang = Parsimmon.createLanguage({
3452
Value: function(r) {

test/core/custom.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ suite('Parsimmon.custom', function(){
5858

5959
assert.deepEqual(parser.parse('acccccb'), {status: true, value: 'acccccb'});
6060
});
61+
6162
});

test/core/formatError.test.js

+32-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
'use strict';
22

3-
test('Parsimmon.formatError', function() {
4-
var parser =
5-
Parsimmon.alt(
6-
Parsimmon.fail('a'),
7-
Parsimmon.fail('b'),
8-
Parsimmon.fail('c')
9-
);
10-
var expectation = 'expected one of a, b, c, got the end of the input';
11-
var input = '';
12-
var answer = Parsimmon.formatError(input, parser.parse(input));
13-
assert.deepEqual(answer, expectation);
3+
suite('formatError', function() {
4+
5+
test('end of input', function() {
6+
var parser =
7+
Parsimmon.alt(
8+
Parsimmon.fail('a'),
9+
Parsimmon.fail('b'),
10+
Parsimmon.fail('c')
11+
);
12+
var expectation = 'expected one of a, b, c, got the end of the input';
13+
var input = '';
14+
var answer = Parsimmon.formatError(input, parser.parse(input));
15+
assert.deepEqual(answer, expectation);
16+
});
17+
18+
test('middle of input', function() {
19+
var parser =
20+
Parsimmon.seq(
21+
Parsimmon.string('1'),
22+
Parsimmon.alt(
23+
Parsimmon.fail('a'),
24+
Parsimmon.fail('b'),
25+
Parsimmon.fail('c')
26+
)
27+
);
28+
var expectation =
29+
'expected one of a, b, c at line 1 column 2, got \'...x11111111111...\'';
30+
var input = '1x1111111111111111111111111111';
31+
var answer = Parsimmon.formatError(input, parser.parse(input));
32+
assert.deepEqual(answer, expectation);
33+
});
34+
1435
});

test/core/noneOf.test.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
suite('noneOf', function() {
4+
5+
test('matches EVERYTHING BUT the characters specified', function() {
6+
var parser = Parsimmon.noneOf('abc');
7+
var a = 'a'.charCodeAt(0);
8+
var c = 'c'.charCodeAt(0);
9+
for (var i = 0; i < 65535; i++) {
10+
var s = String.fromCharCode(i);
11+
if (a <= i && i <= c) {
12+
assert.strictEqual(parser.parse(s).status, false);
13+
} else {
14+
assert.strictEqual(parser.parse(s).status, true);
15+
}
16+
}
17+
});
18+
19+
});

test/core/oneOf.test.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
suite('oneOf', function() {
4+
5+
test('matches ONLY the characters specified', function() {
6+
var parser = Parsimmon.oneOf('abc');
7+
var a = 'a'.charCodeAt(0);
8+
var c = 'c'.charCodeAt(0);
9+
for (var i = 0; i < 65535; i++) {
10+
var s = String.fromCharCode(i);
11+
if (a <= i && i <= c) {
12+
assert.strictEqual(parser.parse(s).status, true);
13+
} else {
14+
assert.strictEqual(parser.parse(s).status, false);
15+
}
16+
}
17+
});
18+
19+
});

test/core/parse.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@ suite('.parse', function() {
1717
assert.deepEqual(result.expected, ['a', 'b', 'c']);
1818
});
1919

20+
test('throws when given a non-string argument', function() {
21+
assert.throws(function() {
22+
Parsimmon.of('kaboom').parse(0);
23+
});
24+
assert.throws(function() {
25+
Parsimmon.of('kaboom').parse();
26+
});
27+
});
28+
2029
});

test/core/regexp.test.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ suite('Parsimmon.regexp', function() {
4343
var parser0 = Parsimmon.regexp(/(\w)(\d)/, 0);
4444
var parser1 = Parsimmon.regexp(/(\w)(\d)/, 1);
4545
var parser2 = Parsimmon.regexp(/(\w)(\d)/, 2);
46-
assert.equal(parser0.parse('a1').value, 'a1');
47-
assert.equal(parser1.parse('a1').value, 'a');
48-
assert.equal(parser2.parse('a1').value, '1');
46+
var parser3 = Parsimmon.regexp(/(\w)(\d)/, 8);
47+
assert.strictEqual(parser0.parse('a1').value, 'a1');
48+
assert.strictEqual(parser1.parse('a1').value, 'a');
49+
assert.strictEqual(parser2.parse('a1').value, '1');
50+
assert.strictEqual(parser3.parse('a1').status, false);
4951
});
5052

5153
});

test/core/seqObj.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ suite('Parsimmon.seqObj', function() {
3232
});
3333
});
3434

35+
36+
test('every parser is used', function() {
37+
var parser = Parsimmon.seqObj(
38+
['a', Parsimmon.of(1)],
39+
['b', Parsimmon.of(2)],
40+
['c', Parsimmon.fail('oopsy')]
41+
);
42+
var result = parser.parse('');
43+
assert.strictEqual(result.status, false);
44+
});
45+
3546
test('every parser is used', function() {
3647
var parser = Parsimmon.seqObj(
3748
Parsimmon.string('a'),
@@ -60,6 +71,9 @@ suite('Parsimmon.seqObj', function() {
6071
assert.throws(function() {
6172
Parsimmon.seqObj(['cool', 'potato']);
6273
});
74+
assert.throws(function() {
75+
Parsimmon.seqObj(0);
76+
});
6377
});
6478

6579
});

0 commit comments

Comments
 (0)