This commit is contained in:
Simon Priet
2021-09-05 22:53:58 +02:00
commit 9e2991e668
17888 changed files with 1263126 additions and 0 deletions

10
node_modules/cucumber-expressions/.babelrc generated vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"presets": [
["@babel/env", {
"targets": {
"node": "current"
}
}]
],
"plugins": ["@babel/plugin-transform-async-to-generator"]
}

21
node_modules/cucumber-expressions/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"parserOptions": {
"ecmaVersion": 8
},
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error", {"trailingComma": "es5", "singleQuote": true, "semi": false}
]
},
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended",
"prettier"
]
}

View File

@@ -0,0 +1,5 @@
PLEASE DO NOT CREATE ISSUES IN THIS REPO.
THIS REPO IS A READ-ONLY MIRROR.
Create your issue in the Cucumber monorepo instead:
https://github.com/cucumber/cucumber/issues

View File

@@ -0,0 +1,5 @@
PLEASE DO NOT CREATE PULL REAUESTS IN THIS REPO.
THIS REPO IS A READ-ONLY MIRROR.
Create your pull request in the Cucumber monorepo instead:
https://github.com/cucumber/cucumber/pulls

14
node_modules/cucumber-expressions/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,14 @@
.idea/
.nyc_output/
coverage/
node_modules/
yarn.lock
npm-debug.log
package-lock.json
.deps
.tested
.eslinted
.compared
acceptance
dist
*-go/

4
node_modules/cucumber-expressions/.rsync generated vendored Normal file
View File

@@ -0,0 +1,4 @@
../../LICENSE LICENSE
../../.templates/github/ .github/
../../.templates/javascript/ .
../examples.txt examples.txt

1
node_modules/cucumber-expressions/.subrepo generated vendored Normal file
View File

@@ -0,0 +1 @@
cucumber/cucumber-expressions-javascript

24
node_modules/cucumber-expressions/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,24 @@
sudo: false
language: node_js
node_js:
- "10"
- "9"
- "8"
- "6"
before_install:
- npm i -g npm@latest
script: make default
jobs:
include:
- stage: deploy
node_js: '10'
deploy:
skip_cleanup: true
provider: npm
email: cukebot@cucumber.io
on:
tags: true
deploy:
api_key:
secure: bUmCug37qcNOAQnjNduP0katc7ETn3vmePjp+cYWlpA27pQTvhf7zxcgocgDJPstS1tooDuIxzyk802mXgkV6rwRo2hs0ul3P/lyRcodsWshiKsAR/4hA3VrJ3om3v+RSOW3Qdlh+e0m7h4a3IANWMxaaEKBmAhd+iHjrmqXYjQsvImEZydkfNddOubO7MBECDOazH4tEQ65ppyrSJIsABRDAs6OD5obRhDBbEnzW2oNBH/m48W/C33H+dSgM3DIisu9+qCscqM1iMb6c/vFhq8q2EHRWpzRnsTEz3yUiXnotmU9A3iX/0qwvM6vpAtrixd4kKG8ruocnK1zK1NEItrEESWSvJ5qbcVDQcwGE3wmUtJhHgRClWcTVxAL68PkIuBAfZYwFrDCfbb2Cx1ecJGZFJ/042D6JE5v4hWApXz7KMMt2pEPz1peQ9ytguolT5sO/xZLtHwaBxegK6AcIjVgJoke/yXRPJPfNHQHW+MGV+mATv0kba9DKDAmo+H57Lo2YPHGaQ2xghWPHn8CaykSleAgC8+17gNOr50JTIu82hNXpLZiFx6Rdy2MP6/TW2+s+iPvq1bmYSqfjZKdw7cONDnxYWZc0SeNxVuxrJgluEzegHstTHQ9Aq5v7qAnPEwPXx+CXP2KmxGt9DjW0zUCCIUz+77pcZuYponG7tg=

21
node_modules/cucumber-expressions/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Cucumber Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
node_modules/cucumber-expressions/Makefile generated vendored Normal file
View File

@@ -0,0 +1,4 @@
include default.mk
.deps:
touch $@

