cucumber-expressions
Advanced tools
Comparing version 3.0.0 to 4.0.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
88546
49
2185
1
11
2
+ Addedbecke-ch--regex--s0-0-v1--base--pl--lib@1.4.0(transitive)