Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cucumber-expressions

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cucumber-expressions - npm Package Compare versions

Comparing version 3.0.0 to 4.0.0

.eslintrc

50

dist/argument.js

@@ -1,2 +0,2 @@

"use strict";
'use strict';

@@ -7,8 +7,36 @@ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var Regex = require('becke-ch--regex--s0-0-v1--base--pl--lib');
var Group = require('./group');
var _require = require('./errors'),
CucumberExpressionError = _require.CucumberExpressionError;
var Argument = function () {
function Argument(offset, value, parameterType) {
_createClass(Argument, null, [{
key: 'build',
value: function build(regexp, text, parameterTypes) {
var m = new Regex(regexp).exec(text);
if (!m) return null;
var matchGroup = new Group(m);
var argGroups = matchGroup.children;
if (argGroups.length !== parameterTypes.length) {
throw new CucumberExpressionError('Expression ' + regexp + ' has ' + argGroups.length + ' arguments (' + argGroups.map(function (g) {
return g.value;
}) + '), but there were ' + parameterTypes.length + ' parameter types (' + parameterTypes.map(function (p) {
return p.name;
}) + ')');
}
return parameterTypes.map(function (parameterType, i) {
return new Argument(argGroups[i], parameterType);
});
}
}]);
function Argument(group, parameterType) {
_classCallCheck(this, Argument);
this._offset = offset;
this._value = value;
this._group = group;
this._parameterType = parameterType;

@@ -18,16 +46,6 @@ }

_createClass(Argument, [{
key: "offset",
key: 'value',
get: function get() {
return this._offset;
return this._parameterType.transform(this._group ? this._group.values : null);
}
}, {
key: "value",
get: function get() {
return this._value;
}
}, {
key: "transformedValue",
get: function get() {
return this._parameterType.transform(this._value);
}
}]);

@@ -34,0 +52,0 @@

@@ -7,4 +7,6 @@ 'use strict';

var util = require('util');
var ParameterTypeMatcher = require('./parameter_type_matcher');
var GeneratedExpression = require('./generated_expression');
var ParameterType = require('./parameter_type');
var CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory');

@@ -19,15 +21,13 @@ var CucumberExpressionGenerator = function () {

_createClass(CucumberExpressionGenerator, [{
key: 'generateExpression',
value: function generateExpression(text) {
var parameterNames = [];
key: 'generateExpressions',
value: function generateExpressions(text) {
var parameterTypeCombinations = [];
var parameterTypeMatchers = this._createParameterTypeMatchers(text);
var parameterTypes = [];
var usageByTypeName = {};
var expression = "";
var expressionTemplate = '';
var pos = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-line no-constant-condition
var matchingParameterTypeMatchers = [];
var _iteratorNormalCompletion = true;

@@ -62,14 +62,54 @@ var _didIteratorError = false;

if (matchingParameterTypeMatchers.length > 0) {
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(ParameterTypeMatcher.compare);
var bestParameterTypeMatcher = matchingParameterTypeMatchers[0];
var parameter = bestParameterTypeMatcher.parameterType;
parameterTypes.push(parameter);
(function () {
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(ParameterTypeMatcher.compare);
var parameterName = this._getParameterName(parameter.name, usageByTypeName);
parameterNames.push(parameterName);
// Find all the best parameter type matchers, they are all candidates.
var bestParameterTypeMatcher = matchingParameterTypeMatchers[0];
var bestParameterTypeMatchers = matchingParameterTypeMatchers.filter(function (m) {
return ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0;
});
expression += text.slice(pos, bestParameterTypeMatcher.start);
expression += '{' + parameter.name + '}';
// Build a list of parameter types without duplicates. The reason there
// might be duplicates is that some parameter types have more than one regexp,
// which means multiple ParameterTypeMatcher objects will have a reference to the
// same ParameterType.
// We're sorting the list so preferential parameter types are listed first.
// Users are most likely to want these, so they should be listed at the top.
var parameterTypes = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
pos = bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length;
try {
for (var _iterator2 = bestParameterTypeMatchers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var parameterTypeMatcher = _step2.value;
if (!parameterTypes.includes(parameterTypeMatcher.parameterType)) {
parameterTypes.push(parameterTypeMatcher.parameterType);
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
parameterTypes = parameterTypes.sort(ParameterType.compare);
parameterTypeCombinations.push(parameterTypes);
expressionTemplate += text.slice(pos, bestParameterTypeMatcher.start);
expressionTemplate += '{%s}';
pos = bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length;
})();
} else {

@@ -84,13 +124,18 @@ break;

expression += text.slice(pos);
return new GeneratedExpression(expression, parameterNames, parameterTypes);
expressionTemplate += text.slice(pos);
return new CombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).generateExpressions();
}
/**
* @deprecated
*/
}, {
key: '_getParameterName',
value: function _getParameterName(typeName, usageByTypeName) {
var count = usageByTypeName[typeName];
count = count ? count + 1 : 1;
usageByTypeName[typeName] = count;
key: 'generateExpression',
value: function generateExpression(text) {
var _this = this;
return count == 1 ? typeName : '' + typeName + count;
return util.deprecate(function () {
return _this.generateExpressions(text)[0];
}, 'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead')();
}

@@ -101,23 +146,25 @@ }, {

var parameterMatchers = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator2 = this._parameterTypeRegistry.parameterTypes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var parameter = _step2.value;
for (var _iterator3 = this._parameterTypeRegistry.parameterTypes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var parameterType = _step3.value;
parameterMatchers = parameterMatchers.concat(this._createParameterTypeMatchers2(parameter, text));
if (parameterType.useForSnippets) {
parameterMatchers = parameterMatchers.concat(this._createParameterTypeMatchers2(parameterType, text));
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
if (_didIteratorError3) {
throw _iteratorError3;
}

@@ -131,25 +178,26 @@ }

key: '_createParameterTypeMatchers2',
value: function _createParameterTypeMatchers2(parameter, text) {
value: function _createParameterTypeMatchers2(parameterType, text) {
// TODO: [].map
var result = [];
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator3 = parameter.regexps[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var regexp = _step3.value;
for (var _iterator4 = parameterType.regexps[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var regexp = _step4.value;
result.push(new ParameterTypeMatcher(parameter, regexp, text));
result.push(new ParameterTypeMatcher(parameterType, regexp, text));
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
if (_didIteratorError4) {
throw _iteratorError4;
}

@@ -156,0 +204,0 @@ }

@@ -1,2 +0,2 @@

"use strict";
'use strict';

@@ -7,14 +7,16 @@ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var matchPattern = require('./build_arguments');
var Argument = require('./argument');
var _require = require('./errors'),
UndefinedParameterTypeError = _require.UndefinedParameterTypeError;
var CucumberExpression = function () {
/**
* @param expression
* @param types Array of type name (String) or types (function). Functions can be a regular function or a constructor
* @param parameterTypeRegistry
*/
function CucumberExpression(expression, types, parameterTypeRegistry) {
function CucumberExpression(expression, parameterTypeRegistry) {
_classCallCheck(this, CucumberExpression);
var PARAMETER_REGEXP = /\{([^}:]+)(:([^}]+))?}/g;
var PARAMETER_REGEXP = /\{([^}]+)}/g;
var OPTIONAL_REGEXP = /\(([^)]+)\)/g;

@@ -25,4 +27,3 @@ var ALTERNATIVE_WORD_REGEXP = /(\w+)((\/\w+)+)/g;

this._parameterTypes = [];
var regexp = "^";
var typeIndex = 0;
var regexp = '^';
var match = void 0;

@@ -32,3 +33,3 @@ var matchOffset = 0;

// Does not include (){} because they have special meaning
expression = expression.replace(/([\\\^\[$.|?*+])/g, "\\$1");
expression = expression.replace(/([\\^[$.|?*+])/g, '\\$1');

@@ -39,35 +40,14 @@ // Create non-capturing, optional capture groups from parenthesis

expression = expression.replace(ALTERNATIVE_WORD_REGEXP, function (_, p1, p2) {
return "(?:" + p1 + p2.replace(/\//g, '|') + ")";
return '(?:' + p1 + p2.replace(/\//g, '|') + ')';
});
while ((match = PARAMETER_REGEXP.exec(expression)) !== null) {
var parameterName = match[1];
var parameterTypeName = match[3];
// eslint-disable-next-line no-console
if (parameterTypeName && typeof console !== 'undefined' && typeof console.error == 'function') {
// eslint-disable-next-line no-console
console.error("Cucumber expression parameter syntax {" + parameterName + ":" + parameterTypeName + "} is deprecated. Please use {" + parameterTypeName + "} instead.");
}
var typeName = match[1];
var type = types.length <= typeIndex ? null : types[typeIndex++];
var parameterType = parameterTypeRegistry.lookupByTypeName(typeName);
if (!parameterType) throw new UndefinedParameterTypeError(typeName);
this._parameterTypes.push(parameterType);
var parameter = void 0;
if (type) {
parameter = parameterTypeRegistry.lookupByType(type);
}
if (!parameter && parameterTypeName) {
parameter = parameterTypeRegistry.lookupByTypeName(parameterTypeName);
}
if (!parameter) {
parameter = parameterTypeRegistry.lookupByTypeName(parameterName);
}
if (!parameter) {
parameter = parameterTypeRegistry.createAnonymousLookup(function (s) {
return s;
});
}
this._parameterTypes.push(parameter);
var text = expression.slice(matchOffset, match.index);
var captureRegexp = getCaptureRegexp(parameter.regexps);
var captureRegexp = getCaptureRegexp(parameterType.regexps);
matchOffset = PARAMETER_REGEXP.lastIndex;

@@ -78,3 +58,3 @@ regexp += text;

regexp += expression.slice(matchOffset);
regexp += "$";
regexp += '$';
this._regexp = new RegExp(regexp);

@@ -84,8 +64,8 @@ }

_createClass(CucumberExpression, [{
key: "match",
key: 'match',
value: function match(text) {
return matchPattern(this._regexp, text, this._parameterTypes);
return Argument.build(this._regexp, text, this._parameterTypes);
}
}, {
key: "source",
key: 'source',
get: function get() {

@@ -101,12 +81,12 @@ return this._expression;

if (regexps.length === 1) {
return "(" + regexps[0] + ")";
return '(' + regexps[0] + ')';
}
var captureGroups = regexps.map(function (group) {
return "(?:" + group + ")";
return '(?:' + group + ')';
});
return "(" + captureGroups.join('|') + ")";
return '(' + captureGroups.join('|') + ')';
}
module.exports = CucumberExpression;

@@ -1,13 +0,16 @@

"use strict";
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var util = require('util');
var GeneratedExpression = function () {
function GeneratedExpression(expression, parameterNames, parameterTypes) {
function GeneratedExpression(expression, parameterTypes) {
_classCallCheck(this, GeneratedExpression);
this._expression = expression;
this._parameterNames = parameterNames;
this._expressionTemplate = expression;
this._parameterTypes = parameterTypes;

@@ -17,5 +20,7 @@ }

_createClass(GeneratedExpression, [{
key: "source",
key: 'source',
get: function get() {
return this._expression;
return util.format.apply(util, [this._expressionTemplate].concat(_toConsumableArray(this._parameterTypes.map(function (t) {
return t.name;
}))));
}

@@ -30,5 +35,8 @@

}, {
key: "parameterNames",
key: 'parameterNames',
get: function get() {
return this._parameterNames;
var usageByTypeName = {};
return this._parameterTypes.map(function (t) {
return getParameterName(t.name, usageByTypeName);
});
}

@@ -41,3 +49,3 @@

}, {
key: "parameterTypes",
key: 'parameterTypes',
get: function get() {

@@ -51,2 +59,10 @@ return this._parameterTypes;

function getParameterName(typeName, usageByTypeName) {
var count = usageByTypeName[typeName];
count = count ? count + 1 : 1;
usageByTypeName[typeName] = count;
return count === 1 ? typeName : '' + typeName + count;
}
module.exports = GeneratedExpression;

@@ -1,8 +0,8 @@

"use strict";
'use strict';
var CucumberExpression = require("./cucumber_expression");
var RegularExpression = require("./regular_expression");
var CucumberExpressionGenerator = require("./cucumber_expression_generator");
var ParameterTypeRegistry = require("./parameter_type_registry");
var ParameterType = require("./parameter_type");
var CucumberExpression = require('./cucumber_expression');
var RegularExpression = require('./regular_expression');
var CucumberExpressionGenerator = require('./cucumber_expression_generator');
var ParameterTypeRegistry = require('./parameter_type_registry');
var ParameterType = require('./parameter_type');

@@ -9,0 +9,0 @@ module.exports = {

@@ -49,5 +49,5 @@ "use strict";

var posComparison = a.start - b.start;
if (posComparison != 0) return posComparison;
if (posComparison !== 0) return posComparison;
var lengthComparison = b.group.length - a.group.length;
if (lengthComparison != 0) return lengthComparison;
if (lengthComparison !== 0) return lengthComparison;
return 0;

@@ -54,0 +54,0 @@ }

'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

@@ -9,4 +7,14 @@

var Parameter = require('./parameter_type');
var ParameterType = require('./parameter_type');
var CucumberExpressionGenerator = require('./cucumber_expression_generator.js');
var _require = require('./errors'),
CucumberExpressionError = _require.CucumberExpressionError,
AmbiguousParameterTypeError = _require.AmbiguousParameterTypeError;
var INTEGER_REGEXPS = [/-?\d+/, /\d+/];
var FLOAT_REGEXP = /-?\d*\.?\d+/;
var WORD_REGEXP = /\w+/;
var STRING_REGEXP = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/;
var ParameterTypeRegistry = function () {

@@ -16,82 +24,39 @@ function ParameterTypeRegistry() {

this._parameterTypesByTypeName = new Map();
this._parameterTypeByName = new Map();
this._parameterTypesByRegexp = new Map();
this._parameterTypesByConstructorName = new Map();
var INTEGER_REGEXPS = [/-?\d+/, /\d+/];
var FLOAT_REGEXP = /-?\d*\.?\d+/;
this._definePredefinedParameterType(new Parameter('int', Number, INTEGER_REGEXPS, parseInt));
this._definePredefinedParameterType(new Parameter('float', Number, FLOAT_REGEXP, parseFloat));
this.defineParameterType(new ParameterType('int', INTEGER_REGEXPS, Number, parseInt, true, true));
this.defineParameterType(new ParameterType('float', FLOAT_REGEXP, Number, parseFloat, true, false));
this.defineParameterType(new ParameterType('word', WORD_REGEXP, String, function (s) {
return s;
}, false, false));
this.defineParameterType(new ParameterType('string', STRING_REGEXP, String, function (s) {
return s.replace(/\\"/g, '"').replace(/\\'/g, "'");
}, true, false));
}
_createClass(ParameterTypeRegistry, [{
key: 'lookupByType',
value: function lookupByType(type) {
if (typeof type === 'function') {
return this.lookupByFunction(type);
} else if (typeof type === 'string') {
return this.lookupByTypeName(type);
} else {
throw new Error('Type must be string or function, but was ' + type + ' of type ' + (typeof type === 'undefined' ? 'undefined' : _typeof(type)));
}
}
}, {
key: 'lookupByFunction',
value: function lookupByFunction(fn) {
if (fn.name) {
var looksLikeCtor = looksLikeConstructor(fn);
var parameter = void 0;
if (looksLikeCtor) {
parameter = this._parameterTypesByConstructorName.get(fn.name);
}
if (!parameter) {
var factory = function factory(s) {
if (looksLikeCtor) {
return new fn(s);
} else {
return fn(s);
}
};
return this.createAnonymousLookup(factory);
} else {
return parameter;
}
} else {
return this.createAnonymousLookup(fn);
}
}
}, {
key: 'lookupByTypeName',
value: function lookupByTypeName(typeName) {
return this._parameterTypesByTypeName.get(typeName);
return this._parameterTypeByName.get(typeName);
}
}, {
key: 'lookupByRegexp',
value: function lookupByRegexp(regexp) {
return this._parameterTypesByRegexp.get(regexp);
value: function lookupByRegexp(parameterTypeRegexp, expressionRegexp, text) {
var parameterTypes = this._parameterTypesByRegexp.get(parameterTypeRegexp);
if (!parameterTypes) return null;
if (parameterTypes.length > 1 && !parameterTypes[0].preferForRegexpMatch) {
// We don't do this check on insertion because we only want to restrict
// ambiguiuty when we look up by Regexp. Users of CucumberExpression should
// not be restricted.
var generatedExpressions = new CucumberExpressionGenerator(this).generateExpressions(text);
throw new AmbiguousParameterTypeError.forRegExp(parameterTypeRegexp, expressionRegexp, parameterTypes, generatedExpressions);
}
return parameterTypes[0];
}
}, {
key: 'createAnonymousLookup',
value: function createAnonymousLookup(fn) {
return new Parameter(null, null, [".+"], fn);
}
}, {
key: 'defineParameterType',
value: function defineParameterType(parameterType) {
this._defineParameterType(parameterType, true);
}
}, {
key: '_definePredefinedParameterType',
value: function _definePredefinedParameterType(parameterType) {
this._defineParameterType(parameterType, false);
}
}, {
key: '_defineParameterType',
value: function _defineParameterType(parameterType, checkConflicts) {
if (looksLikeConstructor(parameterType.constructorFunction)) {
set(this._parameterTypesByConstructorName, parameterType.constructorFunction.name, parameterType, 'constructor', checkConflicts);
}
set(this._parameterTypesByTypeName, parameterType.name, parameterType, 'type name', checkConflicts);
if (this._parameterTypeByName.has(parameterType.name)) throw new Error('There is already a parameter type with name ' + parameterType.name);
this._parameterTypeByName.set(parameterType.name, parameterType);

@@ -104,5 +69,16 @@ var _iteratorNormalCompletion = true;

for (var _iterator = parameterType.regexps[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var regexp = _step.value;
var parameterTypeRegexp = _step.value;
set(this._parameterTypesByRegexp, regexp, parameterType, 'regexp', checkConflicts);
if (!this._parameterTypesByRegexp.has(parameterTypeRegexp)) {
this._parameterTypesByRegexp.set(parameterTypeRegexp, []);
}
var parameterTypes = this._parameterTypesByRegexp.get(parameterTypeRegexp);
var existingParameterType = parameterTypes[0];
if (parameterTypes.length > 0 && existingParameterType.preferForRegexpMatch && parameterType.preferForRegexpMatch) {
throw new CucumberExpressionError('There can only be one preferential parameter type per regexp. ' + ('The regexp /' + parameterTypeRegexp + '/ is used for two preferential parameter types, {' + existingParameterType.name + '} and {' + parameterType.name + '}'));
}
if (!parameterTypes.includes(parameterType)) {
parameterTypes.push(parameterType);
this._parameterTypesByRegexp.set(parameterTypeRegexp, parameterTypes.sort(ParameterType.compare));
}
}

@@ -127,3 +103,3 @@ } catch (err) {

get: function get() {
return this._parameterTypesByTypeName.values();
return this._parameterTypeByName.values();
}

@@ -135,14 +111,2 @@ }]);

function set(map, key, value, prop, checkConflicts) {
if (checkConflicts && map.has(key)) throw new Error('There is already a parameter with ' + prop + ' ' + key);
map.set(key, value);
}
function looksLikeConstructor(fn) {
if (typeof fn !== 'function') return false;
if (!fn.name) return false;
var prefix = fn.name[0];
return prefix.toUpperCase() === prefix;
}
module.exports = ParameterTypeRegistry;

@@ -7,10 +7,39 @@ 'use strict';

var _require = require('./errors'),
CucumberExpressionError = _require.CucumberExpressionError;
var ParameterType = function () {
function ParameterType(name, constructorFunction, regexps, transform) {
_createClass(ParameterType, null, [{
key: 'compare',
value: function compare(pt1, pt2) {
if (pt1.preferForRegexpMatch && !pt2.preferForRegexpMatch) return -1;
if (pt2.preferForRegexpMatch && !pt1.preferForRegexpMatch) return 1;
return pt1.name.localeCompare(pt2.name);
}
/**
* @param name {String} the name of the type
* @param regexps {Array.<RegExp>,RegExp,Array.<String>,String} that matches the type
* @param type {Function} the prototype (constructor) of the type. May be null.
* @param transform {Function} function transforming string to another type. May be null.
* @param useForSnippets {boolean} true if this should be used for snippets. Defaults to true.
* @param preferForRegexpMatch {boolean} true if this is a preferential type. Defaults to false.
*/
}]);
function ParameterType(name, regexps, type, transform, useForSnippets, preferForRegexpMatch) {
_classCallCheck(this, ParameterType);
if (transform === undefined) transform = function transform(s) {
return s;
};
if (useForSnippets === undefined) useForSnippets = true;
if (preferForRegexpMatch === undefined) preferForRegexpMatch = false;
this._name = name;
this._constructorFunction = constructorFunction;
this._regexps = stringArray(regexps);
this._type = type;
this._transform = transform;
this._useForSnippets = useForSnippets;
this._preferForRegexpMatch = preferForRegexpMatch;
}

@@ -20,4 +49,13 @@

key: 'transform',
value: function transform(string) {
return this._transform ? this._transform(string) : string;
value: function transform(groupValues) {
if (this._transform.length === 1) {
// transform function with arity 1.
var nonNullGroupValues = groupValues.filter(function (v) {
return v !== null && v !== undefined;
});
if (nonNullGroupValues.length >= 2) throw new CucumberExpressionError('Single transformer unexpectedly matched 2 values - "' + nonNullGroupValues[0] + '" and "' + nonNullGroupValues[1] + '"');
return this._transform(nonNullGroupValues[0]);
}
return this._transform.apply(null, groupValues);
}

@@ -30,11 +68,21 @@ }, {

}, {
key: 'constructorFunction',
key: 'regexps',
get: function get() {
return this._constructorFunction;
return this._regexps;
}
}, {
key: 'regexps',
key: 'type',
get: function get() {
return this._regexps;
return this._type;
}
}, {
key: 'preferForRegexpMatch',
get: function get() {
return this._preferForRegexpMatch;
}
}, {
key: 'useForSnippets',
get: function get() {
return this._useForSnippets;
}
}]);

@@ -48,3 +96,3 @@

return array.map(function (r) {
return typeof r == 'string' ? r : r.source;
return typeof r === 'string' ? r : r.source;
});

@@ -51,0 +99,0 @@ }

@@ -7,39 +7,34 @@ 'use strict';

var buildArguments = require('./build_arguments');
var Argument = require('./argument');
var ParameterType = require('./parameter_type');
var RegularExpression = function () {
function RegularExpression(regexp, types, parameterTypeRegistry) {
function RegularExpression(regexp, parameterTypeRegistry) {
_classCallCheck(this, RegularExpression);
this._regexp = regexp;
this._parameterTypes = [];
this._parameterTypeRegistry = parameterTypeRegistry;
}
var CAPTURE_GROUP_PATTERN = /\(([^(]+)\)/g;
_createClass(RegularExpression, [{
key: 'match',
value: function match(text) {
var parameterTypes = [];
var typeIndex = 0;
var match = void 0;
while ((match = CAPTURE_GROUP_PATTERN.exec(regexp.source)) !== null) {
var captureGroupPattern = match[1];
var type = types.length <= typeIndex ? null : types[typeIndex++];
var CAPTURE_GROUP_PATTERN = /\((?!\?:)([^(]+)\)/g;
var parameterType = void 0;
if (type) {
parameterType = parameterTypeRegistry.lookupByType(type);
var match = void 0;
while ((match = CAPTURE_GROUP_PATTERN.exec(this._regexp.source)) !== null) {
var parameterTypeRegexp = match[1];
var parameterType = this._parameterTypeRegistry.lookupByRegexp(parameterTypeRegexp, this._regexp, text);
if (!parameterType) {
parameterType = new ParameterType('*', parameterTypeRegexp, String, function (s) {
return s;
}, false, false);
}
parameterTypes.push(parameterType);
}
if (!parameterType) {
parameterType = parameterTypeRegistry.lookupByRegexp(captureGroupPattern);
}
if (!parameterType) {
parameterType = parameterTypeRegistry.createAnonymousLookup(function (s) {
return s;
});
}
this._parameterTypes.push(parameterType);
}
}
_createClass(RegularExpression, [{
key: 'match',
value: function match(text) {
return buildArguments(this._regexp, text, this._parameterTypes);
return Argument.build(this._regexp, text, parameterTypes);
}

@@ -46,0 +41,0 @@ }, {

@@ -1,14 +0,10 @@

I have {n} cuke(s) in my {bodypart} now
I have 22 cukes in my belly now
["22","belly"]
I have {int} cuke(s)
I have 22 cukes
[22]
---
I have {int} cuke(s) in my {bodypart} now
I have 1 cuke in my belly now
[1,"belly"]
I have {int} cuke(s) and some \[]^$.|?*+
I have 1 cuke and some \[]^$.|?*+
[1]
---
I have {int} cuke(s) and some \[]^$.|?*+ {something}
I have 1 cuke and some \[]^$.|?*+ characters
[1,"characters"]
---
/I have (\d+) cukes? in my (.+) now/
/I have (\d+) cukes? in my (\w+) now/
I have 22 cukes in my belly now

@@ -15,0 +11,0 @@ [22,"belly"]

{
"name": "cucumber-expressions",
"version": "3.0.0",
"version": "4.0.0",
"description": "Cucumber Expressions - a simpler alternative to Regular Expressions",

@@ -13,3 +13,4 @@ "main": "dist/index.js",

"mocha": "mocha",
"coverage": "node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- test"
"coverage": "node --harmony ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- test",
"precommit": "lint-staged"
},

@@ -35,10 +36,18 @@ "repository": {

"babel-preset-es2015": "^6.22.0",
"eslint": "^3.15.0",
"eslint": "^4.0.0",
"eslint-config-eslint": "^4.0.0",
"eslint-config-prettier": "^2.1.1",
"eslint-plugin-prettier": "^2.0.1",
"husky": "^0.13.3",
"istanbul": "1.0.0-alpha.2",
"mocha": "^3.2.0"
"lint-staged": "^3.4.1",
"mocha": "^3.2.0",
"prettier": "^1.3.1"
},
"files": [
"*"
]
],
"dependencies": {
"becke-ch--regex--s0-0-v1--base--pl--lib": "^1.2.0"
}
}

@@ -5,6 +5,6 @@ var path = require('path')

fs.exists(path.join(__dirname, '..', 'dist'), function (distExists, err) {
fs.exists(path.join(__dirname, '..', 'dist'), function(distExists, err) {
if (err) throw err
if (!distExists) {
exec('npm run build', {cwd: path.join(__dirname, '..')}, function (err) {
exec('npm run build', { cwd: path.join(__dirname, '..') }, function(err) {
if (err) throw err

@@ -11,0 +11,0 @@ })

@@ -0,21 +1,40 @@

const Regex = require('becke-ch--regex--s0-0-v1--base--pl--lib')
const Group = require('./group')
const { CucumberExpressionError } = require('./errors')
class Argument {
constructor(offset, value, parameterType) {
this._offset = offset
this._value = value
this._parameterType = parameterType
static build(regexp, text, parameterTypes) {
const m = new Regex(regexp).exec(text)
if (!m) return null
const matchGroup = new Group(m)
const argGroups = matchGroup.children
if (argGroups.length !== parameterTypes.length) {
throw new CucumberExpressionError(
`Expression ${regexp} has ${argGroups.length} arguments (${argGroups.map(
g => g.value
)}), but there were ${parameterTypes.length} parameter types (${parameterTypes.map(
p => p.name
)})`
)
}
return parameterTypes.map(
(parameterType, i) => new Argument(argGroups[i], parameterType)
)
}
get offset() {
return this._offset
constructor(group, parameterType) {
this._group = group
this._parameterType = parameterType
}
get value() {
return this._value
return this._parameterType.transform(
this._group ? this._group.values : null
)
}
get transformedValue() {
return this._parameterType.transform(this._value)
}
}
module.exports = Argument

@@ -0,3 +1,5 @@

const util = require('util')
const ParameterTypeMatcher = require('./parameter_type_matcher')
const GeneratedExpression = require('./generated_expression')
const ParameterType = require('./parameter_type')
const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory')

@@ -9,13 +11,12 @@ class CucumberExpressionGenerator {

generateExpression(text) {
const parameterNames = []
generateExpressions(text) {
const parameterTypeCombinations = []
const parameterTypeMatchers = this._createParameterTypeMatchers(text)
const parameterTypes = []
const usageByTypeName = {}
let expression = ""
let expressionTemplate = ''
let pos = 0
while (true) { // eslint-disable-line no-constant-condition
// eslint-disable-next-line no-constant-condition
while (true) {
let matchingParameterTypeMatchers = []
for (const parameterTypeMatcher of parameterTypeMatchers) {

@@ -29,14 +30,33 @@ const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos)

if (matchingParameterTypeMatchers.length > 0) {
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(ParameterTypeMatcher.compare)
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(
ParameterTypeMatcher.compare
)
// Find all the best parameter type matchers, they are all candidates.
const bestParameterTypeMatcher = matchingParameterTypeMatchers[0]
const parameter = bestParameterTypeMatcher.parameterType
parameterTypes.push(parameter)
const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter(
m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0
)
const parameterName = this._getParameterName(parameter.name, usageByTypeName)
parameterNames.push(parameterName)
// Build a list of parameter types without duplicates. The reason there
// might be duplicates is that some parameter types have more than one regexp,
// which means multiple ParameterTypeMatcher objects will have a reference to the
// same ParameterType.
// We're sorting the list so preferential parameter types are listed first.
// Users are most likely to want these, so they should be listed at the top.
let parameterTypes = []
for (const parameterTypeMatcher of bestParameterTypeMatchers) {
if (!parameterTypes.includes(parameterTypeMatcher.parameterType)) {
parameterTypes.push(parameterTypeMatcher.parameterType)
}
}
parameterTypes = parameterTypes.sort(ParameterType.compare)
expression += text.slice(pos, bestParameterTypeMatcher.start)
expression += `{${parameter.name}}`
parameterTypeCombinations.push(parameterTypes)
pos = bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length
expressionTemplate += text.slice(pos, bestParameterTypeMatcher.start)
expressionTemplate += '{%s}'
pos =
bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length
} else {

@@ -51,12 +71,17 @@ break

expression += text.slice(pos)
return new GeneratedExpression(expression, parameterNames, parameterTypes)
expressionTemplate += text.slice(pos)
return new CombinatorialGeneratedExpressionFactory(
expressionTemplate,
parameterTypeCombinations
).generateExpressions()
}
_getParameterName(typeName, usageByTypeName) {
let count = usageByTypeName[typeName]
count = count ? count + 1 : 1
usageByTypeName[typeName] = count
return count == 1 ? typeName : `${typeName}${count}`
/**
* @deprecated
*/
generateExpression(text) {
return util.deprecate(
() => this.generateExpressions(text)[0],
'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead'
)()
}

@@ -66,4 +91,8 @@

let parameterMatchers = []
for (const parameter of this._parameterTypeRegistry.parameterTypes) {
parameterMatchers = parameterMatchers.concat(this._createParameterTypeMatchers2(parameter, text))
for (const parameterType of this._parameterTypeRegistry.parameterTypes) {
if (parameterType.useForSnippets) {
parameterMatchers = parameterMatchers.concat(
this._createParameterTypeMatchers2(parameterType, text)
)
}
}

@@ -73,6 +102,7 @@ return parameterMatchers

_createParameterTypeMatchers2(parameter, text) {
_createParameterTypeMatchers2(parameterType, text) {
// TODO: [].map
const result = []
for (const regexp of parameter.regexps) {
result.push(new ParameterTypeMatcher(parameter, regexp, text))
for (const regexp of parameterType.regexps) {
result.push(new ParameterTypeMatcher(parameterType, regexp, text))
}

@@ -79,0 +109,0 @@ return result

@@ -1,2 +0,3 @@

const matchPattern = require('./build_arguments')
const Argument = require('./argument')
const { UndefinedParameterTypeError } = require('./errors')

@@ -6,7 +7,6 @@ class CucumberExpression {

* @param expression
* @param types Array of type name (String) or types (function). Functions can be a regular function or a constructor
* @param parameterTypeRegistry
*/
constructor (expression, types, parameterTypeRegistry) {
const PARAMETER_REGEXP = /\{([^}:]+)(:([^}]+))?}/g
constructor(expression, parameterTypeRegistry) {
const PARAMETER_REGEXP = /\{([^}]+)}/g
const OPTIONAL_REGEXP = /\(([^)]+)\)/g

@@ -17,4 +17,3 @@ const ALTERNATIVE_WORD_REGEXP = /(\w+)((\/\w+)+)/g

this._parameterTypes = []
let regexp = "^"
let typeIndex = 0
let regexp = '^'
let match

@@ -24,3 +23,3 @@ let matchOffset = 0

// Does not include (){} because they have special meaning
expression = expression.replace(/([\\\^\[$.|?*+])/g, "\\$1")
expression = expression.replace(/([\\^[$.|?*+])/g, '\\$1')

@@ -30,32 +29,16 @@ // Create non-capturing, optional capture groups from parenthesis

expression = expression.replace(ALTERNATIVE_WORD_REGEXP, (_, p1, p2) => `(?:${p1}${p2.replace(/\//g, '|')})`)
expression = expression.replace(
ALTERNATIVE_WORD_REGEXP,
(_, p1, p2) => `(?:${p1}${p2.replace(/\//g, '|')})`
)
while ((match = PARAMETER_REGEXP.exec(expression)) !== null) {
const parameterName = match[1]
const parameterTypeName = match[3]
// eslint-disable-next-line no-console
if (parameterTypeName && (typeof console !== 'undefined') && (typeof console.error == 'function')) {
// eslint-disable-next-line no-console
console.error(`Cucumber expression parameter syntax {${parameterName}:${parameterTypeName}} is deprecated. Please use {${parameterTypeName}} instead.`)
}
const typeName = match[1]
const type = types.length <= typeIndex ? null : types[typeIndex++]
const parameterType = parameterTypeRegistry.lookupByTypeName(typeName)
if (!parameterType) throw new UndefinedParameterTypeError(typeName)
this._parameterTypes.push(parameterType)
let parameter
if (type) {
parameter = parameterTypeRegistry.lookupByType(type)
}
if (!parameter && parameterTypeName) {
parameter = parameterTypeRegistry.lookupByTypeName(parameterTypeName)
}
if (!parameter) {
parameter = parameterTypeRegistry.lookupByTypeName(parameterName)
}
if (!parameter) {
parameter = parameterTypeRegistry.createAnonymousLookup(s => s)
}
this._parameterTypes.push(parameter)
const text = expression.slice(matchOffset, match.index)
const captureRegexp = getCaptureRegexp(parameter.regexps)
const captureRegexp = getCaptureRegexp(parameterType.regexps)
matchOffset = PARAMETER_REGEXP.lastIndex

@@ -66,11 +49,11 @@ regexp += text

regexp += expression.slice(matchOffset)
regexp += "$"
regexp += '$'
this._regexp = new RegExp(regexp)
}
match (text) {
return matchPattern(this._regexp, text, this._parameterTypes)
match(text) {
return Argument.build(this._regexp, text, this._parameterTypes)
}
get source () {
get source() {
return this._expression

@@ -80,3 +63,3 @@ }

function getCaptureRegexp (regexps) {
function getCaptureRegexp(regexps) {
if (regexps.length === 1) {

@@ -83,0 +66,0 @@ return `(${regexps[0]})`

@@ -0,5 +1,6 @@

const util = require('util')
class GeneratedExpression {
constructor(expression, parameterNames, parameterTypes) {
this._expression = expression
this._parameterNames = parameterNames
constructor(expression, parameterTypes) {
this._expressionTemplate = expression
this._parameterTypes = parameterTypes

@@ -9,3 +10,6 @@ }

get source() {
return this._expression
return util.format(
this._expressionTemplate,
...this._parameterTypes.map(t => t.name)
)
}

@@ -19,3 +23,6 @@

get parameterNames() {
return this._parameterNames
const usageByTypeName = {}
return this._parameterTypes.map(t =>
getParameterName(t.name, usageByTypeName)
)
}

@@ -31,2 +38,10 @@

function getParameterName(typeName, usageByTypeName) {
let count = usageByTypeName[typeName]
count = count ? count + 1 : 1
usageByTypeName[typeName] = count
return count === 1 ? typeName : `${typeName}${count}`
}
module.exports = GeneratedExpression

@@ -1,6 +0,6 @@

const CucumberExpression = require("./cucumber_expression")
const RegularExpression = require("./regular_expression")
const CucumberExpressionGenerator = require("./cucumber_expression_generator")
const ParameterTypeRegistry = require("./parameter_type_registry")
const ParameterType = require("./parameter_type")
const CucumberExpression = require('./cucumber_expression')
const RegularExpression = require('./regular_expression')
const CucumberExpressionGenerator = require('./cucumber_expression_generator')
const ParameterTypeRegistry = require('./parameter_type_registry')
const ParameterType = require('./parameter_type')

@@ -12,3 +12,3 @@ module.exports = {

ParameterTypeRegistry,
ParameterType
ParameterType,
}
class ParameterTypeMatcher {
constructor(parameter, regexp, text, matchPosition) {

@@ -18,3 +17,8 @@ this._parameterType = parameter

advanceTo(newMatchPosition) {
return new ParameterTypeMatcher(this._parameterType, this._regexp, this._text, newMatchPosition)
return new ParameterTypeMatcher(
this._parameterType,
this._regexp,
this._text,
newMatchPosition
)
}

@@ -36,5 +40,5 @@

const posComparison = a.start - b.start
if (posComparison != 0) return posComparison
if (posComparison !== 0) return posComparison
const lengthComparison = b.group.length - a.group.length
if (lengthComparison != 0) return lengthComparison
if (lengthComparison !== 0) return lengthComparison
return 0

@@ -41,0 +45,0 @@ }

@@ -1,101 +0,103 @@

const Parameter = require('./parameter_type')
const ParameterType = require('./parameter_type')
const CucumberExpressionGenerator = require('./cucumber_expression_generator.js')
const {
CucumberExpressionError,
AmbiguousParameterTypeError,
} = require('./errors')
const INTEGER_REGEXPS = [/-?\d+/, /\d+/]
const FLOAT_REGEXP = /-?\d*\.?\d+/
const WORD_REGEXP = /\w+/
const STRING_REGEXP = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/
class ParameterTypeRegistry {
constructor() {
this._parameterTypesByTypeName = new Map()
this._parameterTypeByName = new Map()
this._parameterTypesByRegexp = new Map()
this._parameterTypesByConstructorName = new Map()
const INTEGER_REGEXPS = [/-?\d+/, /\d+/]
const FLOAT_REGEXP = /-?\d*\.?\d+/
this._definePredefinedParameterType(new Parameter('int', Number, INTEGER_REGEXPS, parseInt))
this._definePredefinedParameterType(new Parameter('float', Number, FLOAT_REGEXP, parseFloat))
this.defineParameterType(
new ParameterType('int', INTEGER_REGEXPS, Number, parseInt, true, true)
)
this.defineParameterType(
new ParameterType('float', FLOAT_REGEXP, Number, parseFloat, true, false)
)
this.defineParameterType(
new ParameterType('word', WORD_REGEXP, String, s => s, false, false)
)
this.defineParameterType(
new ParameterType(
'string',
STRING_REGEXP,
String,
s => s.replace(/\\"/g, '"').replace(/\\'/g, "'"),
true,
false
)
)
}
get parameterTypes() {
return this._parameterTypesByTypeName.values()
return this._parameterTypeByName.values()
}
lookupByType(type) {
if (typeof type === 'function') {
return this.lookupByFunction(type)
} else if (typeof type === 'string') {
return this.lookupByTypeName(type)
} else {
throw new Error(`Type must be string or function, but was ${type} of type ${typeof type}`)
}
lookupByTypeName(typeName) {
return this._parameterTypeByName.get(typeName)
}
lookupByFunction(fn) {
if (fn.name) {
const looksLikeCtor = looksLikeConstructor(fn)
let parameter
if (looksLikeCtor) {
parameter = this._parameterTypesByConstructorName.get(fn.name)
}
if (!parameter) {
const factory = s => {
if (looksLikeCtor) {
return new fn(s)
} else {
return fn(s)
}
}
return this.createAnonymousLookup(factory)
} else {
return parameter
}
} else {
return this.createAnonymousLookup(fn)
lookupByRegexp(parameterTypeRegexp, expressionRegexp, text) {
const parameterTypes = this._parameterTypesByRegexp.get(parameterTypeRegexp)
if (!parameterTypes) return null
if (parameterTypes.length > 1 && !parameterTypes[0].preferForRegexpMatch) {
// We don't do this check on insertion because we only want to restrict
// ambiguiuty when we look up by Regexp. Users of CucumberExpression should
// not be restricted.
const generatedExpressions = new CucumberExpressionGenerator(
this
).generateExpressions(text)
throw new AmbiguousParameterTypeError.forRegExp(
parameterTypeRegexp,
expressionRegexp,
parameterTypes,
generatedExpressions
)
}
return parameterTypes[0]
}
lookupByTypeName(typeName) {
return this._parameterTypesByTypeName.get(typeName)
}
lookupByRegexp(regexp) {
return this._parameterTypesByRegexp.get(regexp)
}
createAnonymousLookup(fn) {
return new Parameter(null, null, [".+"], fn)
}
defineParameterType(parameterType) {
this._defineParameterType(parameterType, true)
}
if (this._parameterTypeByName.has(parameterType.name))
throw new Error(
`There is already a parameter type with name ${parameterType.name}`
)
this._parameterTypeByName.set(parameterType.name, parameterType)
_definePredefinedParameterType(parameterType) {
this._defineParameterType(parameterType, false)
}
_defineParameterType(parameterType, checkConflicts) {
if(looksLikeConstructor(parameterType.constructorFunction)) {
set(this._parameterTypesByConstructorName, parameterType.constructorFunction.name, parameterType, 'constructor', checkConflicts)
for (const parameterTypeRegexp of parameterType.regexps) {
if (!this._parameterTypesByRegexp.has(parameterTypeRegexp)) {
this._parameterTypesByRegexp.set(parameterTypeRegexp, [])
}
const parameterTypes = this._parameterTypesByRegexp.get(
parameterTypeRegexp
)
const existingParameterType = parameterTypes[0]
if (
parameterTypes.length > 0 &&
existingParameterType.preferForRegexpMatch &&
parameterType.preferForRegexpMatch
) {
throw new CucumberExpressionError(
'There can only be one preferential parameter type per regexp. ' +
`The regexp /${parameterTypeRegexp}/ is used for two preferential parameter types, {${existingParameterType.name}} and {${parameterType.name}}`
)
}
if (!parameterTypes.includes(parameterType)) {
parameterTypes.push(parameterType)
this._parameterTypesByRegexp.set(
parameterTypeRegexp,
parameterTypes.sort(ParameterType.compare)
)
}
}
set(this._parameterTypesByTypeName, parameterType.name, parameterType, 'type name', checkConflicts)
for (const regexp of parameterType.regexps) {
set(this._parameterTypesByRegexp, regexp, parameterType, 'regexp', checkConflicts)
}
}
}
function set(map, key, value, prop, checkConflicts) {
if(checkConflicts && map.has(key))
throw new Error(`There is already a parameter with ${prop} ${key}`)
map.set(key, value)
}
function looksLikeConstructor (fn) {
if(typeof fn !== 'function') return false
if(!fn.name) return false
const prefix = fn.name[0]
return prefix.toUpperCase() === prefix
}
module.exports = ParameterTypeRegistry

@@ -0,7 +1,35 @@

const { CucumberExpressionError } = require('./errors')
class ParameterType {
constructor(name, constructorFunction, regexps, transform) {
static compare(pt1, pt2) {
if (pt1.preferForRegexpMatch && !pt2.preferForRegexpMatch) return -1
if (pt2.preferForRegexpMatch && !pt1.preferForRegexpMatch) return 1
return pt1.name.localeCompare(pt2.name)
}
/**
* @param name {String} the name of the type
* @param regexps {Array.<RegExp>,RegExp,Array.<String>,String} that matches the type
* @param type {Function} the prototype (constructor) of the type. May be null.
* @param transform {Function} function transforming string to another type. May be null.
* @param useForSnippets {boolean} true if this should be used for snippets. Defaults to true.
* @param preferForRegexpMatch {boolean} true if this is a preferential type. Defaults to false.
*/
constructor(
name,
regexps,
type,
transform,
useForSnippets,
preferForRegexpMatch
) {
if (transform === undefined) transform = s => s
if (useForSnippets === undefined) useForSnippets = true
if (preferForRegexpMatch === undefined) preferForRegexpMatch = false
this._name = name
this._constructorFunction = constructorFunction
this._regexps = stringArray(regexps)
this._type = type
this._transform = transform
this._useForSnippets = useForSnippets
this._preferForRegexpMatch = preferForRegexpMatch
}

@@ -13,6 +41,2 @@

get constructorFunction() {
return this._constructorFunction
}
get regexps() {

@@ -22,5 +46,29 @@ return this._regexps

transform(string) {
return this._transform ? this._transform(string) : string
get type() {
return this._type
}
get preferForRegexpMatch() {
return this._preferForRegexpMatch
}
get useForSnippets() {
return this._useForSnippets
}
transform(groupValues) {
if (this._transform.length === 1) {
// transform function with arity 1.
const nonNullGroupValues = groupValues.filter(
v => v !== null && v !== undefined
)
if (nonNullGroupValues.length >= 2)
throw new CucumberExpressionError(
`Single transformer unexpectedly matched 2 values - "${nonNullGroupValues[0]}" and "${nonNullGroupValues[1]}"`
)
return this._transform(nonNullGroupValues[0])
}
return this._transform.apply(null, groupValues)
}
}

@@ -30,5 +78,5 @@

const array = Array.isArray(regexps) ? regexps : [regexps]
return array.map(r => typeof r == 'string' ? r : r.source)
return array.map(r => (typeof r === 'string' ? r : r.source))
}
module.exports = ParameterType

@@ -1,32 +0,38 @@

const buildArguments = require('./build_arguments')
const Argument = require('./argument')
const ParameterType = require('./parameter_type')
class RegularExpression {
constructor(regexp, types, parameterTypeRegistry) {
constructor(regexp, parameterTypeRegistry) {
this._regexp = regexp
this._parameterTypes = []
this._parameterTypeRegistry = parameterTypeRegistry
}
const CAPTURE_GROUP_PATTERN = /\(([^(]+)\)/g
match(text) {
const parameterTypes = []
let typeIndex = 0
const CAPTURE_GROUP_PATTERN = /\((?!\?:)([^(]+)\)/g
let match
while ((match = CAPTURE_GROUP_PATTERN.exec(regexp.source)) !== null) {
const captureGroupPattern = match[1]
const type = types.length <= typeIndex ? null : types[typeIndex++]
while ((match = CAPTURE_GROUP_PATTERN.exec(this._regexp.source)) !== null) {
const parameterTypeRegexp = match[1]
let parameterType
if (type) {
parameterType = parameterTypeRegistry.lookupByType(type)
}
let parameterType = this._parameterTypeRegistry.lookupByRegexp(
parameterTypeRegexp,
this._regexp,
text
)
if (!parameterType) {
parameterType = parameterTypeRegistry.lookupByRegexp(captureGroupPattern)
parameterType = new ParameterType(
'*',
parameterTypeRegexp,
String,
s => s,
false,
false
)
}
if (!parameterType) {
parameterType = parameterTypeRegistry.createAnonymousLookup(s => s)
}
this._parameterTypes.push(parameterType)
parameterTypes.push(parameterType)
}
}
match(text) {
return buildArguments(this._regexp, text, this._parameterTypes)
return Argument.build(this._regexp, text, parameterTypes)
}

@@ -33,0 +39,0 @@

@@ -5,4 +5,4 @@ const assert = require('assert')

module.exports = (fn, message) => {
const regexp = new RegExp(message.replace(/[\-\[\]\/{}()*+?.\\\^$|]/g, "\\$&"))
const regexp = new RegExp(message.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))
assert.throws(fn, regexp)
}

@@ -7,11 +7,9 @@ /* eslint-env mocha */

class Currency {
}
class Currency {}
describe(CucumberExpressionGenerator.name, () => {
describe('CucumberExpressionGenerator', () => {
let parameterTypeRegistry, generator
function assertExpression(expectedExpression, expectedArgumentNames, text) {
const generatedExpression = generator.generateExpression(text)
const generatedExpression = generator.generateExpressions(text)[0]
assert.deepEqual(generatedExpression.parameterNames, expectedArgumentNames)

@@ -26,9 +24,14 @@ assert.equal(generatedExpression.source, expectedExpression)

it("documents expression generation", () => {
it('documents expression generation', () => {
const parameterRegistry = new ParameterTypeRegistry()
/// [generate-expression]
const generator = new CucumberExpressionGenerator(parameterRegistry)
const undefinedStepText = "I have 2 cucumbers and 1.5 tomato"
const generatedExpression = generator.generateExpression(undefinedStepText)
assert.equal(generatedExpression.source, "I have {int} cucumbers and {float} tomato")
const undefinedStepText = 'I have 2 cucumbers and 1.5 tomato'
const generatedExpression = generator.generateExpressions(
undefinedStepText
)[0]
assert.equal(
generatedExpression.source,
'I have {int} cucumbers and {float} tomato'
)
assert.equal(generatedExpression.parameterNames[0], 'int')

@@ -39,58 +42,108 @@ assert.equal(generatedExpression.parameterTypes[1].name, 'float')

it("generates expression for no args", () => {
assertExpression("hello", [], "hello")
it('generates expression for no args', () => {
assertExpression('hello', [], 'hello')
})
it("generates expression for int float arg", () => {
it('generates expression for int float arg', () => {
assertExpression(
"I have {int} cukes and {float} euro", ["int", "float"],
"I have 2 cukes and 1.5 euro")
'I have {int} cukes and {float} euro',
['int', 'float'],
'I have 2 cukes and 1.5 euro'
)
})
it("generates expression for just int", () => {
it('generates expression for strings', () => {
assertExpression(
"{int}", ["int"],
"99999")
'I like {string} and {string}',
['string', 'string2'],
'I like "bangers" and \'mash\''
)
})
it("numbers only second argument when builtin type is not reserved keyword", () => {
it('generates expression for just int', () => {
assertExpression('{int}', ['int'], '99999')
})
it('numbers only second argument when builtin type is not reserved keyword', () => {
assertExpression(
"I have {float} cukes and {float} euro", ["float", "float2"],
"I have 2.5 cukes and 1.5 euro")
'I have {float} cukes and {float} euro',
['float', 'float2'],
'I have 2.5 cukes and 1.5 euro'
)
})
it("generates expression for custom type", () => {
parameterTypeRegistry.defineParameterType(new ParameterType(
'currency',
Currency,
'[A-Z]{3}',
null
))
it('generates expression for custom type', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'currency',
/[A-Z]{3}/,
Currency,
s => new Currency(s),
true,
false
)
)
assertExpression(
"I have a {currency} account", ["currency"],
"I have a EUR account")
'I have a {currency} account',
['currency'],
'I have a EUR account'
)
})
it("prefers leftmost match when there is overlap", () => {
parameterTypeRegistry.defineParameterType(new ParameterType(
'currency',
Currency,
'cd',
null
))
parameterTypeRegistry.defineParameterType(new ParameterType(
'date',
Date,
'bc',
null
))
it('prefers leftmost match when there is overlap', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'currency',
/cd/,
Currency,
s => new Currency(s),
true,
false
)
)
parameterTypeRegistry.defineParameterType(
new ParameterType('date', /bc/, Date, s => new Date(s), true, false)
)
assertExpression(
"a{date}defg", ["date"],
"abcdefg")
assertExpression('a{date}defg', ['date'], 'abcdefg')
})
it("exposes parameter type names in generated expression", () => {
const expression = generator.generateExpression("I have 2 cukes and 1.5 euro")
// TODO: prefers widest match
it('generates all combinations of expressions when several parameter types match', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'currency',
/x/,
null,
s => new Currency(s),
true,
false
)
)
parameterTypeRegistry.defineParameterType(
new ParameterType('date', /x/, null, s => new Date(s), true, false)
)
const generatedExpressions = generator.generateExpressions(
'I have x and x and another x'
)
const expressions = generatedExpressions.map(e => e.source)
assert.deepEqual(expressions, [
'I have {currency} and {currency} and another {currency}',
'I have {currency} and {currency} and another {date}',
'I have {currency} and {date} and another {currency}',
'I have {currency} and {date} and another {date}',
'I have {date} and {currency} and another {currency}',
'I have {date} and {currency} and another {date}',
'I have {date} and {date} and another {currency}',
'I have {date} and {date} and another {date}',
])
})
it('exposes parameter type names in generated expression', () => {
const expression = generator.generateExpressions(
'I have 2 cukes and 1.5 euro'
)[0]
const typeNames = expression.parameterTypes.map(parameter => parameter.name)

@@ -97,0 +150,0 @@ assert.deepEqual(typeNames, ['int', 'float'])

@@ -6,7 +6,7 @@ /* eslint-env mocha */

describe(CucumberExpression.name, () => {
describe('CucumberExpression', () => {
describe('RegExp translation', () => {
it("translates no arguments", () => {
it('translates no arguments', () => {
assertRegexp(
"I have 10 cukes in my belly now",
'I have 10 cukes in my belly now',
/^I have 10 cukes in my belly now$/

@@ -16,5 +16,5 @@ )

it("translates alternation", () => {
it('translates alternation', () => {
assertRegexp(
"I had/have a great/nice/charming friend",
'I had/have a great/nice/charming friend',
/^I (?:had|have) a (?:great|nice|charming) friend$/

@@ -24,19 +24,12 @@ )

it("translates two untyped arguments", () => {
it('translates parameters', () => {
assertRegexp(
"I have {n} cukes in my {bodypart} now",
/^I have (.+) cukes in my (.+) now$/
"I have {float} cukes at {int} o'clock",
/^I have (-?\d*\.?\d+) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/
)
})
it("translates three typed arguments", () => {
it('translates parenthesis to non-capturing optional capture group', () => {
assertRegexp(
"I have {float} cukes in my {bodypart} at {int} o'clock",
/^I have (-?\d*\.?\d+) cukes in my (.+) at ((?:-?\d+)|(?:\d+)) o'clock$/
)
})
it("translates parenthesis to non-capturing optional capture group", () => {
assertRegexp(
"I have many big(ish) cukes",
'I have many big(ish) cukes',
/^I have many big(?:ish)? cukes$/

@@ -49,4 +42,7 @@ )

const assertRegexp = (expression, expectedRegexp) => {
const cucumberExpression = new CucumberExpression(expression, [], new ParameterTypeRegistry())
const cucumberExpression = new CucumberExpression(
expression,
new ParameterTypeRegistry()
)
assert.deepEqual(cucumberExpression._regexp, expectedRegexp)
}

@@ -5,71 +5,98 @@ /* eslint-env mocha */

describe(CucumberExpression.name, () => {
it("documents match arguments", () => {
describe('CucumberExpression', () => {
it('documents match arguments', () => {
const parameterTypeRegistry = new ParameterTypeRegistry()
/// [capture-match-arguments]
const expr = "I have {n} cuke(s) in my {bodypart} now"
const types = ['int', null]
const expression = new CucumberExpression(expr, types, parameterTypeRegistry)
const args = expression.match("I have 7 cukes in my belly now")
assert.equal(7, args[0].transformedValue)
assert.equal("belly", args[1].transformedValue)
const expr = 'I have {int} cuke(s)'
const expression = new CucumberExpression(expr, parameterTypeRegistry)
const args = expression.match('I have 7 cukes')
assert.equal(7, args[0].value)
/// [capture-match-arguments]
})
it("does no transform by default", () => {
assert.deepEqual(match("{what}", "22"), ["22"])
it('matches word', () => {
assert.deepEqual(match('three {word} mice', 'three blind mice'), ['blind'])
})
it("transforms to int by parameterType type", () => {
assert.deepEqual(match("{int}", "22"), [22])
it('matches double quoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind" mice'), [
'blind',
])
})
it("transforms to int by explicit type", () => {
assert.deepEqual(match("{what}", "22", ['int']), [22])
it('matches single quoted string', () => {
assert.deepEqual(match('three {string} mice', "three 'blind' mice"), [
'blind',
])
})
it("doesn't match a float with an int parameterType", () => {
assert.deepEqual(match("{int}", "1.22"), null)
it('does not match misquoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind\' mice'), null)
})
it("transforms to float by parameterType type", () => {
assert.deepEqual(match("{float}", "0.22"), [0.22])
assert.deepEqual(match("{float}", ".22"), [0.22])
it('matches single quoted string with double quotes', () => {
assert.deepEqual(match('three {string} mice', 'three \'"blind"\' mice'), [
'"blind"',
])
})
it("transforms to float by explicit type", () => {
assert.deepEqual(match("{what}", "0.22", ['float']), [0.22])
assert.deepEqual(match("{what}", ".22", ['float']), [0.22])
it('matches double quoted string with single quotes', () => {
assert.deepEqual(match('three {string} mice', 'three "\'blind\'" mice'), [
"'blind'",
])
})
it("leaves unknown type untransformed", () => {
assert.deepEqual(match("{unknown}", "something"), ['something'])
it('matches double quoted string with escaped double quote', () => {
assert.deepEqual(match('three {string} mice', 'three "bl\\"nd" mice'), [
'bl"nd',
])
})
it("supports deprecated {name:type} syntax for now", () => {
assert.deepEqual(match("{param:unknown}", "something"), ['something'])
it('matches single quoted string with escaped single quote', () => {
assert.deepEqual(match('three {string} mice', "three 'bl\\'nd' mice"), [
"bl'nd",
])
})
it("exposes source", () => {
const expr = "I have {int} cuke(s) in my {bodypart} now"
assert.equal(new CucumberExpression(expr, [], new ParameterTypeRegistry()).source, expr)
it('matches int', () => {
assert.deepEqual(match('{int}', '22'), [22])
})
it("exposes offset and value", () => {
const expr = "I have {int} cuke(s) in my {bodypart} now"
const expression = new CucumberExpression(expr, [], new ParameterTypeRegistry())
const arg1 = expression.match("I have 800 cukes in my brain now")[0]
assert.equal(arg1.offset, 7)
assert.equal(arg1.value, "800")
it("doesn't match float as int", () => {
assert.deepEqual(match('{int}', '1.22'), null)
})
describe('RegExp special characters', () => {
['\\', '[', ']', '^', '$', '.', '|', '?', '*', '+'].forEach((character) => {
it('matches float', () => {
assert.deepEqual(match('{float}', '0.22'), [0.22])
assert.deepEqual(match('{float}', '.22'), [0.22])
})
it('throws unknown parameter type', () => {
try {
match('{unknown}', 'something')
assert.fail()
} catch (expected) {
assert.equal(expected.message, 'Undefined parameter type {unknown}')
}
})
it('exposes source', () => {
const expr = 'I have {int} cuke(s)'
assert.equal(
new CucumberExpression(expr, new ParameterTypeRegistry()).source,
expr
)
})
describe('escapes special characters', () => {
;['\\', '[', ']', '^', '$', '.', '|', '?', '*', '+'].forEach(character => {
it(`escapes ${character}`, () => {
const expr = `I have {int} cuke(s) and ${character}`
const expression = new CucumberExpression(expr, [], new ParameterTypeRegistry())
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
const arg1 = expression.match(`I have 800 cukes and ${character}`)[0]
assert.equal(arg1.offset, 7)
assert.equal(arg1.value, "800")
assert.equal(arg1.value, 800)
})

@@ -80,7 +107,9 @@ })

const expr = `I have {int} cuke(s) and .`
const expression = new CucumberExpression(expr, [], new ParameterTypeRegistry())
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
assert.equal(expression.match(`I have 800 cukes and 3`), null)
const arg1 = expression.match(`I have 800 cukes and .`)[0]
assert.equal(arg1.offset, 7)
assert.equal(arg1.value, "800")
assert.equal(arg1.value, 800)
})

@@ -90,8 +119,10 @@

const expr = `I have {int} cuke(s) and a|b`
const expression = new CucumberExpression(expr, [], new ParameterTypeRegistry())
const expression = new CucumberExpression(
expr,
new ParameterTypeRegistry()
)
assert.equal(expression.match(`I have 800 cukes and a`), null)
assert.equal(expression.match(`I have 800 cukes and b`), null)
const arg1 = expression.match(`I have 800 cukes and a|b`)[0]
assert.equal(arg1.offset, 7)
assert.equal(arg1.value, "800")
assert.equal(arg1.value, 800)
})

@@ -101,7 +132,10 @@ })

const match = (expression, text, types) => {
const cucumberExpression = new CucumberExpression(expression, types || [], new ParameterTypeRegistry())
const match = (expression, text) => {
const cucumberExpression = new CucumberExpression(
expression,
new ParameterTypeRegistry()
)
const args = cucumberExpression.match(text)
if (!args) return null
return args.map(arg => arg.transformedValue)
return args.map(arg => arg.value)
}

@@ -19,2 +19,8 @@ /* eslint-env mocha */

class CssColor {
constructor(name) {
this.name = name
}
}
describe('Custom parameter type', () => {

@@ -26,176 +32,217 @@ let parameterTypeRegistry

/// [add-color-parameter-type]
parameterTypeRegistry.defineParameterType(new ParameterType(
'color',
Color,
/red|blue|yellow/,
s => new Color(s)
))
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color',
/red|blue|yellow/,
Color,
s => new Color(s),
false,
true
)
)
/// [add-color-parameter-type]
})
describe(CucumberExpression.name, () => {
it("matches parameters with custom parameter type", () => {
const expression = new CucumberExpression("I have a {color} ball", [], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
describe('CucumberExpression', () => {
it('matches parameters with custom parameter type', () => {
const expression = new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
)
const value = expression.match('I have a red ball')[0].value
assert.equal(value.name, 'red')
})
it("matches parameters with custom parameter type using optional capture group", () => {
parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(new ParameterType(
'color',
Color,
[/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
s => new Color(s)
))
const expression = new CucumberExpression("I have a {color} ball", [], parameterTypeRegistry)
const transformedValue = expression.match("I have a dark red ball")[0].transformedValue
assert.equal(transformedValue.name, "dark red")
it('matches parameters with multiple capture groups', () => {
class Coordinate {
constructor(x, y, z) {
this.x = x
this.y = y
this.z = z
}
}
parameterTypeRegistry.defineParameterType(
new ParameterType(
'coordinate',
/(\d+),\s*(\d+),\s*(\d+)/,
Coordinate,
(x, y, z) => new Coordinate(parseInt(x), parseInt(y), parseInt(z)),
true,
true
)
)
const expression = new CucumberExpression(
'A {int} thick line from {coordinate} to {coordinate}',
parameterTypeRegistry
)
const args = expression.match('A 5 thick line from 10,20,30 to 40,50,60')
const thick = args[0].value
assert.equal(thick, 5)
const from = args[1].value
assert.equal(from.x, 10)
assert.equal(from.y, 20)
assert.equal(from.z, 30)
const to = args[2].value
assert.equal(to.x, 40)
assert.equal(to.y, 50)
assert.equal(to.z, 60)
})
it("matches parameters with custom parameter type without constructor function and transform", () => {
it('matches parameters with custom parameter type using optional capture group', () => {
parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(new ParameterType(
'color',
null,
/red|blue|yellow/,
null
))
const expression = new CucumberExpression("I have a {color} ball", [], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue, "red")
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color',
[/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
Color,
s => new Color(s),
false,
true
)
)
const expression = new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
)
const value = expression.match('I have a dark red ball')[0].value
assert.equal(value.name, 'dark red')
})
it("matches parameters with explicit type", () => {
const expression = new CucumberExpression("I have a {color} ball", [Color], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
})
it('defers transformation until queried from argument', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'throwing',
/bad/,
null,
s => {
throw new Error(`Can't transform [${s}]`)
},
false,
true
)
)
it("defers transformation until queried from argument", () => {
parameterTypeRegistry.defineParameterType(new ParameterType(
'throwing',
() => null,
/bad/,
s => { throw new Error(`Can't transform [${s}]`) }
))
const expression = new CucumberExpression("I have a {throwing} parameter", [], parameterTypeRegistry)
const args = expression.match("I have a bad parameter")
assertThrows(() => args[0].transformedValue, "Can't transform [bad]")
const expression = new CucumberExpression(
'I have a {throwing} parameter',
parameterTypeRegistry
)
const args = expression.match('I have a bad parameter')
assertThrows(() => args[0].value, "Can't transform [bad]")
})
describe("conflicting parameter type", () => {
it("is detected for type name", () => {
assertThrows(() => parameterTypeRegistry.defineParameterType(new ParameterType(
'color',
String,
/.*/,
s => s
)), "There is already a parameter with type name color")
describe('conflicting parameter type', () => {
it('is detected for type name', () => {
assertThrows(
() =>
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color',
/.*/,
CssColor,
s => new CssColor(s),
false,
true
)
),
'There is already a parameter type with name color'
)
})
it("is detected for constructor", () => {
assertThrows(() => parameterTypeRegistry.defineParameterType(new ParameterType(
'color2',
Color,
/.*/,
s => new Color(s)
)), "There is already a parameter with constructor Color")
it('is not detected for type', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'whatever',
/.*/,
Color,
s => new Color(s),
false,
true
)
)
})
it("is detected for regexp", () => {
assertThrows(() => parameterTypeRegistry.defineParameterType(new ParameterType(
'color2',
String,
/red|blue|yellow/,
s => s
)), "There is already a parameter with regexp red|blue|yellow")
})
it('is not detected for regexp', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'css-color',
/red|blue|yellow/,
CssColor,
s => new CssColor(s),
true,
false
)
)
it("is not detected when constructor function is anonymous", () => {
parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(new ParameterType(
'foo',
() => null,
/foo/,
s => s
))
parameterTypeRegistry.defineParameterType(new ParameterType(
'bar',
() => null,
/bar/,
s => s
))
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
).match('I have a blue ball')[0].value.constructor,
CssColor
)
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
).match('I have a blue ball')[0].value.name,
'blue'
)
assert.equal(
new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
).match('I have a blue ball')[0].value.constructor,
Color
)
assert.equal(
new CucumberExpression(
'I have a {color} ball',
parameterTypeRegistry
).match('I have a blue ball')[0].value.name,
'blue'
)
})
it("is not detected when constructor function is null", () => {
parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(new ParameterType(
'foo',
null,
/foo/,
s => s
))
parameterTypeRegistry.defineParameterType(new ParameterType(
'bar',
null,
/bar/,
s => s
))
})
})
// JavaScript-specific
it("matches untyped parameters with explicit type name", () => {
const expression = new CucumberExpression("I have a {color} ball", ['color'], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
})
// JavaScript-specific
it("creates arguments using async transform", async () => {
it('creates arguments using async transform', async () => {
parameterTypeRegistry = new ParameterTypeRegistry()
/// [add-async-parameterType]
parameterTypeRegistry.defineParameterType(new ParameterType(
'asyncColor',
Color,
/red|blue|yellow/,
async s => new Color(s)
))
/// [add-async-parameterType]
/// [add-async-parameter-type]
parameterTypeRegistry.defineParameterType(
new ParameterType(
'asyncColor',
/red|blue|yellow/,
Color,
async s => new Color(s),
false,
true
)
)
/// [add-async-parameter-type]
const expression = new CucumberExpression("I have a {asyncColor} ball", ['asyncColor'], parameterTypeRegistry)
const args = await expression.match("I have a red ball")
const transformedValue = await args[0].transformedValue
assert.equal(transformedValue.name, "red")
const expression = new CucumberExpression(
'I have a {asyncColor} ball',
parameterTypeRegistry
)
const args = await expression.match('I have a red ball')
const value = await args[0].value
assert.equal(value.name, 'red')
})
})
describe(RegularExpression.name, () => {
it("matches parameters with explicit constructor", () => {
const expression = new RegularExpression(/I have a (red|blue|yellow) ball/, [Color], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
describe('RegularExpression', () => {
it('matches arguments with custom parameter type', () => {
const expression = new RegularExpression(
/I have a (red|blue|yellow) ball/,
parameterTypeRegistry
)
const value = expression.match('I have a red ball')[0].value
assert.equal(value.constructor, Color)
assert.equal(value.name, 'red')
})
it("matches parameters without explicit constructor", () => {
const expression = new RegularExpression(/I have a (red|blue|yellow) ball/, [], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
})
it("matches parameters with explicit type that isn't registered", () => {
const expression = new RegularExpression(/I have a (red|blue|yellow) ball/, [Color], new ParameterTypeRegistry())
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
})
// JavaScript-specific (specifying type as string)
it("matches parameters without explicit type name", () => {
const expression = new RegularExpression(/I have a (red|blue|yellow) ball/, ['color'], parameterTypeRegistry)
const transformedValue = expression.match("I have a red ball")[0].transformedValue
assert.equal(transformedValue.name, "red")
})
})
})

@@ -11,11 +11,11 @@ /* eslint-env mocha */

const m = /\/(.*)\//.exec(expression_text)
const expression = m ?
new RegularExpression(new RegExp(m[1]), [], new ParameterTypeRegistry()) :
new CucumberExpression(expression_text, [], new ParameterTypeRegistry())
const expression = m
? new RegularExpression(new RegExp(m[1]), new ParameterTypeRegistry())
: new CucumberExpression(expression_text, new ParameterTypeRegistry())
const args = expression.match(text)
if (!args) return null
return args.map(arg => arg.transformedValue)
return args.map(arg => arg.value)
}
const examples = fs.readFileSync("examples.txt", "utf-8")
const examples = fs.readFileSync('examples.txt', 'utf-8')
const chunks = examples.split(/^---/m)

@@ -25,6 +25,8 @@ for (const chunk of chunks) {

it(`Works with: ${expressionText}`, () => {
assert.deepEqual(JSON.stringify(match(expressionText, text)), expectedArgs)
assert.deepEqual(
JSON.stringify(match(expressionText, text)),
expectedArgs
)
})
}
})
/* eslint-env mocha */
const assert = require('assert')
const assertThrows = require('./assert_throws')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe(RegularExpression.name, () => {
it("documents match arguments", () => {
describe('RegularExpression', () => {
it('documents match arguments', () => {
const parameterRegistry = new ParameterTypeRegistry()

@@ -13,69 +12,58 @@

const expr = /I have (\d+) cukes? in my (\w+) now/
const types = ['int', null]
const expression = new RegularExpression(expr, types, parameterRegistry)
const args = expression.match("I have 7 cukes in my belly now")
assert.equal(7, args[0].transformedValue)
assert.equal("belly", args[1].transformedValue)
const expression = new RegularExpression(expr, parameterRegistry)
const args = expression.match('I have 7 cukes in my belly now')
assert.equal(7, args[0].value)
assert.equal('belly', args[1].value)
/// [capture-match-arguments]
})
it("does no transform by default", () => {
assert.deepEqual(match(/(\d\d)/, "22"), ['22'])
it('does no transform by default', () => {
assert.deepEqual(match(/(\d\d)/, '22')[0], '22')
})
it("transforms int to float by explicit type name", () => {
assert.deepEqual(match(/(.*)/, "22", ['float']), [22.0])
it('transforms negative int', () => {
assert.deepEqual(match(/(-?\d+)/, '-22')[0], -22)
})
it("transforms int to float by explicit function", () => {
assert.deepEqual(match(/(.*)/, "22", [parseFloat]), [22.0])
it('transforms positive int', () => {
assert.deepEqual(match(/(\d+)/, '22')[0], 22)
})
it("transforms int by parameterType pattern", () => {
assert.deepEqual(match(/(-?\d+)/, "22"), [22])
it('transforms float without integer part', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '.22')[0], 0.22)
})
it("transforms int by alternate parameterType pattern", () => {
assert.deepEqual(match(/(\d+)/, "22"), [22])
it('transforms float with sign', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '-1.22')[0], -1.22)
})
it("transforms float without integer part", () => {
assert.deepEqual(match(/(.*)/, ".22", ['float']), [0.22])
it('returns null when there is no match', () => {
assert.equal(match(/hello/, 'world'), null)
})
it("transforms float with sign", () => {
assert.deepEqual(match(/(.*)/, "-1.22", ['float']), [-1.22])
})
it("transforms float with sign using function", () => {
assert.deepEqual(match(/(.*)/, "-1.22", [parseFloat]), [-1.22])
})
it("transforms float with sign using anonymous function", () => {
assert.deepEqual(match(/(.*)/, "-1.22", [s => parseFloat(s)]), [-1.22])
})
it("returns null when there is no match", () => {
assert.equal(match(/hello/, "world"), null)
})
it("fails when type is not type name or function", () => {
assertThrows(
() => match(/(.*)/, "-1.22", [99]),
'Type must be string or function, but was 99 of type number'
it('ignores non capturing groups', () => {
assert.deepEqual(
match(
/(\S+) ?(can|cannot)? (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?/,
'I can cancel the 1st slide upload'
),
['I', 'can', 1, 'slide']
)
})
it("exposes source", () => {
it('exposes source', () => {
const expr = /I have (\d+) cukes? in my (.+) now/
assert.deepEqual(new RegularExpression(expr, [], new ParameterTypeRegistry()).getSource(), expr.toString())
assert.deepEqual(
new RegularExpression(expr, new ParameterTypeRegistry()).getSource(),
expr.toString()
)
})
})
const match = (regexp, text, types) => {
const match = (regexp, text) => {
const parameterRegistry = new ParameterTypeRegistry()
const regularExpression = new RegularExpression(regexp, types || [], parameterRegistry)
const regularExpression = new RegularExpression(regexp, parameterRegistry)
const args = regularExpression.match(text)
if (!args) return null
return args.map(arg => arg.transformedValue)
return args.map(arg => arg.value)
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc