Skip to content

Commit 3ba786c

Browse files
Tests: lookbehind test for patterns (#1890)
This adds a test which whether `lookbehind` (if set to `true`) is necessary for a given pattern.
1 parent a816455 commit 3ba786c

File tree

5 files changed

+54
-22
lines changed

5 files changed

+54
-22
lines changed

components/prism-elixir.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
Prism.languages.elixir = {
2-
'comment': {
3-
pattern: /#.*/m,
4-
lookbehind: true
5-
},
2+
'comment': /#.*/m,
63
// ~r"""foo""" (multi-line), ~r'''foo''' (multi-line), ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>
74
'regex': {
85
pattern: /~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,
@@ -90,4 +87,3 @@ Prism.languages.elixir.string.forEach(function(o) {
9087
}
9188
};
9289
});
93-

components/prism-elixir.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/prism-tt2.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
(function(Prism) {
22

33
Prism.languages.tt2 = Prism.languages.extend('clike', {
4-
comment: {
5-
pattern: /#.*|\[%#[\s\S]*?%\]/,
6-
lookbehind: true
7-
},
8-
keyword: /\b(?:BLOCK|CALL|CASE|CATCH|CLEAR|DEBUG|DEFAULT|ELSE|ELSIF|END|FILTER|FINAL|FOREACH|GET|IF|IN|INCLUDE|INSERT|LAST|MACRO|META|NEXT|PERL|PROCESS|RAWPERL|RETURN|SET|STOP|TAGS|THROW|TRY|SWITCH|UNLESS|USE|WHILE|WRAPPER)\b/,
9-
punctuation: /[[\]{},()]/
4+
'comment': /#.*|\[%#[\s\S]*?%\]/,
5+
'keyword': /\b(?:BLOCK|CALL|CASE|CATCH|CLEAR|DEBUG|DEFAULT|ELSE|ELSIF|END|FILTER|FINAL|FOREACH|GET|IF|IN|INCLUDE|INSERT|LAST|MACRO|META|NEXT|PERL|PROCESS|RAWPERL|RETURN|SET|STOP|TAGS|THROW|TRY|SWITCH|UNLESS|USE|WHILE|WRAPPER)\b/,
6+
'punctuation': /[[\]{},()]/
107
});
118

129
Prism.languages.insertBefore('tt2', 'number', {
13-
operator: /=[>=]?|!=?|<=?|>=?|&&|\|\|?|\b(?:and|or|not)\b/,
14-
variable: {
10+
'operator': /=[>=]?|!=?|<=?|>=?|&&|\|\|?|\b(?:and|or|not)\b/,
11+
'variable': {
1512
pattern: /[a-z]\w*(?:\s*\.\s*(?:\d+|\$?[a-z]\w*))*/i
1613
}
1714
});
@@ -34,7 +31,7 @@
3431
greedy: true,
3532
alias: 'string',
3633
inside: {
37-
variable: {
34+
'variable': {
3835
pattern: /\$(?:[a-z]\w*(?:\.(?:\d+|\$?[a-z]\w*))*)/i
3936
}
4037
}

components/prism-tt2.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/regex-tests.js

+45-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"use strict";
22

3-
const { assert } = require("chai");
3+
const { assert } = require('chai');
44
const PrismLoader = require('./helper/prism-loader');
55
const { languages } = require('../components');
66

7+
78
for (const lang in languages) {
89
if (lang === 'meta') {
910
continue;
@@ -13,19 +14,57 @@ for (const lang in languages) {
1314

1415
const Prism = PrismLoader.createInstance(lang);
1516

16-
it('- should not match the empty string', function () {
17-
let lastToken = '<unknown>';
17+
/**
18+
* Invokes the given function on every pattern in `Prism.languages`.
19+
*
20+
* @param {(values: { pattern: RegExp, tokenName: string, name: string, parent: any }) => void} callback
21+
*/
22+
function forEachPattern(callback) {
23+
/** @type {Map<string, string>} */
24+
const nameMap = new Map();
1825

1926
Prism.languages.DFS(Prism.languages, function (name, value) {
20-
if (typeof this === 'object' && !Array.isArray(this) && name !== 'pattern') {
21-
lastToken = name;
27+
let path = nameMap.get(this) || '<languages>';
28+
if (/^\d+$/.test(name)) {
29+
path += `[${name}]`;
30+
} else if (/^[a-z]\w*$/i.test(name)) {
31+
path += `.${name}`;
32+
} else {
33+
path += `[${JSON.stringify(name)}]`;
34+
}
35+
if (Array.isArray(value) || Prism.util.type(value) === 'Object') {
36+
nameMap.set(value, path);
2237
}
2338

2439
if (Prism.util.type(value) === 'RegExp') {
25-
assert.notMatch('', value, `Token '${lastToken}': ${value} should not match the empty string.`);
40+
callback({
41+
pattern: value,
42+
tokenName: path,
43+
name,
44+
parent: this,
45+
});
2646
}
2747
});
48+
}
2849

50+
51+
it('- should not match the empty string', function () {
52+
forEachPattern(({ pattern, tokenName }) => {
53+
// test for empty string
54+
assert.notMatch('', pattern, `Token ${tokenName}: ${pattern} should not match the empty string.`);
55+
});
56+
});
57+
58+
it('- should have a capturing group if lookbehind is set to true', function () {
59+
forEachPattern(({ pattern, tokenName, name, parent }) => {
60+
if (name === 'pattern' && parent.lookbehind) {
61+
const simplifiedSource = pattern.source.replace(/\\\D/g, '_').replace(/\[[^\]]*\]/g, '_');
62+
63+
if (!/\((?!\?)/.test(simplifiedSource)) {
64+
assert.fail(`Token ${tokenName}: The pattern is set to 'lookbehind: true' but does not have a capturing group.`);
65+
}
66+
}
67+
});
2968
});
3069
});
3170
}

0 commit comments

Comments
 (0)