5
node_modules/cucumber-expressions/README.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Cucumber Expressions for JavaScript
[![Build Status](https://travis-ci.org/cucumber/cucumber-expressions-javascript.svg?branch=master)](https://travis-ci.org/cucumber/cucumber-expressions-javascript)
[The docs are here](https://docs.cucumber.io/cucumber/cucumber-expressions/).

43
node_modules/cucumber-expressions/default.mk generated vendored Normal file
View File

@@ -0,0 +1,43 @@
SHELL := /usr/bin/env bash
JAVASCRIPT_SOURCE_FILES = $(shell find . -name "*.js" -not -path "./node_modules/*")
ifdef TRAVIS_BRANCH
LIBRARY_VERSION=$(TRAVIS_BRANCH)
endif
ifdef TRAVIS_TAG
LIBRARY_VERSION=$(TRAVIS_TAG)
endif
ifndef LIBRARY_VERSION
LIBRARY_VERSION=$(shell git rev-parse --abbrev-ref HEAD)
endif
ASYNC_SUPPORTED := $(shell node --eval "async function foo(){}" 2> /dev/null; echo $$?)
.tested: package-lock.json .deps $(JAVASCRIPT_SOURCE_FILES)
npm run build
ifeq ($(ASYNC_SUPPORTED),0)
npm run coverage
else
npm run build-test
npm run mocha-built
endif
touch $@
default: .tested .eslinted
.PHONY: default
.eslinted: $(JAVASCRIPT_SOURCE_FILES)
npm run eslint-fix
touch $@
package-lock.json: package.json
npm install
npm link
touch $@
clean: clean-javascript
.PHONY: clean
clean-javascript:
rm -rf .deps package-lock.json node_modules coverage dist
.PHONY: clean-javascript

42
node_modules/cucumber-expressions/dist/src/argument.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
"use strict";
const {
CucumberExpressionError
} = require('./errors');
class Argument {
static build(treeRegexp, text, parameterTypes) {
const group = treeRegexp.match(text);
if (!group) return null;
const argGroups = group.children;
if (argGroups.length !== parameterTypes.length) {
throw new CucumberExpressionError(`Expression ${treeRegexp.regexp} has ${argGroups.length} capture groups (${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));
}
constructor(group, parameterType) {
this._group = group;
this._parameterType = parameterType;
}
get group() {
return this._group;
}
/**
* Get the value returned by the parameter type's transformer function.
*
* @param thisObj the object in which the transformer function is applied.
*/
getValue(thisObj) {
let groupValues = this._group ? this._group.values : null;
return this._parameterType.transform(thisObj, groupValues);
}
}
module.exports = Argument;

View File

@@ -0,0 +1,48 @@
"use strict";
const GeneratedExpression = require('./generated_expression'); // 256 generated expressions ought to be enough for anybody
const MAX_EXPRESSIONS = 256;
class CombinatorialGeneratedExpressionFactory {
constructor(expressionTemplate, parameterTypeCombinations) {
this._expressionTemplate = expressionTemplate;
this._parameterTypeCombinations = parameterTypeCombinations;
}
generateExpressions() {
const generatedExpressions = [];
this._generatePermutations(generatedExpressions, 0, []);
return generatedExpressions;
}
_generatePermutations(generatedExpressions, depth, currentParameterTypes) {
if (generatedExpressions.length >= MAX_EXPRESSIONS) {
return;
}
if (depth === this._parameterTypeCombinations.length) {
generatedExpressions.push(new GeneratedExpression(this._expressionTemplate, currentParameterTypes));
return;
}
for (let i = 0; i < this._parameterTypeCombinations[depth].length; ++i) {
// Avoid recursion if no elements can be added.
if (generatedExpressions.length >= MAX_EXPRESSIONS) {
return;
}
const newCurrentParameterTypes = currentParameterTypes.slice(); // clone
newCurrentParameterTypes.push(this._parameterTypeCombinations[depth][i]);
this._generatePermutations(generatedExpressions, depth + 1, newCurrentParameterTypes);
}
}
}
module.exports = CombinatorialGeneratedExpressionFactory;

View File

@@ -0,0 +1,125 @@
"use strict";
const Argument = require('./argument');
const TreeRegexp = require('./tree_regexp');
const ParameterType = require('./parameter_type');
const {
UndefinedParameterTypeError,
CucumberExpressionError
} = require('./errors'); // RegExps with the g flag are stateful in JavaScript. In order to be able
// to reuse them we have to wrap them in a function.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
// Does not include (){} characters because they have special meaning
const ESCAPE_REGEXP = () => /([\\^[$.|?*+])/g;
const PARAMETER_REGEXP = () => /(\\\\)?{([^}]*)}/g;
const OPTIONAL_REGEXP = () => /(\\\\)?\(([^)]+)\)/g;
const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = () => /([^\s^/]+)((\/[^\s^/]+)+)/g;
const DOUBLE_ESCAPE = '\\\\';
const PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = 'Parameter types cannot be alternative: ';
const PARAMETER_TYPES_CANNOT_BE_OPTIONAL = 'Parameter types cannot be optional: ';
class CucumberExpression {
/**
* @param expression
* @param parameterTypeRegistry
*/
constructor(expression, parameterTypeRegistry) {
this._expression = expression;
this._parameterTypes = [];
expression = this.processEscapes(expression);
expression = this.processOptional(expression);
expression = this.processAlternation(expression);
expression = this.processParameters(expression, parameterTypeRegistry);
expression = `^${expression}$`;
this._treeRegexp = new TreeRegexp(expression);
}
processEscapes(expression) {
return expression.replace(ESCAPE_REGEXP(), '\\$1');
}
processOptional(expression) {
return expression.replace(OPTIONAL_REGEXP(), (match, p1, p2) => {
if (p1 === DOUBLE_ESCAPE) {
return `\\(${p2}\\)`;
}
this._checkNoParameterType(p2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL);
return `(?:${p2})?`;
});
}
processAlternation(expression) {
return expression.replace(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP(), match => {
// replace \/ with /
// replace / with |
const replacement = match.replace(/\//g, '|').replace(/\\\|/g, '/');
if (replacement.indexOf('|') !== -1) {
for (const part of replacement.split(/\|/)) {
this._checkNoParameterType(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE);
}
return `(?:${replacement})`;
} else {
return replacement;
}
});
}
processParameters(expression, parameterTypeRegistry) {
return expression.replace(PARAMETER_REGEXP(), (match, p1, p2) => {
if (p1 === DOUBLE_ESCAPE) return `\\{${p2}\\}`;
const typeName = p2;
ParameterType.checkParameterTypeName(typeName);
const parameterType = parameterTypeRegistry.lookupByTypeName(typeName);
if (!parameterType) throw new UndefinedParameterTypeError(typeName);
this._parameterTypes.push(parameterType);
return buildCaptureRegexp(parameterType.regexps);
});
}
match(text) {
return Argument.build(this._treeRegexp, text, this._parameterTypes);
}
get regexp() {
return this._treeRegexp.regexp;
}
get source() {
return this._expression;
}
_checkNoParameterType(s, message) {
if (s.match(PARAMETER_REGEXP())) {
throw new CucumberExpressionError(message + this.source);
}
}
}
function buildCaptureRegexp(regexps) {
if (regexps.length === 1) {
return `(${regexps[0]})`;
}
const captureGroups = regexps.map(group => {
return `(?:${group})`;
});
return `(${captureGroups.join('|')})`;
}
module.exports = CucumberExpression;

View File

@@ -0,0 +1,110 @@
"use strict";
const util = require('util');
const ParameterTypeMatcher = require('./parameter_type_matcher');
const ParameterType = require('./parameter_type');
const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory');
class CucumberExpressionGenerator {
constructor(parameterTypeRegistry) {
this._parameterTypeRegistry = parameterTypeRegistry;
}
generateExpressions(text) {
const parameterTypeCombinations = [];
const parameterTypeMatchers = this._createParameterTypeMatchers(text);
let expressionTemplate = '';
let pos = 0; // eslint-disable-next-line no-constant-condition
while (true) {
let matchingParameterTypeMatchers = [];
for (const parameterTypeMatcher of parameterTypeMatchers) {
const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos);
if (advancedParameterTypeMatcher.find) {
matchingParameterTypeMatchers.push(advancedParameterTypeMatcher);
}
}
if (matchingParameterTypeMatchers.length > 0) {
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(ParameterTypeMatcher.compare); // Find all the best parameter type matchers, they are all candidates.
const bestParameterTypeMatcher = matchingParameterTypeMatchers[0];
const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter(m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0); // 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.indexOf(parameterTypeMatcher.parameterType) === -1) {
parameterTypes.push(parameterTypeMatcher.parameterType);
}
}
parameterTypes = parameterTypes.sort(ParameterType.compare);
parameterTypeCombinations.push(parameterTypes);
expressionTemplate += escape(text.slice(pos, bestParameterTypeMatcher.start));
expressionTemplate += '{%s}';
pos = bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length;
} else {
break;
}
if (pos >= text.length) {
break;
}
}
expressionTemplate += escape(text.slice(pos));
return new CombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).generateExpressions();
}
/**
* @deprecated
*/
generateExpression(text) {
return util.deprecate(() => this.generateExpressions(text)[0], 'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead')();
}
_createParameterTypeMatchers(text) {
let parameterMatchers = [];
for (const parameterType of this._parameterTypeRegistry.parameterTypes) {
if (parameterType.useForSnippets) {
parameterMatchers = parameterMatchers.concat(this._createParameterTypeMatchers2(parameterType, text));
}
}
return parameterMatchers;
}
_createParameterTypeMatchers2(parameterType, text) {
// TODO: [].map
const result = [];
for (const regexp of parameterType.regexps) {
result.push(new ParameterTypeMatcher(parameterType, regexp, text));
}
return result;
}
}
function escape(s) {
return s.replace(/%/g, '%%') // for util.format
.replace(/\(/g, '\\(').replace(/{/g, '\\{').replace(/\//g, '\\/');
}
module.exports = CucumberExpressionGenerator;

45
node_modules/cucumber-expressions/dist/src/errors.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
"use strict";
class CucumberExpressionError extends Error {}
class AmbiguousParameterTypeError extends CucumberExpressionError {
static forConstructor(keyName, keyValue, parameterTypes, generatedExpressions) {
return new this(`parameter type with ${keyName}=${keyValue} is used by several parameter types: ${parameterTypes}, ${generatedExpressions}`);
}
static forRegExp(parameterTypeRegexp, expressionRegexp, parameterTypes, generatedExpressions) {
return new this(`Your Regular Expression ${expressionRegexp}
matches multiple parameter types with regexp ${parameterTypeRegexp}:
${this._parameterTypeNames(parameterTypes)}
I couldn't decide which one to use. You have two options:
1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:
${this._expressions(generatedExpressions)}
2) Make one of the parameter types preferential and continue to use a Regular Expression.
`);
}
static _parameterTypeNames(parameterTypes) {
return parameterTypes.map(p => `{${p.name}}`).join('\n ');
}
static _expressions(generatedExpressions) {
return generatedExpressions.map(e => e.source).join('\n ');
}
}
class UndefinedParameterTypeError extends CucumberExpressionError {
constructor(typeName) {
super(`Undefined parameter type {${typeName}}`);
}
}
module.exports = {
AmbiguousParameterTypeError,
UndefinedParameterTypeError,
CucumberExpressionError
};

View File

@@ -0,0 +1,43 @@
"use strict";
const util = require('util');
class GeneratedExpression {
constructor(expressionTemplate, parameterTypes) {
this._expressionTemplate = expressionTemplate;
this._parameterTypes = parameterTypes;
}
get source() {
return util.format(this._expressionTemplate, ...this._parameterTypes.map(t => t.name));
}
/**
* Returns an array of parameter names to use in generated function/method signatures
*
* @returns {Array.<String>}
*/
get parameterNames() {
const usageByTypeName = {};
return this._parameterTypes.map(t => getParameterName(t.name, usageByTypeName));
}
/**
* @returns {Array.<ParameterType>}
*/
get parameterTypes() {
return this._parameterTypes;
}
}
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;

33
node_modules/cucumber-expressions/dist/src/group.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
"use strict";
class Group {
constructor(value, start, end, children) {
this._value = value;
this._start = start;
this._end = end;
this._children = children;
}
get value() {
return this._value;
}
get start() {
return this._start;
}
get end() {
return this._end;
}
get children() {
return this._children;
}
get values() {
return (this.children.length === 0 ? [this] : this.children).map(g => g.value).filter(v => v !== undefined);
}
}
module.exports = Group;

View File

@@ -0,0 +1,41 @@
"use strict";
const Group = require('./group');
class GroupBuilder {
constructor() {
this._groupBuilders = [];
this._capturing = true;
}
add(groupBuilder) {
this._groupBuilders.push(groupBuilder);
}
build(match, nextGroupIndex) {
const groupIndex = nextGroupIndex();
const children = this._groupBuilders.map(gb => gb.build(match, nextGroupIndex));
return new Group(match[groupIndex], match.index[groupIndex], match.index[groupIndex] + (match[groupIndex] || '').length, children);
}
setNonCapturing() {
this._capturing = false;
}
get capturing() {
return this._capturing;
}
get children() {
return this._groupBuilders;
}
moveChildrenTo(groupBuilder) {
this._groupBuilders.forEach(child => groupBuilder.add(child));
}
}
module.exports = GroupBuilder;

19
node_modules/cucumber-expressions/dist/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
"use strict";
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');
module.exports = {
CucumberExpression,
RegularExpression,
CucumberExpressionGenerator,
ParameterTypeRegistry,
ParameterType
};

View File

@@ -0,0 +1,106 @@
"use strict";
const {
CucumberExpressionError
} = require('./errors');
const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/;
const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g;
class ParameterType {
static compare(pt1, pt2) {
if (pt1.preferForRegexpMatch && !pt2.preferForRegexpMatch) return -1;
if (pt2.preferForRegexpMatch && !pt1.preferForRegexpMatch) return 1;
return pt1.name.localeCompare(pt2.name);
}
static checkParameterTypeName(typeName) {
const unescapedTypeName = typeName.replace(UNESCAPE_PATTERN(), '$2');
const match = unescapedTypeName.match(ILLEGAL_PARAMETER_NAME_PATTERN);
if (match) throw new CucumberExpressionError(`Illegal character '${match[1]}' in parameter name {${unescapedTypeName}}`);
}
/**
* @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;
if (name) ParameterType.checkParameterTypeName(name);
this._name = name;
this._regexps = stringArray(regexps);
this._type = type;
this._transform = transform;
this._useForSnippets = useForSnippets;
this._preferForRegexpMatch = preferForRegexpMatch;
}
get name() {
return this._name;
}
get regexps() {
return this._regexps;
}
get type() {
return this._type;
}
get preferForRegexpMatch() {
return this._preferForRegexpMatch;
}
get useForSnippets() {
return this._useForSnippets;
}
transform(thisObj, groupValues) {
return this._transform.apply(thisObj, groupValues);
}
}
function stringArray(regexps) {
const array = Array.isArray(regexps) ? regexps : [regexps];
return array.map(r => typeof r === 'string' ? r : regexpSource(r));
}
function regexpSource(regexp) {
const flags = regexpFlags(regexp);
for (const flag of ['g', 'i', 'm', 'y']) {
if (flags.indexOf(flag) !== -1) throw new CucumberExpressionError(`ParameterType Regexps can't use flag '${flag}'`);
}
return regexp.source;
} // Backport RegExp.flags for Node 4.x
// https://github.com/nodejs/node/issues/8390
//
// For some strange reason this is not needed for
// `./mocha dist/test`, but it is needed for
// `./mocha dist/test/parameter_type_test.js`
function regexpFlags(regexp) {
let flags = regexp.flags;
if (flags === undefined) {
flags = '';
if (regexp.ignoreCase) flags += 'i';
if (regexp.global) flags += 'g';
if (regexp.multiline) flags += 'm';
}
return flags;
}
module.exports = ParameterType;

View File

@@ -0,0 +1,51 @@
"use strict";
class ParameterTypeMatcher {
constructor(parameter, regexp, text, matchPosition) {
this._parameterType = parameter;
this._treeRegexp = regexp;
this._text = text;
this._matchPosition = matchPosition || 0;
const captureGroupRegexp = new RegExp(`(${regexp})`);
this._match = captureGroupRegexp.exec(text.slice(this._matchPosition));
}
get parameterType() {
return this._parameterType;
}
advanceTo(newMatchPosition) {
for (let advancedPos = newMatchPosition; advancedPos < this._text.length; advancedPos++) {
let matcher = new ParameterTypeMatcher(this._parameterType, this._treeRegexp, this._text, advancedPos);
if (matcher.find) {
return matcher;
}
}
return new ParameterTypeMatcher(this._parameterType, this._treeRegexp, this._text, this._text.length);
}
get find() {
return this._match && this.group !== '';
}
get start() {
return this._matchPosition + this._match.index;
}
get group() {
return this._match[0];
}
static compare(a, b) {
const posComparison = a.start - b.start;
if (posComparison !== 0) return posComparison;
const lengthComparison = b.group.length - a.group.length;
if (lengthComparison !== 0) return lengthComparison;
return 0;
}
}
module.exports = ParameterTypeMatcher;

View File

@@ -0,0 +1,83 @@
"use strict";
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 = /[^\s]+/;
const STRING_REGEXP = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/;
const ANONYMOUS_REGEXP = /.*/;
class ParameterTypeRegistry {
constructor() {
this._parameterTypeByName = new Map();
this._parameterTypesByRegexp = new Map();
this.defineParameterType(new ParameterType('int', INTEGER_REGEXPS, Number, s => s && parseInt(s), true, true));
this.defineParameterType(new ParameterType('float', FLOAT_REGEXP, Number, s => s && parseFloat(s), 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));
this.defineParameterType(new ParameterType('', ANONYMOUS_REGEXP, String, s => s, false, true));
}
get parameterTypes() {
return this._parameterTypeByName.values();
}
lookupByTypeName(typeName) {
return this._parameterTypeByName.get(typeName);
}
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];
}
defineParameterType(parameterType) {
if (parameterType.name !== undefined) {
if (this._parameterTypeByName.has(parameterType.name)) if (parameterType.name.length === 0) throw new Error(`The anonymous parameter type has already been defined`);else throw new Error(`There is already a parameter type with name ${parameterType.name}`);
this._parameterTypeByName.set(parameterType.name, parameterType);
}
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.indexOf(parameterType) === -1) {
parameterTypes.push(parameterType);
this._parameterTypesByRegexp.set(parameterTypeRegexp, parameterTypes.sort(ParameterType.compare));
}
}
}
}
module.exports = ParameterTypeRegistry;

