Hash :
3fda5c95
Author :
Date :
2019-09-03T12:49:58
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
"use strict";
const { assert } = require("chai");
const PrismLoader = require('./helper/prism-loader');
const { languages } = require('../components');
/**
* Performs a breadth-first search on the given start element.
*
* @param {any} start
* @param {(path: { key: string, value: any }[]) => void} callback
*/
function BFS(start, callback) {
const visited = new Set();
/** @type {{ key: string, value: any }[][]} */
let toVisit = [
[{ key: null, value: start }]
];
callback(toVisit[0]);
while (toVisit.length > 0) {
/** @type {{ key: string, value: any }[][]} */
const newToVisit = [];
for (const path of toVisit) {
const obj = path[path.length - 1].value;
if (!visited.has(obj)) {
visited.add(obj);
for (const key in obj) {
const value = obj[key];
path.push({ key, value });
callback(path);
if (Array.isArray(value) || Object.prototype.toString.call(value) == '[object Object]') {
newToVisit.push([...path]);
}
path.pop();
}
}
}
toVisit = newToVisit;
}
}
for (const lang in languages) {
if (lang === 'meta') {
continue;
}
describe(`Patterns of '${lang}'`, function () {
const Prism = PrismLoader.createInstance(lang);
/**
* Invokes the given function on every pattern in `Prism.languages`.
*
* @param {(values: { pattern: RegExp, tokenPath: string, name: string, parent: any, path: { key: string, value: any }[] }) => void} callback
*/
function forEachPattern(callback) {
BFS(Prism.languages, path => {
const { key, value } = path[path.length - 1];
let tokenPath = '<languages>';
for (const { key } of path) {
if (!key) {
// do nothing
} else if (/^\d+$/.test(key)) {
tokenPath += `[${key}]`;
} else if (/^[a-z]\w*$/i.test(key)) {
tokenPath += `.${key}`;
} else {
tokenPath += `[${JSON.stringify(key)}]`;
}
}
if (Object.prototype.toString.call(value) == '[object RegExp]') {
callback({
pattern: value,
tokenPath,
name: key,
parent: path.length > 1 ? path[path.length - 2].value : undefined,
path,
});
}
});
}
it('- should not match the empty string', function () {
forEachPattern(({ pattern, tokenPath }) => {
// test for empty string
assert.notMatch('', pattern, `Token ${tokenPath}: ${pattern} should not match the empty string.`);
});
});
it('- should have a capturing group if lookbehind is set to true', function () {
forEachPattern(({ pattern, tokenPath, name, parent }) => {
if (name === 'pattern' && parent.lookbehind) {
const simplifiedSource = pattern.source.replace(/\\\D/g, '_').replace(/\[[^\]]*\]/g, '_');
if (!/\((?!\?)/.test(simplifiedSource)) {
assert.fail(`Token ${tokenPath}: The pattern is set to 'lookbehind: true' but does not have a capturing group.`);
}
}
});
});
it('- should have nice names and aliases', function () {
const niceName = /^[a-z][a-z\d]*(?:[-_][a-z\d]+)*$/;
function testName(name, desc = 'token name') {
if (!niceName.test(name)) {
assert.fail(`The ${desc} '${name}' does not match ${niceName}`);
}
}
forEachPattern(({ name, parent, tokenPath, path }) => {
// token name
let offset = 1;
if (name == 'pattern') { // regex can be inside an object
offset++;
}
if (Array.isArray(path[path.length - 1 - offset].value)) { // regex/regex object can be inside an array
offset++;
}
const patternName = path[path.length - offset].key;
testName(patternName);
// check alias
if (name == 'pattern' && 'alias' in parent) {
const alias = parent.alias;
if (typeof alias === 'string') {
testName(alias, `alias of '${tokenPath}'`);
} else if (Array.isArray(alias)) {
alias.forEach(name => testName(name, `alias of '${tokenPath}'`));
}
}
});
});
});
}