getChildren() {
47 | return groupBuilders;
48 | }
49 |
50 | String getSource() {
51 | return source;
52 | }
53 |
54 | void setSource(String source) {
55 | this.source = source;
56 | }
57 |
58 | int getStartIndex() {
59 | return startIndex;
60 | }
61 |
62 | int getEndIndex() {
63 | return endIndex;
64 | }
65 |
66 | void setEndIndex(int endIndex) {
67 | this.endIndex = endIndex;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/KeyboardFriendlyDecimalFormatSymbols.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import java.text.DecimalFormatSymbols;
4 | import java.util.Locale;
5 |
6 | /**
7 | * A set of localized decimal symbols that can be written on a regular keyboard.
8 | *
9 | * Note quite complete, feel free to make a suggestion.
10 | */
11 | class KeyboardFriendlyDecimalFormatSymbols {
12 |
13 | static DecimalFormatSymbols getInstance(Locale locale) {
14 | DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
15 |
16 | // Replace the minus sign with minus-hyphen as available on most keyboards.
17 | if (symbols.getMinusSign() == '\u2212') {
18 | symbols.setMinusSign('-');
19 | }
20 |
21 | if (symbols.getDecimalSeparator() == '.') {
22 | // For locales that use the period as the decimal separator
23 | // always use the comma for thousands. The alternatives are
24 | // not available on a keyboard
25 | symbols.setGroupingSeparator(',');
26 | } else if (symbols.getDecimalSeparator() == ',') {
27 | // For locales that use the comma as the decimal separator
28 | // always use the period for thousands. The alternatives are
29 | // not available on a keyboard
30 | symbols.setGroupingSeparator('.');
31 | }
32 | return symbols;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/NumberParser.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import java.math.BigDecimal;
4 | import java.text.DecimalFormat;
5 | import java.text.DecimalFormatSymbols;
6 | import java.text.NumberFormat;
7 | import java.text.ParseException;
8 | import java.util.Locale;
9 |
10 | final class NumberParser {
11 | private final NumberFormat numberFormat;
12 |
13 | NumberParser(Locale locale) {
14 | numberFormat = DecimalFormat.getNumberInstance(locale);
15 | if (numberFormat instanceof DecimalFormat) {
16 | DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
17 | decimalFormat.setParseBigDecimal(true);
18 | DecimalFormatSymbols symbols = KeyboardFriendlyDecimalFormatSymbols.getInstance(locale);
19 | decimalFormat.setDecimalFormatSymbols(symbols);
20 | }
21 | }
22 |
23 | double parseDouble(String s) {
24 | return parse(s).doubleValue();
25 | }
26 |
27 | float parseFloat(String s) {
28 | return parse(s).floatValue();
29 | }
30 |
31 | BigDecimal parseBigDecimal(String s) {
32 | if (numberFormat instanceof DecimalFormat) {
33 | return (BigDecimal) parse(s);
34 | }
35 | // Fall back to default big decimal format
36 | // if the locale does not have a DecimalFormat
37 | return new BigDecimal(s);
38 | }
39 |
40 | private Number parse(String s) {
41 | try {
42 | return numberFormat.parse(s);
43 | } catch (ParseException e) {
44 | throw new CucumberExpressionException("Failed to parse number", e);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/ParameterByTypeTransformer.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.apiguardian.api.API;
4 |
5 | import java.lang.reflect.Type;
6 |
7 | /**
8 | * The {@link ParameterTypeRegistry} uses the default transformer
9 | * to execute all transforms for built-in parameter types and all
10 | * anonymous types.
11 | */
12 | @API(status = API.Status.STABLE)
13 | @FunctionalInterface
14 | public interface ParameterByTypeTransformer {
15 |
16 | Object transform(String fromValue, Type toValueType) throws Throwable;
17 | }
18 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/PatternCompiler.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.apiguardian.api.API;
4 |
5 | import java.util.regex.Pattern;
6 |
7 | /**
8 | * Abstracts creation of new {@link Pattern}. In some platforms and Java versions some flags are not supported (e.g {@link Pattern#UNICODE_CHARACTER_CLASS} on Android) - clients for those platforms should provide resource {@code META-INF/services/io.cucumber.cucumberexpressions.PatternCompiler} pointing to implementation of this interface.
9 | *
10 | * @see DefaultPatternCompiler
11 | * @see java.util.ServiceLoader
12 | */
13 | @API(status = API.Status.STABLE)
14 | @FunctionalInterface
15 | public interface PatternCompiler {
16 |
17 | /**
18 | * @param regexp regular expression
19 | * @param flags additional flags (e.g. {@link Pattern#UNICODE_CHARACTER_CLASS})
20 | * @return new {@link Pattern} instance from provided {@code regexp}
21 | */
22 | Pattern compile(String regexp, int flags);
23 | }
24 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/PatternCompilerProvider.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Iterator;
5 | import java.util.List;
6 | import java.util.ServiceLoader;
7 |
8 | final class PatternCompilerProvider {
9 | // visible from tests
10 | static PatternCompiler service;
11 |
12 | private PatternCompilerProvider() {
13 | }
14 |
15 | static synchronized PatternCompiler getCompiler() {
16 | if (service == null) {
17 | ServiceLoader loader = ServiceLoader.load(PatternCompiler.class);
18 | Iterator iterator = loader.iterator();
19 | findPatternCompiler(iterator);
20 | }
21 | return service;
22 | }
23 |
24 | static void findPatternCompiler(Iterator iterator) {
25 | if (iterator.hasNext()) {
26 | service = iterator.next();
27 | if (iterator.hasNext()) {
28 | throwMoreThanOneCompilerException(iterator);
29 | }
30 | } else {
31 | service = new DefaultPatternCompiler();
32 | }
33 | }
34 |
35 | private static void throwMoreThanOneCompilerException(Iterator iterator) {
36 | List> allCompilers = new ArrayList<>();
37 | allCompilers.add(service.getClass());
38 | while (iterator.hasNext()) {
39 | allCompilers.add(iterator.next().getClass());
40 | }
41 | throw new IllegalStateException("More than one PatternCompiler: " + allCompilers);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/Transformer.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | /**
4 | * Transformer for a @{@link ParameterType} with zero or one capture groups.
5 | *
6 | * @param the type to transform to.
7 | */
8 | @FunctionalInterface
9 | public interface Transformer {
10 | /**
11 | * Transforms a string into to an object. The string is either taken
12 | * from the sole capture group or matches the whole expression. Nested
13 | * capture groups are ignored.
14 | *
15 | * If the capture group is optional arg
may be null.
16 | *
17 | * @param arg the value of the single capture group
18 | * @return the transformed object
19 | * @throws Throwable if transformation failed
20 | */
21 | T transform(String arg) throws Throwable;
22 | }
23 |
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/TypeReference.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import java.lang.reflect.ParameterizedType;
4 | import java.lang.reflect.Type;
5 |
6 | public abstract class TypeReference {
7 |
8 | private final Type type;
9 |
10 | protected TypeReference() {
11 | Type superclass = getClass().getGenericSuperclass();
12 | if (superclass instanceof Class) {
13 | throw new CucumberExpressionException("Missing type parameter: " + superclass);
14 | }
15 | this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
16 | }
17 |
18 | public Type getType() {
19 | return this.type;
20 | }
21 | }
--------------------------------------------------------------------------------
/java/src/main/java/io/cucumber/cucumberexpressions/UndefinedParameterTypeException.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import io.cucumber.cucumberexpressions.Ast.Node;
4 | import org.apiguardian.api.API;
5 |
6 | @API(status = API.Status.STABLE)
7 | public final class UndefinedParameterTypeException extends CucumberExpressionException {
8 | private final String undefinedParameterTypeName;
9 |
10 | UndefinedParameterTypeException(String message, String undefinedParameterTypeName) {
11 | super(message);
12 | this.undefinedParameterTypeName = undefinedParameterTypeName;
13 | }
14 |
15 | public String getUndefinedParameterTypeName() {
16 | return undefinedParameterTypeName;
17 | }
18 |
19 | static CucumberExpressionException createUndefinedParameterType(Node node, String expression, String undefinedParameterTypeName) {
20 | return new UndefinedParameterTypeException(message(
21 | node.start(),
22 | expression,
23 | pointAt(node),
24 | "Undefined parameter type '" +undefinedParameterTypeName+ "'",
25 | "Please register a ParameterType for '"+undefinedParameterTypeName+"'"), undefinedParameterTypeName);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/cucumberexpressions/ArgumentTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.List;
6 | import java.util.Locale;
7 |
8 | import static java.util.Collections.singletonList;
9 | import static org.junit.jupiter.api.Assertions.assertEquals;
10 |
11 | public class ArgumentTest {
12 | @Test
13 | public void exposes_parameter_type() {
14 | TreeRegexp treeRegexp = new TreeRegexp("three (.*) mice");
15 | ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH);
16 | List> arguments = Argument.build(
17 | treeRegexp.match("three blind mice"),
18 | singletonList(parameterTypeRegistry.lookupByTypeName("string")));
19 | Argument> argument = arguments.get(0);
20 | assertEquals("string", argument.getParameterType().getName());
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/cucumberexpressions/EnumParameterTypeTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.List;
6 | import java.util.Locale;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 |
10 | public class EnumParameterTypeTest {
11 |
12 | public enum Mood {
13 | happy,
14 | meh,
15 | sad
16 | }
17 |
18 | @Test
19 | public void converts_to_enum() {
20 | ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
21 | registry.defineParameterType(ParameterType.fromEnum(Mood.class));
22 |
23 | CucumberExpression expression = new CucumberExpression("I am {Mood}", registry);
24 | List> args = expression.match("I am happy");
25 | assertEquals(Mood.happy, args.get(0).getValue());
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/cucumberexpressions/GenericParameterTypeTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.List;
6 | import java.util.Locale;
7 |
8 | import static java.util.Arrays.asList;
9 | import static java.util.Collections.singletonList;
10 | import static org.junit.jupiter.api.Assertions.assertEquals;
11 |
12 | public class GenericParameterTypeTest {
13 |
14 | @Test
15 | public void transforms_to_a_list_of_string() {
16 | ParameterTypeRegistry parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH);
17 | parameterTypeRegistry.defineParameterType(new ParameterType<>(
18 | "stringlist",
19 | singletonList(".*"),
20 | new TypeReference>() {
21 | }.getType(),
22 | new CaptureGroupTransformer>() {
23 | @Override
24 | public List transform(String... args) {
25 | return asList(args[0].split(","));
26 | }
27 | },
28 | false,
29 | false)
30 | );
31 | Expression expression = new CucumberExpression("I have {stringlist} yay", parameterTypeRegistry);
32 | List> args = expression.match("I have three,blind,mice yay");
33 | assertEquals(asList("three", "blind", "mice"), args.get(0).getValue());
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/cucumberexpressions/ParameterTypeComparatorTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.SortedSet;
8 | import java.util.TreeSet;
9 |
10 | import static java.util.Arrays.asList;
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 | import static org.junit.jupiter.api.Assertions.assertNotNull;
13 |
14 | public class ParameterTypeComparatorTest {
15 |
16 | public static class A {
17 | A(String s) {
18 | assertNotNull(s);
19 | }
20 | }
21 |
22 | public static class B {
23 | B(String s) {
24 | assertNotNull(s);
25 | }
26 | }
27 |
28 | public static class C {
29 | C(String s) {
30 | assertNotNull(s);
31 | }
32 | }
33 |
34 | public static class D {
35 | D(String s) {
36 | assertNotNull(s);
37 | }
38 | }
39 |
40 | @Test
41 | public void sorts_parameter_types_by_preferential_then_name() {
42 | SortedSet> set = new TreeSet<>();
43 | set.add(new ParameterType<>("c", "c", C.class, C::new, false, true));
44 | set.add(new ParameterType<>("a", "a", A.class, A::new, false, false));
45 | set.add(new ParameterType<>("d", "d", D.class, D::new, false, false));
46 | set.add(new ParameterType<>("b", "b", B.class, B::new, false, true));
47 |
48 | List names = new ArrayList<>();
49 | for (ParameterType parameterType : set) {
50 | names.add(parameterType.getName());
51 | }
52 | assertEquals(asList("b", "c", "a", "d"), names);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java:
--------------------------------------------------------------------------------
1 | package io.cucumber.cucumberexpressions;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static io.cucumber.cucumberexpressions.RegexpUtils.escapeRegex;
6 | import static org.junit.jupiter.api.Assertions.assertEquals;
7 |
8 | class RegexpUtilsTest {
9 |
10 | @Test
11 | void escape_regex_characters(){
12 | assertEquals("hello \\$world", escapeRegex("hello $world"));
13 | }
14 |
15 | @Test
16 | void escape_all_regexp_characters() {
17 | assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", escapeRegex("^$[](){}.|?*+\\"));
18 | }
19 |
20 | @Test
21 | void escape_escaped_regexp_characters() {
22 | assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", escapeRegex("^$[]\\(\\){}\\\\.|?*+"));
23 | }
24 |
25 |
26 | @Test
27 | void do_not_escape_when_there_is_nothing_to_escape() {
28 | assertEquals("hello world", escapeRegex("hello world"));
29 | }
30 |
31 | @Test
32 | void gives_no_error_for_unicode_characters() {
33 | assertEquals("🥒", escapeRegex("🥒"));
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/javascript/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | dist-try/
3 | .idea/
4 | .nyc_output/
5 | coverage/
6 | node_modules/
7 | yarn.lock
8 | *.log
9 | .deps
10 | .tested*
11 | .linted
12 | .built*
13 | .compared
14 | .codegen
15 | acceptance/
16 | storybook-static
17 | *-go
18 | *.iml
19 | .vscode-test
20 |
21 | # stryker temp files
22 | .stryker-tmp
23 | reports
24 |
--------------------------------------------------------------------------------
/javascript/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "loader": "ts-node/esm",
3 | "extension": ["ts", "tsx"],
4 | "recursive": true,
5 | "timeout": 10000
6 | }
7 |
--------------------------------------------------------------------------------
/javascript/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": false,
4 | "singleQuote": true,
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/javascript/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Cucumber Ltd and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/javascript/package.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cucumber/cucumber-expressions",
3 | "type": "commonjs"
4 | }
5 |
--------------------------------------------------------------------------------
/javascript/src/Argument.ts:
--------------------------------------------------------------------------------
1 | import CucumberExpressionError from './CucumberExpressionError.js'
2 | import Group from './Group.js'
3 | import ParameterType from './ParameterType.js'
4 |
5 | export default class Argument {
6 | public static build(
7 | group: Group,
8 | parameterTypes: readonly ParameterType[]
9 | ): readonly Argument[] {
10 | const argGroups = group.children
11 |
12 | if (argGroups.length !== parameterTypes.length) {
13 | throw new CucumberExpressionError(
14 | `Group has ${argGroups.length} capture groups (${argGroups.map(
15 | (g) => g.value
16 | )}), but there were ${parameterTypes.length} parameter types (${parameterTypes.map(
17 | (p) => p.name
18 | )})`
19 | )
20 | }
21 |
22 | return parameterTypes.map((parameterType, i) => new Argument(argGroups[i], parameterType))
23 | }
24 |
25 | constructor(
26 | public readonly group: Group,
27 | public readonly parameterType: ParameterType
28 | ) {
29 | this.group = group
30 | this.parameterType = parameterType
31 | }
32 |
33 | /**
34 | * Get the value returned by the parameter type's transformer function.
35 | *
36 | * @param thisObj the object in which the transformer function is applied.
37 | */
38 | public getValue(thisObj: unknown): T | null {
39 | const groupValues = this.group ? this.group.values : null
40 | return this.parameterType.transform(thisObj, groupValues)
41 | }
42 |
43 | public getParameterType() {
44 | return this.parameterType
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/javascript/src/CombinatorialGeneratedExpressionFactory.ts:
--------------------------------------------------------------------------------
1 | import GeneratedExpression from './GeneratedExpression.js'
2 | import ParameterType from './ParameterType.js'
3 |
4 | // 256 generated expressions ought to be enough for anybody
5 | const MAX_EXPRESSIONS = 256
6 |
7 | export default class CombinatorialGeneratedExpressionFactory {
8 | constructor(
9 | private readonly expressionTemplate: string,
10 | private readonly parameterTypeCombinations: Array>>
11 | ) {
12 | this.expressionTemplate = expressionTemplate
13 | }
14 |
15 | public generateExpressions(): readonly GeneratedExpression[] {
16 | const generatedExpressions: GeneratedExpression[] = []
17 | this.generatePermutations(generatedExpressions, 0, [])
18 | return generatedExpressions
19 | }
20 |
21 | private generatePermutations(
22 | generatedExpressions: GeneratedExpression[],
23 | depth: number,
24 | currentParameterTypes: Array>
25 | ) {
26 | if (generatedExpressions.length >= MAX_EXPRESSIONS) {
27 | return
28 | }
29 |
30 | if (depth === this.parameterTypeCombinations.length) {
31 | generatedExpressions.push(
32 | new GeneratedExpression(this.expressionTemplate, currentParameterTypes)
33 | )
34 | return
35 | }
36 |
37 | // tslint:disable-next-line:prefer-for-of
38 | for (let i = 0; i < this.parameterTypeCombinations[depth].length; ++i) {
39 | // Avoid recursion if no elements can be added.
40 | if (generatedExpressions.length >= MAX_EXPRESSIONS) {
41 | return
42 | }
43 |
44 | const newCurrentParameterTypes = currentParameterTypes.slice() // clone
45 | newCurrentParameterTypes.push(this.parameterTypeCombinations[depth][i])
46 | this.generatePermutations(generatedExpressions, depth + 1, newCurrentParameterTypes)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/javascript/src/CucumberExpressionError.ts:
--------------------------------------------------------------------------------
1 | export default class CucumberExpressionError extends Error {}
2 |
--------------------------------------------------------------------------------
/javascript/src/ExpressionFactory.ts:
--------------------------------------------------------------------------------
1 | import CucumberExpression from './CucumberExpression.js'
2 | import ParameterTypeRegistry from './ParameterTypeRegistry.js'
3 | import RegularExpression from './RegularExpression.js'
4 | import { Expression } from './types.js'
5 |
6 | export default class ExpressionFactory {
7 | public constructor(private readonly parameterTypeRegistry: ParameterTypeRegistry) {}
8 |
9 | public createExpression(expression: string | RegExp): Expression {
10 | return typeof expression === 'string'
11 | ? new CucumberExpression(expression, this.parameterTypeRegistry)
12 | : new RegularExpression(expression, this.parameterTypeRegistry)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/javascript/src/GeneratedExpression.ts:
--------------------------------------------------------------------------------
1 | import ParameterType from './ParameterType.js'
2 | import { ParameterInfo } from './types.js'
3 |
4 | export default class GeneratedExpression {
5 | constructor(
6 | private readonly expressionTemplate: string,
7 | public readonly parameterTypes: readonly ParameterType[]
8 | ) {}
9 |
10 | get source() {
11 | return format(this.expressionTemplate, ...this.parameterTypes.map((t) => t.name || ''))
12 | }
13 |
14 | /**
15 | * Returns an array of parameter names to use in generated function/method signatures
16 | *
17 | * @returns {ReadonlyArray.}
18 | */
19 | get parameterNames(): readonly string[] {
20 | return this.parameterInfos.map((i) => `${i.name}${i.count === 1 ? '' : i.count.toString()}`)
21 | }
22 |
23 | /**
24 | * Returns an array of ParameterInfo to use in generated function/method signatures
25 | */
26 | get parameterInfos(): readonly ParameterInfo[] {
27 | const usageByTypeName: { [key: string]: number } = {}
28 | return this.parameterTypes.map((t) => getParameterInfo(t, usageByTypeName))
29 | }
30 | }
31 |
32 | function getParameterInfo(
33 | parameterType: ParameterType,
34 | usageByName: { [key: string]: number }
35 | ): ParameterInfo {
36 | const name = parameterType.name || ''
37 | let counter = usageByName[name]
38 | counter = counter ? counter + 1 : 1
39 | usageByName[name] = counter
40 | let type: string | null
41 | if (parameterType.type) {
42 | if (typeof parameterType.type === 'string') {
43 | type = parameterType.type
44 | } else if ('name' in parameterType.type) {
45 | type = parameterType.type.name
46 | } else {
47 | type = null
48 | }
49 | } else {
50 | type = null
51 | }
52 | return {
53 | type,
54 | name,
55 | count: counter,
56 | }
57 | }
58 |
59 | function format(pattern: string, ...args: readonly string[]): string {
60 | return pattern.replace(/{(\d+)}/g, (match, number) => args[number])
61 | }
62 |
--------------------------------------------------------------------------------
/javascript/src/Group.ts:
--------------------------------------------------------------------------------
1 | export default class Group {
2 | constructor(
3 | public readonly value: string,
4 | public readonly start: number | undefined,
5 | public readonly end: number | undefined,
6 | public readonly children: readonly Group[]
7 | ) {}
8 |
9 | get values(): string[] | null {
10 | return (this.children.length === 0 ? [this] : this.children).map((g) => g.value)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/javascript/src/GroupBuilder.ts:
--------------------------------------------------------------------------------
1 | import { RegExpExecArray } from 'regexp-match-indices'
2 |
3 | import Group from './Group.js'
4 |
5 | export default class GroupBuilder {
6 | public source: string
7 | public capturing = true
8 | private readonly groupBuilders: GroupBuilder[] = []
9 |
10 | public add(groupBuilder: GroupBuilder) {
11 | this.groupBuilders.push(groupBuilder)
12 | }
13 |
14 | public build(match: RegExpExecArray, nextGroupIndex: () => number): Group {
15 | const groupIndex = nextGroupIndex()
16 | const children = this.groupBuilders.map((gb) => gb.build(match, nextGroupIndex))
17 | const value = match[groupIndex]
18 | const index = match.indices[groupIndex]
19 | const start = index ? index[0] : undefined
20 | const end = index ? index[1] : undefined
21 | return new Group(value, start, end, children)
22 | }
23 |
24 | public setNonCapturing() {
25 | this.capturing = false
26 | }
27 |
28 | get children() {
29 | return this.groupBuilders
30 | }
31 |
32 | public moveChildrenTo(groupBuilder: GroupBuilder) {
33 | this.groupBuilders.forEach((child) => groupBuilder.add(child))
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/javascript/src/RegularExpression.ts:
--------------------------------------------------------------------------------
1 | import Argument from './Argument.js'
2 | import ParameterType from './ParameterType.js'
3 | import ParameterTypeRegistry from './ParameterTypeRegistry.js'
4 | import TreeRegexp from './TreeRegexp.js'
5 | import { Expression } from './types.js'
6 |
7 | export default class RegularExpression implements Expression {
8 | private readonly treeRegexp: TreeRegexp
9 |
10 | constructor(
11 | public readonly regexp: RegExp,
12 | private readonly parameterTypeRegistry: ParameterTypeRegistry
13 | ) {
14 | this.treeRegexp = new TreeRegexp(regexp)
15 | }
16 |
17 | public match(text: string): readonly Argument[] | null {
18 | const group = this.treeRegexp.match(text)
19 | if (!group) {
20 | return null
21 | }
22 |
23 | const parameterTypes = this.treeRegexp.groupBuilder.children.map((groupBuilder) => {
24 | const parameterTypeRegexp = groupBuilder.source
25 |
26 | const parameterType = this.parameterTypeRegistry.lookupByRegexp(
27 | parameterTypeRegexp,
28 | this.regexp,
29 | text
30 | )
31 | return (
32 | parameterType ||
33 | new ParameterType(
34 | undefined,
35 | parameterTypeRegexp,
36 | String,
37 | (s) => (s === undefined ? null : s),
38 | false,
39 | false
40 | )
41 | )
42 | })
43 |
44 | return Argument.build(group, parameterTypes)
45 | }
46 |
47 | get source(): string {
48 | return this.regexp.source
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/javascript/src/index.ts:
--------------------------------------------------------------------------------
1 | import Argument from './Argument.js'
2 | import { Located, Node, NodeType, Token, TokenType } from './Ast.js'
3 | import CucumberExpression from './CucumberExpression.js'
4 | import CucumberExpressionGenerator from './CucumberExpressionGenerator.js'
5 | import ExpressionFactory from './ExpressionFactory.js'
6 | import GeneratedExpression from './GeneratedExpression.js'
7 | import Group from './Group.js'
8 | import ParameterType, { RegExps, StringOrRegExp } from './ParameterType.js'
9 | import ParameterTypeRegistry from './ParameterTypeRegistry.js'
10 | import RegularExpression from './RegularExpression.js'
11 | import { Expression } from './types.js'
12 |
13 | export {
14 | Argument,
15 | CucumberExpression,
16 | CucumberExpressionGenerator,
17 | Expression,
18 | ExpressionFactory,
19 | GeneratedExpression,
20 | Group,
21 | Located,
22 | Node,
23 | NodeType,
24 | ParameterType,
25 | ParameterTypeRegistry,
26 | RegExps,
27 | RegularExpression,
28 | StringOrRegExp,
29 | Token,
30 | TokenType,
31 | }
32 |
--------------------------------------------------------------------------------
/javascript/src/types.ts:
--------------------------------------------------------------------------------
1 | import Argument from './Argument.js'
2 | import ParameterType from './ParameterType.js'
3 |
4 | export interface DefinesParameterType {
5 | defineParameterType(parameterType: ParameterType): void
6 | }
7 |
8 | export interface Expression {
9 | readonly source: string
10 | match(text: string): readonly Argument[] | null
11 | }
12 |
13 | export type ParameterInfo = {
14 | /**
15 | * The string representation of the original ParameterType#type property
16 | */
17 | type: string | null
18 | /**
19 | * The parameter type name
20 | */
21 | name: string
22 | /**
23 | * The number of times this name has been used so far
24 | */
25 | count: number
26 | }
27 |
--------------------------------------------------------------------------------
/javascript/stryker.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
3 | "packageManager": "npm",
4 | "reporters": [
5 | "html",
6 | "clear-text",
7 | "progress",
8 | "json"
9 | ],
10 | "cleanTempDir": false,
11 | "testRunner": "mocha",
12 | "buildCommand": "npm run build:cjs",
13 | "mochaOptions": {
14 | "spec": [ "dist/cjs/test/**/*.js" ]
15 | },
16 | "coverageAnalysis": "perTest"
17 | }
18 |
--------------------------------------------------------------------------------
/javascript/test/ArgumentTest.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | import Argument from '../src/Argument.js'
4 | import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
5 | import TreeRegexp from '../src/TreeRegexp.js'
6 |
7 | describe('Argument', () => {
8 | it('exposes getParameterTypeName()', () => {
9 | const treeRegexp = new TreeRegexp('three (.*) mice')
10 | const parameterTypeRegistry = new ParameterTypeRegistry()
11 | const group = treeRegexp.match('three blind mice')!
12 | const args = Argument.build(group, [parameterTypeRegistry.lookupByTypeName('string')!])
13 | const argument = args[0]
14 | assert.strictEqual(argument.getParameterType().name, 'string')
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/javascript/test/CombinatorialGeneratedExpressionFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 |
3 | import CombinatorialGeneratedExpressionFactory from '../src/CombinatorialGeneratedExpressionFactory.js'
4 | import ParameterType from '../src/ParameterType.js'
5 |
6 | describe('CucumberExpressionGenerator', () => {
7 | it('generates multiple expressions', () => {
8 | const parameterTypeCombinations = [
9 | [
10 | new ParameterType('color', /red|blue|yellow/, null, (s) => s, false, true),
11 | new ParameterType('csscolor', /red|blue|yellow/, null, (s) => s, false, true),
12 | ],
13 | [
14 | new ParameterType('date', /\d{4}-\d{2}-\d{2}/, null, (s) => s, false, true),
15 | new ParameterType('datetime', /\d{4}-\d{2}-\d{2}/, null, (s) => s, false, true),
16 | new ParameterType('timestamp', /\d{4}-\d{2}-\d{2}/, null, (s) => s, false, true),
17 | ],
18 | ]
19 |
20 | const factory = new CombinatorialGeneratedExpressionFactory(
21 | 'I bought a {{0}} ball on {{1}}',
22 | parameterTypeCombinations
23 | )
24 | const expressions = factory.generateExpressions().map((ge) => ge.source)
25 | assert.deepStrictEqual(expressions, [
26 | 'I bought a {color} ball on {date}',
27 | 'I bought a {color} ball on {datetime}',
28 | 'I bought a {color} ball on {timestamp}',
29 | 'I bought a {csscolor} ball on {date}',
30 | 'I bought a {csscolor} ball on {datetime}',
31 | 'I bought a {csscolor} ball on {timestamp}',
32 | ])
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/javascript/test/CucumberExpressionParserTest.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import fs from 'fs'
3 | import { glob } from 'glob'
4 | import yaml from 'js-yaml'
5 |
6 | import CucumberExpressionError from '../src/CucumberExpressionError.js'
7 | import CucumberExpressionParser from '../src/CucumberExpressionParser.js'
8 | import { testDataDir } from './testDataDir.js'
9 |
10 | type Expectation = {
11 | expression: string
12 | expected_ast?: unknown
13 | exception?: string
14 | }
15 |
16 | describe('CucumberExpressionParser', () => {
17 | for (const path of glob.sync(`${testDataDir}/cucumber-expression/parser/*.yaml`)) {
18 | const expectation = yaml.load(fs.readFileSync(path, 'utf-8')) as Expectation
19 | it(`parses ${path}`, () => {
20 | const parser = new CucumberExpressionParser()
21 | if (expectation.expected_ast !== undefined) {
22 | const node = parser.parse(expectation.expression)
23 | assert.deepStrictEqual(
24 | JSON.parse(JSON.stringify(node)), // Removes type information.
25 | expectation.expected_ast
26 | )
27 | } else if (expectation.exception !== undefined) {
28 | assert.throws(() => {
29 | parser.parse(expectation.expression)
30 | }, new CucumberExpressionError(expectation.exception))
31 | } else {
32 | throw new Error(
33 | `Expectation must have expected or exception: ${JSON.stringify(expectation)}`
34 | )
35 | }
36 | })
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/javascript/test/CucumberExpressionTokenizerTest.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import fs from 'fs'
3 | import { glob } from 'glob'
4 | import yaml from 'js-yaml'
5 |
6 | import CucumberExpressionError from '../src/CucumberExpressionError.js'
7 | import CucumberExpressionTokenizer from '../src/CucumberExpressionTokenizer.js'
8 | import { testDataDir } from './testDataDir.js'
9 |
10 | type Expectation = {
11 | expression: string
12 | expected_tokens?: unknown
13 | exception?: string
14 | }
15 |
16 | describe('CucumberExpressionTokenizer', () => {
17 | for (const path of glob.sync(`${testDataDir}/cucumber-expression/tokenizer/*.yaml`)) {
18 | const expectation = yaml.load(fs.readFileSync(path, 'utf-8')) as Expectation
19 | it(`tokenizes ${path}`, () => {
20 | const tokenizer = new CucumberExpressionTokenizer()
21 | if (expectation.expected_tokens !== undefined) {
22 | const tokens = tokenizer.tokenize(expectation.expression)
23 | assert.deepStrictEqual(
24 | JSON.parse(JSON.stringify(tokens)), // Removes type information.
25 | expectation.expected_tokens
26 | )
27 | } else if (expectation.exception !== undefined) {
28 | assert.throws(() => {
29 | tokenizer.tokenize(expectation.expression)
30 | }, new CucumberExpressionError(expectation.exception))
31 | } else {
32 | throw new Error(
33 | `Expectation must have expected_tokens or exception: ${JSON.stringify(expectation)}`
34 | )
35 | }
36 | })
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/javascript/test/CucumberExpressionTransformationTest.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import fs from 'fs'
3 | import { glob } from 'glob'
4 | import yaml from 'js-yaml'
5 |
6 | import CucumberExpression from '../src/CucumberExpression.js'
7 | import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
8 | import { testDataDir } from './testDataDir.js'
9 |
10 | type Expectation = {
11 | expression: string
12 | expected_regex: string
13 | }
14 |
15 | describe('CucumberExpression', () => {
16 | for (const path of glob.sync(`${testDataDir}/cucumber-expression/transformation/*.yaml`)) {
17 | const expectation = yaml.load(fs.readFileSync(path, 'utf-8')) as Expectation
18 | it(`transforms ${path}`, () => {
19 | const parameterTypeRegistry = new ParameterTypeRegistry()
20 | const expression = new CucumberExpression(expectation.expression, parameterTypeRegistry)
21 | assert.deepStrictEqual(expression.regexp.source, expectation.expected_regex)
22 | })
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/javascript/test/ExpressionFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | import CucumberExpression from '../src/CucumberExpression.js'
4 | import ExpressionFactory from '../src/ExpressionFactory.js'
5 | import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
6 | import RegularExpression from '../src/RegularExpression.js'
7 |
8 | describe('ExpressionFactory', () => {
9 | let expressionFactory: ExpressionFactory
10 | beforeEach(() => {
11 | expressionFactory = new ExpressionFactory(new ParameterTypeRegistry())
12 | })
13 |
14 | it('creates a RegularExpression', () => {
15 | assert.strictEqual(expressionFactory.createExpression(/x/).constructor, RegularExpression)
16 | })
17 |
18 | it('creates a CucumberExpression', () => {
19 | assert.strictEqual(expressionFactory.createExpression('x').constructor, CucumberExpression)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/javascript/test/ParameterTypeRegistryTest.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 |
3 | import ParameterType from '../src/ParameterType.js'
4 | import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
5 |
6 | class Name {
7 | constructor(public readonly name: string) {}
8 | }
9 | class Person {
10 | constructor(public readonly name: string) {}
11 | }
12 | class Place {
13 | constructor(public readonly name: string) {}
14 | }
15 |
16 | const CAPITALISED_WORD = /[A-Z]+\w+/
17 |
18 | describe('ParameterTypeRegistry', () => {
19 | let registry: ParameterTypeRegistry
20 | beforeEach(() => {
21 | registry = new ParameterTypeRegistry()
22 | })
23 |
24 | it('does not allow more than one preferential parameter type for each regexp', () => {
25 | registry.defineParameterType(
26 | new ParameterType('name', CAPITALISED_WORD, Name, (s) => new Name(s), true, true)
27 | )
28 | registry.defineParameterType(
29 | new ParameterType('person', CAPITALISED_WORD, Person, (s) => new Person(s), true, false)
30 | )
31 | try {
32 | registry.defineParameterType(
33 | new ParameterType('place', CAPITALISED_WORD, Place, (s) => new Place(s), true, true)
34 | )
35 | throw new Error('Should have failed')
36 | } catch (err) {
37 | assert.strictEqual(
38 | err.message,
39 | `There can only be one preferential parameter type per regexp. The regexp ${CAPITALISED_WORD} is used for two preferential parameter types, {name} and {place}`
40 | )
41 | }
42 | })
43 |
44 | it('looks up preferential parameter type by regexp', () => {
45 | const name = new ParameterType('name', /[A-Z]+\w+/, null, (s) => new Name(s), true, false)
46 | const person = new ParameterType('person', /[A-Z]+\w+/, null, (s) => new Person(s), true, true)
47 | const place = new ParameterType('place', /[A-Z]+\w+/, null, (s) => new Place(s), true, false)
48 |
49 | registry.defineParameterType(name)
50 | registry.defineParameterType(person)
51 | registry.defineParameterType(place)
52 |
53 | assert.strictEqual(
54 | registry.lookupByRegexp('[A-Z]+\\w+', /([A-Z]+\w+) and ([A-Z]+\w+)/, 'Lisa and Bob'),
55 | person
56 | )
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/javascript/test/ParameterTypeTest.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 |
3 | import ParameterType from '../src/ParameterType.js'
4 | import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
5 |
6 | describe('ParameterType', () => {
7 | it('does not allow ignore flag on regexp', () => {
8 | assert.throws(
9 | () => new ParameterType('case-insensitive', /[a-z]+/i, String, (s) => s, true, true),
10 | { message: "ParameterType Regexps can't use flag 'i'" }
11 | )
12 | })
13 |
14 | it('has a type name for {int}', () => {
15 | const r = new ParameterTypeRegistry()
16 | const t = r.lookupByTypeName('int')!
17 | // @ts-ignore
18 | assert.strictEqual(t.type.name, 'Number')
19 | })
20 |
21 | it('has a type name for {bigint}', () => {
22 | const r = new ParameterTypeRegistry()
23 | const t = r.lookupByTypeName('biginteger')!
24 | // @ts-ignore
25 | assert.strictEqual(t.type.name, 'BigInt')
26 | })
27 |
28 | it('has a type name for {word}', () => {
29 | const r = new ParameterTypeRegistry()
30 | const t = r.lookupByTypeName('word')!
31 | // @ts-ignore
32 | assert.strictEqual(t.type.name, 'String')
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/javascript/test/testDataDir.ts:
--------------------------------------------------------------------------------
1 | export const testDataDir = process.env.CUCUMBER_EXPRESSIONS_TEST_DATA_DIR || '../testdata'
2 |
--------------------------------------------------------------------------------
/javascript/tsconfig.build-cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.build.json",
3 | "compilerOptions": {
4 | "outDir": "dist/cjs",
5 | "module": "CommonJS",
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/javascript/tsconfig.build-esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.build.json",
3 | "compilerOptions": {
4 | "module": "ES6",
5 | "outDir": "dist/esm"
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/javascript/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "declaration": true,
6 | "declarationMap": true,
7 | "sourceMap": true,
8 | "rootDir": ".",
9 | "noEmit": false
10 | },
11 | "include": [
12 | "src",
13 | "test"
14 | ],
15 | }
--------------------------------------------------------------------------------
/javascript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "declaration": true,
5 | "sourceMap": true,
6 | "allowJs": false,
7 | "resolveJsonModule": true,
8 | "esModuleInterop": true,
9 | "noImplicitAny": true,
10 | "downlevelIteration": true,
11 | "skipLibCheck": true,
12 | "strictNullChecks": true,
13 | "experimentalDecorators": true,
14 | "module": "ESNext",
15 | "lib": [
16 | "ES2022"
17 | ],
18 | "target": "ES2022",
19 | "moduleResolution": "node",
20 | "allowSyntheticDefaultImports": true,
21 | "noEmit": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/python/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | venv/
3 | # Pytest
4 | .pytest_cache
5 |
--------------------------------------------------------------------------------
/python/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/psf/black
3 | rev: 22.3.0
4 | hooks:
5 | - id: black
6 | - repo: https://github.com/PyCQA/flake8
7 | rev: 4.0.1
8 | hooks:
9 | - id: flake8
10 | args: ['--max-line-length=130','--ignore=E203,W503']
11 |
--------------------------------------------------------------------------------
/python/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Cucumber Ltd and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | # Cucumber Expressions for Python
2 |
3 | [The main docs are here](https://github.com/cucumber/cucumber-expressions#readme).
4 |
5 | ## Build system
6 |
7 | This project uses [Poetry](https://python-poetry.org/) as its build system.
8 | In order to develop on this project, please install Poetry as per your system's instructions on the link above.
9 |
10 | ## Tests
11 |
12 | The test suite uses `pytest` as its testing Framework.
13 |
14 |
15 | ### Preparing to run the tests
16 |
17 | In order to set up your dev environment, run the following command from this project's directory:
18 |
19 | ``` python
20 | poetry install
21 | ```
22 | It will install all package and development requirements, and once that is done it will do a dev-install of the source code.
23 |
24 | You only need to run it once, code changes will propagate directly and do not require running the install again.
25 |
26 |
27 | ### Running the tests
28 |
29 | `pytest` automatically picks up files in the current directory or any subdirectories that have the prefix or suffix of `test_*.py`.
30 | Test function names must start with `test*`.
31 | Test class names must start with `Test*`.
32 |
33 | To run all tests:
34 |
35 | ``` python
36 | poetry run pytest
37 | ```
38 |
39 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cucumber/cucumber-expressions/51272cff745a7065f794768ab8a3d9630ea770eb/python/cucumber_expressions/__init__.py
--------------------------------------------------------------------------------
/python/cucumber_expressions/argument.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Optional, List
4 |
5 | from cucumber_expressions.group import Group
6 | from cucumber_expressions.parameter_type import ParameterType
7 | from cucumber_expressions.tree_regexp import TreeRegexp
8 | from cucumber_expressions.errors import CucumberExpressionError
9 |
10 |
11 | class Argument:
12 | def __init__(self, group, parameter_type):
13 | self._group: Group = group
14 | self.parameter_type: ParameterType = parameter_type
15 |
16 | @staticmethod
17 | def build(
18 | tree_regexp: TreeRegexp, text: str, parameter_types: List
19 | ) -> Optional[List[Argument]]:
20 | match_group = tree_regexp.match(text)
21 | if not match_group:
22 | return None
23 |
24 | arg_groups = match_group.children
25 |
26 | if len(arg_groups) != len(parameter_types):
27 | raise CucumberExpressionError(
28 | f"Group has {len(arg_groups)} capture groups, but there were {len(parameter_types)} parameter types"
29 | )
30 |
31 | return [
32 | Argument(arg_group, parameter_type)
33 | for parameter_type, arg_group in zip(parameter_types, arg_groups)
34 | ]
35 |
36 | @property
37 | def value(self):
38 | return self.parameter_type.transform(self.group.values if self.group else None)
39 |
40 | @property
41 | def group(self):
42 | return self._group
43 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/combinatorial_generated_expression_factory.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from cucumber_expressions.generated_expression import GeneratedExpression
4 | from cucumber_expressions.parameter_type import ParameterType
5 |
6 | # 256 generated expressions ought to be enough for anybody
7 | MAX_EXPRESSIONS = 256
8 |
9 |
10 | class CombinatorialGeneratedExpressionFactory:
11 | def __init__(self, expression_template, parameter_type_combinations):
12 | self.expression_template = expression_template
13 | self.parameter_type_combinations = parameter_type_combinations
14 |
15 | def generate_expressions(self) -> List[GeneratedExpression]:
16 | generated_expressions = []
17 | self.generate_permutations(generated_expressions, 0, [])
18 | return generated_expressions
19 |
20 | def generate_permutations(
21 | self,
22 | generated_expressions: List[GeneratedExpression],
23 | depth: int,
24 | current_parameter_types: List[ParameterType],
25 | ):
26 | if len(generated_expressions) >= MAX_EXPRESSIONS:
27 | return
28 | if depth == len(self.parameter_type_combinations):
29 | generated_expressions.append(
30 | GeneratedExpression(self.expression_template, current_parameter_types)
31 | )
32 | return
33 | for parameter_type_combination in self.parameter_type_combinations[depth]:
34 | if len(generated_expressions) >= MAX_EXPRESSIONS:
35 | return
36 | new_current_parameter_types = current_parameter_types.copy()
37 | new_current_parameter_types.append(parameter_type_combination)
38 | self.generate_permutations(
39 | generated_expressions, depth + 1, new_current_parameter_types
40 | )
41 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/generated_expression.py:
--------------------------------------------------------------------------------
1 | class GeneratedExpression:
2 | def __init__(self, expression_template: str, parameter_types):
3 | self.expression_template = expression_template
4 | self.parameter_types = parameter_types
5 | self.usage_by_type_name = {}
6 |
7 | @property
8 | def source(self):
9 | return self.expression_template % tuple(p.name for p in self.parameter_types)
10 |
11 | @property
12 | def parameter_names(self):
13 | return [self.get_parameter_name(t.name) for t in self.parameter_types]
14 |
15 | def get_parameter_name(self, type_name):
16 | count = self.usage_by_type_name.get(type_name) or 0
17 | count = count + 1
18 | self.usage_by_type_name[type_name] = count
19 | return type_name if count == 1 else f"{type_name}{count}"
20 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/group.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import List
4 |
5 |
6 | class Group:
7 | def __init__(self, value: str, start: int, end: int, children: List[Group]):
8 | self._children = children
9 | self._value = value
10 | self._start = start
11 | self._end = end
12 |
13 | @property
14 | def value(self):
15 | return self._value
16 |
17 | @property
18 | def start(self):
19 | return self._start
20 |
21 | @property
22 | def end(self):
23 | return self._end
24 |
25 | @property
26 | def children(self):
27 | return self._children
28 |
29 | @property
30 | def values(self):
31 | return [v.value for v in self.children or [self]]
32 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/group_builder.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import List
4 |
5 | from cucumber_expressions.group import Group
6 |
7 |
8 | class GroupBuilder:
9 | def __init__(self):
10 | self._group_builders: List[GroupBuilder] = []
11 | self._capturing = True
12 | self._source: str = ""
13 | self._end_index = None
14 | self._children: List[GroupBuilder] = []
15 |
16 | def add(self, group_builder: GroupBuilder):
17 | self._group_builders.append(group_builder)
18 |
19 | def build(self, match, group_indices) -> Group:
20 | group_index = next(group_indices)
21 | children: List[Group] = [
22 | gb.build(match, group_indices) for gb in self._group_builders
23 | ]
24 | return Group(
25 | value=match.group(group_index),
26 | start=match.regs[group_index][0],
27 | end=match.regs[group_index][1],
28 | children=children,
29 | )
30 |
31 | def move_children_to(self, group_builder: GroupBuilder) -> None:
32 | for child in self._group_builders:
33 | group_builder.add(child)
34 |
35 | @property
36 | def capturing(self):
37 | return self._capturing
38 |
39 | @capturing.setter
40 | def capturing(self, value: bool):
41 | self._capturing = value
42 |
43 | @property
44 | def children(self) -> list[GroupBuilder]:
45 | return self._group_builders
46 |
47 | @property
48 | def source(self) -> str:
49 | return self._source
50 |
51 | @source.setter
52 | def source(self, source: str):
53 | self._source = source
54 |
--------------------------------------------------------------------------------
/python/cucumber_expressions/regular_expression.py:
--------------------------------------------------------------------------------
1 | import re
2 | from typing import Optional, List
3 |
4 | from cucumber_expressions.argument import Argument
5 | from cucumber_expressions.parameter_type import ParameterType
6 | from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
7 | from cucumber_expressions.tree_regexp import TreeRegexp
8 |
9 |
10 | class RegularExpression:
11 | """Creates a new instance. Use this when the transform types are not known in advance,
12 | and should be determined by the regular expression's capture groups. Use this with
13 | dynamically typed languages."""
14 |
15 | def __init__(
16 | self, expression_regexp, parameter_type_registry: ParameterTypeRegistry
17 | ):
18 | """Creates a new instance. Use this when the transform types are not known in advance,
19 | and should be determined by the regular expression's capture groups. Use this with
20 | dynamically typed languages.
21 | :param expression_regexp: the regular expression to use
22 | :type expression_regexp: Pattern
23 | :param parameter_type_registry: used to look up parameter types
24 | :type parameter_type_registry: ParameterTypeRegistry
25 | """
26 | self.expression_regexp = re.compile(expression_regexp)
27 | self.parameter_type_registry = parameter_type_registry
28 | self.tree_regexp: TreeRegexp = TreeRegexp(self.expression_regexp.pattern)
29 |
30 | def match(self, text) -> Optional[List[Argument]]:
31 | return Argument.build(
32 | self.tree_regexp, text, list(self.generate_parameter_types(text))
33 | )
34 |
35 | def generate_parameter_types(self, text):
36 | for group_builder in self.tree_regexp.group_builder.children:
37 | parameter_type_regexp = group_builder.source
38 | possible_regexp = self.parameter_type_registry.lookup_by_regexp(
39 | parameter_type_regexp, self.expression_regexp, text
40 | )
41 | yield possible_regexp or ParameterType(
42 | None, parameter_type_regexp, str, lambda *s: s[0], False, False
43 | )
44 |
45 | @property
46 | def regexp(self):
47 | return self.expression_regexp.pattern
48 |
--------------------------------------------------------------------------------
/python/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "cucumber-expressions"
3 | version = "18.0.1"
4 | description = "Cucumber Expressions - a simpler alternative to Regular Expressions"
5 | authors = ["Jason Allen "]
6 | license = "MIT"
7 |
8 | readme = "README.md"
9 |
10 | packages = [
11 | { include = "cucumber_expressions"}
12 | ]
13 | include = [
14 | { path = "tests", format = "sdist" }
15 | ]
16 |
17 | homepage = "https://github.com/cucumber/cucumber-expressions"
18 | repository = "https://github.com/cucumber/cucumber-expressions"
19 | documentation = "https://github.com/cucumber/cucumber-expressions"
20 |
21 | keywords = ["BDD", "testing", "cucumber", "expressions"]
22 |
23 | classifiers = [
24 | "Development Status :: 3 - Alpha",
25 | "Environment :: Console",
26 | "Intended Audience :: Developers",
27 | "Operating System :: OS Independent",
28 | "Programming Language :: Python :: 3",
29 | "Programming Language :: Python :: 3.8",
30 | "Programming Language :: Python :: 3.9",
31 | "Programming Language :: Python :: 3.10",
32 | "Programming Language :: Python :: 3.11",
33 | "Programming Language :: Python :: 3.12",
34 | "Programming Language :: Python :: Implementation :: CPython",
35 | "Programming Language :: Python :: Implementation :: PyPy",
36 | "Topic :: Software Development :: Testing",
37 | "Topic :: Software Development :: Libraries :: Python Modules",
38 | "License :: OSI Approved :: MIT License",
39 | ]
40 |
41 | [tool.poetry.dependencies]
42 | python = "^3.8"
43 |
44 | [tool.poetry.dev-dependencies]
45 | pre-commit = "^3.3"
46 | pytest = "^8.0.0"
47 | PyYAML = "^6.0"
48 |
49 | [build-system]
50 | requires = ["poetry-core>=1.0.0"]
51 | build-backend = "poetry.core.masonry.api"
52 |
53 | [tool.isort]
54 | py_version = 38
55 | profile = "black"
56 | force_single_line = true
57 | combine_as_imports = true
58 | lines_between_types = 1
59 | lines_after_imports = 2
60 | src_paths = ["cucumber_expressions", "tests"]
61 |
62 |
63 | [tool.black]
64 | target-version = ['py38']
65 |
--------------------------------------------------------------------------------
/python/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cucumber/cucumber-expressions/51272cff745a7065f794768ab8a3d9630ea770eb/python/tests/__init__.py
--------------------------------------------------------------------------------
/python/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import yaml
3 |
4 |
5 | @pytest.fixture
6 | def load_test_yamls(request) -> dict:
7 | """Opens a given test yaml file"""
8 | with open(request.param, encoding="UTF-8") as stream:
9 | yield yaml.safe_load(stream)
10 |
--------------------------------------------------------------------------------
/python/tests/definitions.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | MODULE_ROOT_DIR = Path(Path(__file__).resolve()).parent
4 | PROJECT_ROOT_DIR = Path(MODULE_ROOT_DIR).parent.parent
5 | TESTDATA_ROOT_DIR = Path(PROJECT_ROOT_DIR) / "testdata"
6 |
--------------------------------------------------------------------------------
/python/tests/test_argument.py:
--------------------------------------------------------------------------------
1 | from cucumber_expressions.argument import Argument
2 | from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
3 | from cucumber_expressions.tree_regexp import TreeRegexp
4 |
5 |
6 | class TestArgument:
7 | def test_exposes_parameter_type(self):
8 | tree_regexp = TreeRegexp(r"three (.*) mice")
9 | parameter_type_registry = ParameterTypeRegistry()
10 | arguments = Argument.build(
11 | tree_regexp,
12 | "three blind mice",
13 | [parameter_type_registry.lookup_by_type_name("string")],
14 | )
15 | assert arguments[0].parameter_type.name == "string"
16 |
--------------------------------------------------------------------------------
/python/tests/test_expression_parser.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from tests.definitions import TESTDATA_ROOT_DIR
4 |
5 | import pytest
6 |
7 | from cucumber_expressions.expression_parser import (
8 | CucumberExpressionParser,
9 | )
10 |
11 |
12 | def get_expectation_yamls():
13 | yaml_dir = Path(TESTDATA_ROOT_DIR) / "cucumber-expression" / "parser"
14 | return [
15 | Path(yaml_dir) / file
16 | for file in Path(yaml_dir).iterdir()
17 | if file.suffix == ".yaml"
18 | ]
19 |
20 |
21 | class TestCucumberExpression:
22 | @pytest.mark.parametrize("load_test_yamls", get_expectation_yamls(), indirect=True)
23 | def test_cucumber_expression_matches(self, load_test_yamls: dict):
24 | expectation = load_test_yamls
25 | parser = CucumberExpressionParser()
26 | if "exception" in expectation:
27 | with pytest.raises(Exception) as excinfo:
28 | parser.parse(expectation["expression"])
29 | assert excinfo.value.args[0] == expectation["exception"]
30 | else:
31 | node = parser.parse(expectation["expression"])
32 | assert node.to_json() == expectation["expected_ast"]
33 |
--------------------------------------------------------------------------------
/python/tests/test_expression_tokenizer.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from tests.definitions import TESTDATA_ROOT_DIR
4 |
5 | import pytest
6 |
7 | from cucumber_expressions.expression_tokenizer import (
8 | CucumberExpressionTokenizer,
9 | )
10 | from cucumber_expressions.errors import (
11 | CantEscape,
12 | TheEndOfLineCannotBeEscaped,
13 | )
14 |
15 |
16 | def get_expectation_yamls():
17 | yaml_dir = Path(TESTDATA_ROOT_DIR) / "cucumber-expression" / "tokenizer"
18 | return [
19 | Path(yaml_dir) / file
20 | for file in Path(yaml_dir).iterdir()
21 | if file.suffix == ".yaml"
22 | ]
23 |
24 |
25 | class TestCucumberExpression:
26 | @pytest.mark.parametrize("load_test_yamls", get_expectation_yamls(), indirect=True)
27 | def test_cucumber_expression_tokenizes(self, load_test_yamls: dict):
28 | expectation = load_test_yamls
29 | tokenizer = CucumberExpressionTokenizer()
30 | if "exception" in expectation:
31 | with pytest.raises((CantEscape, TheEndOfLineCannotBeEscaped)) as excinfo:
32 | tokenizer.tokenize(expectation["expression"])
33 | assert excinfo.value.args[0] == expectation["exception"]
34 | else:
35 | tokens = tokenizer.tokenize(expectation["expression"], to_json=True)
36 | assert tokens == expectation["expected_tokens"]
37 |
--------------------------------------------------------------------------------
/python/tests/test_expression_transformation.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from tests.definitions import TESTDATA_ROOT_DIR
6 |
7 | from cucumber_expressions.expression import CucumberExpression
8 | from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry
9 |
10 |
11 | def get_expectation_yamls():
12 | yaml_dir = Path(TESTDATA_ROOT_DIR) / "cucumber-expression" / "transformation"
13 | return [
14 | Path(yaml_dir) / file
15 | for file in Path(yaml_dir).iterdir()
16 | if file.suffix == ".yaml"
17 | ]
18 |
19 |
20 | class TestCucumberExpression:
21 | @pytest.mark.parametrize("load_test_yamls", get_expectation_yamls(), indirect=True)
22 | def test_cucumber_expression_transforms(self, load_test_yamls: dict):
23 | expectation = load_test_yamls
24 | parameter_registry = ParameterTypeRegistry()
25 | expression = CucumberExpression(expectation["expression"], parameter_registry)
26 | assert expression.regexp == expectation["expected_regex"]
27 |
--------------------------------------------------------------------------------
/ruby/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | coverage/
3 | acceptance/
4 | pkg/
5 | *.gem
6 | .deps
7 | *.iml
8 |
--------------------------------------------------------------------------------
/ruby/.rubocop.yml:
--------------------------------------------------------------------------------
1 | require:
2 | - rubocop-performance
3 | - rubocop-rake
4 | - rubocop-rspec
5 |
6 | inherit_from: .rubocop_todo.yml
7 |
8 | inherit_mode:
9 | merge:
10 | - Exclude
11 |
12 | AllCops:
13 | TargetRubyVersion: 2.5
14 | NewCops: enable
15 |
16 | # Disabled on our repo's to enable polyglot-release
17 | Gemspec/RequireMFA:
18 | Enabled: false
19 |
20 | Layout/LineLength:
21 | Max: 200
22 |
23 | # This is documented in this spec to showcase why it's not working and people shouldn't use it
24 | # cf:
25 | Lint/MixedRegexpCaptureTypes:
26 | Exclude:
27 | - 'spec/cucumber/cucumber_expressions/tree_regexp_spec.rb'
28 |
29 | Style/Documentation:
30 | Enabled: false
31 |
32 | Style/RegexpLiteral:
33 | EnforcedStyle: slashes
34 | AllowInnerSlashes: true
35 |
36 | RSpec/MessageSpies:
37 | EnforcedStyle: receive
38 |
--------------------------------------------------------------------------------
/ruby/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | gemspec
5 |
--------------------------------------------------------------------------------
/ruby/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Cucumber Ltd and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ruby/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $:.unshift File.expand_path('../lib', __FILE__)
4 |
5 | Dir['./rake/*.rb'].each do |f|
6 | require f
7 | end
8 |
9 | require 'rspec/core/rake_task'
10 | RSpec::Core::RakeTask.new(:spec)
11 |
12 | task default: :spec
13 |
--------------------------------------------------------------------------------
/ruby/VERSION:
--------------------------------------------------------------------------------
1 | 18.0.1
2 |
--------------------------------------------------------------------------------
/ruby/cucumber-cucumber-expressions.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | # frozen_string_literal: true
3 |
4 | version = File.read(File.expand_path('VERSION', __dir__)).strip
5 |
6 | Gem::Specification.new do |s|
7 | s.name = 'cucumber-cucumber-expressions'
8 | s.version = version
9 | s.authors = ['Aslak Hellesøy']
10 | s.description = 'Cucumber Expressions - a simpler alternative to Regular Expressions'
11 | s.summary = "cucumber-expressions-#{s.version}"
12 | s.email = 'cukes@googlegroups.com'
13 | s.homepage = 'https://github.com/cucumber/cucumber-expressions'
14 | s.platform = Gem::Platform::RUBY
15 | s.license = 'MIT'
16 | s.required_ruby_version = '>= 2.7'
17 |
18 | s.metadata = {
19 | 'bug_tracker_uri' => 'https://github.com/cucumber/cucumber/issues',
20 | 'changelog_uri' => 'https://github.com/cucumber/cucumber-expressions/blob/main/CHANGELOG.md',
21 | 'documentation_uri' => 'https://github.com/cucumber/cucumber-expressions#readme',
22 | 'mailing_list_uri' => 'https://community.smartbear.com/category/cucumber/discussions/cucumberos',
23 | 'source_code_uri' => 'https://github.com/cucumber/cucumber-expressions/tree/main/ruby',
24 | }
25 |
26 | s.add_runtime_dependency 'bigdecimal'
27 |
28 | s.add_development_dependency 'rake', '~> 13.1'
29 | s.add_development_dependency 'rspec', '~> 3.13'
30 | s.add_development_dependency 'rubocop', '~> 1.27.0'
31 | s.add_development_dependency 'rubocop-performance', '~> 1.7.0'
32 | s.add_development_dependency 'rubocop-rake', '~> 0.5.0'
33 | s.add_development_dependency 'rubocop-rspec', '~> 2.0.0'
34 |
35 | s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'LICENSE', 'README.md']
36 | s.rdoc_options = ['--charset=UTF-8']
37 | s.require_path = 'lib'
38 | end
39 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/argument.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/group'
4 | require 'cucumber/cucumber_expressions/errors'
5 |
6 | module Cucumber
7 | module CucumberExpressions
8 | class Argument
9 | attr_reader :group, :parameter_type
10 |
11 | def self.build(tree_regexp, text, parameter_types)
12 | group = tree_regexp.match(text)
13 | return nil if group.nil?
14 |
15 | arg_groups = group.children
16 |
17 | if arg_groups.length != parameter_types.length
18 | raise CucumberExpressionError.new(
19 | "Expression #{tree_regexp.regexp.inspect} has #{arg_groups.length} capture groups (#{arg_groups.map(&:value)}), " \
20 | "but there were #{parameter_types.length} parameter types (#{parameter_types.map(&:name)})"
21 | )
22 | end
23 |
24 | parameter_types.zip(arg_groups).map do |parameter_type, arg_group|
25 | Argument.new(arg_group, parameter_type)
26 | end
27 | end
28 |
29 | def initialize(group, parameter_type)
30 | @group, @parameter_type = group, parameter_type
31 | end
32 |
33 | def value(self_obj = :nil)
34 | raise 'No self_obj' if self_obj == :nil
35 |
36 | group_values = @group ? @group.values : nil
37 | @parameter_type.transform(self_obj, group_values)
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require('cucumber/cucumber_expressions/generated_expression')
4 |
5 | module Cucumber
6 | module CucumberExpressions
7 | class CombinatorialGeneratedExpressionFactory
8 | def initialize(expression_template, parameter_type_combinations)
9 | @expression_template = expression_template
10 | @parameter_type_combinations = parameter_type_combinations
11 | end
12 |
13 | def generate_expressions
14 | generated_expressions = []
15 | generate_permutations(generated_expressions, 0, [])
16 | generated_expressions
17 | end
18 |
19 | # 256 generated expressions ought to be enough for anybody
20 | MAX_EXPRESSIONS = 256
21 |
22 | def generate_permutations(generated_expressions, depth, current_parameter_types)
23 | return if generated_expressions.length >= MAX_EXPRESSIONS
24 |
25 | if depth == @parameter_type_combinations.length
26 | generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types)
27 | generated_expressions.push(generated_expression)
28 | return
29 | end
30 |
31 | (0...@parameter_type_combinations[depth].length).each do |i|
32 | # Avoid recursion if no elements can be added.
33 | break if generated_expressions.length >= MAX_EXPRESSIONS
34 |
35 | new_current_parameter_types = current_parameter_types.dup # clone
36 | new_current_parameter_types.push(@parameter_type_combinations[depth][i])
37 | generate_permutations(generated_expressions, depth + 1, new_current_parameter_types)
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/expression_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/errors'
4 | require 'cucumber/cucumber_expressions/cucumber_expression'
5 | require 'cucumber/cucumber_expressions/regular_expression'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | class ExpressionFactory
10 | def initialize(parameter_type_registry)
11 | @parameter_type_registry = parameter_type_registry
12 | end
13 |
14 | def create_expression(string_or_regexp)
15 | case string_or_regexp
16 | when String then CucumberExpression.new(string_or_regexp, @parameter_type_registry)
17 | when Regexp then RegularExpression.new(string_or_regexp, @parameter_type_registry)
18 | else
19 | raise CucumberExpressionError.new("Can't create an expression from #{string_or_regexp.inspect}")
20 | end
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/generated_expression.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Cucumber
4 | module CucumberExpressions
5 | class GeneratedExpression
6 | attr_reader :parameter_types
7 |
8 | def initialize(expression_template, parameters_types)
9 | @expression_template, @parameter_types = expression_template, parameters_types
10 | end
11 |
12 | def source
13 | sprintf(@expression_template, *@parameter_types.map(&:name))
14 | end
15 |
16 | def parameter_names
17 | usage_by_type_name = Hash.new(0)
18 | @parameter_types.map do |t|
19 | get_parameter_name(t.name, usage_by_type_name)
20 | end
21 | end
22 |
23 | private
24 |
25 | def get_parameter_name(type_name, usage_by_type_name)
26 | count = usage_by_type_name[type_name]
27 | count += 1
28 | usage_by_type_name[type_name] = count
29 | count == 1 ? type_name : "#{type_name}#{count}"
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/group.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Cucumber
4 | module CucumberExpressions
5 | class Group
6 | attr_reader :value, :start, :end, :children
7 |
8 | def initialize(value, start, _end, children)
9 | @value = value
10 | @start = start
11 | @end = _end
12 | @children = children
13 | end
14 |
15 | def values
16 | (children.empty? ? [self] : children).map(&:value)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/group_builder.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/group'
4 |
5 | module Cucumber
6 | module CucumberExpressions
7 | class GroupBuilder
8 | attr_accessor :source
9 |
10 | def initialize
11 | @group_builders = []
12 | @capturing = true
13 | end
14 |
15 | def add(group_builder)
16 | @group_builders.push(group_builder)
17 | end
18 |
19 | def build(match, group_indices)
20 | group_index = group_indices.next
21 | children = @group_builders.map { |gb| gb.build(match, group_indices) }
22 | Group.new(match[group_index], match.offset(group_index)[0], match.offset(group_index)[1], children)
23 | end
24 |
25 | def set_non_capturing!
26 | @capturing = false
27 | end
28 |
29 | def capturing?
30 | @capturing
31 | end
32 |
33 | def move_children_to(group_builder)
34 | @group_builders.each do |child|
35 | group_builder.add(child)
36 | end
37 | end
38 |
39 | def children
40 | @group_builders
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Cucumber
4 | module CucumberExpressions
5 | class ParameterTypeMatcher
6 | attr_reader :parameter_type
7 |
8 | def initialize(parameter_type, regexp, text, match_position = 0)
9 | @parameter_type, @regexp, @text = parameter_type, regexp, text
10 | @match = @regexp.match(@text, match_position)
11 | end
12 |
13 | def advance_to(new_match_position)
14 | (new_match_position...@text.length).each do |advanced_position|
15 | matcher = self.class.new(parameter_type, @regexp, @text, advanced_position)
16 | return matcher if matcher.find && matcher.full_word?
17 | end
18 |
19 | self.class.new(parameter_type, @regexp, @text, @text.length)
20 | end
21 |
22 | def find
23 | !@match.nil? && !group.empty?
24 | end
25 |
26 | def full_word?
27 | space_before_match_or_sentence_start? && space_after_match_or_sentence_end?
28 | end
29 |
30 | def start
31 | @match.begin(0)
32 | end
33 |
34 | def group
35 | @match.captures[0]
36 | end
37 |
38 | def <=>(other)
39 | pos_comparison = start <=> other.start
40 | return pos_comparison if pos_comparison != 0
41 |
42 | length_comparison = other.group.length <=> group.length
43 | return length_comparison if length_comparison != 0
44 |
45 | 0
46 | end
47 |
48 | private
49 |
50 | def space_before_match_or_sentence_start?
51 | match_begin = @match.begin(0)
52 | match_begin == 0 || @text[match_begin - 1].match(/\p{Z}|\p{P}|\p{S}/)
53 | end
54 |
55 | def space_after_match_or_sentence_end?
56 | match_end = @match.end(0)
57 | match_end == @text.length || @text[match_end].match(/\p{Z}|\p{P}|\p{S}/)
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/ruby/lib/cucumber/cucumber_expressions/regular_expression.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/argument'
4 | require 'cucumber/cucumber_expressions/parameter_type'
5 | require 'cucumber/cucumber_expressions/tree_regexp'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | class RegularExpression
10 | def initialize(expression_regexp, parameter_type_registry)
11 | @expression_regexp = expression_regexp
12 | @parameter_type_registry = parameter_type_registry
13 | @tree_regexp = TreeRegexp.new(@expression_regexp)
14 | end
15 |
16 | def match(text)
17 | parameter_types = @tree_regexp.group_builder.children.map do |group_builder|
18 | parameter_type_regexp = group_builder.source
19 | @parameter_type_registry.lookup_by_regexp(
20 | parameter_type_regexp,
21 | @expression_regexp,
22 | text
23 | ) || ParameterType.new(
24 | nil,
25 | parameter_type_regexp,
26 | String,
27 | ->(*s) { s[0] },
28 | false,
29 | false
30 | )
31 | end
32 |
33 | Argument.build(@tree_regexp, text, parameter_types)
34 | end
35 |
36 | def regexp
37 | @expression_regexp
38 | end
39 |
40 | def source
41 | @expression_regexp.source
42 | end
43 |
44 | def to_s
45 | regexp.inspect
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/argument_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/argument'
4 | require 'cucumber/cucumber_expressions/tree_regexp'
5 | require 'cucumber/cucumber_expressions/parameter_type_registry'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | describe Argument do
10 | it 'exposes parameter_type' do
11 | tree_regexp = TreeRegexp.new(/three (.*) mice/)
12 | parameter_type_registry = ParameterTypeRegistry.new
13 | arguments = described_class.build(tree_regexp, 'three blind mice', [parameter_type_registry.lookup_by_type_name('string')])
14 | argument = arguments[0]
15 | expect(argument.parameter_type.name).to eq('string')
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/parameter_type'
4 | require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'
5 |
6 | describe Cucumber::CucumberExpressions::CombinatorialGeneratedExpressionFactory do
7 | let(:klazz) { Class.new }
8 | let(:color_parameter_type) do
9 | Cucumber::CucumberExpressions::ParameterType.new('color', /red|blue|yellow/, klazz, ->(_) { klazz.new }, true, false)
10 | end
11 | let(:css_color_parameter_type) do
12 | Cucumber::CucumberExpressions::ParameterType.new('csscolor', /red|blue|yellow/, klazz, ->(_) { klazz.new }, true, false)
13 | end
14 | let(:date_parameter_type) do
15 | Cucumber::CucumberExpressions::ParameterType.new('date', /\d{4}-\d{2}-\d{2}/, klazz, ->(_) { klazz.new }, true, false)
16 | end
17 | let(:date_time_parameter_type) do
18 | Cucumber::CucumberExpressions::ParameterType.new('datetime', /\d{4}-\d{2}-\d{2}/, klazz, ->(_) { klazz.new }, true, false)
19 | end
20 | let(:timestamp_parameter_type) do
21 | Cucumber::CucumberExpressions::ParameterType.new('timestamp', /\d{4}-\d{2}-\d{2}/, klazz, ->(_) { klazz.new }, true, false)
22 | end
23 |
24 | it 'generates multiple expressions' do
25 | parameter_type_combinations = [
26 | [color_parameter_type, css_color_parameter_type],
27 | [date_parameter_type, date_time_parameter_type, timestamp_parameter_type]
28 | ]
29 |
30 | factory = described_class.new('I bought a {%s} ball on {%s}', parameter_type_combinations)
31 | expressions = factory.generate_expressions.map { |generated_expression| generated_expression.source }
32 | expect(expressions).to eq([
33 | 'I bought a {color} ball on {date}',
34 | 'I bought a {color} ball on {datetime}',
35 | 'I bought a {color} ball on {timestamp}',
36 | 'I bought a {csscolor} ball on {date}',
37 | 'I bought a {csscolor} ball on {datetime}',
38 | 'I bought a {csscolor} ball on {timestamp}',
39 | ])
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'yaml'
4 | require 'cucumber/cucumber_expressions/cucumber_expression_parser'
5 | require 'cucumber/cucumber_expressions/errors'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | describe CucumberExpressionParser do
10 | Dir['../testdata/cucumber-expression/parser/*.yaml'].each do |path|
11 | expectation = YAML.load_file(path)
12 | it "parses #{path}" do
13 | parser = described_class.new
14 | if expectation['exception']
15 | expect { parser.parse(expectation['expression']) }.to raise_error(expectation['exception'])
16 | else
17 | node = parser.parse(expectation['expression'])
18 | node_hash = node.to_hash
19 | expect(node_hash).to eq(expectation['expected_ast'])
20 | end
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'yaml'
4 | require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer'
5 | require 'cucumber/cucumber_expressions/errors'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | describe CucumberExpressionTokenizer do
10 | Dir['../testdata/cucumber-expression/tokenizer/*.yaml'].each do |path|
11 | expectation = YAML.load_file(path)
12 | it "tokenizes #{path}" do
13 | tokenizer = described_class.new
14 | if expectation['exception']
15 | expect { tokenizer.tokenize(expectation['expression']) }.to raise_error(expectation['exception'])
16 | else
17 | tokens = tokenizer.tokenize(expectation['expression'])
18 | token_hashes = tokens.map { |token| token.to_hash }
19 | expect(token_hashes).to eq(expectation['expected_tokens'])
20 | end
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_transformation_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'yaml'
4 | require 'cucumber/cucumber_expressions/cucumber_expression'
5 | require 'cucumber/cucumber_expressions/parameter_type_registry'
6 |
7 | module Cucumber
8 | module CucumberExpressions
9 | describe CucumberExpression do
10 | Dir['../testdata/cucumber-expression/transformation/*.yaml'].each do |path|
11 | expectation = YAML.load_file(path)
12 | it "transforms #{path}" do
13 | parameter_registry = ParameterTypeRegistry.new
14 | if expectation['exception']
15 | expect {
16 | cucumber_expression = described_class.new(expectation['expression'], parameter_registry)
17 | cucumber_expression.match(expectation['text'])
18 | }.to raise_error(expectation['exception'])
19 | else
20 | cucumber_expression = described_class.new(expectation['expression'], parameter_registry)
21 | matches = cucumber_expression.match(expectation['text'])
22 | values = matches.nil? ? nil : matches.map { |arg| arg.value(nil) }
23 | expect(values).to eq(expectation['expected_args'])
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/expression_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/expression_factory'
4 |
5 | module Cucumber
6 | module CucumberExpressions
7 | describe ExpressionFactory do
8 | before do
9 | @expression_factory = described_class.new(ParameterTypeRegistry.new)
10 | end
11 |
12 | it 'creates a RegularExpression' do
13 | expect(@expression_factory.create_expression(/x/).class).to eq(RegularExpression)
14 | end
15 |
16 | it 'creates a CucumberExpression' do
17 | expect(@expression_factory.create_expression('{int}').class).to eq(CucumberExpression)
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/ruby/spec/cucumber/cucumber_expressions/parameter_type_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'cucumber/cucumber_expressions/parameter_type'
4 |
5 | module Cucumber
6 | module CucumberExpressions
7 | describe ParameterType do
8 | it 'does not allow ignore flag on regexp' do
9 | expect do
10 | described_class.new('case-insensitive', /[a-z]+/i, String, ->(s) { s }, true, true)
11 | end.to raise_error(
12 | CucumberExpressionError,
13 | "ParameterType Regexps can't use option Regexp::IGNORECASE"
14 | )
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/allows-escaped-optional-parameter-types.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\({int})"
3 | text: "(3)"
4 | expected_args:
5 | - 3
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/allows-parameter-type-in-alternation-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: a/i{int}n/y
3 | text: i18n
4 | expected_args:
5 | - 18
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/allows-parameter-type-in-alternation-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: a/i{int}n/y
3 | text: a11y
4 | expected_args:
5 | - 11
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-allow-parameter-adjacent-to-alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int}st/nd/rd/th"
3 | text: 3rd
4 | expected_args:
5 | - 3
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-alternation-in-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three( brown/black) mice
3 | text: three brown/black mice
4 | exception: |-
5 | This Cucumber Expression has a problem at column 13:
6 |
7 | three( brown/black) mice
8 | ^
9 | An alternation can not be used inside an optional.
10 | If you did not mean to use an alternation you can use '\/' to escape the '/'. Otherwise rephrase your expression or consider using a regular expression instead.
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int}/x"
3 | text: '3'
4 | exception: |-
5 | This Cucumber Expression has a problem at column 6:
6 |
7 | {int}/x
8 | ^
9 | Alternative may not be empty.
10 | If you did not mean to use an alternative you can use '\/' to escape the '/'
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (brown)/black mice
3 | text: three brown mice
4 | exception: |-
5 | This Cucumber Expression has a problem at column 7:
6 |
7 | three (brown)/black mice
8 | ^-----^
9 | An alternative may not exclusively contain optionals.
10 | If you did not mean to use an optional you can use '\(' to escape the '('
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: x/{int}
3 | text: '3'
4 | exception: |-
5 | This Cucumber Expression has a problem at column 3:
6 |
7 | x/{int}
8 | ^
9 | Alternative may not be empty.
10 | If you did not mean to use an alternative you can use '\/' to escape the '/'
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-alternation-with-empty-alternative.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three brown//black mice
3 | text: three brown mice
4 | exception: |-
5 | This Cucumber Expression has a problem at column 13:
6 |
7 | three brown//black mice
8 | ^
9 | Alternative may not be empty.
10 | If you did not mean to use an alternative you can use '\/' to escape the '/'
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-empty-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three () mice
3 | text: three brown mice
4 | exception: |-
5 | This Cucumber Expression has a problem at column 7:
6 |
7 | three () mice
8 | ^^
9 | An optional must contain some text.
10 | If you did not mean to use an optional you can use '\(' to escape the '('
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-nested-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "(a(b))"
3 | exception: |-
4 | This Cucumber Expression has a problem at column 3:
5 |
6 | (a(b))
7 | ^-^
8 | An optional may not contain an other optional.
9 | If you did not mean to use an optional type you can use '\(' to escape the '('. For more complicated expressions consider using a regular expression instead.
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-optional-parameter-types.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "({int})"
3 | text: '3'
4 | exception: |-
5 | This Cucumber Expression has a problem at column 2:
6 |
7 | ({int})
8 | ^---^
9 | An optional may not contain a parameter type.
10 | If you did not mean to use an parameter type you can use '\{' to escape the '{'
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-parameter-name-with-reserved-characters.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{(string)}"
3 | text: something
4 | exception: |-
5 | This Cucumber Expression has a problem at column 2:
6 |
7 | {(string)}
8 | ^
9 | Parameter names may not contain '{', '}', '(', ')', '\' or '/'.
10 | Did you mean to use a regular expression?
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-unfinished-parenthesis-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (exceptionally\) {string\} mice
3 | exception: |-
4 | This Cucumber Expression has a problem at column 24:
5 |
6 | three (exceptionally\) {string\} mice
7 | ^
8 | The '{' does not have a matching '}'.
9 | If you did not intend to use a parameter you can use '\{' to escape the a parameter
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-unfinished-parenthesis-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (exceptionally\) {string} mice
3 | exception: |-
4 | This Cucumber Expression has a problem at column 7:
5 |
6 | three (exceptionally\) {string} mice
7 | ^
8 | The '(' does not have a matching ')'.
9 | If you did not intend to use optional text you can use '\(' to escape the optional text
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-allow-unfinished-parenthesis-3.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three ((exceptionally\) strong) mice
3 | exception: |-
4 | This Cucumber Expression has a problem at column 7:
5 |
6 | three ((exceptionally\) strong) mice
7 | ^
8 | The '(' does not have a matching ')'.
9 | If you did not intend to use optional text you can use '\(' to escape the optional text
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/does-not-match-misquoted-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three "blind' mice
4 | expected_args:
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/doesnt-match-float-as-int.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int}"
3 | text: '1.22'
4 | expected_args:
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: mice/rats and rats\/mice
3 | text: rats and rats/mice
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-anonymous-parameter-type.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{}"
3 | text: '0.22'
4 | expected_args:
5 | - '0.22'
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-bigdecimal.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{bigdecimal}"
3 | text: '3.1415926535897932384626433832795028841971693993751'
4 | expected_args:
5 | - '3.1415926535897932384626433832795028841971693993751'
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-biginteger.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{biginteger}"
3 | text: '31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679'
4 | expected_args:
5 | - '31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679'
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-byte.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{byte}"
3 | text: '127'
4 | expected_args:
5 | - 127
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} and {string} mice
3 | text: three "" and "handsome" mice
4 | expected_args:
5 | - ''
6 | - handsome
7 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double-quoted-empty-string-as-empty-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three "" mice
4 | expected_args:
5 | - ''
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double-quoted-string-with-escaped-double-quote.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three "bl\"nd" mice
4 | expected_args:
5 | - bl"nd
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double-quoted-string-with-single-quotes.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three "'blind'" mice
4 | expected_args:
5 | - "'blind'"
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double-quoted-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three "blind" mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-double.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{double}"
3 | text: '3.141592653589793'
4 | expected_args:
5 | - 3.141592653589793
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-doubly-escaped-parenthesis.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \\(exceptionally) \\{string} mice
3 | text: three \exceptionally \"blind" mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-doubly-escaped-slash-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: 12\\/2020
3 | text: 12\
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-doubly-escaped-slash-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: 12\\/2020
3 | text: '2020'
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-escaped-parenthesis-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \(exceptionally) \{string} mice
3 | text: three (exceptionally) {string} mice
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-escaped-parenthesis-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \((exceptionally)) \{{string}} mice
3 | text: three (exceptionally) {"blind"} mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-escaped-parenthesis-3.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \((exceptionally)) \{{string}} mice
3 | text: three (exceptionally) {"blind"} mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-escaped-slash.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: 12\/2020
3 | text: 12/2020
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-float-with-integer-part.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{float}"
3 | text: '0.22'
4 | expected_args:
5 | - 0.22
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-float-without-integer-part.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{float}"
3 | text: ".22"
4 | expected_args:
5 | - 0.22
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-float.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{float}"
3 | text: '3.141593'
4 | expected_args:
5 | - 3.141593
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-int.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int}"
3 | text: '2147483647'
4 | expected_args:
5 | - 2147483647
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-long.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{long}"
3 | text: '9223372036854775807'
4 | expected_args:
5 | - 9223372036854775807
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-multiple-double-quoted-strings.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} and {string} mice
3 | text: three "blind" and "crippled" mice
4 | expected_args:
5 | - blind
6 | - crippled
7 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-multiple-single-quoted-strings.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} and {string} mice
3 | text: three 'blind' and 'crippled' mice
4 | expected_args:
5 | - blind
6 | - crippled
7 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-before-alternation-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (brown )mice/rats
3 | text: three brown mice
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-before-alternation-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (brown )mice/rats
3 | text: three rats
4 | expected_args: []
5 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-before-alternation-with-regex-characters-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: I wait {int} second(s)./second(s)?
3 | text: I wait 2 seconds?
4 | expected_args:
5 | - 2
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-before-alternation-with-regex-characters-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: I wait {int} second(s)./second(s)?
3 | text: I wait 1 second.
4 | expected_args:
5 | - 1
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-in-alternation-1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int} rat(s)/mouse/mice"
3 | text: 3 rats
4 | expected_args:
5 | - 3
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-in-alternation-2.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int} rat(s)/mouse/mice"
3 | text: 2 mice
4 | expected_args:
5 | - 2
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-optional-in-alternation-3.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int} rat(s)/mouse/mice"
3 | text: 1 mouse
4 | expected_args:
5 | - 1
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-short.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{short}"
3 | text: '32767'
4 | expected_args:
5 | - 32767
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} and {string} mice
3 | text: three '' and 'handsome' mice
4 | expected_args:
5 | - ''
6 | - handsome
7 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-single-quoted-empty-string-as-empty-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three '' mice
4 | expected_args:
5 | - ''
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-single-quoted-string-with-double-quotes.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three '"blind"' mice
4 | expected_args:
5 | - '"blind"'
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-single-quoted-string-with-escaped-single-quote.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three 'bl\'nd' mice
4 | expected_args:
5 | - bl'nd
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-single-quoted-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | text: three 'blind' mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/matches-word.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {word} mice
3 | text: three blind mice
4 | expected_args:
5 | - blind
6 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/matching/throws-unknown-parameter-type.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{unknown}"
3 | text: something
4 | exception: |-
5 | This Cucumber Expression has a problem at column 1:
6 |
7 | {unknown}
8 | ^-------^
9 | Undefined parameter type 'unknown'.
10 | Please register a ParameterType for 'unknown'
11 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-followed-by-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three blind\ rat/cat(s)
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 23
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: ALTERNATION_NODE
17 | start: 6
18 | end: 23
19 | nodes:
20 | - type: ALTERNATIVE_NODE
21 | start: 6
22 | end: 16
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 6
26 | end: 16
27 | token: blind rat
28 | - type: ALTERNATIVE_NODE
29 | start: 17
30 | end: 23
31 | nodes:
32 | - type: TEXT_NODE
33 | start: 17
34 | end: 20
35 | token: cat
36 | - type: OPTIONAL_NODE
37 | start: 20
38 | end: 23
39 | nodes:
40 | - type: TEXT_NODE
41 | start: 21
42 | end: 22
43 | token: s
44 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three hungry/blind mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 23
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: ALTERNATION_NODE
17 | start: 6
18 | end: 18
19 | nodes:
20 | - type: ALTERNATIVE_NODE
21 | start: 6
22 | end: 12
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 6
26 | end: 12
27 | token: hungry
28 | - type: ALTERNATIVE_NODE
29 | start: 13
30 | end: 18
31 | nodes:
32 | - type: TEXT_NODE
33 | start: 13
34 | end: 18
35 | token: blind
36 | - type: TEXT_NODE
37 | start: 18
38 | end: 19
39 | token: " "
40 | - type: TEXT_NODE
41 | start: 19
42 | end: 23
43 | token: mice
44 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-with-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: I select the {int}st/nd/rd/th
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 29
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 1
11 | token: I
12 | - type: TEXT_NODE
13 | start: 1
14 | end: 2
15 | token: " "
16 | - type: TEXT_NODE
17 | start: 2
18 | end: 8
19 | token: select
20 | - type: TEXT_NODE
21 | start: 8
22 | end: 9
23 | token: " "
24 | - type: TEXT_NODE
25 | start: 9
26 | end: 12
27 | token: the
28 | - type: TEXT_NODE
29 | start: 12
30 | end: 13
31 | token: " "
32 | - type: PARAMETER_NODE
33 | start: 13
34 | end: 18
35 | nodes:
36 | - type: TEXT_NODE
37 | start: 14
38 | end: 17
39 | token: int
40 | - type: ALTERNATION_NODE
41 | start: 18
42 | end: 29
43 | nodes:
44 | - type: ALTERNATIVE_NODE
45 | start: 18
46 | end: 20
47 | nodes:
48 | - type: TEXT_NODE
49 | start: 18
50 | end: 20
51 | token: st
52 | - type: ALTERNATIVE_NODE
53 | start: 21
54 | end: 23
55 | nodes:
56 | - type: TEXT_NODE
57 | start: 21
58 | end: 23
59 | token: nd
60 | - type: ALTERNATIVE_NODE
61 | start: 24
62 | end: 26
63 | nodes:
64 | - type: TEXT_NODE
65 | start: 24
66 | end: 26
67 | token: rd
68 | - type: ALTERNATIVE_NODE
69 | start: 27
70 | end: 29
71 | nodes:
72 | - type: TEXT_NODE
73 | start: 27
74 | end: 29
75 | token: th
76 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-with-unused-end-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three )blind\ mice/rats
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 23
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: ALTERNATION_NODE
17 | start: 6
18 | end: 23
19 | nodes:
20 | - type: ALTERNATIVE_NODE
21 | start: 6
22 | end: 18
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 6
26 | end: 7
27 | token: ")"
28 | - type: TEXT_NODE
29 | start: 7
30 | end: 18
31 | token: blind mice
32 | - type: ALTERNATIVE_NODE
33 | start: 19
34 | end: 23
35 | nodes:
36 | - type: TEXT_NODE
37 | start: 19
38 | end: 23
39 | token: rats
40 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-with-unused-start-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three blind\ mice/rats(
3 | exception: |-
4 | This Cucumber Expression has a problem at column 23:
5 |
6 | three blind\ mice/rats(
7 | ^
8 | The '(' does not have a matching ')'.
9 | If you did not intend to use optional text you can use '\(' to escape the optional text
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation-with-white-space.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\ three\\ hungry/blind\\ mice\\ "
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 29
7 | nodes:
8 | - type: ALTERNATION_NODE
9 | start: 0
10 | end: 29
11 | nodes:
12 | - type: ALTERNATIVE_NODE
13 | start: 0
14 | end: 15
15 | nodes:
16 | - type: TEXT_NODE
17 | start: 0
18 | end: 15
19 | token: " three hungry"
20 | - type: ALTERNATIVE_NODE
21 | start: 16
22 | end: 29
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 16
26 | end: 29
27 | token: 'blind mice '
28 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: mice/rats
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 9
7 | nodes:
8 | - type: ALTERNATION_NODE
9 | start: 0
10 | end: 9
11 | nodes:
12 | - type: ALTERNATIVE_NODE
13 | start: 0
14 | end: 4
15 | nodes:
16 | - type: TEXT_NODE
17 | start: 0
18 | end: 4
19 | token: mice
20 | - type: ALTERNATIVE_NODE
21 | start: 5
22 | end: 9
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 5
26 | end: 9
27 | token: rats
28 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/anonymous-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{}"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 2
7 | nodes:
8 | - type: PARAMETER_NODE
9 | start: 0
10 | end: 2
11 | nodes: []
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/closing-brace.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "}"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 1
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 1
11 | token: "}"
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/closing-parenthesis.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: ")"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 1
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 1
11 | token: ")"
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/empty-alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "/"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 1
7 | nodes:
8 | - type: ALTERNATION_NODE
9 | start: 0
10 | end: 1
11 | nodes:
12 | - type: ALTERNATIVE_NODE
13 | start: 0
14 | end: 0
15 | nodes: []
16 | - type: ALTERNATIVE_NODE
17 | start: 1
18 | end: 1
19 | nodes: []
20 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/empty-alternations.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "//"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 2
7 | nodes:
8 | - type: ALTERNATION_NODE
9 | start: 0
10 | end: 2
11 | nodes:
12 | - type: ALTERNATIVE_NODE
13 | start: 0
14 | end: 0
15 | nodes: []
16 | - type: ALTERNATIVE_NODE
17 | start: 1
18 | end: 1
19 | nodes: []
20 | - type: ALTERNATIVE_NODE
21 | start: 2
22 | end: 2
23 | nodes: []
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/empty-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: ''
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 0
7 | nodes: []
8 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: mice\/rats
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 10
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 10
11 | token: mice/rats
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-backslash.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\\\"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 2
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 2
11 | token: "\\"
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-opening-parenthesis.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\("
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 2
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 2
11 | token: "("
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-optional-followed-by-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \((very) blind) mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 26
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: TEXT_NODE
17 | start: 6
18 | end: 8
19 | token: "("
20 | - type: OPTIONAL_NODE
21 | start: 8
22 | end: 14
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 9
26 | end: 13
27 | token: very
28 | - type: TEXT_NODE
29 | start: 14
30 | end: 15
31 | token: " "
32 | - type: TEXT_NODE
33 | start: 15
34 | end: 20
35 | token: blind
36 | - type: TEXT_NODE
37 | start: 20
38 | end: 21
39 | token: ")"
40 | - type: TEXT_NODE
41 | start: 21
42 | end: 22
43 | token: " "
44 | - type: TEXT_NODE
45 | start: 22
46 | end: 26
47 | token: mice
48 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-optional-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three \(blind) mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 19
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: TEXT_NODE
17 | start: 6
18 | end: 13
19 | token: "(blind"
20 | - type: TEXT_NODE
21 | start: 13
22 | end: 14
23 | token: ")"
24 | - type: TEXT_NODE
25 | start: 14
26 | end: 15
27 | token: " "
28 | - type: TEXT_NODE
29 | start: 15
30 | end: 19
31 | token: mice
32 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/escaped-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\(blind)"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 8
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 7
11 | token: "(blind"
12 | - type: TEXT_NODE
13 | start: 7
14 | end: 8
15 | token: ")"
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/opening-brace.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{"
3 | exception: |-
4 | This Cucumber Expression has a problem at column 1:
5 |
6 | {
7 | ^
8 | The '{' does not have a matching '}'.
9 | If you did not intend to use a parameter you can use '\{' to escape the a parameter
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/opening-parenthesis.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "("
3 | exception: |-
4 | This Cucumber Expression has a problem at column 1:
5 |
6 | (
7 | ^
8 | The '(' does not have a matching ')'.
9 | If you did not intend to use optional text you can use '\(' to escape the optional text
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/optional-containing-nested-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three ((very) blind) mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 25
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: OPTIONAL_NODE
17 | start: 6
18 | end: 20
19 | nodes:
20 | - type: OPTIONAL_NODE
21 | start: 7
22 | end: 13
23 | nodes:
24 | - type: TEXT_NODE
25 | start: 8
26 | end: 12
27 | token: very
28 | - type: TEXT_NODE
29 | start: 13
30 | end: 14
31 | token: " "
32 | - type: TEXT_NODE
33 | start: 14
34 | end: 19
35 | token: blind
36 | - type: TEXT_NODE
37 | start: 20
38 | end: 21
39 | token: " "
40 | - type: TEXT_NODE
41 | start: 21
42 | end: 25
43 | token: mice
44 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/optional-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (blind) mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 18
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: OPTIONAL_NODE
17 | start: 6
18 | end: 13
19 | nodes:
20 | - type: TEXT_NODE
21 | start: 7
22 | end: 12
23 | token: blind
24 | - type: TEXT_NODE
25 | start: 13
26 | end: 14
27 | token: " "
28 | - type: TEXT_NODE
29 | start: 14
30 | end: 18
31 | token: mice
32 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "(blind)"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 7
7 | nodes:
8 | - type: OPTIONAL_NODE
9 | start: 0
10 | end: 7
11 | nodes:
12 | - type: TEXT_NODE
13 | start: 1
14 | end: 6
15 | token: blind
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{string}"
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 8
7 | nodes:
8 | - type: PARAMETER_NODE
9 | start: 0
10 | end: 8
11 | nodes:
12 | - type: TEXT_NODE
13 | start: 1
14 | end: 7
15 | token: string
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three blind mice
3 | expected_ast:
4 | type: EXPRESSION_NODE
5 | start: 0
6 | end: 16
7 | nodes:
8 | - type: TEXT_NODE
9 | start: 0
10 | end: 5
11 | token: three
12 | - type: TEXT_NODE
13 | start: 5
14 | end: 6
15 | token: " "
16 | - type: TEXT_NODE
17 | start: 6
18 | end: 11
19 | token: blind
20 | - type: TEXT_NODE
21 | start: 11
22 | end: 12
23 | token: " "
24 | - type: TEXT_NODE
25 | start: 12
26 | end: 16
27 | token: mice
28 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/parser/unfinished-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{string"
3 | exception: |-
4 | This Cucumber Expression has a problem at column 1:
5 |
6 | {string
7 | ^
8 | The '{' does not have a matching '}'.
9 | If you did not intend to use a parameter you can use '\{' to escape the a parameter
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/alternation-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three blind/cripple mice
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 5
11 | text: three
12 | - type: WHITE_SPACE
13 | start: 5
14 | end: 6
15 | text: " "
16 | - type: TEXT
17 | start: 6
18 | end: 11
19 | text: blind
20 | - type: ALTERNATION
21 | start: 11
22 | end: 12
23 | text: "/"
24 | - type: TEXT
25 | start: 12
26 | end: 19
27 | text: cripple
28 | - type: WHITE_SPACE
29 | start: 19
30 | end: 20
31 | text: " "
32 | - type: TEXT
33 | start: 20
34 | end: 24
35 | text: mice
36 | - type: END_OF_LINE
37 | start: 24
38 | end: 24
39 | text: ''
40 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: blind/cripple
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 5
11 | text: blind
12 | - type: ALTERNATION
13 | start: 5
14 | end: 6
15 | text: "/"
16 | - type: TEXT
17 | start: 6
18 | end: 13
19 | text: cripple
20 | - type: END_OF_LINE
21 | start: 13
22 | end: 13
23 | text: ''
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/empty-string.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: ''
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: END_OF_LINE
9 | start: 0
10 | end: 0
11 | text: ''
12 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escape-non-reserved-character.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\["
3 | exception: |-
4 | This Cucumber Expression has a problem at column 2:
5 |
6 | \[
7 | ^
8 | Only the characters '{', '}', '(', ')', '\', '/' and whitespace can be escaped.
9 | If you did mean to use an '\' you can use '\\' to escape it
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: blind\ and\ famished\/cripple mice
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 29
11 | text: blind and famished/cripple
12 | - type: WHITE_SPACE
13 | start: 29
14 | end: 30
15 | text: " "
16 | - type: TEXT
17 | start: 30
18 | end: 34
19 | text: mice
20 | - type: END_OF_LINE
21 | start: 34
22 | end: 34
23 | text: ''
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-char-has-start-index-of-text-token.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: " \\/ "
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: WHITE_SPACE
9 | start: 0
10 | end: 1
11 | text: " "
12 | - type: TEXT
13 | start: 1
14 | end: 3
15 | text: "/"
16 | - type: WHITE_SPACE
17 | start: 3
18 | end: 4
19 | text: " "
20 | - type: END_OF_LINE
21 | start: 4
22 | end: 4
23 | text: ''
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-end-of-line.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\"
3 | exception: |-
4 | This Cucumber Expression has a problem at column 1:
5 |
6 | \
7 | ^
8 | The end of line can not be escaped.
9 | You can use '\\' to escape the '\'
10 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\(blind\\)"
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 9
11 | text: "(blind)"
12 | - type: END_OF_LINE
13 | start: 9
14 | end: 9
15 | text: ''
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\{string\\}"
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 10
11 | text: "{string}"
12 | - type: END_OF_LINE
13 | start: 10
14 | end: 10
15 | text: ''
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/escaped-space.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "\\ "
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 2
11 | text: " "
12 | - type: END_OF_LINE
13 | start: 2
14 | end: 2
15 | text: ''
16 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/optional-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three (blind) mice
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 5
11 | text: three
12 | - type: WHITE_SPACE
13 | start: 5
14 | end: 6
15 | text: " "
16 | - type: BEGIN_OPTIONAL
17 | start: 6
18 | end: 7
19 | text: "("
20 | - type: TEXT
21 | start: 7
22 | end: 12
23 | text: blind
24 | - type: END_OPTIONAL
25 | start: 12
26 | end: 13
27 | text: ")"
28 | - type: WHITE_SPACE
29 | start: 13
30 | end: 14
31 | text: " "
32 | - type: TEXT
33 | start: 14
34 | end: 18
35 | text: mice
36 | - type: END_OF_LINE
37 | start: 18
38 | end: 18
39 | text: ''
40 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "(blind)"
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: BEGIN_OPTIONAL
9 | start: 0
10 | end: 1
11 | text: "("
12 | - type: TEXT
13 | start: 1
14 | end: 6
15 | text: blind
16 | - type: END_OPTIONAL
17 | start: 6
18 | end: 7
19 | text: ")"
20 | - type: END_OF_LINE
21 | start: 7
22 | end: 7
23 | text: ''
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/parameter-phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three {string} mice
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 5
11 | text: three
12 | - type: WHITE_SPACE
13 | start: 5
14 | end: 6
15 | text: " "
16 | - type: BEGIN_PARAMETER
17 | start: 6
18 | end: 7
19 | text: "{"
20 | - type: TEXT
21 | start: 7
22 | end: 13
23 | text: string
24 | - type: END_PARAMETER
25 | start: 13
26 | end: 14
27 | text: "}"
28 | - type: WHITE_SPACE
29 | start: 14
30 | end: 15
31 | text: " "
32 | - type: TEXT
33 | start: 15
34 | end: 19
35 | text: mice
36 | - type: END_OF_LINE
37 | start: 19
38 | end: 19
39 | text: ''
40 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{string}"
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: BEGIN_PARAMETER
9 | start: 0
10 | end: 1
11 | text: "{"
12 | - type: TEXT
13 | start: 1
14 | end: 7
15 | text: string
16 | - type: END_PARAMETER
17 | start: 7
18 | end: 8
19 | text: "}"
20 | - type: END_OF_LINE
21 | start: 8
22 | end: 8
23 | text: ''
24 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/tokenizer/phrase.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: three blind mice
3 | expected_tokens:
4 | - type: START_OF_LINE
5 | start: 0
6 | end: 0
7 | text: ''
8 | - type: TEXT
9 | start: 0
10 | end: 5
11 | text: three
12 | - type: WHITE_SPACE
13 | start: 5
14 | end: 6
15 | text: " "
16 | - type: TEXT
17 | start: 6
18 | end: 11
19 | text: blind
20 | - type: WHITE_SPACE
21 | start: 11
22 | end: 12
23 | text: " "
24 | - type: TEXT
25 | start: 12
26 | end: 16
27 | text: mice
28 | - type: END_OF_LINE
29 | start: 16
30 | end: 16
31 | text: ''
32 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/alternation-with-optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: a/b(c)
3 | expected_regex: "^(?:a|b(?:c)?)$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/alternation.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: a/b c/d/e
3 | expected_regex: "^(?:a|b) (?:c|d|e)$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/empty.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: ''
3 | expected_regex: "^$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/escape-regex-characters.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "^$[]\\(\\){}\\\\.|?*+"
3 | expected_regex: "^\\^\\$\\[\\]\\(\\)(.*)\\\\\\.\\|\\?\\*\\+$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/optional.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "(a)"
3 | expected_regex: "^(?:a)?$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/parameter.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "{int}"
3 | expected_regex: "^((?:-?\\d+)|(?:\\d+))$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/text.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: a
3 | expected_regex: "^a$"
4 |
--------------------------------------------------------------------------------
/testdata/cucumber-expression/transformation/unicode.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: Привет, Мир(ы)!
3 | expected_regex: "^Привет, Мир(?:ы)?!$"
4 |
--------------------------------------------------------------------------------
/testdata/regular-expression/matching/optional-capture-groups-all.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "^a (b )?c (d )?e (f )?g$"
3 | text: a b c d e f g
4 | expected_args:
5 | - 'b '
6 | - 'd '
7 | - 'f '
8 |
--------------------------------------------------------------------------------
/testdata/regular-expression/matching/optional-capture-groups-issue.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "^I should( not)? be on the map$"
3 | text: I should be on the map
4 | expected_args: [null]
5 |
--------------------------------------------------------------------------------
/testdata/regular-expression/matching/optional-capture-groups-some.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | expression: "^a (b )?c (d )?e (f )?g$"
3 | text: a b c e f g
4 | expected_args:
5 | - 'b '
6 | -
7 | - 'f '
8 |
--------------------------------------------------------------------------------