View File

@@ -0,0 +1,35 @@
"use strict";
const Argument = require('./argument');
const TreeRegexp = require('./tree_regexp');
const ParameterType = require('./parameter_type');
class RegularExpression {
constructor(expressionRegexp, parameterTypeRegistry) {
this._expressionRegexp = expressionRegexp;
this._parameterTypeRegistry = parameterTypeRegistry;
this._treeRegexp = new TreeRegexp(expressionRegexp);
}
match(text) {
const parameterTypes = this._treeRegexp.groupBuilder.children.map(groupBuilder => {
const parameterTypeRegexp = groupBuilder.source;
return this._parameterTypeRegistry.lookupByRegexp(parameterTypeRegexp, this._treeRegexp, text) || new ParameterType(null, parameterTypeRegexp, String, s => s, false, false);
});
return Argument.build(this._treeRegexp, text, parameterTypes);
}
get regexp() {
return this._expressionRegexp;
}
get source() {
return this._expressionRegexp.source;
}
}
module.exports = RegularExpression;

View File

@@ -0,0 +1,74 @@
"use strict";
const Regex = require('becke-ch--regex--s0-0-v1--base--pl--lib');
const GroupBuilder = require('./group_builder');
class TreeRegexp {
constructor(regexp) {
this._re = 'string' === typeof regexp ? new RegExp(regexp) : regexp;
this._regex = new Regex(this._re.source, this._re.flags);
const stack = [new GroupBuilder()];
const groupStartStack = [];
let last = null;
let escaping = false;
let nonCapturingMaybe = false;
let charClass = false;
this._re.source.split('').forEach((c, n) => {
if (c == '[' && !escaping) {
charClass = true;
} else if (c == ']' && !escaping) {
charClass = false;
} else if (c === '(' && !escaping && !charClass) {
stack.push(new GroupBuilder());
groupStartStack.push(n + 1);
nonCapturingMaybe = false;
} else if (c === ')' && !escaping && !charClass) {
const gb = stack.pop();
const groupStart = groupStartStack.pop();
if (gb.capturing) {
gb.source = this._re.source.substring(groupStart, n);
stack[stack.length - 1].add(gb);
} else {
gb.moveChildrenTo(stack[stack.length - 1]);
}
nonCapturingMaybe = false;
} else if (c === '?' && last === '(') {
nonCapturingMaybe = true;
} else if (c === ':' && nonCapturingMaybe) {
stack[stack.length - 1].setNonCapturing();
nonCapturingMaybe = false;
}
escaping = c === '\\' && !escaping;
last = c;
});
this._groupBuilder = stack.pop();
}
get regexp() {
return this._re;
}
get groupBuilder() {
return this._groupBuilder;
}
match(s) {
const match = this._regex.exec(s);
if (!match) return null;
let groupIndex = 0;
const nextGroupIndex = () => groupIndex++;
return this._groupBuilder.build(match, nextGroupIndex);
}
}
module.exports = TreeRegexp;

31
node_modules/cucumber-expressions/examples.txt generated vendored Normal file
View File

@@ -0,0 +1,31 @@
I have {int} cuke(s)
I have 22 cukes
[22]
---
I have {int} cuke(s) and some \[]^$.|?*+
I have 1 cuke and some \[]^$.|?*+
[1]
---
/I have (\d+) cukes? in my (\w+) now/
I have 22 cukes in my belly now
[22,"belly"]
---
/I have (-?\d+) cukes? in my (.*) now/
I have 1 cuke in my belly now
[1,"belly"]
---
/^Something( with an optional argument)?$/
Something
[null]
---
Привет, {word}!
Привет, Мир!
["Мир"]
---
/I have (.*) cukes? in my (.*) now/
I have 22 cukes in my belly now
["22","belly"]
---
I have {} cuke(s) in my {} now
I have 22 cukes in my belly now
["22","belly"]

2
node_modules/cucumber-expressions/mocha generated vendored Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
node_modules/.bin/mocha "$@"

51
node_modules/cucumber-expressions/package.json generated vendored Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "cucumber-expressions",
"version": "6.6.2",
"description": "Cucumber Expressions - a simpler alternative to Regular Expressions",
"main": "dist/src/index.js",
"scripts": {
"test": "mocha",
"eslint-fix": "eslint --fix src test",
"coverage": "nyc --reporter=html --reporter=text mocha",
"build": "babel src --out-dir dist/src",
"build-test": "babel test --out-dir dist/test",
"prepublishOnly": "npm run build",
"mocha-built": "mocha dist/test"
},
"repository": {
"type": "git",
"url": "git://github.com/cucumber/cucumber-expressions-javascript.git"
},
"keywords": [
"cucumber",
"steps",
"regexp",
"regex"
],
"author": "Cucumber Limited <cukes@googlegroups.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/cucumber/cucumber-expressions-javascript/issues"
},
"homepage": "https://github.com/cucumber/cucumber-expressions-javascript#readme",
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-transform-async-to-generator": "^7.0.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"eslint": "^5.7.0",
"eslint-config-eslint": "^5.0.1",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-prettier": "^3.0.0",
"mocha": "^6.0.2",
"nyc": "^13.1.0",
"prettier": "^1.13.2"
},
"files": [
"*"
],
"dependencies": {
"becke-ch--regex--s0-0-v1--base--pl--lib": "^1.2.0"
}
}

45
node_modules/cucumber-expressions/src/argument.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
const { CucumberExpressionError } = require('./errors')
class Argument {
static build(treeRegexp, text, parameterTypes) {
const group = treeRegexp.match(text)
if (!group) return null
const argGroups = group.children
if (argGroups.length !== parameterTypes.length) {
throw new CucumberExpressionError(
`Expression ${treeRegexp.regexp} has ${
argGroups.length
} capture groups (${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)
)
}
constructor(group, parameterType) {
this._group = group
this._parameterType = parameterType
}
get group() {
return this._group
}
/**
* Get the value returned by the parameter type's transformer function.
*
* @param thisObj the object in which the transformer function is applied.
*/
getValue(thisObj) {
let groupValues = this._group ? this._group.values : null
return this._parameterType.transform(thisObj, groupValues)
}
}
module.exports = Argument

View File

@@ -0,0 +1,47 @@
const GeneratedExpression = require('./generated_expression')
// 256 generated expressions ought to be enough for anybody
const MAX_EXPRESSIONS = 256
class CombinatorialGeneratedExpressionFactory {
constructor(expressionTemplate, parameterTypeCombinations) {
this._expressionTemplate = expressionTemplate
this._parameterTypeCombinations = parameterTypeCombinations
}
generateExpressions() {
const generatedExpressions = []
this._generatePermutations(generatedExpressions, 0, [])
return generatedExpressions
}
_generatePermutations(generatedExpressions, depth, currentParameterTypes) {
if (generatedExpressions.length >= MAX_EXPRESSIONS) {
return
}
if (depth === this._parameterTypeCombinations.length) {
generatedExpressions.push(
new GeneratedExpression(this._expressionTemplate, currentParameterTypes)
)
return
}
for (let i = 0; i < this._parameterTypeCombinations[depth].length; ++i) {
// Avoid recursion if no elements can be added.
if (generatedExpressions.length >= MAX_EXPRESSIONS) {
return
}
const newCurrentParameterTypes = currentParameterTypes.slice() // clone
newCurrentParameterTypes.push(this._parameterTypeCombinations[depth][i])
this._generatePermutations(
generatedExpressions,
depth + 1,
newCurrentParameterTypes
)
}
}
}
module.exports = CombinatorialGeneratedExpressionFactory

View File

@@ -0,0 +1,122 @@
const Argument = require('./argument')
const TreeRegexp = require('./tree_regexp')
const ParameterType = require('./parameter_type')
const {
UndefinedParameterTypeError,
CucumberExpressionError,
} = require('./errors')
// RegExps with the g flag are stateful in JavaScript. In order to be able
// to reuse them we have to wrap them in a function.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
// Does not include (){} characters because they have special meaning
const ESCAPE_REGEXP = () => /([\\^[$.|?*+])/g
const PARAMETER_REGEXP = () => /(\\\\)?{([^}]*)}/g
const OPTIONAL_REGEXP = () => /(\\\\)?\(([^)]+)\)/g
const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = () =>
/([^\s^/]+)((\/[^\s^/]+)+)/g
const DOUBLE_ESCAPE = '\\\\'
const PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE =
'Parameter types cannot be alternative: '
const PARAMETER_TYPES_CANNOT_BE_OPTIONAL =
'Parameter types cannot be optional: '
class CucumberExpression {
/**
* @param expression
* @param parameterTypeRegistry
*/
constructor(expression, parameterTypeRegistry) {
this._expression = expression
this._parameterTypes = []
expression = this.processEscapes(expression)
expression = this.processOptional(expression)
expression = this.processAlternation(expression)
expression = this.processParameters(expression, parameterTypeRegistry)
expression = `^${expression}$`
this._treeRegexp = new TreeRegexp(expression)
}
processEscapes(expression) {
return expression.replace(ESCAPE_REGEXP(), '\\$1')
}
processOptional(expression) {
return expression.replace(OPTIONAL_REGEXP(), (match, p1, p2) => {
if (p1 === DOUBLE_ESCAPE) {
return `\\(${p2}\\)`
}
this._checkNoParameterType(p2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL)
return `(?:${p2})?`
})
}
processAlternation(expression) {
return expression.replace(
ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP(),
match => {
// replace \/ with /
// replace / with |
const replacement = match.replace(/\//g, '|').replace(/\\\|/g, '/')
if (replacement.indexOf('|') !== -1) {
for (const part of replacement.split(/\|/)) {
this._checkNoParameterType(
part,
PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE
)
}
return `(?:${replacement})`
} else {
return replacement
}
}
)
}
processParameters(expression, parameterTypeRegistry) {
return expression.replace(PARAMETER_REGEXP(), (match, p1, p2) => {
if (p1 === DOUBLE_ESCAPE) return `\\{${p2}\\}`
const typeName = p2
ParameterType.checkParameterTypeName(typeName)
const parameterType = parameterTypeRegistry.lookupByTypeName(typeName)
if (!parameterType) throw new UndefinedParameterTypeError(typeName)
this._parameterTypes.push(parameterType)
return buildCaptureRegexp(parameterType.regexps)
})
}
match(text) {
return Argument.build(this._treeRegexp, text, this._parameterTypes)
}
get regexp() {
return this._treeRegexp.regexp
}
get source() {
return this._expression
}
_checkNoParameterType(s, message) {
if (s.match(PARAMETER_REGEXP())) {
throw new CucumberExpressionError(message + this.source)
}
}
}
function buildCaptureRegexp(regexps) {
if (regexps.length === 1) {
return `(${regexps[0]})`
}
const captureGroups = regexps.map(group => {
return `(?:${group})`
})
return `(${captureGroups.join('|')})`
}
module.exports = CucumberExpression

View File

@@ -0,0 +1,120 @@
const util = require('util')
const ParameterTypeMatcher = require('./parameter_type_matcher')
const ParameterType = require('./parameter_type')
const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory')
class CucumberExpressionGenerator {
constructor(parameterTypeRegistry) {
this._parameterTypeRegistry = parameterTypeRegistry
}
generateExpressions(text) {
const parameterTypeCombinations = []
const parameterTypeMatchers = this._createParameterTypeMatchers(text)
let expressionTemplate = ''
let pos = 0
// eslint-disable-next-line no-constant-condition
while (true) {
let matchingParameterTypeMatchers = []
for (const parameterTypeMatcher of parameterTypeMatchers) {
const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos)
if (advancedParameterTypeMatcher.find) {
matchingParameterTypeMatchers.push(advancedParameterTypeMatcher)
}
}
if (matchingParameterTypeMatchers.length > 0) {
matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(
ParameterTypeMatcher.compare
)
// Find all the best parameter type matchers, they are all candidates.
const bestParameterTypeMatcher = matchingParameterTypeMatchers[0]
const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter(
m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0
)
// 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.indexOf(parameterTypeMatcher.parameterType) === -1
) {
parameterTypes.push(parameterTypeMatcher.parameterType)
}
}
parameterTypes = parameterTypes.sort(ParameterType.compare)
parameterTypeCombinations.push(parameterTypes)
expressionTemplate += escape(
text.slice(pos, bestParameterTypeMatcher.start)
)
expressionTemplate += '{%s}'
pos =
bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length
} else {
break
}
if (pos >= text.length) {
break
}
}
expressionTemplate += escape(text.slice(pos))
return new CombinatorialGeneratedExpressionFactory(
expressionTemplate,
parameterTypeCombinations
).generateExpressions()
}
/**
* @deprecated
*/
generateExpression(text) {
return util.deprecate(
() => this.generateExpressions(text)[0],
'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead'
)()
}
_createParameterTypeMatchers(text) {
let parameterMatchers = []
for (const parameterType of this._parameterTypeRegistry.parameterTypes) {
if (parameterType.useForSnippets) {
parameterMatchers = parameterMatchers.concat(
this._createParameterTypeMatchers2(parameterType, text)
)
}
}
return parameterMatchers
}
_createParameterTypeMatchers2(parameterType, text) {
// TODO: [].map
const result = []
for (const regexp of parameterType.regexps) {
result.push(new ParameterTypeMatcher(parameterType, regexp, text))
}
return result
}
}
function escape(s) {
return s
.replace(/%/g, '%%') // for util.format
.replace(/\(/g, '\\(')
.replace(/{/g, '\\{')
.replace(/\//g, '\\/')
}
module.exports = CucumberExpressionGenerator

55
node_modules/cucumber-expressions/src/errors.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
class CucumberExpressionError extends Error {}
class AmbiguousParameterTypeError extends CucumberExpressionError {
static forConstructor(
keyName,
keyValue,
parameterTypes,
generatedExpressions
) {
return new this(
`parameter type with ${keyName}=${keyValue} is used by several parameter types: ${parameterTypes}, ${generatedExpressions}`
)
}
static forRegExp(
parameterTypeRegexp,
expressionRegexp,
parameterTypes,
generatedExpressions
) {
return new this(
`Your Regular Expression ${expressionRegexp}
matches multiple parameter types with regexp ${parameterTypeRegexp}:
${this._parameterTypeNames(parameterTypes)}
I couldn't decide which one to use. You have two options:
1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:
${this._expressions(generatedExpressions)}
2) Make one of the parameter types preferential and continue to use a Regular Expression.
`
)
}
static _parameterTypeNames(parameterTypes) {
return parameterTypes.map(p => `{${p.name}}`).join('\n ')
}
static _expressions(generatedExpressions) {
return generatedExpressions.map(e => e.source).join('\n ')
}
}
class UndefinedParameterTypeError extends CucumberExpressionError {
constructor(typeName) {
super(`Undefined parameter type {${typeName}}`)
}
}
module.exports = {
AmbiguousParameterTypeError,
UndefinedParameterTypeError,
CucumberExpressionError,
}

View File

@@ -0,0 +1,44 @@
const util = require('util')
class GeneratedExpression {
constructor(expressionTemplate, parameterTypes) {
this._expressionTemplate = expressionTemplate
this._parameterTypes = parameterTypes
}
get source() {
return util.format(
this._expressionTemplate,
...this._parameterTypes.map(t => t.name)
)
}
/**
* Returns an array of parameter names to use in generated function/method signatures
*
* @returns {Array.<String>}
*/
get parameterNames() {
const usageByTypeName = {}
return this._parameterTypes.map(t =>
getParameterName(t.name, usageByTypeName)
)
}
/**
* @returns {Array.<ParameterType>}
*/
get parameterTypes() {
return this._parameterTypes
}
}
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

32
node_modules/cucumber-expressions/src/group.js generated vendored Normal file
View File

@@ -0,0 +1,32 @@
class Group {
constructor(value, start, end, children) {
this._value = value
this._start = start
this._end = end
this._children = children
}
get value() {
return this._value
}
get start() {
return this._start
}
get end() {
return this._end
}
get children() {
return this._children
}
get values() {
return (this.children.length === 0 ? [this] : this.children)
.map(g => g.value)
.filter(v => v !== undefined)
}
}
module.exports = Group

43
node_modules/cucumber-expressions/src/group_builder.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
const Group = require('./group')
class GroupBuilder {
constructor() {
this._groupBuilders = []
this._capturing = true
}
add(groupBuilder) {
this._groupBuilders.push(groupBuilder)
}
build(match, nextGroupIndex) {
const groupIndex = nextGroupIndex()
const children = this._groupBuilders.map(gb =>
gb.build(match, nextGroupIndex)
)
return new Group(
match[groupIndex],
match.index[groupIndex],
match.index[groupIndex] + (match[groupIndex] || '').length,
children
)
}
setNonCapturing() {
this._capturing = false
}
get capturing() {
return this._capturing
}
get children() {
return this._groupBuilders
}
moveChildrenTo(groupBuilder) {
this._groupBuilders.forEach(child => groupBuilder.add(child))
}
}
module.exports = GroupBuilder

13
node_modules/cucumber-expressions/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
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')
module.exports = {
CucumberExpression,
RegularExpression,
CucumberExpressionGenerator,
ParameterTypeRegistry,
ParameterType,
}

113
node_modules/cucumber-expressions/src/parameter_type.js generated vendored Normal file
View File

@@ -0,0 +1,113 @@
const { CucumberExpressionError } = require('./errors')
const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/
const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g
class ParameterType {
static compare(pt1, pt2) {
if (pt1.preferForRegexpMatch && !pt2.preferForRegexpMatch) return -1
if (pt2.preferForRegexpMatch && !pt1.preferForRegexpMatch) return 1
return pt1.name.localeCompare(pt2.name)
}
static checkParameterTypeName(typeName) {
const unescapedTypeName = typeName.replace(UNESCAPE_PATTERN(), '$2')
const match = unescapedTypeName.match(ILLEGAL_PARAMETER_NAME_PATTERN)
if (match)
throw new CucumberExpressionError(
`Illegal character '${
match[1]
}' in parameter name {${unescapedTypeName}}`
)
}
/**
* @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
if (name) ParameterType.checkParameterTypeName(name)
this._name = name
this._regexps = stringArray(regexps)
this._type = type
this._transform = transform
this._useForSnippets = useForSnippets
this._preferForRegexpMatch = preferForRegexpMatch
}
get name() {
return this._name
}
get regexps() {
return this._regexps
}
get type() {
return this._type
}
get preferForRegexpMatch() {
return this._preferForRegexpMatch
}
get useForSnippets() {
return this._useForSnippets
}
transform(thisObj, groupValues) {
return this._transform.apply(thisObj, groupValues)
}
}
function stringArray(regexps) {
const array = Array.isArray(regexps) ? regexps : [regexps]
return array.map(r => (typeof r === 'string' ? r : regexpSource(r)))
}
function regexpSource(regexp) {
const flags = regexpFlags(regexp)
for (const flag of ['g', 'i', 'm', 'y']) {
if (flags.indexOf(flag) !== -1)
throw new CucumberExpressionError(
`ParameterType Regexps can't use flag '${flag}'`
)
}
return regexp.source
}
// Backport RegExp.flags for Node 4.x
// https://github.com/nodejs/node/issues/8390
//
// For some strange reason this is not needed for
// `./mocha dist/test`, but it is needed for
// `./mocha dist/test/parameter_type_test.js`
function regexpFlags(regexp) {
let flags = regexp.flags
if (flags === undefined) {
flags = ''
if (regexp.ignoreCase) flags += 'i'
if (regexp.global) flags += 'g'
if (regexp.multiline) flags += 'm'
}
return flags
}
module.exports = ParameterType

View File

@@ -0,0 +1,63 @@
class ParameterTypeMatcher {
constructor(parameter, regexp, text, matchPosition) {
this._parameterType = parameter
this._treeRegexp = regexp
this._text = text
this._matchPosition = matchPosition || 0
const captureGroupRegexp = new RegExp(`(${regexp})`)
this._match = captureGroupRegexp.exec(text.slice(this._matchPosition))
}
get parameterType() {
return this._parameterType
}
advanceTo(newMatchPosition) {
for (
let advancedPos = newMatchPosition;
advancedPos < this._text.length;
advancedPos++
) {
let matcher = new ParameterTypeMatcher(
this._parameterType,
this._treeRegexp,
this._text,
advancedPos
)
if (matcher.find) {
return matcher
}
}
return new ParameterTypeMatcher(
this._parameterType,
this._treeRegexp,
this._text,
this._text.length
)
}
get find() {
return this._match && this.group !== ''
}
get start() {
return this._matchPosition + this._match.index
}
get group() {
return this._match[0]
}
static compare(a, b) {
const posComparison = a.start - b.start
if (posComparison !== 0) return posComparison
const lengthComparison = b.group.length - a.group.length
if (lengthComparison !== 0) return lengthComparison
return 0
}
}
module.exports = ParameterTypeMatcher

View File

@@ -0,0 +1,130 @@
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 = /[^\s]+/
const STRING_REGEXP = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/
const ANONYMOUS_REGEXP = /.*/
class ParameterTypeRegistry {
constructor() {
this._parameterTypeByName = new Map()
this._parameterTypesByRegexp = new Map()
this.defineParameterType(
new ParameterType(
'int',
INTEGER_REGEXPS,
Number,
s => s && parseInt(s),
true,
true
)
)
this.defineParameterType(
new ParameterType(
'float',
FLOAT_REGEXP,
Number,
s => s && parseFloat(s),
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
)
)
this.defineParameterType(
new ParameterType('', ANONYMOUS_REGEXP, String, s => s, false, true)
)
}
get parameterTypes() {
return this._parameterTypeByName.values()
}
lookupByTypeName(typeName) {
return this._parameterTypeByName.get(typeName)
}
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]
}
defineParameterType(parameterType) {
if (parameterType.name !== undefined) {
if (this._parameterTypeByName.has(parameterType.name))
if (parameterType.name.length === 0)
throw new Error(
`The anonymous parameter type has already been defined`
)
else
throw new Error(
`There is already a parameter type with name ${parameterType.name}`
)
this._parameterTypeByName.set(parameterType.name, parameterType)
}
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.indexOf(parameterType) === -1) {
parameterTypes.push(parameterType)
this._parameterTypesByRegexp.set(
parameterTypeRegexp,
parameterTypes.sort(ParameterType.compare)
)
}
}
}
}
module.exports = ParameterTypeRegistry

View File

@@ -0,0 +1,47 @@
const Argument = require('./argument')
const TreeRegexp = require('./tree_regexp')
const ParameterType = require('./parameter_type')
class RegularExpression {
constructor(expressionRegexp, parameterTypeRegistry) {
this._expressionRegexp = expressionRegexp
this._parameterTypeRegistry = parameterTypeRegistry
this._treeRegexp = new TreeRegexp(expressionRegexp)
}
match(text) {
const parameterTypes = this._treeRegexp.groupBuilder.children.map(
groupBuilder => {
const parameterTypeRegexp = groupBuilder.source
return (
this._parameterTypeRegistry.lookupByRegexp(
parameterTypeRegexp,
this._treeRegexp,
text
) ||
new ParameterType(
null,
parameterTypeRegexp,
String,
s => s,
false,
false
)
)
}
)
return Argument.build(this._treeRegexp, text, parameterTypes)
}
get regexp() {
return this._expressionRegexp
}
get source() {
return this._expressionRegexp.source
}
}
module.exports = RegularExpression

63
node_modules/cucumber-expressions/src/tree_regexp.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
const Regex = require('becke-ch--regex--s0-0-v1--base--pl--lib')
const GroupBuilder = require('./group_builder')
class TreeRegexp {
constructor(regexp) {
this._re = 'string' === typeof regexp ? new RegExp(regexp) : regexp
this._regex = new Regex(this._re.source, this._re.flags)
const stack = [new GroupBuilder()]
const groupStartStack = []
let last = null
let escaping = false
let nonCapturingMaybe = false
let charClass = false
this._re.source.split('').forEach((c, n) => {
if (c == '[' && !escaping) {
charClass = true
} else if (c == ']' && !escaping) {
charClass = false
} else if (c === '(' && !escaping && !charClass) {
stack.push(new GroupBuilder())
groupStartStack.push(n + 1)
nonCapturingMaybe = false
} else if (c === ')' && !escaping && !charClass) {
const gb = stack.pop()
const groupStart = groupStartStack.pop()
if (gb.capturing) {
gb.source = this._re.source.substring(groupStart, n)
stack[stack.length - 1].add(gb)
} else {
gb.moveChildrenTo(stack[stack.length - 1])
}
nonCapturingMaybe = false
} else if (c === '?' && last === '(') {
nonCapturingMaybe = true
} else if (c === ':' && nonCapturingMaybe) {
stack[stack.length - 1].setNonCapturing()
nonCapturingMaybe = false
}
escaping = c === '\\' && !escaping
last = c
})
this._groupBuilder = stack.pop()
}
get regexp() {
return this._re
}
get groupBuilder() {
return this._groupBuilder
}
match(s) {
const match = this._regex.exec(s)
if (!match) return null
let groupIndex = 0
const nextGroupIndex = () => groupIndex++
return this._groupBuilder.build(match, nextGroupIndex)
}
}
module.exports = TreeRegexp

View File

@@ -0,0 +1,7 @@
const assert = require('assert')
// A better assert.error that allows an exact error message
module.exports = (fn, message) => {
const regexp = new RegExp(message.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))
assert.throws(fn, regexp)
}

View File

@@ -0,0 +1,69 @@
/* eslint-env mocha */
const assert = require('assert')
const ParameterType = require('../src/parameter_type')
const CombinatorialGeneratedExpressionFactory = require('../src/combinatorial_generated_expression_factory')
describe('CucumberExpressionGenerator', () => {
it('generates multiple expressions', () => {
const parameterTypeCombinations = [
[
new ParameterType(
'color',
/red|blue|yellow/,
null,
s => s,
false,
true
),
new ParameterType(
'csscolor',
/red|blue|yellow/,
null,
s => s,
false,
true
),
],
[
new ParameterType(
'date',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
new ParameterType(
'datetime',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
new ParameterType(
'timestamp',
/\d{4}-\d{2}-\d{2}/,
null,
s => s,
false,
true
),
],
]
const factory = new CombinatorialGeneratedExpressionFactory(
'I bought a {%s} ball on {%s}',
parameterTypeCombinations
)
const expressions = factory.generateExpressions().map(ge => ge.source)
assert.deepEqual(expressions, [
'I bought a {color} ball on {date}',
'I bought a {color} ball on {datetime}',
'I bought a {color} ball on {timestamp}',
'I bought a {csscolor} ball on {date}',
'I bought a {csscolor} ball on {datetime}',
'I bought a {csscolor} ball on {timestamp}',
])
})
})

View File

@@ -0,0 +1,248 @@
/* eslint-env mocha */
const assert = require('assert')
const CucumberExpressionGenerator = require('../src/cucumber_expression_generator')
const CucumberExpression = require('../src/cucumber_expression')
const ParameterType = require('../src/parameter_type')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
class Currency {}
describe('CucumberExpressionGenerator', () => {
let parameterTypeRegistry, generator
function assertExpression(expectedExpression, expectedArgumentNames, text) {
const generatedExpression = generator.generateExpressions(text)[0]
assert.deepEqual(generatedExpression.parameterNames, expectedArgumentNames)
assert.equal(generatedExpression.source, expectedExpression)
const cucumberExpression = new CucumberExpression(
generatedExpression.source,
parameterTypeRegistry
)
const match = cucumberExpression.match(text)
if (match === null) {
assert.fail(
`Expected text '${text}' to match generated expression '${
generatedExpression.source
}'`
)
}
assert.equal(match.length, expectedArgumentNames.length)
}
beforeEach(() => {
parameterTypeRegistry = new ParameterTypeRegistry()
generator = new CucumberExpressionGenerator(parameterTypeRegistry)
})
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.generateExpressions(
undefinedStepText
)[0]
assert.equal(
generatedExpression.source,
'I have {int} cucumbers and {float} tomato'
)
assert.equal(generatedExpression.parameterNames[0], 'int')
assert.equal(generatedExpression.parameterTypes[1].name, 'float')
/// [generate-expression]
})
it('generates expression for no args', () => {
assertExpression('hello', [], 'hello')
})
it('generates expression with escaped left parenthesis', () => {
assertExpression('\\(iii)', [], '(iii)')
})
it('generates expression with escaped left curly brace', () => {
assertExpression('\\{iii}', [], '{iii}')
})
it('generates expression with escaped slashes', () => {
assertExpression(
'The {int}\\/{int}\\/{int} hey',
['int', 'int2', 'int3'],
'The 1814/05/17 hey'
)
})
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'
)
})
it('generates expression for strings', () => {
assertExpression(
'I like {string} and {string}',
['string', 'string2'],
'I like "bangers" and \'mash\''
)
})
it('generates expression with % sign', () => {
assertExpression('I am {int}%% foobar', ['int'], 'I am 20%% foobar')
})
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'
)
})
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'
)
})
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')
})
// 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)
assert.deepEqual(typeNames, ['int', 'float'])
})
it('matches parameter types with optional capture groups', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'optional-flight',
/(1st flight)?/,
null,
s => s,
true,
false
)
)
parameterTypeRegistry.defineParameterType(
new ParameterType(
'optional-hotel',
/(1st hotel)?/,
null,
s => s,
true,
false
)
)
const expression = generator.generateExpressions(
'I reach Stage4: 1st flight-1st hotel'
)[0]
// While you would expect this to be `I reach Stage{int}: {optional-flight}-{optional-hotel}` the `-1` causes
// {int} to match just before {optional-hotel}.
assert.equal(
expression.source,
'I reach Stage{int}: {optional-flight}{int}st hotel'
)
})
it('generates at most 256 expressions', () => {
for (let i = 0; i < 4; i++) {
parameterTypeRegistry.defineParameterType(
new ParameterType('my-type-' + i, /[a-z]/, null, s => s, true, false)
)
}
// This would otherwise generate 4^11=419430 expressions and consume just shy of 1.5GB.
const expressions = generator.generateExpressions('a simple step')
assert.equal(expressions.length, 256)
})
it('prefers expression with longest non empty match', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType('zero-or-more', /[a-z]*/, null, s => s, true, false)
)
parameterTypeRegistry.defineParameterType(
new ParameterType('exactly-one', /[a-z]/, null, s => s, true, false)
)
const expressions = generator.generateExpressions('a simple step')
assert.equal(expressions.length, 2)
assert.equal(
expressions[0].source,
'{exactly-one} {zero-or-more} {zero-or-more}'
)
assert.equal(
expressions[1].source,
'{zero-or-more} {zero-or-more} {zero-or-more}'
)
})
})

View File

@@ -0,0 +1,52 @@
/* eslint-env mocha */
const assert = require('assert')
const CucumberExpression = require('../src/cucumber_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('CucumberExpression', () => {
describe('RegExp translation', () => {
it('translates no arguments', () => {
assertRegexp(
'I have 10 cukes in my belly now',
/^I have 10 cukes in my belly now$/
)
})
it('translates alternation', () => {
assertRegexp(
'I had/have a great/nice/charming friend',
/^I (?:had|have) a (?:great|nice|charming) friend$/
)
})
it('translates alternation with non-alpha', () => {
assertRegexp('I said Alpha1/Beta1', /^I said (?:Alpha1|Beta1)$/)
})
it('translates parameters', () => {
assertRegexp(
"I have {float} cukes at {int} o'clock",
/^I have (-?\d*\.\d+) cukes 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$/
)
})
it('translates parenthesis with alpha unicode', () => {
assertRegexp('Привет, Мир(ы)!', /^Привет, Мир(?:ы)?!$/)
})
})
})
const assertRegexp = (expression, expectedRegexp) => {
const cucumberExpression = new CucumberExpression(
expression,
new ParameterTypeRegistry()
)
assert.deepEqual(cucumberExpression.regexp, expectedRegexp)
}

View File

@@ -0,0 +1,270 @@
/* eslint-env mocha */
const assert = require('assert')
const {
CucumberExpression,
ParameterTypeRegistry,
ParameterType,
} = require('../src/index')
describe('CucumberExpression', () => {
it('documents match arguments', () => {
const parameterTypeRegistry = new ParameterTypeRegistry()
/// [capture-match-arguments]
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].getValue(null))
/// [capture-match-arguments]
})
it('matches word', () => {
assert.deepEqual(match('three {word} mice', 'three blind mice'), ['blind'])
})
it('matches double quoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind" mice'), [
'blind',
])
})
it('matches multiple double quoted strings', () => {
assert.deepEqual(
match(
'three {string} and {string} mice',
'three "blind" and "crippled" mice'
),
['blind', 'crippled']
)
})
it('matches single quoted string', () => {
assert.deepEqual(match('three {string} mice', "three 'blind' mice"), [
'blind',
])
})
it('matches multiple single quoted strings', () => {
assert.deepEqual(
match(
'three {string} and {string} mice',
"three 'blind' and 'crippled' mice"
),
['blind', 'crippled']
)
})
it('does not match misquoted string', () => {
assert.deepEqual(match('three {string} mice', 'three "blind\' mice'), null)
})
it('matches single quoted string with double quotes', () => {
assert.deepEqual(match('three {string} mice', 'three \'"blind"\' mice'), [
'"blind"',
])
})
it('matches double quoted string with single quotes', () => {
assert.deepEqual(match('three {string} mice', 'three "\'blind\'" mice'), [
"'blind'",
])
})
it('matches double quoted string with escaped double quote', () => {
assert.deepEqual(match('three {string} mice', 'three "bl\\"nd" mice'), [
'bl"nd',
])
})
it('matches single quoted string with escaped single quote', () => {
assert.deepEqual(match('three {string} mice', "three 'bl\\'nd' mice"), [
"bl'nd",
])
})
it('matches single quoted string with escaped single quote', () => {
assert.deepEqual(match('three {string} mice', "three 'bl\\'nd' mice"), [
"bl'nd",
])
})
it('matches escaped parenthesis', () => {
assert.deepEqual(
match(
'three \\(exceptionally) {string} mice',
'three (exceptionally) "blind" mice'
),
['blind']
)
})
it('matches escaped slash', () => {
assert.deepEqual(match('12\\/2020', '12/2020'), [])
})
it('matches int', () => {
assert.deepEqual(match('{int}', '22'), [22])
})
it("doesn't match float as int", () => {
assert.deepEqual(match('{int}', '1.22'), null)
})
it('matches float', () => {
assert.deepEqual(match('{float}', '0.22'), [0.22])
assert.deepEqual(match('{float}', '.22'), [0.22])
})
it('matches anonymous', () => {
assert.deepEqual(match('{}', '0.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('does not allow optional parameter types', () => {
try {
match('({int})', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be optional: ({int})'
)
}
})
it('allows escaped optional parameter types', () => {
assert.deepEqual(match('\\({int})', '(3)'), [3])
})
it('does not allow text/parameter type alternation', () => {
try {
match('x/{int}', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be alternative: x/{int}'
)
}
})
it('does not allow parameter type/text alternation', () => {
try {
match('{int}/x', '3')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
'Parameter types cannot be alternative: {int}/x'
)
}
})
for (const c of '[]()$.|?*+'.split('')) {
it(`does not allow parameter type with ${c}`, () => {
try {
match(`{${c}string}`, 'something')
assert.fail()
} catch (expected) {
assert.equal(
expected.message,
`Illegal character '${c}' in parameter name {${c}string}`
)
}
})
}
it('exposes source', () => {
const expr = 'I have {int} cuke(s)'
assert.equal(
new CucumberExpression(expr, new ParameterTypeRegistry()).source,
expr
)
})
// JavaScript-specific
it('delegates transform to custom object', () => {
const parameterTypeRegistry = new ParameterTypeRegistry()
parameterTypeRegistry.defineParameterType(
new ParameterType(
'widget',
/\w+/,
null,
function(s) {
return this.createWidget(s)
},
false,
true
)
)
const expression = new CucumberExpression(
'I have a {widget}',
parameterTypeRegistry
)
const world = {
createWidget(s) {
return `widget:${s}`
},
}
const args = expression.match(`I have a bolt`)
assert.equal(args[0].getValue(world), 'widget:bolt')
})
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 arg1 = expression.match(`I have 800 cukes and ${character}`)[0]
assert.equal(arg1.getValue(null), 800)
})
})
it(`escapes .`, () => {
const expr = `I have {int} cuke(s) and .`
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.getValue(null), 800)
})
it(`escapes |`, () => {
const expr = `I have {int} cuke(s) and a|b`
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.getValue(null), 800)
})
})
})
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.getValue(null))
}

View File

@@ -0,0 +1,258 @@
/* eslint-env mocha */
'use strict'
const assert = require('assert')
const assertThrows = require('./assert_throws')
const CucumberExpression = require('../src/cucumber_expression')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
const ParameterType = require('../src/parameter_type')
require('@babel/polyfill')
class Color {
/// [color-constructor]
constructor(name) {
this.name = name
}
/// [color-constructor]
}
class CssColor {
constructor(name) {
this.name = name
}
}
describe('Custom parameter type', () => {
let parameterTypeRegistry
beforeEach(() => {
parameterTypeRegistry = new ParameterTypeRegistry()
/* eslint-disable prettier/prettier */
/// [add-color-parameter-type]
parameterTypeRegistry.defineParameterType(
new ParameterType(
'color', // name
/red|blue|yellow/, // regexp
Color, // type
s => new Color(s), // transformer
false, // useForSnippets
true // preferForRegexpMatch
)
)
/// [add-color-parameter-type]
/* eslint-enable prettier/prettier */
})
describe('CucumberExpression', () => {
it('throws exception for illegal character in parameter name', () => {
assertThrows(
() => new ParameterType('[string]', /.*/, String, s => s, false, true),
"Illegal character '[' in parameter name {[string]}"
)
})
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].getValue(null)
assert.equal(value.name, '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].getValue(null)
assert.equal(thick, 5)
const from = args[1].getValue(null)
assert.equal(from.x, 10)
assert.equal(from.y, 20)
assert.equal(from.z, 30)
const to = args[2].getValue(null)
assert.equal(to.x, 40)
assert.equal(to.y, 50)
assert.equal(to.z, 60)
})
it('matches parameters with custom parameter type using optional capture group', () => {
parameterTypeRegistry = new ParameterTypeRegistry()
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].getValue(null)
assert.equal(value.name, 'dark 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
)
)
const expression = new CucumberExpression(
'I have a {throwing} parameter',
parameterTypeRegistry
)
const args = expression.match('I have a bad parameter')
assertThrows(() => args[0].getValue(null), "Can't transform [bad]")
})
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 not detected for type', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'whatever',
/.*/,
Color,
s => new Color(s),
false,
false
)
)
})
it('is not detected for regexp', () => {
parameterTypeRegistry.defineParameterType(
new ParameterType(
'css-color',
/red|blue|yellow/,
CssColor,
s => new CssColor(s),
true,
false
)
)
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
)
.match('I have a blue ball')[0]
.getValue(null).constructor,
CssColor
)
assert.equal(
new CucumberExpression(
'I have a {css-color} ball',
parameterTypeRegistry
)
.match('I have a blue ball')[0]
.getValue(null).name,
'blue'
)
assert.equal(
new CucumberExpression('I have a {color} ball', parameterTypeRegistry)
.match('I have a blue ball')[0]
.getValue(null).constructor,
Color
)
assert.equal(
new CucumberExpression('I have a {color} ball', parameterTypeRegistry)
.match('I have a blue ball')[0]
.getValue(null).name,
'blue'
)
})
})
// JavaScript-specific
it('creates arguments using async transform', async () => {
parameterTypeRegistry = new ParameterTypeRegistry()
/// [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',
parameterTypeRegistry
)
const args = await expression.match('I have a red ball')
const value = await args[0].getValue(null)
assert.equal(value.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].getValue(null)
assert.equal(value.constructor, Color)
assert.equal(value.name, 'red')
})
})
})

View File

@@ -0,0 +1,30 @@
/* eslint-env mocha */
const fs = require('fs')
const assert = require('assert')
const CucumberExpression = require('../src/cucumber_expression')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('examples.txt', () => {
const match = (expression_text, text) => {
const m = /\/(.*)\//.exec(expression_text)
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.getValue(null))
}
const examples = fs.readFileSync('examples.txt', 'utf-8')
const chunks = examples.split(/^---/m)
for (const chunk of chunks) {
const [expressionText, text, expectedArgs] = chunk.trim().split(/\n/m)
it(`Works with: ${expressionText}`, () => {
assert.deepEqual(
JSON.stringify(match(expressionText, text)),
expectedArgs
)
})
}
})

1
node_modules/cucumber-expressions/test/mocha.opts generated vendored Normal file
View File

@@ -0,0 +1 @@
--recursive

View File

@@ -0,0 +1,99 @@
/* eslint-env mocha */
const assert = require('assert')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
const ParameterType = require('../src/parameter_type')
class Name {}
class Person {}
class Place {}
const CAPITALISED_WORD = /[A-Z]+\w+/
describe('ParameterTypeRegistry', () => {
let registry
beforeEach(() => {
registry = new ParameterTypeRegistry()
})
it('does not allow more than one preferential parameter type for each regexp', () => {
registry.defineParameterType(
new ParameterType(
'name',
CAPITALISED_WORD,
Name,
s => new Name(s),
true,
true
)
)
registry.defineParameterType(
new ParameterType(
'person',
CAPITALISED_WORD,
Person,
s => new Person(s),
true,
false
)
)
try {
registry.defineParameterType(
new ParameterType(
'place',
CAPITALISED_WORD,
Place,
s => new Place(s),
true,
true
)
)
throw new Error('Should have failed')
} catch (err) {
assert.equal(
err.message,
`There can only be one preferential parameter type per regexp. The regexp ${CAPITALISED_WORD} is used for two preferential parameter types, {name} and {place}`
)
}
})
it('looks up preferential parameter type by regexp', () => {
const name = new ParameterType(
'name',
/[A-Z]+\w+/,
null,
s => new Name(s),
true,
false
)
const person = new ParameterType(
'person',
/[A-Z]+\w+/,
null,
s => new Person(s),
true,
true
)
const place = new ParameterType(
'place',
/[A-Z]+\w+/,
null,
s => new Place(s),
true,
false
)
registry.defineParameterType(name)
registry.defineParameterType(person)
registry.defineParameterType(place)
assert.equal(
registry.lookupByRegexp(
'[A-Z]+\\w+',
/([A-Z]+\w+) and ([A-Z]+\w+)/,
'Lisa and Bob'
),
person
)
})
})

View File

@@ -0,0 +1,20 @@
/* eslint-env mocha */
const assertThrows = require('./assert_throws')
const ParameterType = require('../src/parameter_type')
describe('ParameterType', () => {
it('does not allow ignore flag on regexp', () => {
assertThrows(
() =>
new ParameterType(
'case-insensitive',
/[a-z]+/i,
String,
s => s,
true,
true
),
"ParameterType Regexps can't use flag 'i'"
)
})
})

View File

@@ -0,0 +1,106 @@
/* eslint-env mocha */
const assert = require('assert')
const RegularExpression = require('../src/regular_expression')
const ParameterTypeRegistry = require('../src/parameter_type_registry')
describe('RegularExpression', () => {
it('documents match arguments', () => {
const parameterRegistry = new ParameterTypeRegistry()
/// [capture-match-arguments]
const expr = /I have (\d+) cukes? in my (\w+) now/
const expression = new RegularExpression(expr, parameterRegistry)
const args = expression.match('I have 7 cukes in my belly now')
assert.equal(7, args[0].getValue(null))
assert.equal('belly', args[1].getValue(null))
/// [capture-match-arguments]
})
it('does no transform by default', () => {
assert.deepEqual(match(/(\d\d)/, '22')[0], '22')
})
it('does not transform anonymous', () => {
assert.deepEqual(match(/(.*)/, '22')[0], '22')
})
it('transforms negative int', () => {
assert.deepEqual(match(/(-?\d+)/, '-22')[0], -22)
})
it('transforms positive int', () => {
assert.deepEqual(match(/(\d+)/, '22')[0], 22)
})
it('transforms float without integer part', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '.22')[0], 0.22)
})
it('transforms float with sign', () => {
assert.deepEqual(match(/(-?\d*\.?\d+)/, '-1.22')[0], -1.22)
})
it('returns null when there is no match', () => {
assert.equal(match(/hello/, 'world'), null)
})
it('matches nested capture group without match', () => {
assert.deepEqual(match(/^a user( named "([^"]*)")?$/, 'a user'), [null])
})
it('matches nested capture group with match', () => {
assert.deepEqual(
match(/^a user( named "([^"]*)")?$/, 'a user named "Charlie"'),
['Charlie']
)
})
it('matches capture group nested in optional one', () => {
const regexp = /^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/
assert.deepEqual(match(regexp, 'a purchase'), [null, null])
assert.deepEqual(match(regexp, 'a purchase for $33'), [null, 33])
assert.deepEqual(match(regexp, 'a pre buyer fee model purchase'), [
'pre buyer fee model ',
null,
])
})
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('works with escaped parenthesis', () => {
assert.deepEqual(match(/Across the line\(s\)/, 'Across the line(s)'), [])
})
it('exposes regexp and source', () => {
const regexp = /I have (\d+) cukes? in my (.+) now/
let expression = new RegularExpression(regexp, new ParameterTypeRegistry())
assert.deepEqual(expression.regexp, regexp)
assert.deepEqual(expression.source, regexp.source)
})
it('does not take consider parenthesis in character class as group', function() {
const expression = new RegularExpression(
/^drawings: ([A-Z_, ()]+)$/,
new ParameterTypeRegistry()
)
const args = expression.match('drawings: ONE, TWO(ABC)')
assert.equal(args[0].getValue(), 'ONE, TWO(ABC)')
})
})
const match = (regexp, text) => {
const parameterRegistry = new ParameterTypeRegistry()
const regularExpression = new RegularExpression(regexp, parameterRegistry)
const args = regularExpression.match(text)
if (!args) return null
return args.map(arg => arg.getValue(null))
}

View File

@@ -0,0 +1,100 @@
/* eslint-env mocha */
const assert = require('assert')
const TreeRegexp = require('../src/tree_regexp')
describe('TreeRegexp', () => {
it('exposes group source', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
assert.deepEqual(tr.groupBuilder.children.map(gb => gb.source), [
'a(?:b)?',
'c',
])
})
it('builds tree', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
const group = tr.match('ac')
assert.equal(group.value, 'ac')
assert.equal(group.children[0].value, 'a')
assert.deepEqual(group.children[0].children, [])
assert.equal(group.children[1].value, 'c')
})
it('ignores non-capturing groups', () => {
const tr = new TreeRegexp(/(a(?:b)?)(c)/)
const group = tr.match('ac')
assert.equal(group.value, 'ac')
assert.equal(group.children[0].value, 'a')
assert.deepEqual(group.children[0].children, [])
assert.equal(group.children[1].value, 'c')
})
it('matches optional group', () => {
const tr = new TreeRegexp(/^Something( with an optional argument)?/)
const group = tr.match('Something')
assert.equal(group.children[0].value, null)
})
it('matches nested groups', () => {
const tr = new TreeRegexp(
/^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+))/
)
const group = tr.match('A 5 thick line from 10,20,30 to 40,50,60')
assert.equal(group.children[0].value, '5')
assert.equal(group.children[1].value, '10,20,30')
assert.equal(group.children[1].children[0].value, '10')
assert.equal(group.children[1].children[1].value, '20')
assert.equal(group.children[1].children[2].value, '30')
assert.equal(group.children[2].value, '40,50,60')
assert.equal(group.children[2].children[0].value, '40')
assert.equal(group.children[2].children[1].value, '50')
assert.equal(group.children[2].children[2].value, '60')
})
it('detects multiple non capturing groups', () => {
const tr = new TreeRegexp(/(?:a)(:b)(\?c)(d)/)
const group = tr.match('a:b?cd')
assert.equal(group.children.length, 3)
})
it('works with escaped backslash', () => {
const tr = new TreeRegexp(/foo\\(bar|baz)/)
const group = tr.match('foo\\bar')
assert.equal(group.children.length, 1)
})
it('works with escaped slash', () => {
const tr = new TreeRegexp(/^I go to '\/(.+)'$/)
const group = tr.match("I go to '/hello'")
assert.equal(group.children.length, 1)
})
it('works with digit and word', () => {
const tr = new TreeRegexp(/^(\d) (\w+)$/)
const group = tr.match('2 you')
assert.equal(group.children.length, 2)
})
it('captures non capturing groups with capturing groups inside', () => {
const tr = new TreeRegexp('the stdout(?: from "(.*?)")?')
const group = tr.match('the stdout')
assert.equal(group.value, 'the stdout')
assert.equal(group.children[0].value, null)
assert.equal(group.children.length, 1)
})
it('works with flags', () => {
const tr = new TreeRegexp(/HELLO/i)
const group = tr.match('hello')
assert.equal(group.value, 'hello')
})
it('does not consider parenthesis in character class as group', () => {
const tr = new TreeRegexp(/^drawings: ([A-Z, ()]+)$/)
const group = tr.match('drawings: ONE(TWO)')
assert.equal(group.value, 'drawings: ONE(TWO)')
assert.equal(group.children.length, 1)
assert.equal(group.children[0].value, 'ONE(TWO)')
})
})

3634
node_modules/cucumber-expressions/yarn.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff